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;
+}