cosmic-settings/cosmic-settings/src/pages/desktop/wallpaper/widgets.rs
2026-05-15 16:26:20 -06:00

187 lines
5.7 KiB
Rust

// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use super::Message;
use cosmic::iced::Radius;
use cosmic::iced::core::gradient::Linear;
use cosmic::iced::core::{Background, Border, Color, Degrees, Length};
use cosmic::iced::runtime::core::image::Handle as ImageHandle;
use cosmic::prelude::*;
use cosmic::widget::{Space, button, container};
use cosmic::{Element, iced};
use cosmic_settings_wallpaper as wallpaper;
use slotmap::DefaultKey;
use std::sync::OnceLock;
const COLOR_WIDTH: u16 = 70;
const COLUMN_SPACING: u16 = 12;
const ROW_SPACING: u16 = 16;
/// A button for selecting a color or gradient.
#[must_use]
pub fn color_button(
color: wallpaper::Color,
removable: bool,
selected: bool,
) -> Element<'static, Message> {
let content = color_image(color.clone(), COLOR_WIDTH, COLOR_WIDTH, None);
let on_remove = if removable {
Some(Message::ColorRemove(color.clone()))
} else {
None
};
button::custom_image_button(content, on_remove)
.padding(0)
.selected(selected)
.class(button::ButtonClass::Image)
.on_press(Message::ColorSelect(color))
.into()
}
/// A sized container that's filled with a color or gradient.
#[must_use]
pub fn color_image<'a, M: 'a>(
color: wallpaper::Color,
width: u16,
height: u16,
border_radius: Option<f32>,
) -> Element<'a, M> {
container(Space::new().width(width).height(height))
.class(cosmic::theme::Container::custom(move |theme| {
container::Style {
icon_color: None,
text_color: None,
snap: true,
background: Some(match &color {
wallpaper::Color::Single([r, g, b]) => {
Background::Color(Color::from_rgb(*r, *g, *b))
}
wallpaper::Color::Gradient(wallpaper::Gradient { colors, radius }) => {
let stop_increment = 1.0 / (colors.len() - 1) as f32;
let mut stop = 0.0;
let mut linear = Linear::new(Degrees(*radius));
for &[r, g, b] in &**colors {
linear = linear.add_stop(stop, iced::Color::from_rgb(r, g, b));
stop += stop_increment;
}
Background::Gradient(iced::core::Gradient::Linear(linear))
}
}),
border: Border {
radius: border_radius.map_or_else(
|| Radius::from(theme.cosmic().corner_radii.radius_s),
|br| br.into(),
),
..Default::default()
},
shadow: Default::default(),
}
}))
.padding(0)
.into()
}
/// Color selection list
///
/// Begin with removable custom colors, and chain with non-removable default colors.
#[must_use]
pub fn color_select_options(
context: &super::Context,
selected: Option<&wallpaper::Color>,
) -> Element<'static, Message> {
static SORTED: OnceLock<Vec<wallpaper::Color>> = OnceLock::new();
let sorted = &**SORTED.get_or_init(|| {
let mut sorted = wallpaper::DEFAULT_COLORS.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).expect("Neither slices should have NaNs"));
sorted
});
flex_select_row(
context
.custom_colors
.iter()
.rev()
.filter_map(|color| {
sorted
.binary_search_by(|probe| {
probe
.partial_cmp(color)
.expect("Neither slices should have NaNs")
})
.is_err()
.then_some((color, true))
})
.chain(wallpaper::DEFAULT_COLORS.iter().map(|color| (color, false)))
.map(|(color, removable)| {
color_button(color.clone(), removable, selected == Some(color))
})
.collect::<Vec<_>>(),
)
}
/// Background selection list
#[must_use]
pub fn wallpaper_select_options(
page: &super::Page,
selected: Option<DefaultKey>,
show_custom_images: bool,
) -> Element<'_, Message> {
let mut vec = Vec::with_capacity(page.selection.selection_handles.len());
if show_custom_images {
// Place removable custom images first
for id in page.selection.custom_images.iter().rev() {
let handle = &page.selection.selection_handles[*id];
vec.push(wallpaper_button(
handle,
*id,
true,
selected.is_some_and(|selection| id == &selection),
));
}
}
// Then place non-removable images from the current folder
for (id, handle) in &page.selection.selection_handles {
if page.selection.is_custom.contains_key(id) {
continue;
}
vec.push(wallpaper_button(handle, id, false, selected == Some(id)));
}
flex_select_row(vec)
}
fn flex_select_row(elements: Vec<Element<'_, Message>>) -> Element<'_, Message> {
cosmic::widget::flex_row(elements)
.column_spacing(COLUMN_SPACING)
.row_spacing(ROW_SPACING)
.apply(container)
.center_x(Length::Fill)
.into()
}
fn wallpaper_button(
handle: &ImageHandle,
id: DefaultKey,
removable: bool,
selected: bool,
) -> Element<'_, Message> {
cosmic::widget::button::image(handle.clone())
.selected(selected)
.on_press(Message::Select(id))
.on_remove_maybe(if removable {
Some(Message::ImageRemove(id))
} else {
None
})
.into()
}