Files
Windows-Gotify-Client-Rust/ui/app.slint
T
KennethCheng a091e6839a fix(ui): Phase 7 C5 — 移除假按钮 + 真实托盘图标
用户报告设置页很多按钮无反应 + 关闭后托盘看不到图标。两处问题:\n\n1. ServerCard 占位按钮无回调:\n- server-card.slint:去掉了 toggled/edit-clicked/remove-clicked 三个回调以及 enabled 切换、Edit / Remove 按钮;变成纯展示卡(状态点 + name + url + 当前标记)\n- settings-page.slint:去掉硬编码的 Home / NAS 占位,改用 active-server-name / active-server-url / active-server-enabled 三个属性(MainWindow → AppConfig 实时填充);空时显示 '(未配置服务器)' 空态提示\n- app.slint:MainWindow 加三个属性 + 转发给 SettingsPage\n- lib.rs:startup 阶段把活跃服务器信息塞进 UI;测试连接成功/失败时更新 enabled 状态\n\n2. 托盘图标看不见:\n- tray.rs:原本 hIcon 写 null,Win10+ 上 Shell_NotifyIconW(NIM_ADD, NULL hIcon) 实际渲染为空白或很弱的小方块。改用 LoadIconW(NULL, IDI_APPLICATION) 加载 Windows 自带的应用程序图标(无需嵌入 .ico 资源,主题感知)。show_toast / Drop 的 hIcon 维持 null(让 NIM_MODIFY/NIM_DELETE 不动原有图标)。AGENTS.md §53 标记真 .ico 资源为 V2 项。
2026-09-19 20:40:33 +08:00

417 lines
17 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Main window + sidebar (AGENTS.md §6, §7).
//
// Phase 7 C4:
// - UI 字符串全部翻译为简体中文(AGENTS.md §55:默认中文)。
// - 优先级过滤从 Slint 侧 visible 谓词改为 Rust 侧重新构建
// filtered VecModel(参见 message-list.slint 的注释)。
// - 新增 `priority-filter-changed(int)` 回调,Sidebar 切换过滤
// 时通知 Rust 重建 filtered model。
import { AppearanceMode } from "./theme.slint";
import { NavItem, SectionLabel } from "./components.slint";
import { MessageItem, Priority } 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;
in-out property <AppearanceMode> theme: AppearanceMode.Dark;
out property <bool> is-dark: theme != AppearanceMode.Light;
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 7 C4 fix: AboutDialog overlay (see dialogs.slint header) ──
in-out property <bool> show-about: false;
callback close-about();
// ── 消息 + 选择 ──
in-out property <[MessageItem]> messages;
in-out property <int> selected-message-index: -1;
callback test-notification-clicked();
callback message-card-clicked(int);
// ── Phase 7 C4: 优先级过滤。sidebar 的高亮状态由此属性驱动,但
// 真正的列表过滤已搬到 Rust 侧(每次切换重建 filtered VecModel,
// 避免 Slint `visible: false` 在 for 循环里不折叠空间导致的布局
// bug)。0=全部, 1=低, 2=普通, 3=高, 4=最高 ──
in-out property <int> priority-filter: 0;
callback priority-filter-changed(int);
// ── 设置 / About / 窗口状态 ──
in-out property <int> appearance-index: 2;
callback about-clicked();
callback appearance-changed-persist(int);
in-out property <bool> desktop-notify: true;
in-out property <bool> sound: true;
in-out property <bool> notify-high: true;
in-out property <bool> notify-max: true;
in-out property <bool> start-with-windows: false;
in-out property <bool> start-minimized: false;
in-out property <bool> close-to-tray: true;
callback setting-toggled(string, bool);
in-out property <bool> is-connected: false;
in-out property <string> status-text: "未连接";
callback test-connection-requested();
// Phase 7 C5: active-server summary forwarded into SettingsPage
// so the Connection section reflects the real AppConfig instead
// of the old hardcoded "Home / NAS" placeholders.
in property <string> active-server-name: "(未配置服务器)";
in property <string> active-server-url: "";
in property <bool> active-server-enabled: false;
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 客户端 · Phase 7";
color: text-muted;
font-size: 10px;
}
Rectangle { height: 12px; }
// Live status row (Phase 3 C2): dot + text.
HorizontalLayout {
spacing: 6px;
alignment: center;
Rectangle {
width: 8px;
height: 8px;
border-radius: 4px;
background: is-connected ? success : text-muted;
}
Text {
text: is-connected ? "已连接" : "未连接";
color: is-connected ? success : text-muted;
font-size: 11px;
font-weight: 600;
}
}
Rectangle { height: 12px; }
SectionLabel { label: "消息"; text-color: text-muted; }
NavItem {
label: "全部";
selected: root.selection == SidebarSelection.All
&& root.priority-filter == 0;
clicked => {
root.selection = SidebarSelection.All;
root.priority-filter = 0;
root.priority-filter-changed(0);
}
}
Rectangle { height: 12px; }
SectionLabel { label: "服务器"; text-color: text-muted; }
Text {
text: "(未配置服务器)";
color: text-muted;
font-size: 11px;
}
Rectangle { height: 12px; }
SectionLabel { label: "过滤"; text-color: text-muted; }
NavItem {
label: "最高";
selected: root.selection == SidebarSelection.All
&& root.priority-filter == 4;
has-accent: true;
accent-color: priority-max;
clicked => {
root.selection = SidebarSelection.All;
root.priority-filter = 4;
root.priority-filter-changed(4);
}
}
NavItem {
label: "高";
selected: root.selection == SidebarSelection.All
&& root.priority-filter == 3;
has-accent: true;
accent-color: priority-high;
clicked => {
root.selection = SidebarSelection.All;
root.priority-filter = 3;
root.priority-filter-changed(3);
}
}
NavItem {
label: "普通";
selected: root.selection == SidebarSelection.All
&& root.priority-filter == 2;
has-accent: true;
accent-color: priority-normal;
clicked => {
root.selection = SidebarSelection.All;
root.priority-filter = 2;
root.priority-filter-changed(2);
}
}
NavItem {
label: "低";
selected: root.selection == SidebarSelection.All
&& root.priority-filter == 1;
has-accent: true;
accent-color: priority-low;
clicked => {
root.selection = SidebarSelection.All;
root.priority-filter = 1;
root.priority-filter-changed(1);
}
}
Rectangle { height: 12px; }
SectionLabel { label: "应用"; text-color: text-muted; }
NavItem {
label: "设置";
selected: root.selection == SidebarSelection.Settings;
clicked => { root.selection = SidebarSelection.Settings; }
}
NavItem {
label: "关于";
selected: root.selection == SidebarSelection.About;
clicked => {
root.selection = SidebarSelection.About;
root.about-clicked();
root.show-about = true;
}
}
}
}
// Right pane — driven by `selection`.
Rectangle {
background: bg;
HorizontalLayout {
padding: 0;
spacing: 0;
// 消息列(All 视图时可见)。
Rectangle {
background: bg;
horizontal-stretch: 1;
visible: root.selection == SidebarSelection.All;
VerticalLayout {
padding: 24px;
spacing: 4px;
Text {
text: messages.length == 0 ? "消息" : "消息 · " + messages.length;
color: text-primary;
font-size: 18px;
font-weight: 700;
}
Text {
text: status-text;
color: text-muted;
font-size: 11px;
}
Rectangle { height: 12px; }
if messages.length > 0: MessageList {
messages: messages;
selected-index: root.selected-message-index;
vertical-stretch: 1;
message-clicked(idx) => {
root.selected-message-index = idx;
root.message-card-clicked(idx);
}
}
if messages.length == 0: EmptyState {
vertical-stretch: 1;
test-notification => {
root.test-notification-clicked();
}
}
}
}
// 详情面板:选中消息且在 All 视图时显示。
if root.selection == SidebarSelection.All
&& root.selected-message-index >= 0
&& root.selected-message-index < root.messages.length: Rectangle {
width: 360px;
background: surface-elev;
border-color: border;
border-width: 1px;
VerticalLayout {
padding: 20px;
spacing: 8px;
HorizontalLayout {
spacing: 8px;
alignment: center;
Rectangle {
width: 10px;
height: 10px;
border-radius: 5px;
background: root.messages[root.selected-message-index].priority == Priority.Low ? priority-low :
root.messages[root.selected-message-index].priority == Priority.Normal ? priority-normal :
root.messages[root.selected-message-index].priority == Priority.High ? priority-high :
priority-max;
}
Text {
text: root.messages[root.selected-message-index].app_name;
color: text-second;
font-size: 12px;
font-weight: 600;
horizontal-stretch: 1;
}
Text {
text: root.messages[root.selected-message-index].timestamp_unix == 0
? ""
: "id #" + root.messages[root.selected-message-index].id;
color: text-muted;
font-size: 11px;
}
}
Text {
text: root.messages[root.selected-message-index].title != ""
? root.messages[root.selected-message-index].title
: root.messages[root.selected-message-index].body;
color: text-primary;
font-size: 16px;
font-weight: 700;
wrap: word-wrap;
}
Text {
text: root.messages[root.selected-message-index].relative_time;
color: text-muted;
font-size: 11px;
}
Rectangle {
height: 1px;
background: border;
}
Text {
text: root.messages[root.selected-message-index].body;
color: text-primary;
font-size: 13px;
wrap: word-wrap;
vertical-stretch: 1;
}
if root.messages[root.selected-message-index].has_click_url: Rectangle {
height: 32px;
background: accent;
border-radius: 6px;
TouchArea {
clicked => {
root.message-card-clicked(root.selected-message-index);
}
}
Text {
x: 0;
y: 0;
width: 100%;
height: 100%;
text: "打开链接 ↗";
color: white;
font-size: 12px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
}
}
if root.selection == SidebarSelection.Settings: SettingsPage {
appearance-index: root.appearance-index;
desktop-notify: root.desktop-notify;
sound: root.sound;
notify-high: root.notify-high;
notify-max: root.notify-max;
start-with-windows: root.start-with-windows;
start-minimized: root.start-minimized;
close-to-tray: root.close-to-tray;
// Phase 7 C5: forward active server config so the
// Connection section shows the real entry instead
// of the hardcoded "Home / NAS" placeholders.
active-server-name: root.active-server-name;
active-server-url: root.active-server-url;
active-server-enabled: root.active-server-enabled;
appearance-changed(idx) => {
root.appearance-index = idx;
root.theme = idx == 0 ? AppearanceMode.System :
idx == 1 ? AppearanceMode.Light :
AppearanceMode.Dark;
root.appearance-changed-persist(idx);
}
test-connection-requested => {
root.test-connection-requested();
}
toggle-persist(label, value) => {
root.setting-toggled(label, value);
}
}
}
}
if root.show-about: AboutDialog {
close-dialog => {
root.show-about = false;
}
}
}