From def5febf3e51ce93ffe2a0d64e41eda23d076cf5 Mon Sep 17 00:00:00 2001 From: EspenTinius Date: Fri, 15 May 2026 23:48:09 +0200 Subject: [PATCH] markedet jeg har lagd ferdig marked --- .../ntnu/idi/idatt2003/g40/mappe/Main.java | 24 +- .../view/widgets/market/MarketActions.java | 20 + .../view/widgets/market/MarketController.java | 95 +++++ .../mappe/view/widgets/market/MarketSort.java | 15 + .../mappe/view/widgets/market/MarketView.java | 379 ++++++++++++++++++ .../view/widgets/topbar/TopBarController.java | 56 +++ .../mappe/view/widgets/topbar/TopBarView.java | 16 + src/main/resources/styles.css | 138 ++++++- 8 files changed, 739 insertions(+), 4 deletions(-) create mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketActions.java create mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java create mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketSort.java create mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java index 691eaa7..7b1c456 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java @@ -23,6 +23,8 @@ import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.financialsummary.SummaryView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard.DashBoardController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.dashboard.DashBoardView; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market.MarketController; +import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market.MarketView; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.topbar.TopBarController; import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.topbar.TopBarView; import javafx.application.Application; @@ -92,13 +94,13 @@ public void start(final Stage stage) throws Exception { // Top bar with summary section TopBarView topBarView = new TopBarView(summaryView); - new TopBarController(topBarView, eventManager); + TopBarController topBarController = new TopBarController(topBarView, eventManager); // Top bar without summary section TopBarView topBarView2 = new TopBarView(); new TopBarController(topBarView2, eventManager); - // Stats page + // Stats page (dashboard, default center-view in-game) DashBoardView dashBoardView = new DashBoardView(); new DashBoardController(dashBoardView, eventManager, @@ -106,9 +108,25 @@ public void start(final Stage stage) throws Exception { exchange, stocksInFile); + // Market page (vises i samme InGameView, byttes til via Market-knappen) + MarketView marketView = new MarketView(); + new MarketController(marketView, + eventManager, + player, + exchange, + stocksInFile); + // In-game (Change "topBarView" to "topBarView2" if no summary section). + // Dashboard er default center-view. InGameView inGameView = new InGameView(topBarView, dashBoardView.getRootPane()); + // Wire top bar buttons til å bytte mellom dashboard og market. + topBarController.setMarketIntegration( + inGameView::changeCenterView, + dashBoardView.getRootPane(), + marketView.getRootPane() + ); + // Register all views viewManager.addView(mainMenuView); viewManager.addView(playGameView); @@ -118,4 +136,4 @@ public void start(final Stage stage) throws Exception { stage.show(); } -} \ No newline at end of file +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketActions.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketActions.java new file mode 100644 index 0000000..65d9bd5 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketActions.java @@ -0,0 +1,20 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market; + +/** + * Enum representing all interactable actions in {@link MarketView}. + * + *

The market is embedded as the center-view inside + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView}, so + * navigation (close, settings, etc.) is handled by the shared top bar. + * Only the filter-bar sort buttons are local to this widget.

+ * */ +public enum MarketActions { + /** Sort stocks alphabetically by ticker. */ + SORT_TICKER, + /** Sort stocks by current price (descending). */ + SORT_PRICE, + /** Sort stocks by latest change (descending). */ + SORT_CHANGE, + /** Sort stocks by amount owned (descending). */ + SORT_OWNED; +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java new file mode 100644 index 0000000..e185fb3 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java @@ -0,0 +1,95 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market; + +import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Player; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Share; +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; +import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; +import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; + +import java.math.BigDecimal; +import java.util.List; + +/** + * Controller for {@link MarketView}. + * + *

Wires up the sort buttons and listens to {@link Exchange} weeks and + * {@link Player} net worth so the grid (prices, change %, owned counts) + * stays in sync as the game advances.

+ * */ +public class MarketController extends ViewController { + + /** The {@link Player} owning shares displayed in the market. */ + private final Player player; + + /** The {@link Exchange} the market is connected to. */ + private final Exchange exchange; + + /** Stocks shown in the market grid. */ + private final List stockList; + + /** + * Constructor. + * + * @param viewElement the {@link MarketView} this controller is attached to. + * @param eventManager the active {@link EventManager}. + * @param player the {@link Player} whose ownership is displayed. + * @param exchange the {@link Exchange} the market connects to. + * @param stockList the stocks to display. + * + * @throws IllegalArgumentException if any argument is invalid. + * */ + public MarketController(final MarketView viewElement, + final EventManager eventManager, + final Player player, + final Exchange exchange, + final List stockList) + throws IllegalArgumentException { + this.player = player; + this.exchange = exchange; + this.stockList = stockList; + super(viewElement, eventManager); + } + + /** {@inheritDoc} */ + @Override + protected void initInteractions() { + getViewElement().setOwnedLookup(this::ownedQuantity); + getViewElement().setStocks(stockList); + + getViewElement().setOnAction(MarketActions.SORT_TICKER, + () -> getViewElement().setSort(MarketSort.TICKER)); + getViewElement().setOnAction(MarketActions.SORT_PRICE, + () -> getViewElement().setSort(MarketSort.PRICE)); + getViewElement().setOnAction(MarketActions.SORT_CHANGE, + () -> getViewElement().setSort(MarketSort.CHANGE)); + getViewElement().setOnAction(MarketActions.SORT_OWNED, + () -> getViewElement().setSort(MarketSort.OWNED)); + + exchange.weekProperty().addListener((observable, o, n) -> { + getViewElement().renderStocks(); + }); + + player.getNetWorthAsFloatProperty().addListener((observable, o, n) -> { + getViewElement().renderStocks(); + }); + } + + /** + * Returns the total quantity of shares the player owns of a given symbol. + * + * @param symbol the stock symbol. + * + * @return the total quantity, or 0 if the symbol is invalid. + * */ + private int ownedQuantity(final String symbol) { + if (!Validator.VALID_STOCK_NAME.isValid(symbol)) { + return 0; + } + return player.getPortfolio().getShares(symbol).stream() + .map(Share::getQuantity) + .reduce(BigDecimal.ZERO, BigDecimal::add) + .intValue(); + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketSort.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketSort.java new file mode 100644 index 0000000..3bd9805 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketSort.java @@ -0,0 +1,15 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market; + +/** + * Enum representing the available sorting modes in {@link MarketView}. + * */ +public enum MarketSort { + /** Alphabetical sort by ticker symbol. */ + TICKER, + /** Descending sort by current sales price. */ + PRICE, + /** Descending sort by latest change percentage. */ + CHANGE, + /** Descending sort by amount of shares the player owns. */ + OWNED; +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java new file mode 100644 index 0000000..4cef923 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketView.java @@ -0,0 +1,379 @@ +package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.market; + +import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock; +import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.TilePane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Polyline; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.ToIntFunction; +import java.util.stream.Stream; + +/** + * Market widget shown as the center-view of + * {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ingame.InGameView}. + * + *

Layout: a filter-bar with search and sort buttons on top, and a + * scrollable grid of stock cards beneath. Navigation (back, settings, etc.) + * is handled by the shared top bar, so this widget contains no chrome of + * its own and fills the entire center area with no outer padding.

+ * */ +public class MarketView extends ViewElement { + + /** Filter bar elements. */ + private TextField searchField; + private Button sortTickerButton; + private Button sortPriceButton; + private Button sortChangeButton; + private Button sortOwnedButton; + + /** Maps every sort mode to its filter-bar button. Used for active state. */ + private Map sortButtonMap; + + /** Container holding the stock cards. Rebuilt on every render. */ + private TilePane stocksGrid; + + /** Scroll container around the grid. */ + private ScrollPane stocksScroll; + + /** Current list of stocks to display. */ + private List stockList; + + /** Current search term. */ + private String searchTerm; + + /** Current sort mode. */ + private MarketSort currentSort; + + /** Lookup returning how many shares of a given symbol the player owns. */ + private ToIntFunction ownedLookup; + + /** Callback invoked when a stock card is clicked. */ + private Consumer onStockSelected; + + /** Constructor. Widget without its own ViewEnum. */ + public MarketView() { + super(new VBox(), MarketActions.class); + } + + /** {@inheritDoc} */ + @Override + protected void initLayout() { + // Init all state used during layout/render BEFORE building the UI. + // Field initializers don't run until after the super-constructor + // returns, but initLayout is called from inside the super-constructor, + // so we must initialize these here instead of at field declaration. + stockList = new ArrayList<>(); + searchTerm = ""; + currentSort = MarketSort.TICKER; + ownedLookup = s -> 0; + onStockSelected = s -> { }; + sortButtonMap = new EnumMap<>(MarketSort.class); + + VBox root = getRootPane(); + root.setSpacing(8); + root.setFillWidth(true); + root.setPadding(Insets.EMPTY); + + root.getChildren().addAll(buildFilterBar(), buildStocksGrid()); + + setSort(currentSort); + } + + /** + * Builds the search field and the four sort buttons. + * */ + private HBox buildFilterBar() { + searchField = new TextField(); + searchField.setPromptText("search ticker or name..."); + searchField.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(searchField, Priority.ALWAYS); + searchField.textProperty().addListener((obs, o, n) -> { + searchTerm = (n == null) ? "" : n; + renderStocks(); + }); + + sortTickerButton = new Button("A-Z"); + sortPriceButton = new Button("price"); + sortChangeButton = new Button("change"); + sortOwnedButton = new Button("owned"); + + sortButtonMap.put(MarketSort.TICKER, sortTickerButton); + sortButtonMap.put(MarketSort.PRICE, sortPriceButton); + sortButtonMap.put(MarketSort.CHANGE, sortChangeButton); + sortButtonMap.put(MarketSort.OWNED, sortOwnedButton); + + registerButton(MarketActions.SORT_TICKER, sortTickerButton); + registerButton(MarketActions.SORT_PRICE, sortPriceButton); + registerButton(MarketActions.SORT_CHANGE, sortChangeButton); + registerButton(MarketActions.SORT_OWNED, sortOwnedButton); + + HBox filterBar = new HBox(8, + searchField, sortTickerButton, sortPriceButton, + sortChangeButton, sortOwnedButton); + filterBar.setAlignment(Pos.CENTER_LEFT); + filterBar.setPadding(Insets.EMPTY); + + return filterBar; + } + + /** + * Builds the scrollable tile pane that holds the stock cards. + * */ + private ScrollPane buildStocksGrid() { + stocksGrid = new TilePane(); + stocksGrid.setHgap(10); + stocksGrid.setVgap(10); + stocksGrid.setPrefTileWidth(210); + stocksGrid.setPrefTileHeight(120); + stocksGrid.setPadding(Insets.EMPTY); + + stocksScroll = new ScrollPane(stocksGrid); + stocksScroll.setFitToWidth(true); + stocksScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + stocksScroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + VBox.setVgrow(stocksScroll, Priority.ALWAYS); + + return stocksScroll; + } + + /** {@inheritDoc} */ + @Override + protected void initStyling() { + getRootPane().getStyleClass().add("market-container"); + + searchField.getStyleClass().add("market-search"); + Stream.of(sortTickerButton, sortPriceButton, sortChangeButton, sortOwnedButton) + .forEach(b -> b.getStyleClass().add("market-filter-btn")); + + stocksScroll.getStyleClass().add("market-scroll"); + stocksGrid.getStyleClass().add("market-grid"); + } + + /** + * Replaces the list of stocks shown in the grid. + * + * @param stocks the stocks to show. + * */ + public void setStocks(final List stocks) { + this.stockList = (stocks == null) ? new ArrayList<>() : new ArrayList<>(stocks); + renderStocks(); + } + + /** + * Sets the lookup used to figure out how many shares the player owns of a + * given symbol. Called when rendering each stock card. + * + * @param lookup the lookup function. + * */ + public void setOwnedLookup(final ToIntFunction lookup) { + this.ownedLookup = (lookup == null) ? s -> 0 : lookup; + } + + /** + * Sets the handler called when a stock card is clicked. + * + * @param handler callback receiving the selected stock. + * */ + public void setOnStockSelected(final Consumer handler) { + this.onStockSelected = (handler != null) ? handler : s -> { }; + } + + /** + * Getter for the search field, exposed for the controller. + * + * @return the search {@link TextField}. + * */ + public TextField getSearchField() { + return searchField; + } + + /** + * Getter for the currently selected sort mode. + * + * @return current {@link MarketSort}. + * */ + public MarketSort getCurrentSort() { + return currentSort; + } + + /** + * Updates the current sort mode, refreshes the active state of the filter + * buttons, and re-renders the stock grid. + * + * @param sort the new sort mode. + * */ + public void setSort(final MarketSort sort) { + currentSort = (sort == null) ? MarketSort.TICKER : sort; + if (sortButtonMap != null) { + sortButtonMap.values().forEach(b -> b.getStyleClass().remove("active")); + Button active = sortButtonMap.get(currentSort); + if (active != null && !active.getStyleClass().contains("active")) { + active.getStyleClass().add("active"); + } + } + renderStocks(); + } + + /** + * Rebuilds the stock card grid based on the current search term and + * sort mode. + * */ + public void renderStocks() { + if (stocksGrid == null) { + return; + } + stocksGrid.getChildren().clear(); + + String term = (searchTerm == null) ? "" : searchTerm.toLowerCase(); + List filtered = stockList.stream() + .filter(s -> + s.getSymbol().toLowerCase().contains(term) + || s.getCompany().toLowerCase().contains(term)) + .sorted(comparatorFor(currentSort)) + .toList(); + + if (filtered.isEmpty()) { + Label empty = new Label("no stocks match your search"); + empty.getStyleClass().add("market-empty"); + stocksGrid.getChildren().add(empty); + return; + } + + for (Stock stock : filtered) { + stocksGrid.getChildren().add(buildStockCard(stock)); + } + } + + /** + * Builds a comparator for the given sort mode. + * */ + private Comparator comparatorFor(final MarketSort sort) { + return switch (sort) { + case TICKER -> Comparator.comparing(Stock::getSymbol); + case PRICE -> Comparator.comparing(Stock::getSalesPrice).reversed(); + case CHANGE -> Comparator.comparingDouble( + (Stock s) -> changePercent(s)).reversed(); + case OWNED -> Comparator.comparingInt( + (Stock s) -> ownedLookup.applyAsInt(s.getSymbol())).reversed(); + }; + } + + /** + * Calculates the change percentage between the first and last historical + * price of a stock. + * */ + private double changePercent(final Stock stock) { + List prices = stock.getHistoricalPrices(); + if (prices.size() < 2) { + return 0.0; + } + double start = prices.getFirst().doubleValue(); + double end = prices.getLast().doubleValue(); + if (start == 0.0) { + return 0.0; + } + return ((end - start) / start) * 100.0; + } + + /** + * Builds a single stock card. The card is clickable and forwards the + * selection to the registered {@code onStockSelected} consumer. + * */ + private VBox buildStockCard(final Stock stock) { + Label ticker = new Label(stock.getSymbol()); + ticker.getStyleClass().add("market-ticker"); + + Label company = new Label(stock.getCompany()); + company.getStyleClass().add("market-stock-name"); + + VBox tickerBox = new VBox(2, ticker, company); + tickerBox.setAlignment(Pos.TOP_LEFT); + + Label price = new Label(String.format("$%.2f", stock.getSalesPrice().doubleValue())); + price.getStyleClass().add("market-price"); + + Region headerSpacer = new Region(); + HBox.setHgrow(headerSpacer, Priority.ALWAYS); + HBox header = new HBox(tickerBox, headerSpacer, price); + header.setAlignment(Pos.TOP_LEFT); + + Polyline chart = buildMiniChart(stock); + + double change = changePercent(stock); + boolean up = change >= 0; + Label changeBadge = new Label( + (up ? "+" : "") + String.format("%.2f%%", change) + ); + changeBadge.getStyleClass().addAll("market-change", up ? "up" : "down"); + + Region footerSpacer = new Region(); + HBox.setHgrow(footerSpacer, Priority.ALWAYS); + + HBox footer = new HBox(changeBadge, footerSpacer); + footer.setAlignment(Pos.CENTER_LEFT); + + int owned = ownedLookup.applyAsInt(stock.getSymbol()); + if (owned > 0) { + Label ownedBadge = new Label("owned: " + owned); + ownedBadge.getStyleClass().add("market-owned-badge"); + footer.getChildren().add(ownedBadge); + } + + VBox card = new VBox(4, header, chart, footer); + card.setPadding(new Insets(10, 12, 10, 12)); + card.setCursor(Cursor.HAND); + card.getStyleClass().add("market-stock-card"); + card.setOnMouseClicked(e -> onStockSelected.accept(stock)); + return card; + } + + /** + * Builds a small sparkline-style polyline of the stocks historical prices. + * Coloring is applied via CSS based on whether the stock is up or down. + * */ + private Polyline buildMiniChart(final Stock stock) { + Polyline line = new Polyline(); + line.setFill(null); + + double width = 180.0; + double height = 40.0; + + List history = stock.getHistoricalPrices(); + if (history.isEmpty()) { + return line; + } + + double max = history.stream().mapToDouble(BigDecimal::doubleValue).max().orElse(1.0); + double min = history.stream().mapToDouble(BigDecimal::doubleValue).min().orElse(0.0); + double range = Math.max(max - min, 0.0001); + + int n = history.size(); + for (int i = 0; i < n; i++) { + double x = (n == 1) ? 0 : (i / (double) (n - 1)) * width; + double y = height - ((history.get(i).doubleValue() - min) / range) * (height - 6) - 3; + line.getPoints().addAll(x, y); + } + + boolean up = changePercent(stock) >= 0; + line.getStyleClass().addAll("market-mini-chart", up ? "up" : "down"); + return line; + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java index 2f0c344..c8772f0 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarController.java @@ -3,9 +3,20 @@ import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewController; import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum; +import javafx.scene.Node; + +import java.util.function.Consumer; public class TopBarController extends ViewController { + /** + * Whether the market is currently the active center-view. + * + *

When true, the quit/back button returns to the dashboard instead + * of exiting to the main menu.

+ * */ + private boolean inMarketView = false; + /** * {@inheritDoc}. */ @@ -24,4 +35,49 @@ protected void initInteractions() { getViewElement().setOnAction(TopBarActions.SETTINGS, () -> changeScene(ViewEnum.MAIN_MENU)); } + + /** + * Wires the top bar buttons to swap between the dashboard and market + * center-views inside the in-game view. + * + *

After this method is called:

+ *
    + *
  • {@code STATS} swaps the center to the dashboard and resets the + * quit/back button to "Quit".
  • + *
  • {@code MARKET} swaps the center to the market and switches the + * button to "Back".
  • + *
  • {@code EXIT} returns to the dashboard if the market is open, + * otherwise to the main menu.
  • + *
+ * + * @param centerSwitcher callback that swaps the center-view (typically + * {@code inGameView::changeCenterView}). + * @param dashboardCenter root pane of the dashboard widget. + * @param marketCenter root pane of the market widget. + * */ + public void setMarketIntegration(final Consumer centerSwitcher, + final Node dashboardCenter, + final Node marketCenter) { + getViewElement().setOnAction(TopBarActions.EXIT, () -> { + if (inMarketView) { + centerSwitcher.accept(dashboardCenter); + getViewElement().setQuitText("Quit"); + inMarketView = false; + } else { + changeScene(ViewEnum.MAIN_MENU); + } + }); + + getViewElement().setOnAction(TopBarActions.STATS, () -> { + centerSwitcher.accept(dashboardCenter); + getViewElement().setQuitText("Quit"); + inMarketView = false; + }); + + getViewElement().setOnAction(TopBarActions.MARKET, () -> { + centerSwitcher.accept(marketCenter); + getViewElement().setQuitText("Back"); + inMarketView = true; + }); + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarView.java index 0394021..cdb8727 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/topbar/TopBarView.java @@ -74,4 +74,20 @@ protected void initStyling() { Stream.of(quitBtn, statsBtn, marketBtn, settingsBtn) .forEach(b -> b.getStyleClass().add("menu-button")); } + + /** + * Updates the text displayed on the quit/back button. + * + *

The same button is used for both "Quit" (return to the main menu + * from the dashboard) and "Back" (return to the dashboard from the + * market). The controller decides which behaviour is currently active + * and uses this method to keep the label in sync.

+ * + * @param text the label text to show on the button. + * */ + public void setQuitText(final String text) { + if (quitBtn != null) { + quitBtn.setText(text); + } + } } diff --git a/src/main/resources/styles.css b/src/main/resources/styles.css index a55cf3f..c5f1c4a 100644 --- a/src/main/resources/styles.css +++ b/src/main/resources/styles.css @@ -294,4 +294,140 @@ -fx-cursor: hand; -fx-scale-x: 1.05; -fx-scale-y: 1.05; -} \ No newline at end of file +} + +/* ------------- MARKET VIEW ------------- */ +/* Root pane fills the center area with a light translucent background. */ +.market-container { + -fx-background-color: rgba(245, 245, 245, 0.95); +} + +/* Search field. */ +.market-search { + -fx-background-color: rgba(255, 255, 255, 0.9); + -fx-border-color: #bbbbbb; + -fx-border-radius: 12; + -fx-background-radius: 12; + -fx-padding: 7 14; + -fx-font-style: italic; + -fx-font-size: 14; +} + +.market-search:focused { + -fx-border-color: #333333; +} + +/* Sort buttons (A-Z, price, change, owned). */ +.market-filter-btn { + -fx-background-color: white; + -fx-border-color: #cccccc; + -fx-border-radius: 12; + -fx-background-radius: 12; + -fx-padding: 6 14; + -fx-font-style: italic; + -fx-font-weight: bold; + -fx-font-size: 13; + -fx-cursor: hand; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 2, 0, 0, 1); +} + +.market-filter-btn.active { + -fx-background-color: #333333; + -fx-border-color: #333333; + -fx-text-fill: white; +} + +/* Scroll area + tile pane around the cards. */ +.market-scroll { + -fx-background: transparent; + -fx-background-color: transparent; + -fx-padding: 0; +} + +.market-scroll > .viewport { + -fx-background-color: transparent; +} + +.market-grid { + -fx-background-color: transparent; +} + +/* Individual stock card. */ +.market-stock-card { + -fx-background-color: rgba(255, 255, 255, 0.85); + -fx-background-radius: 14; + -fx-border-color: rgba(0, 0, 0, 0.12); + -fx-border-radius: 14; + -fx-cursor: hand; +} + +.market-stock-card:hover { + -fx-background-color: white; + -fx-translate-y: -2; +} + +.market-ticker { + -fx-font-style: italic; + -fx-font-weight: bold; + -fx-font-size: 22; + -fx-text-fill: black; +} + +.market-stock-name { + -fx-font-size: 11; + -fx-text-fill: #555555; + -fx-font-style: italic; +} + +.market-price { + -fx-font-style: italic; + -fx-font-weight: bold; + -fx-font-size: 16; + -fx-text-fill: black; +} + +/* Mini sparkline polyline. */ +.market-mini-chart { + -fx-stroke-width: 2.4; + -fx-stroke-line-cap: round; + -fx-stroke-line-join: round; +} + +.market-mini-chart.up { + -fx-stroke: #27ae60; +} + +.market-mini-chart.down { + -fx-stroke: #c0392b; +} + +/* Change percentage badge (pill). */ +.market-change { + -fx-font-style: italic; + -fx-font-weight: bold; + -fx-font-size: 13; + -fx-padding: 3 12; + -fx-background-radius: 10; + -fx-text-fill: white; +} + +.market-change.up { + -fx-background-color: #27ae60; +} + +.market-change.down { + -fx-background-color: #c0392b; +} + +.market-owned-badge { + -fx-font-style: italic; + -fx-font-size: 11; + -fx-text-fill: #555555; +} + +/* Empty-state label shown when no stocks match the search. */ +.market-empty { + -fx-font-style: italic; + -fx-text-fill: #555555; + -fx-padding: 40; +}