Workcat: cute desktop cat, no window closing

Reworks the example into a pure desktop-pet: the cat wanders, sits, grooms,
stretches, dashes and naps, reacts to petting and dragging, and follows the
cursor with its eyes. The window-scanning and Accessibility code is gone.

The cat is drawn from rounded primitives plus Shape paths in a fixed 100x90
design space, so it scales cleanly to any size.

Linux/X11 is the primary target: the tray is optional, Wayland gets a warning,
and WORKCAT_X11_BYPASS / WORKCAT_OPAQUE cover uncooperative window managers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
JulianandClaude Opus 5 committed 2026-09-10 22:20:35 +02:00
commit acef398f49
17 files changed
+1584

No files matched your search

+529
View File
@@ -0,0 +1,529 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Shapes
// A chibi cat drawn from primitives and vector paths.
// Design space is 100 x 90 units, ground line at y = 84, cat faces right.
Item {
id: cat
property color coat: "#f4b183"
property bool facingRight: true
property string mood: "walking" // walking running sitting grooming stretching sleeping loving alert
property bool lifted: false
property bool petted: false
property point look: Qt.point(0, 0)
readonly property bool dark: coat.hslLightness < 0.45
readonly property color coatShade: Qt.darker(coat, 1.12)
readonly property color coatLight: Qt.lighter(coat, dark ? 1.35 : 1.16)
readonly property color ink: dark ? Qt.lighter(coat, 1.9) : Qt.darker(coat, 1.7)
readonly property color eyeColor: dark ? "#f7d67c" : "#2f2a36"
readonly property color pink: "#f2929f"
readonly property color blush: "#f77d95"
readonly property bool walking: mood === "walking"
readonly property bool running: mood === "running"
readonly property bool moving: (walking || running) && !lifted
readonly property bool sleeping: mood === "sleeping"
readonly property bool happy: mood === "loving" || mood === "grooming" || mood === "stretching" || petted
readonly property real unit: width / 100
// tail pose (design units); swish is added on top by the idle animation
property real tailC1X: 14
property real tailC1Y: 66
property real tailC2X: 8
property real tailC2Y: 46
property real tailX: 13
property real tailY: 34
property real tailSwish: 0
Behavior on tailC1X { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } }
Behavior on tailC1Y { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } }
Behavior on tailC2X { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } }
Behavior on tailC2Y { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } }
Behavior on tailX { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } }
Behavior on tailY { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } }
implicitWidth: 100
implicitHeight: 90
transform: Scale {
origin.x: cat.width / 2
xScale: cat.facingRight ? 1 : -1
}
Item {
id: art
width: 100
height: 90
transformOrigin: Item.TopLeft
scale: cat.unit
// ground shadow
Rectangle {
id: shadow
x: 24; y: 79
width: 56; height: 8
radius: 4
color: "#000000"
opacity: 0.16
antialiasing: true
scale: 1 - torso.y * -0.02
}
Item {
id: torso
width: 100
height: 90
// tail
Shape {
id: tailShape
z: -1
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: tail
strokeColor: cat.coatShade
strokeWidth: 7
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 30; startY: 60
PathCubic {
control1X: cat.tailC1X; control1Y: cat.tailC1Y
control2X: cat.tailC2X + cat.tailSwish * 0.5; control2Y: cat.tailC2Y
x: cat.tailX + cat.tailSwish; y: cat.tailY
}
}
SequentialAnimation {
running: !cat.sleeping && !cat.lifted
loops: Animation.Infinite
alwaysRunToEnd: true
NumberAnimation { target: cat; property: "tailSwish"; to: 6; duration: 900; easing.type: Easing.InOutSine }
NumberAnimation { target: cat; property: "tailSwish"; to: -4; duration: 900; easing.type: Easing.InOutSine }
}
}
// legs — the far pair sits behind the body, the near pair in front
Repeater {
id: legs
model: 4
Rectangle {
id: leg
required property int index
readonly property bool front: index >= 2
readonly property bool far: index % 2 === 0
readonly property real swing: cat.running ? 38 : 24
readonly property real restRotation: front ? (cat.mood === "stretching" ? (far ? 48 : 40)
: cat.lifted ? (far ? -12 : -18) : 0)
: (cat.mood === "stretching" ? -6 : 0)
z: far ? 0 : 2
x: front ? (cat.seated && !cat.lifted ? 60 : 56) + (far ? 0 : 8)
: (cat.seated && !cat.lifted ? 36 : 30) + (far ? 0 : 8)
y: cat.sleeping && !cat.lifted ? 74
: (front ? (cat.seated && !cat.lifted ? 60 : 63)
: (cat.seated && !cat.lifted ? 72 : 63))
width: 10
height: cat.sleeping && !cat.lifted ? 8
: (front ? (cat.seated && !cat.lifted ? 24 : 20)
: (cat.seated && !cat.lifted ? 12 : 20))
radius: 5
color: far ? cat.coatShade : cat.coat
antialiasing: true
transformOrigin: Item.Top
Behavior on x { NumberAnimation { duration: 400; easing.type: Easing.InOutQuad } }
Behavior on y { NumberAnimation { duration: 400; easing.type: Easing.InOutQuad } }
Behavior on height { NumberAnimation { duration: 400; easing.type: Easing.InOutQuad } }
// paw
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
width: parent.width
height: 6.5
radius: 3.25
color: cat.coatLight
antialiasing: true
Rectangle { // toe line
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 1.2
width: 4.5
height: 1
radius: 0.5
color: cat.coatShade
opacity: 0.7
visible: !leg.far && !cat.sleeping
}
}
// While walking the gait animation owns rotation; otherwise the pose does.
states: State {
name: "resting"
when: !cat.moving
PropertyChanges { leg.rotation: leg.restRotation }
}
transitions: Transition {
NumberAnimation { property: "rotation"; duration: 400; easing.type: Easing.OutBack }
}
SequentialAnimation on rotation {
running: cat.moving
loops: Animation.Infinite
alwaysRunToEnd: true
NumberAnimation { to: leg.far ? leg.swing : -leg.swing; duration: cat.running ? 130 : 240; easing.type: Easing.InOutSine }
NumberAnimation { to: leg.far ? -leg.swing : leg.swing; duration: cat.running ? 130 : 240; easing.type: Easing.InOutSine }
}
}
}
// body
Rectangle {
id: body
z: 1
x: 24; y: 46
width: 50; height: 32
radius: 16
color: cat.coat
antialiasing: true
Behavior on x { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
Behavior on y { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
Behavior on width { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
Behavior on height { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
Behavior on rotation { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
Rectangle { // belly
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 3
width: parent.width * 0.5
height: parent.height * 0.42
radius: height / 2
color: cat.coatLight
antialiasing: true
}
}
// head
Item {
id: head
z: 3
x: 46; y: 12
width: 50; height: 44
transformOrigin: Item.Bottom
Behavior on x { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
Behavior on y { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
Behavior on rotation { NumberAnimation { duration: 450; easing.type: Easing.InOutBack } }
// ears
Shape {
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath { // left ear
fillColor: cat.coat
strokeColor: "transparent"
startX: 6; startY: 14
PathQuad { controlX: 2; controlY: -4; x: 8; y: -4 }
PathQuad { controlX: 14; controlY: -2; x: 24; y: 5 }
PathLine { x: 6; y: 14 }
}
ShapePath { // left inner
fillColor: cat.pink
strokeColor: "transparent"
startX: 9; startY: 11
PathQuad { controlX: 6; controlY: 2; x: 9; y: 1 }
PathQuad { controlX: 14; controlY: 3; x: 19; y: 6 }
PathLine { x: 9; y: 11 }
}
ShapePath { // right ear
fillColor: cat.coat
strokeColor: "transparent"
startX: 44; startY: 14
PathQuad { controlX: 48; controlY: -4; x: 42; y: -4 }
PathQuad { controlX: 36; controlY: -2; x: 26; y: 5 }
PathLine { x: 44; y: 14 }
}
ShapePath { // right inner
fillColor: cat.pink
strokeColor: "transparent"
startX: 41; startY: 11
PathQuad { controlX: 44; controlY: 2; x: 41; y: 1 }
PathQuad { controlX: 36; controlY: 3; x: 31; y: 6 }
PathLine { x: 41; y: 11 }
}
}
Rectangle { // face
x: 0; y: 4
width: 50; height: 40
radius: 21
color: cat.coat
antialiasing: true
}
// cheeks
Repeater {
model: 2
Rectangle {
required property int index
x: index === 0 ? 5 : 35
y: 30
width: 10; height: 5.5
radius: 3
color: cat.blush
opacity: cat.happy ? 0.75 : 0.4
antialiasing: true
Behavior on opacity { NumberAnimation { duration: 300 } }
}
}
// eyes
Repeater {
model: 2
Item {
id: eye
required property int index
property real open: 1
x: (index === 0 ? 12 : 29) + cat.look.x * 2.2
y: 17 + cat.look.y * 1.6
width: 9.5; height: 12
Behavior on open { NumberAnimation { duration: 110 } }
Rectangle {
id: eyeBall
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
width: eye.width * (cat.mood === "alert" ? 1.15 : 1)
height: eye.height * eye.open * (cat.mood === "alert" ? 1.15 : 1)
radius: width / 2
color: cat.eyeColor
visible: eye.open > 0.08
antialiasing: true
Rectangle { // highlight
x: parent.width * 0.5; y: parent.height * 0.14
width: 3.4; height: 3.4 * eye.open
radius: 1.7
color: "white"
antialiasing: true
}
Rectangle {
x: parent.width * 0.22; y: parent.height * 0.6
width: 1.6; height: 1.6 * eye.open
radius: 0.8
color: "white"
antialiasing: true
}
}
Shape { // closed eye: happy arch or sleepy curve
anchors.fill: parent
visible: eye.open <= 0.08
preferredRendererType: Shape.CurveRenderer
ShapePath {
strokeColor: cat.ink
strokeWidth: 1.8
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 0.5; startY: 7
PathQuad {
controlX: 4.75
controlY: cat.happy ? 2 : 10
x: 9; y: 7
}
}
}
Timer { // blink
interval: 2600 + Math.random() * 3800
running: !cat.sleeping && !cat.happy && cat.mood !== "alert"
repeat: true
onTriggered: { interval = 2600 + Math.random() * 3800; blinkAnim.start() }
}
SequentialAnimation {
id: blinkAnim
NumberAnimation { target: eye; property: "open"; to: 0; duration: 80 }
NumberAnimation { target: eye; property: "open"; to: 1; duration: 120 }
}
states: State {
name: "closed"
when: cat.sleeping || cat.happy
PropertyChanges { eye.open: 0 }
}
}
}
// nose
Rectangle {
x: 22.5; y: 28
width: 5; height: 3.6
radius: 1.8
color: cat.pink
antialiasing: true
}
// mouth "ω"
Shape {
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
strokeColor: cat.ink
strokeWidth: 1.1
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 20.5; startY: 32
PathQuad { controlX: 22.5; controlY: 35.2; x: 25; y: 32.4 }
PathQuad { controlX: 27.5; controlY: 35.2; x: 29.5; y: 32 }
}
}
// whiskers
Repeater {
model: 4
Rectangle {
required property int index
readonly property bool leftSide: index < 2
x: leftSide ? -7 : 45
y: 25 + (index % 2) * 4.5
width: 12
height: 1
radius: 0.5
color: cat.ink
opacity: 0.55
rotation: (leftSide ? 1 : -1) * ((index % 2) === 0 ? 7 : -7)
antialiasing: true
}
}
// raised paw while grooming
Rectangle {
x: 8; y: 26
width: 10; height: 16
radius: 5
color: cat.coat
antialiasing: true
visible: cat.mood === "grooming"
transformOrigin: Item.Bottom
SequentialAnimation on rotation {
running: cat.mood === "grooming"
loops: Animation.Infinite
NumberAnimation { to: 14; duration: 260; easing.type: Easing.InOutSine }
NumberAnimation { to: -6; duration: 260; easing.type: Easing.InOutSine }
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
y: 0
width: parent.width; height: 6
radius: 3
color: cat.coatLight
}
}
}
// walking bob
SequentialAnimation on y {
running: cat.moving
loops: Animation.Infinite
alwaysRunToEnd: true
NumberAnimation { to: cat.running ? -4 : -2; duration: cat.running ? 130 : 240; easing.type: Easing.InOutSine }
NumberAnimation { to: 0; duration: cat.running ? 130 : 240; easing.type: Easing.InOutSine }
}
// purr wiggle while petted / loving
SequentialAnimation on rotation {
running: cat.petted || cat.mood === "loving"
loops: Animation.Infinite
alwaysRunToEnd: true
NumberAnimation { to: 2.5; duration: 180; easing.type: Easing.InOutSine }
NumberAnimation { to: -2.5; duration: 180; easing.type: Easing.InOutSine }
}
}
// speech bubble: "!" / "z" / "♥"
Text {
id: bubble
readonly property string glyph: cat.mood === "alert" ? "!" : cat.sleeping ? "z" : (cat.mood === "loving" || cat.petted) ? "♥" : ""
x: 82; y: 4
text: glyph
visible: glyph.length > 0
font.pixelSize: cat.mood === "alert" ? 22 : 16
font.bold: true
font.family: "Helvetica Neue"
color: cat.mood === "alert" ? "#f2a33c" : (glyph === "♥" ? "#f25f7a" : cat.ink)
transform: Scale { xScale: cat.facingRight ? 1 : -1; origin.x: bubble.width / 2 }
Accessible.ignored: true
SequentialAnimation on y {
running: bubble.visible
loops: Animation.Infinite
NumberAnimation { from: 10; to: -8; duration: cat.sleeping ? 1800 : 900; easing.type: Easing.OutQuad }
}
SequentialAnimation on opacity {
running: bubble.visible
loops: Animation.Infinite
NumberAnimation { from: 0; to: 1; duration: cat.sleeping ? 500 : 250 }
NumberAnimation { to: 0; duration: cat.sleeping ? 1300 : 650 }
}
}
}
states: [
State {
name: "running"
when: cat.running && !cat.lifted
PropertyChanges { body.rotation: -5; head.y: 15; head.x: 48 }
PropertyChanges { cat.tailC1X: 8; cat.tailC1Y: 60; cat.tailC2X: 4; cat.tailC2Y: 56; cat.tailX: 0; cat.tailY: 52 }
},
State {
name: "sitting"
when: (cat.mood === "sitting" || cat.mood === "loving" || cat.mood === "alert") && !cat.lifted
PropertyChanges { body.x: 34; body.y: 40; body.width: 40; body.height: 40 }
PropertyChanges { head.y: 8; head.x: 40 }
PropertyChanges { cat.tailC1X: 26; cat.tailC1Y: 84; cat.tailC2X: 40; cat.tailC2Y: 88; cat.tailX: 52; cat.tailY: 80 }
},
State {
name: "grooming"
when: cat.mood === "grooming" && !cat.lifted
PropertyChanges { body.x: 34; body.y: 40; body.width: 40; body.height: 40 }
PropertyChanges { head.y: 10; head.x: 38; head.rotation: -10 }
PropertyChanges { cat.tailC1X: 26; cat.tailC1Y: 84; cat.tailC2X: 40; cat.tailC2Y: 88; cat.tailX: 52; cat.tailY: 80 }
},
State {
name: "stretching"
when: cat.mood === "stretching" && !cat.lifted
PropertyChanges { body.x: 16; body.y: 50; body.width: 64; body.height: 26; body.rotation: -8 }
PropertyChanges { head.y: 32; head.x: 52; head.rotation: 7 }
PropertyChanges { cat.tailC1X: 10; cat.tailC1Y: 48; cat.tailC2X: 6; cat.tailC2Y: 30; cat.tailX: 14; cat.tailY: 22 }
},
State {
name: "sleeping"
when: cat.sleeping && !cat.lifted
PropertyChanges { body.x: 22; body.y: 54; body.width: 56; body.height: 26 }
PropertyChanges { head.y: 24; head.x: 44; head.rotation: 4 }
PropertyChanges { cat.tailC1X: 24; cat.tailC1Y: 86; cat.tailC2X: 60; cat.tailC2Y: 90; cat.tailX: 76; cat.tailY: 78 }
},
State {
name: "lifted"
when: cat.lifted
PropertyChanges { torso.rotation: 8; torso.y: -6 }
PropertyChanges { body.x: 28; body.y: 40; body.width: 42; body.height: 40 }
PropertyChanges { head.y: 8; head.x: 40 }
PropertyChanges { cat.tailC1X: 22; cat.tailC1Y: 74; cat.tailC2X: 18; cat.tailC2Y: 86; cat.tailX: 24; cat.tailY: 92 }
PropertyChanges { shadow.opacity: 0.06; shadow.scale: 0.7 }
}
]
transitions: Transition {
NumberAnimation { target: torso; properties: "rotation,y"; duration: 260; easing.type: Easing.OutBack }
NumberAnimation { target: shadow; properties: "opacity,scale"; duration: 260 }
}
readonly property bool seated: mood === "sitting" || mood === "loving" || mood === "alert" || mood === "grooming"
}
+87
View File
@@ -0,0 +1,87 @@
pragma ComponentBehavior: Bound
import QtQuick
import Workcat
Window {
id: root
required property CatController cat
required property AppSettings settings
readonly property var moodNames: ["walking", "running", "sitting", "grooming", "stretching", "sleeping", "loving", "alert"]
readonly property real floorY: Screen.desktopAvailableHeight - height
function openSettings() {
settingsLoader.active = true
if (settingsLoader.status === Loader.Ready)
(settingsLoader.item as SettingsWindow).show()
}
width: settings.catSize
height: Math.round(settings.catSize * 0.9)
x: cat.x
y: floorY
color: Platform.opaqueBackground ? settings.coat : "transparent"
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool
| Qt.NoDropShadowWindowHint | Qt.WindowDoesNotAcceptFocus
| (Platform.bypassWindowManager ? Qt.X11BypassWindowManagerHint : 0)
visible: true
title: qsTr("Workcat")
Component.onCompleted: {
cat.screenWidth = Screen.desktopAvailableWidth
cat.catWidth = width
}
onWidthChanged: cat.catWidth = width
Screen.onDesktopAvailableWidthChanged: cat.screenWidth = Screen.desktopAvailableWidth
Cat {
id: sprite
anchors.fill: parent
coat: root.settings.coat
facingRight: root.cat.facingRight
mood: root.moodNames[root.cat.mood]
lifted: root.cat.dragging
petted: root.cat.hovered && !root.cat.dragging
look: root.cat.look
Accessible.role: Accessible.Button
Accessible.name: qsTr("Workcat")
}
HoverHandler {
onHoveredChanged: root.cat.hovered = hovered
}
DragHandler {
target: null
onActiveChanged: root.cat.dragging = active
onCentroidChanged: {
if (active)
root.cat.x = Math.max(0, Math.min(root.cat.screenWidth - root.width,
root.x + centroid.position.x - centroid.pressPosition.x))
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: root.cat.pet()
onDoubleTapped: root.openSettings()
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: root.openSettings()
}
Loader {
id: settingsLoader
active: false
sourceComponent: SettingsWindow {
cat: root.cat
settings: root.settings
onClosing: settingsLoader.active = false
}
onLoaded: (item as SettingsWindow).show()
}
}
+169
View File
@@ -0,0 +1,169 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls.Basic
import QtQuick.Layouts
import Workcat
Window {
id: win
required property CatController cat
required property AppSettings settings
readonly property var coats: [
{ name: qsTr("Apricot"), color: "#f4b183" },
{ name: qsTr("Ivory"), color: "#f3ede2" },
{ name: qsTr("Grey"), color: "#a4a6b3" },
{ name: qsTr("Charcoal"), color: "#3c3b46" },
{ name: qsTr("Sage"), color: "#a9bfa0" },
{ name: qsTr("Plum"), color: "#9a7bb0" }
]
width: 440
height: 600
minimumWidth: 380
minimumHeight: 520
title: qsTr("Workcat")
color: "#faf7f2"
flags: Qt.Dialog | Qt.WindowStaysOnTopHint
ColumnLayout {
anchors.fill: parent
anchors.margins: 24
spacing: 18
RowLayout {
Layout.fillWidth: true
spacing: 16
Cat {
Layout.preferredWidth: 110
Layout.preferredHeight: 99
coat: win.settings.coat
mood: "sitting"
petted: previewHover.hovered
HoverHandler { id: previewHover }
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Label {
Layout.fillWidth: true
text: qsTr("Workcat")
font.pixelSize: 26
font.bold: true
color: "#2f2a36"
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("A little cat that wanders along the bottom of your screen. Drag it around, pet it with a click, and it will purr.")
color: "#7a7484"
}
}
}
GroupBox {
Layout.fillWidth: true
title: qsTr("Coat")
Flow {
anchors.fill: parent
spacing: 10
Repeater {
model: win.coats
Column {
id: swatch
required property var modelData
readonly property bool selected: Qt.colorEqual(win.settings.coat, modelData.color)
spacing: 4
Rectangle {
width: 40; height: 40
radius: 20
color: swatch.modelData.color
border.width: swatch.selected ? 3 : 1
border.color: swatch.selected ? "#f25f7a" : "#d8d2ca"
antialiasing: true
Accessible.role: Accessible.RadioButton
Accessible.name: swatch.modelData.name
Accessible.checked: swatch.selected
activeFocusOnTab: true
Keys.onSpacePressed: win.settings.coat = swatch.modelData.color
TapHandler { onTapped: win.settings.coat = swatch.modelData.color }
}
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: swatch.modelData.name
font.pixelSize: 11
color: "#7a7484"
}
}
}
}
}
GridLayout {
Layout.fillWidth: true
columns: 2
columnSpacing: 14
rowSpacing: 12
Label { text: qsTr("Size") }
Slider {
Layout.fillWidth: true
from: 72; to: 260; stepSize: 4
value: win.settings.catSize
onMoved: win.settings.catSize = value
}
Label { text: qsTr("Pace") }
Slider {
Layout.fillWidth: true
from: 10; to: 200; stepSize: 5
value: win.settings.walkSpeed
onMoved: win.settings.walkSpeed = value
}
Label { text: qsTr("Eyes") }
Switch {
text: qsTr("Follow the mouse")
checked: win.settings.followCursor
onToggled: win.settings.followCursor = checked
}
Label { text: qsTr("Cat") }
Switch {
text: checked ? qsTr("Napping") : qsTr("Awake")
checked: win.cat.paused
onToggled: win.cat.paused = checked
}
}
GroupBox {
Layout.fillWidth: true
title: qsTr("Tricks")
Flow {
anchors.fill: parent
spacing: 8
Button { text: qsTr("Sit"); onClicked: win.cat.doTrick(CatController.Sitting) }
Button { text: qsTr("Groom"); onClicked: win.cat.doTrick(CatController.Grooming) }
Button { text: qsTr("Stretch"); onClicked: win.cat.doTrick(CatController.Stretching) }
Button { text: qsTr("Dash"); onClicked: win.cat.doTrick(CatController.Running) }
Button { text: qsTr("Nap"); onClicked: win.cat.doTrick(CatController.Sleeping) }
}
}
Item { Layout.fillHeight: true }
RowLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
text: qsTr("%n pet(s) so far.", "", win.settings.petCount)
color: "#7a7484"
}
Button {
text: qsTr("Quit")
onClicked: Qt.quit()
}
}
}
}