fix clippy warnings
This commit is contained in:
1 parent
7b88083020
commit
bdc3ba435f
39 files changed
+825
-688
No files matched your search
@@ -169,6 +169,35 @@ fn account_picture(user: &str) -> Option<image::Handle> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_wallpaper(path: &std::path::Path, blur: f32) -> Result<image::Handle, String> {
|
||||
use ::image::ImageDecoder;
|
||||
let mut reader = ::image::ImageReader::open(path)
|
||||
.map_err(|e| e.to_string())?
|
||||
.with_guessed_format()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut limits = ::image::Limits::default();
|
||||
limits.max_alloc = Some(256 * 1024 * 1024);
|
||||
reader.limits(limits);
|
||||
let decoder = reader.into_decoder().map_err(|e| e.to_string())?;
|
||||
let (width, height) = decoder.dimensions();
|
||||
if u64::from(width) * u64::from(height) * 4 > 256 * 1024 * 1024 {
|
||||
return Err("Lock wallpaper exceeds the decode limit".into());
|
||||
}
|
||||
let pixels = ::image::DynamicImage::from_decoder(decoder)
|
||||
.map_err(|e| e.to_string())?
|
||||
.into_rgba8();
|
||||
let pixels = if blur > 0. {
|
||||
::image::imageops::blur(&::image::imageops::thumbnail(&pixels, 640, 640), blur)
|
||||
} else {
|
||||
pixels
|
||||
};
|
||||
Ok(image::Handle::from_rgba(
|
||||
pixels.width(),
|
||||
pixels.height(),
|
||||
pixels.into_raw(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -215,39 +244,10 @@ mod tests {
|
||||
appearance.accent,
|
||||
ferese_theme::parse_color(&theme.tokens.colors.accent).unwrap()
|
||||
);
|
||||
assert!(ferese_theme::contrast(appearance.accent.into(), appearance.on_accent.into()) >= 4.5);
|
||||
assert!(ferese_theme::contrast(appearance.accent, appearance.on_accent) >= 4.5);
|
||||
assert!(!appearance.show_clock && !appearance.show_date);
|
||||
assert_eq!(appearance.clock_format(), "%I:%M %p");
|
||||
assert_eq!(appearance.dim, 0.7);
|
||||
assert_eq!(appearance.theme().cosmic().corner_radii.radius_m, [0.; 4]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_wallpaper(path: &std::path::Path, blur: f32) -> Result<image::Handle, String> {
|
||||
use ::image::ImageDecoder;
|
||||
let mut reader = ::image::ImageReader::open(path)
|
||||
.map_err(|e| e.to_string())?
|
||||
.with_guessed_format()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut limits = ::image::Limits::default();
|
||||
limits.max_alloc = Some(256 * 1024 * 1024);
|
||||
reader.limits(limits);
|
||||
let decoder = reader.into_decoder().map_err(|e| e.to_string())?;
|
||||
let (width, height) = decoder.dimensions();
|
||||
if u64::from(width) * u64::from(height) * 4 > 256 * 1024 * 1024 {
|
||||
return Err("Lock wallpaper exceeds the decode limit".into());
|
||||
}
|
||||
let pixels = ::image::DynamicImage::from_decoder(decoder)
|
||||
.map_err(|e| e.to_string())?
|
||||
.into_rgba8();
|
||||
let pixels = if blur > 0. {
|
||||
::image::imageops::blur(&::image::imageops::thumbnail(&pixels, 640, 640), blur)
|
||||
} else {
|
||||
pixels
|
||||
};
|
||||
Ok(image::Handle::from_rgba(
|
||||
pixels.width(),
|
||||
pixels.height(),
|
||||
pixels.into_raw(),
|
||||
))
|
||||
}
|
||||
@@ -82,7 +82,7 @@ fn main() -> iced::Result {
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Message {
|
||||
ThemeChanged(ferese_config::theme::Snapshot),
|
||||
ThemeChanged(Box<ferese_config::theme::Snapshot>),
|
||||
WallpaperLoaded(u64, Result<widget::image::Handle, String>),
|
||||
Event(Box<Event>),
|
||||
Input(String),
|
||||
@@ -193,7 +193,7 @@ impl cosmic::Application for Locker {
|
||||
if self.preview {
|
||||
Subscription::none()
|
||||
} else {
|
||||
ferese_theme::service::subscription().map(Message::ThemeChanged)
|
||||
ferese_theme::service::subscription().map(|snapshot| Message::ThemeChanged(Box::new(snapshot)))
|
||||
},
|
||||
event::listen_with(|event, _, _| match event {
|
||||
Event::PlatformSpecific(PlatformSpecific::Wayland(
|
||||
|
||||
@@ -52,7 +52,7 @@ enum Message {
|
||||
RefreshDisplays,
|
||||
DisplaysLoaded(Result<Vec<displays::Display>, String>),
|
||||
RefreshRate(String, displays::Mode, bool),
|
||||
ThemeChanged(ferese_config::theme::Snapshot),
|
||||
ThemeChanged(Box<ferese_config::theme::Snapshot>),
|
||||
DragWindow,
|
||||
Page(Page),
|
||||
Search(String),
|
||||
@@ -207,7 +207,7 @@ impl cosmic::Application for App {
|
||||
return self.edit_many(displays::edits(&prefix, mode, automatic));
|
||||
}
|
||||
Message::ThemeChanged(snapshot) => {
|
||||
self.resolved = snapshot;
|
||||
self.resolved = *snapshot;
|
||||
self.font = ferese_theme::font(Some(&self.resolved.presented.tokens.typography.font_family));
|
||||
return self.update_theme();
|
||||
}
|
||||
@@ -596,8 +596,8 @@ impl cosmic::Application for App {
|
||||
|
||||
fn subscription(&self) -> cosmic::iced::Subscription<Message> {
|
||||
cosmic::iced::Subscription::batch([
|
||||
cosmic::iced::Subscription::run_with(self.path.clone(), watch::changes),
|
||||
ferese_theme::service::subscription().map(Message::ThemeChanged),
|
||||
cosmic::iced::Subscription::run_with(self.path.clone(), |path| watch::changes(path)),
|
||||
ferese_theme::service::subscription().map(|snapshot| Message::ThemeChanged(Box::new(snapshot))),
|
||||
if self.page == Page::Displays {
|
||||
cosmic::iced::time::every(std::time::Duration::from_secs(2)).map(|_| Message::RefreshDisplays)
|
||||
} else {
|
||||
|
||||
@@ -412,12 +412,14 @@ impl App {
|
||||
format!("{prefix}.scale"),
|
||||
"Scale",
|
||||
"Logical size of text and controls.",
|
||||
1.,
|
||||
0.75,
|
||||
3.,
|
||||
0.25,
|
||||
"×",
|
||||
false,
|
||||
schema::RangeSpec {
|
||||
default: 1.,
|
||||
min: 0.75,
|
||||
max: 3.,
|
||||
step: 0.25,
|
||||
suffix: "×",
|
||||
integer: false,
|
||||
},
|
||||
)));
|
||||
body = body.push(container(group).padding(8).class(visuals::surface(palette.card, 14.)));
|
||||
}
|
||||
|
||||
+347
-252
@@ -148,17 +148,30 @@ fn toggle(path: &str, label: &str, description: &str, default: bool) -> Field {
|
||||
Field::new(path, label, description, Kind::Toggle(default))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RangeSpec {
|
||||
pub default: f64,
|
||||
pub min: f64,
|
||||
pub max: f64,
|
||||
pub step: f64,
|
||||
pub suffix: &'static str,
|
||||
pub integer: bool,
|
||||
}
|
||||
|
||||
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,
|
||||
spec: RangeSpec,
|
||||
) -> Field {
|
||||
let RangeSpec {
|
||||
default,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
suffix,
|
||||
integer,
|
||||
} = spec;
|
||||
Field::new(
|
||||
path,
|
||||
label,
|
||||
@@ -278,100 +291,118 @@ fn clock_fields() -> Vec<Field> {
|
||||
"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,
|
||||
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.",
|
||||
80.0,
|
||||
0.0,
|
||||
1000.0,
|
||||
1.0,
|
||||
" px",
|
||||
true,
|
||||
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.",
|
||||
440.0,
|
||||
64.0,
|
||||
1600.0,
|
||||
1.0,
|
||||
" px",
|
||||
true,
|
||||
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.",
|
||||
320.0,
|
||||
32.0,
|
||||
800.0,
|
||||
1.0,
|
||||
" px",
|
||||
true,
|
||||
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.",
|
||||
128.0,
|
||||
8.0,
|
||||
240.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
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.",
|
||||
18.0,
|
||||
8.0,
|
||||
96.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
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.",
|
||||
0.9,
|
||||
0.0,
|
||||
1.0,
|
||||
0.05,
|
||||
"",
|
||||
false,
|
||||
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.",
|
||||
4.0,
|
||||
0.0,
|
||||
64.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
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.",
|
||||
12.0,
|
||||
0.0,
|
||||
64.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 12.0,
|
||||
min: 0.0,
|
||||
max: 64.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
choice(
|
||||
"desktop_widgets.clock.alignment",
|
||||
@@ -422,100 +453,118 @@ pub fn note_fields(index: usize) -> Vec<Field> {
|
||||
format!("{prefix}.width"),
|
||||
"Width",
|
||||
"Logical pixels; centered axes ignore margins.",
|
||||
320.0,
|
||||
120.0,
|
||||
1200.0,
|
||||
1.0,
|
||||
" px",
|
||||
true,
|
||||
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.",
|
||||
240.0,
|
||||
80.0,
|
||||
1200.0,
|
||||
1.0,
|
||||
" px",
|
||||
true,
|
||||
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.",
|
||||
48.0,
|
||||
0.0,
|
||||
1000.0,
|
||||
1.0,
|
||||
" px",
|
||||
true,
|
||||
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.",
|
||||
80.0,
|
||||
0.0,
|
||||
1000.0,
|
||||
1.0,
|
||||
" px",
|
||||
true,
|
||||
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.",
|
||||
16.0,
|
||||
8.0,
|
||||
96.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
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.",
|
||||
18.0,
|
||||
8.0,
|
||||
96.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
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.",
|
||||
0.9,
|
||||
0.0,
|
||||
1.0,
|
||||
0.05,
|
||||
"",
|
||||
false,
|
||||
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.",
|
||||
20.0,
|
||||
0.0,
|
||||
64.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
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.",
|
||||
8.0,
|
||||
0.0,
|
||||
64.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 8.0,
|
||||
min: 0.0,
|
||||
max: 64.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
Field::new(
|
||||
format!("{prefix}.alignment"),
|
||||
@@ -536,23 +585,27 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"lock_screen.dim_after_seconds",
|
||||
"Dim after",
|
||||
"Dim the locked display after inactivity. Zero disables dimming.",
|
||||
30.0,
|
||||
0.0,
|
||||
600.0,
|
||||
5.0,
|
||||
"s",
|
||||
true,
|
||||
RangeSpec {
|
||||
default: 30.0,
|
||||
min: 0.0,
|
||||
max: 600.0,
|
||||
step: 5.0,
|
||||
suffix: "s",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"lock_screen.sleep_after_seconds",
|
||||
"Display sleep after",
|
||||
"Turn off locked displays after inactivity. Input wakes them; zero disables display sleep.",
|
||||
120.0,
|
||||
0.0,
|
||||
1800.0,
|
||||
15.0,
|
||||
"s",
|
||||
true,
|
||||
RangeSpec {
|
||||
default: 120.0,
|
||||
min: 0.0,
|
||||
max: 1800.0,
|
||||
step: 15.0,
|
||||
suffix: "s",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
toggle(
|
||||
"lock_screen.show_clock",
|
||||
@@ -577,23 +630,27 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"lock_screen.background_blur",
|
||||
"Wallpaper softness",
|
||||
"Blur the lock screen wallpaper. Zero keeps the original image sharp.",
|
||||
18.0,
|
||||
0.0,
|
||||
40.0,
|
||||
1.0,
|
||||
"",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 18.0,
|
||||
min: 0.0,
|
||||
max: 40.0,
|
||||
step: 1.0,
|
||||
suffix: "",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"lock_screen.background_dim",
|
||||
"Wallpaper dimming",
|
||||
"Darken the wallpaper behind the clock and sign-in card.",
|
||||
0.48,
|
||||
0.0,
|
||||
0.9,
|
||||
0.02,
|
||||
"",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 0.48,
|
||||
min: 0.0,
|
||||
max: 0.9,
|
||||
step: 0.02,
|
||||
suffix: "",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
],
|
||||
Page::Accessibility => vec![
|
||||
@@ -664,12 +721,14 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"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,
|
||||
RangeSpec {
|
||||
default: 14.0,
|
||||
min: 0.0,
|
||||
max: 32.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
text(
|
||||
"theme.accent",
|
||||
@@ -688,23 +747,27 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"theme.material.opacity",
|
||||
"Shell opacity",
|
||||
"Background opacity for the bar, popovers, menus, notifications and sharing dialog. Text and icons stay opaque.",
|
||||
ferese_config::DEFAULT_MATERIAL_OPACITY,
|
||||
0.0,
|
||||
1.0,
|
||||
0.01,
|
||||
"",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: ferese_config::DEFAULT_MATERIAL_OPACITY,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
suffix: "",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"theme.material.blur_radius",
|
||||
"Background blur",
|
||||
"Softness behind translucent surfaces.",
|
||||
12.0,
|
||||
0.0,
|
||||
32.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 12.0,
|
||||
min: 0.0,
|
||||
max: 32.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
text(
|
||||
"theme.typography.font_family",
|
||||
@@ -716,12 +779,14 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"theme.geometry.focus_ring_width",
|
||||
"Focus border",
|
||||
"A subtle outline around the active window.",
|
||||
2.0,
|
||||
0.0,
|
||||
4.0,
|
||||
0.5,
|
||||
" px",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 2.0,
|
||||
min: 0.0,
|
||||
max: 4.0,
|
||||
step: 0.5,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
],
|
||||
Page::Wallpaper => vec![
|
||||
@@ -757,12 +822,14 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"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,
|
||||
RangeSpec {
|
||||
default: 6000.0,
|
||||
min: 1000.0,
|
||||
max: 30000.0,
|
||||
step: 1000.0,
|
||||
suffix: " ms",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
],
|
||||
Page::Bar => vec![
|
||||
@@ -776,45 +843,53 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"theme.geometry.top_bar_height",
|
||||
"Height",
|
||||
"Keeps icon and text sizes unchanged.",
|
||||
28.0,
|
||||
24.0,
|
||||
48.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 28.0,
|
||||
min: 24.0,
|
||||
max: 48.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: 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,
|
||||
RangeSpec {
|
||||
default: 0.0,
|
||||
min: 0.0,
|
||||
max: 24.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: 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,
|
||||
RangeSpec {
|
||||
default: 0.0,
|
||||
min: 0.0,
|
||||
max: 32.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"theme.geometry.top_bar_window_gap",
|
||||
"Window clearance",
|
||||
"Space below the menu bar.",
|
||||
0.0,
|
||||
0.0,
|
||||
24.0,
|
||||
1.0,
|
||||
" px",
|
||||
true,
|
||||
RangeSpec {
|
||||
default: 0.0,
|
||||
min: 0.0,
|
||||
max: 24.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
toggle(
|
||||
"status.battery_percentage",
|
||||
@@ -852,45 +927,53 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"scrolling.default_column_width",
|
||||
"New window width",
|
||||
"Fraction of the available workspace.",
|
||||
0.5,
|
||||
0.25,
|
||||
1.0,
|
||||
0.05,
|
||||
"",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 0.5,
|
||||
min: 0.25,
|
||||
max: 1.0,
|
||||
step: 0.05,
|
||||
suffix: "",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"layout.inner_gap",
|
||||
"Between windows",
|
||||
"Space between neighboring windows.",
|
||||
10.0,
|
||||
0.0,
|
||||
32.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 10.0,
|
||||
min: 0.0,
|
||||
max: 32.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"layout.outer_gap",
|
||||
"Workspace edges",
|
||||
"Space around the outside of the layout.",
|
||||
4.0,
|
||||
0.0,
|
||||
32.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 4.0,
|
||||
min: 0.0,
|
||||
max: 32.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
range(
|
||||
"theme.geometry.window_radius",
|
||||
"Corner radius",
|
||||
"Rounded corners for application windows.",
|
||||
14.0,
|
||||
0.0,
|
||||
28.0,
|
||||
1.0,
|
||||
" px",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 14.0,
|
||||
min: 0.0,
|
||||
max: 28.0,
|
||||
step: 1.0,
|
||||
suffix: " px",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
toggle(
|
||||
"appearance.inactive_dim.enabled",
|
||||
@@ -902,12 +985,14 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"appearance.inactive_dim.amount",
|
||||
"Dimming amount",
|
||||
"How much inactive windows recede.",
|
||||
0.15,
|
||||
0.0,
|
||||
0.5,
|
||||
0.01,
|
||||
"",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 0.15,
|
||||
min: 0.0,
|
||||
max: 0.5,
|
||||
step: 0.01,
|
||||
suffix: "",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
],
|
||||
Page::Motion => vec![
|
||||
@@ -921,23 +1006,27 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"animations.speed",
|
||||
"Animation speed",
|
||||
"Lower values give transitions more time.",
|
||||
1.0,
|
||||
0.25,
|
||||
2.0,
|
||||
0.05,
|
||||
"×",
|
||||
false,
|
||||
RangeSpec {
|
||||
default: 1.0,
|
||||
min: 0.25,
|
||||
max: 2.0,
|
||||
step: 0.05,
|
||||
suffix: "×",
|
||||
integer: 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,
|
||||
RangeSpec {
|
||||
default: 150.0,
|
||||
min: 0.0,
|
||||
max: 600.0,
|
||||
step: 10.0,
|
||||
suffix: " ms",
|
||||
integer: false,
|
||||
},
|
||||
),
|
||||
],
|
||||
Page::Keyboard => vec![
|
||||
@@ -957,23 +1046,27 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"input.repeat_rate",
|
||||
"Key repeat",
|
||||
"Characters repeated each second while a key is held.",
|
||||
25.0,
|
||||
1.0,
|
||||
70.0,
|
||||
1.0,
|
||||
" /s",
|
||||
true,
|
||||
RangeSpec {
|
||||
default: 25.0,
|
||||
min: 1.0,
|
||||
max: 70.0,
|
||||
step: 1.0,
|
||||
suffix: " /s",
|
||||
integer: 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,
|
||||
RangeSpec {
|
||||
default: 600.0,
|
||||
min: 150.0,
|
||||
max: 1000.0,
|
||||
step: 25.0,
|
||||
suffix: " ms",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
toggle(
|
||||
"input.focus_follows_mouse",
|
||||
@@ -1003,12 +1096,14 @@ pub fn fields(page: Page) -> Vec<Field> {
|
||||
"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,
|
||||
RangeSpec {
|
||||
default: 80.0,
|
||||
min: 16.0,
|
||||
max: 1000.0,
|
||||
step: 8.0,
|
||||
suffix: " px",
|
||||
integer: true,
|
||||
},
|
||||
),
|
||||
],
|
||||
Page::Shortcuts => vec![toggle(
|
||||
|
||||
@@ -199,12 +199,14 @@ impl App {
|
||||
let tile_id = gallery_id(&id, appearance);
|
||||
tiles = tiles.push(gallery::tile(
|
||||
family,
|
||||
appearance,
|
||||
active,
|
||||
selected,
|
||||
palette,
|
||||
self.font,
|
||||
tile_id,
|
||||
gallery::TileOptions {
|
||||
variant: appearance,
|
||||
active,
|
||||
selected,
|
||||
palette,
|
||||
font: self.font,
|
||||
id: tile_id,
|
||||
},
|
||||
Message::Family(id, appearance),
|
||||
move |key| {
|
||||
gallery::neighbor(index, ids.len(), key)
|
||||
|
||||
@@ -294,9 +294,11 @@ mod tests {
|
||||
fn presets_are_distinct_and_readable_on_settings_surfaces() {
|
||||
for (index, preset) in PRESETS.iter().enumerate() {
|
||||
let tokens = ferese_config::theme::preset(preset.id, preset.appearance).unwrap();
|
||||
let mut theme = ferese_config::theme::ResolvedTheme::default();
|
||||
theme.appearance = preset.appearance;
|
||||
theme.tokens = tokens;
|
||||
let theme = ferese_config::theme::ResolvedTheme {
|
||||
appearance: preset.appearance,
|
||||
tokens,
|
||||
..Default::default()
|
||||
};
|
||||
let palette = Palette::from_resolved(&theme);
|
||||
for background in [palette.background, palette.sidebar, palette.card] {
|
||||
assert!(contrast(palette.text, background) >= 4.5, "{} text", preset.name);
|
||||
@@ -313,10 +315,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn selection_tracks_all_preset_colors_and_gradient_changes() {
|
||||
for index in 0..PRESETS.len() {
|
||||
for (index, preset) in PRESETS.iter().enumerate() {
|
||||
let mut snapshot = configured_preset(index);
|
||||
assert!(preset_selected(&snapshot, index));
|
||||
let kind = if PRESETS[index].appearance == ferese_config::theme::Appearance::Light {
|
||||
let kind = if preset.appearance == ferese_config::theme::Appearance::Light {
|
||||
"light"
|
||||
} else {
|
||||
"dark"
|
||||
@@ -343,9 +345,11 @@ mod tests {
|
||||
|
||||
for (index, item) in PRESETS.iter().enumerate() {
|
||||
let _ = index;
|
||||
let mut resolved = ferese_config::theme::ResolvedTheme::default();
|
||||
resolved.appearance = item.appearance;
|
||||
resolved.tokens = ferese_config::theme::preset(item.id, item.appearance).unwrap();
|
||||
let resolved = ferese_config::theme::ResolvedTheme {
|
||||
appearance: item.appearance,
|
||||
tokens: ferese_config::theme::preset(item.id, item.appearance).unwrap(),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
Palette::from_resolved(&resolved).native_theme().cosmic().is_dark,
|
||||
item.appearance == ferese_config::theme::Appearance::Dark
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Watch the parent directory so atomic editor saves keep working.
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use cosmic::iced::futures::{SinkExt, Stream};
|
||||
@@ -8,8 +8,8 @@ use notify::{RecursiveMode, Watcher};
|
||||
use crate::Message;
|
||||
use crate::store::Snapshot;
|
||||
|
||||
pub fn changes(path: &PathBuf) -> impl Stream<Item = Message> + use<> {
|
||||
let path = path.clone();
|
||||
pub fn changes(path: &Path) -> impl Stream<Item = Message> + use<> {
|
||||
let path = path.to_path_buf();
|
||||
|
||||
cosmic::iced::stream::channel(1, async move |mut output| {
|
||||
let (send, mut events) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
@@ -37,7 +37,6 @@ pub(crate) struct WorkspaceSnapshot {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct WindowSnapshot {
|
||||
pub(crate) id: u64,
|
||||
pub(crate) workspace: u64,
|
||||
pub(crate) app_id: String,
|
||||
pub(crate) title: String,
|
||||
@@ -138,12 +137,6 @@ impl ShellControl {
|
||||
let _ = self.connection.flush();
|
||||
}
|
||||
|
||||
pub(crate) fn activate_window(&self, id: u64) {
|
||||
let (hi, lo) = split_id(id);
|
||||
self.shell.activate_window(hi, lo);
|
||||
let _ = self.connection.flush();
|
||||
}
|
||||
|
||||
pub(crate) fn confirm_logout(&self, serial: u32, revision: u32, token: u32, force: bool) {
|
||||
self.shell
|
||||
.confirm_logout_with_inhibitors(serial, revision, token, u32::from(force));
|
||||
@@ -268,8 +261,8 @@ impl Dispatch<FereseShellV1, ()> for ControlState {
|
||||
});
|
||||
}
|
||||
ferese_shell_v1::Event::Window {
|
||||
window_hi,
|
||||
window_lo,
|
||||
window_hi: _,
|
||||
window_lo: _,
|
||||
workspace_hi,
|
||||
workspace_lo,
|
||||
app_id,
|
||||
@@ -285,7 +278,6 @@ impl Dispatch<FereseShellV1, ()> for ControlState {
|
||||
};
|
||||
|
||||
state.pending.windows.push(WindowSnapshot {
|
||||
id: join_id(window_hi, window_lo),
|
||||
workspace: join_id(workspace_hi, workspace_lo),
|
||||
app_id,
|
||||
title,
|
||||
|
||||
@@ -221,7 +221,7 @@ struct OutputSurfaces {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Message {
|
||||
ThemeChanged(ferese_config::theme::Snapshot),
|
||||
ThemeChanged(Box<ferese_config::theme::Snapshot>),
|
||||
ThemeMode(ferese_config::theme::Mode),
|
||||
ThemeModeSet(Result<(), String>),
|
||||
BeginNoteEdit(String),
|
||||
@@ -340,7 +340,7 @@ impl cosmic::Application for FereseShell {
|
||||
|
||||
fn subscription(&self) -> Subscription<Self::Message> {
|
||||
Subscription::batch([
|
||||
ferese_theme::service::subscription().map(Message::ThemeChanged),
|
||||
ferese_theme::service::subscription().map(|snapshot| Message::ThemeChanged(Box::new(snapshot))),
|
||||
if self
|
||||
.system_modal
|
||||
.as_ref()
|
||||
@@ -1208,7 +1208,6 @@ mod tests {
|
||||
}],
|
||||
workspaces: Vec::new(),
|
||||
windows: vec![WindowSnapshot {
|
||||
id: 1,
|
||||
workspace,
|
||||
app_id: "dev.ferese.Test".to_owned(),
|
||||
title: "Test".to_owned(),
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::bar::status_icon;
|
||||
use super::controls::status_summary;
|
||||
use super::{Menu, MenuRows, MenuStyle};
|
||||
use crate::status::Action;
|
||||
use crate::{FereseShell, Message, color_with_opacity, text};
|
||||
use crate::{FereseShell, Message, text};
|
||||
|
||||
const DEVICE_LIST_HEIGHT: f32 = 180.0;
|
||||
|
||||
@@ -15,12 +15,7 @@ fn device_list_height(count: usize) -> Option<f32> {
|
||||
}
|
||||
|
||||
pub(super) fn view<'a>(shell: &'a FereseShell, mut rows: MenuRows<'a>, style: MenuStyle) -> MenuRows<'a> {
|
||||
let MenuStyle {
|
||||
theme,
|
||||
primary,
|
||||
muted,
|
||||
opacity: p,
|
||||
} = style;
|
||||
let MenuStyle { muted, .. } = style;
|
||||
if let Some(b) = &shell.status.bluetooth {
|
||||
rows = rows.push(status_summary(
|
||||
status_icon(Menu::Bluetooth, &shell.status).0,
|
||||
@@ -36,10 +31,7 @@ pub(super) fn view<'a>(shell: &'a FereseShell, mut rows: MenuRows<'a>, style: Me
|
||||
} else {
|
||||
"Your connected devices"
|
||||
},
|
||||
primary,
|
||||
muted,
|
||||
color_with_opacity(theme.accent, p),
|
||||
p,
|
||||
style,
|
||||
b.enabled,
|
||||
Some((b.enabled, Action::Bluetooth(!b.enabled))),
|
||||
));
|
||||
|
||||
@@ -3,19 +3,23 @@ use cosmic::widget::{button, column, container, row, slider};
|
||||
use cosmic::{Element, theme};
|
||||
|
||||
use crate::status::Action;
|
||||
use crate::{Message, ShellTheme, accented_icon, color, motion, shell_font, text};
|
||||
use crate::{Message, ShellTheme, accented_icon, color, color_with_opacity, motion, shell_font, text};
|
||||
|
||||
pub(super) fn status_summary<'a>(
|
||||
source: &'static [u8],
|
||||
title: &'a str,
|
||||
subtitle: &'a str,
|
||||
primary: Color,
|
||||
muted: Color,
|
||||
accent: Color,
|
||||
opacity: f32,
|
||||
style: super::MenuStyle,
|
||||
enabled: bool,
|
||||
toggle: Option<(bool, Action)>,
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
let super::MenuStyle {
|
||||
theme,
|
||||
primary,
|
||||
muted,
|
||||
opacity,
|
||||
} = style;
|
||||
let accent = color_with_opacity(theme.accent, opacity);
|
||||
let badge_color = if enabled { accent } else { primary };
|
||||
let badge = container(accented_icon(source, 22, primary, accent))
|
||||
.width(32)
|
||||
|
||||
@@ -5,14 +5,11 @@ use super::bar::status_icon;
|
||||
use super::controls::{level_meter, shell_switch, status_summary};
|
||||
use super::{Menu, MenuRows, MenuStyle};
|
||||
use crate::status::Action;
|
||||
use crate::{FereseShell, color_with_opacity, text};
|
||||
use crate::{FereseShell, text};
|
||||
|
||||
pub(super) fn view<'a>(shell: &'a FereseShell, mut rows: MenuRows<'a>, style: MenuStyle) -> MenuRows<'a> {
|
||||
let MenuStyle {
|
||||
theme,
|
||||
primary,
|
||||
muted,
|
||||
opacity: p,
|
||||
primary, opacity: p, ..
|
||||
} = style;
|
||||
if let Some(n) = &shell.status.network {
|
||||
let connected = n.enabled && n.connection.is_some();
|
||||
@@ -30,10 +27,7 @@ pub(super) fn view<'a>(shell: &'a FereseShell, mut rows: MenuRows<'a>, style: Me
|
||||
} else {
|
||||
"Wireless connections are paused"
|
||||
},
|
||||
primary,
|
||||
muted,
|
||||
color_with_opacity(theme.accent, p),
|
||||
p,
|
||||
style,
|
||||
n.enabled,
|
||||
None,
|
||||
));
|
||||
|
||||
@@ -178,21 +178,6 @@ fn connection_control<'a>(
|
||||
container(tile).width(Length::FillPortion(1)).into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn connection_captions_fit_one_short_line() {
|
||||
assert_eq!(super::connection_caption("88%"), "88%");
|
||||
assert_eq!(super::connection_caption("Flow84@Lofree"), "Flow84@Lofr…");
|
||||
assert_eq!(super::connection_caption("123456789012"), "123456789012");
|
||||
assert_eq!(super::connection_caption("My very long headphones"), "My very lon…");
|
||||
assert_eq!(super::connection_caption(" Device\n name "), "Device name");
|
||||
let caption = super::connection_caption("耳機耳機耳機耳機耳機耳機耳機");
|
||||
assert_eq!(caption.chars().count(), 12);
|
||||
assert!(caption.ends_with('…'));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn heading<'a>(
|
||||
shell: &'a FereseShell,
|
||||
mut heading: super::MenuHeading<'a>,
|
||||
@@ -229,3 +214,18 @@ pub(super) fn heading<'a>(
|
||||
}
|
||||
heading
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn connection_captions_fit_one_short_line() {
|
||||
assert_eq!(super::connection_caption("88%"), "88%");
|
||||
assert_eq!(super::connection_caption("Flow84@Lofree"), "Flow84@Lofr…");
|
||||
assert_eq!(super::connection_caption("123456789012"), "123456789012");
|
||||
assert_eq!(super::connection_caption("My very long headphones"), "My very lon…");
|
||||
assert_eq!(super::connection_caption(" Device\n name "), "Device name");
|
||||
let caption = super::connection_caption("耳機耳機耳機耳機耳機耳機耳機");
|
||||
assert_eq!(caption.chars().count(), 12);
|
||||
assert!(caption.ends_with('…'));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user