@@ -3,20 +3,13 @@ import QtQuick.Window
33import QtQuick.Layouts
44import QtQuick.Controls
55
6- import "components"
7-
86/*
97 * Preview.qml - Development harness for the SDDM theme.
108 *
11- * Mirrors the Main.qml layout but provides mock objects in place of
12- * the SDDM context properties (sddm, userModel, sessionModel, config,
13- * screenModel, keyboard).
14- *
15- * Components receive their dependencies as explicit properties, so they
16- * work identically whether driven by SDDM or by this preview.
17- *
18- * The preview script symlinks components/ and assets/ into preview/
19- * so that relative imports and asset paths resolve to the selected theme.
9+ * Provides mock objects in place of the SDDM context properties, then loads
10+ * the theme layout through a Loader. A file-watcher timer detects changes
11+ * (via a signal file touched by entr) and reloads the Loader in-place,
12+ * keeping the window open for a true live-preview experience.
2013 *
2114 * Usage: ./scripts/preview.sh [-theme <name>]
2215 */
@@ -32,10 +25,7 @@ Window {
3225 // Mock objects
3326 // ═══════════════════════════════════════════════════════════════
3427
35- QtObject {
36- id: mockConfig
37- // Set to "" to use the solid fallback color, or provide a path
38- // to an image (e.g. drop your own into assets/background.jpg).
28+ property QtObject mockConfig: QtObject {
3929 property string background: Qt .resolvedUrl (" assets/background.jpg" )
4030 property string type: " image"
4131 property string color: " #1a1a2e"
@@ -52,8 +42,7 @@ Window {
5242 property int screenHeight: previewWindow .height
5343 }
5444
55- QtObject {
56- id: mockSddm
45+ property QtObject mockSddm: QtObject {
5746 property string hostname: " preview-host"
5847 property bool canPowerOff: true
5948 property bool canReboot: true
@@ -81,8 +70,7 @@ Window {
8170 function hybridSleep () { console .log (" [mock] sddm.hybridSleep()" ) }
8271 }
8372
84- ListModel {
85- id: mockUserModel
73+ property ListModel mockUserModel: ListModel {
8674 property string lastUser: " user"
8775 property int lastIndex: 0
8876 property int disableAvatarsThreshold: 7
@@ -92,17 +80,15 @@ Window {
9280 ListElement { name: " guest" ; realName: " Guest User" ; icon: " " ; needsPassword: true }
9381 }
9482
95- ListModel {
96- id: mockSessionModel
83+ property ListModel mockSessionModel: ListModel {
9784 property int lastIndex: 0
9885
9986 ListElement { name: " Plasma (Wayland)" ; comment: " " }
10087 ListElement { name: " Plasma (X11)" ; comment: " " }
10188 ListElement { name: " GNOME" ; comment: " " }
10289 }
10390
104- QtObject {
105- id: mockKeyboard
91+ property QtObject mockKeyboard: QtObject {
10692 property bool capsLock: false
10793 property bool numLock: true
10894 }
@@ -118,129 +104,60 @@ Window {
118104 property color accentColor: mockConfig .accentColor || " #4a9eff"
119105
120106 // ═══════════════════════════════════════════════════════════════
121- // Theme layout (identical to Main.qml)
107+ // Hot-reload Loader
122108 // ═══════════════════════════════════════════════════════════════
123109
124- // ── Background ───────────────────────────────────────────────
125- Image {
126- id: backgroundImage
127- anchors .fill : parent
128- source: mockConfig .background || " "
129- fillMode: Image .PreserveAspectCrop
130- asynchronous: true
131- onStatusChanged: {
132- if (status === Image .Error && source != " " )
133- console .log (" Background image not found — using fallback color. " +
134- " Drop an image into assets/background.jpg or update theme.conf." )
135- }
136- }
137-
138- Rectangle {
139- id: backgroundFallback
140- anchors .fill : parent
141- color: mockConfig .color || " #1a1a2e"
142- visible: backgroundImage .status !== Image .Ready
143- }
110+ // Reload generation counter — incremented by the file watcher.
111+ property int reloadGeneration: 0
144112
145- Rectangle {
113+ Loader {
114+ id: themeLoader
146115 anchors .fill : parent
147- color: mockConfig .backgroundOverlayColor || " #000000"
148- opacity: mockConfig .backgroundOverlayOpacity || 0.3
149- }
150116
151- // ── Clock ────────────────────────────────────────────────────
152- Clock {
153- id: clock
154- visible: mockConfig .clockVisible === " true"
155- anchors {
156- top: parent .top
157- horizontalCenter: parent .horizontalCenter
158- topMargin: previewWindow .height * 0.08
117+ onStatusChanged: {
118+ if (status === Loader .Error )
119+ console .log (" [hot-reload] Error loading ThemeLayout.qml — check for syntax errors" )
159120 }
160- textColor: primaryColor
161- fontSize: baseFontSize * 4
162- timeFormat: mockConfig .clockFormat || " hh:mm"
163- dateFormat: mockConfig .dateFormat || " dddd, MMMM d"
164121 }
165122
166- // ── Login form ───────────────────────────────────────────────
167- LoginForm {
168- id: loginForm
169- anchors .centerIn : parent
170- width: 320
171-
172- textColor: primaryColor
173- accentColor: previewWindow .accentColor
174- fontSize: baseFontSize
175- fontFamily: previewWindow .fontFamily
176- defaultUsername: mockUserModel .lastUser || " "
177- notificationMessage: previewWindow .notificationMessage
178- capsLockOn: mockKeyboard .capsLock
179-
180- sessionIndex: sessionSelector .currentIndex
181-
182- onLoginRequest : function (username , password ) {
183- previewWindow .notificationMessage = " "
184- mockSddm .login (username, password, sessionSelector .currentIndex )
185- }
123+ // (Re)load the theme layout, passing the preview reference as an
124+ // initial property so `required property` is satisfied at creation time.
125+ function reloadTheme () {
126+ themeLoader .setSource (" ThemeLayout.qml" , { " preview" : previewWindow })
186127 }
187128
188- // ── Footer ───────────────────────────────────────────────────
189- RowLayout {
190- id: footer
191- anchors {
192- bottom: parent .bottom
193- left: parent .left
194- right: parent .right
195- margins: 16
196- }
197- height: 48
198-
199- SessionSelector {
200- id: sessionSelector
201- textColor: primaryColor
202- fontSize: baseFontSize - 2
203- fontFamily: previewWindow .fontFamily
204- sessions: mockSessionModel
205- Layout .preferredWidth : 180
206- Layout .preferredHeight : 32
207- }
208-
209- Item { Layout .fillWidth : true }
210-
211- PowerBar {
212- id: powerBar
213- textColor: primaryColor
214- fontSize: baseFontSize - 2
215- iconSize: baseFontSize + 6
216- Layout .preferredHeight : 48
217-
218- canSuspend: mockSddm .canSuspend
219- canReboot: mockSddm .canReboot
220- canPowerOff: mockSddm .canPowerOff
129+ onReloadGenerationChanged: reloadTheme ()
130+ Component .onCompleted : reloadTheme ()
221131
222- onSuspendClicked: mockSddm .suspend ()
223- onRebootClicked: mockSddm .reboot ()
224- onPowerOffClicked: mockSddm .powerOff ()
225- }
226- }
227-
228- // ── SDDM connections ─────────────────────────────────────────
229- Connections {
230- target: mockSddm
231- function onLoginFailed () {
232- previewWindow .notificationMessage = " Login Failed"
233- loginForm .clearPassword ()
234- }
235- function onLoginSucceeded () {
236- previewWindow .notificationMessage = " "
237- }
238- }
132+ // Poll a signal file whose content is a timestamp written by entr.
133+ // When the content changes, we bump the Loader's generation counter.
134+ // This avoids needing C++ filesystem-watcher plugins.
135+ property string signalFileUrl: Qt .resolvedUrl (" .reload-signal" )
136+ property string lastSignalContent: " "
239137
240138 Timer {
241- interval: 3000
242- running: notificationMessage !== " "
243- onTriggered: notificationMessage = " "
139+ id: fileWatcher
140+ interval: 500
141+ running: true
142+ repeat: true
143+ onTriggered: {
144+ var xhr = new XMLHttpRequest ()
145+ // Append cache-buster so QML doesn't serve a stale cached read
146+ xhr .open (" GET" , previewWindow .signalFileUrl + " ?t=" + Date .now (), false )
147+ try {
148+ xhr .send ()
149+ if (xhr .status === 200 || xhr .responseText !== " " ) {
150+ var content = xhr .responseText .trim ()
151+ if (content !== " " && content !== previewWindow .lastSignalContent ) {
152+ previewWindow .lastSignalContent = content
153+ previewWindow .reloadGeneration ++
154+ console .log (" [hot-reload] Change detected — reloading theme (gen " + previewWindow .reloadGeneration + " )" )
155+ }
156+ }
157+ } catch (e) {
158+ // Signal file doesn't exist yet — ignore
159+ }
160+ }
244161 }
245162
246163 // ── Preview overlay ──────────────────────────────────────────
@@ -266,8 +183,8 @@ Window {
266183 }
267184 Text { text: " |" ; color: " #80ffffff" ; font .pointSize : 9 }
268185 Text {
269- text: " Preview Mode "
270- color: " #ffcc00 "
186+ text: " Live Preview "
187+ color: " #00ff88 "
271188 font .pointSize : 9
272189 font .bold : true
273190 }
0 commit comments