Skip to content

Commit

Permalink
Merge pull request #169 from Team-40-IDATT2003/168-little-fixes
Browse files Browse the repository at this point in the history
fisket litt på små ting
  • Loading branch information
tommyah authored May 27, 2026
2 parents 71d742c + 3d65189 commit b0c509a
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
Expand All @@ -15,11 +14,8 @@
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.StringConverter;

import java.io.File;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;

/**
* View shown after the player clicks "Create new game" in the
Expand Down Expand Up @@ -51,8 +47,8 @@ public class CreateGameView extends ViewElement<StackPane, CreateGameActions> {
/** Filename input field. */
private TextField fileNameField;

/** Starting-capital chooser. */
private ChoiceBox<Double> startingCapitalChoice;
/** Starting-capital input. Free-form numeric entry. */
private TextField startingCapitalField;

/** "Bruk default aksje data" button. */
private Button useDefaultStocksButton;
Expand Down Expand Up @@ -112,13 +108,37 @@ public String getFileName() {
}

/**
* Returns the starting capital currently selected, or {@code null}
* if nothing is selected yet.
* Returns the starting capital currently entered, or {@code null}
* if nothing is entered yet or the input is not a valid positive
* number.
*
* @return the selected starting capital, or {@code null}.
* <p>Accepts both "," and "." as decimal separators and ignores
* whitespace, so "10 000", "10000", "10000.5" and "10000,5" all
* parse to the same value.</p>
*
* @return the entered starting capital, or {@code null} if invalid.
*/
public Double getStartingCapital() {
return startingCapitalChoice.getValue();
if (startingCapitalField == null) {
return null;
}
String raw = startingCapitalField.getText();
if (raw == null) {
return null;
}
String cleaned = raw.replace(" ", "").replace(",", ".").trim();
if (cleaned.isEmpty()) {
return null;
}
try {
double parsed = Double.parseDouble(cleaned);
if (parsed <= 0 || Double.isNaN(parsed) || Double.isInfinite(parsed)) {
return null;
}
return parsed;
} catch (NumberFormatException _) {
return null;
}
}

