Share native theme components and fix dialog backdrop blur

Centralize native palettes, presets, typography, controls, menus, calendar rendering, and icon assets in ferese-theme. Reuse the shared components across the shell, settings, lock screen, authentication prompt, and screen-sharing chooser.

Attach themed dialogs to compositor-owned materials, correct backdrop capture and sampling across output transforms, smooth patterned backgrounds, and prevent clipped picker buttons with the configured shell font.
This commit is contained in:
Samuel Onoja committed 2026-09-29 21:19:19 +01:00
1 parent 362bf79012
commit b2363a18b0
85 files changed
+2163 -1591

No files matched your search

+22 -57
View File
@@ -1,5 +1,6 @@
use cosmic::{iced::Color, widget::image};
use ferese_config::Document;
#[derive(Clone)]
pub struct Appearance {
pub dim: f32,
@@ -14,6 +15,7 @@ pub struct Appearance {
pub wallpaper: image::Handle,
pub avatar: Option<image::Handle>,
}
impl Appearance {
pub fn load(user: &str, path: Option<&std::path::Path>) -> Self {
let doc = path
@@ -26,9 +28,7 @@ impl Appearance {
.unwrap_or(fallback)
.to_owned()
};
let color = |key, fallback| {
parse_color(&string(key, fallback)).unwrap_or_else(|| parse_color(fallback).unwrap())
};
let palette = ferese_theme::Palette::from_document(doc.as_ref());
let font = string("theme.typography.font_family", "Inter");
let path = string("theme.background.path", ferese_config::default_wallpaper());
let path = if let Some(tail) = path.strip_prefix("~/") {
@@ -77,23 +77,16 @@ impl Appearance {
show_clock: boolean("lock_screen.show_clock", true),
show_date: boolean("lock_screen.show_date", true),
twelve_hour: string("lock_screen.clock_format", "24h") == "12h",
panel: color("theme.colors.surface_base", "#111821"),
text: color("theme.colors.text_primary", "#F4F7FB"),
accent: color("theme.colors.accent", "#3D7BE6"),
radius: doc
.as_ref()
.and_then(|d| {
d.get("theme.geometry.shell_radius")
.or_else(|| d.get("appearance.corner_radius"))
})
.and_then(|v| v.as_f64())
.unwrap_or(14.)
.clamp(0., 64.) as f32,
font: cosmic::font::Font::with_name(Box::leak(font.into_boxed_str())),
panel: palette.sidebar,
text: palette.text,
accent: palette.accent,
radius: palette.radius,
font: ferese_theme::font(Some(&font)),
wallpaper,
avatar: account_picture(user),
}
}
pub fn clock_format(&self) -> &'static str {
if self.twelve_hour {
"%I:%M %p"
@@ -101,48 +94,20 @@ impl Appearance {
"%H:%M"
}
}
pub fn theme(&self) -> cosmic::Theme {
use cosmic::cosmic_theme::{ThemeBuilder, palette::Srgba};
let color = |c: Color| Srgba::new(c.r, c.g, c.b, c.a);
let builder = if self.panel.r + self.panel.g + self.panel.b > 1.5 {
ThemeBuilder::light()
} else {
ThemeBuilder::dark()
};
let corners = cosmic::cosmic_theme::CornerRadii {
radius_0: Default::default(),
radius_xs: [self.radius.min(4.); 4],
radius_s: [self.radius.min(8.); 4],
radius_m: [self.radius; 4],
radius_l: [self.radius; 4],
radius_xl: [self.radius; 4],
};
let mut native = builder
.corner_radii(corners)
.bg_color(color(self.panel))
.primary_container_bg(color(self.panel))
.text_tint(color(self.text).color)
.accent(ferese_theme::accent_color(self.accent, self.panel))
.build();
ferese_theme::apply(&mut native, self.text);
cosmic::Theme::custom(std::sync::Arc::new(native))
}
}
fn parse_color(value: &str) -> Option<Color> {
let hex = value.strip_prefix('#')?;
let packed = u32::from_str_radix(hex, 16).ok()?;
let rgba = match hex.len() {
6 => (packed << 8) | 255,
8 => packed,
_ => return None,
};
Some(Color::from_rgba8(
(rgba >> 24) as u8,
(rgba >> 16) as u8,
(rgba >> 8) as u8,
(rgba & 255) as f32 / 255.,
))
pub fn theme(&self) -> cosmic::Theme {
ferese_theme::Palette {
background: self.panel,
sidebar: self.panel,
card: self.panel,
text: self.text,
muted: self.text.scale_alpha(0.55),
accent: self.accent,
radius: self.radius,
error: Color::from_rgb8(235, 98, 98),
}
.native_theme()
}
}
fn account_picture(user: &str) -> Option<image::Handle> {
+4 -36
View File
@@ -343,7 +343,7 @@ impl Locker {
label: impl Into<std::borrow::Cow<'a, str>> + 'a,
size: u16,
) -> widget::Text<'a, cosmic::Theme, cosmic::Renderer> {
widget::text(label).font(self.appearance.font).size(size)
ferese_theme::text(label, self.appearance.font).size(size)
}
fn screen(&self) -> Element<'_, Message> {
@@ -368,7 +368,9 @@ impl Locker {
let mut input = widget::text_input("Password", self.password.as_str())
.password()
.padding([8, 18])
.style(password_style(a.radius, a.accent, a.panel))
.style(ferese_theme::controls::lock_input(
a.radius, a.accent, a.panel,
))
.font(a.font)
.id(widget::Id::new("password"));
@@ -530,40 +532,6 @@ fn ferese_symbol() -> widget::icon::Icon {
.size(30)
}
fn password_style(radius: f32, accent: iced::Color, surface: iced::Color) -> theme::TextInput {
let appearance = move |focused: bool| widget::text_input::Appearance {
background: iced::Color::from_rgba(
(surface.r + 1.) * 0.5,
(surface.g + 1.) * 0.5,
(surface.b + 1.) * 0.5,
0.24,
)
.into(),
border_radius: (radius * 2.).min(26.).into(),
border_width: 1.,
border_offset: None,
border_color: if focused {
accent.scale_alpha(0.7)
} else {
iced::Color::WHITE.scale_alpha(0.15)
},
icon_color: Some(iced::Color::WHITE.scale_alpha(0.8)),
text_color: Some(iced::Color::WHITE),
placeholder_color: iced::Color::WHITE.scale_alpha(0.55),
selected_text_color: iced::Color::WHITE,
selected_fill: accent.scale_alpha(0.5),
label_color: iced::Color::WHITE,
};
theme::TextInput::Custom {
active: Box::new(move |_| appearance(false)),
hovered: Box::new(move |_| appearance(true)),
focused: Box::new(move |_| appearance(true)),
error: Box::new(move |_| appearance(false)),
disabled: Box::new(move |_| appearance(false)),
}
}
#[cfg(test)]
mod tests {
use super::*;