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:
parent
511ca3744a
commit
57958ed853
|
@ -3,13 +3,17 @@ import Solitare
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: drawPile
|
id: drawPile
|
||||||
width: drawPileCard.width
|
|
||||||
height: drawPileCard.height
|
required property real cardWidth
|
||||||
|
required property real cardHeight
|
||||||
|
|
||||||
|
width: cardWidth
|
||||||
|
height: cardHeight
|
||||||
|
|
||||||
CardModel {
|
CardModel {
|
||||||
id: drawPileCard
|
id: drawPileCard
|
||||||
|
anchors.fill: drawPile
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: GameState.drawPile.length > 0
|
visible: GameState.drawPile.length > 0
|
||||||
|
|
||||||
card: GameState.drawPile.length > 0 ? GameState.drawPile[GameState.drawPile.length - 1] : null
|
card: GameState.drawPile.length > 0 ? GameState.drawPile[GameState.drawPile.length - 1] : null
|
||||||
|
@ -24,9 +28,9 @@ Item {
|
||||||
|
|
||||||
color: "gray"
|
color: "gray"
|
||||||
border.color: "white"
|
border.color: "white"
|
||||||
border.width: 3
|
border.width: width * 0.03
|
||||||
opacity: 0.4
|
opacity: 0.4
|
||||||
radius: 10
|
radius: width * 0.125
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: flipIcon
|
id: flipIcon
|
||||||
|
@ -46,14 +50,16 @@ Item {
|
||||||
color: "black"
|
color: "black"
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
|
|
||||||
width: cardCountText.width + 8 // 8px padding
|
property real padding: Math.max(cardCountText.width, cardCountText.height) * 0.3
|
||||||
height: cardCountText.height + 8 // ^^
|
|
||||||
|
width: cardCountText.width + padding
|
||||||
|
height: cardCountText.height + padding
|
||||||
|
|
||||||
visible: drawPileCard.visible
|
visible: drawPileCard.visible
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.margins: 4
|
anchors.margins: parent.width * 0.05
|
||||||
radius: 4
|
radius: parent.width * 0.1
|
||||||
z: 1 // Behind the text, but above the card
|
z: 1 // Behind the text, but above the card
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +68,7 @@ Item {
|
||||||
|
|
||||||
text: GameState.drawPile.length
|
text: GameState.drawPile.length
|
||||||
color: "white"
|
color: "white"
|
||||||
font.pixelSize: 16
|
font.pixelSize: parent.width * 0.2
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
||||||
visible: drawPileCard.visible
|
visible: drawPileCard.visible
|
||||||
|
|
|
@ -2,15 +2,20 @@ import QtQuick
|
||||||
import Solitare
|
import Solitare
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
spacing: 15
|
id: foundationRow
|
||||||
|
|
||||||
|
required property real cardWidth
|
||||||
|
required property real cardHeight
|
||||||
|
|
||||||
|
spacing: cardWidth * 0.2
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: 4 // Each of the 4 suits
|
model: 4 // Each of the 4 suits
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: foundationPile
|
id: foundationPile
|
||||||
width: foundationCard.width
|
width: foundationRow.cardWidth
|
||||||
height: foundationCard.height
|
height: foundationRow.cardHeight
|
||||||
|
|
||||||
required property int index // passed from repeater
|
required property int index // passed from repeater
|
||||||
|
|
||||||
|
@ -32,14 +37,14 @@ Row {
|
||||||
|
|
||||||
color: "gray"
|
color: "gray"
|
||||||
border.color: "white"
|
border.color: "white"
|
||||||
border.width: 3
|
border.width: width * 0.03
|
||||||
opacity: 0.4
|
opacity: 0.4
|
||||||
radius: 10
|
radius: width * 0.125
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "A"
|
text: "A"
|
||||||
color: "white"
|
color: "white"
|
||||||
font.pixelSize: 40
|
font.pixelSize: parent.width * 0.5
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
70
qml/Main.qml
70
qml/Main.qml
|
@ -4,16 +4,56 @@ import QtQuick.Layouts
|
||||||
import Solitare
|
import Solitare
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
|
id: app
|
||||||
|
|
||||||
width: 750
|
width: 750
|
||||||
height: 650
|
height: 650
|
||||||
visible: true
|
visible: true
|
||||||
title: qsTr("Solitare")
|
title: qsTr("Solitare")
|
||||||
color: "#019934"
|
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 {
|
ScoreBar {
|
||||||
id: scoreBar
|
id: scoreBar
|
||||||
|
|
||||||
height: 50
|
height: Math.max(parent.height * 0.08, 50)
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
@ -23,7 +63,7 @@ ApplicationWindow {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: firstRow
|
id: firstRow
|
||||||
|
|
||||||
height: 135
|
height: app.cardHeight * 1.35
|
||||||
anchors.top: scoreBar.bottom
|
anchors.top: scoreBar.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
@ -31,21 +71,34 @@ ApplicationWindow {
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
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
|
// Left side
|
||||||
FoundationPiles {
|
FoundationPiles {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
|
cardWidth: app.cardWidth
|
||||||
|
cardHeight: app.cardHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right side
|
// Right side
|
||||||
Row {
|
Row {
|
||||||
spacing: 20
|
spacing: app.cardWidth * 0.25
|
||||||
anchors.right: parent.right
|
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 {
|
Tableau {
|
||||||
anchors.top: firstRow.bottom
|
anchors.top: firstRow.bottom
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.topMargin: 50
|
anchors.topMargin: app.cardHeight * 0.5
|
||||||
|
|
||||||
|
cardWidth: app.cardWidth
|
||||||
|
cardHeight: app.cardHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,11 @@ import Solitare
|
||||||
// In solitare, Tableau refers to the part of the board where all of the columns are placed
|
// In solitare, Tableau refers to the part of the board where all of the columns are placed
|
||||||
Row {
|
Row {
|
||||||
id: tableau
|
id: tableau
|
||||||
spacing: 10
|
|
||||||
|
required property real cardWidth
|
||||||
|
required property real cardHeight
|
||||||
|
|
||||||
|
spacing: cardWidth * 0.125
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: colRepeater
|
id: colRepeater
|
||||||
|
@ -13,9 +17,7 @@ Row {
|
||||||
delegate: Column {
|
delegate: Column {
|
||||||
required property int index // passed from repeater
|
required property int index // passed from repeater
|
||||||
|
|
||||||
onChildrenChanged: {
|
spacing: -tableau.cardHeight * 0.8 // Overlap
|
||||||
spacing = -(slotRepeater.itemAt(0).height * 0.8); // Overlap
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: slotRepeater
|
id: slotRepeater
|
||||||
|
@ -25,6 +27,9 @@ Row {
|
||||||
required property int index // passed from repeater
|
required property int index // passed from repeater
|
||||||
cardId: index
|
cardId: index
|
||||||
columnId: parent.index // this is actually passed from the Column, not the Repeater
|
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
|
required property int cardId
|
||||||
property ColumnSlot colSlot: GameState.columns[columnId].length > 0 ? GameState.columns[columnId][cardId] : null
|
property ColumnSlot colSlot: GameState.columns[columnId].length > 0 ? GameState.columns[columnId][cardId] : null
|
||||||
|
|
||||||
height: colCard.height
|
|
||||||
width: colCard.width
|
|
||||||
|
|
||||||
CardModel {
|
CardModel {
|
||||||
id: colCard
|
id: colCard
|
||||||
|
|
||||||
|
@ -67,9 +69,9 @@ Row {
|
||||||
|
|
||||||
color: "gray"
|
color: "gray"
|
||||||
border.color: "white"
|
border.color: "white"
|
||||||
border.width: 2
|
border.width: width * 0.025
|
||||||
opacity: 0.2
|
opacity: 0.2
|
||||||
radius: 5
|
radius: width * 0.05
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,12 @@ import Solitare
|
||||||
|
|
||||||
// The throwaway pile (shows last 3 cards)
|
// The throwaway pile (shows last 3 cards)
|
||||||
Row {
|
Row {
|
||||||
|
id: throwawayRow
|
||||||
|
required property real cardWidth
|
||||||
|
required property real cardHeight
|
||||||
|
|
||||||
// This allows makes the cards overlap
|
// This allows makes the cards overlap
|
||||||
spacing: -60
|
spacing: -cardWidth * 0.75
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: Math.min(GameState.throwawayPile.length, 3)
|
model: Math.min(GameState.throwawayPile.length, 3)
|
||||||
|
@ -13,6 +17,9 @@ Row {
|
||||||
required property int index // passed from repeater
|
required property int index // passed from repeater
|
||||||
property int reversedIndex: Math.min(GameState.throwawayPile.length, 3) - 1 - index
|
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]
|
card: GameState.throwawayPile[GameState.throwawayPile.length - 1 - reversedIndex]
|
||||||
isFaceDown: false
|
isFaceDown: false
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
@ -21,9 +28,9 @@ Row {
|
||||||
if (reversedIndex == 0) {
|
if (reversedIndex == 0) {
|
||||||
if (GameState.autoMoveThrownCard()) {
|
if (GameState.autoMoveThrownCard()) {
|
||||||
if (GameState.isWinnable()) {
|
if (GameState.isWinnable()) {
|
||||||
console.log("Still winnable")
|
console.log("Still winnable");
|
||||||
} else {
|
} else {
|
||||||
console.log("Game is lost")
|
console.log("Game is lost");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue