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.
This commit is contained in:
ItsDrike 2024-12-08 11:41:28 +01:00
parent 511ca3744a
commit 57958ed853
Signed by: ItsDrike
GPG key ID: FA2745890B7048C0
5 changed files with 111 additions and 35 deletions

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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");
}
}
}