refactor: allow opting out of auto corner radii tracking

This commit is contained in:
Ashley Wulber 2026-05-10 22:52:00 -04:00 committed by Ashley Wulber
parent bd56f82ca0
commit 58980231b9
3 changed files with 153 additions and 100 deletions

View file

@ -6,11 +6,15 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use super::{Action, Application, ApplicationExt, Subscription}; use super::{Action, Application, ApplicationExt, Subscription};
#[cfg(all(feature = "wayland", target_os = "linux"))]
use crate::core::Auto;
use crate::theme::{THEME, Theme, ThemeType}; use crate::theme::{THEME, Theme, ThemeType};
use crate::{Core, Element, keyboard_nav}; use crate::{Core, Element, keyboard_nav};
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "wayland", target_os = "linux"))]
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState}; use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
use cosmic_theme::ThemeMode; use cosmic_theme::ThemeMode;
#[cfg(all(feature = "wayland", target_os = "linux"))]
use enumflags2::BitFlags;
#[cfg(not(any(feature = "multi-window", feature = "wayland", target_os = "linux")))] #[cfg(not(any(feature = "multi-window", feature = "wayland", target_os = "linux")))]
use iced::Application as IcedApplication; use iced::Application as IcedApplication;
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "wayland", target_os = "linux"))]
@ -754,11 +758,12 @@ impl<T: Application> Cosmic<T> {
self.app.core_mut().window.is_maximized = self.app.core_mut().window.is_maximized =
state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN); state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN);
} }
if self.app.core().sync_window_border_radii_to_theme() { {
use iced_winit::platform_specific::commands::corner_radius::corner_radius; use iced_winit::platform_specific::commands::corner_radius::corner_radius;
let theme = THEME.lock().unwrap(); let theme = THEME.lock().unwrap();
let rounded = !self.app.core().window.sharp_corners let rounded = (!self.app.core().window.sharp_corners
&& self.app.core().sync_window_border_radii_to_theme())
|| self || self
.surface_views .surface_views
.get(&id) .get(&id)
@ -772,8 +777,8 @@ impl<T: Application> Cosmic<T> {
} }
}); });
let cur_rad = self.app.core().app_type.corners(&theme, rounded); let cur_rad = self.app.core().corners(&theme, rounded);
return Task::batch([corner_radius(id, Some(cur_rad)).discard()]); return Task::batch([corner_radius(id, cur_rad).discard()]);
} }
} }
@ -864,9 +869,9 @@ impl<T: Application> Cosmic<T> {
guard.transparent = new_blur; guard.transparent = new_blur;
drop(guard); drop(guard);
let core = self.app.core();
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "wayland", target_os = "linux"))]
{ {
let core = self.app.core();
let mut cmds = Vec::with_capacity(1 + self.surface_views.len()); let mut cmds = Vec::with_capacity(1 + self.surface_views.len());
let blur = if new_blur { let blur = if new_blur {
iced::window::enable_blur iced::window::enable_blur
@ -942,14 +947,13 @@ impl<T: Application> Cosmic<T> {
cosmic_theme.transparent = new_blur; cosmic_theme.transparent = new_blur;
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "wayland", target_os = "linux"))]
if self.app.core().sync_window_border_radii_to_theme() { {
use iced_winit::platform_specific::commands::corner_radius::corner_radius; use iced_winit::platform_specific::commands::corner_radius::corner_radius;
let t = cosmic_theme.cosmic(); let rounded = self.app.core().sync_window_border_radii_to_theme()
&& !self.app.core().window.sharp_corners;
let rounded = !self.app.core().window.sharp_corners; let cur_rad = self.app.core().corners(&cosmic_theme, rounded);
let cur_rad = self.app.core().app_type.corners(&cosmic_theme, rounded);
// Update radius for the main window // Update radius for the main window
let main_window_id = self let main_window_id = self
@ -957,16 +961,31 @@ impl<T: Application> Cosmic<T> {
.core() .core()
.main_window_id() .main_window_id()
.unwrap_or(window::Id::RESERVED); .unwrap_or(window::Id::RESERVED);
let mut cmds = let mut cmds = vec![corner_radius(main_window_id, cur_rad).discard()];
vec![corner_radius(main_window_id, Some(cur_rad)).discard()];
// Update radius for each tracked view with the window surface type // Update radius for each tracked view with the window surface type
for (id, (_, surface_type, _)) in &self.surface_views { for (id, (_, surface_type, _)) in &self.surface_views {
let cur_rad = corners(*surface_type, rounded, &cosmic_theme); let cur_rad = corners(
cmds.push(corner_radius(*id, Some(cur_rad)).discard()); *surface_type,
rounded,
&cosmic_theme,
self.app.core().auto_corner_radius,
);
if cur_rad.is_none() {
continue;
}
cmds.push(corner_radius(*id, cur_rad).discard());
} }
for (id, wrapper) in &self.tracked_surfaces { for (id, wrapper) in &self.tracked_surfaces {
let cur_rad = corners(*wrapper, rounded, &cosmic_theme); let cur_rad = corners(
cmds.push(corner_radius(*id, Some(cur_rad)).discard()); *wrapper,
rounded,
&cosmic_theme,
self.app.core().auto_corner_radius,
);
if cur_rad.is_none() {
continue;
}
cmds.push(corner_radius(*id, cur_rad).discard());
} }
return Task::batch(cmds); return Task::batch(cmds);
@ -1045,12 +1064,12 @@ impl<T: Application> Cosmic<T> {
cosmic_theme.set_theme(new_theme.theme_type); cosmic_theme.set_theme(new_theme.theme_type);
cosmic_theme.transparent = new_blur; cosmic_theme.transparent = new_blur;
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "wayland", target_os = "linux"))]
if self.app.core().sync_window_border_radii_to_theme() { {
use iced_winit::platform_specific::commands::corner_radius::corner_radius; use iced_winit::platform_specific::commands::corner_radius::corner_radius;
let rounded = !self.app.core().window.sharp_corners; let rounded = self.app.core().sync_window_border_radii_to_theme()
let cur_rad = && !self.app.core().window.sharp_corners;
self.app.core().app_type.corners(&cosmic_theme, rounded); let cur_rad = self.app.core().corners(&cosmic_theme, rounded);
// Update radius for the main window // Update radius for the main window
let main_window_id = self let main_window_id = self
@ -1059,15 +1078,31 @@ impl<T: Application> Cosmic<T> {
.main_window_id() .main_window_id()
.unwrap_or(window::Id::RESERVED); .unwrap_or(window::Id::RESERVED);
let mut cmds = let mut cmds =
vec![corner_radius(main_window_id, Some(cur_rad)).discard()]; vec![corner_radius(main_window_id, cur_rad).discard()];
// Update radius for each tracked view with the window surface type // Update radius for each tracked view with the window surface type
for (id, (_, surface_type, _)) in &self.surface_views { for (id, (_, surface_type, _)) in &self.surface_views {
let cur_rad = corners(*surface_type, rounded, &cosmic_theme); let cur_rad = corners(
cmds.push(corner_radius(*id, Some(cur_rad)).discard()); *surface_type,
rounded,
&cosmic_theme,
self.app.core().auto_corner_radius,
);
if cur_rad.is_none() {
continue;
}
cmds.push(corner_radius(*id, cur_rad).discard());
} }
for (id, wrapper) in &self.tracked_surfaces { for (id, wrapper) in &self.tracked_surfaces {
let cur_rad = corners(*wrapper, rounded, &cosmic_theme); let cur_rad = corners(
cmds.push(corner_radius(*id, Some(cur_rad)).discard()); *wrapper,
rounded,
&cosmic_theme,
self.app.core().auto_corner_radius,
);
if cur_rad.is_none() {
continue;
}
cmds.push(corner_radius(*id, cur_rad).discard());
} }
let core = self.app.core(); let core = self.app.core();
@ -1344,13 +1379,14 @@ impl<T: Application> Cosmic<T> {
} }
Action::Opened(id) => { Action::Opened(id) => {
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "wayland", target_os = "linux"))]
if self.app.core().sync_window_border_radii_to_theme() { {
use iced_winit::platform_specific::commands::corner_radius::corner_radius; use iced_winit::platform_specific::commands::corner_radius::corner_radius;
let mut theme = THEME.lock().unwrap(); let mut theme = THEME.lock().unwrap();
// TODO do we need per window sharp corners? // TODO do we need per window sharp corners?
let rounded = !self.app.core().window.sharp_corners let rounded = (!self.app.core().window.sharp_corners
&& self.app.core().sync_window_border_radii_to_theme())
|| self || self
.surface_views .surface_views
.get(&id) .get(&id)
@ -1374,6 +1410,7 @@ impl<T: Application> Cosmic<T> {
.get(&id) .get(&id)
.map(|s| s.1) .map(|s| s.1)
.or(self.tracked_surfaces.get(&id).copied()); .or(self.tracked_surfaces.get(&id).copied());
// this will blur untracked windows as if they were the main window // this will blur untracked windows as if they were the main window
let blur_cmd = if self.app.core().blur(&theme, wrapper) { let blur_cmd = if self.app.core().blur(&theme, wrapper) {
let blur = if new_blur { let blur = if new_blur {
@ -1389,13 +1426,16 @@ impl<T: Application> Cosmic<T> {
Task::none() Task::none()
}; };
let corner_task = if let Some(s) = self let corner_task = if let Some((_, cur_rad)) = self
.surface_views .surface_views
.get(&id) .get(&id)
.map(|s| s.1) .map(|s| s.1)
.or(self.tracked_surfaces.get(&id).copied()) .or(self.tracked_surfaces.get(&id).copied())
{ .and_then(|s| {
corner_radius(id, Some(corners(s, rounded, &theme))).discard() corners(s, rounded, &theme, self.app.core().auto_corner_radius)
.map(|c| (s, c))
}) {
corner_radius(id, Some(cur_rad)).discard()
} else if id } else if id
== self == self
.app .app
@ -1403,11 +1443,11 @@ impl<T: Application> Cosmic<T> {
.main_window_id() .main_window_id()
.unwrap_or(window::Id::RESERVED) .unwrap_or(window::Id::RESERVED)
{ {
corner_radius(id, Some(self.app.core().app_type.corners(&theme, rounded))) corner_radius(id, self.app.core().corners(&theme, rounded)).discard()
.discard()
} else { } else {
Task::none() Task::none()
}; };
return Task::batch([ return Task::batch([
blur_cmd, blur_cmd,
corner_task, corner_task,
@ -1580,9 +1620,18 @@ fn corners(
surface_type: SurfaceIdWrapper, surface_type: SurfaceIdWrapper,
rounded: bool, rounded: bool,
theme: &Theme, theme: &Theme,
) -> iced_runtime::platform_specific::wayland::CornerRadius { auto_corner_radius: BitFlags<Auto>,
) -> Option<iced_runtime::platform_specific::wayland::CornerRadius> {
if !match surface_type {
SurfaceIdWrapper::Window(_) => auto_corner_radius.contains(Auto::Window),
SurfaceIdWrapper::LayerSurface(_) => auto_corner_radius.contains(Auto::System),
SurfaceIdWrapper::Popup(_) => auto_corner_radius.contains(Auto::Popup),
_ => false,
} {
return None;
}
let theme = theme.cosmic(); let theme = theme.cosmic();
if let SurfaceIdWrapper::Popup(_) = surface_type { Some(if let SurfaceIdWrapper::Popup(_) = surface_type {
let radius_m = theme.radius_m(); let radius_m = theme.radius_m();
iced_runtime::platform_specific::wayland::CornerRadius { iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_m[0].round() as u32, top_left: radius_m[0].round() as u32,
@ -1591,7 +1640,7 @@ fn corners(
bottom_left: radius_m[3].round() as u32, bottom_left: radius_m[3].round() as u32,
} }
} else if let SurfaceIdWrapper::Window(_) = surface_type } else if let SurfaceIdWrapper::Window(_) = surface_type
&& rounded && !rounded
{ {
let radius_0 = theme.radius_0(); let radius_0 = theme.radius_0();
iced_runtime::platform_specific::wayland::CornerRadius { iced_runtime::platform_specific::wayland::CornerRadius {
@ -1608,5 +1657,5 @@ fn corners(
bottom_right: radius_s[2].round() as u32, bottom_right: radius_s[2].round() as u32,
bottom_left: radius_s[3].round() as u32, bottom_left: radius_s[3].round() as u32,
} }
} })
} }

View file

@ -112,11 +112,10 @@ pub struct Core {
pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>, pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub(crate) sync_window_border_radii_to_theme: bool,
pub(crate) auto_blur: BitFlags<Auto>, pub(crate) auto_blur: BitFlags<Auto>,
pub(crate) auto_corner_radius: BitFlags<Auto>,
pub(crate) app_type: AppType, pub(crate) app_type: AppType,
} }
@ -130,45 +129,6 @@ pub enum AppType {
Applet, Applet,
} }
impl AppType {
/// Calculate suggested corners for each app type main window
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub fn corners(
&self,
theme: &Theme,
rounded: bool,
) -> iced_runtime::platform_specific::wayland::CornerRadius {
let theme = theme.cosmic();
if let Self::Applet = self {
let radius_l = theme.radius_l();
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_l[0].round() as u32,
top_right: radius_l[1].round() as u32,
bottom_right: radius_l[2].round() as u32,
bottom_left: radius_l[3].round() as u32,
}
} else if let Self::Window = self
&& rounded
{
let radius_0 = theme.radius_0();
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_0[0].round() as u32,
top_right: radius_0[1].round() as u32,
bottom_right: radius_0[2].round() as u32,
bottom_left: radius_0[3].round() as u32,
}
} else {
let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_s[0].round() as u32,
top_right: radius_s[1].round() as u32,
bottom_right: radius_s[2].round() as u32,
bottom_left: radius_s[3].round() as u32,
}
}
}
}
impl Default for Core { impl Default for Core {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -225,9 +185,8 @@ impl Default for Core {
main_window: None, main_window: None,
exit_on_main_window_closed: true, exit_on_main_window_closed: true,
menu_bars: HashMap::new(), menu_bars: HashMap::new(),
#[cfg(all(feature = "wayland", target_os = "linux"))]
sync_window_border_radii_to_theme: true,
auto_blur: Auto::System | Auto::Popup | Auto::Window, auto_blur: Auto::System | Auto::Popup | Auto::Window,
auto_corner_radius: Auto::System | Auto::Popup | Auto::Window,
app_type: AppType::Window, app_type: AppType::Window,
} }
} }
@ -560,15 +519,13 @@ impl Core {
crate::command::toggle_maximize(id) crate::command::toggle_maximize(id)
} }
// TODO should we emit tasks setting the corner radius or unsetting it if this is changed?
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub fn set_sync_window_border_radii_to_theme(&mut self, sync: bool) {
self.sync_window_border_radii_to_theme = sync;
}
#[cfg(all(feature = "wayland", target_os = "linux"))] #[cfg(all(feature = "wayland", target_os = "linux"))]
pub fn sync_window_border_radii_to_theme(&self) -> bool { pub fn sync_window_border_radii_to_theme(&self) -> bool {
self.sync_window_border_radii_to_theme match self.app_type {
AppType::Window => self.auto_corner_radius.contains(Auto::Window),
AppType::System => self.auto_corner_radius.contains(Auto::System),
AppType::Applet => false,
}
} }
pub fn set_auto_blur(&mut self, auto_blur: BitFlags<Auto>) { pub fn set_auto_blur(&mut self, auto_blur: BitFlags<Auto>) {
@ -579,6 +536,14 @@ impl Core {
self.auto_blur self.auto_blur
} }
pub fn set_auto_corner_radius(&mut self, auto_corner_radius: BitFlags<Auto>) {
self.auto_corner_radius = auto_corner_radius;
}
pub fn auto_corner_radius(&self) -> BitFlags<Auto> {
self.auto_corner_radius
}
pub fn set_app_type(&mut self, app_type: AppType) { pub fn set_app_type(&mut self, app_type: AppType) {
self.app_type = app_type; self.app_type = app_type;
} }
@ -621,4 +586,46 @@ impl Core {
_ => false, _ => false,
} }
} }
/// Calculate suggested corners for each app type main window
#[must_use]
#[cfg(all(feature = "wayland", target_os = "linux"))]
pub fn corners(
&self,
theme: &Theme,
rounded: bool,
) -> Option<iced_runtime::platform_specific::wayland::CornerRadius> {
if !self.sync_window_border_radii_to_theme() {
return None;
}
let theme = theme.cosmic();
let ret = if let AppType::Applet = self.app_type {
let radius_l = theme.radius_l();
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_l[0].round() as u32,
top_right: radius_l[1].round() as u32,
bottom_right: radius_l[2].round() as u32,
bottom_left: radius_l[3].round() as u32,
}
} else if let AppType::Window = self.app_type
&& !rounded
{
let radius_0 = theme.radius_0();
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_0[0].round() as u32,
top_right: radius_0[1].round() as u32,
bottom_right: radius_0[2].round() as u32,
bottom_left: radius_0[3].round() as u32,
}
} else {
let radius_s = theme.radius_s().map(|x| if x < 4.0 { x } else { x + 4.0 });
iced_runtime::platform_specific::wayland::CornerRadius {
top_left: radius_s[0].round() as u32,
top_right: radius_s[1].round() as u32,
bottom_right: radius_s[2].round() as u32,
bottom_left: radius_s[3].round() as u32,
}
};
Some(ret)
}
} }

View file

@ -2,6 +2,7 @@
use std::time::Duration; use std::time::Duration;
use crate::anim; use crate::anim;
use crate::theme::THEME;
use crate::widget::card::style::Style; use crate::widget::card::style::Style;
use crate::widget::icon::{self, Handle}; use crate::widget::icon::{self, Handle};
use crate::widget::{button, column, row, text}; use crate::widget::{button, column, row, text};
@ -19,7 +20,6 @@ const TOP_SPACING: u16 = 4;
const VERTICAL_SPACING: f32 = 8.0; const VERTICAL_SPACING: f32 = 8.0;
const PADDING: u16 = 16; const PADDING: u16 = 16;
const BG_CARD_VISIBLE_HEIGHT: f32 = 4.0; const BG_CARD_VISIBLE_HEIGHT: f32 = 4.0;
const BG_CARD_BORDER_RADIUS: f32 = 8.0;
const BG_CARD_MARGIN_STEP: f32 = 8.0; const BG_CARD_MARGIN_STEP: f32 = 8.0;
/// get an expandable stack of cards /// get an expandable stack of cards
@ -337,14 +337,15 @@ where
for i in 1..self.elements.len().min(3) { for i in 1..self.elements.len().min(3) {
// height must be 16px for 8px padding // height must be 16px for 8px padding
// but we only want 4px visible // but we only want 4px visible
let guard = THEME.lock().unwrap();
let radius_xs = guard.cosmic().radius_xs();
let margin = f32::from(u8::try_from(i).unwrap()) * BG_CARD_MARGIN_STEP; let margin = f32::from(u8::try_from(i).unwrap()) * BG_CARD_MARGIN_STEP;
let node = let node = Node::new(Size::new(width - 2.0 * margin, radius_xs[0] * 2.0))
Node::new(Size::new(width - 2.0 * margin, BG_CARD_BORDER_RADIUS * 2.0)) .translate(Vector::new(
.translate(Vector::new( margin,
margin, size.height - radius_xs[0] * 2.0 + BG_CARD_VISIBLE_HEIGHT,
size.height - BG_CARD_BORDER_RADIUS * 2.0 + BG_CARD_VISIBLE_HEIGHT, ));
));
size.height += BG_CARD_VISIBLE_HEIGHT; size.height += BG_CARD_VISIBLE_HEIGHT;
children.push(node); children.push(node);
} }
@ -424,6 +425,7 @@ where
_ = tree_children.next(); _ = tree_children.next();
} }
let radius_xs = theme.cosmic().radius_xs();
// Draw first to appear behind // Draw first to appear behind
if fully_unexpanded { if fully_unexpanded {
let card_layout = layout.next().unwrap(); let card_layout = layout.next().unwrap();
@ -434,12 +436,7 @@ where
Quad { Quad {
bounds: layout.bounds(), bounds: layout.bounds(),
border: Border { border: Border {
radius: Radius::from([ radius: Radius::from([0.0, 0.0, radius_xs[2], radius_xs[3]]),
0.0,
0.0,
BG_CARD_BORDER_RADIUS,
BG_CARD_BORDER_RADIUS,
]),
..Default::default() ..Default::default()
}, },
shadow: Shadow::default(), shadow: Shadow::default(),