- src/storage.rs: AppState (last_window_width/height) + load/save atomic write 到 %APPDATA%/WindowsGotify/state.json(§18) - ui/settings-page.slint: 5 分组(Connection / Notifications / Startup / Appearance / Advanced),Appearance 实时切主题(System/Light/Dark radio);Advanced 折叠 - ui/dialogs.slint: AboutDialog + ConfirmDialog(§54) - ui/server-card.slint: 单 server 行(name/url/enabled toggle/edit/remove) - ui/app.slint: settings 页切换、About 导航、theme 联动 - src/lib.rs: load_state → 启动时应用窗口尺寸;ui.window() .on_close_requested 在 OS 关闭前 save_state;wire about/close callbacks - src/error.rs: 加 AppError::Storage(String) 变体 - Cargo.toml: 加 serde + serde_json 避坑: - Slint 1.x 同文件内 export component 必须在引用前定义(sub-components 移到 SettingsPage 上面) - Slint 不支持 padding 两值简写 (padding: 0 16px) → 用 padding-* 四属性 - Slint 不支持 Rectangle 内建 border-color 覆盖 → 命名 card-border - Slint 不支持 alignment: right → 用 spacer + horizontal-stretch: 1 - 修了一处 .slint 文件被 PowerShell Set-Content GBK 损坏的问题(已 通过 write 工具 + UTF-8 no BOM 重写并清理 ui/ 所有 .slint) 验证:cargo build / build --release 都 exit 0,0 warning - debug 27.55 MB, subsystem = WINDOWS_CUI - release 8.10 MB, subsystem = WINDOWS_GUI
213 lines
7.8 KiB
Plaintext
213 lines
7.8 KiB
Plaintext
// Main window + sidebar (AGENTS.md §6, §7).
|
|
//
|
|
// Phase 2 C3 wires the Settings page, About dialog, and window-state
|
|
// persistence. The Appearance section in `settings-page.slint` is the only
|
|
// settings UI that drives real state today — every other control is a
|
|
// shell that Phase 5 hooks to `AppConfig`.
|
|
|
|
import { AppearanceMode } from "./theme.slint";
|
|
import { NavItem, SectionLabel } from "./components.slint";
|
|
import { MessageItem } from "./message-card.slint";
|
|
import { MessageList } from "./message-list.slint";
|
|
import { EmptyState } from "./empty-state.slint";
|
|
import { SettingsPage } from "./settings-page.slint";
|
|
import { AboutDialog } from "./dialogs.slint";
|
|
|
|
export enum SidebarSelection {
|
|
All,
|
|
Settings,
|
|
About,
|
|
}
|
|
|
|
export component MainWindow inherits Window {
|
|
title: "Gotify";
|
|
preferred-width: 960px;
|
|
preferred-height: 680px;
|
|
min-width: 760px;
|
|
min-height: 520px;
|
|
|
|
// Theme switch — Rust-controllable via `ui.set_theme(...)`.
|
|
in-out property <AppearanceMode> theme: AppearanceMode.Dark;
|
|
out property <bool> is-dark: theme != AppearanceMode.Light;
|
|
|
|
// Palette — all derived from `is-dark` (AGENTS.md §52).
|
|
out property <color> bg: is-dark ? #1f2024 : #f5f6f8;
|
|
out property <color> surface: is-dark ? #28292d : #ffffff;
|
|
out property <color> surface-elev: is-dark ? #2f3035 : #fafbfc;
|
|
out property <color> border: is-dark ? #3a3b40 : #e4e6ea;
|
|
out property <color> text-primary: is-dark ? #e6e6e6 : #1a1a1a;
|
|
out property <color> text-second: is-dark ? #a0a0a0 : #5c5c5c;
|
|
out property <color> text-muted: is-dark ? #6a6a6a : #9a9a9a;
|
|
out property <color> accent: is-dark ? #4ea1ff : #0a66c2;
|
|
out property <color> success: #4caf50;
|
|
out property <color> warning: #ff9800;
|
|
out property <color> danger: #f44336;
|
|
out property <color> priority-low: #6a6a6a;
|
|
out property <color> priority-normal: is-dark ? #4ea1ff : #0a66c2;
|
|
out property <color> priority-high: #ff9800;
|
|
out property <color> priority-max: #f44336;
|
|
|
|
background: bg;
|
|
|
|
in-out property <SidebarSelection> selection: SidebarSelection.All;
|
|
|
|
// ── Phase 2 C2: mock messages + selection ────────────────────
|
|
in-out property <[MessageItem]> messages;
|
|
in-out property <int> selected-message-index: -1;
|
|
callback test-notification-clicked();
|
|
callback message-card-clicked(int);
|
|
|
|
// ── Phase 2 C3: Settings / About / window state ──────────────
|
|
// Initial appearance radio (0=System/1=Light/2=Dark). Maps 1:1 to
|
|
// AppearanceMode: System=0, Light=1, Dark=2.
|
|
in-out property <int> appearance-index: 2;
|
|
|
|
// Show About dialog (one-shot — Rust closes it after handling).
|
|
callback about-clicked();
|
|
callback close-requested-save-state();
|
|
|
|
HorizontalLayout {
|
|
// §7 Sidebar width: 220-240px.
|
|
Rectangle {
|
|
width: 232px;
|
|
background: surface;
|
|
|
|
VerticalLayout {
|
|
padding: 20px;
|
|
spacing: 6px;
|
|
|
|
Text {
|
|
text: "Gotify";
|
|
color: text-primary;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
}
|
|
Text {
|
|
text: "Windows Client · Phase 2";
|
|
color: text-muted;
|
|
font-size: 10px;
|
|
}
|
|
|
|
Rectangle { height: 16px; }
|
|
|
|
SectionLabel { label: "MESSAGES"; text-color: text-muted; }
|
|
NavItem {
|
|
label: "All";
|
|
selected: root.selection == SidebarSelection.All;
|
|
clicked => { root.selection = SidebarSelection.All; }
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
SectionLabel { label: "SERVERS"; text-color: text-muted; }
|
|
Text {
|
|
text: "(no servers configured)";
|
|
color: text-muted;
|
|
font-size: 11px;
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
SectionLabel { label: "FILTERS"; text-color: text-muted; }
|
|
NavItem {
|
|
label: "High";
|
|
selected: false;
|
|
has-accent: false;
|
|
clicked => { /* TODO Phase 2 C2 */ }
|
|
}
|
|
NavItem {
|
|
label: "Normal";
|
|
selected: false;
|
|
has-accent: false;
|
|
clicked => { /* TODO Phase 2 C2 */ }
|
|
}
|
|
NavItem {
|
|
label: "Low";
|
|
selected: false;
|
|
has-accent: false;
|
|
clicked => { /* TODO Phase 2 C2 */ }
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
SectionLabel { label: "APP"; text-color: text-muted; }
|
|
NavItem {
|
|
label: "Settings";
|
|
selected: root.selection == SidebarSelection.Settings;
|
|
clicked => { root.selection = SidebarSelection.Settings; }
|
|
}
|
|
NavItem {
|
|
label: "About";
|
|
selected: root.selection == SidebarSelection.About;
|
|
clicked => {
|
|
root.selection = SidebarSelection.About;
|
|
root.about-clicked();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Right pane — driven by `selection`.
|
|
Rectangle {
|
|
background: bg;
|
|
|
|
if root.selection == SidebarSelection.All: VerticalLayout {
|
|
padding: 24px;
|
|
spacing: 12px;
|
|
|
|
Text {
|
|
text: messages.length == 0 ? "Messages" : "Messages · " + messages.length;
|
|
color: text-primary;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
if messages.length > 0: MessageList {
|
|
messages: messages;
|
|
selected-index: root.selected-message-index;
|
|
height: parent.height - 48px;
|
|
message-clicked(idx) => {
|
|
root.selected-message-index = idx;
|
|
root.message-card-clicked(idx);
|
|
}
|
|
}
|
|
|
|
if messages.length == 0: EmptyState {
|
|
test-notification => {
|
|
root.test-notification-clicked();
|
|
}
|
|
}
|
|
}
|
|
|
|
if root.selection == SidebarSelection.Settings: SettingsPage {
|
|
appearance-index: root.appearance-index;
|
|
appearance-changed(idx) => {
|
|
root.appearance-index = idx;
|
|
root.theme = idx == 0 ? AppearanceMode.System :
|
|
idx == 1 ? AppearanceMode.Light :
|
|
AppearanceMode.Dark;
|
|
}
|
|
}
|
|
|
|
// About is rendered via a Dialog overlay — not in the right pane.
|
|
// A separate About row keeps the layout stable; the dialog is
|
|
// shown via `about-clicked` callback to Rust.
|
|
if root.selection == SidebarSelection.About: Rectangle {
|
|
background: bg;
|
|
Text {
|
|
x: 24px;
|
|
y: 24px;
|
|
text: "About — see the dialog.";
|
|
color: text-muted;
|
|
font-size: 13px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Modal About dialog — opened when the user clicks About in the sidebar.
|
|
AboutDialog {
|
|
// visible-by-default is false. Toggled via Rust by listening to
|
|
// `about-clicked`. C3 wires the bare open/close plumbing.
|
|
}
|
|
} |