- ui/message-card.slint: export enum Priority + struct MessageItem + MessageCard 组件(§7 优先级圆点 + 标题 + 应用·预览 + 相对时间 + URL 指示) - ui/message-list.slint: VerticalLayout + for 渲染 MessageCard (避开 std-widgets 的 include_paths 配置需求) - ui/empty-state.slint: §9 ✦ glyph(Path 自绘)+ "You're all caught up" + Test Notification 按钮 - ui/app.slint: messages.length 切换 list / empty-state;点击回 调到 MainWindow 顶层 callback - src/models.rs: mock_messages() 生成 12 条覆盖 4 个 priority 的 假消息;format_relative() 输出 "2 min ago" / Yesterday 等 - src/lib.rs: ui.set_messages(mock_messages_model()) 装载;wire on_test_notification_clicked / on_message_card_clicked 避坑: - Slint 1.18 不允许覆盖 Rectangle 内建 border-color,命名用 card-border - ModelRc::from 需要 Rc<VecModel<T>> 不是裸 VecModel 验证:cargo build / build --release 都 exit 0,0 warning - debug 26.55 MB, subsystem = WINDOWS_CUI - release 7.94 MB, subsystem = WINDOWS_GUI
200 lines
7.2 KiB
Plaintext
200 lines
7.2 KiB
Plaintext
// Main window + sidebar (AGENTS.md §6, §7).
|
||
//
|
||
// Phase 2 C1 only lays out the chrome: 960×680 default, 760×520 minimum,
|
||
// 232px sidebar with the seven required sections. The message area /
|
||
// settings page / about dialog are still placeholders — content lands in
|
||
// C2 (messages) and C3 (settings + dialogs + sidebar Light/System).
|
||
|
||
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";
|
||
|
||
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(...)`. Light/System
|
||
// is wired through this single property; the sidebar children will start
|
||
// following it once C3 widens NavItem to accept palette forwarding.
|
||
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-driven message list + selection state. Set from Rust
|
||
// via `ui.set_messages(model)` in `run()`. The list / empty-state switch
|
||
// is purely driven by `messages.length` (Slint's `for` over an empty
|
||
// model is a no-op).
|
||
in-out property <[MessageItem]> messages;
|
||
in-out property <int> selected-message-index: -1;
|
||
|
||
// Hooks that Phase 6 (Windows Integration) wires to a real Windows
|
||
// toast. C2 logs them so the click path is observable end-to-end.
|
||
callback test-notification-clicked();
|
||
callback message-card-clicked(int);
|
||
|
||
HorizontalLayout {
|
||
// §7 Sidebar width: 220-240px.
|
||
Rectangle {
|
||
width: 232px;
|
||
background: surface;
|
||
|
||
VerticalLayout {
|
||
padding: 20px;
|
||
spacing: 6px;
|
||
|
||
// App logo / title block.
|
||
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; }
|
||
}
|
||
}
|
||
}
|
||
|
||
// Right pane — placeholder content 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: Rectangle {
|
||
background: bg;
|
||
Text {
|
||
x: 24px;
|
||
y: 24px;
|
||
text: "Settings page arrives in Phase 2 C3.";
|
||
color: text-muted;
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
|
||
if root.selection == SidebarSelection.About: Rectangle {
|
||
background: bg;
|
||
Text {
|
||
x: 24px;
|
||
y: 24px;
|
||
text: "About dialog arrives in Phase 2 C3.";
|
||
color: text-muted;
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |