mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-24 21:57:08 +08:00
[ui] unify settings dialogs and improve announcement controls
This commit is contained in:
@@ -1493,6 +1493,10 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
|
|||||||
SyncAnnouncements();
|
SyncAnnouncements();
|
||||||
});
|
});
|
||||||
main->on_refresh_announcements([this] { announcements_.Refresh(); });
|
main->on_refresh_announcements([this] { announcements_.Refresh(); });
|
||||||
|
main->on_read_all_announcements([this] {
|
||||||
|
announcements_.ReadAll();
|
||||||
|
SyncAnnouncements();
|
||||||
|
});
|
||||||
main->on_open_announcement_link([this](slint::SharedString link) {
|
main->on_open_announcement_link([this](slint::SharedString link) {
|
||||||
const std::string url(link);
|
const std::string url(link);
|
||||||
if (announcement_text::IsWebUrl(url)) OpenUrl(url);
|
if (announcement_text::IsWebUrl(url)) OpenUrl(url);
|
||||||
@@ -2637,6 +2641,7 @@ void GuiApplication::SyncAnnouncements() {
|
|||||||
revision == ui_->announcement_selected_revision) {
|
revision == ui_->announcement_selected_revision) {
|
||||||
selected_found = true;
|
selected_found = true;
|
||||||
main->set_announcement_selected_id(row.id);
|
main->set_announcement_selected_id(row.id);
|
||||||
|
main->set_announcement_selected_revision(row.revision);
|
||||||
main->set_announcement_selected_title(row.title);
|
main->set_announcement_selected_title(row.title);
|
||||||
main->set_announcement_selected_body(
|
main->set_announcement_selected_body(
|
||||||
announcement_text::Format(item["body"].get<std::string>()));
|
announcement_text::Format(item["body"].get<std::string>()));
|
||||||
@@ -2648,6 +2653,7 @@ void GuiApplication::SyncAnnouncements() {
|
|||||||
if (!selected_found) {
|
if (!selected_found) {
|
||||||
ui_->announcement_selected_id.clear();
|
ui_->announcement_selected_id.clear();
|
||||||
main->set_announcement_selected_id("");
|
main->set_announcement_selected_id("");
|
||||||
|
main->set_announcement_selected_revision("");
|
||||||
main->set_announcement_selected_body(slint::StyledText{});
|
main->set_announcement_selected_body(slint::StyledText{});
|
||||||
}
|
}
|
||||||
main->set_announcements(
|
main->set_announcements(
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regenerate message_spacing.ttf with fontTools (only needed for regeneration).
|
||||||
|
|
||||||
|
Slint 1.17 pins line height to the requested font's ascent, descent and leading,
|
||||||
|
but StyledText does not expose line-height. This metrics-only font supplies 1.5em
|
||||||
|
line boxes without replacing any visible glyphs: Slint's normal system fallback
|
||||||
|
fonts still render the text and links. Use this family for message and release-note
|
||||||
|
bodies without changing the typography of other controls.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fontTools.fontBuilder import FontBuilder
|
||||||
|
from fontTools.pens.ttGlyphPen import TTGlyphPen
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
font = FontBuilder(1000, isTTF=True)
|
||||||
|
glyphs = [".notdef", "zeroWidthSpace"]
|
||||||
|
font.setupGlyphOrder(glyphs)
|
||||||
|
font.setupCharacterMap({0x200B: "zeroWidthSpace"})
|
||||||
|
font.setupGlyf({name: TTGlyphPen(None).glyph() for name in glyphs})
|
||||||
|
font.setupHorizontalMetrics({name: (0, 0) for name in glyphs})
|
||||||
|
font.setupHorizontalHeader(ascent=1000, descent=-250, lineGap=250)
|
||||||
|
font.setupOS2(
|
||||||
|
version=4,
|
||||||
|
sTypoAscender=1000,
|
||||||
|
sTypoDescender=-250,
|
||||||
|
sTypoLineGap=250,
|
||||||
|
usWinAscent=1000,
|
||||||
|
usWinDescent=250,
|
||||||
|
fsSelection=0xC0, # Regular, use typographic metrics.
|
||||||
|
)
|
||||||
|
font.setupNameTable({
|
||||||
|
"familyName": "CrossDesk Message Spacing",
|
||||||
|
"styleName": "Regular",
|
||||||
|
"uniqueFontIdentifier": "CrossDesk-Message-Spacing-1.0",
|
||||||
|
"fullName": "CrossDesk Message Spacing",
|
||||||
|
"psName": "CrossDeskMessageSpacing-Regular",
|
||||||
|
"version": "Version 1.0",
|
||||||
|
"copyright": "Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.",
|
||||||
|
})
|
||||||
|
font.setupPost()
|
||||||
|
# Fixed timestamps keep the generated asset reproducible.
|
||||||
|
font.font["head"].created = font.font["head"].modified = 3874867200
|
||||||
|
font.font.recalcTimestamp = False
|
||||||
|
font.save(Path(__file__).with_name("message_spacing.ttf"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
@@ -23,10 +23,11 @@ struct TranslationRow {
|
|||||||
X(messages, u8"消息", "Messages", u8"Сообщения") \
|
X(messages, u8"消息", "Messages", u8"Сообщения") \
|
||||||
X(message_unread, u8"未读", "Unread", u8"Не прочитано") \
|
X(message_unread, u8"未读", "Unread", u8"Не прочитано") \
|
||||||
X(message_read, u8"已读", "Read", u8"Прочитано") \
|
X(message_read, u8"已读", "Read", u8"Прочитано") \
|
||||||
|
X(message_read_all, u8"全部已读", "Mark all as read", u8"Отметить всё прочитанным") \
|
||||||
X(message_empty, u8"暂无公告", "No announcements", u8"Нет объявлений") \
|
X(message_empty, u8"暂无公告", "No announcements", u8"Нет объявлений") \
|
||||||
X(message_select, u8"选择公告查看详情", "Select an announcement to read", u8"Выберите объявление") \
|
X(message_select, u8"选择公告查看详情", "Select an announcement to read", u8"Выберите объявление") \
|
||||||
X(message_loading, u8"正在加载…", "Loading…", u8"Загрузка…") \
|
X(message_loading, u8"正在加载…", "Loading…", u8"Загрузка…") \
|
||||||
X(message_read_save_error, u8"无法保存本地阅读状态,请重新打开公告重试。", "Unable to save reading status locally. Open the announcement again to retry.", u8"Не удалось сохранить статус чтения. Откройте объявление снова.") \
|
X(message_read_save_error, u8"无法保存本地阅读状态,请重试。", "Unable to save reading status locally. Please try again.", u8"Не удалось сохранить статус чтения. Повторите попытку.") \
|
||||||
X(message_close, u8"关闭", "Close", u8"Закрыть") \
|
X(message_close, u8"关闭", "Close", u8"Закрыть") \
|
||||||
X(message_delete, u8"删除", "Delete", u8"Удалить") \
|
X(message_delete, u8"删除", "Delete", u8"Удалить") \
|
||||||
X(message_delete_title, u8"删除公告", "Delete announcement", u8"Удалить объявление") \
|
X(message_delete_title, u8"删除公告", "Delete announcement", u8"Удалить объявление") \
|
||||||
|
|||||||
@@ -154,6 +154,29 @@ bool AnnouncementInbox::Read(int64_t id, int64_t revision) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
bool AnnouncementInbox::ReadAll() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
if (catalog_revision_ < 0) return false;
|
||||||
|
auto updated = reads_;
|
||||||
|
// Use the complete catalog, including announcements whose bodies have not
|
||||||
|
// been paged in. Persist once before changing any visible reading state.
|
||||||
|
for (const auto& [id, revision] : catalog_) {
|
||||||
|
if (!IsDismissed(id, revision)) updated[id] = revision;
|
||||||
|
}
|
||||||
|
if (updated == reads_) return true;
|
||||||
|
if (!SaveLocalState(updated, dismissed_)) {
|
||||||
|
state_.read_save_failed = true;
|
||||||
|
++state_.version;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reads_ = std::move(updated);
|
||||||
|
state_.read_save_failed = false;
|
||||||
|
state_.delete_failed = false;
|
||||||
|
UpdateReadingState();
|
||||||
|
++state_.version;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool AnnouncementInbox::IsDismissed(int64_t id, int64_t revision) const {
|
bool AnnouncementInbox::IsDismissed(int64_t id, int64_t revision) const {
|
||||||
const auto found = dismissed_.find(id);
|
const auto found = dismissed_.find(id);
|
||||||
return found != dismissed_.end() && found->second == revision;
|
return found != dismissed_.end() && found->second == revision;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class AnnouncementInbox {
|
|||||||
void Refresh();
|
void Refresh();
|
||||||
void LoadMore();
|
void LoadMore();
|
||||||
bool Read(int64_t id, int64_t revision);
|
bool Read(int64_t id, int64_t revision);
|
||||||
|
bool ReadAll();
|
||||||
bool Dismiss(int64_t id, int64_t revision);
|
bool Dismiss(int64_t id, int64_t revision);
|
||||||
nlohmann::json TakePendingRequest(std::chrono::steady_clock::time_point now =
|
nlohmann::json TakePendingRequest(std::chrono::steady_clock::time_point now =
|
||||||
std::chrono::steady_clock::now());
|
std::chrono::steady_clock::now());
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import { DeviceCardActions, DeviceCardStyle } from "device_card_actions.slint";
|
|||||||
import { DeviceGroupDialog, DeviceGroupOption } from "device_group_dialog.slint";
|
import { DeviceGroupDialog, DeviceGroupOption } from "device_group_dialog.slint";
|
||||||
import { GalleryView, GalleryTile, GalleryRecentDevice, GalleryActionDevice } from "gallery_view.slint";
|
import { GalleryView, GalleryTile, GalleryRecentDevice, GalleryActionDevice } from "gallery_view.slint";
|
||||||
import { GalleryNetworkDialog, GalleryNetworkDevice } from "gallery_network_dialog.slint";
|
import { GalleryNetworkDialog, GalleryNetworkDevice } from "gallery_network_dialog.slint";
|
||||||
import { SettingsButton, SettingsSelect, SettingsGroup, SettingsRow, SettingsToggleRow } from "settings_controls.slint";
|
import { SettingsDialog, SettingsButton, SettingsSelect, SettingsGroup, SettingsRow, SettingsToggleRow } from "settings_controls.slint";
|
||||||
import { GallerySettings } from "gallery_settings.slint";
|
import { GallerySettings } from "gallery_settings.slint";
|
||||||
import { GroupFilter } from "group_filter.slint";
|
import { GroupFilter } from "group_filter.slint";
|
||||||
import { ScrollView } from "std-widgets.slint";
|
import { ScrollView } from "std-widgets.slint";
|
||||||
import { RoundedLineEdit } from "rounded_line_edit.slint";
|
import { RoundedLineEdit } from "rounded_line_edit.slint";
|
||||||
import { AnnouncementItem, MessagePanel, MessageStrings } from "message_panel.slint";
|
import { AnnouncementItem, MessagePanel, MessageStrings } from "message_panel.slint";
|
||||||
import { FontAwesomeIcons, IconButton, CompactComboBox, ImGuiCheckBox, MiniToggleSwitch, CompactButton, PrimaryButton, SecondaryButton, ToggleSwitch, SectionTitle, DialogSurface, ImGuiLineStyle, ImGuiFontStyle } from "common.slint";
|
import { FontAwesomeIcons, IconButton, CompactComboBox, ImGuiCheckBox, MiniToggleSwitch, CompactButton, PrimaryButton, SecondaryButton, ToggleSwitch, SectionTitle, DialogSurface, ImGuiLineStyle, ImGuiFontStyle } from "common.slint";
|
||||||
|
import "../assets/fonts/message_spacing.ttf";
|
||||||
|
|
||||||
export struct RecentConnection {
|
export struct RecentConnection {
|
||||||
remote-id: string,
|
remote-id: string,
|
||||||
@@ -344,6 +345,7 @@ export component MainWindow inherits Window {
|
|||||||
in property <bool> announcement-loaded;
|
in property <bool> announcement-loaded;
|
||||||
in-out property <bool> messages-open: false;
|
in-out property <bool> messages-open: false;
|
||||||
in property <string> announcement-selected-id;
|
in property <string> announcement-selected-id;
|
||||||
|
in property <string> announcement-selected-revision;
|
||||||
in property <string> announcement-selected-title;
|
in property <string> announcement-selected-title;
|
||||||
in property <styled-text> announcement-selected-body;
|
in property <styled-text> announcement-selected-body;
|
||||||
in property <string> announcement-selected-date;
|
in property <string> announcement-selected-date;
|
||||||
@@ -352,6 +354,7 @@ export component MainWindow inherits Window {
|
|||||||
callback open-announcement-link(string);
|
callback open-announcement-link(string);
|
||||||
callback delete-announcement(string, string);
|
callback delete-announcement(string, string);
|
||||||
callback refresh-announcements;
|
callback refresh-announcements;
|
||||||
|
callback read-all-announcements;
|
||||||
callback load-more-announcements;
|
callback load-more-announcements;
|
||||||
in property <bool> update-available: false;
|
in property <bool> update-available: false;
|
||||||
in property <UpdateCheckStatus> update-check-status: idle;
|
in property <UpdateCheckStatus> update-check-status: idle;
|
||||||
@@ -500,6 +503,10 @@ export component MainWindow inherits Window {
|
|||||||
if root.messages-open { focus-sink.focus(); }
|
if root.messages-open { focus-sink.focus(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changed about-open => {
|
||||||
|
if root.about-open { focus-sink.focus(); }
|
||||||
|
}
|
||||||
|
|
||||||
reset-remote-id => {
|
reset-remote-id => {
|
||||||
root.remote-id-input = "";
|
root.remote-id-input = "";
|
||||||
remote-id-editor.focus();
|
remote-id-editor.focus();
|
||||||
@@ -713,6 +720,10 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
return accept;
|
return accept;
|
||||||
}
|
}
|
||||||
|
if root.about-open && !root.update-open {
|
||||||
|
if event.text == Key.Escape { root.about-open = false; }
|
||||||
|
return accept;
|
||||||
|
}
|
||||||
if root.messages-open {
|
if root.messages-open {
|
||||||
if event.text == Key.Escape { root.messages-open = false; }
|
if event.text == Key.Escape { root.messages-open = false; }
|
||||||
return accept;
|
return accept;
|
||||||
@@ -1477,14 +1488,14 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if root.messages-open: Rectangle {
|
if root.messages-open: Rectangle {
|
||||||
background: #00000042;
|
background: #15223850;
|
||||||
z: 40;
|
z: 40;
|
||||||
TouchArea { clicked => { focus-sink.focus(); } }
|
TouchArea { clicked => { focus-sink.focus(); } }
|
||||||
MessagePanel {
|
MessagePanel {
|
||||||
x: (parent.width - self.width) / 2;
|
x: (parent.width - self.width) / 2;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
width: 520px;
|
width: min(592px, parent.width - 32px);
|
||||||
height: 330px;
|
height: min(400px, parent.height - 24px);
|
||||||
items: root.announcements;
|
items: root.announcements;
|
||||||
unread-count: root.announcement-unread; total: root.announcement-total;
|
unread-count: root.announcement-unread; total: root.announcement-total;
|
||||||
loading: root.announcement-loading; failed: root.announcement-failed; loaded: root.announcement-loaded;
|
loading: root.announcement-loading; failed: root.announcement-failed; loaded: root.announcement-loaded;
|
||||||
@@ -1492,12 +1503,14 @@ export component MainWindow inherits Window {
|
|||||||
delete-failed: root.announcement-delete-failed;
|
delete-failed: root.announcement-delete-failed;
|
||||||
connected: root.signal-connected;
|
connected: root.signal-connected;
|
||||||
selected-id: root.announcement-selected-id; selected-title: root.announcement-selected-title;
|
selected-id: root.announcement-selected-id; selected-title: root.announcement-selected-title;
|
||||||
|
selected-revision: root.announcement-selected-revision;
|
||||||
selected-body: root.announcement-selected-body; selected-date: root.announcement-selected-date;
|
selected-body: root.announcement-selected-body; selected-date: root.announcement-selected-date;
|
||||||
selected-read: root.announcement-selected-read;
|
selected-read: root.announcement-selected-read;
|
||||||
open-message(id, revision) => { root.open-announcement(id, revision); }
|
open-message(id, revision) => { root.open-announcement(id, revision); }
|
||||||
open-link(url) => { root.open-announcement-link(url); }
|
open-link(url) => { root.open-announcement-link(url); }
|
||||||
delete-message(id, revision) => { root.delete-announcement(id, revision); }
|
delete-message(id, revision) => { root.delete-announcement(id, revision); }
|
||||||
load-more => { root.load-more-announcements(); }
|
load-more => { root.load-more-announcements(); }
|
||||||
|
read-all => { root.read-all-announcements(); }
|
||||||
close => { root.messages-open = false; }
|
close => { root.messages-open = false; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1941,113 +1954,107 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if root.about-open: Rectangle {
|
if root.about-open: Rectangle {
|
||||||
background: transparent;
|
background: #15223850;
|
||||||
z: 46;
|
z: 46;
|
||||||
TouchArea { clicked => { focus-sink.focus(); } }
|
TouchArea { clicked => { focus-sink.focus(); } }
|
||||||
DialogSurface {
|
SettingsDialog {
|
||||||
x: (parent.width - self.width) / 2;
|
x: (parent.width - self.width) / 2;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
width: 320px;
|
width: min(400px, parent.width - 40px);
|
||||||
height: max(216px, about-content.preferred-height);
|
height: self.content-top + about-content.preferred-height + 24px;
|
||||||
border-radius: UiRadius.surface;
|
title: UiStrings.about;
|
||||||
|
close-label: MessageStrings.close;
|
||||||
|
close => { root.about-open = false; }
|
||||||
|
|
||||||
Rectangle {
|
// Content-driven height keeps translated update states and actions
|
||||||
x: 0px;
|
// above the footer without reserving an empty update section.
|
||||||
y: 0px;
|
|
||||||
width: parent.width;
|
|
||||||
height: 21px;
|
|
||||||
background: #d3d3d3;
|
|
||||||
border-top-left-radius: UiRadius.surface;
|
|
||||||
border-top-right-radius: UiRadius.surface;
|
|
||||||
z: 5;
|
|
||||||
Text { x: 6px; text: UiStrings.about; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
|
|
||||||
}
|
|
||||||
// Grow for update actions and wrapped translations while keeping
|
|
||||||
// the footer below the version and check result.
|
|
||||||
about-content := VerticalLayout {
|
about-content := VerticalLayout {
|
||||||
padding: 16px;
|
x: 24px; y: parent.content-top; width: parent.width - 48px;
|
||||||
padding-top: 35px;
|
height: self.preferred-height;
|
||||||
spacing: 12px;
|
padding: 0px; spacing: 20px;
|
||||||
alignment: space-between;
|
Rectangle {
|
||||||
|
height: max(96px, product-info.preferred-height + 32px);
|
||||||
VerticalLayout {
|
background: WorkbenchStyle.surface;
|
||||||
vertical-stretch: 0;
|
border-radius: UiRadius.surface;
|
||||||
spacing: 4px;
|
border-width: 1px; border-color: WorkbenchStyle.border;
|
||||||
Text {
|
Image {
|
||||||
text: "CrossDesk " + root.current-version;
|
x: 16px; y: (parent.height - self.height) / 2;
|
||||||
color: #202124;
|
width: 56px; height: 56px;
|
||||||
font-size: ImGuiFontStyle.base;
|
source: @image-url("../../../resources/linux/crossdesk_128x128.png");
|
||||||
font-weight: 600;
|
image-fit: contain;
|
||||||
min-height: 18px;
|
|
||||||
overflow: elide;
|
|
||||||
}
|
}
|
||||||
if root.update-available: Rectangle {
|
product-info := VerticalLayout {
|
||||||
height: max(18px, version-link.preferred-height);
|
x: 88px; y: 16px; width: parent.width - 104px; height: parent.height - 32px;
|
||||||
accessible-role: button;
|
alignment: center; spacing: 5px;
|
||||||
accessible-label: UiStrings.release-notes + " " + root.latest-version;
|
Text { text: "CrossDesk"; font-size: 22px; font-weight: 600; color: WorkbenchStyle.text; }
|
||||||
accessible-action-default => { version-touch.clicked(); }
|
Text {
|
||||||
version-link := Text {
|
text: UiStrings.version + " " + root.current-version;
|
||||||
x: 0px;
|
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
|
||||||
y: 0px;
|
|
||||||
width: parent.width;
|
|
||||||
height: parent.height;
|
|
||||||
text: UiStrings.update-available + ": " + root.latest-version;
|
|
||||||
color: version-touch.has-hover ? #174ea6 : #334fd1;
|
|
||||||
font-size: ImGuiFontStyle.body;
|
|
||||||
vertical-alignment: center;
|
|
||||||
horizontal-alignment: left;
|
|
||||||
wrap: word-wrap;
|
|
||||||
}
|
|
||||||
version-touch := TouchArea {
|
|
||||||
mouse-cursor: pointer;
|
|
||||||
clicked => { root.about-open = false; root.update-open = true; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if root.update-available: HorizontalLayout {
|
}
|
||||||
alignment: start;
|
if root.update-available || root.update-status-text != "": Rectangle {
|
||||||
padding-top: 4px;
|
height: update-summary.preferred-height + 24px;
|
||||||
CompactButton {
|
background: root.update-check-status == UpdateCheckStatus.failed ? #fef2f2 : WorkbenchStyle.field-surface;
|
||||||
width: max(76px, release-notes-button-metrics.preferred-width + 20px);
|
border-radius: UiRadius.control;
|
||||||
text: UiStrings.view-release-notes;
|
border-width: 1px;
|
||||||
|
border-color: root.update-check-status == UpdateCheckStatus.failed ? #f3d4d1 : WorkbenchStyle.separator;
|
||||||
|
update-summary := VerticalLayout {
|
||||||
|
x: 12px; y: 12px; width: parent.width - 24px;
|
||||||
|
height: self.preferred-height;
|
||||||
|
padding: 0px; spacing: 10px;
|
||||||
|
if root.update-available: Rectangle {
|
||||||
|
height: version-link.preferred-height;
|
||||||
accessible-role: button;
|
accessible-role: button;
|
||||||
accessible-label: self.text;
|
accessible-label: UiStrings.release-notes + " " + root.latest-version;
|
||||||
accessible-action-default => { self.clicked(); }
|
accessible-action-default => { version-touch.clicked(); }
|
||||||
clicked => { root.about-open = false; root.update-open = true; }
|
version-link := Text {
|
||||||
release-notes-button-metrics := Text { visible: false; text: UiStrings.view-release-notes; font-size: ImGuiFontStyle.body; }
|
width: parent.width;
|
||||||
|
text: UiStrings.update-available + ": " + root.latest-version;
|
||||||
|
color: version-touch.has-hover ? #254cc0 : WorkbenchStyle.accent;
|
||||||
|
font-size: 13px; font-weight: 500; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
version-touch := TouchArea {
|
||||||
|
mouse-cursor: pointer;
|
||||||
|
clicked => { root.about-open = false; root.update-open = true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if root.update-status-text != "": Text {
|
||||||
|
text: root.update-status-text;
|
||||||
|
color: root.update-check-status == UpdateCheckStatus.failed ? #b3261e
|
||||||
|
: root.update-check-status == UpdateCheckStatus.up-to-date ? WorkbenchStyle.success : WorkbenchStyle.muted;
|
||||||
|
font-size: 12px; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
if root.update-available: HorizontalLayout {
|
||||||
|
alignment: start;
|
||||||
|
SettingsButton {
|
||||||
|
width: max(116px, release-notes-button-metrics.preferred-width + 24px);
|
||||||
|
text: UiStrings.view-release-notes;
|
||||||
|
clicked => { root.about-open = false; root.update-open = true; }
|
||||||
|
release-notes-button-metrics := Text { visible: false; text: UiStrings.view-release-notes; font-size: 12px; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if root.update-status-text != "": Text {
|
|
||||||
text: root.update-status-text;
|
|
||||||
color: root.update-check-status == UpdateCheckStatus.failed ? #b3261e : #61656c;
|
|
||||||
font-size: ImGuiFontStyle.body;
|
|
||||||
min-height: 18px;
|
|
||||||
wrap: word-wrap;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
vertical-stretch: 0;
|
spacing: 5px;
|
||||||
spacing: 3px;
|
Text { text: UiStrings.copyright; color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap; }
|
||||||
Rectangle { height: 1px; background: #e6e8eb; }
|
Text { text: UiStrings.license; color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap; }
|
||||||
Text { height: 16px; text: UiStrings.copyright; color: #61656c; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
|
||||||
Text { height: 16px; text: UiStrings.license; color: #61656c; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
|
|
||||||
}
|
}
|
||||||
|
VerticalLayout {
|
||||||
HorizontalLayout {
|
spacing: 16px;
|
||||||
vertical-stretch: 0;
|
Rectangle { height: 1px; background: WorkbenchStyle.separator; }
|
||||||
alignment: end;
|
HorizontalLayout {
|
||||||
spacing: 8px;
|
alignment: end; spacing: 8px;
|
||||||
CompactButton {
|
SettingsButton {
|
||||||
width: max(76px, check-button-metrics.preferred-width + 20px);
|
width: max(116px, check-button-metrics.preferred-width + 24px);
|
||||||
text: UiStrings.check-for-updates;
|
text: UiStrings.check-for-updates;
|
||||||
enabled: root.update-check-status != UpdateCheckStatus.checking;
|
enabled: root.update-check-status != UpdateCheckStatus.checking;
|
||||||
accessible-role: button;
|
clicked => { root.check-for-updates(); }
|
||||||
accessible-label: self.text;
|
check-button-metrics := Text { visible: false; text: UiStrings.check-for-updates; font-size: 12px; }
|
||||||
accessible-action-default => { if self.enabled { root.check-for-updates(); } }
|
}
|
||||||
clicked => { root.check-for-updates(); }
|
SettingsButton { width: 80px; primary: true; text: UiStrings.ok; clicked => { root.about-open = false; } }
|
||||||
check-button-metrics := Text { visible: false; text: UiStrings.check-for-updates; font-size: ImGuiFontStyle.body; }
|
|
||||||
}
|
}
|
||||||
CompactButton { width: 48px; primary: true; text: UiStrings.ok; clicked => { root.about-open = false; } }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2151,6 +2158,8 @@ export component MainWindow inherits Window {
|
|||||||
text: block.content;
|
text: block.content;
|
||||||
default-color: WorkbenchStyle.text;
|
default-color: WorkbenchStyle.text;
|
||||||
default-font-size: 13px;
|
default-font-size: 13px;
|
||||||
|
// Match the 1.5em line height of message bodies.
|
||||||
|
default-font-family: "CrossDesk Message Spacing";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { UiRadius } from "ui_radius.slint";
|
import { UiRadius } from "ui_radius.slint";
|
||||||
import { ScrollView } from "std-widgets.slint";
|
import { ScrollView } from "std-widgets.slint";
|
||||||
import { CompactButton, DialogSurface, IconButton, FontAwesomeIcons, ImGuiFontStyle } from "common.slint";
|
import { IconButton, FontAwesomeIcons } from "common.slint";
|
||||||
|
import { SettingsDialog, SettingsButton } from "settings_controls.slint";
|
||||||
|
import { WorkbenchStyle } from "workbench_controls.slint";
|
||||||
|
import "../assets/fonts/message_spacing.ttf";
|
||||||
|
|
||||||
export struct AnnouncementItem {
|
export struct AnnouncementItem {
|
||||||
id: string,
|
id: string,
|
||||||
@@ -14,10 +17,11 @@ export global MessageStrings {
|
|||||||
in-out property <string> messages: "Messages";
|
in-out property <string> messages: "Messages";
|
||||||
in-out property <string> unread: "Unread";
|
in-out property <string> unread: "Unread";
|
||||||
in-out property <string> read: "Read";
|
in-out property <string> read: "Read";
|
||||||
|
in-out property <string> read-all: "Mark all as read";
|
||||||
in-out property <string> empty: "No announcements";
|
in-out property <string> empty: "No announcements";
|
||||||
in-out property <string> select-message: "Select an announcement to read";
|
in-out property <string> select-message: "Select an announcement to read";
|
||||||
in-out property <string> loading: "Loading…";
|
in-out property <string> loading: "Loading…";
|
||||||
in-out property <string> read-save-error: "Unable to save reading status locally. Open the announcement again to retry.";
|
in-out property <string> read-save-error: "Unable to save reading status locally. Please try again.";
|
||||||
in-out property <string> close: "Close";
|
in-out property <string> close: "Close";
|
||||||
in-out property <string> delete-message: "Delete";
|
in-out property <string> delete-message: "Delete";
|
||||||
in-out property <string> delete-title: "Delete announcement";
|
in-out property <string> delete-title: "Delete announcement";
|
||||||
@@ -26,7 +30,7 @@ export global MessageStrings {
|
|||||||
in-out property <string> cancel: "Cancel";
|
in-out property <string> cancel: "Cancel";
|
||||||
}
|
}
|
||||||
|
|
||||||
export component MessagePanel inherits DialogSurface {
|
export component MessagePanel inherits SettingsDialog {
|
||||||
in property <[AnnouncementItem]> items;
|
in property <[AnnouncementItem]> items;
|
||||||
in property <int> unread-count;
|
in property <int> unread-count;
|
||||||
in property <int> total;
|
in property <int> total;
|
||||||
@@ -37,6 +41,7 @@ export component MessagePanel inherits DialogSurface {
|
|||||||
in property <bool> loaded;
|
in property <bool> loaded;
|
||||||
in property <bool> connected;
|
in property <bool> connected;
|
||||||
in property <string> selected-id;
|
in property <string> selected-id;
|
||||||
|
in property <string> selected-revision;
|
||||||
in property <string> selected-title;
|
in property <string> selected-title;
|
||||||
in property <styled-text> selected-body;
|
in property <styled-text> selected-body;
|
||||||
in property <string> selected-date;
|
in property <string> selected-date;
|
||||||
@@ -45,11 +50,9 @@ export component MessagePanel inherits DialogSurface {
|
|||||||
callback open-link(string);
|
callback open-link(string);
|
||||||
callback delete-message(string, string);
|
callback delete-message(string, string);
|
||||||
callback load-more;
|
callback load-more;
|
||||||
callback close;
|
callback read-all;
|
||||||
private property <string> notice-text: root.delete-failed ? MessageStrings.delete-error
|
private property <string> notice-text: root.delete-failed ? MessageStrings.delete-error
|
||||||
: root.read-save-failed ? MessageStrings.read-save-error : "";
|
: root.read-save-failed ? MessageStrings.read-save-error : "";
|
||||||
private property <length> content-top: title-bar.height + (root.notice-text == "" ? 14px : 50px);
|
|
||||||
private property <color> separator-color: #e6e8eb;
|
|
||||||
private property <string> pending-delete-id;
|
private property <string> pending-delete-id;
|
||||||
private property <string> pending-delete-revision;
|
private property <string> pending-delete-revision;
|
||||||
function request-delete(id: string, revision: string) {
|
function request-delete(id: string, revision: string) {
|
||||||
@@ -68,136 +71,151 @@ export component MessagePanel inherits DialogSurface {
|
|||||||
return reject;
|
return reject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
background: white;
|
title: MessageStrings.messages;
|
||||||
border-radius: UiRadius.surface;
|
close-label: MessageStrings.close;
|
||||||
TouchArea { }
|
badge-count: root.unread-count;
|
||||||
title-bar := Rectangle {
|
badge-label: MessageStrings.unread + " " + root.unread-count;
|
||||||
x: 0px; y: 0px; width: parent.width; height: 21px;
|
header-status: root.loading ? MessageStrings.loading : "";
|
||||||
background: #d3d3d3; border-top-left-radius: UiRadius.surface; border-top-right-radius: UiRadius.surface;
|
action-text: MessageStrings.read-all;
|
||||||
message-heading := Text {
|
action-enabled: root.loaded && !root.loading && root.unread-count > 0;
|
||||||
x: 6px; width: parent.width - 100px; height: parent.height;
|
action => { root.read-all(); }
|
||||||
text: MessageStrings.messages;
|
notice := Rectangle {
|
||||||
font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124;
|
x: 24px; y: root.content-top; width: parent.width - 48px;
|
||||||
vertical-alignment: center; overflow: elide;
|
height: root.notice-text == "" ? 0px : notice-label.preferred-height + 20px;
|
||||||
}
|
visible: root.notice-text != "";
|
||||||
if root.unread-count > 0: Rectangle {
|
border-radius: UiRadius.control; background: #fef2f2;
|
||||||
x: message-heading.x + message-heading.preferred-width + 8px;
|
notice-label := Text {
|
||||||
y: (parent.height - self.height) / 2; height: 16px;
|
x: 10px; y: 10px; width: parent.width - 20px;
|
||||||
width: max(16px, unread-count-label.preferred-width + 8px);
|
height: self.preferred-height;
|
||||||
border-radius: 8px; background: #eaf2ff;
|
text: root.notice-text; font-size: 12px; wrap: word-wrap; color: #b3261e;
|
||||||
accessible-role: text;
|
|
||||||
accessible-label: MessageStrings.unread + " " + root.unread-count;
|
|
||||||
unread-count-label := Text {
|
|
||||||
text: "" + root.unread-count; color: #2563eb;
|
|
||||||
font-size: ImGuiFontStyle.small; font-weight: 600;
|
|
||||||
horizontal-alignment: center; vertical-alignment: center;
|
|
||||||
accessible-role: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if root.loading: Text {
|
|
||||||
x: parent.width - 150px; width: 110px; height: parent.height;
|
|
||||||
text: MessageStrings.loading; color: #61656c; font-size: ImGuiFontStyle.small;
|
|
||||||
horizontal-alignment: right; vertical-alignment: center;
|
|
||||||
}
|
|
||||||
IconButton {
|
|
||||||
x: parent.width - 25px; y: 2px; width: 19px; height: parent.height - 4px;
|
|
||||||
border-radius: UiRadius.control;
|
|
||||||
icon: FontAwesomeIcons.xmark; icon-size: ImGuiFontStyle.base;
|
|
||||||
icon-color: #202124;
|
|
||||||
hover-background: #bfc2c7; pressed-background: #b2b2b2;
|
|
||||||
accessible-role: button;
|
|
||||||
accessible-label: MessageStrings.close;
|
|
||||||
accessible-action-default => { root.close(); }
|
|
||||||
clicked => { root.close(); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if root.notice-text != "": Rectangle {
|
message-content := Rectangle {
|
||||||
x: 16px; y: title-bar.height + 10px; width: parent.width - 32px; height: 28px;
|
x: 24px; y: root.content-top + (root.notice-text == "" ? 0px : notice.height + 12px);
|
||||||
border-radius: UiRadius.surface;
|
width: parent.width - 48px; height: parent.height - self.y - 24px;
|
||||||
background: #fef2f2;
|
background: WorkbenchStyle.surface;
|
||||||
Text {
|
border-radius: UiRadius.surface; border-width: 1px; border-color: WorkbenchStyle.border;
|
||||||
x: 8px; width: parent.width - 16px; height: parent.height;
|
if root.items.length > 0 || root.selected-id != "": Rectangle {
|
||||||
text: root.notice-text; font-size: ImGuiFontStyle.body; overflow: elide;
|
x: 1px; y: 1px; width: content-divider.x - 1px; height: parent.height - 2px;
|
||||||
color: #b3261e;
|
background: WorkbenchStyle.field-surface;
|
||||||
vertical-alignment: center;
|
border-top-left-radius: UiRadius.surface - 1px;
|
||||||
|
border-bottom-left-radius: UiRadius.surface - 1px;
|
||||||
}
|
}
|
||||||
}
|
announcement-list := Rectangle {
|
||||||
announcement-list := Rectangle {
|
x: 8px; y: 8px; width: 176px; height: parent.height - 16px;
|
||||||
x: 16px; y: root.content-top; width: 168px; height: parent.height - self.y - 16px;
|
announcements-scroll := ScrollView {
|
||||||
announcements-scroll := ScrollView {
|
width: parent.width; height: parent.height;
|
||||||
width: parent.width; height: parent.height;
|
viewport-width: self.width - 10px;
|
||||||
VerticalLayout {
|
viewport-height: announcement-entries.preferred-height;
|
||||||
width: announcements-scroll.width; spacing: 0px; padding: 0px;
|
horizontal-scrollbar-policy: always-off;
|
||||||
padding-right: 8px;
|
vertical-scrollbar-policy: as-needed;
|
||||||
alignment: start;
|
announcement-entries := VerticalLayout {
|
||||||
for item[index] in root.items: Rectangle {
|
width: announcements-scroll.viewport-width; spacing: 10px; padding: 0px;
|
||||||
height: 54px;
|
alignment: start;
|
||||||
background: root.selected-id == item.id ? #eaf2ff : entry-touch.has-hover ? #f0f1f2 : transparent;
|
for item in root.items: Rectangle {
|
||||||
border-radius: UiRadius.control;
|
height: 64px;
|
||||||
Text { x: 8px; y: 6px; width: parent.width - 32px; height: 18px; text: item.title; overflow: elide; font-size: ImGuiFontStyle.base; font-weight: item.read ? 400 : 600; color: #202124; vertical-alignment: center; }
|
background: root.selected-id == item.id ? #e8efff
|
||||||
if !item.read: Rectangle {
|
: entry-touch.pressed ? WorkbenchStyle.pressed-surface
|
||||||
x: parent.width - 17px; y: 13px; width: 5px; height: 5px;
|
: entry-touch.has-hover ? WorkbenchStyle.hover-surface : WorkbenchStyle.surface;
|
||||||
border-radius: 2.5px; background: #2563eb;
|
border-radius: UiRadius.control;
|
||||||
accessible-role: none;
|
border-width: 1px;
|
||||||
}
|
border-color: root.selected-id == item.id ? #b5c9f5 : WorkbenchStyle.border;
|
||||||
Text { x: 8px; y: 28px; width: parent.width - 36px; height: 16px; text: item.date; color: #61656c; font-size: ImGuiFontStyle.small; overflow: elide; vertical-alignment: center; }
|
Text {
|
||||||
accessible-role: button;
|
x: 10px; y: 9px; width: parent.width - 30px; height: 20px;
|
||||||
accessible-label: item.title + " " + (item.read ? MessageStrings.read : MessageStrings.unread);
|
text: item.title; overflow: elide; font-size: 13px;
|
||||||
accessible-action-default => { root.open-message(item.id, item.revision); }
|
font-weight: item.read ? 400 : 600;
|
||||||
entry-touch := TouchArea { mouse-cursor: pointer; clicked => { root.open-message(item.id, item.revision); } }
|
color: root.selected-id == item.id ? WorkbenchStyle.accent : WorkbenchStyle.text;
|
||||||
IconButton {
|
vertical-alignment: center;
|
||||||
x: parent.width - 24px; y: 26px; width: 20px; height: 20px;
|
}
|
||||||
icon: FontAwesomeIcons.xmark; icon-size: ImGuiFontStyle.small;
|
if !item.read: Rectangle {
|
||||||
icon-color: #61656c; danger: true;
|
x: parent.width - 15px; y: 16px; width: 6px; height: 6px;
|
||||||
|
border-radius: 3px; background: WorkbenchStyle.accent;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
x: 10px; y: 35px; width: parent.width - 20px; height: 18px;
|
||||||
|
text: item.date; color: WorkbenchStyle.muted; font-size: 11px;
|
||||||
|
overflow: elide; vertical-alignment: center;
|
||||||
|
}
|
||||||
accessible-role: button;
|
accessible-role: button;
|
||||||
accessible-label: MessageStrings.delete-message + " " + item.title;
|
accessible-label: item.title + " " + (item.read ? MessageStrings.read : MessageStrings.unread);
|
||||||
accessible-action-default => { root.request-delete(item.id, item.revision); }
|
accessible-action-default => { root.open-message(item.id, item.revision); }
|
||||||
clicked => { root.request-delete(item.id, item.revision); }
|
entry-touch := TouchArea {
|
||||||
}
|
mouse-cursor: pointer;
|
||||||
if index < root.items.length - 1: Rectangle {
|
clicked => { root.open-message(item.id, item.revision); }
|
||||||
x: 8px; y: parent.height - 1px;
|
}
|
||||||
width: parent.width - 16px; height: 1px;
|
|
||||||
background: root.separator-color;
|
|
||||||
accessible-role: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if root.items.length == 0 && root.total == 0 && root.loaded && root.connected && !root.loading && !root.failed: Text {
|
||||||
if root.items.length == 0 && root.total == 0 && root.loaded && root.connected && !root.loading && !root.failed: Text {
|
x: 24px; width: parent.width - 48px; height: parent.height;
|
||||||
x: 16px; y: root.content-top; width: parent.width - 32px; height: announcement-list.height;
|
text: MessageStrings.empty; wrap: word-wrap;
|
||||||
text: MessageStrings.empty; wrap: word-wrap;
|
color: WorkbenchStyle.muted; font-size: 13px;
|
||||||
color: #61656c; font-size: ImGuiFontStyle.body;
|
horizontal-alignment: center; vertical-alignment: center;
|
||||||
horizontal-alignment: center; vertical-alignment: center;
|
}
|
||||||
}
|
content-divider := Rectangle {
|
||||||
content-divider := Rectangle {
|
x: announcement-list.x + announcement-list.width + 8px;
|
||||||
x: announcement-list.x + announcement-list.width + 12px;
|
y: 1px; width: 1px; height: parent.height - 2px;
|
||||||
y: root.content-top; width: 1px; height: announcement-list.height;
|
background: WorkbenchStyle.separator;
|
||||||
background: root.separator-color;
|
visible: root.items.length > 0 || root.selected-id != "";
|
||||||
visible: root.items.length > 0 || root.selected-id != "";
|
accessible-role: none;
|
||||||
accessible-role: none;
|
}
|
||||||
}
|
announcement-details := Rectangle {
|
||||||
announcement-details := Rectangle {
|
x: content-divider.x + 17px; y: 16px;
|
||||||
x: content-divider.x + content-divider.width + 12px;
|
width: parent.width - self.x - 16px; height: parent.height - 32px;
|
||||||
y: root.content-top; width: parent.width - self.x - 16px; height: announcement-list.height;
|
clip: true;
|
||||||
if root.selected-id == "" && root.items.length > 0: Text { x: 8px; width: parent.width - 16px; text: MessageStrings.select-message; color: #61656c; font-size: ImGuiFontStyle.body; wrap: word-wrap; horizontal-alignment: center; vertical-alignment: center; }
|
if root.selected-id == "" && root.items.length > 0: Text {
|
||||||
if root.selected-id != "": details-scroll := ScrollView {
|
width: parent.width; height: parent.height;
|
||||||
width: parent.width; height: parent.height;
|
text: MessageStrings.select-message; color: WorkbenchStyle.muted; font-size: 13px;
|
||||||
VerticalLayout {
|
wrap: word-wrap; horizontal-alignment: center; vertical-alignment: center;
|
||||||
width: details-scroll.width; padding: 8px; padding-top: 6px; padding-right: 16px;
|
}
|
||||||
spacing: 12px; alignment: start;
|
if root.selected-id != "": details-scroll := ScrollView {
|
||||||
VerticalLayout {
|
width: parent.width; height: parent.height;
|
||||||
vertical-stretch: 0; spacing: 4px;
|
viewport-width: self.width - 12px;
|
||||||
Text { vertical-stretch: 0; min-height: 18px; text: root.selected-title; wrap: word-wrap; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
|
viewport-height: details-content.preferred-height;
|
||||||
Text { vertical-stretch: 0; min-height: 16px; text: root.selected-date; accessible-label: root.selected-date + " · " + (root.selected-read ? MessageStrings.read : MessageStrings.unread); color: #61656c; font-size: ImGuiFontStyle.small; wrap: word-wrap; vertical-alignment: center; }
|
horizontal-scrollbar-policy: always-off;
|
||||||
}
|
vertical-scrollbar-policy: as-needed;
|
||||||
Rectangle { height: 1px; background: root.separator-color; }
|
details-content := VerticalLayout {
|
||||||
StyledText {
|
width: details-scroll.viewport-width; padding: 0px; padding-bottom: 4px;
|
||||||
vertical-stretch: 0;
|
spacing: 14px; alignment: start;
|
||||||
text: root.selected-body;
|
Rectangle {
|
||||||
default-color: #202124; default-font-size: ImGuiFontStyle.body;
|
height: max(28px, selected-heading.preferred-height);
|
||||||
link-color: #2563eb;
|
selected-heading := VerticalLayout {
|
||||||
link-clicked(url) => { root.open-link(url); }
|
x: 0px; y: 0px;
|
||||||
|
width: parent.width - 40px; height: self.preferred-height;
|
||||||
|
padding: 0px; spacing: 6px;
|
||||||
|
Text {
|
||||||
|
vertical-stretch: 0; text: root.selected-title; wrap: word-wrap;
|
||||||
|
font-size: 16px; font-weight: 600; color: WorkbenchStyle.text;
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
vertical-stretch: 0; text: root.selected-date;
|
||||||
|
accessible-label: root.selected-date + " · " + (root.selected-read ? MessageStrings.read : MessageStrings.unread);
|
||||||
|
color: WorkbenchStyle.muted; font-size: 11px; wrap: word-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IconButton {
|
||||||
|
x: parent.width - self.width; y: 0px; width: 28px; height: 28px;
|
||||||
|
icon: FontAwesomeIcons.trash-can; icon-size: 12px;
|
||||||
|
icon-color: WorkbenchStyle.muted; danger: true;
|
||||||
|
accessible-role: button;
|
||||||
|
accessible-label: MessageStrings.delete-message + " " + root.selected-title;
|
||||||
|
accessible-action-default => { root.request-delete(root.selected-id, root.selected-revision); }
|
||||||
|
clicked => { root.request-delete(root.selected-id, root.selected-revision); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle { height: 1px; background: WorkbenchStyle.separator; }
|
||||||
|
StyledText {
|
||||||
|
vertical-stretch: 0;
|
||||||
|
text: root.selected-body;
|
||||||
|
default-color: WorkbenchStyle.text; default-font-size: 13px;
|
||||||
|
// Metrics-only family provides 1.5em line height; visible
|
||||||
|
// glyphs continue to use Slint's normal font fallback.
|
||||||
|
default-font-family: "CrossDesk Message Spacing";
|
||||||
|
link-color: WorkbenchStyle.accent;
|
||||||
|
link-clicked(url) => { root.open-link(url); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,30 +229,31 @@ export component MessagePanel inherits DialogSurface {
|
|||||||
triggered => { root.load-more(); }
|
triggered => { root.load-more(); }
|
||||||
}
|
}
|
||||||
if root.pending-delete-id != "": Rectangle {
|
if root.pending-delete-id != "": Rectangle {
|
||||||
background: #00000042; border-radius: root.border-radius; z: 30;
|
background: #15223850; border-radius: root.border-radius; z: 30;
|
||||||
TouchArea { clicked => { confirmation-keys.focus(); } }
|
TouchArea { clicked => { confirmation-keys.focus(); } }
|
||||||
DialogSurface {
|
SettingsDialog {
|
||||||
x: (parent.width - self.width) / 2;
|
x: (parent.width - self.width) / 2;
|
||||||
y: (parent.height - self.height) / 2;
|
y: (parent.height - self.height) / 2;
|
||||||
width: 230px; height: confirmation-content.preferred-height;
|
width: min(360px, root.width - 48px);
|
||||||
border-radius: root.border-radius;
|
height: self.content-top + confirmation-content.preferred-height + 24px;
|
||||||
TouchArea { clicked => { confirmation-keys.focus(); } }
|
title: MessageStrings.delete-title;
|
||||||
|
close-label: MessageStrings.cancel;
|
||||||
|
close => { root.pending-delete-id = ""; }
|
||||||
confirmation-content := VerticalLayout {
|
confirmation-content := VerticalLayout {
|
||||||
padding: 16px;
|
x: 24px; y: parent.content-top; width: parent.width - 48px;
|
||||||
padding-top: 26px;
|
height: self.preferred-height;
|
||||||
padding-bottom: 26px;
|
padding: 0px; spacing: 24px;
|
||||||
spacing: 20px;
|
Text { text: MessageStrings.delete-confirmation; font-size: 13px; color: WorkbenchStyle.text; wrap: word-wrap; }
|
||||||
Text { text: MessageStrings.delete-confirmation; font-size: ImGuiFontStyle.base; color: #202124; wrap: word-wrap; horizontal-alignment: center; }
|
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
alignment: center; spacing: 8px;
|
alignment: end; spacing: 8px;
|
||||||
cancel-delete-button := CompactButton {
|
cancel-delete-button := SettingsButton {
|
||||||
width: 72px; text: MessageStrings.cancel;
|
width: 88px; text: MessageStrings.cancel;
|
||||||
accessible-role: button; accessible-label: MessageStrings.cancel;
|
accessible-role: button; accessible-label: MessageStrings.cancel;
|
||||||
accessible-action-default => { cancel-delete-button.clicked(); }
|
accessible-action-default => { cancel-delete-button.clicked(); }
|
||||||
clicked => { root.pending-delete-id = ""; }
|
clicked => { root.pending-delete-id = ""; }
|
||||||
}
|
}
|
||||||
confirm-delete-button := CompactButton {
|
confirm-delete-button := SettingsButton {
|
||||||
width: 72px; text: MessageStrings.delete-message; primary: true;
|
width: 88px; text: MessageStrings.delete-message; primary: true;
|
||||||
accessible-role: button; accessible-label: MessageStrings.delete-title;
|
accessible-role: button; accessible-label: MessageStrings.delete-title;
|
||||||
accessible-action-default => { confirm-delete-button.clicked(); }
|
accessible-action-default => { confirm-delete-button.clicked(); }
|
||||||
clicked => {
|
clicked => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UiRadius } from "ui_radius.slint";
|
import { UiRadius } from "ui_radius.slint";
|
||||||
import { CompactComboBox, ToggleSwitch } from "common.slint";
|
import { CompactComboBox, ToggleSwitch, DialogSurface, IconButton, FontAwesomeIcons } from "common.slint";
|
||||||
import { WorkbenchStyle } from "workbench_controls.slint";
|
import { WorkbenchStyle } from "workbench_controls.slint";
|
||||||
|
|
||||||
// Shared visual treatment for the global settings pages.
|
// Shared visual treatment for the global settings pages.
|
||||||
@@ -28,6 +28,66 @@ export component SettingsButton inherits Rectangle {
|
|||||||
touch := TouchArea { enabled: root.enabled; mouse-cursor: pointer; clicked => { root.clicked(); } }
|
touch := TouchArea { enabled: root.enabled; mouse-cursor: pointer; clicked => { root.clicked(); } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export component SettingsDialog inherits DialogSurface {
|
||||||
|
in property <string> title;
|
||||||
|
in property <string> close-label;
|
||||||
|
in property <int> badge-count: 0;
|
||||||
|
in property <string> badge-label;
|
||||||
|
in property <string> header-status;
|
||||||
|
in property <string> action-text;
|
||||||
|
in property <bool> action-enabled: true;
|
||||||
|
out property <length> content-top: 64px;
|
||||||
|
callback action();
|
||||||
|
callback close();
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: WorkbenchStyle.separator;
|
||||||
|
drop-shadow-color: #14294424;
|
||||||
|
drop-shadow-blur: 24px;
|
||||||
|
drop-shadow-offset-y: 8px;
|
||||||
|
TouchArea { }
|
||||||
|
HorizontalLayout {
|
||||||
|
x: 24px; y: 18px; width: parent.width - 48px; height: 28px;
|
||||||
|
spacing: 10px;
|
||||||
|
Text {
|
||||||
|
text: root.title; font-size: 20px; font-weight: 600;
|
||||||
|
color: WorkbenchStyle.text; vertical-alignment: center;
|
||||||
|
}
|
||||||
|
if root.badge-count > 0: Rectangle {
|
||||||
|
width: max(24px, count-label.preferred-width + 14px); height: 22px;
|
||||||
|
border-radius: 11px; background: #e8efff;
|
||||||
|
accessible-role: text; accessible-label: root.badge-label;
|
||||||
|
count-label := Text {
|
||||||
|
text: "" + root.badge-count; color: WorkbenchStyle.accent;
|
||||||
|
font-size: 11px; font-weight: 600;
|
||||||
|
horizontal-alignment: center; vertical-alignment: center;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle { horizontal-stretch: 1; }
|
||||||
|
if root.header-status != "": Text {
|
||||||
|
text: root.header-status; color: WorkbenchStyle.muted;
|
||||||
|
font-size: 11px; vertical-alignment: center;
|
||||||
|
}
|
||||||
|
if root.action-text != "": SettingsButton {
|
||||||
|
width: action-label-metrics.preferred-width + 24px; height: 28px;
|
||||||
|
text: root.action-text; enabled: root.action-enabled;
|
||||||
|
clicked => { root.action(); }
|
||||||
|
action-label-metrics := Text { visible: false; text: root.action-text; font-size: 12px; }
|
||||||
|
}
|
||||||
|
IconButton {
|
||||||
|
width: 28px; height: 28px;
|
||||||
|
icon: FontAwesomeIcons.xmark; icon-size: 14px;
|
||||||
|
icon-color: WorkbenchStyle.muted;
|
||||||
|
hover-background: WorkbenchStyle.hover-surface;
|
||||||
|
pressed-background: WorkbenchStyle.pressed-surface;
|
||||||
|
accessible-role: button; accessible-label: root.close-label;
|
||||||
|
accessible-action-default => { root.close(); }
|
||||||
|
clicked => { root.close(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@children
|
||||||
|
}
|
||||||
|
|
||||||
export component SettingsSelect inherits CompactComboBox {
|
export component SettingsSelect inherits CompactComboBox {
|
||||||
subtle: true;
|
subtle: true;
|
||||||
show-arrow: false;
|
show-arrow: false;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ inline int ApplyMainWindowStrings(
|
|||||||
messages.set_messages(Text(localization::messages[language]));
|
messages.set_messages(Text(localization::messages[language]));
|
||||||
messages.set_unread(Text(localization::message_unread[language]));
|
messages.set_unread(Text(localization::message_unread[language]));
|
||||||
messages.set_read(Text(localization::message_read[language]));
|
messages.set_read(Text(localization::message_read[language]));
|
||||||
|
messages.set_read_all(Text(localization::message_read_all[language]));
|
||||||
messages.set_empty(Text(localization::message_empty[language]));
|
messages.set_empty(Text(localization::message_empty[language]));
|
||||||
messages.set_select_message(Text(localization::message_select[language]));
|
messages.set_select_message(Text(localization::message_select[language]));
|
||||||
messages.set_loading(Text(localization::message_loading[language]));
|
messages.set_loading(Text(localization::message_loading[language]));
|
||||||
|
|||||||
Reference in New Issue
Block a user