From 1e3480bc1f563bfe77092ef5835e1ddafe809fbf Mon Sep 17 00:00:00 2001 From: Zmax0 Date: Thu, 15 Aug 2024 10:52:57 +0800 Subject: [PATCH] feat(gui): lazy loading `TrafficCounterLineChart` --- .../client/gui/console/Console.java | 24 ++- .../TrafficCounterLineChartBackstage.java | 137 ++++++++++++++++++ .../TrafficCounterLineChartBuilder.java | 118 --------------- 3 files changed, 158 insertions(+), 121 deletions(-) create mode 100644 urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChartBackstage.java delete mode 100644 urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChartBuilder.java diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/console/Console.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/console/Console.java index cfa5f27..3e98f5a 100644 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/console/Console.java +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/console/Console.java @@ -9,7 +9,7 @@ import com.urbanspork.client.gui.Resource; import com.urbanspork.client.gui.console.widget.*; import com.urbanspork.client.gui.i18n.I18N; -import com.urbanspork.client.gui.traffic.TrafficCounterLineChartBuilder; +import com.urbanspork.client.gui.traffic.TrafficCounterLineChartBackstage; import com.urbanspork.client.gui.tray.Tray; import com.urbanspork.common.codec.CipherKind; import com.urbanspork.common.config.ClientConfig; @@ -24,6 +24,7 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableObjectValue; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -46,6 +47,7 @@ import java.net.URI; import java.util.Arrays; import java.util.List; +import java.util.Optional; public class Console extends Application { private static final Logger logger = LoggerFactory.getLogger(Console.class); @@ -55,9 +57,11 @@ public class Console extends Application { Tray tray; Proxy proxy; final ObjectProperty trafficCounter = new SimpleObjectProperty<>(); + final TrafficCounterLineChartBackstage trafficCounterLineChartBackstage = new TrafficCounterLineChartBackstage(trafficCounter); private Stage primaryStage; private JFXTabPane root; + private Tab tab2; private TextArea logTextArea; private JFXListView serverConfigJFXListView; private Button newServerConfigButton; @@ -97,6 +101,7 @@ public void start(Stage primaryStage) { primaryStage.setTitle(I18N.getString(I18N.PROGRAM_TITLE)); primaryStage.setOnCloseRequest(event -> primaryStage.hide()); primaryStage.hide(); + initTrafficCounterLineChart(); launchProxy(); } @@ -304,7 +309,7 @@ private JFXTabPane initTabPane() { // tab1 Tab tab1 = newSingleNodeTab(logTextArea, I18N.getString(I18N.CONSOLE_TAB1_TEXT)); // tab2 - Tab tab2 = initTrafficTab(); + tab2 = initTrafficTab(); // ==================== // main tab pane // ==================== @@ -349,7 +354,6 @@ private Tab newSingleNodeTab(Node node, String tabTitle) { private Tab initTrafficTab() { StackPane stackPane = new StackPane(); stackPane.setAlignment(Pos.TOP_CENTER); - stackPane.getChildren().add(new TrafficCounterLineChartBuilder(trafficCounter).build()); Tab tab = new Tab(I18N.getString(I18N.CONSOLE_TAB2_TEXT)); tab.setContent(stackPane); tab.setClosable(false); @@ -546,6 +550,20 @@ private void importServerConfig() { dialog.showAndWait().map(URI::create).flatMap(ShareableServerConfig::fromUri).ifPresent(serverConfigObservableList::add); } + private void initTrafficCounterLineChart() { + ObservableList children = ((StackPane) tab2.getContent()).getChildren(); + primaryStage.setOnHidden(event -> { + children.clear(); + trafficCounterLineChartBackstage.stop(); + }); + primaryStage.setOnShown(event -> { + if (children.isEmpty()) { + children.add(trafficCounterLineChartBackstage.newLineChart()); + } + Optional.of(trafficCounter).map(ObservableObjectValue::get).ifPresent(trafficCounterLineChartBackstage::refresh); + }); + } + private boolean validate() { boolean result = currentConfigHostTextField.validate(); result |= currentConfigPortTextField.validate(); diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChartBackstage.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChartBackstage.java new file mode 100644 index 0000000..f77d27d --- /dev/null +++ b/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChartBackstage.java @@ -0,0 +1,137 @@ +package com.urbanspork.client.gui.traffic; + +import com.urbanspork.client.gui.spine.CatmullRom; +import io.netty.handler.traffic.TrafficCounter; +import javafx.animation.Animation; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.beans.property.ObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Point2D; +import javafx.scene.chart.Axis; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; +import javafx.util.Duration; + +import java.util.ArrayList; + +public class TrafficCounterLineChartBackstage { + private static final int WINDOW = 60; + private final Timeline timeline = new Timeline(); + private final ObservableList> write = FXCollections.observableArrayList(new ArrayList<>()); + private final ObservableList> read = FXCollections.observableArrayList(new ArrayList<>()); + private XYChart.Series writeSeries; + private XYChart.Series readSeries; + + public TrafficCounterLineChartBackstage(ObjectProperty trafficCounter) { + timeline.setCycleCount(Timeline.INDEFINITE); + trafficCounter.addListener((observable, oldValue, newValue) -> { + if (newValue != null && timeline.getStatus() == Animation.Status.RUNNING) { + refresh(newValue); + } + }); + } + + public LineChart newLineChart() { + NumberAxis xAxis = new NumberAxis(0, WINDOW, WINDOW); + NumberAxis yAxis = new NumberAxis(); + yAxis.setTickLabelsVisible(false); + yAxis.setTickMarkVisible(false); + yAxis.lookup(".axis-minor-tick-mark").setVisible(false); + LineChart lineChart = new TrafficCounterLineChart(xAxis, yAxis); + lineChart.setAnimated(false); + lineChart.setCreateSymbols(false); + ObservableList> list = lineChart.getData(); + write.clear(); + read.clear(); + for (int i = 0; i <= WINDOW; i++) { + write.add(new XYChart.Data<>(i, 0)); + read.add(new XYChart.Data<>(i, 0)); + } + writeSeries = new XYChart.Series<>(write); + readSeries = new XYChart.Series<>(read); + writeSeries.setName("0 KB/s"); + readSeries.setName("0 KB/s"); + list.add(writeSeries); + list.add(readSeries); + return lineChart; + } + + public void refresh(TrafficCounter counter) { + ObservableList keyFrames = timeline.getKeyFrames(); + keyFrames.clear(); + keyFrames.add(new KeyFrame(Duration.millis(counter.checkInterval()), event -> { + long writeBytes = counter.lastWrittenBytes() / 1024; + long readBytes = counter.lastReadBytes() / 1024; + writeSeries.setName(writeBytes + " KB/s"); + readSeries.setName(readBytes + " KB/s"); + scroll(write, writeBytes); + scroll(read, readBytes); + })); + timeline.playFromStart(); + } + + public void stop() { + timeline.stop(); + } + + private void scroll(ObservableList> dataList, Number y) { + for (int i = 0; i < dataList.size() - 1; i++) { + dataList.get(i).setYValue(dataList.get(i + 1).getYValue()); + } + dataList.getLast().setYValue(y); + } + + private static class TrafficCounterLineChart extends LineChart { + public TrafficCounterLineChart(Axis xAxis, Axis yAxis) { + super(xAxis, yAxis); + } + + @Override + protected void layoutPlotChildren() { + super.layoutPlotChildren(); + ObservableList> data = getData(); + data.stream().map(Series::getNode).mapMulti((node, consumer) -> { + if (node instanceof Path path) { + consumer.accept(path); + } + }).forEach(this::curve); + } + + private void curve(Path path) { + ObservableList elements = path.getElements(); + if (elements.size() > 3) { + curve(elements); + } + } + + private void curve(ObservableList elements) { + int size = elements.size(); + Point2D[] points = new Point2D[size - 1]; + for (int i = 0; i < points.length; i++) { + PathElement e = elements.get(i + 1); + if (e instanceof LineTo lineTo) { + points[i] = new Point2D(lineTo.getX(), lineTo.getY()); + } + } + Point2D[] interpolate = new CatmullRom(0.5).interpolate(points, 32); + // update + for (int i = 1; i < size; i++) { + PathElement e = elements.get(i); + if (e instanceof LineTo lineTo) { + lineTo.setX(interpolate[i - 1].getX()); + lineTo.setY(interpolate[i - 1].getY()); + } + } + // add + for (int i = 0; i < interpolate.length - size; i++) { + elements.add(new LineTo(interpolate[i + size].getX(), interpolate[i + size].getY())); + } + } + } +} diff --git a/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChartBuilder.java b/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChartBuilder.java deleted file mode 100644 index d8a0986..0000000 --- a/urban-spork-client-gui/src/com/urbanspork/client/gui/traffic/TrafficCounterLineChartBuilder.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.urbanspork.client.gui.traffic; - -import com.urbanspork.client.gui.spine.CatmullRom; -import io.netty.handler.traffic.TrafficCounter; -import javafx.animation.Animation; -import javafx.animation.KeyFrame; -import javafx.animation.Timeline; -import javafx.beans.property.ObjectProperty; -import javafx.collections.ObservableList; -import javafx.geometry.Point2D; -import javafx.scene.chart.LineChart; -import javafx.scene.chart.NumberAxis; -import javafx.scene.chart.XYChart; -import javafx.scene.shape.LineTo; -import javafx.scene.shape.Path; -import javafx.scene.shape.PathElement; -import javafx.util.Duration; - -public class TrafficCounterLineChartBuilder { - private static final int WINDOW = 60; - - private final XYChart.Series write = new XYChart.Series<>(); - private final XYChart.Series read = new XYChart.Series<>(); - private final Timeline timeline = new Timeline(); - - public TrafficCounterLineChartBuilder(ObjectProperty counter) { - counter.addListener((observable, oldValue, newValue) -> refresh(newValue)); - timeline.setCycleCount(Animation.INDEFINITE); - } - - public LineChart build() { - NumberAxis xAxis = new NumberAxis(0, WINDOW, WINDOW); - NumberAxis yAxis = new NumberAxis(); - yAxis.setTickLabelsVisible(false); - yAxis.setTickMarkVisible(false); - yAxis.lookup(".axis-minor-tick-mark").setVisible(false); - LineChart lineChart = new LineChart<>(xAxis, yAxis) { - @Override - protected void layoutPlotChildren() { - super.layoutPlotChildren(); - ObservableList> data = getData(); - data.stream().map(Series::getNode).mapMulti((node, consumer) -> { - if (node instanceof Path path) { - consumer.accept(path); - } - }).forEach(this::curve); - } - - private void curve(Path path) { - ObservableList elements = path.getElements(); - if (elements.size() > 3) { - curve(elements); - } - } - - private void curve(ObservableList elements) { - int size = elements.size(); - Point2D[] points = new Point2D[size - 1]; - for (int i = 0; i < points.length; i++) { - PathElement e = elements.get(i + 1); - if (e instanceof LineTo lineTo) { - points[i] = new Point2D(lineTo.getX(), lineTo.getY()); - } - } - Point2D[] interpolate = new CatmullRom(0.5).interpolate(points, 32); - // update - for (int i = 1; i < size; i++) { - PathElement e = elements.get(i); - if (e instanceof LineTo lineTo) { - lineTo.setX(interpolate[i - 1].getX()); - lineTo.setY(interpolate[i - 1].getY()); - } - } - // add - for (int i = 0; i < interpolate.length - size; i++) { - elements.add(new LineTo(interpolate[i + size].getX(), interpolate[i + size].getY())); - } - } - }; - lineChart.setAnimated(false); - lineChart.setCreateSymbols(false); - ObservableList> data = lineChart.getData(); - write.setName("0 KB/s"); - read.setName("0 KB/s"); - ObservableList> writeData = write.getData(); - ObservableList> readData = read.getData(); - for (int i = 0; i <= WINDOW; i++) { - writeData.add(new XYChart.Data<>(i, 0)); - readData.add(new XYChart.Data<>(i, 0)); - } - data.add(write); - data.add(read); - return lineChart; - } - - private void refresh(TrafficCounter counter) { - ObservableList keyFrames = timeline.getKeyFrames(); - keyFrames.clear(); - keyFrames.add(new KeyFrame(Duration.millis(counter.checkInterval()), event -> { - ObservableList> writeData = write.getData(); - ObservableList> readData = read.getData(); - long writeBytes = counter.lastWrittenBytes() / 1024; - long readBytes = counter.lastReadBytes() / 1024; - write.setName(writeBytes + " KB/s"); - read.setName(readBytes + " KB/s"); - scroll(writeData, writeBytes); - scroll(readData, readBytes); - })); - timeline.playFromStart(); - } - - private void scroll(ObservableList> dataList, Number y) { - for (int i = 0; i < dataList.size() - 1; i++) { - dataList.get(i).setYValue(dataList.get(i + 1).getYValue()); - } - dataList.getLast().setYValue(y); - } -}