new View
@ -1,12 +1,10 @@
|
|||||||
package vassistent.ui;
|
package vassistent.ui;
|
||||||
|
|
||||||
import vassistent.bootstrap.ApplicationContext;
|
|
||||||
import vassistent.controller.DashboardController;
|
|
||||||
import vassistent.util.ConfigLoader;
|
import vassistent.util.ConfigLoader;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.Border;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,6 +12,9 @@ import java.util.Properties;
|
|||||||
*/
|
*/
|
||||||
public class AppWindow extends JFrame {
|
public class AppWindow extends JFrame {
|
||||||
|
|
||||||
|
private static final int EXPANDED_DASHBOARD_WIDTH = 460;
|
||||||
|
private static final int EXPANDED_DIVIDER_SIZE = 8;
|
||||||
|
|
||||||
private static final Properties config =
|
private static final Properties config =
|
||||||
ConfigLoader.loadProperties(
|
ConfigLoader.loadProperties(
|
||||||
"config/application.properties"
|
"config/application.properties"
|
||||||
@ -22,6 +23,9 @@ public class AppWindow extends JFrame {
|
|||||||
private PixelStreamingView streamingView;
|
private PixelStreamingView streamingView;
|
||||||
private DashboardView dashboardView;
|
private DashboardView dashboardView;
|
||||||
private JTabbedPane tabs;
|
private JTabbedPane tabs;
|
||||||
|
private JSplitPane driveSplitPane;
|
||||||
|
private JPanel dashboardCard;
|
||||||
|
private JToggleButton dashboardToggleButton;
|
||||||
|
|
||||||
private JLabel mqttStatusLabel;
|
private JLabel mqttStatusLabel;
|
||||||
private JLabel problemLevelLabel;
|
private JLabel problemLevelLabel;
|
||||||
@ -33,10 +37,12 @@ public class AppWindow extends JFrame {
|
|||||||
|
|
||||||
setTitle("Virtueller Gesundheitsassistent");
|
setTitle("Virtueller Gesundheitsassistent");
|
||||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
setSize(1400, 850);
|
setMinimumSize(new Dimension(1280, 720));
|
||||||
|
setSize(1700, 900);
|
||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
|
getContentPane().setBackground(new Color(15, 19, 26));
|
||||||
|
|
||||||
setLayout(new BorderLayout(10,10));
|
setLayout(new BorderLayout(12, 12));
|
||||||
|
|
||||||
String streamUrl =
|
String streamUrl =
|
||||||
normalizeConfigValue(
|
normalizeConfigValue(
|
||||||
@ -51,11 +57,13 @@ public class AppWindow extends JFrame {
|
|||||||
|
|
||||||
dashboardView = new DashboardView();
|
dashboardView = new DashboardView();
|
||||||
|
|
||||||
|
add(createHeaderPanel(), BorderLayout.NORTH);
|
||||||
tabs = createTabPane();
|
tabs = createTabPane();
|
||||||
add(tabs, BorderLayout.CENTER);
|
add(tabs, BorderLayout.CENTER);
|
||||||
add(createStatusBar(), BorderLayout.SOUTH);
|
add(createStatusBar(), BorderLayout.SOUTH);
|
||||||
|
SwingUtilities.invokeLater(() -> setDashboardVisible(false));
|
||||||
|
|
||||||
Font uiFont = new Font("Segoe UI", Font.PLAIN, 14);
|
Font uiFont = new Font("Segoe UI", Font.PLAIN, 16);
|
||||||
UIManager.put("Label.font", uiFont);
|
UIManager.put("Label.font", uiFont);
|
||||||
UIManager.put("TabbedPane.font", uiFont);
|
UIManager.put("TabbedPane.font", uiFont);
|
||||||
}
|
}
|
||||||
@ -68,17 +76,96 @@ public class AppWindow extends JFrame {
|
|||||||
private JTabbedPane createTabPane() {
|
private JTabbedPane createTabPane() {
|
||||||
|
|
||||||
JTabbedPane tabs = new JTabbedPane();
|
JTabbedPane tabs = new JTabbedPane();
|
||||||
tabs.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
|
tabs.setBorder(BorderFactory.createEmptyBorder(0, 10, 6, 10));
|
||||||
|
tabs.setFocusable(false);
|
||||||
JPanel streamingPanel = new JPanel(new BorderLayout(10,10));
|
tabs.putClientProperty("JTabbedPane.tabHeight", 38);
|
||||||
streamingPanel.add(streamingView, BorderLayout.CENTER);
|
tabs.putClientProperty("JTabbedPane.hideTabAreaWithOneTab", true);
|
||||||
|
tabs.addTab("Drive View", createDriveViewPanel());
|
||||||
tabs.addTab("Avatar Streaming", streamingView);
|
|
||||||
tabs.addTab("Dashboard", dashboardView);
|
|
||||||
|
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the center-display optimized drive panel.
|
||||||
|
*
|
||||||
|
* @return drive panel with large assistant stream and compact dashboard
|
||||||
|
*/
|
||||||
|
private JPanel createDriveViewPanel() {
|
||||||
|
JPanel drivePanel = new JPanel(new BorderLayout());
|
||||||
|
drivePanel.setOpaque(false);
|
||||||
|
drivePanel.setBorder(BorderFactory.createEmptyBorder(6, 0, 0, 0));
|
||||||
|
|
||||||
|
JPanel streamCard = createCardPanel();
|
||||||
|
streamCard.setLayout(new BorderLayout(0, 12));
|
||||||
|
streamCard.add(
|
||||||
|
createSectionHeader(
|
||||||
|
"Virtual Health Assistant",
|
||||||
|
null
|
||||||
|
),
|
||||||
|
BorderLayout.NORTH
|
||||||
|
);
|
||||||
|
streamCard.add(streamingView, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
dashboardCard = createCardPanel();
|
||||||
|
dashboardCard.setLayout(new BorderLayout(0, 12));
|
||||||
|
dashboardCard.add(
|
||||||
|
createSectionHeader(
|
||||||
|
"Health Overview",
|
||||||
|
"Live trend and current risk level"
|
||||||
|
),
|
||||||
|
BorderLayout.NORTH
|
||||||
|
);
|
||||||
|
dashboardCard.add(dashboardView, BorderLayout.CENTER);
|
||||||
|
dashboardCard.setPreferredSize(new Dimension(EXPANDED_DASHBOARD_WIDTH, 0));
|
||||||
|
dashboardCard.setMinimumSize(new Dimension(380, 0));
|
||||||
|
|
||||||
|
driveSplitPane =
|
||||||
|
new JSplitPane(
|
||||||
|
JSplitPane.HORIZONTAL_SPLIT,
|
||||||
|
streamCard,
|
||||||
|
dashboardCard
|
||||||
|
);
|
||||||
|
|
||||||
|
driveSplitPane.setBorder(null);
|
||||||
|
driveSplitPane.setOpaque(false);
|
||||||
|
driveSplitPane.setContinuousLayout(true);
|
||||||
|
driveSplitPane.setOneTouchExpandable(false);
|
||||||
|
driveSplitPane.setDividerSize(EXPANDED_DIVIDER_SIZE);
|
||||||
|
driveSplitPane.setResizeWeight(0.72);
|
||||||
|
driveSplitPane.setDividerLocation(0.72);
|
||||||
|
|
||||||
|
drivePanel.add(driveSplitPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
return drivePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a top banner with project title and context.
|
||||||
|
*
|
||||||
|
* @return header panel
|
||||||
|
*/
|
||||||
|
private JPanel createHeaderPanel() {
|
||||||
|
JPanel header = new JPanel(new BorderLayout(12, 0));
|
||||||
|
header.setBorder(BorderFactory.createEmptyBorder(12, 16, 0, 16));
|
||||||
|
header.setOpaque(false);
|
||||||
|
|
||||||
|
JLabel title = new JLabel("Virtueller Gesundheitsassistent");
|
||||||
|
title.setFont(new Font("Segoe UI Semibold", Font.PLAIN, 30));
|
||||||
|
title.setForeground(new Color(238, 244, 255));
|
||||||
|
|
||||||
|
JPanel textStack = new JPanel();
|
||||||
|
textStack.setLayout(new BoxLayout(textStack, BoxLayout.Y_AXIS));
|
||||||
|
textStack.setOpaque(false);
|
||||||
|
title.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
textStack.add(title);
|
||||||
|
textStack.add(Box.createVerticalStrut(4));
|
||||||
|
|
||||||
|
header.add(textStack, BorderLayout.WEST);
|
||||||
|
header.add(createDashboardToggleButton(), BorderLayout.EAST);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the status bar with MQTT and problem-level indicators.
|
* Creates the status bar with MQTT and problem-level indicators.
|
||||||
*
|
*
|
||||||
@ -86,18 +173,18 @@ public class AppWindow extends JFrame {
|
|||||||
*/
|
*/
|
||||||
private JPanel createStatusBar() {
|
private JPanel createStatusBar() {
|
||||||
|
|
||||||
JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT,15,5));
|
JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 12, 8));
|
||||||
|
statusBar.setBackground(new Color(13, 16, 23));
|
||||||
|
|
||||||
statusBar.setBorder(BorderFactory.createCompoundBorder(
|
statusBar.setBorder(BorderFactory.createCompoundBorder(
|
||||||
BorderFactory.createMatteBorder(1,0,0,0, Color.LIGHT_GRAY),
|
BorderFactory.createMatteBorder(1, 0, 0, 0, new Color(54, 64, 78)),
|
||||||
BorderFactory.createEmptyBorder(5,10,5,10)
|
BorderFactory.createEmptyBorder(6, 12, 8, 12)
|
||||||
));
|
));
|
||||||
|
|
||||||
mqttStatusLabel = new JLabel("MQTT: Disconnected");
|
mqttStatusLabel = createStatusChip("MQTT: Disconnected");
|
||||||
problemLevelLabel = new JLabel("Problem: NONE");
|
problemLevelLabel = createStatusChip("Problem: NONE");
|
||||||
|
|
||||||
statusBar.add(mqttStatusLabel);
|
statusBar.add(mqttStatusLabel);
|
||||||
statusBar.add(new JLabel(" | "));
|
|
||||||
statusBar.add(problemLevelLabel);
|
statusBar.add(problemLevelLabel);
|
||||||
|
|
||||||
return statusBar;
|
return statusBar;
|
||||||
@ -109,8 +196,11 @@ public class AppWindow extends JFrame {
|
|||||||
* @param connected whether MQTT is currently connected
|
* @param connected whether MQTT is currently connected
|
||||||
*/
|
*/
|
||||||
public void updateMqttStatus(boolean connected) {
|
public void updateMqttStatus(boolean connected) {
|
||||||
mqttStatusLabel.setText("MQTT: " +
|
mqttStatusLabel.setText("MQTT: "
|
||||||
(connected ? "Connected" : "Disconnected"));
|
+ (connected ? "Connected" : "Disconnected"));
|
||||||
|
mqttStatusLabel.setBackground(
|
||||||
|
connected ? new Color(30, 101, 70) : new Color(108, 47, 47)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,6 +210,7 @@ public class AppWindow extends JFrame {
|
|||||||
*/
|
*/
|
||||||
public void updateProblemLevel(String level) {
|
public void updateProblemLevel(String level) {
|
||||||
problemLevelLabel.setText("Problem: " + level);
|
problemLevelLabel.setText("Problem: " + level);
|
||||||
|
problemLevelLabel.setBackground(getProblemChipColor(level));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,6 +240,160 @@ public class AppWindow extends JFrame {
|
|||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a panel with a subtle border and dark background.
|
||||||
|
*
|
||||||
|
* @return styled card panel
|
||||||
|
*/
|
||||||
|
private JPanel createCardPanel() {
|
||||||
|
JPanel card = new JPanel();
|
||||||
|
card.setOpaque(true);
|
||||||
|
card.setBackground(new Color(24, 31, 41));
|
||||||
|
card.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createLineBorder(new Color(61, 74, 92)),
|
||||||
|
BorderFactory.createEmptyBorder(12, 12, 12, 12)
|
||||||
|
));
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a section heading with title and subtitle text.
|
||||||
|
*
|
||||||
|
* @param title section title
|
||||||
|
* @param subtitle section subtitle
|
||||||
|
* @return heading panel
|
||||||
|
*/
|
||||||
|
private JPanel createSectionHeader(String title, String subtitle) {
|
||||||
|
JPanel panel = new JPanel();
|
||||||
|
panel.setOpaque(false);
|
||||||
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||||
|
|
||||||
|
JLabel titleLabel = new JLabel(title);
|
||||||
|
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
titleLabel.setFont(new Font("Segoe UI Semibold", Font.PLAIN, 20));
|
||||||
|
titleLabel.setForeground(new Color(233, 241, 255));
|
||||||
|
|
||||||
|
JLabel subtitleLabel = new JLabel(subtitle);
|
||||||
|
subtitleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
subtitleLabel.setFont(new Font("Segoe UI", Font.PLAIN, 13));
|
||||||
|
subtitleLabel.setForeground(new Color(143, 159, 182));
|
||||||
|
|
||||||
|
panel.add(titleLabel);
|
||||||
|
panel.add(Box.createVerticalStrut(2));
|
||||||
|
panel.add(subtitleLabel);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates one status label used in the bottom status bar.
|
||||||
|
*
|
||||||
|
* @param text initial status text
|
||||||
|
* @return styled status label
|
||||||
|
*/
|
||||||
|
private JLabel createStatusChip(String text) {
|
||||||
|
JLabel label = new JLabel(text);
|
||||||
|
label.setOpaque(true);
|
||||||
|
label.setFont(new Font("Segoe UI Semibold", Font.PLAIN, 14));
|
||||||
|
label.setForeground(new Color(237, 244, 255));
|
||||||
|
label.setBackground(new Color(56, 66, 83));
|
||||||
|
label.setBorder(BorderFactory.createEmptyBorder(5, 12, 5, 12));
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a status chip background color for the current problem level.
|
||||||
|
*
|
||||||
|
* @param level problem level name
|
||||||
|
* @return matching background color
|
||||||
|
*/
|
||||||
|
private Color getProblemChipColor(String level) {
|
||||||
|
if (level == null) {
|
||||||
|
return new Color(56, 66, 83);
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = level.trim().toUpperCase(Locale.ROOT);
|
||||||
|
|
||||||
|
switch (normalized) {
|
||||||
|
case "WARNING":
|
||||||
|
return new Color(134, 104, 29);
|
||||||
|
case "HIGH":
|
||||||
|
return new Color(168, 92, 31);
|
||||||
|
case "DISASTER":
|
||||||
|
return new Color(140, 49, 49);
|
||||||
|
default:
|
||||||
|
return new Color(46, 99, 61);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the button used to toggle dashboard visibility.
|
||||||
|
*
|
||||||
|
* @return configured toggle button
|
||||||
|
*/
|
||||||
|
private JToggleButton createDashboardToggleButton() {
|
||||||
|
dashboardToggleButton = new JToggleButton("Dashboard einblenden");
|
||||||
|
dashboardToggleButton.setFocusable(false);
|
||||||
|
dashboardToggleButton.setFont(new Font("Segoe UI Semibold", Font.PLAIN, 14));
|
||||||
|
dashboardToggleButton.setForeground(new Color(233, 241, 255));
|
||||||
|
dashboardToggleButton.setBackground(new Color(39, 77, 57));
|
||||||
|
dashboardToggleButton.setBorder(BorderFactory.createEmptyBorder(8, 14, 8, 14));
|
||||||
|
dashboardToggleButton.addActionListener(
|
||||||
|
event -> setDashboardVisible(dashboardToggleButton.isSelected())
|
||||||
|
);
|
||||||
|
return dashboardToggleButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the dashboard side panel in drive view.
|
||||||
|
*
|
||||||
|
* @param visible whether dashboard should be visible
|
||||||
|
*/
|
||||||
|
private void setDashboardVisible(boolean visible) {
|
||||||
|
if (driveSplitPane == null || dashboardCard == null || dashboardToggleButton == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardCard.setVisible(visible);
|
||||||
|
driveSplitPane.setDividerSize(visible ? EXPANDED_DIVIDER_SIZE : 0);
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
dashboardToggleButton.setSelected(true);
|
||||||
|
dashboardToggleButton.setText("Dashboard ausblenden");
|
||||||
|
dashboardToggleButton.setBackground(new Color(69, 86, 109));
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
int width = driveSplitPane.getWidth();
|
||||||
|
if (width <= 0) {
|
||||||
|
driveSplitPane.setDividerLocation(0.72);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dividerLocation = Math.max(
|
||||||
|
(int) (width * 0.55),
|
||||||
|
width - EXPANDED_DASHBOARD_WIDTH
|
||||||
|
);
|
||||||
|
driveSplitPane.setDividerLocation(dividerLocation);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dashboardToggleButton.setSelected(false);
|
||||||
|
dashboardToggleButton.setText("Dashboard einblenden");
|
||||||
|
dashboardToggleButton.setBackground(new Color(39, 77, 57));
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
int width = driveSplitPane.getWidth();
|
||||||
|
if (width > 0) {
|
||||||
|
driveSplitPane.setDividerLocation(width);
|
||||||
|
} else {
|
||||||
|
driveSplitPane.setDividerLocation(1.0d);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
driveSplitPane.revalidate();
|
||||||
|
driveSplitPane.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trims a config value and strips optional wrapping quotes.
|
* Trims a config value and strips optional wrapping quotes.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -12,18 +12,15 @@ import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
|
|||||||
import org.jfree.chart.ui.Layer;
|
import org.jfree.chart.ui.Layer;
|
||||||
import org.jfree.chart.ui.RectangleAnchor;
|
import org.jfree.chart.ui.RectangleAnchor;
|
||||||
import org.jfree.chart.ui.TextAnchor;
|
import org.jfree.chart.ui.TextAnchor;
|
||||||
import org.jfree.data.category.DefaultCategoryDataset;
|
|
||||||
import org.jfree.data.time.Millisecond;
|
import org.jfree.data.time.Millisecond;
|
||||||
import org.jfree.data.time.TimeSeries;
|
import org.jfree.data.time.TimeSeries;
|
||||||
import org.jfree.data.time.TimeSeriesCollection;
|
import org.jfree.data.time.TimeSeriesCollection;
|
||||||
import vassistent.model.ProblemLevel;
|
|
||||||
import vassistent.model.RatioPoint;
|
import vassistent.model.RatioPoint;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.sql.Timestamp;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -42,28 +39,30 @@ public class DashboardView extends JPanel {
|
|||||||
* Creates the dashboard panel with problem-level bar and time-series chart.
|
* Creates the dashboard panel with problem-level bar and time-series chart.
|
||||||
*/
|
*/
|
||||||
public DashboardView() {
|
public DashboardView() {
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout(0, 12));
|
||||||
setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
|
setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
|
||||||
setOpaque(false);
|
setOpaque(false);
|
||||||
|
|
||||||
JPanel card = new JPanel(new BorderLayout(10,10));
|
JPanel card = new JPanel(new BorderLayout(0, 14));
|
||||||
card.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
|
card.setBackground(new Color(22, 28, 37));
|
||||||
|
|
||||||
card.setBackground(UIManager.getColor("Panel.background"));
|
|
||||||
card.setOpaque(true);
|
card.setOpaque(true);
|
||||||
|
|
||||||
card.setBorder(BorderFactory.createCompoundBorder(
|
card.setBorder(BorderFactory.createCompoundBorder(
|
||||||
BorderFactory.createLineBorder(
|
BorderFactory.createLineBorder(
|
||||||
UIManager.getColor("Component.borderColor")
|
new Color(58, 72, 90)
|
||||||
),
|
),
|
||||||
BorderFactory.createEmptyBorder(12,12,12,12)
|
BorderFactory.createEmptyBorder(14, 14, 14, 14)
|
||||||
));
|
));
|
||||||
|
|
||||||
// ---------- TOP: Problem Level ----------
|
// ---------- TOP: Problem Level ----------
|
||||||
JPanel topWrapper = new JPanel(new BorderLayout());
|
JPanel topWrapper = new JPanel(new BorderLayout(0, 8));
|
||||||
topWrapper.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));
|
topWrapper.setBorder(BorderFactory.createEmptyBorder(0, 0, 8, 0));
|
||||||
topWrapper.setOpaque(false);
|
topWrapper.setOpaque(false);
|
||||||
|
|
||||||
|
JLabel topLabel = new JLabel("Current Risk Level");
|
||||||
|
topLabel.setFont(new Font("Segoe UI Semibold", Font.PLAIN, 24));
|
||||||
|
topLabel.setForeground(new Color(230, 237, 249));
|
||||||
|
topWrapper.add(topLabel, BorderLayout.NORTH);
|
||||||
|
|
||||||
levelBar = new ProblemLevelBar();
|
levelBar = new ProblemLevelBar();
|
||||||
topWrapper.add(levelBar, BorderLayout.CENTER);
|
topWrapper.add(levelBar, BorderLayout.CENTER);
|
||||||
|
|
||||||
@ -83,21 +82,28 @@ public class DashboardView extends JPanel {
|
|||||||
);
|
);
|
||||||
|
|
||||||
XYPlot plot = chart.getXYPlot();
|
XYPlot plot = chart.getXYPlot();
|
||||||
|
chart.setBackgroundPaint(new Color(22, 28, 37));
|
||||||
|
chart.getTitle().setPaint(new Color(222, 231, 245));
|
||||||
|
chart.getTitle().setFont(new Font("Segoe UI Semibold", Font.PLAIN, 16));
|
||||||
|
|
||||||
long tenMinutes = 10 * 60 * 1000L;
|
long tenMinutes = 10 * 60 * 1000L;
|
||||||
|
|
||||||
DateAxis domainAxis = (DateAxis) plot.getDomainAxis();
|
DateAxis domainAxis = (DateAxis) plot.getDomainAxis();
|
||||||
|
|
||||||
domainAxis.setFixedAutoRange(tenMinutes);
|
domainAxis.setFixedAutoRange(tenMinutes);
|
||||||
domainAxis.setAutoRange(true);
|
domainAxis.setAutoRange(true);
|
||||||
|
domainAxis.setDateFormatOverride(new SimpleDateFormat("HH:mm:ss"));
|
||||||
|
domainAxis.setLabelPaint(new Color(170, 185, 208));
|
||||||
|
domainAxis.setTickLabelPaint(new Color(170, 185, 208));
|
||||||
|
domainAxis.setTickMarkPaint(new Color(89, 105, 128));
|
||||||
|
|
||||||
XYLineAndShapeRenderer renderer =
|
XYLineAndShapeRenderer renderer =
|
||||||
(XYLineAndShapeRenderer) plot.getRenderer();
|
(XYLineAndShapeRenderer) plot.getRenderer();
|
||||||
|
|
||||||
renderer.setDefaultShapesVisible(false);
|
renderer.setDefaultShapesVisible(false);
|
||||||
|
renderer.setSeriesPaint(0, new Color(67, 175, 255));
|
||||||
renderer.setSeriesStroke(0,
|
renderer.setSeriesStroke(0,
|
||||||
new BasicStroke(
|
new BasicStroke(
|
||||||
2.5f,
|
3.0f,
|
||||||
BasicStroke.CAP_ROUND,
|
BasicStroke.CAP_ROUND,
|
||||||
BasicStroke.JOIN_ROUND
|
BasicStroke.JOIN_ROUND
|
||||||
));
|
));
|
||||||
@ -110,14 +116,25 @@ public class DashboardView extends JPanel {
|
|||||||
chart.getXYPlot().getRangeAxis().setRange(0.0, 1.02);
|
chart.getXYPlot().getRangeAxis().setRange(0.0, 1.02);
|
||||||
NumberAxis rangeAxis = (NumberAxis) chart.getXYPlot().getRangeAxis();
|
NumberAxis rangeAxis = (NumberAxis) chart.getXYPlot().getRangeAxis();
|
||||||
rangeAxis.setTickUnit(new NumberTickUnit(0.1));
|
rangeAxis.setTickUnit(new NumberTickUnit(0.1));
|
||||||
|
rangeAxis.setLabelPaint(new Color(170, 185, 208));
|
||||||
|
rangeAxis.setTickLabelPaint(new Color(170, 185, 208));
|
||||||
|
rangeAxis.setTickMarkPaint(new Color(89, 105, 128));
|
||||||
|
|
||||||
|
plot.setBackgroundPaint(new Color(16, 20, 27));
|
||||||
|
plot.setOutlineVisible(false);
|
||||||
|
plot.setRangeGridlinePaint(new Color(89, 105, 128, 120));
|
||||||
|
plot.setDomainGridlinePaint(new Color(89, 105, 128, 120));
|
||||||
|
|
||||||
chart.setAntiAlias(true);
|
chart.setAntiAlias(true);
|
||||||
chart.setTextAntiAlias(true);
|
chart.setTextAntiAlias(true);
|
||||||
chart.getPlot().setBackgroundPaint(new Color(245,245,245));
|
|
||||||
chart.getPlot().setOutlineVisible(false);
|
|
||||||
|
|
||||||
ChartPanel chartPanel = new ChartPanel(chart);
|
ChartPanel chartPanel = new ChartPanel(chart);
|
||||||
chartPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
|
chartPanel.setOpaque(false);
|
||||||
|
chartPanel.setBackground(new Color(16, 20, 27));
|
||||||
|
chartPanel.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createLineBorder(new Color(58, 72, 90)),
|
||||||
|
BorderFactory.createEmptyBorder(8, 8, 8, 8)
|
||||||
|
));
|
||||||
|
|
||||||
card.add(chartPanel, BorderLayout.CENTER);
|
card.add(chartPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
@ -188,7 +205,7 @@ public class DashboardView extends JPanel {
|
|||||||
|
|
||||||
ValueMarker marker = new ValueMarker(value);
|
ValueMarker marker = new ValueMarker(value);
|
||||||
|
|
||||||
marker.setPaint(new Color(150,150,150,150));
|
marker.setPaint(getThresholdColor(label));
|
||||||
|
|
||||||
float[] dash = {6.0f, 6.0f};
|
float[] dash = {6.0f, 6.0f};
|
||||||
marker.setStroke(new BasicStroke(
|
marker.setStroke(new BasicStroke(
|
||||||
@ -201,12 +218,31 @@ public class DashboardView extends JPanel {
|
|||||||
));
|
));
|
||||||
|
|
||||||
marker.setLabel(label);
|
marker.setLabel(label);
|
||||||
marker.setLabelFont(new Font("Segoe UI", Font.PLAIN, 11));
|
marker.setLabelFont(new Font("Segoe UI Semibold", Font.PLAIN, 12));
|
||||||
marker.setLabelPaint(new Color(160,160,160));
|
marker.setLabelPaint(new Color(188, 203, 223));
|
||||||
|
|
||||||
marker.setLabelAnchor(RectangleAnchor.RIGHT);
|
marker.setLabelAnchor(RectangleAnchor.RIGHT);
|
||||||
marker.setLabelTextAnchor(TextAnchor.TOP_RIGHT);
|
marker.setLabelTextAnchor(TextAnchor.TOP_RIGHT);
|
||||||
|
|
||||||
plot.addRangeMarker(marker, Layer.BACKGROUND);
|
plot.addRangeMarker(marker, Layer.BACKGROUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a distinct marker color for each threshold level.
|
||||||
|
*
|
||||||
|
* @param label threshold label
|
||||||
|
* @return translucent marker color
|
||||||
|
*/
|
||||||
|
private Color getThresholdColor(String label) {
|
||||||
|
switch (label) {
|
||||||
|
case "Warning":
|
||||||
|
return new Color(255, 210, 69, 165);
|
||||||
|
case "High":
|
||||||
|
return new Color(255, 153, 65, 170);
|
||||||
|
case "Disaster":
|
||||||
|
return new Color(255, 92, 92, 175);
|
||||||
|
default:
|
||||||
|
return new Color(168, 178, 194, 140);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,6 @@ import org.cef.handler.CefAppHandlerAdapter;
|
|||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Embedded JCEF browser panel used to display the avatar pixel stream.
|
* Embedded JCEF browser panel used to display the avatar pixel stream.
|
||||||
@ -31,7 +29,9 @@ public class PixelStreamingView extends JPanel {
|
|||||||
*/
|
*/
|
||||||
public PixelStreamingView(String startURL, boolean useOSR, boolean isTransparent) {
|
public PixelStreamingView(String startURL, boolean useOSR, boolean isTransparent) {
|
||||||
super(new BorderLayout());
|
super(new BorderLayout());
|
||||||
|
setOpaque(true);
|
||||||
|
setBackground(new Color(8, 11, 16));
|
||||||
|
|
||||||
CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
|
CefApp.addAppHandler(new CefAppHandlerAdapter(null) {
|
||||||
/**
|
/**
|
||||||
* Handles JCEF application state transitions.
|
* Handles JCEF application state transitions.
|
||||||
@ -55,7 +55,16 @@ public class PixelStreamingView extends JPanel {
|
|||||||
browser = client.createBrowser(startURL, useOSR, isTransparent);
|
browser = client.createBrowser(startURL, useOSR, isTransparent);
|
||||||
browserUI_ = browser.getUIComponent();
|
browserUI_ = browser.getUIComponent();
|
||||||
|
|
||||||
add(browserUI_, BorderLayout.CENTER);
|
JPanel browserContainer = new JPanel(new BorderLayout());
|
||||||
|
browserContainer.setOpaque(true);
|
||||||
|
browserContainer.setBackground(new Color(6, 9, 14));
|
||||||
|
browserContainer.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createLineBorder(new Color(56, 68, 86)),
|
||||||
|
BorderFactory.createEmptyBorder(6, 6, 6, 6)
|
||||||
|
));
|
||||||
|
browserContainer.add(browserUI_, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
add(browserContainer, BorderLayout.CENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -17,9 +17,10 @@ public class ProblemLevelBar extends JPanel {
|
|||||||
* Creates the level bar component with preferred sizing and transparent background.
|
* Creates the level bar component with preferred sizing and transparent background.
|
||||||
*/
|
*/
|
||||||
public ProblemLevelBar() {
|
public ProblemLevelBar() {
|
||||||
setPreferredSize(new Dimension(100, 50));
|
setPreferredSize(new Dimension(420, 88));
|
||||||
|
setMinimumSize(new Dimension(320, 72));
|
||||||
setOpaque(false);
|
setOpaque(false);
|
||||||
setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
|
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,47 +67,45 @@ public class ProblemLevelBar extends JPanel {
|
|||||||
Graphics2D g2 = (Graphics2D) g;
|
Graphics2D g2 = (Graphics2D) g;
|
||||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||||
|
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
|
||||||
// --- Hintergrund ---
|
int arc = Math.max(22, height / 2);
|
||||||
GradientPaint bg = new GradientPaint(
|
|
||||||
0, 0, new Color(235,235,235),
|
GradientPaint trackGradient = new GradientPaint(
|
||||||
0, height, new Color(210,210,210)
|
0, 0, new Color(51, 60, 76),
|
||||||
|
0, height, new Color(29, 36, 48)
|
||||||
);
|
);
|
||||||
|
g2.setPaint(trackGradient);
|
||||||
|
g2.fillRoundRect(0, 0, width, height, arc, arc);
|
||||||
|
|
||||||
g2.setPaint(bg);
|
|
||||||
g2.fillRoundRect(0, 0, width, height, 20, 20);
|
|
||||||
|
|
||||||
// --- Gefüllter Bereich ---
|
|
||||||
int filledWidth = (int) (width * ratio);
|
int filledWidth = (int) (width * ratio);
|
||||||
|
if (filledWidth > 0) {
|
||||||
|
filledWidth = Math.max(filledWidth, arc / 2);
|
||||||
|
}
|
||||||
|
|
||||||
Color baseColor = getColorForLevel(level);
|
Color baseColor = getColorForLevel(level);
|
||||||
Color darkerColor = baseColor.darker();
|
GradientPaint fillGradient = new GradientPaint(
|
||||||
|
0, 0, baseColor.darker(),
|
||||||
GradientPaint gradient = new GradientPaint(
|
Math.max(1, filledWidth), 0, baseColor
|
||||||
0, 0, darkerColor,
|
|
||||||
filledWidth, 0, baseColor
|
|
||||||
);
|
);
|
||||||
|
g2.setPaint(fillGradient);
|
||||||
g2.setPaint(gradient);
|
g2.fillRoundRect(0, 0, filledWidth, height, arc, arc);
|
||||||
g2.fillRoundRect(0, 0, filledWidth, height, 20, 20);
|
|
||||||
|
|
||||||
g2.setStroke(new BasicStroke(2f));
|
g2.setStroke(new BasicStroke(2f));
|
||||||
g2.setColor(baseColor.brighter());
|
g2.setColor(new Color(175, 190, 212));
|
||||||
g2.drawRoundRect(1,1,width-2,height-2,20,20);
|
g2.drawRoundRect(1, 1, width - 2, height - 2, arc, arc);
|
||||||
|
|
||||||
// --- Text ---
|
String text = "Problem " + level.name() + " " + (int) (ratio * 100) + "%";
|
||||||
String text = level.name() + " (" + (int)(ratio * 100) + "%)";
|
g2.setFont(new Font("Segoe UI Semibold",
|
||||||
|
Font.PLAIN,
|
||||||
g2.setColor(Color.BLACK);
|
Math.max(19, (int) (height * 0.33))));
|
||||||
|
g2.setColor(new Color(244, 248, 255));
|
||||||
|
|
||||||
FontMetrics fm = g2.getFontMetrics();
|
FontMetrics fm = g2.getFontMetrics();
|
||||||
int textWidth = fm.stringWidth(text);
|
int textX = (width - fm.stringWidth(text)) / 2;
|
||||||
|
int textY = (height - fm.getHeight()) / 2 + fm.getAscent();
|
||||||
g2.drawString(
|
g2.drawString(text, textX, textY);
|
||||||
text,
|
|
||||||
(width - textWidth) / 2,
|
|
||||||
(height + fm.getAscent()) / 2 - 3
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,13 +117,13 @@ public class ProblemLevelBar extends JPanel {
|
|||||||
private Color getColorForLevel(ProblemLevel level) {
|
private Color getColorForLevel(ProblemLevel level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case DISASTER:
|
case DISASTER:
|
||||||
return new Color(200, 0, 0);
|
return new Color(214, 70, 70);
|
||||||
case HIGH:
|
case HIGH:
|
||||||
return new Color(255, 140, 0);
|
return new Color(234, 134, 45);
|
||||||
case WARNING:
|
case WARNING:
|
||||||
return new Color(255, 215, 0);
|
return new Color(219, 180, 52);
|
||||||
default:
|
default:
|
||||||
return new Color(0, 170, 0);
|
return new Color(53, 178, 107);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
tmp_chunks/chunk_00.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
tmp_chunks/chunk_01.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
tmp_chunks/chunk_02.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
tmp_chunks/chunk_03.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
tmp_chunks/chunk_04.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
tmp_chunks/chunk_05.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
tmp_chunks/chunk_06.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
tmp_chunks/chunk_07.png
Normal file
|
After Width: | Height: | Size: 22 KiB |