Merge pull request #232 from joshuamegnauth54/list-icon-themes
feat: Preview icon themes
This commit is contained in:
commit
462f310df2
2 changed files with 299 additions and 30 deletions
|
|
@ -2,8 +2,8 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ashpd::desktop::file_chooser::{FileFilter, SelectedFiles};
|
use ashpd::desktop::file_chooser::{FileFilter, SelectedFiles};
|
||||||
|
|
@ -13,13 +13,12 @@ use cosmic::cosmic_theme::palette::{FromColor, Hsv, Srgb, Srgba};
|
||||||
use cosmic::cosmic_theme::{
|
use cosmic::cosmic_theme::{
|
||||||
CornerRadii, Theme, ThemeBuilder, ThemeMode, DARK_THEME_BUILDER_ID, LIGHT_THEME_BUILDER_ID,
|
CornerRadii, Theme, ThemeBuilder, ThemeMode, DARK_THEME_BUILDER_ID, LIGHT_THEME_BUILDER_ID,
|
||||||
};
|
};
|
||||||
use cosmic::iced_core::{alignment, Color, Length};
|
use cosmic::iced_core::{alignment, Background, Color, Length};
|
||||||
use cosmic::iced_widget::scrollable;
|
use cosmic::iced_widget::scrollable;
|
||||||
use cosmic::prelude::CollectionWidget;
|
use cosmic::prelude::CollectionWidget;
|
||||||
use cosmic::widget::dropdown;
|
use cosmic::widget::icon::{self, from_name, icon};
|
||||||
use cosmic::widget::icon::{from_name, icon};
|
|
||||||
use cosmic::widget::{
|
use cosmic::widget::{
|
||||||
button, color_picker::ColorPickerUpdate, container, horizontal_space, row, settings,
|
button, color_picker::ColorPickerUpdate, container, flex_row, horizontal_space, row, settings,
|
||||||
spin_button, text, ColorPickerModel,
|
spin_button, text, ColorPickerModel,
|
||||||
};
|
};
|
||||||
use cosmic::Apply;
|
use cosmic::Apply;
|
||||||
|
|
@ -35,7 +34,13 @@ use crate::app;
|
||||||
|
|
||||||
use super::wallpaper::widgets::color_image;
|
use super::wallpaper::widgets::color_image;
|
||||||
|
|
||||||
|
const ICON_PREV_N: usize = 6;
|
||||||
|
const ICON_PREV_ROW: usize = 3;
|
||||||
|
const ICON_TRY_SIZES: [u16; 3] = [32, 48, 64];
|
||||||
|
const ICON_THUMB_SIZE: u16 = 32;
|
||||||
|
const ICON_NAME_TRUNC: usize = 20;
|
||||||
type IconThemes = Vec<String>;
|
type IconThemes = Vec<String>;
|
||||||
|
type IconHandles = Vec<[icon::Handle; ICON_PREV_N]>;
|
||||||
|
|
||||||
crate::cache_dynamic_lazy! {
|
crate::cache_dynamic_lazy! {
|
||||||
static HEX: String = fl!("hex");
|
static HEX: String = fl!("hex");
|
||||||
|
|
@ -52,6 +57,7 @@ enum ContextView {
|
||||||
ContainerBackground,
|
ContainerBackground,
|
||||||
ControlComponent,
|
ControlComponent,
|
||||||
CustomAccent,
|
CustomAccent,
|
||||||
|
Experimental,
|
||||||
InterfaceText,
|
InterfaceText,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +74,8 @@ pub struct Page {
|
||||||
roundness: Roundness,
|
roundness: Roundness,
|
||||||
|
|
||||||
icon_theme_active: Option<usize>,
|
icon_theme_active: Option<usize>,
|
||||||
icon_themes: Vec<String>,
|
icon_themes: IconThemes,
|
||||||
|
icon_handles: IconHandles,
|
||||||
|
|
||||||
theme_mode: ThemeMode,
|
theme_mode: ThemeMode,
|
||||||
theme_mode_config: Option<Config>,
|
theme_mode_config: Option<Config>,
|
||||||
|
|
@ -190,6 +197,7 @@ impl
|
||||||
no_custom_window_hint: theme_builder.accent.is_some(),
|
no_custom_window_hint: theme_builder.accent.is_some(),
|
||||||
icon_theme_active: None,
|
icon_theme_active: None,
|
||||||
icon_themes: Vec::new(),
|
icon_themes: Vec::new(),
|
||||||
|
icon_handles: Vec::new(),
|
||||||
theme_mode_config,
|
theme_mode_config,
|
||||||
theme_builder_config,
|
theme_builder_config,
|
||||||
theme_mode,
|
theme_mode,
|
||||||
|
|
@ -267,7 +275,8 @@ pub enum Message {
|
||||||
ControlComponent(ColorPickerUpdate),
|
ControlComponent(ColorPickerUpdate),
|
||||||
CustomAccent(ColorPickerUpdate),
|
CustomAccent(ColorPickerUpdate),
|
||||||
DarkMode(bool),
|
DarkMode(bool),
|
||||||
Entered(IconThemes),
|
Entered((IconThemes, IconHandles)),
|
||||||
|
ExperimentalContextDrawer,
|
||||||
ExportError,
|
ExportError,
|
||||||
ExportFile(Arc<SelectedFiles>),
|
ExportFile(Arc<SelectedFiles>),
|
||||||
ExportSuccess,
|
ExportSuccess,
|
||||||
|
|
@ -439,6 +448,49 @@ impl Page {
|
||||||
.map(crate::pages::Message::Appearance)
|
.map(crate::pages::Message::Appearance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn experimental_context_view(&self) -> Element<'_, crate::pages::Message> {
|
||||||
|
let active = self.icon_theme_active;
|
||||||
|
let theme = cosmic::theme::active();
|
||||||
|
let theme = theme.cosmic();
|
||||||
|
cosmic::iced::widget::column![
|
||||||
|
// Export theme choice
|
||||||
|
settings::view_section("").add(
|
||||||
|
settings::item::builder(fl!("enable-export"))
|
||||||
|
.description(fl!("enable-export", "desc"))
|
||||||
|
.toggler(self.tk.apply_theme_global, Message::ApplyThemeGlobal)
|
||||||
|
),
|
||||||
|
// Icon theme previews
|
||||||
|
// cosmic::iced::widget::column![text(&*ICON_THEME), text(&*ICON_THEME_DESC).size(10)]
|
||||||
|
// .spacing(2),
|
||||||
|
cosmic::widget::column::with_children(vec![
|
||||||
|
text::heading(&*ICON_THEME).into(),
|
||||||
|
scrollable(
|
||||||
|
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_theme_button(theme, handles, i, selected)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.row_spacing(theme.space_xs())
|
||||||
|
.column_spacing(theme.space_xxxs())
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
])
|
||||||
|
.spacing(theme.space_xxs())
|
||||||
|
]
|
||||||
|
// .padding(theme.space_s())
|
||||||
|
.spacing(theme.space_m())
|
||||||
|
// .align_items(cosmic::iced_core::Alignment::Center)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.apply(Element::from)
|
||||||
|
.map(crate::pages::Message::Appearance)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn update(&mut self, message: Message) -> Command<app::Message> {
|
pub fn update(&mut self, message: Message) -> Command<app::Message> {
|
||||||
self.theme_builder_needs_update = false;
|
self.theme_builder_needs_update = false;
|
||||||
|
|
@ -572,7 +624,7 @@ impl Page {
|
||||||
self.theme_builder_needs_update = true;
|
self.theme_builder_needs_update = true;
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::Entered(icon_themes) => {
|
Message::Entered((icon_themes, icon_handles)) => {
|
||||||
*self = Self::default();
|
*self = Self::default();
|
||||||
|
|
||||||
// Set the icon themes, and define the active icon theme.
|
// Set the icon themes, and define the active icon theme.
|
||||||
|
|
@ -581,6 +633,7 @@ impl Page {
|
||||||
.icon_themes
|
.icon_themes
|
||||||
.iter()
|
.iter()
|
||||||
.position(|theme| theme == &self.tk.icon_theme);
|
.position(|theme| theme == &self.tk.icon_theme);
|
||||||
|
self.icon_handles = icon_handles;
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::Left => Command::perform(async {}, |()| {
|
Message::Left => Command::perform(async {}, |()| {
|
||||||
|
|
@ -832,6 +885,10 @@ impl Page {
|
||||||
}
|
}
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
Message::ExperimentalContextDrawer => {
|
||||||
|
self.context_view = Some(ContextView::Experimental);
|
||||||
|
cosmic::command::message(crate::app::Message::OpenContextDrawer("".into()))
|
||||||
|
}
|
||||||
Message::Daytime(day_time) => {
|
Message::Daytime(day_time) => {
|
||||||
self.day_time = day_time;
|
self.day_time = day_time;
|
||||||
Command::none()
|
Command::none()
|
||||||
|
|
@ -941,6 +998,7 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
sections.insert(mode_and_colors()),
|
sections.insert(mode_and_colors()),
|
||||||
sections.insert(style()),
|
sections.insert(style()),
|
||||||
sections.insert(window_management()),
|
sections.insert(window_management()),
|
||||||
|
sections.insert(experimental()),
|
||||||
sections.insert(reset_button()),
|
sections.insert(reset_button()),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
@ -1019,6 +1077,8 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
|this| &this.custom_accent,
|
|this| &this.custom_accent,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
ContextView::Experimental => self.experimental_context_view(),
|
||||||
|
|
||||||
ContextView::InterfaceText => self.color_picker_context_view(
|
ContextView::InterfaceText => self.color_picker_context_view(
|
||||||
None,
|
None,
|
||||||
RESET_TO_DEFAULT.as_str().into(),
|
RESET_TO_DEFAULT.as_str().into(),
|
||||||
|
|
@ -1301,10 +1361,6 @@ pub fn style() -> Section<crate::pages::Message> {
|
||||||
fl!("style", "square").into(),
|
fl!("style", "square").into(),
|
||||||
fl!("frosted").into(),
|
fl!("frosted").into(),
|
||||||
fl!("frosted", "desc").into(),
|
fl!("frosted", "desc").into(),
|
||||||
fl!("enable-export").into(),
|
|
||||||
fl!("enable-export", "desc").into(),
|
|
||||||
ICON_THEME.as_str().into(),
|
|
||||||
ICON_THEME_DESC.as_str().into(),
|
|
||||||
])
|
])
|
||||||
.view::<Page>(|_binder, page, section| {
|
.view::<Page>(|_binder, page, section| {
|
||||||
let descriptions = §ion.descriptions;
|
let descriptions = §ion.descriptions;
|
||||||
|
|
@ -1393,20 +1449,6 @@ pub fn style() -> Section<crate::pages::Message> {
|
||||||
.description(&*descriptions[4])
|
.description(&*descriptions[4])
|
||||||
.toggler(page.theme_builder.is_frosted, Message::Frosted),
|
.toggler(page.theme_builder.is_frosted, Message::Frosted),
|
||||||
)
|
)
|
||||||
.add(
|
|
||||||
settings::item::builder(&*descriptions[5])
|
|
||||||
.description(&*descriptions[6])
|
|
||||||
.toggler(page.tk.apply_theme_global, Message::ApplyThemeGlobal),
|
|
||||||
)
|
|
||||||
.add(
|
|
||||||
settings::item::builder(&*ICON_THEME)
|
|
||||||
.description(&*ICON_THEME_DESC)
|
|
||||||
.control(dropdown(
|
|
||||||
&page.icon_themes,
|
|
||||||
page.icon_theme_active,
|
|
||||||
Message::IconTheme,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.apply(Element::from)
|
.apply(Element::from)
|
||||||
.map(crate::pages::Message::Appearance)
|
.map(crate::pages::Message::Appearance)
|
||||||
})
|
})
|
||||||
|
|
@ -1441,6 +1483,23 @@ pub fn window_management() -> Section<crate::pages::Message> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn experimental() -> Section<crate::pages::Message> {
|
||||||
|
Section::default()
|
||||||
|
.descriptions(vec![fl!("experimental-settings").into()])
|
||||||
|
.view::<Page>(|_binder, _page, section| {
|
||||||
|
let descriptions = &*section.descriptions;
|
||||||
|
settings::view_section("")
|
||||||
|
.add(
|
||||||
|
settings::item::builder(&*descriptions[0]).control(
|
||||||
|
button::icon(from_name("go-next-symbolic"))
|
||||||
|
.on_press(Message::ExperimentalContextDrawer),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.apply(Element::from)
|
||||||
|
.map(crate::pages::Message::Appearance)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn reset_button() -> Section<crate::pages::Message> {
|
pub fn reset_button() -> Section<crate::pages::Message> {
|
||||||
Section::default()
|
Section::default()
|
||||||
|
|
@ -1486,7 +1545,8 @@ pub fn color_button<'a, Message: 'a + Clone>(
|
||||||
|
|
||||||
/// Find all icon themes available on the system.
|
/// Find all icon themes available on the system.
|
||||||
async fn fetch_icon_themes() -> Message {
|
async fn fetch_icon_themes() -> Message {
|
||||||
let mut icon_themes = BTreeSet::new();
|
let mut icon_themes = BTreeMap::new();
|
||||||
|
let mut theme_paths: BTreeMap<String, PathBuf> = BTreeMap::new();
|
||||||
|
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
|
||||||
|
|
@ -1533,6 +1593,7 @@ async fn fetch_icon_themes() -> Message {
|
||||||
|
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
let mut name = None;
|
let mut name = None;
|
||||||
|
let mut valid_dirs = Vec::new();
|
||||||
|
|
||||||
let mut line_reader = tokio::io::BufReader::new(file);
|
let mut line_reader = tokio::io::BufReader::new(file);
|
||||||
while let Ok(read) = line_reader.read_line(&mut buffer).await {
|
while let Ok(read) = line_reader.read_line(&mut buffer).await {
|
||||||
|
|
@ -1550,16 +1611,46 @@ async fn fetch_icon_themes() -> Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if valid_dirs.is_empty() {
|
||||||
|
if let Some(value) = buffer.strip_prefix("Inherits=") {
|
||||||
|
valid_dirs.extend(value.trim().split(',').map(|fallback| {
|
||||||
|
if let Some(path) = theme_paths.get(fallback) {
|
||||||
|
path.iter()
|
||||||
|
.last()
|
||||||
|
.and_then(|os| os.to_str().map(ToOwned::to_owned))
|
||||||
|
.unwrap_or_else(|| fallback.to_owned())
|
||||||
|
} else {
|
||||||
|
fallback.to_owned()
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
icon_themes.insert(name);
|
// Name of the directory theme was found in (e.g. Pop for Pop)
|
||||||
|
valid_dirs.push(
|
||||||
|
path.iter()
|
||||||
|
.last()
|
||||||
|
.and_then(|os| os.to_str().map(ToOwned::to_owned))
|
||||||
|
.unwrap_or_else(|| name.clone()),
|
||||||
|
);
|
||||||
|
theme_paths.entry(name.clone()).or_insert(path);
|
||||||
|
|
||||||
|
let theme = name.clone();
|
||||||
|
// `icon::from_name` may perform blocking I/O
|
||||||
|
if let Ok(handles) =
|
||||||
|
tokio::task::spawn_blocking(|| preview_handles(theme, valid_dirs)).await
|
||||||
|
{
|
||||||
|
icon_themes.insert(name, handles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Entered(icon_themes.into_iter().collect())
|
Message::Entered(icon_themes.into_iter().unzip())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the preferred icon theme for GNOME/GTK applications.
|
/// Set the preferred icon theme for GNOME/GTK applications.
|
||||||
|
|
@ -1574,3 +1665,179 @@ async fn set_gnome_icon_theme(theme: String) {
|
||||||
.status()
|
.status()
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate [icon::Handle]s to use for icon theme previews.
|
||||||
|
fn preview_handles(theme: String, inherits: Vec<String>) -> [icon::Handle; ICON_PREV_N] {
|
||||||
|
// Cache current default and set icon theme as a temporary default
|
||||||
|
let default = cosmic::icon_theme::default();
|
||||||
|
cosmic::icon_theme::set_default(theme);
|
||||||
|
|
||||||
|
// Evaluate handles with the temporary theme
|
||||||
|
let handles = [
|
||||||
|
icon_handle("folder", "folder-symbolic", &inherits),
|
||||||
|
icon_handle("user-home", "user-home-symbolic", &inherits),
|
||||||
|
icon_handle(
|
||||||
|
"preferences-system",
|
||||||
|
"preferences-system-symbolic",
|
||||||
|
&inherits,
|
||||||
|
),
|
||||||
|
icon_handle("image-x-generic", "images-x-generic-symbolic", &inherits),
|
||||||
|
icon_handle("audio-x-generic", "audio-x-generic-symbolic", &inherits),
|
||||||
|
icon_handle("video-x-generic", "video-x-generic-symbolic", &inherits),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Reset default icon theme.
|
||||||
|
cosmic::icon_theme::set_default(default);
|
||||||
|
handles
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate an icon handle for a specific theme.
|
||||||
|
///
|
||||||
|
/// `alternate` is a fallback icon name such as a symbolic variant.
|
||||||
|
///
|
||||||
|
/// `valid_dirs` should be a slice of directories from which we consider an icon to be valid. Valid
|
||||||
|
/// directories would usually be inherited themes as well as the actual theme's location.
|
||||||
|
fn icon_handle(icon_name: &str, alternate: &str, valid_dirs: &[String]) -> icon::Handle {
|
||||||
|
ICON_TRY_SIZES
|
||||||
|
.iter()
|
||||||
|
.zip(std::iter::repeat(icon_name).take(ICON_TRY_SIZES.len()))
|
||||||
|
// Try fallback icon name after the default
|
||||||
|
.chain(
|
||||||
|
ICON_TRY_SIZES
|
||||||
|
.iter()
|
||||||
|
.zip(std::iter::repeat(alternate))
|
||||||
|
.take(ICON_TRY_SIZES.len()),
|
||||||
|
)
|
||||||
|
.find_map(|(&size, name)| {
|
||||||
|
icon::from_name(name)
|
||||||
|
// Set the size on the handle to evaluate the correct icon
|
||||||
|
.size(size)
|
||||||
|
// Get the path to the icon for the currently set theme.
|
||||||
|
// Without the exact path, the handles will all resolve to icons from the same theme in
|
||||||
|
// [`icon_theme_button`] rather than the icons for each different theme
|
||||||
|
.path()
|
||||||
|
// `libcosmic` should always return a path if the default theme is installed
|
||||||
|
// The returned path has to be verified as an icon from the set theme or an
|
||||||
|
// inherited theme
|
||||||
|
.and_then(|path| {
|
||||||
|
let mut theme_dir = &*path;
|
||||||
|
while let Some(parent) = theme_dir.parent() {
|
||||||
|
if parent.ends_with("icons") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
theme_dir = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dir_name) =
|
||||||
|
theme_dir.iter().last().and_then(std::ffi::OsStr::to_str)
|
||||||
|
{
|
||||||
|
valid_dirs
|
||||||
|
.iter()
|
||||||
|
.any(|valid| dir_name == valid)
|
||||||
|
.then(|| icon::from_path(path))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Fallback icon handle
|
||||||
|
.unwrap_or_else(|| icon::from_name(icon_name).size(ICON_THUMB_SIZE).handle())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Button with a preview of the icon theme.
|
||||||
|
fn icon_theme_button(
|
||||||
|
name: &str,
|
||||||
|
handles: &[icon::Handle],
|
||||||
|
id: usize,
|
||||||
|
selected: bool,
|
||||||
|
) -> Element<'static, Message> {
|
||||||
|
let theme = cosmic::theme::active();
|
||||||
|
let theme = theme.cosmic();
|
||||||
|
let background = Background::Color(theme.palette.neutral_4.into());
|
||||||
|
|
||||||
|
cosmic::widget::column()
|
||||||
|
.push(
|
||||||
|
cosmic::widget::button::custom_image_button(
|
||||||
|
cosmic::widget::column::with_children(vec![
|
||||||
|
cosmic::widget::row()
|
||||||
|
.extend(
|
||||||
|
handles
|
||||||
|
.iter()
|
||||||
|
.take(ICON_PREV_ROW)
|
||||||
|
.cloned()
|
||||||
|
// TODO: Maybe allow choosable sizes/zooming
|
||||||
|
.map(|handle| handle.icon().size(ICON_THUMB_SIZE)),
|
||||||
|
)
|
||||||
|
.spacing(theme.space_xxs())
|
||||||
|
.into(),
|
||||||
|
cosmic::widget::row()
|
||||||
|
.extend(
|
||||||
|
handles
|
||||||
|
.iter()
|
||||||
|
.skip(ICON_PREV_ROW)
|
||||||
|
.cloned()
|
||||||
|
// TODO: Maybe allow choosable sizes/zooming
|
||||||
|
.map(|handle| handle.icon().size(ICON_THUMB_SIZE)),
|
||||||
|
)
|
||||||
|
.spacing(theme.space_xxs())
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
|
.spacing(theme.space_xs()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.on_press(Message::IconTheme(id))
|
||||||
|
.selected(selected)
|
||||||
|
.padding(theme.space_xxs())
|
||||||
|
// Image button's style mostly works, but it needs a background to fit the design
|
||||||
|
.style(button::Style::Custom {
|
||||||
|
active: Box::new(move |focused, theme| {
|
||||||
|
let mut appearance = <cosmic::theme::Theme as button::StyleSheet>::active(
|
||||||
|
theme,
|
||||||
|
focused,
|
||||||
|
selected,
|
||||||
|
&cosmic::theme::Button::Image,
|
||||||
|
);
|
||||||
|
appearance.background = Some(background);
|
||||||
|
appearance
|
||||||
|
}),
|
||||||
|
disabled: Box::new(move |theme| {
|
||||||
|
let mut appearance = <cosmic::theme::Theme as button::StyleSheet>::disabled(
|
||||||
|
theme,
|
||||||
|
&cosmic::theme::Button::Image,
|
||||||
|
);
|
||||||
|
appearance.background = Some(background);
|
||||||
|
appearance
|
||||||
|
}),
|
||||||
|
hovered: Box::new(move |focused, theme| {
|
||||||
|
let mut appearance = <cosmic::theme::Theme as button::StyleSheet>::hovered(
|
||||||
|
theme,
|
||||||
|
focused,
|
||||||
|
selected,
|
||||||
|
&cosmic::theme::Button::Image,
|
||||||
|
);
|
||||||
|
appearance.background = Some(background);
|
||||||
|
appearance
|
||||||
|
}),
|
||||||
|
pressed: Box::new(move |focused, theme| {
|
||||||
|
let mut appearance = <cosmic::theme::Theme as button::StyleSheet>::pressed(
|
||||||
|
theme,
|
||||||
|
focused,
|
||||||
|
selected,
|
||||||
|
&cosmic::theme::Button::Image,
|
||||||
|
);
|
||||||
|
appearance.background = Some(background);
|
||||||
|
appearance
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
text(if name.len() > ICON_NAME_TRUNC {
|
||||||
|
format!("{name:.ICON_NAME_TRUNC$}...")
|
||||||
|
} else {
|
||||||
|
name.into()
|
||||||
|
})
|
||||||
|
.width(Length::Fixed((ICON_THUMB_SIZE * 3) as _)),
|
||||||
|
)
|
||||||
|
.spacing(theme.space_xs())
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ control-tint = Control component tint
|
||||||
frosted = Frosted glass effect on system interface
|
frosted = Frosted glass effect on system interface
|
||||||
.desc = Applies background blur to panel, dock, applets, launcher, and application library.
|
.desc = Applies background blur to panel, dock, applets, launcher, and application library.
|
||||||
|
|
||||||
|
experimental-settings = Experimental settings
|
||||||
|
|
||||||
enable-export = Apply this theme to GNOME apps.
|
enable-export = Apply this theme to GNOME apps.
|
||||||
.desc = Not all toolkits support auto-switching. Non-COSMIC apps may need to be restarted after a theme change.
|
.desc = Not all toolkits support auto-switching. Non-COSMIC apps may need to be restarted after a theme change.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue