Files
Progetto-ingegneria-del-sof…/deliverables/Coverage/htmlReport/ns-12/sources/source-5.html
T
2026-06-19 21:50:38 +02:00

903 lines
47 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html id="htmlId">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Coverage Report > MainFXMLController</title>
<style type="text/css">
@import "../../css/coverage.css";
@import "../../css/idea.min.css";
</style>
<script type="text/javascript" src="../../js/highlight.min.js"></script>
<script type="text/javascript" src="../../js/highlightjs-line-numbers.min.js"></script>
</head>
<body>
<div class="content">
<div class="breadCrumbs">
Current scope: <a href="../../index.html">all classes</a>
<span class="separator">|</span>
<a href="../index.html">it.polimi.ingsw.gc14.View.GUI</a>
</div>
<h1>Coverage Summary for Class: MainFXMLController (it.polimi.ingsw.gc14.View.GUI)</h1>
<table class="coverageStats">
<tr>
<th class="name">Class</th>
<th class="coverageStat
">
Class, %
</th>
<th class="coverageStat
">
Method, %
</th>
<th class="coverageStat
">
Branch, %
</th>
<th class="coverageStat
">
Line, %
</th>
</tr>
<tr>
<td class="name">MainFXMLController</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/1)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/50)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/124)
</span>
</td>
<td class="coverageStat">
<span class="percent">
0%
</span>
<span class="absValue">
(0/426)
</span>
</td>
</tr>
</table>
<br/>
<br/>
<pre>
<code class="sourceCode" id="sourceCode">&nbsp;package it.polimi.ingsw.gc14.View.GUI;
&nbsp;
&nbsp;import it.polimi.ingsw.gc14.Controller.ClientController;
&nbsp;import it.polimi.ingsw.gc14.Model.*;
&nbsp;import it.polimi.ingsw.gc14.Model.Cards.BuildingCard;
&nbsp;import it.polimi.ingsw.gc14.Model.Cards.TribeCard;
&nbsp;import it.polimi.ingsw.gc14.Model.Orders.OrderPlayer;
&nbsp;import it.polimi.ingsw.gc14.Network.EventType;
&nbsp;import it.polimi.ingsw.gc14.Network.NetworkEvent;
&nbsp;import javafx.animation.ScaleTransition;
&nbsp;import javafx.animation.TranslateTransition;
&nbsp;import javafx.application.Platform;
&nbsp;import javafx.beans.value.ChangeListener;
&nbsp;import javafx.beans.value.ObservableValue;
&nbsp;import javafx.event.Event;
&nbsp;import javafx.fxml.FXML;
&nbsp;import javafx.geometry.Bounds;
&nbsp;import javafx.geometry.Insets;
&nbsp;import javafx.geometry.Pos;
&nbsp;import javafx.geometry.Rectangle2D;
&nbsp;import javafx.scene.Cursor;
&nbsp;import javafx.scene.Node;
&nbsp;import javafx.scene.Scene;
&nbsp;import javafx.scene.control.Button;
&nbsp;import javafx.scene.control.Label;
&nbsp;import javafx.scene.control.ScrollPane;
&nbsp;import javafx.scene.effect.ColorAdjust;
&nbsp;import javafx.scene.effect.DropShadow;
&nbsp;import javafx.scene.image.*;
&nbsp;import javafx.scene.input.MouseEvent;
&nbsp;import javafx.scene.layout.*;
&nbsp;import javafx.scene.paint.Color;
&nbsp;import javafx.scene.shape.*;
&nbsp;import javafx.scene.text.Font;
&nbsp;import javafx.stage.Popup;
&nbsp;import javafx.stage.Screen;
&nbsp;import javafx.stage.Window;
&nbsp;import javafx.util.Duration;
&nbsp;
&nbsp;import java.util.*;
&nbsp;
&nbsp;/**
&nbsp; * FXML controller for the main game scene.
&nbsp; *
&nbsp; * &lt;p&gt;Renders the board (upper/lower tribe and building card rows), player stats,
&nbsp; * the player&#39;s hand, and action buttons (skip, details).
&nbsp; */
<b class="nc">&nbsp;public class MainFXMLController {</b>
&nbsp; //TODO
&nbsp; @FXML private GridPane leftGrid;
&nbsp; //TODO
&nbsp; @FXML private ScrollPane playerSide;
&nbsp; //TODO
&nbsp; @FXML private HBox mainHBox;
&nbsp; //TODO
&nbsp; @FXML private ImageView backgroundImage;
&nbsp; //TODO
&nbsp; @FXML private HBox board;
&nbsp; //TODO
&nbsp; @FXML private HBox upperList;
&nbsp; //TODO
&nbsp; @FXML private HBox lowerList;
&nbsp; //TODO
&nbsp; @FXML private HBox myHand;
&nbsp; //TODO
&nbsp; @FXML private Button skipBtn;
&nbsp; //TODO
&nbsp; @FXML private Button detailsBtn;
&nbsp; //TODO
&nbsp; @FXML private Label infoText;
&nbsp; //TODO
<b class="nc">&nbsp; private final Map&lt;String, Label&gt; foodLabels = new HashMap&lt;&gt;();</b>
&nbsp; //TODO
<b class="nc">&nbsp; private final Map&lt;String, Label&gt; prestigeLabels = new HashMap&lt;&gt;();</b>
&nbsp; //TODO
<b class="nc">&nbsp; private final Map&lt;String, VBox&gt; playerCards = new HashMap&lt;&gt;();</b>
&nbsp; //TODO
<b class="nc">&nbsp; private final Map&lt;String, List&lt;ImageView&gt;&gt; iconViews = new HashMap&lt;&gt;();</b>
&nbsp; //TODO
&nbsp; private Popup popup;
&nbsp; //TODO
&nbsp; private HBox popupCards;
&nbsp; //TODO
&nbsp; private ClientController controller;
&nbsp;
&nbsp; /** {@code true} when the last received event was an error response. */
&nbsp; private boolean isError;
&nbsp;
&nbsp; /** Sets the error flag; called by {@link GUI} before delegating to {@link #render()}. */
<b class="nc">&nbsp; public void setError(boolean error) { this.isError = error; }</b>
&nbsp;
&nbsp; // ==== IMAGE CACHE ====
<b class="nc">&nbsp; private static final Map&lt;String, Image&gt; imageCache = new HashMap&lt;&gt;();</b>
&nbsp;
&nbsp; /** Returns a cached {@link Image} for the given classpath {@code path}, loading it on first access. */
&nbsp; private Image loadImage(String path) {
<b class="nc">&nbsp; return imageCache.computeIfAbsent(path,</b>
<b class="nc">&nbsp; p -&gt; new Image(getClass().getResourceAsStream(p)));</b>
&nbsp; }
&nbsp;
&nbsp; /**
&nbsp; * Injects the client controller and wires up the skip-turn button action.
&nbsp; *
&nbsp; * @param controller the client controller to use.
&nbsp; */
&nbsp; public void setController(ClientController controller) {
<b class="nc">&nbsp; this.controller = controller;</b>
<b class="nc">&nbsp; skipBtn.setOnAction(e -&gt; controller.skipTurn());</b>
&nbsp; }
&nbsp;
&nbsp; /** Initializes the scene: loads fonts, sets up the popup, and registers input listeners. */
&nbsp; @FXML
&nbsp; public void initialize() {
<b class="nc">&nbsp; Font.loadFont(getClass().getResourceAsStream(&quot;/Fonts/InknutAntiqua-Regular.ttf&quot;), 14);</b>
&nbsp;
<b class="nc">&nbsp; addHoverZoom(skipBtn);</b>
<b class="nc">&nbsp; addHoverZoom(detailsBtn);</b>
<b class="nc">&nbsp; popup = new Popup();</b>
<b class="nc">&nbsp; mainHBox.sceneProperty().addListener((obs, oldScene, newScene) -&gt; {</b>
<b class="nc">&nbsp; if (newScene != null) {</b>
<b class="nc">&nbsp; newScene.addEventFilter(MouseEvent.MOUSE_PRESSED, e -&gt; {</b>
<b class="nc">&nbsp; if (popup.isShowing()) popup.hide();</b>
&nbsp; });
<b class="nc">&nbsp; newScene.getRoot().applyCss();</b>
<b class="nc">&nbsp; newScene.windowProperty().addListener((o2, ow, nw) -&gt; {</b>
<b class="nc">&nbsp; if (nw != null) {</b>
<b class="nc">&nbsp; nw.focusedProperty().addListener((o3, wf, nf) -&gt; {</b>
<b class="nc">&nbsp; if (!nf) popup.hide();</b>
&nbsp; });
&nbsp; }
&nbsp; });
&nbsp; }
&nbsp; else {
<b class="nc">&nbsp; popup.hide();</b>
&nbsp; }
&nbsp; });
<b class="nc">&nbsp; initPopup();</b>
<b class="nc">&nbsp; detailsBtn.setOnAction(e -&gt; openDetailsPopup());</b>
<b class="nc">&nbsp; renderBackground();</b>
<b class="nc">&nbsp; isError=false;</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== RENDER ====
&nbsp; /** Incrementally re-renders only the parts of the scene affected by the last network event. */
&nbsp; public void render() {
<b class="nc">&nbsp; if(foodLabels.isEmpty() || controller.getMiniModel().lastEvent == null</b>
<b class="nc">&nbsp; || controller.getMiniModel().lastEvent.getEventType().equals(EventType.TOTEM_CHOICE))</b>
<b class="nc">&nbsp; buildSidePanel();</b>
<b class="nc">&nbsp; if(isError)</b>
&nbsp; {
<b class="nc">&nbsp; updateSidePanel();</b>
<b class="nc">&nbsp; isError=false;</b>
&nbsp; return;
&nbsp; }
<b class="nc">&nbsp; if(controller.getMiniModel().lastEvent==null ||controller.getMiniModel().lastEvent.getEventType().equals(EventType.TOTEM_CHOICE) || controller.getMiniModel().lastEvent.getEventType().equals(EventType.NEXT_ROUND)) {</b>
<b class="nc">&nbsp; renderMyHand();</b>
<b class="nc">&nbsp; renderUpper();</b>
<b class="nc">&nbsp; renderBoard();</b>
<b class="nc">&nbsp; renderLower();</b>
<b class="nc">&nbsp; updateSidePanel();</b>
&nbsp; return;
&nbsp; }
<b class="nc">&nbsp; if(controller.getMiniModel().lastEvent.getEventType().equals(EventType.DRAW_UPPER_TRIBE) || controller.getMiniModel().lastEvent.getEventType().equals(EventType.DRAW_UPPER_BUILD))</b>
&nbsp; {
<b class="nc">&nbsp; renderUpper();</b>
&nbsp; }
<b class="nc">&nbsp; if(controller.getMiniModel().lastEvent.getEventType().equals(EventType.DRAW_LOWER_TRIBE) || controller.getMiniModel().lastEvent.getEventType().equals(EventType.DRAW_LOWER_BUILD))</b>
&nbsp; {
<b class="nc">&nbsp; renderLower();</b>
&nbsp; }
<b class="nc">&nbsp; if(controller.getMiniModel().lastEvent.getUsername().equals(controller.getMyUsername()))</b>
&nbsp; {
<b class="nc">&nbsp; renderMyHand();</b>
&nbsp; }
<b class="nc">&nbsp; renderBoard();</b>
<b class="nc">&nbsp; updateSidePanel();</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== EFFECTS ====
&nbsp; /** Binds a rounded-rectangle clip to {@code img} so its corners are cropped. */
&nbsp; private void addClip(ImageView img) {
<b class="nc">&nbsp; Rectangle clip = new Rectangle();</b>
<b class="nc">&nbsp; clip.setArcWidth(20);</b>
<b class="nc">&nbsp; clip.setArcHeight(20);</b>
<b class="nc">&nbsp; img.setClip(clip);</b>
<b class="nc">&nbsp; clip.heightProperty().bind(img.layoutBoundsProperty().map(b -&gt; b.getHeight()));</b>
<b class="nc">&nbsp; clip.widthProperty().bind(img.layoutBoundsProperty().map(b -&gt; b.getWidth()));</b>
&nbsp; }
&nbsp;
&nbsp; /** Scales {@code node} to 1.1× on hover and restores on exit; sets a hand cursor. */
&nbsp; private void addHoverZoom(Node node) {
<b class="nc">&nbsp; ScaleTransition scaleUp = new ScaleTransition(Duration.millis(150), node);</b>
<b class="nc">&nbsp; scaleUp.setToX(1.1);</b>
<b class="nc">&nbsp; scaleUp.setToY(1.1);</b>
<b class="nc">&nbsp; ScaleTransition scaleDown = new ScaleTransition(Duration.millis(150), node);</b>
<b class="nc">&nbsp; scaleDown.setToX(1.0);</b>
<b class="nc">&nbsp; scaleDown.setToY(1.0);</b>
<b class="nc">&nbsp; node.addEventHandler(MouseEvent.MOUSE_ENTERED, e -&gt; { scaleUp.play(); node.setCursor(Cursor.HAND); });</b>
<b class="nc">&nbsp; node.addEventHandler(MouseEvent.MOUSE_EXITED, e -&gt; { scaleDown.play(); node.setCursor(Cursor.DEFAULT); });</b>
&nbsp; }
&nbsp;
&nbsp; /** Applies a drop-shadow to {@code node} that intensifies on hover. */
&nbsp; private void addShadow(Node node) {
<b class="nc">&nbsp; DropShadow shadow = new DropShadow();</b>
<b class="nc">&nbsp; shadow.setColor(Color.rgb(0, 0, 0, 0.3));</b>
<b class="nc">&nbsp; shadow.setRadius(6);</b>
<b class="nc">&nbsp; shadow.setOffsetX(2);</b>
<b class="nc">&nbsp; shadow.setOffsetY(2);</b>
<b class="nc">&nbsp; node.setEffect(shadow);</b>
<b class="nc">&nbsp; node.addEventHandler(MouseEvent.MOUSE_ENTERED, e -&gt; {</b>
<b class="nc">&nbsp; shadow.setRadius(16);</b>
<b class="nc">&nbsp; shadow.setOffsetX(4);</b>
<b class="nc">&nbsp; shadow.setOffsetY(4);</b>
<b class="nc">&nbsp; shadow.setColor(Color.rgb(0, 0, 0, 0.5));</b>
&nbsp; });
<b class="nc">&nbsp; node.addEventHandler(MouseEvent.MOUSE_EXITED, e -&gt; {</b>
<b class="nc">&nbsp; shadow.setRadius(6);</b>
<b class="nc">&nbsp; shadow.setOffsetX(2);</b>
<b class="nc">&nbsp; shadow.setOffsetY(2);</b>
<b class="nc">&nbsp; shadow.setColor(Color.rgb(0, 0, 0, 0.3));</b>
&nbsp; });
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== ELEMENTS ====
&nbsp;
&nbsp; /** Builds the side-panel card widget for {@code player} with totem, stats, and card-type icons. */
&nbsp; private VBox buildPlayerCard(Player player) {
<b class="nc">&nbsp; VBox card = new VBox(3);</b>
<b class="nc">&nbsp; VBox.setMargin(card, new Insets(15, 15, 0, 15));</b>
<b class="nc">&nbsp; card.getStyleClass().add(&quot;player-card&quot;);</b>
<b class="nc">&nbsp; card.setPadding(new Insets(4));</b>
&nbsp;
<b class="nc">&nbsp; HBox headerRow = new HBox(8);</b>
<b class="nc">&nbsp; headerRow.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; ImageView totem = new ImageView(loadImage(&quot;/GUIImages/Totems/totem_&quot;</b>
<b class="nc">&nbsp; + player.getTotem().toString().toLowerCase(Locale.ROOT) + &quot;.png&quot;));</b>
<b class="nc">&nbsp; totem.setFitHeight(20);</b>
<b class="nc">&nbsp; totem.setPreserveRatio(true);</b>
<b class="nc">&nbsp; Label usernameLabel = new Label(player.getUserName());</b>
<b class="nc">&nbsp; usernameLabel.getStyleClass().add(&quot;label-medium&quot;);</b>
<b class="nc">&nbsp; usernameLabel.setStyle(&quot;-fx-text-fill: #711423;&quot;);</b>
<b class="nc">&nbsp; if (player.getUserName().equals(controller.getMyUsername())) {</b>
<b class="nc">&nbsp; usernameLabel.setText(usernameLabel.getText() + &quot; (you)&quot;);</b>
&nbsp; }
<b class="nc">&nbsp; if (controller.getMiniModel().disconnectedPlayers.contains(player.getUserName())) {</b>
<b class="nc">&nbsp; card.getStyleClass().add(&quot;player-card-crashed&quot;);</b>
&nbsp; }
<b class="nc">&nbsp; headerRow.getChildren().addAll(totem, usernameLabel);</b>
&nbsp;
<b class="nc">&nbsp; HBox statsRow = new HBox(10);</b>
<b class="nc">&nbsp; statsRow.setAlignment(Pos.CENTER);</b>
&nbsp;
<b class="nc">&nbsp; HBox foodBox = new HBox(4);</b>
<b class="nc">&nbsp; foodBox.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; ImageView foodIcon = new ImageView(loadImage(&quot;/GUIImages/Icons/Food.png&quot;));</b>
<b class="nc">&nbsp; foodIcon.setFitHeight(20);</b>
<b class="nc">&nbsp; foodIcon.setPreserveRatio(true);</b>
<b class="nc">&nbsp; Label foodLabel = new Label(String.valueOf(player.getFoodValue()));</b>
<b class="nc">&nbsp; foodLabel.getStyleClass().add(&quot;label-small&quot;);</b>
<b class="nc">&nbsp; foodLabels.put(player.getUserName(), foodLabel);</b>
<b class="nc">&nbsp; foodBox.getChildren().addAll(foodIcon, foodLabel);</b>
&nbsp;
<b class="nc">&nbsp; HBox prestigeBox = new HBox(1);</b>
<b class="nc">&nbsp; prestigeBox.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; ImageView ppIcon = new ImageView(loadImage(&quot;/GUIImages/Icons/PrestigePoint.png&quot;));</b>
<b class="nc">&nbsp; ppIcon.setFitHeight(35);</b>
<b class="nc">&nbsp; ppIcon.setPreserveRatio(true);</b>
<b class="nc">&nbsp; Label prestigeLabel = new Label(String.valueOf(player.getPrestigeValue()));</b>
<b class="nc">&nbsp; prestigeLabel.getStyleClass().add(&quot;label-small&quot;);</b>
<b class="nc">&nbsp; prestigeLabels.put(player.getUserName(), prestigeLabel);</b>
<b class="nc">&nbsp; prestigeBox.getChildren().addAll(ppIcon, prestigeLabel);</b>
&nbsp;
<b class="nc">&nbsp; ImageView buildingIcon = new ImageView(loadImage(&quot;/GUIImages/Icons/Building.png&quot;));</b>
<b class="nc">&nbsp; buildingIcon.setFitWidth(28);</b>
<b class="nc">&nbsp; buildingIcon.setFitHeight(28);</b>
<b class="nc">&nbsp; buildingIcon.setPreserveRatio(true);</b>
<b class="nc">&nbsp; buildingIcon.setOnMouseClicked(e -&gt; {</b>
<b class="nc">&nbsp; ArrayList&lt;PlayableCard&gt; cards = getPlayerCards(player.getUserName(), &quot;building&quot;);</b>
<b class="nc">&nbsp; if (!cards.isEmpty()) openPopup(cards);</b>
&nbsp; });
<b class="nc">&nbsp; addHoverZoom(buildingIcon);</b>
&nbsp;
<b class="nc">&nbsp; statsRow.getChildren().addAll(foodBox, prestigeBox, buildingIcon);</b>
&nbsp;
<b class="nc">&nbsp; HBox iconsRow1 = new HBox(6);</b>
<b class="nc">&nbsp; iconsRow1.setSpacing(20);</b>
<b class="nc">&nbsp; iconsRow1.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; HBox iconsRow2 = new HBox(6);</b>
<b class="nc">&nbsp; iconsRow2.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; iconsRow2.setSpacing(20);</b>
&nbsp;
<b class="nc">&nbsp; String[][] iconTypes = {</b>
&nbsp; {&quot;Artist&quot;, &quot;artists&quot;},
&nbsp; {&quot;Gatherer&quot;, &quot;gatherers&quot;},
&nbsp; {&quot;Inventor&quot;, &quot;inventors&quot;},
&nbsp; {&quot;Builder&quot;, &quot;builders&quot;},
&nbsp; {&quot;Shaman&quot;, &quot;shamans&quot;},
&nbsp; {&quot;Hunter&quot;, &quot;hunters&quot;}
&nbsp; };
&nbsp;
<b class="nc">&nbsp; List&lt;ImageView&gt; icons = new ArrayList&lt;&gt;();</b>
&nbsp; // building must be index 0; updateSidePanel iterates icons in [&quot;building&quot;,&quot;artists&quot;,...] order
<b class="nc">&nbsp; icons.add(buildingIcon);</b>
&nbsp;
<b class="nc">&nbsp; for (int i = 0; i &lt; iconTypes.length; i++) {</b>
<b class="nc">&nbsp; String iconName = iconTypes[i][0];</b>
<b class="nc">&nbsp; String fieldName = iconTypes[i][1];</b>
&nbsp;
<b class="nc">&nbsp; ImageView icon = new ImageView(loadImage(&quot;/GUIImages/Icons/&quot; + iconName + &quot;.png&quot;));</b>
<b class="nc">&nbsp; icon.setFitWidth(28);</b>
<b class="nc">&nbsp; icon.setFitHeight(28);</b>
<b class="nc">&nbsp; icon.setPreserveRatio(true);</b>
<b class="nc">&nbsp; icon.setOnMouseClicked(e -&gt; {</b>
<b class="nc">&nbsp; ArrayList&lt;PlayableCard&gt; cards = getPlayerCards(player.getUserName(), fieldName);</b>
<b class="nc">&nbsp; if (!cards.isEmpty()) openPopup(cards);</b>
&nbsp; });
<b class="nc">&nbsp; addHoverZoom(icon);</b>
<b class="nc">&nbsp; icons.add(icon);</b>
&nbsp;
<b class="nc">&nbsp; if (i &lt; 3) iconsRow1.getChildren().add(icon);</b>
<b class="nc">&nbsp; else iconsRow2.getChildren().add(icon);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; iconViews.put(player.getUserName(), icons);</b>
&nbsp;
<b class="nc">&nbsp; card.getChildren().addAll(headerRow, statsRow, iconsRow1, iconsRow2);</b>
<b class="nc">&nbsp; return card;</b>
&nbsp; }
&nbsp;
&nbsp; /** Creates a {@link StackPane} containing the order card image for a game of {@code num} players. */
&nbsp; private StackPane createOrder(String num) {
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Orders/order-&quot; + num + &quot;.png&quot;));</b>
<b class="nc">&nbsp; img.fitHeightProperty().bind(board.sceneProperty().get().heightProperty().subtract(56).divide(4).multiply(0.94));</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; return new StackPane(img);</b>
&nbsp; }
&nbsp;
&nbsp; /** Creates a {@link StackPane} for a board slot, optionally overlaying the occupying player&#39;s totem. */
&nbsp; private StackPane createSlot(Slot slot, boolean withZoom, boolean withShadow) {
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Fronts/card-&quot; + slot.getSlotId() + &quot;.png&quot;));</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; img.fitHeightProperty().bind(board.sceneProperty().get().heightProperty().subtract(56).divide(4).multiply(0.94));</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; StackPane wrapper = new StackPane(img);</b>
&nbsp;
<b class="nc">&nbsp; Player player = controller.getMiniModel().slotPlayerMap.get(slot);</b>
<b class="nc">&nbsp; if (player != null &amp;&amp; controller.getMiniModel().getPositionByUsername(player.getUserName()) != -1) {</b>
<b class="nc">&nbsp; ImageView totem = new ImageView(loadImage(&quot;/GUIImages/Totems/totem_&quot; + player.getTotem().toString().toLowerCase(Locale.ROOT) + &quot;.png&quot;));</b>
<b class="nc">&nbsp; totem.fitHeightProperty().bind(img.fitHeightProperty().multiply(0.332));</b>
<b class="nc">&nbsp; totem.setPreserveRatio(true);</b>
<b class="nc">&nbsp; StackPane.setAlignment(totem, Pos.TOP_LEFT);</b>
<b class="nc">&nbsp; img.fitHeightProperty().addListener((obs, ov, nv) -&gt; {</b>
<b class="nc">&nbsp; StackPane.setMargin(totem, new Insets(0, 0, 0, 0.224 * nv.doubleValue()));</b>
&nbsp; });
<b class="nc">&nbsp; if (img.getFitHeight() &gt; 0) {</b>
<b class="nc">&nbsp; StackPane.setMargin(totem, new Insets(0, 0, 0, 0.224 * img.getFitHeight()));</b>
&nbsp; }
<b class="nc">&nbsp; wrapper.getChildren().add(totem);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; if (withShadow) addShadow(wrapper);</b>
<b class="nc">&nbsp; if (withZoom) addHoverZoom(wrapper);</b>
<b class="nc">&nbsp; return wrapper;</b>
&nbsp; }
&nbsp;
&nbsp; /** Creates a {@link StackPane} for a playable card, scaling its height to a fraction of {@code parent}&#39;s scene. */
&nbsp; private StackPane createCard(PlayableCard card, boolean withZoom, boolean withShadow, Region parent) {
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Fronts/card-&quot; + card.getIdIMG() + &quot;.png&quot;));</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; if(controller.getMiniModel().players.size()==5)</b>
&nbsp; {
<b class="nc">&nbsp; img.fitHeightProperty().bind(parent.getScene().heightProperty().subtract(56).divide(4).multiply(0.85));</b>
&nbsp; }
&nbsp; else
<b class="nc">&nbsp; img.fitHeightProperty().bind(parent.getScene().heightProperty().subtract(56).divide(4).multiply(0.90));</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; StackPane wrapper = new StackPane(img);</b>
<b class="nc">&nbsp; if (withShadow) addShadow(wrapper);</b>
<b class="nc">&nbsp; if (withZoom) addHoverZoom(wrapper);</b>
<b class="nc">&nbsp; return wrapper;</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp;
&nbsp; // ==== GROUPS ====
&nbsp; /** Builds the full side panel from scratch, creating a player card for each connected player. */
&nbsp; private void buildSidePanel() {
<b class="nc">&nbsp; infoText.setText(&quot;Round: &quot;+Integer.toString(controller.getMiniModel().currentState.getRound()) + &quot;&quot; + controller.getMiniModel().currentState.getGameStage().toString());</b>
<b class="nc">&nbsp; VBox playerList = new VBox();</b>
<b class="nc">&nbsp; playerList.setFillWidth(true);</b>
<b class="nc">&nbsp; for (Player p : controller.getMiniModel().players.values()) {</b>
<b class="nc">&nbsp; VBox card = buildPlayerCard(p);</b>
<b class="nc">&nbsp; playerCards.put(p.getUserName(), card);</b>
<b class="nc">&nbsp; playerList.getChildren().add(card);</b>
&nbsp; }
<b class="nc">&nbsp; playerSide.setContent(playerList);</b>
&nbsp; }
&nbsp;
&nbsp; /** Updates food/prestige labels, current-player highlight, card-icon opacity, and plays error shake if needed. */
&nbsp; private void updateSidePanel() {
<b class="nc">&nbsp; infoText.setText(&quot;Round: &quot;+Integer.toString(controller.getMiniModel().currentState.getRound()) + &quot;&quot; + controller.getMiniModel().currentState.getGameStage().toString());</b>
<b class="nc">&nbsp; if (controller.getMiniModel().currentState.getCurrentPlayer() == null) return;</b>
<b class="nc">&nbsp; String current = controller.getMiniModel().currentState.getCurrentPlayer().getUserName();</b>
&nbsp; // ordine: building, artists, gatherers, inventors, builders, shamans, hunters
<b class="nc">&nbsp; String[] fields = {&quot;building&quot;, &quot;artists&quot;, &quot;gatherers&quot;, &quot;inventors&quot;, &quot;builders&quot;, &quot;shamans&quot;, &quot;hunters&quot;};</b>
&nbsp;
<b class="nc">&nbsp; for (Player p : controller.getMiniModel().players.values()) {</b>
<b class="nc">&nbsp; String u = p.getUserName();</b>
&nbsp;
<b class="nc">&nbsp; foodLabels.get(u).setText(String.valueOf(p.getFoodValue()));</b>
<b class="nc">&nbsp; prestigeLabels.get(u).setText(String.valueOf(p.getPrestigeValue()));</b>
&nbsp;
<b class="nc">&nbsp; List&lt;ImageView&gt; icons = iconViews.get(u);</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; icons.size(); i++) {</b>
<b class="nc">&nbsp; boolean empty = getPlayerCards(u, fields[i]).isEmpty();</b>
<b class="nc">&nbsp; icons.get(i).setOpacity(empty ? 0.3 : 1.0);</b>
<b class="nc">&nbsp; icons.get(i).setEffect(empty ? new ColorAdjust() : null);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; VBox card = playerCards.get(u);</b>
<b class="nc">&nbsp; if(controller.getMiniModel().disconnectedPlayers.contains(u))</b>
&nbsp; {
<b class="nc">&nbsp; card.getStyleClass().clear();</b>
<b class="nc">&nbsp; card.getStyleClass().add(&quot;player-card-crashed&quot;);</b>
&nbsp; }
&nbsp; else {
<b class="nc">&nbsp; card.getStyleClass().clear();</b>
<b class="nc">&nbsp; card.getStyleClass().add(&quot;player-card&quot;);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; if (u.equals(current)) {</b>
<b class="nc">&nbsp; card.setStyle(&quot;-fx-effect: dropshadow(gaussian, #fff8dc, 15, 0.10, 0, 0)&quot;);</b>
<b class="nc">&nbsp; ScaleTransition st = new ScaleTransition(Duration.millis(150), card);</b>
<b class="nc">&nbsp; st.setToX(1.1);</b>
<b class="nc">&nbsp; st.setToY(1.1);</b>
<b class="nc">&nbsp; st.play();</b>
&nbsp; } else {
<b class="nc">&nbsp; ScaleTransition st = new ScaleTransition(Duration.millis(150), card);</b>
<b class="nc">&nbsp; st.setToX(1.0);</b>
<b class="nc">&nbsp; st.setToY(1.0);</b>
<b class="nc">&nbsp; st.play();</b>
<b class="nc">&nbsp; card.setStyle(&quot;&quot;);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; if (isError &amp;&amp; u.equals(controller.getMyUsername())) {</b>
<b class="nc">&nbsp; TranslateTransition tt = new TranslateTransition(Duration.millis(56), card);</b>
<b class="nc">&nbsp; tt.setFromX(0);</b>
<b class="nc">&nbsp; tt.setByX(10);</b>
<b class="nc">&nbsp; tt.setCycleCount(6);</b>
<b class="nc">&nbsp; tt.setAutoReverse(true);</b>
<b class="nc">&nbsp; tt.play();</b>
&nbsp; }
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Renders the top card of the upper building stack with a count badge; clicking opens the selection popup. */
&nbsp; private void drawUpperBuilding() {
<b class="nc">&nbsp; ArrayList&lt;BuildingCard&gt; cardList = new ArrayList&lt;&gt;(controller.getMiniModel().upperListBuildingCards);</b>
<b class="nc">&nbsp; if (!cardList.isEmpty()) {</b>
<b class="nc">&nbsp; StackPane img = createCard(cardList.getLast(), true, true, upperList);</b>
<b class="nc">&nbsp; Label label = new Label(String.valueOf(cardList.size()));</b>
<b class="nc">&nbsp; label.setTranslateY(-56);</b>
<b class="nc">&nbsp; label.setTranslateX(10);</b>
<b class="nc">&nbsp; label.getStyleClass().add(&quot;label-large&quot;);</b>
<b class="nc">&nbsp; StackPane.setAlignment(label, Pos.TOP_RIGHT);</b>
<b class="nc">&nbsp; img.getChildren().add(label);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; openPopupUpperBuilding(cardList));</b>
<b class="nc">&nbsp; upperList.getChildren().add(img);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Renders the top card of the lower building stack with a count badge; clicking opens the selection popup. */
&nbsp; private void drawLowerBuilding() {
<b class="nc">&nbsp; ArrayList&lt;BuildingCard&gt; cardList = new ArrayList&lt;&gt;(controller.getMiniModel().lowerListBuildingCards);</b>
<b class="nc">&nbsp; if (!cardList.isEmpty()) {</b>
<b class="nc">&nbsp; StackPane img = createCard(cardList.getLast(), true, true, lowerList);</b>
<b class="nc">&nbsp; Label label = new Label(String.valueOf(cardList.size()));</b>
<b class="nc">&nbsp; label.setTranslateY(-56);</b>
<b class="nc">&nbsp; label.setTranslateX(10);</b>
<b class="nc">&nbsp; label.getStyleClass().add(&quot;label-large&quot;);</b>
<b class="nc">&nbsp; StackPane.setAlignment(label, Pos.TOP_RIGHT);</b>
<b class="nc">&nbsp; img.getChildren().add(label);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; openPopupLowerBuilding(cardList));</b>
<b class="nc">&nbsp; lowerList.getChildren().add(img);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Renders a single card-type pile in the hand area, or an invisible placeholder if the list is empty. */
&nbsp; private void drawMyHandList(ArrayList&lt;? extends PlayableCard&gt; cardList) {
<b class="nc">&nbsp; if (!cardList.isEmpty()) {</b>
<b class="nc">&nbsp; StackPane img = createCard(cardList.getLast(), true, true, myHand);</b>
<b class="nc">&nbsp; Label label = new Label(String.valueOf(cardList.size()));</b>
<b class="nc">&nbsp; label.setTranslateY(-56);</b>
<b class="nc">&nbsp; label.setTranslateX(10);</b>
<b class="nc">&nbsp; label.getStyleClass().add(&quot;label-large&quot;);</b>
<b class="nc">&nbsp; StackPane.setAlignment(label, Pos.TOP_RIGHT);</b>
<b class="nc">&nbsp; img.getChildren().add(label);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; openPopup(new ArrayList&lt;&gt;(cardList)));</b>
<b class="nc">&nbsp; myHand.getChildren().add(img);</b>
&nbsp; } else {
<b class="nc">&nbsp; Region placeholder = new Region();</b>
<b class="nc">&nbsp; placeholder.prefHeightProperty().bind(myHand.getScene().heightProperty().subtract(56).divide(4).multiply(0.90));</b>
<b class="nc">&nbsp; placeholder.prefWidthProperty().bind(placeholder.prefHeightProperty().multiply(0.675));</b>
<b class="nc">&nbsp; placeholder.setStyle(&quot;-fx-background-color: transparent;&quot;);</b>
<b class="nc">&nbsp; myHand.getChildren().add(placeholder);</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Returns a copy of the named card collection for {@code username}. */
&nbsp; private ArrayList&lt;PlayableCard&gt; getPlayerCards(String username, String type) {
<b class="nc">&nbsp; Player p = controller.getMiniModel().players.get(username);</b>
<b class="nc">&nbsp; return switch (type) {</b>
<b class="nc">&nbsp; case &quot;artists&quot; -&gt; new ArrayList&lt;&gt;(p.getArtists());</b>
<b class="nc">&nbsp; case &quot;gatherers&quot; -&gt; new ArrayList&lt;&gt;(p.getGatherers());</b>
<b class="nc">&nbsp; case &quot;inventors&quot; -&gt; new ArrayList&lt;&gt;(p.getInventors());</b>
<b class="nc">&nbsp; case &quot;builders&quot; -&gt; new ArrayList&lt;&gt;(p.getBuilders());</b>
<b class="nc">&nbsp; case &quot;shamans&quot; -&gt; new ArrayList&lt;&gt;(p.getShamans());</b>
<b class="nc">&nbsp; case &quot;hunters&quot; -&gt; new ArrayList&lt;&gt;(p.getHunters());</b>
<b class="nc">&nbsp; case &quot;building&quot; -&gt; new ArrayList&lt;&gt;(p.getBuildingCards());</b>
<b class="nc">&nbsp; default -&gt; new ArrayList&lt;&gt;();</b>
&nbsp; };
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== ABSOLUTES ====
&nbsp; /** Clears and rebuilds the upper tribe row with click handlers for drawing, then appends the upper building stack. */
&nbsp; private void renderUpper() {
<b class="nc">&nbsp; upperList.setSpacing(14);</b>
<b class="nc">&nbsp; upperList.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; upperList.getChildren().clear();</b>
&nbsp;
<b class="nc">&nbsp; int i = 0;</b>
<b class="nc">&nbsp; for (TribeCard card : controller.getMiniModel().upperListTribeCards) {</b>
<b class="nc">&nbsp; final int index = i;</b>
<b class="nc">&nbsp; StackPane img = createCard(card, true, true, upperList);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; controller.drawUpperTribeCard(index));</b>
<b class="nc">&nbsp; upperList.getChildren().add(img);</b>
<b class="nc">&nbsp; i++;</b>
&nbsp; }
<b class="nc">&nbsp; drawUpperBuilding();</b>
&nbsp; }
&nbsp;
&nbsp; /** Clears and rebuilds the lower tribe row with click handlers for drawing, then appends the lower building stack. */
&nbsp; private void renderLower() {
<b class="nc">&nbsp; lowerList.setSpacing(14);</b>
<b class="nc">&nbsp; lowerList.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; lowerList.getChildren().clear();</b>
&nbsp;
<b class="nc">&nbsp; int i = 0;</b>
<b class="nc">&nbsp; for (TribeCard card : controller.getMiniModel().lowerListTribeCards) {</b>
<b class="nc">&nbsp; final int index = i;</b>
<b class="nc">&nbsp; StackPane img = createCard(card, true, true, lowerList);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; controller.drawLowerTribeCard(index));</b>
<b class="nc">&nbsp; lowerList.getChildren().add(img);</b>
<b class="nc">&nbsp; i++;</b>
&nbsp; }
<b class="nc">&nbsp; drawLowerBuilding();</b>
&nbsp; }
&nbsp;
&nbsp; /** Sets the full-cover background image on the main HBox container. */
&nbsp; private void renderBackground() {
<b class="nc">&nbsp; BackgroundSize size = new BackgroundSize(</b>
&nbsp; BackgroundSize.AUTO, BackgroundSize.AUTO,
&nbsp; false, false, true, true
&nbsp; );
<b class="nc">&nbsp; mainHBox.setBackground(new Background(new BackgroundImage(</b>
<b class="nc">&nbsp; loadImage(&quot;/GUIImages/Background.png&quot;),</b>
&nbsp; BackgroundRepeat.NO_REPEAT,
&nbsp; BackgroundRepeat.NO_REPEAT,
&nbsp; BackgroundPosition.CENTER,
&nbsp; size
&nbsp; )));
&nbsp; }
&nbsp;
&nbsp; /** Clears the board area and orchestrates deck, order card, and slot-map rendering. */
&nbsp; private void renderBoard() {
<b class="nc">&nbsp; board.setSpacing(14);</b>
<b class="nc">&nbsp; board.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; board.getChildren().clear();</b>
<b class="nc">&nbsp; renderDeck();</b>
<b class="nc">&nbsp; renderOrder();</b>
<b class="nc">&nbsp; renderSlotMap();</b>
&nbsp; }
&nbsp;
&nbsp; /** Adds the era-specific deck back image to the board. */
&nbsp; private void renderDeck() {
<b class="nc">&nbsp; String path = switch (controller.getMiniModel().currentState.getEra()) {</b>
<b class="nc">&nbsp; case 1 -&gt; &quot;/GUIImages/Backs/back-001.png&quot;;</b>
<b class="nc">&nbsp; case 2 -&gt; &quot;/GUIImages/Backs/back-030.png&quot;;</b>
<b class="nc">&nbsp; case 3 -&gt; &quot;/GUIImages/Backs/back-058.png&quot;;</b>
<b class="nc">&nbsp; default -&gt; &quot;/GUIImages/Backs/back-001.png&quot;;</b>
<b class="nc">&nbsp; };</b>
<b class="nc">&nbsp; ImageView back = new ImageView(loadImage(path));</b>
<b class="nc">&nbsp; back.fitHeightProperty().bind(board.sceneProperty().get().heightProperty().subtract(56).divide(4).multiply(0.94));</b>
<b class="nc">&nbsp; back.setPreserveRatio(true);</b>
<b class="nc">&nbsp; addClip(back);</b>
<b class="nc">&nbsp; addShadow(back);</b>
<b class="nc">&nbsp; addHoverZoom(back);</b>
<b class="nc">&nbsp; board.getChildren().add(back);</b>
&nbsp; }
&nbsp;
&nbsp; /** Renders the order card and overlays each player&#39;s totem at their proportional position. */
&nbsp; private void renderOrder() {
<b class="nc">&nbsp; int numPlayers = controller.getMiniModel().players.size();</b>
<b class="nc">&nbsp; StackPane card = createOrder(Integer.toString(numPlayers));</b>
&nbsp;
<b class="nc">&nbsp; Map&lt;Integer, double[]&gt; slotPositions = Map.of(</b>
<b class="nc">&nbsp; 2, new double[]{0.229, 0.413},</b>
<b class="nc">&nbsp; 3, new double[]{0.183, 0.367, 0.548},</b>
<b class="nc">&nbsp; 4, new double[]{0.142, 0.316, 0.503, 0.690},</b>
<b class="nc">&nbsp; 5, new double[]{0.066, 0.251, 0.433, 0.617, 0.802}</b>
&nbsp; );
<b class="nc">&nbsp; double[] positions = slotPositions.get(numPlayers);</b>
<b class="nc">&nbsp; if (positions == null) return;</b>
&nbsp;
<b class="nc">&nbsp; Pane overlay = new Pane();</b>
<b class="nc">&nbsp; overlay.setPickOnBounds(false);</b>
<b class="nc">&nbsp; overlay.setMouseTransparent(true);</b>
&nbsp;
<b class="nc">&nbsp; for (int i = 0; i &lt; controller.getMiniModel().orderLogicCard.getPlayerList().size(); i++) {</b>
<b class="nc">&nbsp; OrderPlayer op = controller.getMiniModel().orderLogicCard.getPlayerList().get(i);</b>
&nbsp;
<b class="nc">&nbsp; ImageView totem = new ImageView(loadImage(</b>
<b class="nc">&nbsp; &quot;/GUIImages/Totems/totem_&quot; + op.getPlayer().getTotem().toString().toLowerCase(Locale.ROOT) + &quot;.png&quot;</b>
&nbsp; ));
<b class="nc">&nbsp; totem.setPreserveRatio(true);</b>
&nbsp;
<b class="nc">&nbsp; if (op.isPlayed()) {</b>
<b class="nc">&nbsp; totem.setVisible(false);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; final double yRatio = positions[i];</b>
&nbsp;
<b class="nc">&nbsp; overlay.prefHeightProperty().bind(card.heightProperty());</b>
<b class="nc">&nbsp; overlay.prefWidthProperty().bind(card.widthProperty());</b>
&nbsp;
<b class="nc">&nbsp; totem.fitHeightProperty().bind(card.heightProperty().multiply(0.323));</b>
&nbsp;
<b class="nc">&nbsp; card.heightProperty().addListener((obs, ov, nv) -&gt; {</b>
<b class="nc">&nbsp; totem.setLayoutY(nv.doubleValue() * (yRatio-0.196));</b>
&nbsp; });
<b class="nc">&nbsp; card.widthProperty().addListener((obs, ov, nv) -&gt; {</b>
<b class="nc">&nbsp; totem.setLayoutX(nv.doubleValue() * 0.364);</b>
&nbsp; });
&nbsp;
&nbsp; // initialize immediately if already laid out
<b class="nc">&nbsp; if (card.getHeight() &gt; 0) totem.setLayoutY(card.getHeight() *(yRatio-0.196));</b>
<b class="nc">&nbsp; if (card.getWidth() &gt; 0) totem.setLayoutX(card.getWidth() * 0.364);</b>
&nbsp;
<b class="nc">&nbsp; overlay.getChildren().add(totem);</b>
&nbsp; }
&nbsp;
<b class="nc">&nbsp; card.getChildren().add(overlay);</b>
<b class="nc">&nbsp; board.getChildren().add(card);</b>
&nbsp; }
&nbsp;
&nbsp; /** Adds a clickable slot widget for each entry in the slot-player map. */
&nbsp; private void renderSlotMap() {
<b class="nc">&nbsp; int i = 0;</b>
<b class="nc">&nbsp; for (Map.Entry&lt;Slot, Player&gt; entry : controller.getMiniModel().slotPlayerMap.entrySet()) {</b>
<b class="nc">&nbsp; Slot slot = entry.getKey();</b>
<b class="nc">&nbsp; final int index = i;</b>
<b class="nc">&nbsp; StackPane img = createSlot(slot, true, true);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; controller.slotChoice(index));</b>
<b class="nc">&nbsp; board.getChildren().add(img);</b>
<b class="nc">&nbsp; i++;</b>
&nbsp; }
&nbsp; }
&nbsp;
&nbsp; /** Clears and re-renders all seven card-type piles in the local player&#39;s hand area. */
&nbsp; private void renderMyHand() {
<b class="nc">&nbsp; myHand.getChildren().clear();</b>
<b class="nc">&nbsp; myHand.setSpacing(14);</b>
<b class="nc">&nbsp; myHand.setAlignment(Pos.CENTER);</b>
&nbsp;
<b class="nc">&nbsp; Player me = controller.getMiniModel().players.get(controller.getMyUsername());</b>
<b class="nc">&nbsp; drawMyHandList(me.getArtists());</b>
<b class="nc">&nbsp; drawMyHandList(me.getGatherers());</b>
<b class="nc">&nbsp; drawMyHandList(me.getInventors());</b>
<b class="nc">&nbsp; drawMyHandList(me.getBuilders());</b>
<b class="nc">&nbsp; drawMyHandList(me.getShamans());</b>
<b class="nc">&nbsp; drawMyHandList(me.getHunters());</b>
<b class="nc">&nbsp; drawMyHandList(me.getBuildingCards());</b>
&nbsp; }
&nbsp;
&nbsp;
&nbsp; // ==== POPUP ====
&nbsp; /** Creates and configures the shared card-preview popup container. */
&nbsp; private void initPopup() {
<b class="nc">&nbsp; popupCards = new HBox(10);</b>
<b class="nc">&nbsp; popupCards.setAlignment(Pos.CENTER);</b>
<b class="nc">&nbsp; popupCards.setPadding(new Insets(10));</b>
<b class="nc">&nbsp; popupCards.setStyle(</b>
&nbsp; &quot;-fx-background-color: #b42224;&quot; +
&nbsp; &quot;-fx-border-color: black;&quot; +
&nbsp; &quot;-fx-border-width: 2;&quot; +
&nbsp; &quot;-fx-border-radius: 10;&quot; +
&nbsp; &quot;-fx-background-radius: 10;&quot;
&nbsp; );
&nbsp;
<b class="nc">&nbsp; popup.getContent().add(popupCards);</b>
<b class="nc">&nbsp; popup.setAutoHide(false);</b>
<b class="nc">&nbsp; popup.addEventHandler(Event.ANY, e -&gt; {</b>
<b class="nc">&nbsp; if (popup.getScene() != null) {</b>
<b class="nc">&nbsp; popup.getScene().setFill(Color.TRANSPARENT);</b>
&nbsp; }
&nbsp; });
&nbsp; }
&nbsp;
&nbsp; /** Centers the popup over the current window and makes it visible. */
&nbsp; private void showPopup() {
<b class="nc">&nbsp; Window window = myHand.getScene().getWindow();</b>
<b class="nc">&nbsp; popup.show(window, 0, 0);</b>
<b class="nc">&nbsp; popup.getScene().setFill(Color.TRANSPARENT);</b>
<b class="nc">&nbsp; popup.setX(window.getX() + (window.getWidth() - popup.getWidth()) / 2);</b>
<b class="nc">&nbsp; popup.setY(window.getY() + (window.getHeight() - popup.getHeight()) / 2);</b>
&nbsp; }
&nbsp;
&nbsp; /** Populates the popup with non-clickable card images and shows it. */
&nbsp; private void openPopup(ArrayList&lt;? extends PlayableCard&gt; cardList) {
<b class="nc">&nbsp; popupCards.getChildren().clear();</b>
<b class="nc">&nbsp; for (PlayableCard card : cardList) {</b>
<b class="nc">&nbsp; popupCards.getChildren().add(createCardPopup(card, false, false));</b>
&nbsp; }
<b class="nc">&nbsp; showPopup();</b>
&nbsp; }
&nbsp;
&nbsp; /** Populates the popup with clickable upper-building cards; clicking one draws it via the controller. */
&nbsp; private void openPopupUpperBuilding(ArrayList&lt;BuildingCard&gt; cardList) {
<b class="nc">&nbsp; popupCards.getChildren().clear();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; cardList.size(); i++) {</b>
<b class="nc">&nbsp; final int index = i;</b>
<b class="nc">&nbsp; StackPane img = createCardPopup(cardList.get(i), true, false);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; {</b>
<b class="nc">&nbsp; popup.hide();</b>
<b class="nc">&nbsp; controller.drawUpperBuildingCard(index);</b>
&nbsp; });
<b class="nc">&nbsp; popupCards.getChildren().add(img);</b>
&nbsp; }
<b class="nc">&nbsp; showPopup();</b>
&nbsp; }
&nbsp;
&nbsp; /** Populates the popup with clickable lower-building cards; clicking one draws it via the controller. */
&nbsp; private void openPopupLowerBuilding(ArrayList&lt;BuildingCard&gt; cardList) {
<b class="nc">&nbsp; popupCards.getChildren().clear();</b>
<b class="nc">&nbsp; for (int i = 0; i &lt; cardList.size(); i++) {</b>
<b class="nc">&nbsp; final int index = i;</b>
<b class="nc">&nbsp; StackPane img = createCardPopup(cardList.get(i), true, false);</b>
<b class="nc">&nbsp; img.setOnMouseClicked(e -&gt; {</b>
<b class="nc">&nbsp; popup.hide();</b>
<b class="nc">&nbsp; controller.drawLowerBuildingCard(index);</b>
&nbsp; });
<b class="nc">&nbsp; popupCards.getChildren().add(img);</b>
&nbsp; }
<b class="nc">&nbsp; showPopup();</b>
&nbsp; }
&nbsp;
&nbsp; /** Shows the deck details/rules card in the popup. */
&nbsp; private void openDetailsPopup() {
<b class="nc">&nbsp; popupCards.getChildren().clear();</b>
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Backs/back-118.png&quot;));</b>
<b class="nc">&nbsp; img.setFitHeight(290);</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; StackPane wrapper = new StackPane(img);</b>
<b class="nc">&nbsp; addShadow(wrapper);</b>
<b class="nc">&nbsp; popupCards.getChildren().add(wrapper);</b>
<b class="nc">&nbsp; showPopup();</b>
&nbsp; }
&nbsp;
&nbsp; /** Creates a fixed-height {@link StackPane} card widget suitable for use inside the popup. */
&nbsp; private StackPane createCardPopup(PlayableCard card, boolean withZoom, boolean withShadow) {
<b class="nc">&nbsp; ImageView img = new ImageView(loadImage(&quot;/GUIImages/Fronts/card-&quot; + card.getIdIMG() + &quot;.png&quot;));</b>
<b class="nc">&nbsp; img.setFitHeight(200);</b>
<b class="nc">&nbsp; img.setPreserveRatio(true);</b>
<b class="nc">&nbsp; addClip(img);</b>
<b class="nc">&nbsp; StackPane wrapper = new StackPane(img);</b>
<b class="nc">&nbsp; if (withShadow) addShadow(wrapper);</b>
<b class="nc">&nbsp; if (withZoom) addHoverZoom(wrapper);</b>
<b class="nc">&nbsp; return wrapper;</b>
&nbsp; }
&nbsp;}
</code>
</pre>
</div>
<script type="text/javascript">
(function() {
var msie = false, msie9 = false;
/*@cc_on
msie = true;
@if (@_jscript_version >= 9)
msie9 = true;
@end
@*/
if (!msie || msie && msie9) {
hljs.highlightAll()
hljs.initLineNumbersOnLoad();
}
})();
</script>
<div class="footer">
<div style="float:right;">generated on 2026-06-14 21:53</div>
</div>
</body>
</html>