From 869055f34e44923fe5532883317add42ff46b544 Mon Sep 17 00:00:00 2001 From: Per Eric Trapnes Date: Thu, 14 May 2026 16:29:36 +0200 Subject: [PATCH] refactor the popups and adds reloading of the styling when updated --- .../gruppe42/Controller/PopupsController.java | 19 +-- .../ViewControllers/StartViewController.java | 8 +- .../idi/idatt2003/gruppe42/View/Popup.java | 141 +++++++++++++----- .../gruppe42/View/Views/DesktopView.java | 71 +++++++-- .../gruppe42/View/Views/StartView.java | 66 ++++++-- src/main/resources/css/apps.css | 11 +- src/main/resources/css/desktop.css | 4 +- src/main/resources/css/global.css | 24 --- src/main/resources/css/login.css | 6 +- src/main/resources/css/popup.css | 20 +-- 10 files changed, 253 insertions(+), 117 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupsController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupsController.java index b2a0df1..37c76f8 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupsController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupsController.java @@ -6,8 +6,8 @@ import edu.ntnu.idi.idatt2003.gruppe42.View.Popup; import java.util.List; import javafx.scene.control.Button; -import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; /** * Manages popup visibility, dragging, and bounds-clamping. @@ -56,30 +56,27 @@ public List getPopups() { return popups; } - public void bindOpenButton(Button button, App type){ + public void bindOpenButton(Button button, App type) { button.setOnAction(event -> show(type)); } private void makeDraggable(Popup popup) { - BorderPane root = popup.getRoot(); + StackPane root = popup.getRoot(); double[] offset = new double[2]; - double[] parentBounds = new double[2]; // cache here + double[] maxXY = new double[2]; popup.getHeader().setOnMousePressed(event -> { offset[0] = event.getSceneX() - root.getLayoutX(); offset[1] = event.getSceneY() - root.getLayoutY(); - // snapshot bounds once on press, not on every drag tick if (root.getParent() instanceof Pane parent) { - parentBounds[0] = parent.getWidth() - popup.getWidth(); - parentBounds[1] = parent.getHeight() - popup.getHeight(); + maxXY[0] = parent.getWidth() - root.getWidth(); + maxXY[1] = parent.getHeight() - root.getHeight(); } }); popup.getHeader().setOnMouseDragged(event -> { - root.setLayoutX(Math.max(0, Math.min( - event.getSceneX() - offset[0], parentBounds[0]))); - root.setLayoutY(Math.max(0, Math.min( - event.getSceneY() - offset[1], parentBounds[1]))); + root.setLayoutX(Math.max(0, Math.min(event.getSceneX() - offset[0], maxXY[0]))); + root.setLayoutY(Math.max(0, Math.min(event.getSceneY() - offset[1], maxXY[1]))); }); } } \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/StartViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/StartViewController.java index 770211e..4859916 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/StartViewController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/StartViewController.java @@ -29,7 +29,13 @@ public class StartViewController { "Hustler G", "Chin Checker", "Sparkling Water Enthusiast", - "The Talisman" + "The Talisman", + "Epstein follower", + "Looks Maxer", + "Mentioned in Epstein files", + "Baby Oil Hoarder", + "Roofie Supplier", + "Hentai virus" ); public StartViewController(final Millions application, final StartView startView) { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java index 78e2f1a..cc9a3b5 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java @@ -6,12 +6,15 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; +import javafx.scene.effect.DropShadow; import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Rectangle; @@ -21,10 +24,14 @@ */ public abstract class Popup { + private static final boolean DEV_CSS_RELOAD = true; + private static final double ARC = 16; + private final int width; private final int height; private final App type; + private final StackPane wrapper; private final BorderPane root; private final HBox header; private final Button closeButton; @@ -47,33 +54,95 @@ protected Popup(int width, int height, int x, int y, App type) { scrollPane.setFitToHeight(true); closeButton = buildCloseButton(); - header = buildHeader(closeButton, titleString()); + header = buildHeader(closeButton, type.getDisplayName()); root = new BorderPane(); root.getStyleClass().add("popup-root"); - root.getStylesheets().addAll( - resource("/css/global.css"), - resource("/css/popup.css"), - resource("/css/apps.css") - ); root.setTop(header); root.setCenter(scrollPane); - root.setLayoutX(x); - root.setLayoutY(y); - root.setManaged(false); - root.setVisible(false); - root.autosize(); - root.setClip(roundedClip()); - root.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> root.toFront()); + root.setClip(buildClip(root)); + + Rectangle borderOverlay = buildBorderOverlay(); + + wrapper = new StackPane(root, borderOverlay); + wrapper.setLayoutX(x); + wrapper.setLayoutY(y); + wrapper.setManaged(false); + wrapper.setVisible(false); + wrapper.setEffect(new DropShadow(14, 0, 4, Color.rgb(0, 0, 0, 0.25))); + wrapper.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> wrapper.toFront()); + + loadStylesheets(); + + if (DEV_CSS_RELOAD) { + wrapper.setFocusTraversable(true); + wrapper.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.R) { + loadStylesheets(); + System.out.println("[CSS] Popup stylesheets reloaded"); + } + }); + } + } + + private Rectangle buildClip(BorderPane target) { + Rectangle clip = new Rectangle(); + clip.setArcWidth(ARC); + clip.setArcHeight(ARC); + clip.widthProperty().bind(target.widthProperty()); + clip.heightProperty().bind(target.heightProperty()); + return clip; + } + + private Rectangle buildBorderOverlay() { + Rectangle border = new Rectangle(); + border.getStyleClass().add("popup-border-overlay"); + border.setArcWidth(ARC); + border.setArcHeight(ARC); + border.widthProperty().bind(root.widthProperty()); + border.heightProperty().bind(root.heightProperty()); + border.setFill(Color.TRANSPARENT); + border.setMouseTransparent(true); + return border; + } + + private void loadStylesheets() { + root.getStylesheets().clear(); + try { + if (DEV_CSS_RELOAD) { + String projectDir = System.getProperty("user.dir"); + root.getStylesheets().addAll( + devCopyToTemp(projectDir + "/src/main/resources/css/global.css"), + devCopyToTemp(projectDir + "/src/main/resources/css/popup.css"), + devCopyToTemp(projectDir + "/src/main/resources/css/apps.css") + ); + } else { + root.getStylesheets().addAll( + resource("/css/global.css"), + resource("/css/popup.css"), + resource("/css/apps.css") + ); + } + } catch (Exception e) { + System.err.println("[CSS] Failed to load popup stylesheets: " + e.getMessage()); + } + } + + private String devCopyToTemp(final String sourcePath) throws Exception { + java.nio.file.Path src = java.nio.file.Paths.get(sourcePath); + java.nio.file.Path tmp = java.nio.file.Files.createTempFile("css_", ".css"); + java.nio.file.Files.copy(src, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + tmp.toFile().deleteOnExit(); + return tmp.toUri().toString(); } private Button buildCloseButton() { - Button btn = new Button(); - btn.setShape(new Circle(6)); - btn.setMinSize(12, 12); - btn.setMaxSize(12, 12); - btn.getStyleClass().add("popup-close-button"); - return btn; + Button button = new Button(); + button.setShape(new Circle(6)); + button.setMinSize(12, 12); + button.setMaxSize(12, 12); + button.getStyleClass().add("popup-close-button"); + return button; } private HBox buildHeader(Button closeButton, String title) { @@ -91,41 +160,33 @@ private HBox buildHeader(Button closeButton, String title) { return hbox; } - private String titleString() { - return type.getDisplayName(); - } - - private Rectangle roundedClip() { - Rectangle clip = new Rectangle(); - clip.setArcWidth(20); - clip.setArcHeight(20); - clip.widthProperty().bind(root.widthProperty()); - clip.heightProperty().bind(root.heightProperty()); - return clip; - } - - private String resource(String path) { + private String resource(final String path) { return getClass().getResource(path).toExternalForm(); } /** * Binds this popup's layoutX/Y so it is always centered inside its parent. + * Safe to call before or after the wrapper is added to the scene graph. */ public void centerInParent() { - root.parentProperty().addListener((obs, oldParent, parent) -> { - if (parent instanceof Pane pane) { - root.layoutXProperty().bind(pane.widthProperty().subtract(width).divide(2)); - root.layoutYProperty().bind(pane.heightProperty().subtract(height).divide(2)); + Runnable bind = () -> { + if (wrapper.getParent() instanceof Pane pane) { + wrapper.layoutXProperty().bind( + pane.widthProperty().subtract(wrapper.widthProperty()).divide(2)); + wrapper.layoutYProperty().bind( + pane.heightProperty().subtract(wrapper.heightProperty()).divide(2)); } - }); + }; + bind.run(); + wrapper.parentProperty().addListener((obs, oldParent, newParent) -> bind.run()); } public App getType() { return type; } - public BorderPane getRoot() { - return root; + public StackPane getRoot() { + return wrapper; } public HBox getHeader() { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java index 26a4d40..642cd6c 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java @@ -30,6 +30,11 @@ */ public final class DesktopView { + // ── DEV FLAG ───────────────────────────────────────────────────────────── + // Set to false (or delete this + all DEV_CSS_RELOAD blocks) before release. + private static final boolean DEV_CSS_RELOAD = true; + // ───────────────────────────────────────────────────────────────────────── + /** * Z-stack of modal levels, ordered from lowest to highest. Each value * is its own named token; no numeric z-index lives anywhere else. @@ -73,16 +78,59 @@ public BorderPane getRoot() { bottomBar = createBottomBar(); root.setCenter(gridArea); root.setBottom(bottomBar); - root.getStylesheets().addAll( - resource("/css/global.css"), - resource("/css/desktop.css"), - resource("/css/apps.css") - ); root.getStyleClass().add("desktop-root"); + + loadStylesheets(); + + // DEV_CSS_RELOAD: press R to hot-reload CSS from source without restarting + if (DEV_CSS_RELOAD) { + root.setFocusTraversable(true); + root.sceneProperty().addListener((obs, oldScene, newScene) -> { + if (newScene != null) root.requestFocus(); + }); + root.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.R) { + loadStylesheets(); + System.out.println("[CSS] Stylesheets reloaded"); + } + }); + } } return root; } + private void loadStylesheets() { + root.getStylesheets().clear(); + try { + if (DEV_CSS_RELOAD) { + // DEV_CSS_RELOAD: reads directly from source, copies to temp to bust JavaFX cache + String projectDir = System.getProperty("user.dir"); + root.getStylesheets().addAll( + devCopyToTemp(projectDir + "/src/main/resources/css/global.css"), + devCopyToTemp(projectDir + "/src/main/resources/css/desktop.css"), + devCopyToTemp(projectDir + "/src/main/resources/css/apps.css") + ); + } else { + root.getStylesheets().addAll( + resource("/css/global.css"), + resource("/css/desktop.css"), + resource("/css/apps.css") + ); + } + } catch (Exception e) { + System.err.println("[CSS] Failed to load stylesheets: " + e.getMessage()); + } + } + + // DEV_CSS_RELOAD: copies a CSS file to a unique temp path so JavaFX can't cache it + private String devCopyToTemp(final String sourcePath) throws Exception { + java.nio.file.Path src = java.nio.file.Paths.get(sourcePath); + java.nio.file.Path tmp = java.nio.file.Files.createTempFile("css_", ".css"); + java.nio.file.Files.copy(src, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + tmp.toFile().deleteOnExit(); + return tmp.toUri().toString(); + } + // Layer registration: the controller adds nodes to the scene graph in // the exact z-order required for the layering rules to work. @@ -290,8 +338,7 @@ private HBox buildHeartsBox(final Player player) { hearts[i].getStyleClass().add(active ? "heart-active" : "heart-empty"); } }; - player.getLivesProperty().addListener(( - obs, ov, nv) -> Platform.runLater(refresh)); + player.getLivesProperty().addListener((obs, ov, nv) -> Platform.runLater(refresh)); refresh.run(); return box; @@ -303,10 +350,10 @@ private HBox createStatusBox() { TimeAndWeatherController twc = controller.getTimeAndWeatherController(); Label weather = styledLabel("desktop-label"); - Label temp = styledLabel("desktop-label"); - Label day = styledLabel("desktop-label-bold"); - Label week = styledLabel("desktop-label-bold"); - Label clock = styledLabel("desktop-label-bold"); + Label temp = styledLabel("desktop-label"); + Label day = styledLabel("desktop-label-bold"); + Label week = styledLabel("desktop-label-bold"); + Label clock = styledLabel("desktop-label-bold"); weather.setText(twc.weatherProperty().get()); temp.setText(twc.temperatureProperty().get() + "°C"); @@ -346,4 +393,4 @@ private String resource(final String path) { public Button getNextWeekButton() { return nextWeekButton; } -} +} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/StartView.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/StartView.java index e1c359a..d48e6ac 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/StartView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/StartView.java @@ -17,10 +17,15 @@ public class StartView { + // ── DEV FLAG ───────────────────────────────────────────────────────────── + // Set to false (or delete this + all DEV_CSS_RELOAD blocks) before release. + private static final boolean DEV_CSS_RELOAD = true; + // ───────────────────────────────────────────────────────────────────────── + private final TextField usernameField = new TextField(); private final Button loginButton = new Button("→"); private final ToggleGroup startingMoneyGroup = new ToggleGroup(); - private final StackPane root = new StackPane(); + private final StackPane root = new StackPane(); public StackPane getRoot() { @@ -66,9 +71,9 @@ public StackPane getRoot() { startingMoneyOptions.setAlignment(Pos.CENTER); // Starting Money Options - RadioButton easyCheck = new RadioButton("Easy ($1000)"); + RadioButton easyCheck = new RadioButton("Easy ($1000)"); RadioButton mediumCheck = new RadioButton("Medium ($100)"); - RadioButton hardCheck = new RadioButton("Hard ($0)"); + RadioButton hardCheck = new RadioButton("Hard ($0)"); easyCheck.getStyleClass().add("difficulty-label"); mediumCheck.getStyleClass().add("difficulty-label"); hardCheck.getStyleClass().add("difficulty-label"); @@ -81,17 +86,59 @@ public StackPane getRoot() { loginContainer.getChildren().addAll( avatarContainer, usernameLabel, loginInputBox, startingMoneyLabel, startingMoneyOptions ); - + root.getChildren().add(loginContainer); root.getStyleClass().add("login-root"); - root.getStylesheets().addAll( - getClass().getResource("/css/global.css").toExternalForm(), - getClass().getResource("/css/login.css").toExternalForm() - ); + + loadStylesheets(); + + // DEV_CSS_RELOAD: press R to hot-reload CSS from source without restarting + if (DEV_CSS_RELOAD) { + root.setFocusTraversable(true); + root.sceneProperty().addListener((obs, oldScene, newScene) -> { + if (newScene != null) root.requestFocus(); + }); + root.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.R) { + loadStylesheets(); + System.out.println("[CSS] Stylesheets reloaded"); + } + }); + } return root; } + private void loadStylesheets() { + root.getStylesheets().clear(); + try { + if (DEV_CSS_RELOAD) { + // DEV_CSS_RELOAD: reads directly from source, copies to temp to bust JavaFX cache + String projectDir = System.getProperty("user.dir"); + root.getStylesheets().addAll( + copyToTemp(projectDir + "/src/main/resources/css/global.css"), + copyToTemp(projectDir + "/src/main/resources/css/login.css") + ); + } else { + root.getStylesheets().addAll( + getClass().getResource("/css/global.css").toExternalForm(), + getClass().getResource("/css/login.css").toExternalForm() + ); + } + } catch (Exception e) { + System.err.println("[CSS] Failed to load stylesheets: " + e.getMessage()); + } + } + + // DEV_CSS_RELOAD: copies a CSS file to a unique temp path so JavaFX can't cache it + private String copyToTemp(String sourcePath) throws Exception { + java.nio.file.Path src = java.nio.file.Paths.get(sourcePath); + java.nio.file.Path tmp = java.nio.file.Files.createTempFile("css_", ".css"); + java.nio.file.Files.copy(src, tmp, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + tmp.toFile().deleteOnExit(); + return tmp.toUri().toString(); + } + public TextField getUsernameField() { return usernameField; } @@ -99,7 +146,6 @@ public TextField getUsernameField() { /** * Getter for the difficulty mode the user has selected. * @return Difficulty - * */ public String getSelectedMode() { RadioButton selectedMode = (RadioButton) startingMoneyGroup.getSelectedToggle(); @@ -112,4 +158,4 @@ public String getSelectedMode() { public Button getLoginButton() { return loginButton; } -} +} \ No newline at end of file diff --git a/src/main/resources/css/apps.css b/src/main/resources/css/apps.css index a165306..2201e8c 100644 --- a/src/main/resources/css/apps.css +++ b/src/main/resources/css/apps.css @@ -35,7 +35,7 @@ } .stock-company { - -fx-font-size: 12px; + -fx-font-size: 14px; -fx-text-fill: -text-muted; } @@ -58,7 +58,7 @@ .stock-title { -fx-font-weight: bold; - -fx-font-size: 20px; + -fx-font-size: 24px; -fx-text-fill: -text-strong; } @@ -89,6 +89,7 @@ .stock-quantity-spinner .text-field { -fx-background-color: transparent; -fx-alignment: center; + -fx-font-size: 18px; } .stock-price-change-large { @@ -112,12 +113,12 @@ } .stock-area-chart .chart-series-area-line { - -fx-stroke: -primary; + -fx-stroke: #004e92; -fx-stroke-width: 2px; } .stock-area-chart .chart-series-area-fill { - -fx-fill: rgba(0, 122, 255, 0.15); + -fx-fill: linear-gradient(to bottom, #004e9280, transparent 80%); } .stock-area-chart .chart-plot-background { @@ -131,7 +132,7 @@ .bank-summary-card { -fx-background-color: linear-gradient(to bottom right, #004e92, #000428); - -fx-background-radius: 15; + -fx-background-radius: 25; -fx-padding: 25; -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 10, 0, 0, 5); } diff --git a/src/main/resources/css/desktop.css b/src/main/resources/css/desktop.css index 7e29ad3..f870240 100644 --- a/src/main/resources/css/desktop.css +++ b/src/main/resources/css/desktop.css @@ -28,10 +28,10 @@ } .app-button { - -fx-background-radius: 12; + -fx-background-radius: 22; -fx-background-color: #f0f0f0; -fx-border-color: -popup-border; - -fx-border-radius: 12; + -fx-border-radius: 22; -fx-text-fill: -text-base; -fx-font-weight: bold; -fx-cursor: hand; diff --git a/src/main/resources/css/global.css b/src/main/resources/css/global.css index 40680d1..08a8755 100644 --- a/src/main/resources/css/global.css +++ b/src/main/resources/css/global.css @@ -59,31 +59,7 @@ -fx-opacity: 0; } -.scroll-bar .increment-arrow, -.scroll-bar .decrement-arrow { - -fx-padding: 0; - -fx-shape: ""; - -fx-opacity: 0; -} - -.scroll-bar:vertical .increment-button, -.scroll-bar:vertical .decrement-button { - -fx-pref-height: 0; - -fx-min-height: 0; - -fx-max-height: 0; -} - -.scroll-bar:horizontal .increment-button, -.scroll-bar:horizontal .decrement-button { - -fx-pref-width: 0; - -fx-min-width: 0; - -fx-max-width: 0; -} .list-view { -fx-background-color: transparent; } - -.list-view .scroll-bar:vertical { - -fx-opacity: 1.0; -} diff --git a/src/main/resources/css/login.css b/src/main/resources/css/login.css index 4d602f6..b75b5d7 100644 --- a/src/main/resources/css/login.css +++ b/src/main/resources/css/login.css @@ -1,6 +1,6 @@ .login-root { - -fx-background-color: linear-gradient(#FF9D23, #EA5252); + -fx-background-color: linear-gradient(to bottom , #67C090, #26677F); } .login-container { @@ -31,7 +31,7 @@ } .login-submit-button { - -fx-background-color: linear-gradient(to bottom, #ffffff, #e1e1e1); + -fx-background-color: linear-gradient( #ffffff, #e1e1e1); -fx-background-radius: 4; -fx-border-color: #707070; -fx-border-radius: 4; @@ -42,7 +42,7 @@ } .login-submit-button:hover { - -fx-background-color: linear-gradient(to bottom, #e5f1fb, #c6e0f7); + -fx-background-color: linear-gradient( #e5f1fb, #c6e0f7); -fx-border-color: #3c7fb1; } diff --git a/src/main/resources/css/popup.css b/src/main/resources/css/popup.css index 0a47f13..f15ca7b 100644 --- a/src/main/resources/css/popup.css +++ b/src/main/resources/css/popup.css @@ -1,14 +1,12 @@ - - .popup-root { -fx-background-color: -popup-bg; - -fx-background-radius: 12; - -fx-border-color: -popup-border; - -fx-border-width: 1; - -fx-border-radius: 12; -fx-background-insets: 0; - -fx-border-insets: 0; - -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.18), 14, 0, 0, 4); +} + +.popup-border-overlay { + -fx-stroke: -popup-border; + -fx-stroke-width: 1; + -fx-fill: transparent; } .popup-header { @@ -43,6 +41,10 @@ -fx-viewport-background-color: -popup-bg; } +.popup-scroll-pane:focused { + -fx-background-insets: 0; +} + .popup-content { -fx-background-color: -popup-bg; -} +} \ No newline at end of file