/**
Expand Down Expand Up @@ -179,8 +199,8 @@ public void resetFields() {
if (fileNameField != null) {
fileNameField.clear();
}
if (startingCapitalChoice != null) {
startingCapitalChoice.getSelectionModel().clearSelection();
if (startingCapitalField != null) {
startingCapitalField.clear();
}
this.stockSelection = StockSelection.NONE;
this.customStockFile = null;
Expand Down Expand Up @@ -208,17 +228,10 @@ protected void initLayout() {
// Row 2 - starting capital
Label capitalLabel = new Label("Start capital");
capitalLabel.getStyleClass().add("create-game-label");
startingCapitalChoice = new ChoiceBox<>();
startingCapitalChoice.getItems().addAll(
1_000.0,
10_000.0,
100_000.0,
1_000_000.0,
10_000_000.0
);
startingCapitalChoice.setConverter(buildCapitalConverter());
startingCapitalChoice.getStyleClass().add("create-game-input");
VBox capitalRow = new VBox(6, capitalLabel, startingCapitalChoice);
startingCapitalField = new TextField();
startingCapitalField.setPromptText("Set starting capital (NOK)...");
startingCapitalField.getStyleClass().add("create-game-input");
VBox capitalRow = new VBox(6, capitalLabel, startingCapitalField);

// Row 3 - stock data choice (two buttons side by side)
Label stockLabel = new Label("Stock data");
Expand Down Expand Up @@ -275,7 +288,7 @@ protected void initLayout() {
ChangeListener<Object> enableListener = (obs, oldV, newV) ->
refreshCreateButtonState();
fileNameField.textProperty().addListener(enableListener);
startingCapitalChoice.valueProperty().addListener(enableListener);
startingCapitalField.textProperty().addListener(enableListener);

registerButton(CreateGameActions.USE_DEFAULT_STOCKS,
useDefaultStocksButton);
Expand Down Expand Up @@ -335,27 +348,4 @@ private void refreshCreateButtonState() {
? "create-game-button-enabled"
: "create-game-button-disabled");
}

/**
* Builds a {@link StringConverter} that renders the starting-capital
* values in the choice box using a "1 000 kr"-style format instead
* of the default raw double output.
*/
private StringConverter<Double> buildCapitalConverter() {
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setGroupingSeparator(' ');
final DecimalFormat format = new DecimalFormat("#,##0", symbols);
return new StringConverter<>() {
@Override
public String toString(final Double value) {
return value == null ? "" : format.format(value) + " kr";
}

@Override
public Double fromString(final String text) {
// Not used - the choice box is not editable.
return null;
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public enum MarketActions {
SORT_TICKER,
/** Sort stocks by current price (descending). */
SORT_PRICE,
/** Sort stocks by latest change (descending). */
/** Cycle the change-filter between winners-only and losers-only. */
SORT_CHANGE,
/** Sort stocks by amount owned (descending). */
/** Show only stocks the player currently owns. */
SORT_OWNED;
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,17 @@ protected void initInteractions() {
() -> getViewElement().setSort(MarketSort.TICKER));
getViewElement().setOnAction(MarketActions.SORT_PRICE,
() -> getViewElement().setSort(MarketSort.PRICE));
// The change button cycles between showing only winners and only
// losers - never both at once. The first press defaults to winners
// (the most common "what's going up?" question) and each subsequent
// press flips to the opposite side.
getViewElement().setOnAction(MarketActions.SORT_CHANGE,
() -> getViewElement().setSort(MarketSort.CHANGE));
() -> {
MarketSort next =
(getViewElement().getCurrentSort() == MarketSort.WINNERS)
? MarketSort.LOSERS : MarketSort.WINNERS;
getViewElement().setSort(next);
});
getViewElement().setOnAction(MarketActions.SORT_OWNED,
() -> getViewElement().setSort(MarketSort.OWNED));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@

/**
* Enum representing the available sorting modes in {@link MarketView}.
*
* <p>{@link #WINNERS} and {@link #LOSERS} are mutually exclusive views
* of the "change" filter button: pressing the button cycles between
* the two. {@link #OWNED} acts as a "show only owned" filter rather
* than a pure sort.</p>
* */
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. */
/** Show only stocks with positive change, sorted descending. */
WINNERS,
/** Show only stocks with negative change, sorted ascending. */
LOSERS,
/** Show only stocks the player currently owns, sorted by quantity. */
OWNED;
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ private HBox buildFilterBar() {

sortButtonMap.put(MarketSort.TICKER, sortTickerButton);
sortButtonMap.put(MarketSort.PRICE, sortPriceButton);
sortButtonMap.put(MarketSort.CHANGE, sortChangeButton);
// WINNERS and LOSERS share the same button, the controller cycles
// between them on each press.
sortButtonMap.put(MarketSort.WINNERS, sortChangeButton);
sortButtonMap.put(MarketSort.LOSERS, sortChangeButton);
sortButtonMap.put(MarketSort.OWNED, sortOwnedButton);

registerButton(MarketActions.SORT_TICKER, sortTickerButton);
Expand Down Expand Up @@ -218,6 +221,10 @@ public MarketSort getCurrentSort() {
* Updates the current sort mode, refreshes the active state of the filter
* buttons, and re-renders the stock grid.
*
* <p>The change button doubles as a winners/losers cycle - its label
* updates to reflect the active mode so the user can tell which one
* they're currently looking at.</p>
*
* @param sort the new sort mode.
* */
public void setSort(final MarketSort sort) {
Expand All @@ -229,12 +236,34 @@ public void setSort(final MarketSort sort) {
active.getStyleClass().add("active");
}
}
refreshChangeButtonLabel();
renderStocks();
}

/**
* Updates the label on the change button to reflect which filter mode
* is currently active. The button cycles winners -> losers -> winners
* on press, so showing the active mode makes the cycle discoverable.
* */
private void refreshChangeButtonLabel() {
if (sortChangeButton == null) {
return;
}
sortChangeButton.setText(switch (currentSort) {
case WINNERS -> "winners";
case LOSERS -> "losers";
default -> "change";
});
}

/**
* Rebuilds the stock card grid based on the current search term and
* sort mode.
*
* <p>{@link MarketSort#WINNERS}, {@link MarketSort#LOSERS} and
* {@link MarketSort#OWNED} act as filters: in those modes the grid
* only shows the matching subset of stocks. The other modes show
* every stock that matches the search term.</p>
* */
public void renderStocks() {
if (stocksGrid == null) {
Expand All @@ -247,11 +276,12 @@ public void renderStocks() {
.filter(s ->
s.getSymbol().toLowerCase().contains(term)
|| s.getCompany().toLowerCase().contains(term))
.filter(this::matchesSortFilter)
.sorted(comparatorFor(currentSort))
.toList();

if (filtered.isEmpty()) {
Label empty = new Label("no stocks match your search");
Label empty = new Label(emptyMessageFor(currentSort));
empty.getStyleClass().add("market-empty");
stocksGrid.getChildren().add(empty);
return;
Expand All @@ -262,15 +292,45 @@ public void renderStocks() {
}
}

/**
* Filter predicate that strips out stocks that don't belong in the
* current mode. Sort-only modes (TICKER, PRICE) keep every stock;
* the others filter by change sign or by ownership.
* */
private boolean matchesSortFilter(final Stock stock) {
return switch (currentSort) {
case WINNERS -> changePercent(stock) > 0;
case LOSERS -> changePercent(stock) < 0;
case OWNED -> ownedLookup.applyAsInt(stock.getSymbol()) > 0;
default -> true;
};
}

/**
* Returns the empty-state message that fits the current sort mode,
* so the user knows whether they actually have no matches or whether
* the filter excluded everything.
* */
private String emptyMessageFor(final MarketSort sort) {
return switch (sort) {
case WINNERS -> "no winners right now";
case LOSERS -> "no losers right now";
case OWNED -> "you don't own any stocks yet";
default -> "no stocks match your search";
};
}

/**
* Builds a comparator for the given sort mode.
* */
private Comparator<Stock> comparatorFor(final MarketSort sort) {
return switch (sort) {
case TICKER -> Comparator.comparing(Stock::getSymbol);
case PRICE -> Comparator.comparing(Stock::getSalesPrice).reversed();
case CHANGE -> Comparator.comparingDouble(
case WINNERS -> Comparator.comparingDouble(
(Stock s) -> changePercent(s)).reversed();
case LOSERS -> Comparator.comparingDouble(
(Stock s) -> changePercent(s));
case OWNED -> Comparator.comparingInt(
(Stock s) -> ownedLookup.applyAsInt(s.getSymbol())).reversed();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ protected void initLayout() {
@Override
protected void initStyling() {
getRootPane().getStyleClass().add("find-stock-minigame-root");
targetLabel.getStyleClass().add("find-stock-minigame-target");
grid.getStyleClass().add("find-stock-minigame-grid");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ private VBox buildKpiTile(final String labelText,
Label label = new Label(labelText);
label.getStyleClass().add("stats-kpi-label");
valueLabel.getStyleClass().add("stats-kpi-value");
// Apply the shared sub-label styling here so every tile picks up
// the readable size/colour. The up/down tint added later in
// renderKpis() composes on top of this base.
if (!subLabel.getStyleClass().contains("stats-kpi-sub")) {
subLabel.getStyleClass().add("stats-kpi-sub");
}

VBox tile = new VBox(2, label, valueLabel, subLabel);
tile.getStyleClass().add("stats-kpi");
Expand Down
11 changes: 11 additions & 0 deletions src/main/resources/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,13 @@
-fx-alignment: CENTER;
}

.find-stock-minigame-target {
-fx-font-family: "Aptos";
-fx-font-weight: bold;
-fx-font-size: 22px;
-fx-text-fill: #f0f4ff;
}

.find-stock-minigame-grid {
-fx-hgap: 15px;
-fx-vgap: 15px;
Expand Down Expand Up @@ -2381,6 +2388,10 @@
-fx-effect: dropshadow(gaussian, rgba(10, 132, 217, 0.55), 14, 0.35, 0, 0);
}

.light-mode .find-stock-minigame-target {
-fx-text-fill: #0f1b34;
}

/* ---------- IN-GAME : SETTINGS OVERLAY ---------- */
.light-mode .ingame-settings-overlay-dimmer {
-fx-background-color: rgba(10, 27, 52, 0.35);
Expand Down

0 comments on commit b0c509a

Please sign in to comment.