Remove desktop clock and sticky-note widgets
This commit is contained in:
1 parent
78c7168a92
commit
b356f63b93
21 files changed
+136
-3021
No files matched your search
@@ -1,8 +1,8 @@
|
||||
use cosmic::widget::column;
|
||||
|
||||
use super::{
|
||||
Alignment, App, Element, Field, Kind, Length, Message, button, container, fonts, row, set, slider, text_input,
|
||||
visuals, widget,
|
||||
Alignment, App, Element, Field, Kind, Length, Message, button, container, row, set, slider, text_input, visuals,
|
||||
widget,
|
||||
};
|
||||
|
||||
impl App {
|
||||
@@ -118,56 +118,6 @@ impl App {
|
||||
let labels = labels.width(Length::Fill);
|
||||
let path = field.path.clone();
|
||||
let control: Element<'static, Message> = match field.kind.clone() {
|
||||
Kind::Font => {
|
||||
let value = self
|
||||
.inputs
|
||||
.get(&path)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.draft.string(&path, ""));
|
||||
let families = fonts::families();
|
||||
let selected = if value.is_empty() {
|
||||
Some(0)
|
||||
} else {
|
||||
families.iter().position(|family| family == &value)
|
||||
};
|
||||
let selection_path = path.clone();
|
||||
let commit = field.clone();
|
||||
column([])
|
||||
.spacing(4)
|
||||
.push(
|
||||
ferese_theme::controls::select(
|
||||
cosmic::iced::widget::pick_list(
|
||||
families,
|
||||
selected.map(|index| families[index].clone()),
|
||||
move |family: String| {
|
||||
let value = if family == "Default font" {
|
||||
String::new()
|
||||
} else {
|
||||
family
|
||||
};
|
||||
Message::SelectFont(selection_path.clone(), value)
|
||||
},
|
||||
)
|
||||
.font(self.font)
|
||||
.text_size(12)
|
||||
.width(225),
|
||||
palette,
|
||||
)
|
||||
.width(225),
|
||||
)
|
||||
.push(
|
||||
text_input("Default font", value)
|
||||
.font(self.font)
|
||||
.padding([4, 8])
|
||||
.style(visuals::input_style(palette))
|
||||
.on_input(move |value| Message::Draft(path.clone(), value))
|
||||
.on_submit(move |_| Message::Commit(field.clone()))
|
||||
.on_unfocus(Message::Commit(commit))
|
||||
.width(225)
|
||||
.size(12),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
Kind::Toggle(default) => {
|
||||
let enabled = self.draft.boolean(&path, default);
|
||||
ferese_theme::controls::switch(enabled, palette)
|
||||
|
||||
@@ -119,7 +119,6 @@ enum Message {
|
||||
PanelPreviewResolved(ferese_config::panel::layout::Resolution),
|
||||
PanelPreviewOverflow,
|
||||
PanelEdit(panel_edit::Action),
|
||||
SelectFont(String, String),
|
||||
Draft(String, String),
|
||||
Commit(Field),
|
||||
Range(Field, f64),
|
||||
@@ -146,9 +145,6 @@ enum Message {
|
||||
DefaultWallpaperThumbnail(ferese_config::theme::Appearance, Result<widget::image::Handle, String>),
|
||||
PreviewLock,
|
||||
LockPreviewStarted(Result<(), String>),
|
||||
AddNote,
|
||||
NoteAction(String, widget::text_editor::Action),
|
||||
SaveNote(String, u64),
|
||||
WallpaperPicked(wallpaper_controls::Target, Result<Option<String>, String>),
|
||||
NewCommand(String),
|
||||
AddCommand,
|
||||
@@ -162,7 +158,6 @@ struct App {
|
||||
displays: Vec<displays::Display>,
|
||||
display_selection: Option<String>,
|
||||
display_error: Option<String>,
|
||||
note_editors: HashMap<String, NoteEditor>,
|
||||
core: Core,
|
||||
path: PathBuf,
|
||||
current: Snapshot,
|
||||
@@ -227,12 +222,6 @@ impl Drop for App {
|
||||
}
|
||||
}
|
||||
|
||||
struct NoteEditor {
|
||||
content: widget::text_editor::Content<cosmic::Renderer>,
|
||||
revision: u64,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl cosmic::Application for App {
|
||||
type Executor = cosmic::executor::Default;
|
||||
type Flags = (PathBuf, Result<Snapshot, String>, Option<InitialPage>);
|
||||
@@ -273,7 +262,6 @@ impl cosmic::Application for App {
|
||||
displays: vec![],
|
||||
display_selection: None,
|
||||
display_error: None,
|
||||
note_editors: HashMap::new(),
|
||||
core,
|
||||
path,
|
||||
draft: current.clone(),
|
||||
@@ -322,7 +310,6 @@ impl cosmic::Application for App {
|
||||
.map(|id| app.set_window_title("Ferese Settings".into(), id))
|
||||
.unwrap_or_else(Task::none);
|
||||
|
||||
app.sync_notes();
|
||||
let connections = if app.page == Page::Connections {
|
||||
app.refresh_connections()
|
||||
} else {
|
||||
@@ -393,77 +380,16 @@ impl cosmic::Application for App {
|
||||
theme
|
||||
};
|
||||
}
|
||||
Message::NoteAction(id, action) => {
|
||||
if let Some(editor) = self.note_editors.get_mut(&id) {
|
||||
let edited = action.is_edit();
|
||||
editor.content.perform(action);
|
||||
|
||||
if edited {
|
||||
editor.dirty = true;
|
||||
editor.revision = editor.revision.wrapping_add(1);
|
||||
let revision = editor.revision;
|
||||
return cosmic::task::future(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||
Message::SaveNote(id, revision)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::SaveNote(id, revision) => {
|
||||
let Some(index) = self.note_index(&id) else {
|
||||
return Task::none();
|
||||
};
|
||||
|
||||
if let Some(editor) = self.note_editors.get_mut(&id)
|
||||
&& editor.dirty
|
||||
&& editor.revision == revision
|
||||
{
|
||||
let value = editor.content.text();
|
||||
editor.dirty = false;
|
||||
return self.change(set(&format!("desktop_widgets.notes.{index}.text"), value));
|
||||
}
|
||||
}
|
||||
Message::AddNote if !self.saving && self.draft.records("desktop_widgets.notes") < 32 => {
|
||||
let id = format!(
|
||||
"note-{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos()
|
||||
);
|
||||
let task = self.change(Edit::Add(
|
||||
"desktop_widgets.notes".into(),
|
||||
vec![
|
||||
("id".into(), id.into()),
|
||||
("title".into(), "Note".into()),
|
||||
("text".into(), "".into()),
|
||||
],
|
||||
));
|
||||
|
||||
self.sync_notes();
|
||||
return task;
|
||||
}
|
||||
Message::SelectFont(path, family) => {
|
||||
self.inputs.remove(&path);
|
||||
return self.change(set(&path, family));
|
||||
}
|
||||
Message::DragWindow => return self.core.drag(None),
|
||||
Message::ExternalConfig(result) => match result {
|
||||
Ok(snapshot) if snapshot.source == self.current.source || snapshot.source == self.draft.source => {}
|
||||
Ok(snapshot) => {
|
||||
if self.saving
|
||||
|| self.note_editors.values().any(|editor| editor.dirty)
|
||||
|| !self.inputs.is_empty()
|
||||
|| !self.ranges.is_empty()
|
||||
|| !self.pending.is_empty()
|
||||
{
|
||||
if self.saving || !self.inputs.is_empty() || !self.ranges.is_empty() || !self.pending.is_empty() {
|
||||
self.status = "Config changed externally · Reload when your edits are finished".into();
|
||||
} else {
|
||||
self.current = snapshot.clone();
|
||||
self.draft = snapshot;
|
||||
self.family_sections =
|
||||
theme_controls::FamilySections::new(&self.resolved.families, &self.draft);
|
||||
self.sync_notes();
|
||||
self.font = fonts::interface_font(&self.resolved.presented.tokens.typography.font_family);
|
||||
self.undo = None;
|
||||
self.error = None;
|
||||
@@ -734,7 +660,6 @@ impl cosmic::Application for App {
|
||||
|
||||
self.family_sections =
|
||||
theme_controls::FamilySections::new(&self.resolved.families, &self.draft);
|
||||
self.sync_notes();
|
||||
self.status = if live {
|
||||
"Saved · desktop updated"
|
||||
} else if self.path != store::config_path() {
|
||||
@@ -765,8 +690,6 @@ impl cosmic::Application for App {
|
||||
self.current = snapshot.clone();
|
||||
self.draft = snapshot;
|
||||
self.family_sections = theme_controls::FamilySections::new(&self.resolved.families, &self.draft);
|
||||
self.note_editors.clear();
|
||||
self.sync_notes();
|
||||
self.font = fonts::interface_font(&self.resolved.presented.tokens.typography.font_family);
|
||||
self.pending.clear();
|
||||
self.inputs.clear();
|
||||
@@ -957,7 +880,6 @@ impl cosmic::Application for App {
|
||||
Message::Remove(table, index) if !self.saving => {
|
||||
self.inputs.clear();
|
||||
let task = self.change(Edit::Remove(table, index));
|
||||
self.sync_notes();
|
||||
return task;
|
||||
}
|
||||
_ => {}
|
||||
@@ -1021,33 +943,6 @@ impl App {
|
||||
self.native_palette = palette;
|
||||
cosmic::command::set_theme(palette.native_theme())
|
||||
}
|
||||
|
||||
fn note_index(&self, id: &str) -> Option<usize> {
|
||||
(0..self.draft.records("desktop_widgets.notes"))
|
||||
.find(|index| self.draft.string(&format!("desktop_widgets.notes.{index}.id"), "note") == id)
|
||||
}
|
||||
|
||||
fn sync_notes(&mut self) {
|
||||
let mut ids = Vec::new();
|
||||
|
||||
for index in 0..self.draft.records("desktop_widgets.notes") {
|
||||
let prefix = format!("desktop_widgets.notes.{index}");
|
||||
let id = self.draft.string(&format!("{prefix}.id"), "note");
|
||||
let text = self.draft.string(&format!("{prefix}.text"), "");
|
||||
let editor = self.note_editors.entry(id.clone()).or_insert_with(|| NoteEditor {
|
||||
content: widget::text_editor::Content::with_text(&text),
|
||||
revision: 0,
|
||||
dirty: false,
|
||||
});
|
||||
if !editor.dirty && editor.content.text() != text {
|
||||
editor.content = widget::text_editor::Content::with_text(&text);
|
||||
}
|
||||
ids.push(id);
|
||||
}
|
||||
|
||||
self.note_editors.retain(|id, _| ids.contains(id));
|
||||
}
|
||||
|
||||
fn edit_many(&mut self, edits: Vec<Edit>) -> Task<Message> {
|
||||
for edit in edits {
|
||||
if let Err(error) = self.draft.edit(&edit) {
|
||||
@@ -1770,46 +1665,6 @@ mod tests {
|
||||
assert_eq!(app.draft.string("theme.light.file", ""), "/tmp/light.kdl");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notes_save_only_the_latest_revision_and_preserve_multiline_text() {
|
||||
let mut app = app();
|
||||
app.draft
|
||||
.edit(&Edit::Add(
|
||||
"desktop_widgets.notes".into(),
|
||||
vec![("id".into(), "test".into()), ("text".into(), "first\nsecond".into())],
|
||||
))
|
||||
.unwrap();
|
||||
app.sync_notes();
|
||||
let editor = app.note_editors.get_mut("test").unwrap();
|
||||
editor.dirty = true;
|
||||
editor.revision = 2;
|
||||
let _ = app.update(Message::SaveNote("test".into(), 1));
|
||||
assert!(!app.saving);
|
||||
assert!(app.note_editors["test"].dirty);
|
||||
let _ = app.update(Message::SaveNote("test".into(), 2));
|
||||
assert!(app.saving);
|
||||
assert!(!app.note_editors["test"].dirty);
|
||||
assert_eq!(app.draft.string("desktop_widgets.notes.0.text", ""), "first\nsecond");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_config_does_not_replace_an_unsaved_note() {
|
||||
let mut app = app();
|
||||
app.draft
|
||||
.edit(&Edit::Add(
|
||||
"desktop_widgets.notes".into(),
|
||||
vec![("id".into(), "test".into())],
|
||||
))
|
||||
.unwrap();
|
||||
app.sync_notes();
|
||||
app.note_editors.get_mut("test").unwrap().dirty = true;
|
||||
let _ = app.update(Message::ExternalConfig(Snapshot::parse(
|
||||
"animations {\n speed 0.5\n}\n".into(),
|
||||
)));
|
||||
assert!(app.note_editors.contains_key("test"));
|
||||
assert!(app.status.contains("externally"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rapid_edits_are_serialized_and_not_lost_when_a_save_finishes() {
|
||||
let mut app = app();
|
||||
|
||||
@@ -284,49 +284,6 @@ impl App {
|
||||
));
|
||||
body = body.push(self.note("The preview is an ordinary window and does not lock your session. Automatic locking is configured separately in Login items."));
|
||||
}
|
||||
Page::Desktop => {
|
||||
let can_change_list = !self.saving && !self.note_editors.values().any(|e| e.dirty);
|
||||
body = body.push(self.label("Sticky notes", 14.));
|
||||
body = body.push(self.note("Edit here; notes save after you pause typing. Desktop cards stay behind windows and are click-through."));
|
||||
for index in 0..self.draft.records("desktop_widgets.notes") {
|
||||
let id = self.draft.string(&format!("desktop_widgets.notes.{index}.id"), "note");
|
||||
let mut group = column([]).spacing(8);
|
||||
|
||||
for field in schema::note_fields(index) {
|
||||
group = group.push(self.field(field));
|
||||
}
|
||||
|
||||
if let Some(editor) = self.note_editors.get(&id) {
|
||||
let id = id.clone();
|
||||
group = group.push(
|
||||
widget::TextEditor::new(&editor.content)
|
||||
.height(160)
|
||||
.font(self.font)
|
||||
.size(13.)
|
||||
.on_action(move |action| Message::NoteAction(id.clone(), action)),
|
||||
);
|
||||
}
|
||||
group = group.push(self.settings_icon_button(
|
||||
"Remove note",
|
||||
"M4 6h16 M9 6V3h6v3 M6 6l1 15h10l1-15 M10 10v7 M14 10v7",
|
||||
can_change_list.then_some(Message::Remove("desktop_widgets.notes".into(), index)),
|
||||
));
|
||||
body = body.push(
|
||||
container(group)
|
||||
.padding([7, 10])
|
||||
.class(visuals::surface(palette.card, 14.)),
|
||||
);
|
||||
}
|
||||
body = body.push(
|
||||
self.settings_button(
|
||||
"Add note",
|
||||
"M12 5v14 M5 12h14",
|
||||
(can_change_list && self.draft.records("desktop_widgets.notes") < 32)
|
||||
.then_some(Message::AddNote),
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
Page::Startup => {
|
||||
body = body.push(self.note("Login items update live. Disabling or removing an item stops the session-owned process; enabling one starts it."));
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ pub enum Page {
|
||||
Appearance,
|
||||
Accessibility,
|
||||
Wallpaper,
|
||||
Desktop,
|
||||
Bar,
|
||||
Notifications,
|
||||
LockScreen,
|
||||
@@ -18,10 +17,7 @@ pub enum Page {
|
||||
|
||||
impl Page {
|
||||
pub const NAVIGATION: [(&'static str, &'static [Self]); 3] = [
|
||||
(
|
||||
"Desktop",
|
||||
&[Self::Appearance, Self::Wallpaper, Self::Desktop, Self::Bar],
|
||||
),
|
||||
("Desktop", &[Self::Appearance, Self::Wallpaper, Self::Bar]),
|
||||
("Interaction", &[Self::Windows, Self::Keyboard, Self::Accessibility]),
|
||||
(
|
||||
"System",
|
||||
@@ -64,11 +60,10 @@ impl Page {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub const ALL: [Self; 14] = [
|
||||
pub const ALL: [Self; 13] = [
|
||||
Self::Appearance,
|
||||
Self::Accessibility,
|
||||
Self::Wallpaper,
|
||||
Self::Desktop,
|
||||
Self::Bar,
|
||||
Self::Notifications,
|
||||
Self::LockScreen,
|
||||
@@ -86,7 +81,6 @@ impl Page {
|
||||
Self::Appearance => "Appearance",
|
||||
Self::Accessibility => "Accessibility",
|
||||
Self::Wallpaper => "Wallpaper",
|
||||
Self::Desktop => "Desktop widgets",
|
||||
Self::Bar => "Panel & Shell",
|
||||
Self::Notifications => "Notifications",
|
||||
Self::LockScreen => "Lock screen",
|
||||
@@ -105,7 +99,6 @@ impl Page {
|
||||
Self::Appearance => "A desktop that feels like yours.",
|
||||
Self::Accessibility => "Make the desktop easier to see and use.",
|
||||
Self::Wallpaper => "Set the scene for your workspace.",
|
||||
Self::Desktop => "Clock and sticky notes on your desktop.",
|
||||
Self::Bar => "Arrange panel items and groups.",
|
||||
Self::Notifications => "Stay informed on your terms.",
|
||||
Self::LockScreen => "Your desktop, safely put away.",
|
||||
@@ -126,7 +119,6 @@ impl Page {
|
||||
"M12 3a9 9 0 1 0 0 18h1a2 2 0 0 0 0-4h-1a1 1 0 0 1 0-2h3a6 6 0 0 0 0-12z M7 9h.01 M10 6h.01 M15 6h.01 M18 10h.01"
|
||||
}
|
||||
Self::Wallpaper => "M4 4h16v16H4z M4 16l5-5 4 4 3-3 4 4 M15 8h.01",
|
||||
Self::Desktop => "M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0 M12 7v5l3 2",
|
||||
Self::Bar => "M3 5h18v14H3z M3 9h18 M6 7h.01 M18 7h.01",
|
||||
Self::Notifications => "M6 8a6 6 0 0 1 12 0v6l2 3H4l2-3z M10 21h4",
|
||||
Self::LockScreen => "M7 10V7a5 5 0 0 1 10 0v3 M5 10h14v11H5z M12 14v3",
|
||||
@@ -162,7 +154,6 @@ impl Page {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Kind {
|
||||
Font,
|
||||
Toggle(bool),
|
||||
Range {
|
||||
default: f64,
|
||||
@@ -262,379 +253,6 @@ pub fn choice(
|
||||
) -> Field {
|
||||
Field::new(path, label, description, Kind::Choice { default, choices })
|
||||
}
|
||||
|
||||
fn clock_fields() -> Vec<Field> {
|
||||
vec![
|
||||
toggle(
|
||||
"desktop_widgets.clock.enabled",
|
||||
"Desktop clock",
|
||||
"Behind windows and click-through.",
|
||||
false,
|
||||
),
|
||||
choice(
|
||||
"desktop_widgets.clock.style",
|
||||
"Clock style",
|
||||
"Pixel stacks heavy, alternating-color digits; Minimal uses plain text.",
|
||||
"pixel",
|
||||
&[("pixel", "Pixel"), ("minimal", "Minimal")],
|
||||
),
|
||||
toggle(
|
||||
"desktop_widgets.clock.bold",
|
||||
"Bold text",
|
||||
"Use the font's bold weight.",
|
||||
false,
|
||||
),
|
||||
toggle(
|
||||
"desktop_widgets.clock.show_date",
|
||||
"Show date",
|
||||
"A small date above the digits in Pixel; beneath the time in Minimal.",
|
||||
true,
|
||||
),
|
||||
toggle(
|
||||
"desktop_widgets.clock.lowercase",
|
||||
"Lowercase",
|
||||
"Lowercase month, weekday and am/pm.",
|
||||
true,
|
||||
),
|
||||
text(
|
||||
"desktop_widgets.clock.anchor",
|
||||
"Position",
|
||||
"top_left, top_center, top_right, center_left, center, center_right, bottom_left, bottom_center, bottom_right",
|
||||
"top_left",
|
||||
),
|
||||
Field::new(
|
||||
"desktop_widgets.clock.font_family",
|
||||
"Font",
|
||||
"Choose an installed family or enter a name. Empty uses the style default.",
|
||||
Kind::Font,
|
||||
),
|
||||
text(
|
||||
"desktop_widgets.clock.time_format",
|
||||
"Time format",
|
||||
"%H:%M for 24-hour; %-I:%M %p for 12-hour; add :%S for seconds.",
|
||||
"%-I:%M %p",
|
||||
),
|
||||
text(
|
||||
"desktop_widgets.clock.date_format",
|
||||
"Date format",
|
||||
"%A, %-d %B for weekday and date; %-d %b for a short date.",
|
||||
"%a, %b %-d",
|
||||
),
|
||||
text(
|
||||
"desktop_widgets.clock.time_zone",
|
||||
"Time zone",
|
||||
"Empty follows system time; otherwise an IANA name such as Africa/Lagos.",
|
||||
"",
|
||||
),
|
||||
text(
|
||||
"desktop_widgets.clock.color",
|
||||
"Time color",
|
||||
"#RRGGBB or #RRGGBBAA; empty follows the theme.",
|
||||
"",
|
||||
),
|
||||
text(
|
||||
"desktop_widgets.clock.date_color",
|
||||
"Date color",
|
||||
"Empty follows muted theme text.",
|
||||
"",
|
||||
),
|
||||
text(
|
||||
"desktop_widgets.clock.background",
|
||||
"Background",
|
||||
"Empty is transparent; optionally #RRGGBB or #RRGGBBAA.",
|
||||
"",
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.margin_x",
|
||||
"Horizontal margin",
|
||||
"Distance from left/right edge; ignored when horizontally centered.",
|
||||
RangeSpec {
|
||||
default: 64.0,
|
||||
min: 0.0,
|
||||
max: 1000.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.margin_y",
|
||||
"Vertical margin",
|
||||
"Distance from top/bottom edge; ignored when vertically centered.",
|
||||
RangeSpec {
|
||||
default: 80.0,
|
||||
min: 0.0,
|
||||
max: 1000.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.width",
|
||||
"Widget width",
|
||||
"Logical pixels; increase for longer formats.",
|
||||
RangeSpec {
|
||||
default: 440.0,
|
||||
min: 64.0,
|
||||
max: 1600.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.height",
|
||||
"Widget height",
|
||||
"Leave room for both text lines.",
|
||||
RangeSpec {
|
||||
default: 320.0,
|
||||
min: 32.0,
|
||||
max: 800.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.time_size",
|
||||
"Time size",
|
||||
"Logical pixels; scales with your output.",
|
||||
RangeSpec {
|
||||
default: 128.0,
|
||||
min: 8.0,
|
||||
max: 240.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.date_size",
|
||||
"Date size",
|
||||
"Separate size for the date line.",
|
||||
RangeSpec {
|
||||
default: 18.0,
|
||||
min: 8.0,
|
||||
max: 96.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.opacity",
|
||||
"Opacity",
|
||||
"Applies to text and the optional background.",
|
||||
RangeSpec {
|
||||
default: 0.9,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.05,
|
||||
suffix: "",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.gap",
|
||||
"Line spacing",
|
||||
"Space between time and date.",
|
||||
RangeSpec {
|
||||
default: 4.0,
|
||||
min: 0.0,
|
||||
max: 64.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"desktop_widgets.clock.padding",
|
||||
"Inner padding",
|
||||
"Space around the labels.",
|
||||
RangeSpec {
|
||||
default: 12.0,
|
||||
min: 0.0,
|
||||
max: 64.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
choice(
|
||||
"desktop_widgets.clock.alignment",
|
||||
"Text alignment",
|
||||
"Alignment within the widget.",
|
||||
"center",
|
||||
&[("left", "Left"), ("center", "Center"), ("right", "Right")],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn note_fields(index: usize) -> Vec<Field> {
|
||||
let prefix = format!("desktop_widgets.notes.{index}");
|
||||
vec![
|
||||
Field::new(
|
||||
format!("{prefix}.interactive"),
|
||||
"Desktop editing",
|
||||
"Drag the title and edit directly. Off makes the card click-through.",
|
||||
Kind::Toggle(true),
|
||||
),
|
||||
Field::new(format!("{prefix}.enabled"), "Show note", "", Kind::Toggle(true)),
|
||||
text(format!("{prefix}.title"), "Title", "Empty hides the title.", "Note"),
|
||||
text(
|
||||
format!("{prefix}.anchor"),
|
||||
"Position",
|
||||
"Nine positions, e.g. top_right or center.",
|
||||
"top_right",
|
||||
),
|
||||
Field::new(
|
||||
format!("{prefix}.font_family"),
|
||||
"Font",
|
||||
"Choose an installed family or enter a name. Empty follows the desktop font.",
|
||||
Kind::Font,
|
||||
),
|
||||
text(
|
||||
format!("{prefix}.color"),
|
||||
"Text color",
|
||||
"Empty or theme follows the theme; otherwise #RRGGBB / #RRGGBBAA.",
|
||||
"",
|
||||
),
|
||||
text(
|
||||
format!("{prefix}.background"),
|
||||
"Background",
|
||||
"theme follows the palette; empty is transparent; otherwise hex color.",
|
||||
"theme",
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.width"),
|
||||
"Width",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 320.0,
|
||||
min: 120.0,
|
||||
max: 1200.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.height"),
|
||||
"Height",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 240.0,
|
||||
min: 80.0,
|
||||
max: 1200.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.margin_x"),
|
||||
"Horizontal margin",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 48.0,
|
||||
min: 0.0,
|
||||
max: 1000.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.margin_y"),
|
||||
"Vertical margin",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 80.0,
|
||||
min: 0.0,
|
||||
max: 1000.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.text_size"),
|
||||
"Text size",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 16.0,
|
||||
min: 8.0,
|
||||
max: 96.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.title_size"),
|
||||
"Title size",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 18.0,
|
||||
min: 8.0,
|
||||
max: 96.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.opacity"),
|
||||
"Opacity",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 0.9,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.05,
|
||||
suffix: "",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.padding"),
|
||||
"Padding",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 20.0,
|
||||
min: 0.0,
|
||||
max: 64.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
format!("{prefix}.gap"),
|
||||
"Title spacing",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
RangeSpec {
|
||||
default: 8.0,
|
||||
min: 0.0,
|
||||
max: 64.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
Field::new(
|
||||
format!("{prefix}.alignment"),
|
||||
"Text alignment",
|
||||
"",
|
||||
Kind::Choice {
|
||||
default: "left",
|
||||
choices: &[("left", "Left"), ("center", "Center"), ("right", "Right")],
|
||||
},
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn fields(page: Page) -> Vec<Field> {
|
||||
match page {
|
||||
Page::LockScreen => vec![
|
||||
@@ -777,7 +395,7 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
range(
|
||||
"theme.geometry.shell_radius",
|
||||
"Shell corner radius",
|
||||
"Rounds the bar, menus, notifications, desktop widgets and hover backgrounds. Window corners are separate.",
|
||||
"Rounds the bar, menus, notifications and hover backgrounds. Window corners are separate.",
|
||||
RangeSpec {
|
||||
default: 14.0,
|
||||
min: 0.0,
|
||||
@@ -875,7 +493,6 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
&[("fill", "Fill"), ("fit", "Fit")],
|
||||
),
|
||||
],
|
||||
Page::Desktop => clock_fields(),
|
||||
Page::Notifications => vec![
|
||||
toggle(
|
||||
"notifications.show_popups",
|
||||
|
||||
@@ -345,27 +345,11 @@ window-rule app-id="mine" floating=#true
|
||||
assert_eq!(snapshot.records("autostart"), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_note_lists_keep_existing_clock_settings() {
|
||||
let mut snapshot =
|
||||
Snapshot::parse("desktop-widgets {\n clock {\n enabled #true\n }\n}\n".into()).unwrap();
|
||||
snapshot
|
||||
.edit(&Edit::Add(
|
||||
"desktop_widgets.notes".into(),
|
||||
vec![("id".into(), "first".into()), ("text".into(), "one\ntwo".into())],
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(snapshot.records("desktop_widgets.notes"), 1);
|
||||
assert!(snapshot.boolean("desktop_widgets.clock.enabled", false));
|
||||
assert_eq!(snapshot.string("desktop_widgets.notes.0.text", ""), "one\ntwo");
|
||||
snapshot.edit(&Edit::Remove("desktop_widgets.notes".into(), 0)).unwrap();
|
||||
assert_eq!(snapshot.records("desktop_widgets.notes"), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires a built compositor; run with FERESE_TEST_BINARY=target/debug/ferese"]
|
||||
fn all_gui_controls_and_presets_pass_compositor_validation() {
|
||||
let binary = std::env::var_os("FERESE_TEST_BINARY").expect("Set FERESE_TEST_BINARY");
|
||||
let initial = Snapshot::parse(include_str!("../../../packaging/config.kdl").into()).unwrap();
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("config.kdl");
|
||||
let validate = |snapshot: &Snapshot| {
|
||||
@@ -379,14 +363,10 @@ window-rule app-id="mine" floating=#true
|
||||
);
|
||||
};
|
||||
for page in crate::schema::Page::ALL {
|
||||
let mut fields = crate::schema::fields(page);
|
||||
if page == crate::schema::Page::Desktop {
|
||||
fields.extend(crate::schema::note_fields(0));
|
||||
}
|
||||
let fields = crate::schema::fields(page);
|
||||
for field in fields {
|
||||
use crate::schema::Kind;
|
||||
let values: Vec<Value> = match field.kind {
|
||||
Kind::Font => vec!["".into(), "sans-serif".into()],
|
||||
Kind::Toggle(_) => vec![true.into(), false.into()],
|
||||
Kind::Range { min, max, integer, .. } => {
|
||||
if integer {
|
||||
@@ -399,22 +379,14 @@ window-rule app-id="mine" floating=#true
|
||||
Kind::Choice { choices, .. } => choices.iter().map(|(v, _)| (*v).into()).collect(),
|
||||
};
|
||||
for value in values {
|
||||
let mut snapshot = Snapshot::parse(String::new()).unwrap();
|
||||
if field.path.starts_with("desktop_widgets.notes.") {
|
||||
snapshot
|
||||
.edit(&Edit::Add(
|
||||
"desktop_widgets.notes".into(),
|
||||
vec![("id".into(), "test".into())],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
let mut snapshot = initial.clone();
|
||||
snapshot.edit(&set(&field.path, value)).unwrap();
|
||||
validate(&snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
for preset in 0..crate::visuals::PRESETS.len() {
|
||||
let mut snapshot = Snapshot::parse(String::new()).unwrap();
|
||||
let mut snapshot = initial.clone();
|
||||
for edit in crate::visuals::preset(preset) {
|
||||
snapshot.edit(&edit).unwrap();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user