From 57958ed85388435ca9bae014cbc76fada5bed797 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Sun, 8 Dec 2024 11:41:28 +0100 Subject: [PATCH] Dynamically scale the cards & everything else Currently, most things were scaled based on the card dimensions, though some things were hard-coded. This commit moves away from all hard-coded sizes in favor of everything relying on card heights. Additionally, it makes the card height itself no longer hard-coded, passing it as a property to most custom structures. The card height itself is now calculated from Main.qml to make sure everything fits within the screen and scales as the window is resized. --- qml/DrawPile.qml | 26 +++++++++------ qml/FoundationPiles.qml | 17 ++++++---- qml/Main.qml | 70 ++++++++++++++++++++++++++++++++++++----- qml/Tableau.qml | 20 ++++++------ qml/ThrowawayPile.qml | 13 ++++++-- 5 files changed, 111 insertions(+), 35 deletions(-) diff --git a/qml/DrawPile.qml b/qml/DrawPile.qml index f812165..ffca6cf 100644 --- a/qml/DrawPile.qml +++ b/qml/DrawPile.qml @@ -3,13 +3,17 @@ import Solitare Item { id: drawPile - width: drawPileCard.width - height: drawPileCard.height + + required property real cardWidth + required property real cardHeight + + width: cardWidth + height: cardHeight CardModel { id: drawPileCard + anchors.fill: drawPile - anchors.fill: parent visible: GameState.drawPile.length > 0 card: GameState.drawPile.length > 0 ? GameState.drawPile[GameState.drawPile.length - 1] : null @@ -24,9 +28,9 @@ Item { color: "gray" border.color: "white" - border.width: 3 + border.width: width * 0.03 opacity: 0.4 - radius: 10 + radius: width * 0.125 Image { id: flipIcon @@ -46,14 +50,16 @@ Item { color: "black" opacity: 0.7 - width: cardCountText.width + 8 // 8px padding - height: cardCountText.height + 8 // ^^ + property real padding: Math.max(cardCountText.width, cardCountText.height) * 0.3 + + width: cardCountText.width + padding + height: cardCountText.height + padding visible: drawPileCard.visible anchors.bottom: parent.bottom anchors.left: parent.left - anchors.margins: 4 - radius: 4 + anchors.margins: parent.width * 0.05 + radius: parent.width * 0.1 z: 1 // Behind the text, but above the card } @@ -62,7 +68,7 @@ Item { text: GameState.drawPile.length color: "white" - font.pixelSize: 16 + font.pixelSize: parent.width * 0.2 font.bold: true visible: drawPileCard.visible diff --git a/qml/FoundationPiles.qml b/qml/FoundationPiles.qml index 3f12b1c..139c22c 100644 --- a/qml/FoundationPiles.qml +++ b/qml/FoundationPiles.qml @@ -2,15 +2,20 @@ import QtQuick import Solitare Row { - spacing: 15 + id: foundationRow + + required property real cardWidth + required property real cardHeight + + spacing: cardWidth * 0.2 Repeater { model: 4 // Each of the 4 suits Item { id: foundationPile - width: foundationCard.width - height: foundationCard.height + width: foundationRow.cardWidth + height: foundationRow.cardHeight required property int index // passed from repeater @@ -32,14 +37,14 @@ Row { color: "gray" border.color: "white" - border.width: 3 + border.width: width * 0.03 opacity: 0.4 - radius: 10 + radius: width * 0.125 Text { text: "A" color: "white" - font.pixelSize: 40 + font.pixelSize: parent.width * 0.5 font.bold: true anchors.centerIn: parent diff --git a/qml/Main.qml b/qml/Main.qml index 45f15e6..6abaf2a 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -4,16 +4,56 @@ import QtQuick.Layouts import Solitare ApplicationWindow { + id: app + width: 750 height: 650 visible: true title: qsTr("Solitare") color: "#019934" + // Card dimensions calculation: + // + // Horizontal space + // ================ + // The tableau columns take up at most 7*(w*1.125) = 7.875 widths. + // + // The first row: + // * foundation piles: 4*w*1.2 = 4.8 widths. + // * throwaway pile: 3*(w-w*0.75)+w = 1.75 widths. + // * draw pile: 1 width + // * spacing between throwaway & draw pile: 0.25 widths. + // * spacing between foundations & throwaway pile: 0.25 widths + // + // Totalling at: 8.05 card widths. + // + // Vertical space + // ============== + // * The score bar takes up min(8% app height, 50). + // * The first row takes up 1.35 card heights. + // * Margin between first row & tableau: 0.5 card heights. + // * The tableau height depends on the max amount of cards that can be in a single column. + // Generally, the column will take up: ((maxCards - 1) * (h-h*0.8))+h = h(0.2 * maxCards + 0.8). + // + // A good base assumption is 14 cards, though the cards should dynamically rescale + // if this amount is surpassed, which is very possible. + // + // Totalling at: (0.2 * maxCards + 2.65) card heights, filling up 100% - min(8%, 50) of screen height. + // + // + // Aspect Ratio + // ============ + // Finally, we need to make sure that the card proportions are preserved, + // with the height being 1.4x the width. + property int maxCardsPerColumn: Math.max(GameState.columns.reduce((max, col) => Math.max(max, col.length), 0), 14) + + property real cardHeight: Math.min((app.height - Math.min(app.height * 0.08, 50)) / (maxCardsPerColumn * 0.2 + 2.65), (app.width / 8.05) * 1.4) + property real cardWidth: cardHeight / 1.4 + ScoreBar { id: scoreBar - height: 50 + height: Math.max(parent.height * 0.08, 50) anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -23,7 +63,7 @@ ApplicationWindow { Rectangle { id: firstRow - height: 135 + height: app.cardHeight * 1.35 anchors.top: scoreBar.bottom anchors.left: parent.left anchors.right: parent.right @@ -31,21 +71,34 @@ ApplicationWindow { Item { anchors.fill: parent - anchors.margins: 10 + + anchors.leftMargin: app.cardWidth * 0.125 + anchors.rightMargin: app.cardWidth * 0.125 + anchors.topMargin: app.cardHeight * 0.2 + anchors.bottomMargin: app.cardHeight * 0.2 // Left side FoundationPiles { anchors.left: parent.left + + cardWidth: app.cardWidth + cardHeight: app.cardHeight } // Right side Row { - spacing: 20 + spacing: app.cardWidth * 0.25 anchors.right: parent.right - ThrowawayPile {} + ThrowawayPile { + cardWidth: app.cardWidth + cardHeight: app.cardHeight + } - DrawPile {} + DrawPile { + cardWidth: app.cardWidth + cardHeight: app.cardHeight + } } } } @@ -53,6 +106,9 @@ ApplicationWindow { Tableau { anchors.top: firstRow.bottom anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 50 + anchors.topMargin: app.cardHeight * 0.5 + + cardWidth: app.cardWidth + cardHeight: app.cardHeight } } diff --git a/qml/Tableau.qml b/qml/Tableau.qml index 4a11f75..9fb64f8 100644 --- a/qml/Tableau.qml +++ b/qml/Tableau.qml @@ -4,7 +4,11 @@ import Solitare // In solitare, Tableau refers to the part of the board where all of the columns are placed Row { id: tableau - spacing: 10 + + required property real cardWidth + required property real cardHeight + + spacing: cardWidth * 0.125 Repeater { id: colRepeater @@ -13,9 +17,7 @@ Row { delegate: Column { required property int index // passed from repeater - onChildrenChanged: { - spacing = -(slotRepeater.itemAt(0).height * 0.8); // Overlap - } + spacing: -tableau.cardHeight * 0.8 // Overlap Repeater { id: slotRepeater @@ -25,6 +27,9 @@ Row { required property int index // passed from repeater cardId: index columnId: parent.index // this is actually passed from the Column, not the Repeater + + width: tableau.cardWidth + height: tableau.cardHeight } } } @@ -35,9 +40,6 @@ Row { required property int cardId property ColumnSlot colSlot: GameState.columns[columnId].length > 0 ? GameState.columns[columnId][cardId] : null - height: colCard.height - width: colCard.width - CardModel { id: colCard @@ -67,9 +69,9 @@ Row { color: "gray" border.color: "white" - border.width: 2 + border.width: width * 0.025 opacity: 0.2 - radius: 5 + radius: width * 0.05 } } } diff --git a/qml/ThrowawayPile.qml b/qml/ThrowawayPile.qml index 9140836..c0e16e6 100644 --- a/qml/ThrowawayPile.qml +++ b/qml/ThrowawayPile.qml @@ -3,8 +3,12 @@ import Solitare // The throwaway pile (shows last 3 cards) Row { + id: throwawayRow + required property real cardWidth + required property real cardHeight + // This allows makes the cards overlap - spacing: -60 + spacing: -cardWidth * 0.75 Repeater { model: Math.min(GameState.throwawayPile.length, 3) @@ -13,6 +17,9 @@ Row { required property int index // passed from repeater property int reversedIndex: Math.min(GameState.throwawayPile.length, 3) - 1 - index + width: throwawayRow.cardWidth + height: throwawayRow.cardHeight + card: GameState.throwawayPile[GameState.throwawayPile.length - 1 - reversedIndex] isFaceDown: false onClicked: { @@ -21,9 +28,9 @@ Row { if (reversedIndex == 0) { if (GameState.autoMoveThrownCard()) { if (GameState.isWinnable()) { - console.log("Still winnable") + console.log("Still winnable"); } else { - console.log("Game is lost") + console.log("Game is lost"); } } }