From 6b9553f6d41562f3c8979ab6ad4538dee76c345b Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 11 Sep 2026 03:10:04 +0200 Subject: [PATCH] Stop clipping the cat at the window edge The cat was drawn in a 100 x 90 box that was exactly the window, so anything reaching past it was cut off: the right whiskers always, and more of them in poses that push the head forward, up to art x 109 while stretching. The window box is now 120 x 106 with the drawing inset at (7, 14). The slack is symmetric on purpose, because facing left mirrors the art about the box centre, so asymmetric slack would clip the other side instead. The mood-name mapper grew the shared aspect ratio and became CatDefs, so every window sizes the cat the same way. Nose and mug offsets follow the new inset. Co-Authored-By: Claude Opus 5 --- CMakeLists.txt | 4 +- qml/Cat.qml | 97 +++++++++++++++++++++++------------------- qml/CatDefs.qml | 16 +++++++ qml/CatMoods.qml | 13 ------ qml/CatWindow.qml | 4 +- qml/OverlayWindow.qml | 4 +- qml/SettingsWindow.qml | 2 +- src/catcontroller.cpp | 4 +- 8 files changed, 78 insertions(+), 66 deletions(-) create mode 100644 qml/CatDefs.qml delete mode 100644 qml/CatMoods.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 45f54d7..3629c33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,13 +22,13 @@ qt_add_executable(tncat target_include_directories(tncat PRIVATE src) # QML files live in qml/ but register at the module root (TNCat.Main etc.) -set(TNCAT_QML Main Cat Chip Cup CatWindow ChipWindow CupWindow OverlayWindow SettingsWindow CatMoods) +set(TNCAT_QML Main Cat Chip Cup CatWindow ChipWindow CupWindow OverlayWindow SettingsWindow CatDefs) foreach(qmlfile IN LISTS TNCAT_QML) set_source_files_properties(qml/${qmlfile}.qml PROPERTIES QT_RESOURCE_ALIAS ${qmlfile}.qml) list(APPEND TNCAT_QML_FILES qml/${qmlfile}.qml) endforeach() -set_source_files_properties(qml/CatMoods.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) +set_source_files_properties(qml/CatDefs.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) qt_add_qml_module(tncat URI TNCat diff --git a/qml/Cat.qml b/qml/Cat.qml index 1a87a9c..454e12d 100644 --- a/qml/Cat.qml +++ b/qml/Cat.qml @@ -40,7 +40,15 @@ Item { readonly property bool sleeping: mood === "sleeping" readonly property bool happy: mood === "loving" || mood === "grooming" || mood === "stretching" || mood === "drinking" || petted - readonly property real unit: width / 100 + // The cat is drawn in a 100 x 90 box. The window box is larger so whiskers, + // tail and the glyphs above the head stay inside it in every pose. The slack + // has to be symmetric: facing left mirrors the art about the box centre. + // Widest reach is the right whiskers while stretching, art x 109. + readonly property real designWidth: 120 + readonly property real designHeight: 106 + readonly property real artX: 7 + readonly property real artY: 14 + readonly property real unit: width / designWidth // tail pose (design units); swish is added on top by the idle animation property real tailC1X: 14 @@ -57,8 +65,8 @@ Item { Behavior on tailX { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } } Behavior on tailY { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } } - implicitWidth: 100 - implicitHeight: 90 + implicitWidth: designWidth + implicitHeight: designHeight transform: Scale { origin.x: cat.width / 2 @@ -67,6 +75,8 @@ Item { Item { id: art + x: cat.artX * cat.unit + y: cat.artY * cat.unit width: 100 height: 90 transformOrigin: Item.TopLeft @@ -459,6 +469,46 @@ Item { } } + // Machine code tag, flashed in the headroom whenever the mood changes + Item { + id: codeTag + x: 16; y: -12 + width: codeBg.width + height: codeBg.height + opacity: 0 + visible: cat.showCodes && opacity > 0.01 + transform: Scale { xScale: cat.facingRight ? 1 : -1; origin.x: codeTag.width / 2 } + z: 10 + Accessible.ignored: true + + Rectangle { + id: codeBg + width: codeText.implicitWidth + 8 + height: codeText.implicitHeight + 4 + radius: 3 + color: "#242833" + opacity: 0.92 + antialiasing: true + + Text { + id: codeText + anchors.centerIn: parent + text: cat.moodCode + color: "#8fd6ff" + font.pixelSize: 10 + font.bold: true + font.family: "Menlo" + } + } + + SequentialAnimation { + id: codeFlash + NumberAnimation { target: codeTag; property: "opacity"; to: 1; duration: 160 } + PauseAnimation { duration: 1500 } + NumberAnimation { target: codeTag; property: "opacity"; to: 0; duration: 500 } + } + } + // speech bubble: "!" / "z" / "♥" Text { id: bubble @@ -490,47 +540,6 @@ Item { } } - // Machine code tag above the head, flashed whenever the mood changes - Item { - id: codeTag - x: 30; y: 2 - width: codeBg.width - height: codeBg.height - opacity: 0 - visible: cat.showCodes && opacity > 0.01 - transformOrigin: Item.Center - transform: Scale { xScale: cat.facingRight ? 1 : -1; origin.x: codeTag.width / 2 } - z: 10 - scale: cat.unit - Accessible.ignored: true - - Rectangle { - id: codeBg - width: codeText.implicitWidth + 10 - height: codeText.implicitHeight + 6 - radius: 4 - color: "#242833" - opacity: 0.92 - antialiasing: true - - Text { - id: codeText - anchors.centerIn: parent - text: cat.moodCode - color: "#8fd6ff" - font.pixelSize: 11 - font.bold: true - font.family: "Menlo" - } - } - - SequentialAnimation { - id: codeFlash - NumberAnimation { target: codeTag; property: "opacity"; to: 1; duration: 160 } - PauseAnimation { duration: 1500 } - NumberAnimation { target: codeTag; property: "opacity"; to: 0; duration: 500 } - } - } onMoodCodeChanged: if (showCodes && moodCode.length > 0) codeFlash.restart() diff --git a/qml/CatDefs.qml b/qml/CatDefs.qml new file mode 100644 index 0000000..9e0ab97 --- /dev/null +++ b/qml/CatDefs.qml @@ -0,0 +1,16 @@ +pragma Singleton +import QtQuick + +// Shared facts about the drawing, so every window sizes the cat the same way. +QtObject { + // Cat.qml draws in a 100 x 90 box inside a 120 x 106 window box. The slack + // holds the whiskers, the tail and the glyphs that float above the head. + readonly property real aspect: 106 / 120 + + readonly property var moodNames: ["walking", "running", "sitting", "grooming", "stretching", + "sleeping", "loving", "alert", "chasing", "batting", "drinking"] + + function mood(value) { + return moodNames[value] !== undefined ? moodNames[value] : "sitting" + } +} diff --git a/qml/CatMoods.qml b/qml/CatMoods.qml deleted file mode 100644 index 74e2f87..0000000 --- a/qml/CatMoods.qml +++ /dev/null @@ -1,13 +0,0 @@ -pragma Singleton -import QtQuick -import TNCat - -// Maps CatController::Mood onto the strings Cat.qml understands. -QtObject { - readonly property var names: ["walking", "running", "sitting", "grooming", "stretching", - "sleeping", "loving", "alert", "chasing", "batting", "drinking"] - - function name(mood) { - return names[mood] !== undefined ? names[mood] : "sitting" - } -} diff --git a/qml/CatWindow.qml b/qml/CatWindow.qml index cc960d6..33be2ce 100644 --- a/qml/CatWindow.qml +++ b/qml/CatWindow.qml @@ -13,7 +13,7 @@ Window { signal settingsRequested() width: settings.catSize - height: Math.round(settings.catSize * 0.9) + height: Math.round(settings.catSize * CatDefs.aspect) x: cat.x y: Screen.desktopAvailableHeight - height - settings.bottomMargin color: Platform.opaqueBackground ? settings.coat : "transparent" @@ -41,7 +41,7 @@ Window { anchors.fill: parent coat: win.settings.coat facingRight: win.cat.facingRight - mood: CatMoods.name(win.cat.mood) + mood: CatDefs.mood(win.cat.mood) lifted: win.cat.dragging petted: win.cat.hovered && !win.cat.dragging look: win.cat.look diff --git a/qml/OverlayWindow.qml b/qml/OverlayWindow.qml index a746434..d05012b 100644 --- a/qml/OverlayWindow.qml +++ b/qml/OverlayWindow.qml @@ -18,7 +18,7 @@ Window { property bool overlayActive: false readonly property int catW: settings.catSize - readonly property int catH: Math.round(settings.catSize * 0.9) + readonly property int catH: Math.round(settings.catSize * CatDefs.aspect) readonly property real groundY: height - catH - settings.bottomMargin // Only the cat itself should swallow clicks; everything else falls through. @@ -88,7 +88,7 @@ Window { anchors.fill: parent coat: win.settings.coat facingRight: win.cat.facingRight - mood: CatMoods.name(win.cat.mood) + mood: CatDefs.mood(win.cat.mood) lifted: win.cat.dragging petted: win.cat.hovered && !win.cat.dragging look: win.cat.look diff --git a/qml/SettingsWindow.qml b/qml/SettingsWindow.qml index c92905a..2277b40 100644 --- a/qml/SettingsWindow.qml +++ b/qml/SettingsWindow.qml @@ -37,7 +37,7 @@ Window { spacing: 16 Loader { Layout.preferredWidth: 110 - Layout.preferredHeight: 99 + Layout.preferredHeight: Math.round(110 * CatDefs.aspect) active: win.visible sourceComponent: Cat { coat: win.settings.coat diff --git a/src/catcontroller.cpp b/src/catcontroller.cpp index 6f131d1..184b3ae 100644 --- a/src/catcontroller.cpp +++ b/src/catcontroller.cpp @@ -50,7 +50,7 @@ CatController::CatController(AppSettings *settings, QObject *parent) qreal CatController::catNose() const { - return m_facingRight ? m_x + m_catWidth * 0.78 : m_x + m_catWidth * 0.22; + return m_facingRight ? m_x + m_catWidth * 0.86 : m_x + m_catWidth * 0.14; } void CatController::setX(qreal x) @@ -193,7 +193,7 @@ void CatController::coffeeBreak() return; endChase(); - const qreal reach = m_catWidth * 1.08; // just past the whiskers + const qreal reach = m_catWidth * 1.02; // just past the whiskers const qreal edge = m_catWidth * 0.5; // Put the mug on the side the cat faces, unless that runs off the screen.