Files
feresaria/shell/ferese-settings/src/schema.rs
T

902 lines
25 KiB
Rust
Raw 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.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Page {
Appearance,
Wallpaper,
Desktop,
Bar,
Notifications,
Windows,
Motion,
Keyboard,
Shortcuts,
Startup,
Displays,
}
impl Page {
pub const ALL: [Self; 11] = [
Self::Appearance,
Self::Wallpaper,
Self::Desktop,
Self::Bar,
Self::Notifications,
Self::Windows,
Self::Motion,
Self::Keyboard,
Self::Shortcuts,
Self::Startup,
Self::Displays,
];
pub fn title(self) -> &'static str {
match self {
Self::Appearance => "Appearance",
Self::Wallpaper => "Wallpaper",
Self::Desktop => "Desktop widgets",
Self::Bar => "Menu bar",
Self::Notifications => "Notifications",
Self::Windows => "Windows",
Self::Motion => "Motion",
Self::Keyboard => "Keyboard & mouse",
Self::Shortcuts => "Shortcuts",
Self::Startup => "Login items",
Self::Displays => "Displays",
}
}
pub fn subtitle(self) -> &'static str {
match self {
Self::Appearance => "A desktop that feels like yours.",
Self::Wallpaper => "Set the scene for your workspace.",
Self::Desktop => "A clock that feels at home on your wallpaper.",
Self::Bar => "Everything you need, within reach.",
Self::Notifications => "Stay informed on your terms.",
Self::Windows => "Make room for the way you work.",
Self::Motion => "Find your rhythm.",
Self::Keyboard => "Fine-tune the everyday details.",
Self::Shortcuts => "Your most-used actions, a keystroke away.",
Self::Startup => "Ready when you sign in.",
Self::Displays => "A place for every screen.",
}
}
pub fn icon(self) -> &'static str {
match self {
Self::Appearance => {
"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::Windows => "M3 4h12v12H3z M8 9h13v12H8z",
Self::Motion => "M3 8h9a3 3 0 1 0-3-3 M3 12h15a3 3 0 1 1-3 3 M3 16h5",
Self::Keyboard => {
"M3 6h18v12H3z M6 9h.01 M10 9h.01 M14 9h.01 M18 9h.01 M6 12h.01 M10 12h.01 M14 12h.01 M18 12h.01 M8 15h8"
}
Self::Shortcuts => {
"M8 8h8v8H8z M8 8H5a3 3 0 1 1 3-3v3z M16 8V5a3 3 0 1 1 3 3h-3z M16 16h3a3 3 0 1 1-3 3v-3z M8 16v3a3 3 0 1 1-3-3h3z"
}
Self::Startup => "M12 3v9 M7 5a9 9 0 1 0 10 0",
Self::Displays => "M3 4h18v13H3z M12 17v4 M8 21h8",
}
}
pub fn matches(self, query: &str) -> bool {
let query = query.to_lowercase();
self.title().to_lowercase().contains(&query)
|| fields(self).iter().any(|f| {
format!("{} {} {}", f.label, f.description, f.path)
.to_lowercase()
.contains(&query)
})
}
}
#[derive(Clone, Debug)]
pub enum Kind {
Font,
Toggle(bool),
Range {
default: f64,
min: f64,
max: f64,
step: f64,
suffix: &'static str,
integer: bool,
},
Choice {
default: &'static str,
choices: &'static [(&'static str, &'static str)],
},
Text {
default: &'static str,
argv: bool,
},
}
#[derive(Clone, Debug)]
pub struct Field {
pub path: String,
pub label: String,
pub description: String,
pub kind: Kind,
}
impl Field {
pub fn new(
path: impl Into<String>,
label: impl Into<String>,
description: impl Into<String>,
kind: Kind,
) -> Self {
Self {
path: path.into(),
label: label.into(),
description: description.into(),
kind,
}
}
}
fn toggle(path: &str, label: &str, description: &str, default: bool) -> Field {
Field::new(path, label, description, Kind::Toggle(default))
}
pub fn range(
path: impl Into<String>,
label: impl Into<String>,
description: impl Into<String>,
default: f64,
min: f64,
max: f64,
step: f64,
suffix: &'static str,
integer: bool,
) -> Field {
Field::new(
path,
label,
description,
Kind::Range {
default,
min,
max,
step,
suffix,
integer,
},
)
}
pub fn text(
path: impl Into<String>,
label: impl Into<String>,
description: impl Into<String>,
default: &'static str,
) -> Field {
Field::new(
path,
label,
description,
Kind::Text {
default,
argv: false,
},
)
}
fn choice(
path: &str,
label: &str,
description: &str,
default: &'static str,
choices: &'static [(&'static str, &'static str)],
) -> 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.",
64.0,
0.0,
1000.0,
1.0,
" px",
true,
),
range(
"desktop_widgets.clock.margin_y",
"Vertical margin",
"Distance from top/bottom edge; ignored when vertically centered.",
80.0,
0.0,
1000.0,
1.0,
" px",
true,
),
range(
"desktop_widgets.clock.width",
"Widget width",
"Logical pixels; increase for longer formats.",
440.0,
64.0,
1600.0,
1.0,
" px",
true,
),
range(
"desktop_widgets.clock.height",
"Widget height",
"Leave room for both text lines.",
320.0,
32.0,
800.0,
1.0,
" px",
true,
),
range(
"desktop_widgets.clock.time_size",
"Time size",
"Logical pixels; scales with your output.",
128.0,
8.0,
240.0,
1.0,
" px",
false,
),
range(
"desktop_widgets.clock.date_size",
"Date size",
"Separate size for the date line.",
18.0,
8.0,
96.0,
1.0,
" px",
false,
),
range(
"desktop_widgets.clock.opacity",
"Opacity",
"Applies to text and the optional background.",
0.9,
0.0,
1.0,
0.05,
"",
false,
),
range(
"desktop_widgets.clock.gap",
"Line spacing",
"Space between time and date.",
4.0,
0.0,
64.0,
1.0,
" px",
false,
),
range(
"desktop_widgets.clock.padding",
"Inner padding",
"Space around the labels.",
12.0,
0.0,
64.0,
1.0,
" px",
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.",
320.0,
120.0,
1200.0,
1.0,
" px",
true,
),
range(
format!("{prefix}.height"),
"Height",
"Logical pixels; centered axes ignore margins.",
240.0,
80.0,
1200.0,
1.0,
" px",
true,
),
range(
format!("{prefix}.margin_x"),
"Horizontal margin",
"Logical pixels; centered axes ignore margins.",
48.0,
0.0,
1000.0,
1.0,
" px",
true,
),
range(
format!("{prefix}.margin_y"),
"Vertical margin",
"Logical pixels; centered axes ignore margins.",
80.0,
0.0,
1000.0,
1.0,
" px",
true,
),
range(
format!("{prefix}.text_size"),
"Text size",
"Logical pixels; centered axes ignore margins.",
16.0,
8.0,
96.0,
1.0,
" px",
false,
),
range(
format!("{prefix}.title_size"),
"Title size",
"Logical pixels; centered axes ignore margins.",
18.0,
8.0,
96.0,
1.0,
" px",
false,
),
range(
format!("{prefix}.opacity"),
"Opacity",
"Logical pixels; centered axes ignore margins.",
0.9,
0.0,
1.0,
0.05,
"",
false,
),
range(
format!("{prefix}.padding"),
"Padding",
"Logical pixels; centered axes ignore margins.",
20.0,
0.0,
64.0,
1.0,
" px",
false,
),
range(
format!("{prefix}.gap"),
"Title spacing",
"Logical pixels; centered axes ignore margins.",
8.0,
0.0,
64.0,
1.0,
" px",
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::Appearance => vec![
range(
"theme.geometry.shell_radius",
"Shell corner radius",
"Rounds the bar, menus, notifications, desktop widgets and hover backgrounds. Window corners are separate.",
14.0,
0.0,
32.0,
1.0,
" px",
false,
),
text(
"theme.colors.accent",
"Accent color",
"Focus rings and selected controls. Use a hex color.",
"#3D7BE6",
),
text(
"theme.colors.surface_base",
"Surface color",
"The base color for desktop surfaces.",
"#111821",
),
choice(
"theme.material.style",
"Surface style",
"Choose the finish for the bar and menus.",
"solid",
&[("solid", "Solid"), ("translucent", "Translucent")],
),
range(
"theme.material.blur_radius",
"Background blur",
"Softness behind translucent surfaces.",
12.0,
0.0,
32.0,
1.0,
" px",
false,
),
text(
"theme.typography.font_family",
"Interface font",
"Use the name of an installed font family.",
"Inter",
),
range(
"theme.geometry.focus_ring_width",
"Focus border",
"A subtle outline around the active window.",
2.0,
0.0,
4.0,
0.5,
" px",
false,
),
],
Page::Wallpaper => vec![
text(
"theme.background.path",
"Image location",
"Choose an image above, or enter its full path.",
ferese_config::default_wallpaper(),
),
choice(
"theme.background.mode",
"Image placement",
"Fill crops to the screen. Fit keeps the whole image.",
"fill",
&[("fill", "Fill"), ("fit", "Fit")],
),
],
Page::Desktop => clock_fields(),
Page::Notifications => vec![
toggle(
"notifications.show_popups",
"Show popup notifications",
"Keep notifications in history when popups are hidden.",
true,
),
toggle(
"notifications.do_not_disturb",
"Do Not Disturb",
"Silence normal popups; critical notifications can still appear.",
false,
),
range(
"notifications.timeout_ms",
"Popup timeout",
"Default duration; apps can request another timeout. Pauses while hovered.",
6000.0,
1000.0,
30000.0,
1000.0,
" ms",
true,
),
],
Page::Bar => vec![
range(
"theme.geometry.top_bar_height",
"Height",
"Keeps icon and text sizes unchanged.",
28.0,
24.0,
48.0,
1.0,
" px",
false,
),
range(
"theme.geometry.top_bar_margin_top",
"Top margin",
"Set to zero for an edge-to-edge bar.",
0.0,
0.0,
24.0,
1.0,
" px",
true,
),
range(
"theme.geometry.top_bar_margin_horizontal",
"Side margins",
"Space between the bar and the display edges.",
0.0,
0.0,
32.0,
1.0,
" px",
true,
),
range(
"theme.surface.bar.opacity",
"Opacity",
"Lower values reveal more of the background.",
0.78,
0.2,
1.0,
0.01,
"",
false,
),
range(
"theme.geometry.top_bar_window_gap",
"Window clearance",
"Space below the menu bar.",
0.0,
0.0,
24.0,
1.0,
" px",
true,
),
toggle(
"status.battery_percentage",
"Battery percentage",
"Show the remaining charge beside the battery icon.",
true,
),
],
Page::Windows => vec![
choice(
"layout.mode",
"Layout for new workspaces",
"Existing workspaces keep their current layout.",
"scrolling",
&[("scrolling", "Scrolling"), ("tree", "Tiling")],
),
choice(
"scrolling.focus_strategy",
"Scroll behavior",
"How the viewport follows the focused column.",
"minimal",
&[
("minimal", "Minimal"),
("center_on_focus", "Centered"),
("paged", "Paged"),
],
),
range(
"scrolling.default_column_width",
"New window width",
"Fraction of the available workspace.",
0.5,
0.25,
1.0,
0.05,
"",
false,
),
range(
"layout.inner_gap",
"Between windows",
"Space between neighboring windows.",
10.0,
0.0,
32.0,
1.0,
" px",
false,
),
range(
"layout.outer_gap",
"Workspace edges",
"Space around the outside of the layout.",
4.0,
0.0,
32.0,
1.0,
" px",
false,
),
range(
"theme.geometry.window_radius",
"Corner radius",
"Rounded corners for application windows.",
14.0,
0.0,
28.0,
1.0,
" px",
false,
),
toggle(
"appearance.inactive_dim.enabled",
"Dim inactive windows",
"Keep attention on the window you are using.",
false,
),
range(
"appearance.inactive_dim.amount",
"Dimming amount",
"How much inactive windows recede.",
0.15,
0.0,
0.5,
0.01,
"",
false,
),
],
Page::Motion => vec![
toggle(
"animations.enabled",
"Animations",
"Animate window changes and shell transitions.",
true,
),
range(
"animations.speed",
"Animation speed",
"Lower values give transitions more time.",
1.0,
0.25,
2.0,
0.05,
"×",
false,
),
toggle(
"animations.reduced_motion",
"Reduce motion",
"Use immediate changes in place of animations.",
false,
),
range(
"appearance.inactive_dim.duration_ms",
"Focus transition",
"Time taken to dim an inactive window.",
150.0,
0.0,
600.0,
10.0,
" ms",
false,
),
],
Page::Keyboard => vec![
text(
"input.xkb_layout",
"Keyboard layouts",
"Layout codes, separated by commas. For example: us,ru.",
"us",
),
text(
"input.xkb_variant",
"Layout variant",
"Leave empty to use the standard variant.",
"",
),
range(
"input.repeat_rate",
"Key repeat",
"Characters repeated each second while a key is held.",
25.0,
1.0,
70.0,
1.0,
" /s",
true,
),
range(
"input.repeat_delay_ms",
"Delay before repeat",
"How long to hold a key before it starts repeating.",
600.0,
150.0,
1000.0,
25.0,
" ms",
true,
),
toggle(
"input.focus_follows_mouse",
"Focus follows pointer",
"Focus windows as the pointer moves over them.",
false,
),
toggle(
"input.touchpad.tap",
"Tap to click",
"Tap the touchpad to click. Updates connected devices immediately.",
true,
),
toggle(
"input.touchpad.natural_scroll",
"Natural scrolling",
"Move content in the direction of your fingers. Updates immediately.",
true,
),
toggle(
"input.touchpad.disable_while_typing",
"Ignore touchpad while typing",
"Avoid accidental pointer movement.",
true,
),
range(
"input.touchpad.swipe_threshold",
"Swipe distance",
"Distance needed to navigate. Larger values reduce accidental swipes.",
80.0,
16.0,
1000.0,
8.0,
" px",
true,
),
],
Page::Shortcuts | Page::Startup | Page::Displays => vec![],
}
}