fix(appearance): Multiple fixes + refactor (#1244)
- Switching theme mode is now reflected immediately in the settings application; - Color changes are propagated immediately when selected; - Imported theme is now loaded immediately; - Interface density is now responsive when selected; - Font Selection is updated to the screen when changes are committed; - Icons toolkit's "Apply to Gnome" toggle is now responsive; Logical fixes: - Style (Round, Square, etc) is now sync'ed between dark & light mode; - Interface Density is now updated for both modes; - Window Management is also updated for both modes; Improvements/Perf fixes: - Removed code paths where the same configuration field was written multiple times in the same pass; - Stopped completely rebuilding the whole page when theme needed to be rebuild; - Simplified initialization of the Appearance module; - Extracted theme manipulation to its own module for future performance improvement; --------- Co-authored-by: Michael Aaron Murphy <michael@mmurphy.dev>
This commit is contained in:
parent
126503fe5f
commit
f7d16417cd
9 changed files with 1901 additions and 1707 deletions
|
|
@ -756,8 +756,9 @@ impl cosmic::Application for SettingsApp {
|
|||
}
|
||||
}
|
||||
|
||||
Message::SetTheme(t) => return cosmic::command::set_theme(t),
|
||||
|
||||
Message::SetTheme(t) => {
|
||||
return cosmic::command::set_theme(t);
|
||||
}
|
||||
Message::OpenContextDrawer(page) => {
|
||||
self.core.window.show_context = true;
|
||||
self.active_context_page = Some(page);
|
||||
|
|
|
|||
486
cosmic-settings/src/pages/desktop/appearance/drawer.rs
Normal file
486
cosmic-settings/src/pages/desktop/appearance/drawer.rs
Normal file
|
|
@ -0,0 +1,486 @@
|
|||
use cosmic::app::{ContextDrawer, context_drawer};
|
||||
use cosmic::config::CosmicTk;
|
||||
use cosmic::cosmic_config::{Config, ConfigSet};
|
||||
use cosmic::cosmic_theme::Spacing;
|
||||
use cosmic::cosmic_theme::palette::{FromColor, Hsv, Srgb};
|
||||
use cosmic::iced_core::{Color, Length};
|
||||
use cosmic::widget::{
|
||||
ColorPickerModel, color_picker::ColorPickerUpdate, container, flex_row, settings, text,
|
||||
};
|
||||
use cosmic::{Apply, Task};
|
||||
use cosmic::{Element, widget};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::app;
|
||||
use crate::widget::color_picker_context_view;
|
||||
|
||||
use super::{
|
||||
ContextView, Message, font_config, icon_themes,
|
||||
icon_themes::{IconHandles, IconThemes},
|
||||
theme_manager,
|
||||
};
|
||||
|
||||
pub struct Content {
|
||||
context_view: Option<ContextView>,
|
||||
pub custom_accent: ColorPickerModel,
|
||||
pub accent_window_hint: ColorPickerModel,
|
||||
pub application_background: ColorPickerModel,
|
||||
pub container_background: ColorPickerModel,
|
||||
pub interface_text: ColorPickerModel,
|
||||
pub control_component: ColorPickerModel,
|
||||
|
||||
font_config: font_config::Model,
|
||||
|
||||
icons_fetched: bool,
|
||||
icon_fetch_handle: Option<cosmic::iced::task::Handle>,
|
||||
|
||||
icon_theme_active: Option<usize>,
|
||||
icon_global: bool,
|
||||
icon_themes: IconThemes,
|
||||
icon_handles: IconHandles,
|
||||
tk_config: Option<Config>,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FontMessage {
|
||||
FontLoaded(Vec<Arc<str>>, Vec<Arc<str>>),
|
||||
Search(String),
|
||||
Select(Arc<str>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IconMessage {
|
||||
IconLoaded((IconThemes, IconHandles)),
|
||||
IconTheme(usize),
|
||||
ApplyThemeGlobal(bool),
|
||||
}
|
||||
|
||||
crate::cache_dynamic_lazy! {
|
||||
static HEX: String = fl!("hex");
|
||||
static RGB: String = fl!("rgb");
|
||||
static ICON_THEME: String = fl!("icon-theme");
|
||||
static RESET_TO_DEFAULT: String = fl!("reset-to-default");
|
||||
}
|
||||
|
||||
impl From<&theme_manager::Manager> for Content {
|
||||
fn from(theme_manager: &theme_manager::Manager) -> Self {
|
||||
let theme = theme_manager.theme();
|
||||
Self {
|
||||
context_view: None,
|
||||
custom_accent: ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
None,
|
||||
theme_manager.get_color(&ContextView::CustomAccent),
|
||||
),
|
||||
application_background: ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
Some(theme.background.base.into()),
|
||||
theme_manager.get_color(&ContextView::ApplicationBackground),
|
||||
),
|
||||
container_background: ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
None,
|
||||
theme_manager.get_color(&ContextView::ContainerBackground),
|
||||
),
|
||||
interface_text: ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
Some(theme.background.on.into()),
|
||||
theme_manager.get_color(&ContextView::InterfaceText),
|
||||
),
|
||||
control_component: ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
Some(theme.palette.neutral_5.into()),
|
||||
theme_manager.get_color(&ContextView::ControlComponent),
|
||||
),
|
||||
accent_window_hint: ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
None,
|
||||
theme_manager.get_color(&ContextView::AccentWindowHint),
|
||||
),
|
||||
font_config: font_config::Model::new(),
|
||||
icons_fetched: false,
|
||||
icon_global: cosmic::config::apply_theme_global(),
|
||||
icon_fetch_handle: None,
|
||||
icon_theme_active: None,
|
||||
icon_themes: Vec::new(),
|
||||
icon_handles: Vec::new(),
|
||||
tk_config: CosmicTk::config().ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub fn current_font_family(&self, context_view: &ContextView) -> String {
|
||||
match *context_view {
|
||||
ContextView::SystemFont => self.font_config.interface_font.family.clone(),
|
||||
ContextView::MonospaceFont => self.font_config.monospace_font.family.clone(),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_font(
|
||||
&mut self,
|
||||
message: FontMessage,
|
||||
context_view: Option<&ContextView>,
|
||||
) -> Task<app::Message> {
|
||||
match message {
|
||||
FontMessage::FontLoaded(interface, mono) => {
|
||||
return self.font_config.font_loaded(mono, interface);
|
||||
}
|
||||
FontMessage::Search(input) => match context_view {
|
||||
None => Task::none(),
|
||||
Some(c) => self.font_config.search(input.to_string(), c),
|
||||
},
|
||||
FontMessage::Select(font) => {
|
||||
if let Some(context_view) = context_view {
|
||||
if let Some(task) = self.font_config.select(font.to_string(), context_view) {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_color(
|
||||
&mut self,
|
||||
message: ColorPickerUpdate,
|
||||
context_view: &ContextView,
|
||||
) -> Task<app::Message> {
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
tasks.push(match message {
|
||||
ColorPickerUpdate::AppliedColor | ColorPickerUpdate::Reset => {
|
||||
self.context_view = None;
|
||||
cosmic::task::message(crate::pages::Message::CloseContextDrawer)
|
||||
}
|
||||
|
||||
ColorPickerUpdate::ActionFinished => Task::none(),
|
||||
|
||||
ColorPickerUpdate::Cancel => {
|
||||
self.context_view = None;
|
||||
cosmic::task::message(crate::pages::Message::CloseContextDrawer)
|
||||
}
|
||||
|
||||
_ => Task::none(),
|
||||
});
|
||||
|
||||
tasks.push(match *context_view {
|
||||
ContextView::CustomAccent => self.custom_accent.update(message),
|
||||
ContextView::ApplicationBackground => self.application_background.update(message),
|
||||
ContextView::ContainerBackground => self.container_background.update(message),
|
||||
ContextView::InterfaceText => self.interface_text.update(message),
|
||||
ContextView::ControlComponent => self.control_component.update(message),
|
||||
ContextView::AccentWindowHint => self.accent_window_hint.update(message),
|
||||
_ => Task::none(),
|
||||
});
|
||||
|
||||
cosmic::Task::batch(tasks)
|
||||
}
|
||||
|
||||
pub fn update_icon(
|
||||
&mut self,
|
||||
message: IconMessage,
|
||||
_context_view: &ContextView,
|
||||
) -> Task<app::Message> {
|
||||
match message {
|
||||
IconMessage::IconTheme(id) => {
|
||||
if let Some(theme) = self.icon_themes.get(id).cloned() {
|
||||
self.icon_theme_active = Some(id);
|
||||
|
||||
if let Some(ref config) = self.tk_config {
|
||||
_ = config.set::<String>("icon_theme", theme.id);
|
||||
}
|
||||
|
||||
tokio::spawn(icon_themes::set_gnome_icon_theme(theme.name));
|
||||
}
|
||||
}
|
||||
IconMessage::ApplyThemeGlobal(enabled) => {
|
||||
if let Some(config) = self.tk_config.as_ref() {
|
||||
_ = config.set("apply_theme_global", enabled);
|
||||
self.icon_global = enabled;
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Failed to apply theme to GNOME config because the CosmicTK config does not exist."
|
||||
);
|
||||
}
|
||||
}
|
||||
IconMessage::IconLoaded((icon_themes, icon_handles)) => {
|
||||
let active_icon_theme = cosmic::config::icon_theme();
|
||||
|
||||
// Set the icon themes, and define the active icon theme.
|
||||
self.icon_themes = icon_themes;
|
||||
self.icon_theme_active = self
|
||||
.icon_themes
|
||||
.iter()
|
||||
.position(|theme| theme.id == active_icon_theme);
|
||||
self.icon_handles = icon_handles;
|
||||
|
||||
return cosmic::task::message(app::Message::SetTheme(
|
||||
cosmic::theme::system_preference(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub fn on_open(&mut self, context_view: &ContextView) -> Task<app::Message> {
|
||||
match *context_view {
|
||||
ContextView::IconsAndToolkit => {
|
||||
if self.icons_fetched {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
self.icons_fetched = true;
|
||||
let (task, handle) = cosmic::task::future(icon_themes::fetch()).abortable();
|
||||
self.icon_fetch_handle = Some(handle);
|
||||
|
||||
return task;
|
||||
}
|
||||
ContextView::MonospaceFont | ContextView::SystemFont => {
|
||||
self.font_config.reset();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub fn on_leave(&mut self) -> Task<crate::pages::Message> {
|
||||
if let Some(handle) = self.icon_fetch_handle.take() {
|
||||
handle.abort();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// Returns the color associated with the color picker for the context view.
|
||||
// Returns None if the context view is not associated to any color picker.
|
||||
pub fn current_color(&self, context_view: &ContextView) -> Option<Color> {
|
||||
match *context_view {
|
||||
ContextView::CustomAccent => self.custom_accent.get_applied_color(),
|
||||
ContextView::ApplicationBackground => self.application_background.get_applied_color(),
|
||||
ContextView::ContainerBackground => self.container_background.get_applied_color(),
|
||||
ContextView::InterfaceText => self.interface_text.get_applied_color(),
|
||||
ContextView::ControlComponent => self.control_component.get_applied_color(),
|
||||
ContextView::AccentWindowHint => self.accent_window_hint.get_applied_color(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, manager: &theme_manager::Manager) {
|
||||
self.application_background = ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
Some(manager.theme().background.base.into()),
|
||||
manager.get_color(&ContextView::ApplicationBackground),
|
||||
);
|
||||
self.custom_accent = ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
None,
|
||||
manager.get_color(&ContextView::CustomAccent),
|
||||
);
|
||||
self.container_background = ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
None,
|
||||
manager.get_color(&ContextView::ContainerBackground),
|
||||
);
|
||||
self.interface_text = ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
Some(manager.theme().background.on.into()),
|
||||
manager.get_color(&ContextView::InterfaceText),
|
||||
);
|
||||
self.control_component = ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
Some(manager.theme().palette.neutral_5.into()),
|
||||
manager.get_color(&ContextView::ControlComponent),
|
||||
);
|
||||
self.accent_window_hint = ColorPickerModel::new(
|
||||
&*HEX,
|
||||
&*RGB,
|
||||
None,
|
||||
manager.get_color(&ContextView::AccentWindowHint),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn context_drawer(
|
||||
&self,
|
||||
context_view: Option<ContextView>,
|
||||
) -> Option<ContextDrawer<'_, crate::pages::Message>> {
|
||||
Some(match context_view? {
|
||||
ContextView::AccentWindowHint => context_drawer(
|
||||
color_picker_context_view(
|
||||
None,
|
||||
RESET_TO_DEFAULT.as_str().into(),
|
||||
Message::DrawerColor,
|
||||
&self.accent_window_hint,
|
||||
)
|
||||
.map(crate::pages::Message::Appearance),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
)
|
||||
.title(fl!("window-hint-accent")),
|
||||
|
||||
ContextView::ApplicationBackground => context_drawer(
|
||||
color_picker_context_view(
|
||||
None,
|
||||
RESET_TO_DEFAULT.as_str().into(),
|
||||
Message::DrawerColor,
|
||||
&self.application_background,
|
||||
)
|
||||
.map(crate::pages::Message::Appearance),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
)
|
||||
.title(fl!("app-background")),
|
||||
|
||||
ContextView::ContainerBackground => context_drawer(
|
||||
color_picker_context_view(
|
||||
Some(fl!("container-background", "desc-detail").into()),
|
||||
fl!("container-background", "reset").into(),
|
||||
Message::DrawerColor,
|
||||
&self.container_background,
|
||||
)
|
||||
.map(crate::pages::Message::Appearance),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
)
|
||||
.title(fl!("container-background")),
|
||||
|
||||
ContextView::ControlComponent => context_drawer(
|
||||
color_picker_context_view(
|
||||
None,
|
||||
RESET_TO_DEFAULT.as_str().into(),
|
||||
Message::DrawerColor,
|
||||
&self.control_component,
|
||||
)
|
||||
.map(crate::pages::Message::Appearance),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
)
|
||||
.title(fl!("control-tint")),
|
||||
|
||||
ContextView::CustomAccent => context_drawer(
|
||||
color_picker_context_view(
|
||||
None,
|
||||
RESET_TO_DEFAULT.as_str().into(),
|
||||
Message::DrawerColor,
|
||||
&self.custom_accent,
|
||||
)
|
||||
.map(crate::pages::Message::Appearance),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
)
|
||||
.title(fl!("accent-color")),
|
||||
|
||||
ContextView::InterfaceText => context_drawer(
|
||||
color_picker_context_view(
|
||||
None,
|
||||
RESET_TO_DEFAULT.as_str().into(),
|
||||
Message::DrawerColor,
|
||||
&self.interface_text,
|
||||
)
|
||||
.map(crate::pages::Message::Appearance),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
)
|
||||
.title(fl!("text-tint")),
|
||||
|
||||
ContextView::SystemFont => context_drawer(
|
||||
self.font_config
|
||||
.selection_context(&ContextView::SystemFont, |name| {
|
||||
Message::DrawerFont(FontMessage::Select(name))
|
||||
})
|
||||
.map(crate::pages::Message::Appearance),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
)
|
||||
.title(fl!("interface-font"))
|
||||
.header(self.font_config.search_input()),
|
||||
|
||||
ContextView::MonospaceFont => context_drawer(
|
||||
self.font_config
|
||||
.selection_context(&ContextView::MonospaceFont, |name| {
|
||||
Message::DrawerFont(FontMessage::Select(name))
|
||||
})
|
||||
.map(crate::pages::Message::Appearance),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
)
|
||||
.title(fl!("monospace-font"))
|
||||
.header(self.font_config.search_input()),
|
||||
|
||||
ContextView::IconsAndToolkit => context_drawer(
|
||||
self.icons_and_toolkit(),
|
||||
crate::pages::Message::CloseContextDrawer,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn icons_and_toolkit(&self) -> Element<'_, crate::pages::Message> {
|
||||
let Spacing {
|
||||
space_xxs,
|
||||
space_xs,
|
||||
space_m,
|
||||
..
|
||||
} = cosmic::theme::spacing();
|
||||
|
||||
let active = self.icon_theme_active;
|
||||
|
||||
cosmic::iced::widget::column![
|
||||
// Export theme choice
|
||||
settings::section().add(
|
||||
settings::item::builder(fl!("enable-export"))
|
||||
.description(fl!("enable-export", "desc"))
|
||||
.toggler(self.icon_global, |b| {
|
||||
Message::DrawerIcon(IconMessage::ApplyThemeGlobal(b))
|
||||
})
|
||||
),
|
||||
// Icon theme previews
|
||||
widget::column::with_children(vec![
|
||||
text::heading(&*ICON_THEME).into(),
|
||||
flex_row(
|
||||
self.icon_themes
|
||||
.iter()
|
||||
.zip(self.icon_handles.iter())
|
||||
.enumerate()
|
||||
.map(|(i, (theme, handles))| {
|
||||
let selected = active.map(|j| i == j).unwrap_or_default();
|
||||
icon_themes::button(&theme.name, handles, i, selected, |id| {
|
||||
Message::DrawerIcon(IconMessage::IconTheme(id))
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.row_spacing(space_xs)
|
||||
.column_spacing(space_xs)
|
||||
.apply(container)
|
||||
.center_x(Length::Fill)
|
||||
.into()
|
||||
])
|
||||
.spacing(space_xxs)
|
||||
]
|
||||
.spacing(space_m)
|
||||
.width(Length::Fill)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Appearance)
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_color_control(
|
||||
field: &mut ColorPickerModel,
|
||||
manager: &theme_manager::Manager,
|
||||
context_view: ContextView,
|
||||
) -> Vec<Task<app::Message>> {
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
let color = manager.get_color(&context_view).map(Srgb::from);
|
||||
|
||||
if let Some(c) = color {
|
||||
tasks.push(field.update(ColorPickerUpdate::ActiveColor(Hsv::from_color(c))));
|
||||
tasks.push(field.update(ColorPickerUpdate::AppliedColor));
|
||||
} else {
|
||||
tasks.push(field.update(ColorPickerUpdate::Reset));
|
||||
}
|
||||
|
||||
tasks
|
||||
}
|
||||
|
|
@ -12,6 +12,10 @@ use cosmic::{
|
|||
};
|
||||
use cosmic_config::ConfigSet;
|
||||
|
||||
use crate::app;
|
||||
|
||||
use super::{ContextView, Message, drawer};
|
||||
|
||||
const INTERFACE_FONT: &str = "interface_font";
|
||||
const MONOSPACE_FONT: &str = "monospace_font";
|
||||
|
||||
|
|
@ -62,42 +66,174 @@ pub fn load_font_families() -> (Vec<Arc<str>>, Vec<Arc<str>>) {
|
|||
(interface, mono)
|
||||
}
|
||||
|
||||
pub fn selection_context<'a>(
|
||||
families: &'a [Arc<str>],
|
||||
current_font: &str,
|
||||
system: bool,
|
||||
) -> Element<'a, super::Message> {
|
||||
let svg_accent = Rc::new(|theme: &cosmic::Theme| svg::Style {
|
||||
color: Some(theme.cosmic().accent_color().into()),
|
||||
});
|
||||
#[derive(Debug)]
|
||||
pub struct Model {
|
||||
font_search: String,
|
||||
font_filter: Vec<Arc<str>>,
|
||||
|
||||
let list = families.iter().fold(widget::list_column(), |list, family| {
|
||||
let selected = &**family == current_font;
|
||||
list.add(
|
||||
settings::item_row(vec![
|
||||
widget::text::body(&**family)
|
||||
.wrapping(Wrapping::Word)
|
||||
.width(cosmic::iced::Length::Fill)
|
||||
.into(),
|
||||
if selected {
|
||||
widget::icon::from_name("object-select-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
.class(cosmic::theme::Svg::Custom(svg_accent.clone()))
|
||||
.into()
|
||||
} else {
|
||||
widget::horizontal_space().width(16).into()
|
||||
},
|
||||
])
|
||||
.apply(widget::container)
|
||||
.class(cosmic::theme::Container::List)
|
||||
.apply(widget::button::custom)
|
||||
.class(cosmic::theme::Button::Transparent)
|
||||
.on_press(super::Message::FontSelect(system, family.clone())),
|
||||
)
|
||||
});
|
||||
interface_font_families: Vec<Arc<str>>,
|
||||
pub interface_font: FontConfig,
|
||||
|
||||
list.into()
|
||||
monospace_font_families: Vec<Arc<str>>,
|
||||
pub monospace_font: FontConfig,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn new() -> Model {
|
||||
Model {
|
||||
font_filter: Vec::new(),
|
||||
font_search: String::new(),
|
||||
interface_font_families: Vec::new(),
|
||||
interface_font: cosmic::config::interface_font(),
|
||||
monospace_font_families: Vec::new(),
|
||||
monospace_font: cosmic::config::monospace_font(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.font_search.clear();
|
||||
self.font_filter.clear();
|
||||
}
|
||||
|
||||
pub fn font_loaded(
|
||||
&mut self,
|
||||
mono: Vec<Arc<str>>,
|
||||
interface: Vec<Arc<str>>,
|
||||
) -> Task<crate::app::Message> {
|
||||
self.interface_font_families = interface;
|
||||
self.monospace_font_families = mono;
|
||||
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub fn select(
|
||||
&mut self,
|
||||
font: String,
|
||||
context_view: &ContextView,
|
||||
) -> Option<Task<app::Message>> {
|
||||
match *context_view {
|
||||
ContextView::MonospaceFont => {
|
||||
self.monospace_font = FontConfig {
|
||||
family: font.to_string(),
|
||||
weight: cosmic::iced::font::Weight::Normal,
|
||||
style: cosmic::iced::font::Style::Normal,
|
||||
stretch: cosmic::iced::font::Stretch::Normal,
|
||||
};
|
||||
|
||||
update_config(MONOSPACE_FONT, self.monospace_font.clone());
|
||||
return None;
|
||||
}
|
||||
ContextView::SystemFont => {
|
||||
self.interface_font = FontConfig {
|
||||
family: font.to_string(),
|
||||
weight: cosmic::iced::font::Weight::Normal,
|
||||
style: cosmic::iced::font::Style::Normal,
|
||||
stretch: cosmic::iced::font::Stretch::Normal,
|
||||
};
|
||||
update_config(INTERFACE_FONT, self.interface_font.clone());
|
||||
tokio::spawn(async move {
|
||||
set_gnome_font_name(font.as_ref()).await;
|
||||
});
|
||||
return None;
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search(&mut self, input: String, context_view: &ContextView) -> Task<app::Message> {
|
||||
self.font_search = input.to_lowercase();
|
||||
self.font_filter.clear();
|
||||
|
||||
let mut result: Option<Vec<Arc<str>>> = None;
|
||||
|
||||
if let Some(fonts) = self.current_font_family(context_view) {
|
||||
result = Some(
|
||||
fonts
|
||||
.iter()
|
||||
.filter(|f| f.to_lowercase().contains(&self.font_search))
|
||||
.map(|f| f.clone())
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(fonts) = result.as_mut() {
|
||||
self.font_filter.append(fonts);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub fn search_input(&self) -> Element<'_, crate::pages::Message> {
|
||||
widget::search_input(fl!("type-to-search"), &self.font_search)
|
||||
.on_input(|input| Message::DrawerFont(drawer::FontMessage::Search(input)))
|
||||
.on_clear(Message::DrawerFont(drawer::FontMessage::Search(
|
||||
String::new(),
|
||||
)))
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Appearance)
|
||||
}
|
||||
|
||||
pub fn selection_context(
|
||||
&self,
|
||||
context_view: &ContextView,
|
||||
callback: impl Fn(Arc<str>) -> super::Message,
|
||||
) -> Element<'_, super::Message> {
|
||||
let svg_accent = Rc::new(|theme: &cosmic::Theme| svg::Style {
|
||||
color: Some(theme.cosmic().accent_color().into()),
|
||||
});
|
||||
|
||||
let (mut families, current_font) = match *context_view {
|
||||
ContextView::MonospaceFont => {
|
||||
(&self.monospace_font_families, &self.monospace_font.family)
|
||||
}
|
||||
ContextView::SystemFont => (&self.interface_font_families, &self.interface_font.family),
|
||||
_ => (&self.monospace_font_families, &self.monospace_font.family),
|
||||
};
|
||||
|
||||
if !self.font_filter.is_empty() {
|
||||
families = &self.font_filter;
|
||||
}
|
||||
|
||||
let list = families.iter().fold(widget::list_column(), |list, family| {
|
||||
let selected = &**family == current_font;
|
||||
list.add(
|
||||
settings::item_row(vec![
|
||||
widget::text::body(&**family)
|
||||
.wrapping(Wrapping::Word)
|
||||
.width(cosmic::iced::Length::Fill)
|
||||
.into(),
|
||||
if selected {
|
||||
widget::icon::from_name("object-select-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
.class(cosmic::theme::Svg::Custom(svg_accent.clone()))
|
||||
.into()
|
||||
} else {
|
||||
widget::horizontal_space().width(16).into()
|
||||
},
|
||||
])
|
||||
.apply(widget::container)
|
||||
.class(cosmic::theme::Container::List)
|
||||
.apply(widget::button::custom)
|
||||
.class(cosmic::theme::Button::Transparent)
|
||||
.on_press(callback(family.clone())),
|
||||
)
|
||||
});
|
||||
list.into()
|
||||
}
|
||||
|
||||
fn current_font_family(&self, context_view: &ContextView) -> Option<&Vec<Arc<str>>> {
|
||||
match *context_view {
|
||||
ContextView::SystemFont => Some(&self.interface_font_families),
|
||||
ContextView::MonospaceFont => Some(&self.monospace_font_families),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_config(variant: &str, font: FontConfig) {
|
||||
if let Ok(config) = CosmicTk::config() {
|
||||
_ = config.set(variant, font);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the preferred icon theme for GNOME/GTK applications.
|
||||
|
|
@ -107,96 +243,3 @@ pub async fn set_gnome_font_name(font_name: &str) {
|
|||
.status()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
InterfaceFontFamily(usize),
|
||||
LoadedFonts(Vec<Arc<str>>, Vec<Arc<str>>),
|
||||
MonospaceFontFamily(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Model {
|
||||
pub interface_font_families: Vec<Arc<str>>,
|
||||
pub interface_font_family: Option<usize>,
|
||||
pub monospace_font_families: Vec<Arc<str>>,
|
||||
pub monospace_font_family: Option<usize>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub const fn new() -> Model {
|
||||
Model {
|
||||
interface_font_families: Vec::new(),
|
||||
interface_font_family: None,
|
||||
monospace_font_families: Vec::new(),
|
||||
monospace_font_family: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, message: Message) -> Task<crate::app::Message> {
|
||||
match message {
|
||||
Message::InterfaceFontFamily(id) => {
|
||||
if let Some(family) = self.interface_font_families.get(id) {
|
||||
update_config(
|
||||
INTERFACE_FONT,
|
||||
FontConfig {
|
||||
family: family.to_string(),
|
||||
weight: cosmic::iced::font::Weight::Normal,
|
||||
style: cosmic::iced::font::Style::Normal,
|
||||
stretch: cosmic::iced::font::Stretch::Normal,
|
||||
},
|
||||
);
|
||||
|
||||
self.interface_font_family = Some(id);
|
||||
|
||||
let family = family.clone();
|
||||
tokio::spawn(async move {
|
||||
set_gnome_font_name(family.as_ref()).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Message::LoadedFonts(interface, mono) => {
|
||||
self.interface_font_families = interface;
|
||||
self.monospace_font_families = mono;
|
||||
|
||||
let interface_font = cosmic::config::interface_font();
|
||||
let monospace_font = cosmic::config::monospace_font();
|
||||
|
||||
self.interface_font_family =
|
||||
font_family_to_pos(&self.interface_font_families, &interface_font.family);
|
||||
|
||||
self.monospace_font_family =
|
||||
font_family_to_pos(&self.monospace_font_families, &monospace_font.family);
|
||||
}
|
||||
|
||||
Message::MonospaceFontFamily(id) => {
|
||||
if let Some(family) = self.monospace_font_families.get(id) {
|
||||
update_config(
|
||||
MONOSPACE_FONT,
|
||||
FontConfig {
|
||||
family: family.to_string(),
|
||||
weight: cosmic::iced::font::Weight::Normal,
|
||||
style: cosmic::iced::font::Style::Normal,
|
||||
stretch: cosmic::iced::font::Stretch::Normal,
|
||||
},
|
||||
);
|
||||
|
||||
self.monospace_font_family = Some(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
fn font_family_to_pos(families: &[Arc<str>], family: &str) -> Option<usize> {
|
||||
families.iter().position(|f| &**f == family)
|
||||
}
|
||||
|
||||
fn update_config(variant: &str, font: FontConfig) {
|
||||
if let Ok(config) = CosmicTk::config() {
|
||||
_ = config.set(variant, font);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ pub fn button(
|
|||
handles: &[icon::Handle],
|
||||
id: usize,
|
||||
selected: bool,
|
||||
callback: impl Fn(usize) -> super::Message,
|
||||
) -> Element<'static, Message> {
|
||||
let theme = cosmic::theme::active();
|
||||
let theme = theme.cosmic();
|
||||
|
|
@ -61,7 +62,7 @@ pub fn button(
|
|||
.spacing(theme.space_xxxs()),
|
||||
None,
|
||||
)
|
||||
.on_press(Message::IconTheme(id))
|
||||
.on_press(callback(id))
|
||||
.selected(selected)
|
||||
.padding(theme.space_xs())
|
||||
// Image button's style mostly works, but it needs a background to fit the design
|
||||
|
|
@ -231,7 +232,9 @@ pub async fn fetch() -> Message {
|
|||
}
|
||||
}
|
||||
|
||||
Message::Entered(icon_themes.into_iter().unzip())
|
||||
Message::DrawerIcon(super::drawer::IconMessage::IconLoaded(
|
||||
icon_themes.into_iter().unzip(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Set the preferred icon theme for GNOME/GTK applications.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
379
cosmic-settings/src/pages/desktop/appearance/mode_and_colors.rs
Normal file
379
cosmic-settings/src/pages/desktop/appearance/mode_and_colors.rs
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
use crate::pages::desktop::wallpaper::widgets::color_image;
|
||||
use cosmic::cosmic_theme::Spacing;
|
||||
use cosmic::cosmic_theme::palette::Srgba;
|
||||
use cosmic::iced_core::{Alignment, Length};
|
||||
use cosmic::widget::icon::{from_name, icon};
|
||||
use cosmic::widget::{button, container, scrollable, settings, text};
|
||||
use cosmic::{Apply, Element};
|
||||
use cosmic_settings_page::Section;
|
||||
use cosmic_settings_wallpaper as wallpaper;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{ContextView, Message, Page};
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn section() -> Section<crate::pages::Message> {
|
||||
let (descriptions, label_keys) = i18n();
|
||||
|
||||
Section::default()
|
||||
.title(fl!("mode-and-colors"))
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, section| {
|
||||
let label_keys = label_keys.clone();
|
||||
let descriptions = §ion.descriptions;
|
||||
let theme_manager = &page.theme_manager;
|
||||
|
||||
let mut section = settings::section()
|
||||
.title(§ion.title)
|
||||
.add(theme_mode(&page, section, &label_keys))
|
||||
.add(auto_switch(&page, section, &label_keys))
|
||||
.add(accent_color_palette(&page, section, &label_keys))
|
||||
.add(application_background(&page, section, &label_keys))
|
||||
.add(container_background(&page, section, &label_keys))
|
||||
.add(interface_text(&page, section, &label_keys))
|
||||
.add(control_tint(&page, section, &label_keys))
|
||||
.add(
|
||||
settings::item::builder(&descriptions[label_keys["window_hint_toggle"]])
|
||||
.toggler(
|
||||
theme_manager.custom_window_hint().is_some(),
|
||||
Message::UseDefaultWindowHint,
|
||||
),
|
||||
);
|
||||
if theme_manager.custom_window_hint().is_some() {
|
||||
section = section.add(
|
||||
settings::item::builder(&descriptions[label_keys["window_hint"]]).control(
|
||||
page.drawer
|
||||
.accent_window_hint
|
||||
.picker_button(
|
||||
|_| Message::DrawerOpen(ContextView::AccentWindowHint),
|
||||
Some(24),
|
||||
)
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(24.0)),
|
||||
),
|
||||
);
|
||||
}
|
||||
section
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Appearance)
|
||||
})
|
||||
}
|
||||
|
||||
fn container_background<'a>(
|
||||
page: &Page,
|
||||
section: &'a Section<crate::pages::Message>,
|
||||
labels: &HashMap<String, usize>,
|
||||
) -> impl Into<Element<'a, Message>> {
|
||||
let descriptions = §ion.descriptions;
|
||||
let go_next_icon = from_name("go-next-symbolic").handle();
|
||||
|
||||
settings::item::builder(&descriptions[labels["container_bg"]])
|
||||
.description(&descriptions[labels["container_bg_desc"]])
|
||||
.control(
|
||||
if page
|
||||
.drawer
|
||||
.container_background
|
||||
.get_applied_color()
|
||||
.is_some()
|
||||
{
|
||||
Element::from(
|
||||
page.drawer
|
||||
.container_background
|
||||
.picker_button(
|
||||
|_| Message::DrawerOpen(ContextView::ContainerBackground),
|
||||
Some(24),
|
||||
)
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(24.0)),
|
||||
)
|
||||
} else {
|
||||
container(
|
||||
button::text(&descriptions[labels["auto"]])
|
||||
.trailing_icon(go_next_icon.clone())
|
||||
.on_press(Message::DrawerOpen(ContextView::ContainerBackground)),
|
||||
)
|
||||
.into()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn application_background<'a>(
|
||||
page: &Page,
|
||||
section: &'a Section<crate::pages::Message>,
|
||||
labels: &HashMap<String, usize>,
|
||||
) -> impl Into<Element<'a, Message>> {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::item::builder(&descriptions[labels["app_bg"]]).control(
|
||||
page.drawer
|
||||
.application_background
|
||||
.picker_button(
|
||||
|_| Message::DrawerOpen(ContextView::ApplicationBackground),
|
||||
Some(24),
|
||||
)
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(24.0)),
|
||||
)
|
||||
}
|
||||
|
||||
fn control_tint<'a>(
|
||||
page: &Page,
|
||||
section: &'a Section<crate::pages::Message>,
|
||||
labels: &HashMap<String, usize>,
|
||||
) -> impl Into<Element<'a, Message>> {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::item::builder(&descriptions[labels["control_tint"]])
|
||||
.description(&descriptions[labels["control_tint_desc"]])
|
||||
.control(
|
||||
page.drawer
|
||||
.control_component
|
||||
.picker_button(
|
||||
|_| Message::DrawerOpen(ContextView::ControlComponent),
|
||||
Some(24),
|
||||
)
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(24.0)),
|
||||
)
|
||||
}
|
||||
|
||||
fn interface_text<'a>(
|
||||
page: &Page,
|
||||
section: &'a Section<crate::pages::Message>,
|
||||
labels: &HashMap<String, usize>,
|
||||
) -> impl Into<Element<'a, Message>> {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::item::builder(&descriptions[labels["text_tint"]])
|
||||
.description(&descriptions[labels["text_tint_desc"]])
|
||||
.control(
|
||||
page.drawer
|
||||
.interface_text
|
||||
.picker_button(
|
||||
|_| Message::DrawerOpen(ContextView::InterfaceText),
|
||||
Some(24),
|
||||
)
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(24.0)),
|
||||
)
|
||||
}
|
||||
fn auto_switch<'a>(
|
||||
page: &Page,
|
||||
section: &'a Section<crate::pages::Message>,
|
||||
labels: &HashMap<String, usize>,
|
||||
) -> impl Into<Element<'a, Message>> {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::item::builder(&descriptions[labels["auto_switch"]])
|
||||
.description(
|
||||
if !page.day_time && page.theme_manager.mode().is_dark {
|
||||
&descriptions[labels["auto_switch_desc/sunrise"]]
|
||||
} else if page.day_time && !page.theme_manager.mode().is_dark {
|
||||
&descriptions[labels["auto_switch_desc/sunset"]]
|
||||
} else if page.day_time && page.theme_manager.mode().is_dark {
|
||||
&descriptions[labels["auto_switch_desc/next-sunrise"]]
|
||||
} else {
|
||||
&descriptions[labels["auto_switch_desc/next-sunset"]]
|
||||
}
|
||||
.clone(),
|
||||
)
|
||||
.toggler(page.theme_manager.mode().auto_switch, Message::Autoswitch)
|
||||
}
|
||||
|
||||
fn accent_color_palette<'a>(
|
||||
page: &Page,
|
||||
section: &'a Section<crate::pages::Message>,
|
||||
labels: &HashMap<String, usize>,
|
||||
) -> impl Into<Element<'a, Message>> {
|
||||
let Spacing { space_xxs, .. } = cosmic::theme::spacing();
|
||||
let descriptions = §ion.descriptions;
|
||||
let palette = &page.theme_manager.builder().palette.as_ref();
|
||||
let accent = page.theme_manager.accent_palette().as_ref().unwrap();
|
||||
let cur_accent = page
|
||||
.theme_manager
|
||||
.builder()
|
||||
.accent
|
||||
.map_or(palette.accent_blue, Srgba::from);
|
||||
let mut accent_palette_row = cosmic::widget::row::with_capacity(accent.len());
|
||||
|
||||
for &color in accent {
|
||||
accent_palette_row = accent_palette_row.push(color_button(
|
||||
Some(Message::PaletteAccent(color.into())),
|
||||
color.into(),
|
||||
cur_accent == color,
|
||||
48,
|
||||
48,
|
||||
));
|
||||
}
|
||||
|
||||
cosmic::iced::widget::column![
|
||||
text::body(&descriptions[labels["accent_color"]]),
|
||||
scrollable::horizontal(
|
||||
accent_palette_row
|
||||
.push(
|
||||
if let Some(c) = page.drawer.custom_accent.get_applied_color() {
|
||||
container(color_button(
|
||||
Some(Message::DrawerOpen(ContextView::CustomAccent)),
|
||||
c,
|
||||
cosmic::iced::Color::from(cur_accent) == c,
|
||||
48,
|
||||
48,
|
||||
))
|
||||
} else {
|
||||
container(
|
||||
page.drawer
|
||||
.custom_accent
|
||||
.picker_button(
|
||||
|_| Message::DrawerOpen(ContextView::CustomAccent),
|
||||
Some(24),
|
||||
)
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(48.0)),
|
||||
)
|
||||
}
|
||||
)
|
||||
.padding([0, 0, 16, 0])
|
||||
.spacing(16)
|
||||
)
|
||||
]
|
||||
.padding([16, 0, 0, 0])
|
||||
.spacing(space_xxs)
|
||||
}
|
||||
|
||||
fn theme_mode<'a>(
|
||||
page: &Page,
|
||||
section: &'a Section<crate::pages::Message>,
|
||||
labels: &HashMap<String, usize>,
|
||||
) -> impl Into<Element<'a, Message>> {
|
||||
let descriptions = §ion.descriptions;
|
||||
let dark_mode_illustration = from_name("illustration-appearance-mode-dark").handle();
|
||||
let light_mode_illustration = from_name("illustration-appearance-mode-light").handle();
|
||||
|
||||
container(
|
||||
cosmic::iced::widget::row![
|
||||
cosmic::iced::widget::column![
|
||||
button::custom(
|
||||
icon(dark_mode_illustration)
|
||||
.width(Length::Fixed(191.0))
|
||||
.height(Length::Fixed(100.0))
|
||||
)
|
||||
.class(button::ButtonClass::Image)
|
||||
.padding([8, 0])
|
||||
.selected(page.theme_manager.mode().is_dark)
|
||||
.on_press(super::Message::DarkMode(true)),
|
||||
text::body(&descriptions[labels["dark"]])
|
||||
]
|
||||
.spacing(8)
|
||||
.width(Length::FillPortion(1))
|
||||
.align_x(Alignment::Center),
|
||||
cosmic::iced::widget::column![
|
||||
button::custom(
|
||||
icon(light_mode_illustration,)
|
||||
.width(Length::Fixed(191.0))
|
||||
.height(Length::Fixed(100.0))
|
||||
)
|
||||
.class(button::ButtonClass::Image)
|
||||
.selected(!page.theme_manager.mode().is_dark)
|
||||
.padding([8, 0])
|
||||
.on_press(super::Message::DarkMode(false)),
|
||||
text::body(&descriptions[labels["light"]])
|
||||
]
|
||||
.spacing(8)
|
||||
.width(Length::FillPortion(1))
|
||||
.align_x(Alignment::Center)
|
||||
]
|
||||
.spacing(8)
|
||||
.width(Length::Fixed(478.0))
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.center_x(Length::Fill)
|
||||
}
|
||||
|
||||
/// A button for selecting a color or gradient.
|
||||
pub fn color_button<'a, Message: 'a + Clone>(
|
||||
on_press: Option<Message>,
|
||||
color: cosmic::iced::Color,
|
||||
selected: bool,
|
||||
width: u16,
|
||||
height: u16,
|
||||
) -> Element<'a, Message> {
|
||||
button::custom(color_image(
|
||||
wallpaper::Color::Single([color.r, color.g, color.b]),
|
||||
width,
|
||||
height,
|
||||
None,
|
||||
))
|
||||
.padding(0)
|
||||
.selected(selected)
|
||||
.class(button::ButtonClass::Image)
|
||||
.on_press_maybe(on_press)
|
||||
.width(Length::Fixed(f32::from(width)))
|
||||
.height(Length::Fixed(f32::from(height)))
|
||||
.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn i18n() -> (slab::Slab<String>, HashMap<String, usize>) {
|
||||
let mut descriptions = slab::Slab::new();
|
||||
let keys: HashMap<String, usize> = HashMap::from([
|
||||
("auto".into(), descriptions.insert(fl!("auto"))),
|
||||
(
|
||||
"auto_switch".into(),
|
||||
descriptions.insert(fl!("auto-switch")),
|
||||
),
|
||||
(
|
||||
"auto_switch_desc/sunrise".into(),
|
||||
descriptions.insert(fl!("auto-switch", "sunrise")),
|
||||
),
|
||||
(
|
||||
"auto_switch_desc/sunset".into(),
|
||||
descriptions.insert(fl!("auto-switch", "sunrise")),
|
||||
),
|
||||
(
|
||||
"auto_switch_desc/next-sunrise".into(),
|
||||
descriptions.insert(fl!("auto-switch", "next-sunrise")),
|
||||
),
|
||||
(
|
||||
"auto_switch_desc/next-sunset".into(),
|
||||
descriptions.insert(fl!("auto-switch", "next-sunrise")),
|
||||
),
|
||||
(
|
||||
"accent_color".into(),
|
||||
descriptions.insert(fl!("accent-color")),
|
||||
),
|
||||
("app_bg".into(), descriptions.insert(fl!("app-background"))),
|
||||
(
|
||||
"container_bg".into(),
|
||||
descriptions.insert(fl!("container-background")),
|
||||
),
|
||||
(
|
||||
"container_bg_desc".into(),
|
||||
descriptions.insert(fl!("container-background", "desc")),
|
||||
),
|
||||
("text_tint".into(), descriptions.insert(fl!("text-tint"))),
|
||||
(
|
||||
"text_tint_desc".into(),
|
||||
descriptions.insert(fl!("text-tint", "desc")),
|
||||
),
|
||||
(
|
||||
"control_tint".into(),
|
||||
descriptions.insert(fl!("control-tint")),
|
||||
),
|
||||
(
|
||||
"control_tint_desc".into(),
|
||||
descriptions.insert(fl!("control-tint", "desc")),
|
||||
),
|
||||
(
|
||||
"window_hint_toggle".into(),
|
||||
descriptions.insert(fl!("window-hint-accent-toggle")),
|
||||
),
|
||||
(
|
||||
"window_hint".into(),
|
||||
descriptions.insert(fl!("window-hint-accent")),
|
||||
),
|
||||
("dark".into(), descriptions.insert(fl!("dark"))),
|
||||
("light".into(), descriptions.insert(fl!("light"))),
|
||||
]);
|
||||
|
||||
(descriptions, keys)
|
||||
}
|
||||
140
cosmic-settings/src/pages/desktop/appearance/style.rs
Normal file
140
cosmic-settings/src/pages/desktop/appearance/style.rs
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
use cosmic::iced_core::{Alignment, Length};
|
||||
use cosmic::widget::icon::{from_name, icon};
|
||||
use cosmic::widget::{button, container, settings, text};
|
||||
use cosmic::{Apply, Element};
|
||||
use cosmic_settings_page::Section;
|
||||
use slab::Slab;
|
||||
|
||||
use super::{Message, Page, Roundness};
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn section() -> Section<crate::pages::Message> {
|
||||
let mut descriptions = Slab::new();
|
||||
|
||||
let round = descriptions.insert(fl!("style", "round"));
|
||||
let slightly_round = descriptions.insert(fl!("style", "slightly-round"));
|
||||
let square = descriptions.insert(fl!("style", "square"));
|
||||
|
||||
let dark_round_style = from_name("illustration-appearance-dark-style-round").handle();
|
||||
let light_round_style = from_name("illustration-appearance-light-style-round").handle();
|
||||
|
||||
let dark_slightly_round_style =
|
||||
from_name("illustration-appearance-dark-style-slightly-round").handle();
|
||||
let light_slightly_round_style =
|
||||
from_name("illustration-appearance-light-style-slightly-round").handle();
|
||||
|
||||
let dark_square_style = from_name("illustration-appearance-dark-style-square").handle();
|
||||
let light_square_style = from_name("illustration-appearance-light-style-square").handle();
|
||||
|
||||
fn style_container() -> cosmic::theme::Container<'static> {
|
||||
cosmic::theme::Container::custom(|theme| {
|
||||
let mut background = theme.cosmic().palette.neutral_9;
|
||||
background.alpha = 0.1;
|
||||
container::Style {
|
||||
background: Some(cosmic::iced::Background::Color(background.into())),
|
||||
border: cosmic::iced::Border {
|
||||
radius: theme.cosmic().radius_s().into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Section::default()
|
||||
.title(fl!("style"))
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::section()
|
||||
.title(§ion.title)
|
||||
.add(
|
||||
container(
|
||||
cosmic::iced::widget::row![
|
||||
cosmic::iced::widget::column![
|
||||
button::custom(
|
||||
icon(
|
||||
if page.theme_manager.mode().is_dark {
|
||||
&dark_round_style
|
||||
} else {
|
||||
&light_round_style
|
||||
}
|
||||
.clone()
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(100.0))
|
||||
)
|
||||
.selected(matches!(page.roundness, Roundness::Round))
|
||||
.class(button::ButtonClass::Image)
|
||||
.padding(0)
|
||||
.on_press(Message::Roundness(Roundness::Round))
|
||||
.apply(container)
|
||||
.width(Length::Fixed(191.0))
|
||||
.class(style_container()),
|
||||
text::body(&descriptions[round])
|
||||
]
|
||||
.spacing(8)
|
||||
.width(Length::FillPortion(1))
|
||||
.align_x(Alignment::Center),
|
||||
cosmic::iced::widget::column![
|
||||
button::custom(
|
||||
icon(
|
||||
if page.theme_manager.mode().is_dark {
|
||||
&dark_slightly_round_style
|
||||
} else {
|
||||
&light_slightly_round_style
|
||||
}
|
||||
.clone()
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(100.0))
|
||||
)
|
||||
.selected(matches!(page.roundness, Roundness::SlightlyRound))
|
||||
.class(button::ButtonClass::Image)
|
||||
.padding(0)
|
||||
.on_press(Message::Roundness(Roundness::SlightlyRound))
|
||||
.apply(container)
|
||||
.width(Length::Fixed(191.0))
|
||||
.class(style_container()),
|
||||
text::body(&descriptions[slightly_round])
|
||||
]
|
||||
.spacing(8)
|
||||
.width(Length::FillPortion(1))
|
||||
.align_x(Alignment::Center),
|
||||
cosmic::iced::widget::column![
|
||||
button::custom(
|
||||
icon(
|
||||
if page.theme_manager.mode().is_dark {
|
||||
&dark_square_style
|
||||
} else {
|
||||
&light_square_style
|
||||
}
|
||||
.clone()
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(100.0))
|
||||
)
|
||||
.width(Length::FillPortion(1))
|
||||
.selected(matches!(page.roundness, Roundness::Square))
|
||||
.class(button::ButtonClass::Image)
|
||||
.padding(0)
|
||||
.on_press(Message::Roundness(Roundness::Square))
|
||||
.apply(container)
|
||||
.width(Length::Fixed(191.0))
|
||||
.class(style_container()),
|
||||
text::body(&descriptions[square])
|
||||
]
|
||||
.spacing(8)
|
||||
.align_x(Alignment::Center)
|
||||
.width(Length::FillPortion(1))
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(Alignment::Center),
|
||||
)
|
||||
.center_x(Length::Fill),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Appearance)
|
||||
})
|
||||
}
|
||||
535
cosmic-settings/src/pages/desktop/appearance/theme_manager.rs
Normal file
535
cosmic-settings/src/pages/desktop/appearance/theme_manager.rs
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
use cosmic::cosmic_config::{Config, ConfigSet, CosmicConfigEntry};
|
||||
use cosmic::cosmic_theme::palette::{Srgb, Srgba};
|
||||
use cosmic::cosmic_theme::{
|
||||
CornerRadii, DARK_THEME_BUILDER_ID, LIGHT_THEME_BUILDER_ID, Spacing, Theme, ThemeBuilder,
|
||||
ThemeMode,
|
||||
};
|
||||
use cosmic::iced_core::Color;
|
||||
|
||||
use cosmic::Task;
|
||||
use cosmic::theme::ThemeType;
|
||||
use std::borrow::BorrowMut;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::app;
|
||||
|
||||
use super::ContextView;
|
||||
|
||||
pub enum ThemeStaged {
|
||||
Current,
|
||||
Both,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Manager {
|
||||
mode: (ThemeMode, Option<Config>),
|
||||
light: ThemeCustomizer,
|
||||
dark: ThemeCustomizer,
|
||||
|
||||
custom_accent: Option<Srgb>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ThemeCustomizer {
|
||||
builder: (ThemeBuilder, Option<Config>),
|
||||
theme: (Theme, Option<Config>),
|
||||
accent_palette: Option<Vec<Srgba>>,
|
||||
custom_window_hint: Option<Srgb>,
|
||||
}
|
||||
|
||||
impl From<(Option<Config>, Option<Config>, Option<Vec<Srgba>>)> for ThemeCustomizer {
|
||||
fn from(
|
||||
(theme_config, builder_config, palette): (
|
||||
Option<Config>,
|
||||
Option<Config>,
|
||||
Option<Vec<Srgba>>,
|
||||
),
|
||||
) -> Self {
|
||||
let theme = Theme::get_entry(theme_config.as_ref().unwrap()).unwrap_or_default();
|
||||
|
||||
let mut theme_builder = match ThemeBuilder::get_entry(builder_config.as_ref().unwrap()) {
|
||||
Ok(t) => t,
|
||||
Err((errors, t)) => {
|
||||
for e in errors {
|
||||
tracing::error!("{e}");
|
||||
}
|
||||
t
|
||||
}
|
||||
};
|
||||
|
||||
theme_builder = theme_builder
|
||||
.accent(theme.accent.base.color)
|
||||
.bg_color(theme.bg_color())
|
||||
.corner_radii(theme.corner_radii)
|
||||
.destructive(theme.destructive.base.color)
|
||||
.spacing(theme.spacing)
|
||||
.success(theme.success.base.color)
|
||||
.warning(theme.warning.base.color)
|
||||
.neutral_tint(theme.palette.neutral_5.color)
|
||||
.text_tint(theme.background.on.color);
|
||||
|
||||
theme_builder.gaps = theme.gaps;
|
||||
|
||||
let mut customizer = Self {
|
||||
builder: (theme_builder, builder_config),
|
||||
theme: (theme, theme_config),
|
||||
accent_palette: palette,
|
||||
custom_window_hint: None,
|
||||
};
|
||||
|
||||
if let None = customizer.accent_palette {
|
||||
let palette = customizer.builder.0.palette.as_ref();
|
||||
customizer.accent_palette = Some(vec![
|
||||
palette.accent_blue,
|
||||
palette.accent_indigo,
|
||||
palette.accent_purple,
|
||||
palette.accent_pink,
|
||||
palette.accent_red,
|
||||
palette.accent_orange,
|
||||
palette.accent_yellow,
|
||||
palette.accent_green,
|
||||
palette.accent_warm_grey,
|
||||
]);
|
||||
}
|
||||
|
||||
customizer
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Manager {
|
||||
fn default() -> Self {
|
||||
let settings_config = crate::config::Config::new();
|
||||
|
||||
let theme_mode_config = ThemeMode::config().ok();
|
||||
let theme_mode = theme_mode_config
|
||||
.as_ref()
|
||||
.map(|c| match ThemeMode::get_entry(c) {
|
||||
Ok(t) => t,
|
||||
Err((errors, t)) => {
|
||||
for e in errors {
|
||||
tracing::error!("{e}");
|
||||
}
|
||||
t
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut manager = Self {
|
||||
mode: (theme_mode, theme_mode_config),
|
||||
light: (
|
||||
Theme::light_config().ok(),
|
||||
ThemeBuilder::light_config().ok(),
|
||||
settings_config.accent_palette_light().ok(),
|
||||
)
|
||||
.into(),
|
||||
dark: (
|
||||
Theme::dark_config().ok(),
|
||||
ThemeBuilder::dark_config().ok(),
|
||||
settings_config.accent_palette_dark().ok(),
|
||||
)
|
||||
.into(),
|
||||
custom_accent: None,
|
||||
};
|
||||
|
||||
let customizer = manager.selected_customizer();
|
||||
manager.custom_accent = customizer.builder.0.accent.filter(|c| {
|
||||
let c = Srgba::new(c.red, c.green, c.blue, 1.0);
|
||||
let theme = &customizer.theme.0;
|
||||
c != theme.palette.accent_blue
|
||||
&& c != theme.palette.accent_green
|
||||
&& c != theme.palette.accent_indigo
|
||||
&& c != theme.palette.accent_orange
|
||||
&& c != theme.palette.accent_pink
|
||||
&& c != theme.palette.accent_purple
|
||||
&& c != theme.palette.accent_red
|
||||
&& c != theme.palette.accent_warm_grey
|
||||
&& c != theme.palette.accent_yellow
|
||||
});
|
||||
|
||||
manager
|
||||
}
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn build_theme<'a>(&mut self, stage: ThemeStaged) -> Task<app::Message> {
|
||||
macro_rules! theme_transaction {
|
||||
($config:ident, $current_theme:ident, $new_theme:ident, { $($name:ident;)+ }) => {
|
||||
let tx = $config.transaction();
|
||||
|
||||
$(
|
||||
if $current_theme.$name != $new_theme.$name {
|
||||
_ = tx.set(stringify!($name), $new_theme.$name.clone());
|
||||
}
|
||||
)+
|
||||
|
||||
_ = tx.commit();
|
||||
}
|
||||
}
|
||||
|
||||
let mut tasks: Vec<Task<app::Message>> = Vec::new();
|
||||
let customizers = match stage {
|
||||
ThemeStaged::Current => vec![self.selected_customizer_mut()],
|
||||
ThemeStaged::Both => vec![self.light.borrow_mut(), self.dark.borrow_mut()],
|
||||
};
|
||||
|
||||
customizers.into_iter().for_each(|customizer| {
|
||||
let builder = customizer.builder.0.clone();
|
||||
let (current_theme, config) = customizer.theme.clone();
|
||||
|
||||
tasks.push(cosmic::task::future(async move {
|
||||
if let Some(config) = config {
|
||||
let new_theme = builder.build();
|
||||
|
||||
theme_transaction!(config, current_theme, new_theme, {
|
||||
accent;
|
||||
accent_button;
|
||||
background;
|
||||
button;
|
||||
destructive;
|
||||
destructive_button;
|
||||
link_button;
|
||||
icon_button;
|
||||
palette;
|
||||
primary;
|
||||
secondary;
|
||||
shade;
|
||||
success;
|
||||
text_button;
|
||||
warning;
|
||||
warning_button;
|
||||
window_hint;
|
||||
});
|
||||
|
||||
app::Message::from(super::Message::NewTheme(Box::new(new_theme)))
|
||||
} else {
|
||||
app::Message::None
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
cosmic::task::batch(tasks)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn selected_customizer(&self) -> &ThemeCustomizer {
|
||||
if self.mode.0.is_dark {
|
||||
&self.dark
|
||||
} else {
|
||||
&self.light
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn selected_customizer_mut(&mut self) -> &mut ThemeCustomizer {
|
||||
if self.mode.0.is_dark {
|
||||
&mut self.dark
|
||||
} else {
|
||||
&mut self.light
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn theme(&self) -> &Theme {
|
||||
&self.selected_customizer().theme.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mode(&self) -> &ThemeMode {
|
||||
&self.mode.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn builder(&self) -> &ThemeBuilder {
|
||||
&self.selected_customizer().builder.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn custom_accent(&self) -> &Option<Srgb> {
|
||||
&self.custom_accent
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn accent_palette(&self) -> &Option<Vec<Srgba>> {
|
||||
&self.selected_customizer().accent_palette
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn custom_window_hint(&self) -> &Option<Srgb> {
|
||||
&self.selected_customizer().custom_window_hint()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn theme_mode_config(&self) -> &Option<Config> {
|
||||
&self.mode.1
|
||||
}
|
||||
|
||||
pub fn dark_mode(&mut self, enabled: bool) -> Result<bool, cosmic_config::Error> {
|
||||
if let Some(config) = self.mode.1.as_ref() {
|
||||
return self.mode.0.set_is_dark(config, enabled);
|
||||
}
|
||||
|
||||
self.mode.0.is_dark = enabled;
|
||||
|
||||
let (theme_id, builder_fn): (&str, fn() -> ThemeBuilder) = if enabled {
|
||||
(DARK_THEME_BUILDER_ID, ThemeBuilder::dark)
|
||||
} else {
|
||||
(LIGHT_THEME_BUILDER_ID, ThemeBuilder::light)
|
||||
};
|
||||
|
||||
let builder = cosmic::cosmic_config::Config::system(theme_id, ThemeBuilder::VERSION)
|
||||
.map_or_else(
|
||||
|_| builder_fn(),
|
||||
|config| match ThemeBuilder::get_entry(&config) {
|
||||
Ok(t) => t,
|
||||
Err((errs, t)) => {
|
||||
for err in errs {
|
||||
tracing::warn!(?err, "Error getting system theme builder");
|
||||
}
|
||||
t
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
self.selected_customizer_mut().set_builder(builder);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn auto_switch(&mut self, enabled: bool) {
|
||||
self.mode.0.auto_switch = enabled;
|
||||
|
||||
if let Some(config) = self.mode.1.as_ref() {
|
||||
_ = config.set::<bool>("auto_switch", enabled);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make it rollback if the first operation succeeds and the second
|
||||
// one fails?
|
||||
pub fn set_active_hint(&mut self, active_hint: u32) -> Option<ThemeStaged> {
|
||||
self.dark.set_active_hint(active_hint)?;
|
||||
self.light.set_active_hint(active_hint)?;
|
||||
Some(ThemeStaged::Both)
|
||||
}
|
||||
|
||||
// TODO: Make it rollback if the first operation succeeds and the second
|
||||
// one fails?
|
||||
pub fn set_spacing(&mut self, spacing: Spacing) -> Option<ThemeStaged> {
|
||||
self.dark.set_spacing(spacing)?;
|
||||
self.light.set_spacing(spacing)?;
|
||||
Some(ThemeStaged::Both)
|
||||
}
|
||||
|
||||
pub fn set_gap_size(&mut self, gap: u32) -> Option<ThemeStaged> {
|
||||
self.dark.set_gap_size(gap)?;
|
||||
self.light.set_gap_size(gap)?;
|
||||
Some(ThemeStaged::Both)
|
||||
}
|
||||
|
||||
pub fn get_color(&self, context: &ContextView) -> Option<Color> {
|
||||
match *context {
|
||||
ContextView::CustomAccent => self.custom_accent().map(Color::from),
|
||||
ContextView::ApplicationBackground => self.builder().bg_color.map(Color::from),
|
||||
ContextView::ContainerBackground => {
|
||||
self.builder().primary_container_bg.map(Color::from)
|
||||
}
|
||||
ContextView::InterfaceText => self.builder().text_tint.map(Color::from),
|
||||
ContextView::ControlComponent => self.builder().neutral_tint.map(Color::from),
|
||||
ContextView::AccentWindowHint => self.builder().window_hint.map(Color::from),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color(
|
||||
&mut self,
|
||||
color: Option<Color>,
|
||||
context: &ContextView,
|
||||
) -> Option<ThemeStaged> {
|
||||
let theme_customizer = self.selected_customizer_mut();
|
||||
match *context {
|
||||
ContextView::CustomAccent => theme_customizer.set_accent(color.map(Srgb::from)),
|
||||
ContextView::ApplicationBackground => {
|
||||
theme_customizer.set_bg_color(color.map(Srgba::from))
|
||||
}
|
||||
ContextView::ContainerBackground => {
|
||||
theme_customizer.set_primary_container_bg(color.map(Srgba::from))
|
||||
}
|
||||
ContextView::InterfaceText => theme_customizer.set_text_tint(color.map(Srgb::from)),
|
||||
ContextView::ControlComponent => {
|
||||
theme_customizer.set_neutral_tint(color.map(Srgb::from))
|
||||
}
|
||||
ContextView::AccentWindowHint => {
|
||||
theme_customizer.set_window_hint(color.map(Srgb::from))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cosmic_theme(&self) -> cosmic::Theme {
|
||||
cosmic::Theme {
|
||||
theme_type: ThemeType::Custom(Arc::new(self.theme().clone())),
|
||||
..cosmic::Theme::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThemeCustomizer {
|
||||
/// Set theme builder without writing to cosmic-config.
|
||||
pub fn set_builder(&mut self, builder: ThemeBuilder) -> &mut Self {
|
||||
self.builder.0 = builder;
|
||||
self
|
||||
}
|
||||
|
||||
/// Write theme builder to cosmic-config, notifying all subscribers.
|
||||
pub fn apply_builder(&mut self) -> &mut Self {
|
||||
if let Some(config) = self.builder.1.as_ref() {
|
||||
let _ = self.builder.0.write_entry(config);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Set theme without writing to cosmic-config.
|
||||
pub fn set_theme(&mut self, theme: Theme) -> &mut Self {
|
||||
self.theme.0 = theme;
|
||||
self
|
||||
}
|
||||
|
||||
/// Write theme to cosmic-config, notifying all subscribers.
|
||||
pub fn apply_theme(&mut self) -> &mut Self {
|
||||
if let Some(config) = self.theme.1.as_ref() {
|
||||
let _ = self.theme.0.write_entry(config);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_window_hint(&mut self, color: Option<Srgb>) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
|
||||
self.custom_window_hint = color;
|
||||
self.builder.0.set_window_hint(config, color).ok()?;
|
||||
self.theme
|
||||
.0
|
||||
.set_window_hint(self.theme.1.as_ref()?, color)
|
||||
.ok()?;
|
||||
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
pub fn custom_window_hint(&self) -> &Option<Srgb> {
|
||||
&self.custom_window_hint
|
||||
}
|
||||
|
||||
pub fn set_bg_color(&mut self, color: Option<Srgba>) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
|
||||
self.builder.0.set_bg_color(config, color).ok()?;
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
pub fn set_primary_container_bg(&mut self, color: Option<Srgba>) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
|
||||
self.builder
|
||||
.0
|
||||
.set_primary_container_bg(config, color)
|
||||
.ok()?;
|
||||
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
pub fn set_accent(&mut self, color: Option<Srgb>) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
|
||||
self.builder.0.set_accent(config, color).ok()?;
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
pub fn set_text_tint(&mut self, color: Option<Srgb>) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
|
||||
self.builder.0.set_text_tint(config, color).ok()?;
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
pub fn set_neutral_tint(&mut self, color: Option<Srgb>) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
|
||||
self.builder.0.set_neutral_tint(config, color).ok()?;
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
pub fn set_spacing(&mut self, spacing: Spacing) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
|
||||
self.builder.0.set_spacing(config, spacing).ok()?;
|
||||
self.theme
|
||||
.0
|
||||
.set_spacing(self.theme.1.as_ref()?, spacing)
|
||||
.ok()?;
|
||||
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
pub fn set_corner_radii(&mut self, corner_radii: CornerRadii) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
|
||||
self.builder.0.set_corner_radii(config, corner_radii).ok()?;
|
||||
|
||||
self.theme
|
||||
.0
|
||||
.set_corner_radii(self.theme.1.as_ref()?, corner_radii)
|
||||
.ok()?;
|
||||
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
pub fn set_gap_size(&mut self, gap: u32) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
let builder = &mut self.builder.0;
|
||||
let mut gaps = builder.gaps;
|
||||
|
||||
// Ensure that the gap is never less than what the active hint size is.
|
||||
gaps.1 = if gap < builder.active_hint {
|
||||
builder.active_hint
|
||||
} else {
|
||||
gap
|
||||
};
|
||||
|
||||
if let Err(err) = builder.set_gaps(config, gaps) {
|
||||
tracing::error!(?err, "Error setting the gap");
|
||||
return None;
|
||||
}
|
||||
|
||||
self.theme.0.set_gaps(self.theme.1.as_ref()?, gaps).ok()?;
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
|
||||
// set active hints is set on all themes to be consistent between dark & light themes.
|
||||
pub fn set_active_hint(&mut self, active_hint: u32) -> Option<ThemeStaged> {
|
||||
let config = self.builder.1.as_ref()?;
|
||||
let builder = &mut self.builder.0;
|
||||
|
||||
if let Err(err) = builder.set_active_hint(config, active_hint) {
|
||||
tracing::error!(?err, "Error setting the active hint");
|
||||
return None;
|
||||
}
|
||||
|
||||
// Update the gap if it's less than the active hint
|
||||
if active_hint > builder.gaps.1 {
|
||||
let mut gaps = builder.gaps;
|
||||
gaps.1 = active_hint;
|
||||
if builder.set_gaps(config, gaps).unwrap_or_default() {
|
||||
let _ = self.theme.0.set_active_hint(self.theme.1.as_ref()?, gaps.1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the active_hint in the config
|
||||
self.theme
|
||||
.0
|
||||
.set_active_hint(self.theme.1.as_ref()?, active_hint)
|
||||
.ok()?;
|
||||
|
||||
Some(ThemeStaged::Current)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ mod config;
|
|||
pub mod widgets;
|
||||
|
||||
pub use config::Config;
|
||||
use futures::StreamExt;
|
||||
use url::Url;
|
||||
|
||||
use std::{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue