Skip to content

refactor the popups and adds reloading of the styling when updated #108

Merged
merged 2 commits into from
May 14, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -56,30 +56,27 @@ public List<Popup> 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])));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
141 changes: 101 additions & 40 deletions src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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;
Expand All @@ -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");
Expand Down Expand Up @@ -346,4 +393,4 @@ private String resource(final String path) {
public Button getNextWeekButton() {
return nextWeekButton;
}
}
}
Loading