diff --git a/ui/app.slint b/ui/app.slint index 6b53065..ab7f663 100644 --- a/ui/app.slint +++ b/ui/app.slint @@ -53,6 +53,16 @@ export component MainWindow inherits Window { in-out property selection: SidebarSelection.All; + // ── Phase 7 C3 fix: AboutDialog overlay. The previous version + // inherited Slint's `Dialog` (a PopupWindow-derived type) and + // instantiated it inline; without a runtime `.show()` call, + // close() was a no-op and the Close button became inert. We + // now drive the dialog with a plain boolean (toggled from Rust) + // and render it as a manual Rectangle overlay in `if + // root.show-about: AboutDialog { … }`. See dialogs.slint. ── + in-out property show-about: false; + callback close-about(); + // ── Phase 2 C2: mock messages + selection ──────────────────── in-out property <[MessageItem]> messages; in-out property selected-message-index: -1; @@ -225,6 +235,13 @@ export component MainWindow inherits Window { clicked => { root.selection = SidebarSelection.About; root.about-clicked(); + // Phase 7 C3 fix: surface the (now + // Rectangle-overlay) About dialog. The + // Rust `about-clicked` callback still runs + // for telemetry; the actual show happens + // here so the dialog opens the moment the + // user clicks the nav row. + root.show-about = true; } } } @@ -415,21 +432,20 @@ export component MainWindow inherits Window { } } - // About entry stays as a simple right-pane marker; the - // modal AboutDialog overlay is wired through AboutDialog in - // this component (visibility toggled via `about-clicked`). - if root.selection == SidebarSelection.About: Rectangle { - background: bg; - Text { - x: 24px; - y: 24px; - text: "About — see the dialog."; - color: text-muted; - font-size: 13px; - } - } + // Phase 7 C3 fix: the About page is no longer a separate + // right-pane marker. Clicking the sidebar About row opens + // a modal dialog overlay (see `if root.show-about: + // AboutDialog { … }` further down), so there's nothing + // to show in the right pane for `selection == About`. } } - AboutDialog {} + // Phase 7 C3 fix: conditional About overlay driven by + // `show-about`. See the comment on the property above for why + // we no longer use Slint's `Dialog` type here. + if root.show-about: AboutDialog { + close-dialog => { + root.show-about = false; + } + } } \ No newline at end of file diff --git a/ui/dialogs.slint b/ui/dialogs.slint index 52da406..e0e9411 100644 --- a/ui/dialogs.slint +++ b/ui/dialogs.slint @@ -1,79 +1,120 @@ -// AGENTS.md §54 modals. C3 only ships the About dialog (called from the -// sidebar) and a generic confirm dialog template; full delete-confirm flow -// lands in Phase 5 once `AppConfig` persistence is wired. +// AGENTS.md §54 modals. +// +// Phase 7 C3 fix: the original `inherits Dialog` (a Slint +// PopupWindow-derived type) was instantiated inline inside +// MainWindow. PopupWindow expects to be `.show()`n via the Rust +// runtime; without that, it stays "declared but never properly +// popped up", and its `close()` no-op'd — the Close button became +// inert and the user had to force-kill the process. +// +// Fix: make AboutDialog an inline Rectangle overlay backed by a +// `visible` property toggled from Rust. A full-window dimmed +// backdrop catches outside-clicks to dismiss; the content card +// stays interactive. Same wiring pattern the rest of the UI uses +// (Slint `if root.show-about: AboutDialog { ... }`). -export component AboutDialog inherits Dialog { +export component AboutDialog inherits Rectangle { in property version-string: "v0.1.0"; in property build-date: "2026-09-19"; - // Theme-aware color tokens (Dark defaults; C4 will widen). in property bg-color: #28292d; in property text-primary-color: #e6e6e6; in property text-secondary-color: #a0a0a0; in property accent-color: #4ea1ff; in property button-bg: #2f3035; in property button-border: #3a3b40; + in property backdrop-color: #000000; - title: "About Gotify Client"; - background: bg-color; + callback close-dialog(); - VerticalLayout { - padding: 24px; - spacing: 12px; - width: 360px; + background: transparent; - Text { - text: "Windows Gotify Client (Rust)"; - color: text-primary-color; - font-size: 16px; - font-weight: 700; + // Backdrop: full-window dimmed layer that swallows clicks so + // clicking outside the card dismisses the dialog. + Rectangle { + x: 0; + y: 0; + width: 100%; + height: 100%; + background: backdrop-color; + opacity: 0.55; + TouchArea { + clicked => { + root.close-dialog(); + } } - Text { - text: version-string; - color: accent-color; - font-size: 13px; - } - Text { - text: "Built " + build-date; - color: text-secondary-color; - font-size: 11px; - } - Rectangle { height: 8px; } - Text { - text: "Lightweight, modern, Windows-native Gotify\ndesktop client. Phase 2 of 8."; - color: text-secondary-color; - font-size: 12px; - wrap: word-wrap; - } - Rectangle { height: 12px; } + } - // OK button. - Rectangle { - width: 100px; - height: 32px; - background: button-bg; - border-color: button-border; - border-width: 1px; - border-radius: 6px; - x: parent.width - self.width; + // Card: 360x240-ish centered modal content. + Rectangle { + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + width: 380px; + height: 280px; + background: bg-color; + border-color: button-border; + border-width: 1px; + border-radius: 12px; + + VerticalLayout { + padding: 24px; + spacing: 10px; Text { - x: (parent.width - self.width) / 2; - y: (parent.height - self.height) / 2; - text: "Close"; + text: "Windows Gotify Client (Rust)"; color: text-primary-color; - font-size: 12px; + font-size: 16px; + font-weight: 700; } - TouchArea { - clicked => { - root.close(); + Text { + text: version-string; + color: accent-color; + font-size: 13px; + } + Text { + text: "Built " + build-date; + color: text-secondary-color; + font-size: 11px; + } + Rectangle { height: 8px; } + Text { + text: "Lightweight, modern, Windows-native Gotify\ndesktop client. Phase 1–8 complete."; + color: text-secondary-color; + font-size: 12px; + wrap: word-wrap; + } + Rectangle { height: 8px; } + + // Close button row. + HorizontalLayout { + alignment: end; + Rectangle { + width: 100px; + height: 32px; + background: button-bg; + border-color: button-border; + border-width: 1px; + border-radius: 6px; + Text { + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + text: "Close"; + color: text-primary-color; + font-size: 12px; + } + // TouchArea last so it sits on top of the Text. + TouchArea { + clicked => { + root.close-dialog(); + } + } } } } } } -export component ConfirmDialog inherits Dialog { +export component ConfirmDialog inherits Rectangle { in property title-text: "Confirm"; in property message: "Are you sure?"; in property confirm-label: "Confirm"; @@ -88,65 +129,87 @@ export component ConfirmDialog inherits Dialog { in property danger-color: #f44336; in property button-bg: #2f3035; in property button-border: #3a3b40; + in property backdrop-color: #000000; - title: title-text; - background: bg-color; + background: transparent; - VerticalLayout { - padding: 24px; - spacing: 16px; - width: 360px; - - Text { - text: message; - color: text-primary-color; - font-size: 13px; - wrap: word-wrap; + Rectangle { + x: 0; + y: 0; + width: 100%; + height: 100%; + background: backdrop-color; + opacity: 0.55; + TouchArea { + clicked => { + root.cancelled(); + } } + } - HorizontalLayout { - spacing: 12px; + Rectangle { + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + width: 380px; + height: 180px; + background: bg-color; + border-color: button-border; + border-width: 1px; + border-radius: 12px; - Rectangle { horizontal-stretch: 1; } + VerticalLayout { + padding: 24px; + spacing: 16px; - Rectangle { - width: 90px; - height: 32px; - background: button-bg; - border-color: button-border; - border-width: 1px; - border-radius: 6px; - Text { - x: (parent.width - self.width) / 2; - y: (parent.height - self.height) / 2; - text: cancel-label; - color: text-primary-color; - font-size: 12px; - } - TouchArea { - clicked => { - root.cancelled(); - root.close(); + Text { + text: message; + color: text-primary-color; + font-size: 13px; + wrap: word-wrap; + } + + HorizontalLayout { + spacing: 12px; + + Rectangle { horizontal-stretch: 1; } + + Rectangle { + width: 90px; + height: 32px; + background: button-bg; + border-color: button-border; + border-width: 1px; + border-radius: 6px; + Text { + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + text: cancel-label; + color: text-primary-color; + font-size: 12px; + } + TouchArea { + clicked => { + root.cancelled(); + } } } - } - Rectangle { - width: 90px; - height: 32px; - background: danger-color; - border-radius: 6px; - Text { - x: (parent.width - self.width) / 2; - y: (parent.height - self.height) / 2; - text: confirm-label; - color: #ffffff; - font-size: 12px; - font-weight: 600; - } - TouchArea { - clicked => { - root.confirmed(); - root.close(); + Rectangle { + width: 90px; + height: 32px; + background: danger-color; + border-radius: 6px; + Text { + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + text: confirm-label; + color: #ffffff; + font-size: 12px; + font-weight: 600; + } + TouchArea { + clicked => { + root.confirmed(); + } } } }