feat(settings): refine density, group detailed options and add edge maximize gestures

This commit is contained in:
aria committed 2026-10-28 04:49:27 +09:00
1 parent d7e122711a
commit 9908dde9f8
11 files changed
+588 -195

No files matched your search

+7 -7
View File
@@ -506,7 +506,7 @@ impl App {
}
fn hub_layout(&self, columns: usize) -> Element<'_, Message> {
let palette = visuals::Palette::from_resolved(&self.resolved.presented);
let mut out = column([]).spacing(24);
let mut out = column([]).spacing(20);
for (name, pages) in Page::NAVIGATION {
if name == "Advanced" {
continue;
@@ -527,12 +527,12 @@ impl App {
} else {
name
}),
12.,
visuals::SECTION_TITLE_SIZE,
)
.class(cosmic::theme::Text::Color(palette.muted)),
);
for pair in pages.chunks(columns) {
let mut cards = row([]).spacing(12);
let mut cards = row([]).spacing(16);
for &page in pair {
cards = cards.push(self.hub_card(page));
}
@@ -555,7 +555,7 @@ impl App {
column([])
.spacing(4)
.push(self.label(p.title(), 14.).font(cosmic::iced::Font {
weight: cosmic::iced::font::Weight::Medium,
weight: cosmic::iced::font::Weight::Normal,
..self.font
}))
.push(
@@ -928,9 +928,9 @@ impl App {
container(out).width(Length::Fill).padding([5, 2]).into()
}
pub fn tray_status_controls(&self) -> Element<'_, Message> {
let mut out = column([]).spacing(8).push(self.note(t(
"Status icons can also be hidden individually in Panel & Shell.",
)));
let mut out = column([])
.spacing(12)
.push(self.panel_heading(t("Panel status icons"), 14.));
let panels: Vec<ferese_panel::Panel> = self
.draft
.item("panels")
+48 -18
View File
@@ -1,5 +1,3 @@
use cosmic::widget::column;
use super::{
Alignment, App, Element, Field, Kind, Length, Message, button, container, row, set, slider,
text_input, visuals, widget,
@@ -14,15 +12,26 @@ impl App {
ferese_theme::text(text, self.font).size(size)
}
pub(super) fn panel_heading(
pub(super) fn window_chrome<'a>(
&self,
text: impl Into<std::borrow::Cow<'static, str>> + 'static,
content: impl Into<Element<'a, Message>> + 'a,
) -> Element<'a, Message> {
widget::mouse_area(content)
.on_drag(Message::DragWindow)
.on_double_click(Message::ToggleMaximize)
.into()
}
pub(super) fn panel_heading<'a>(
&self,
text: impl Into<std::borrow::Cow<'a, str>> + 'a,
size: f32,
) -> widget::Text<'static, cosmic::Theme, cosmic::Renderer> {
self.label(text, size).font(cosmic::iced::Font {
weight: cosmic::iced::font::Weight::Semibold,
..self.font
})
) -> widget::Text<'a, cosmic::Theme, cosmic::Renderer> {
self.label(text, size.min(visuals::SECTION_TITLE_SIZE))
.font(cosmic::iced::Font {
weight: cosmic::iced::font::Weight::Bold,
..self.font
})
}
pub(super) fn note(&self, text: &str) -> Element<'static, Message> {
@@ -121,16 +130,37 @@ impl App {
}
}
let palette = visuals::Palette::from_resolved(&self.resolved.presented);
let mut labels = column([])
.spacing(2)
.push(self.label(ferese_config::locale::translate(&field.label), 13.));
let regular = cosmic::iced::Font {
weight: cosmic::iced::font::Weight::Normal,
..self.font
};
let mut spans = vec![
cosmic::iced::widget::span::<(), _>(ferese_config::locale::translate(&field.label))
.font(regular)
.size(13.)
.color(palette.text),
];
if !field.description.is_empty() {
labels = labels.push(
self.label(ferese_config::locale::translate(&field.description), 11.)
.class(cosmic::theme::Text::Color(palette.muted)),
spans.push(
cosmic::iced::widget::span(format!(
" {}",
ferese_config::locale::translate(&field.description)
))
.font(regular)
.size(12.)
.color(palette.muted),
);
}
let labels = labels.width(Length::Fill);
// Short explanations flow beside the label. The paragraph wraps only
// when the remaining width actually requires another line.
let labels = container(
cosmic::iced::widget::rich_text(spans)
.font(regular)
.size(13.)
.width(Length::Fill),
)
.id(widget::Id::new(format!("setting-label-{}", field.path)))
.width(Length::Fill);
let path = field.path.clone();
let control: Element<'static, Message> = match field.kind.clone() {
Kind::Toggle(default) => {
@@ -243,12 +273,12 @@ impl App {
};
container(
row([])
.spacing(10)
.spacing(12)
.align_y(Alignment::Center)
.push(labels)
.push(control),
)
.padding([10, 12])
.padding([12, 14])
.width(Length::Fill)
.class(visuals::surface(cosmic::iced::Color::TRANSPARENT, 0.))
.into()
+7 -4
View File
@@ -128,6 +128,7 @@ enum Message {
RefreshRate(String, displays::Mode, bool),
ThemeChanged(Box<ferese_ipc::theme::Snapshot>),
DragWindow,
ToggleMaximize,
Page(Page),
PagePresented(Page),
RowVisibility(&'static str, usize, bool),
@@ -378,6 +379,7 @@ impl cosmic::Application for App {
Message::RefreshTrayApps => return self.load_tray_apps(),
Message::Options(input) => return self.options_input(input),
Message::DragWindow => return self.core.drag(None),
Message::ToggleMaximize => return self.core.toggle_maximize(None),
Message::ReadRenderScale(id) => {
return cosmic::iced::window::scale_factor(id)
.map(|scale| cosmic::Action::App(Message::RenderScale(scale)));
@@ -1268,9 +1270,11 @@ mod tests {
let mut app = app();
app.core.set_main_window_id(None);
assert_eq!(app.update(Message::DragWindow).units(), 0);
assert_eq!(app.update(Message::ToggleMaximize).units(), 0);
app.core
.set_main_window_id(Some(cosmic::iced::window::Id::unique()));
assert_eq!(app.update(Message::DragWindow).units(), 1);
assert_eq!(app.update(Message::ToggleMaximize).units(), 1);
}
fn shortcuts(count: usize) -> App {
@@ -1636,10 +1640,9 @@ mod tests {
app.visible_rows
.extend((0..100).map(|index| ("bindings", index)));
let eager = nodes(&app);
assert!(
eager > visible + 96 * 10,
"visible={visible}, eager={eager}"
);
// Mounting only visible rows should cut the widget tree substantially,
// independently of how many wrappers a field label uses.
assert!(eager > visible * 2, "visible={visible}, eager={eager}");
}
#[test]
+154 -114
View File
@@ -17,7 +17,7 @@ impl App {
fn page_layout(&self, compact: bool, height: f32, width: f32) -> Element<'_, Message> {
let palette = visuals::Palette::from_resolved(&self.resolved.presented);
let mut sidebar = column([])
let sidebar = column([])
.spacing(4)
.push(
widget::mouse_area(
@@ -31,7 +31,8 @@ impl App {
)
.width(Length::Fill),
)
.on_press(Message::DragWindow)
.on_drag(Message::DragWindow)
.on_double_click(Message::ToggleMaximize)
.interaction(cosmic::iced::mouse::Interaction::Grab),
)
.push(widget::Space::new().height(14))
@@ -44,15 +45,7 @@ impl App {
.size(12),
)
.push(widget::Space::new().height(8));
if compact {
sidebar = sidebar.push(self.settings_button(
ferese_config::tr!("Close navigation"),
"M6 6l12 12 M6 18L18 6",
Some(Message::Sidebar(false)),
false,
));
}
let mut navigation = column([]).spacing(24);
let mut navigation = column([]).spacing(20);
for (title, pages) in Page::NAVIGATION {
let mut section = column([]).spacing(3).push(
container(
@@ -62,8 +55,12 @@ impl App {
} else {
title
}),
11.,
visuals::SECTION_TITLE_SIZE,
)
.font(cosmic::iced::Font {
weight: cosmic::iced::font::Weight::Bold,
..self.font
})
.class(cosmic::theme::Text::Color(palette.muted)),
)
.padding([0, 8]),
@@ -140,35 +137,21 @@ impl App {
.height(Length::Fill)
.padding([24, 12])
.class(visuals::sidebar(palette));
let mut heading = row([]).align_y(Alignment::Center).spacing(12).push(
column([])
.spacing(3)
.push(
self.label(
if self.search.is_empty() {
self.page.navigation_page().title()
} else {
"Search"
},
24.,
)
.font(cosmic::iced::Font {
weight: cosmic::iced::font::Weight::Medium,
..self.font
}),
)
.push(
self.label(
if self.search.is_empty() {
self.page.subtitle()
} else {
"Find a setting for your desktop."
},
12.,
)
.class(cosmic::theme::Text::Color(palette.muted)),
)
.width(Length::Fill),
let sidebar = self.window_chrome(sidebar);
let mut heading = row([]).align_y(Alignment::Center).spacing(16).push(
self.label(
if self.search.is_empty() {
self.page.navigation_page().title()
} else {
ferese_config::tr!("Search")
},
visuals::PAGE_TITLE_SIZE,
)
.font(cosmic::iced::Font {
weight: cosmic::iced::font::Weight::Medium,
..self.font
})
.width(Length::Fill),
);
if self.page == Page::Theme && self.search.is_empty() {
let automatic = self.draft.string("theme.mode", "dark") == "auto";
@@ -223,7 +206,7 @@ impl App {
));
}
let mut body = column([]).spacing(6);
let mut body = column([]).spacing(20);
if !self.search.is_empty() {
let pages = || {
@@ -260,23 +243,12 @@ impl App {
} else {
if self.page == Page::Appearance {
body = body.push(
self.settings_hub((width - if compact { 0. } else { 208. }).min(1040.) - 62.),
self.settings_hub((width - if compact { 0. } else { 208. }).min(1280.) - 54.),
);
}
if self.page == Page::Configuration {
body = body.push(self.all_options_view());
}
if self.page == Page::Tray {
body = body
.push(self.tray_application_controls())
.push(self.tray_status_controls());
}
if self.page == Page::Windows {
body = body.push(visuals::preview_resolved(
&self.draft,
&self.resolved.presented,
));
}
if self.page == Page::Theme {
let split = visuals::split(&self.draft);
if split {
@@ -315,55 +287,84 @@ impl App {
schema::fields(self.page)
};
if !fields.is_empty() {
let mut group = column([]).spacing(1);
if self.page == Page::Theme && self.auto_details {
group = group.push(self.auto_controls());
}
for field in fields {
if self.page == Page::Theme
&& (field.path == "theme.mode"
let fields = fields
.into_iter()
.filter(|field| {
self.page != Page::Theme
|| !(field.path == "theme.mode"
|| field.path.starts_with("theme.schedule.")
|| field.path.ends_with(".file"))
{
continue;
}
group = group.push(self.field(field));
})
.collect();
for group in schema::grouped_fields(self.page, fields) {
let title = group.title;
let mut rows = column([]).spacing(4);
if self.page == Page::Windows && title == "Focus and window effects" {
rows = rows.push(visuals::preview_resolved(
&self.draft,
&self.resolved.presented,
));
}
if self.page == Page::Theme {
group = group.push(
button::custom(
row([])
.spacing(8)
.align_y(Alignment::Center)
.push(self.label(ferese_config::tr!("Advanced customization"), 13.))
.push(visuals::action_icon(
if self.advanced_theme {
"M6 15l6-6 6 6"
} else {
"M6 9l6 6 6-6"
},
palette.muted,
)),
)
.name(if self.advanced_theme {
"Collapse advanced customization"
} else {
"Expand advanced customization"
})
.padding([7, 10])
.width(Length::Fill)
.class(visuals::button_style(palette, false))
.on_press(Message::AdvancedTheme(!self.advanced_theme)),
for field in group.fields {
rows = rows.push(self.field(field));
}
body = body.push(
column([])
.spacing(10)
.push(self.panel_heading(
ferese_config::locale::text(title),
visuals::SECTION_TITLE_SIZE,
))
.push(container(rows).padding(4).class(visuals::section(palette))),
);
}
if self.page == Page::Theme {
if self.auto_details {
body = body.push(
column([])
.spacing(10)
.push(
self.panel_heading(ferese_config::tr!("Automatic appearance"), 14.),
)
.push(self.auto_controls()),
);
if self.advanced_theme {
group = group.push(self.theme_file_picker());
}
}
body = body.push(container(group).padding(4).class(visuals::section(palette)));
body = body.push(
button::custom(
row([])
.spacing(10)
.align_y(Alignment::Center)
.push(self.label(ferese_config::tr!("Advanced customization"), 13.))
.push(visuals::action_icon(
if self.advanced_theme {
"M6 15l6-6 6 6"
} else {
"M6 9l6 6 6-6"
},
palette.muted,
)),
)
.name(if self.advanced_theme {
"Collapse advanced customization"
} else {
"Expand advanced customization"
})
.padding([8, 12])
.width(Length::Fill)
.class(visuals::button_style(palette, false))
.on_press(Message::AdvancedTheme(!self.advanced_theme)),
);
if self.advanced_theme {
body = body.push(self.theme_file_picker());
}
}
match self.page {
Page::Tray => {
body = body
.push(self.tray_application_controls())
.push(self.tray_status_controls())
}
Page::Connections => body = body.push(self.connections_view()),
Page::LockScreen => {
body = body.push(self.note(ferese_config::tr!("Uses your wallpaper, shell colors, font and corner radius from Appearance. Changes apply when the locker or preview opens.")));
@@ -443,6 +444,8 @@ impl App {
);
}
Page::Shortcuts => {
body =
body.push(self.panel_heading(ferese_config::tr!("Custom shortcuts"), 14.));
body = body.push(self.note(ferese_config::tr!("Keyboard, mouse and swipe bindings share the same actions. Mouse shortcuts can use MouseLeft, MouseRight, MouseMiddle, MouseBack, MouseForward or WheelUp/Down/Left/Right with modifiers. Use pointer-move or pointer-resize for dragging. Swipes use Swipe3Up, Swipe3Down, Swipe3Left or Swipe3Right (also supports 4 or 5 fingers). Actions such as toggle-overview and none need no argument.")));
let mut gestures = row([]).spacing(6);
for (keys, label) in [
@@ -536,7 +539,8 @@ impl App {
}
page_heading = page_heading.push(
widget::mouse_area(heading.width(Length::Fill))
.on_press(Message::DragWindow)
.on_drag(Message::DragWindow)
.on_double_click(Message::ToggleMaximize)
.interaction(cosmic::iced::mouse::Interaction::Grab),
);
let mut content = column([]).spacing(16).push(page_heading);
@@ -700,28 +704,64 @@ impl App {
} else {
sidebar.into()
};
let body_width = (width - if compact { 0. } else { 208. }).max(0.);
let outer_gutter = ((body_width - 1280.) / 2.).max(0.);
let blank = |id: &'static str, w: Length, h: Length| {
self.window_chrome(container(widget::Space::new().width(w).height(h)).id(id))
};
let frame = column([])
.push(blank(
"settings-top-blank",
Length::Fill,
Length::Fixed(16.),
))
.push(
row([])
.push(blank(
"settings-left-blank",
Length::Fixed(20.),
Length::Fill,
))
.push(
column([])
.push(content.height(Length::Fill))
.push(footer)
.spacing(8)
.width(Length::Fill)
.height(Length::Fill),
)
.push(blank(
"settings-right-blank",
Length::Fixed(20.),
Length::Fill,
))
.height(Length::Fill),
)
.push(blank(
"settings-bottom-blank",
Length::Fill,
Length::Fixed(12.),
))
.width(Length::Fill)
.height(Length::Fill);
let view = row([]).push(sidebar).push(
container(
container(
column([])
.push(content.height(Length::Fill))
.push(footer)
.spacing(6),
)
.padding(cosmic::iced::Padding {
top: 24.,
right: 24.,
bottom: 8.,
left: 24.,
})
.max_width(if self.page == Page::Bar { 1120 } else { 1040 })
.width(Length::Fill)
.height(Length::Fill),
row([])
.push(blank(
"settings-outer-left-blank",
Length::Fixed(outer_gutter),
Length::Fill,
))
.push(frame)
.push(blank(
"settings-outer-right-blank",
Length::Fixed(outer_gutter),
Length::Fill,
)),
)
.width(Length::Fill)
.center_x(Length::Fill)
.height(Length::Fill)
.id("settings-page")
.width(Length::Fill)
.height(Length::Fill)
.class(visuals::surface(palette.background, 0.)),
);
// Keep the page mounted and full-width while compact navigation is open.
+144
View File
@@ -1014,3 +1014,147 @@ pub fn fields(page: Page) -> Vec<Field> {
| Page::Connections => vec![],
}
}
pub struct FieldGroup {
pub title: &'static str,
pub fields: Vec<Field>,
}
/// Semantic groups, preserving every field and the declaration order in each group.
pub fn grouped_fields(page: Page, fields: Vec<Field>) -> Vec<FieldGroup> {
let mut groups: Vec<FieldGroup> = Vec::new();
for field in fields {
let p = field.path.as_str();
let title = match page {
Page::General => "Interface",
Page::Windows => {
if p.starts_with("workspaces.") || p == "layout.mode" || p.starts_with("scrolling.")
{
"Workspaces and layout"
} else if p == "layout.inner_gap"
|| p == "layout.outer_gap"
|| p == "theme.geometry.window_radius"
{
"Spacing and corners"
} else {
"Focus and window effects"
}
}
Page::Keyboard => {
if p.starts_with("input.xkb_") {
"Keyboard layout"
} else if p.starts_with("input.repeat_") {
"Keyboard repeat"
} else if p.starts_with("input.mouse.") {
"Mouse"
} else {
"Touchpad"
}
}
Page::LockScreen => {
if p.starts_with("lock_screen.background_") {
"Lock background"
} else if p.contains("after_seconds") {
"Display power"
} else {
"Clock and date"
}
}
Page::Theme => {
if p.ends_with(".file") {
"Theme overrides"
} else if p == "theme.mode" || p.starts_with("theme.schedule.") {
"Automatic appearance"
} else if p.starts_with("theme.typography.") {
"Typography"
} else if p.starts_with("theme.material.") {
"Surface and transparency"
} else {
"Colors and accents"
}
}
Page::Wallpaper => "Wallpaper image",
Page::Notifications => {
if p.ends_with("timeout_ms") {
"Notification timing"
} else {
"Notification behavior"
}
}
Page::Accessibility => {
if p.starts_with("animations.") {
"Motion"
} else {
"Visual accessibility"
}
}
Page::Motion => {
if p.starts_with("animations.") {
"Animations"
} else {
"Focus transition"
}
}
Page::Shortcuts => "Shortcut guide",
Page::Tray => "Application tray",
_ => "General",
};
if let Some(group) = groups.iter_mut().find(|group| group.title == title) {
group.fields.push(field)
} else {
groups.push(FieldGroup {
title,
fields: vec![field],
});
}
}
groups
}
#[cfg(test)]
mod group_tests {
use super::*;
#[test]
fn semantic_groups_keep_every_field_once_and_separate_input_devices() {
for page in Page::ALL {
let mut expected = fields(page).into_iter().map(|f| f.path).collect::<Vec<_>>();
let groups = grouped_fields(page, fields(page));
assert!(groups.iter().all(|g| !g.fields.is_empty()));
let mut actual = groups
.into_iter()
.flat_map(|g| g.fields.into_iter().map(|f| f.path))
.collect::<Vec<_>>();
expected.sort();
actual.sort();
assert_eq!(actual, expected, "{page:?}");
}
let keyboard = grouped_fields(Page::Keyboard, fields(Page::Keyboard));
assert!(
keyboard
.iter()
.find(|g| g.title == "Mouse")
.unwrap()
.fields
.iter()
.all(|f| f.path.starts_with("input.mouse."))
);
assert!(
keyboard
.iter()
.find(|g| g.title == "Touchpad")
.unwrap()
.fields
.iter()
.all(|f| f.path.starts_with("input.touchpad."))
);
let windows = grouped_fields(Page::Windows, fields(Page::Windows));
assert_eq!(
windows.iter().map(|g| g.title).collect::<Vec<_>>(),
vec![
"Workspaces and layout",
"Spacing and corners",
"Focus and window effects"
]
);
}
}
+121 -10
View File
@@ -133,15 +133,16 @@ fn appearance_scroll_preserves_progress_and_settles_visibility() {
fn traverse(&mut self, children: &mut dyn FnMut(&mut dyn Operation)) {
children(self);
}
fn text(&mut self, _: Option<&widget::Id>, bounds: Rectangle, text: &str) {
if matches!(
text,
"Shell corner radius"
| "Shell opacity"
| "Background blur"
| "Interface font"
| "Focus border"
) {
fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) {
if [
"theme.accent",
"theme.focus_ring.style",
"theme.material.blur_radius",
"theme.typography.font_family",
]
.iter()
.any(|path| id == Some(&widget::Id::new(format!("setting-label-{path}"))))
{
self.labels.push(bounds);
}
}
@@ -187,7 +188,7 @@ fn appearance_scroll_preserves_progress_and_settles_visibility() {
),
)
.0;
app.page = Page::Appearance;
app.page = Page::Theme;
let theme = app.native_palette.native_theme();
let mut tree: Option<Tree> = None;
let mut previous = 0.;
@@ -1254,3 +1255,113 @@ fn sidebar_rows_center_inside_the_full_button_hit_area() {
}
}
}
#[test]
fn double_clicking_blank_edges_toggles_maximize_without_changing_settings() {
#[derive(Default)]
struct Blank {
bounds: Option<Rectangle>,
label: Option<Rectangle>,
}
impl Operation for Blank {
fn traverse(&mut self, children: &mut dyn FnMut(&mut dyn Operation)) {
children(self);
}
fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) {
if id == Some(&widget::Id::new("settings-right-blank")) {
self.bounds = Some(bounds);
}
if id == Some(&widget::Id::new("setting-label-ui.language")) {
self.label = Some(bounds);
}
}
}
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let renderer = runtime
.block_on(<cosmic::Renderer as Headless>::new(
Font::default(),
Pixels(14.),
Some("tiny-skia"),
))
.unwrap();
let app = App::init(
Core::default(),
(
"/unused/edge-gesture.kdl".into(),
Snapshot::parse(String::new()),
Some(InitialPage::Settings(Page::General)),
),
)
.0;
let before = app.draft.source.clone();
let mut view = app.page_view();
let mut tree = Tree::new(&view);
let viewport = Rectangle::with_size(Size::new(1280., 760.));
let node = view.as_widget_mut().layout(
&mut tree,
&renderer,
&layout::Limits::new(Size::ZERO, viewport.size()),
);
let mut blank = Blank::default();
view.as_widget_mut()
.operate(&mut tree, Layout::new(&node), &renderer, &mut blank);
let bounds = blank.bounds.unwrap();
let cursor = mouse::Cursor::Available(bounds.center());
let mut messages = Vec::new();
for _ in 0..2 {
for event in [
mouse::Event::ButtonPressed(mouse::Button::Left),
mouse::Event::ButtonReleased(mouse::Button::Left),
] {
view.as_widget_mut().update(
&mut tree,
&Event::Mouse(event),
Layout::new(&node),
cursor,
&renderer,
&mut clipboard::Null,
&mut Shell::new(&mut messages),
&viewport,
);
}
}
assert_eq!(
messages
.iter()
.filter(|m| matches!(m, Message::ToggleMaximize))
.count(),
1
);
assert!(
!messages
.iter()
.any(|m| matches!(m, Message::Change(_) | Message::DragWindow))
);
assert_eq!(app.draft.source, before);
messages.clear();
let cursor = mouse::Cursor::Available(blank.label.unwrap().center());
for _ in 0..2 {
for event in [
mouse::Event::ButtonPressed(mouse::Button::Left),
mouse::Event::ButtonReleased(mouse::Button::Left),
] {
view.as_widget_mut().update(
&mut tree,
&Event::Mouse(event),
Layout::new(&node),
cursor,
&renderer,
&mut clipboard::Null,
&mut Shell::new(&mut messages),
&viewport,
);
}
}
assert!(
messages.is_empty(),
"field labels must not act as window chrome"
);
}
+1 -1
View File
@@ -73,7 +73,7 @@ impl App {
}
}
let mut out = column([])
.spacing(8)
.spacing(12)
.push(self.panel_heading(t("Application icons"), 15.));
for app in apps {
let visible = !hidden.contains(&app.id);
+3 -1
View File
@@ -2,6 +2,8 @@ use cosmic::Element;
use cosmic::iced::{Color, Length};
use cosmic::widget::icon as svg_icon;
pub use ferese_theme::Palette;
pub const PAGE_TITLE_SIZE: f32 = 16.;
pub const SECTION_TITLE_SIZE: f32 = 14.;
pub use ferese_theme::controls::{
button_style, navigation_style, settings_input as input_style, surface,
};
@@ -358,7 +360,7 @@ fn preview(
.symbolic(false)
.icon()
.width(Length::Fill)
.height(Length::Fixed(132.))
.height(Length::Fixed(84.))
.content_fit(cosmic::iced::ContentFit::Contain)
.into()
}