Merge branch 'pop-os:master' into master

This commit is contained in:
UchiWerfer 2025-10-07 19:06:23 +00:00 • committed by GitHub
commit 1867d3d50c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 484 additions and 340 deletions

View file

@ -229,7 +229,7 @@ impl Config {
// Start a transaction (to set multiple configs at the same time) // Start a transaction (to set multiple configs at the same time)
#[inline] #[inline]
pub fn transaction(&self) -> ConfigTransaction { pub fn transaction(&self) -> ConfigTransaction<'_> {
ConfigTransaction { ConfigTransaction {
config: self, config: self,
updates: Mutex::new(Vec::new()), updates: Mutex::new(Vec::new()),

View file

@ -93,7 +93,7 @@ async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicCo
} }
Err((errors, t)) => { Err((errors, t)) => {
let update = crate::Update { let update = crate::Update {
errors: errors, errors,
keys: Vec::new(), keys: Vec::new(),
config: t.clone(), config: t.clone(),
}; };

View file

@ -814,7 +814,7 @@ pub struct ThemeBuilder {
impl Default for ThemeBuilder { impl Default for ThemeBuilder {
fn default() -> Self { fn default() -> Self {
Self { Self {
palette: DARK_PALETTE.to_owned().into(), palette: DARK_PALETTE.to_owned(),
spacing: Spacing::default(), spacing: Spacing::default(),
corner_radii: CornerRadii::default(), corner_radii: CornerRadii::default(),
neutral_tint: Default::default(), neutral_tint: Default::default(),
@ -1077,7 +1077,7 @@ impl ThemeBuilder {
component_pressed_overlay = component_hovered_overlay; component_pressed_overlay = component_hovered_overlay;
component_pressed_overlay.alpha = 0.2; component_pressed_overlay.alpha = 0.2;
let container = Container::new( Container::new(
Component::component( Component::component(
component_base, component_base,
accent, accent,
@ -1101,9 +1101,7 @@ impl ThemeBuilder {
), ),
get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]), get_small_widget_color(base_index, 5, &neutral_steps, &control_steps_array[6]),
is_high_contrast, is_high_contrast,
); )
container
}; };
let accent_text = if is_dark { let accent_text = if is_dark {

View file

@ -93,7 +93,7 @@ pub fn get_text(
let index = get_index(base_index, 70, step_array.len(), is_dark) let index = get_index(base_index, 70, step_array.len(), is_dark)
.or_else(|| get_index(base_index, 50, step_array.len(), is_dark)) .or_else(|| get_index(base_index, 50, step_array.len(), is_dark))
.unwrap_or_else(|| if is_dark { 99 } else { 0 }); .unwrap_or(if is_dark { 99 } else { 0 });
*step_array.get(index).unwrap_or(fallback) *step_array.get(index).unwrap_or(fallback)
} }

View file

@ -0,0 +1,8 @@
close = Закрыць
license = Ліцэнзія
links = Спасылкі
developers = Распрацоўшчыкі
designers = Дызайнеры
artists = Мастакі
translators = Перакладчыкі
documenters = Дакументалісты

View file

@ -0,0 +1,7 @@
license = Licencia
links = Enlaces
developers = Desarrolladores
designers = Diseñadores
artists = Artistas
translators = Traductores
documenters = Documentadores

View file

@ -5,3 +5,4 @@ developers = Arendajad
artists = Kunstnikud artists = Kunstnikud
translators = Tõlkijad translators = Tõlkijad
documenters = Dokumenteerijad documenters = Dokumenteerijad
designers = Kujundajad

View file

@ -0,0 +1,8 @@
close = 閉じる
license = ライセンス
links = リンク
developers = 開発者
designers = デザイナー
artists = アーティスト
translators = 翻訳者
documenters = ドキュメント作成者

View file

@ -0,0 +1,8 @@
close = Закрыть
license = Лицензия
links = Ссылки
developers = Разработчики
designers = Дизайнеры
artists = Художники
translators = Переводчики
documenters = Авторы документации

View file

@ -1,11 +1,5 @@
# Context Drawer # Context Drawer
close = Затвори close = Затвори
# About # About
license = Лиценца license = Лиценца
links = Линкови links = Линкови
Developers = Програмери
Designers = Дизајнери
Artists = Уметници
Translators = Преводиоци
Documenters = Документатори

View file

@ -93,6 +93,7 @@ pub struct Cosmic<App: Application> {
), ),
>, >,
pub tracked_windows: HashSet<window::Id>, pub tracked_windows: HashSet<window::Id>,
pub opened_surfaces: HashMap<window::Id, u32>,
} }
impl<T: Application> Cosmic<T> impl<T: Application> Cosmic<T>
@ -113,7 +114,7 @@ where
( (
Self::new(model), Self::new(model),
Task::batch(vec![ Task::batch([
command, command,
iced_runtime::window::run_with_handle(id, init_windowing_system), iced_runtime::window::run_with_handle(id, init_windowing_system),
]), ]),
@ -161,6 +162,7 @@ where
} }
}) { }) {
let settings = settings(&mut self.app); let settings = settings(&mut self.app);
self.get_subsurface(settings, *view) self.get_subsurface(settings, *view)
} else { } else {
iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app)) iced_winit::commands::subsurface::get_subsurface(settings(&mut self.app))
@ -188,6 +190,7 @@ where
} }
}) { }) {
let settings = settings(); let settings = settings();
self.get_subsurface(settings, Box::new(move |_| view())) self.get_subsurface(settings, Box::new(move |_| view()))
} else { } else {
iced_winit::commands::subsurface::get_subsurface(settings()) iced_winit::commands::subsurface::get_subsurface(settings())
@ -292,6 +295,8 @@ where
} }
}) { }) {
let settings = settings(&mut self.app); let settings = settings(&mut self.app);
self.tracked_windows.insert(id);
self.get_window(id, settings, *view) self.get_window(id, settings, *view)
} else { } else {
let settings = settings(&mut self.app); let settings = settings(&mut self.app);
@ -327,6 +332,8 @@ where
} }
}) { }) {
let settings = settings(); let settings = settings();
self.tracked_windows.insert(id);
self.get_window(id, settings, Box::new(move |_| view())) self.get_window(id, settings, Box::new(move |_| view()))
} else { } else {
let settings = settings(); let settings = settings();
@ -384,7 +391,7 @@ where
pub fn style(&self, theme: &Theme) -> iced_runtime::Appearance { pub fn style(&self, theme: &Theme) -> iced_runtime::Appearance {
if let Some(style) = self.app.style() { if let Some(style) = self.app.style() {
style style
} else if self.app.core().window.sharp_corners { } else if self.app.core().window.is_maximized {
let theme = THEME.lock().unwrap(); let theme = THEME.lock().unwrap();
crate::style::iced::application::appearance(theme.borrow()) crate::style::iced::application::appearance(theme.borrow())
} else { } else {
@ -542,7 +549,7 @@ where
} }
#[cfg(feature = "multi-window")] #[cfg(feature = "multi-window")]
pub fn view(&self, id: window::Id) -> Element<crate::Action<T::Message>> { pub fn view(&self, id: window::Id) -> Element<'_, crate::Action<T::Message>> {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
if let Some((_, _, v)) = self.surface_views.get(&id) { if let Some((_, _, v)) = self.surface_views.get(&id) {
return v(&self.app); return v(&self.app);
@ -641,6 +648,8 @@ impl<T: Application> Cosmic<T> {
| WindowState::TILED_TOP | WindowState::TILED_TOP
| WindowState::TILED_BOTTOM, | WindowState::TILED_BOTTOM,
); );
self.app.core_mut().window.is_maximized =
state.intersects(WindowState::MAXIMIZED | WindowState::FULLSCREEN);
} }
if self.app.core().sync_window_border_radii_to_theme() { if self.app.core().sync_window_border_radii_to_theme() {
use iced_runtime::platform_specific::wayland::CornerRadius; use iced_runtime::platform_specific::wayland::CornerRadius;
@ -656,9 +665,21 @@ impl<T: Application> Cosmic<T> {
bottom_left: radii[3].round() as u32, bottom_left: radii[3].round() as u32,
}; };
let rounded = !self.app.core().window.sharp_corners; let rounded = !self.app.core().window.sharp_corners;
return Task::batch(vec![ return Task::batch([corner_radius(
corner_radius(id, rounded.then_some(cur_rad)).discard(), id,
]); if rounded {
Some(cur_rad)
} else {
let rad_0 = t.radius_0();
Some(CornerRadius {
top_left: rad_0[0].round() as u32,
top_right: rad_0[1].round() as u32,
bottom_right: rad_0[2].round() as u32,
bottom_left: rad_0[3].round() as u32,
})
},
)
.discard()]);
} }
} }
@ -792,19 +813,63 @@ impl<T: Application> Cosmic<T> {
.main_window_id() .main_window_id()
.unwrap_or(window::Id::RESERVED); .unwrap_or(window::Id::RESERVED);
let mut cmds = vec![ let mut cmds = vec![
corner_radius(main_window_id, rounded.then_some(cur_rad)).discard(), corner_radius(
main_window_id,
if rounded {
Some(cur_rad)
} else {
let rad_0 = t.radius_0();
Some(CornerRadius {
top_left: rad_0[0].round() as u32,
top_right: rad_0[1].round() as u32,
bottom_right: rad_0[2].round() as u32,
bottom_left: rad_0[3].round() as u32,
})
},
)
.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.iter() { for (id, (_, surface_type, _)) in self.surface_views.iter() {
if let SurfaceIdWrapper::Window(_) = surface_type { if let SurfaceIdWrapper::Window(_) = surface_type {
cmds.push( cmds.push(
corner_radius(*id, rounded.then_some(cur_rad)).discard(), corner_radius(
*id,
if rounded {
Some(cur_rad)
} else {
let rad_0 = t.radius_0();
Some(CornerRadius {
top_left: rad_0[0].round() as u32,
top_right: rad_0[1].round() as u32,
bottom_right: rad_0[2].round() as u32,
bottom_left: rad_0[3].round() as u32,
})
},
)
.discard(),
); );
} }
} }
// Update radius for all tracked windows // Update radius for all tracked windows
for id in self.tracked_windows.iter() { for id in self.tracked_windows.iter() {
cmds.push(corner_radius(*id, rounded.then_some(cur_rad)).discard()); cmds.push(
corner_radius(
*id,
if rounded {
Some(cur_rad)
} else {
let rad_0 = t.radius_0();
Some(CornerRadius {
top_left: rad_0[0].round() as u32,
top_right: rad_0[1].round() as u32,
bottom_right: rad_0[2].round() as u32,
bottom_left: rad_0[3].round() as u32,
})
},
)
.discard(),
);
} }
return Task::batch(cmds); return Task::batch(cmds);
@ -894,22 +959,62 @@ impl<T: Application> Cosmic<T> {
.main_window_id() .main_window_id()
.unwrap_or(window::Id::RESERVED); .unwrap_or(window::Id::RESERVED);
let mut cmds = vec![ let mut cmds = vec![
corner_radius(main_window_id, rounded.then_some(cur_rad)) corner_radius(
.discard(), main_window_id,
if rounded {
Some(cur_rad)
} else {
let rad_0 = t.radius_0();
Some(CornerRadius {
top_left: rad_0[0].round() as u32,
top_right: rad_0[1].round() as u32,
bottom_right: rad_0[2].round() as u32,
bottom_left: rad_0[3].round() as u32,
})
},
)
.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.iter() { for (id, (_, surface_type, _)) in self.surface_views.iter() {
if let SurfaceIdWrapper::Window(_) = surface_type { if let SurfaceIdWrapper::Window(_) = surface_type {
cmds.push( cmds.push(
corner_radius(*id, rounded.then_some(cur_rad)) corner_radius(
.discard(), *id,
if rounded {
Some(cur_rad)
} else {
let rad_0 = t.radius_0();
Some(CornerRadius {
top_left: rad_0[0].round() as u32,
top_right: rad_0[1].round() as u32,
bottom_right: rad_0[2].round() as u32,
bottom_left: rad_0[3].round() as u32,
})
},
)
.discard(),
); );
} }
} }
// Update radius for all tracked windows // Update radius for all tracked windows
for id in self.tracked_windows.iter() { for id in self.tracked_windows.iter() {
cmds.push( cmds.push(
corner_radius(*id, rounded.then_some(cur_rad)).discard(), corner_radius(
*id,
if rounded {
Some(cur_rad)
} else {
let rad_0 = t.radius_0();
Some(CornerRadius {
top_left: rad_0[0].round() as u32,
top_right: rad_0[1].round() as u32,
bottom_right: rad_0[2].round() as u32,
bottom_left: rad_0[3].round() as u32,
})
},
)
.discard(),
); );
} }
@ -935,9 +1040,15 @@ impl<T: Application> Cosmic<T> {
Action::Surface(action) => return self.surface_update(action), Action::Surface(action) => return self.surface_update(action),
Action::SurfaceClosed(id) => { Action::SurfaceClosed(id) => {
#[cfg(feature = "wayland")] if self.opened_surfaces.get_mut(&id).is_some_and(|v| {
self.surface_views.remove(&id); *v = v.saturating_sub(1);
self.tracked_windows.remove(&id); *v == 0
}) {
self.opened_surfaces.remove(&id);
#[cfg(feature = "wayland")]
self.surface_views.remove(&id);
self.tracked_windows.remove(&id);
}
let mut ret = if let Some(msg) = self.app.on_close_requested(id) { let mut ret = if let Some(msg) = self.app.on_close_requested(id) {
self.app.update(msg) self.app.update(msg)
@ -948,7 +1059,7 @@ impl<T: Application> Cosmic<T> {
if core.exit_on_main_window_closed if core.exit_on_main_window_closed
&& core.main_window_id().is_some_and(|m_id| id == m_id) && core.main_window_id().is_some_and(|m_id| id == m_id)
{ {
ret = Task::batch(vec![iced::exit::<crate::Action<T::Message>>()]); ret = Task::batch([iced::exit::<crate::Action<T::Message>>()]);
} }
return ret; return ret;
} }
@ -1101,7 +1212,6 @@ impl<T: Application> Cosmic<T> {
core.applet.suggested_bounds = b; core.applet.suggested_bounds = b;
} }
Action::Opened(id) => { Action::Opened(id) => {
self.tracked_windows.insert(id);
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
if self.app.core().sync_window_border_radii_to_theme() { if self.app.core().sync_window_border_radii_to_theme() {
use iced_runtime::platform_specific::wayland::CornerRadius; use iced_runtime::platform_specific::wayland::CornerRadius;
@ -1119,8 +1229,22 @@ impl<T: Application> Cosmic<T> {
// 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;
return Task::batch(vec![ return Task::batch([
corner_radius(id, rounded.then_some(cur_rad)).discard(), corner_radius(
id,
if rounded {
Some(cur_rad)
} else {
let rad_0 = t.radius_0();
Some(CornerRadius {
top_left: rad_0[0].round() as u32,
top_right: rad_0[1].round() as u32,
bottom_right: rad_0[2].round() as u32,
bottom_left: rad_0[3].round() as u32,
})
},
)
.discard(),
iced_runtime::window::run_with_handle(id, init_windowing_system), iced_runtime::window::run_with_handle(id, init_windowing_system),
]); ]);
} }
@ -1140,6 +1264,7 @@ impl<App: Application> Cosmic<App> {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
surface_views: HashMap::new(), surface_views: HashMap::new(),
tracked_windows: HashSet::new(), tracked_windows: HashSet::new(),
opened_surfaces: HashMap::new(),
} }
} }
@ -1154,6 +1279,7 @@ impl<App: Application> Cosmic<App> {
) -> Task<crate::Action<App::Message>> { ) -> Task<crate::Action<App::Message>> {
use iced_winit::commands::subsurface::get_subsurface; use iced_winit::commands::subsurface::get_subsurface;
*self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1;
self.surface_views.insert( self.surface_views.insert(
settings.id, settings.id,
( (
@ -1175,7 +1301,7 @@ impl<App: Application> Cosmic<App> {
>, >,
) -> Task<crate::Action<App::Message>> { ) -> Task<crate::Action<App::Message>> {
use iced_winit::commands::popup::get_popup; use iced_winit::commands::popup::get_popup;
*self.opened_surfaces.entry(settings.id).or_insert_with(|| 0) += 1;
self.surface_views.insert( self.surface_views.insert(
settings.id, settings.id,
( (
@ -1198,9 +1324,9 @@ impl<App: Application> Cosmic<App> {
>, >,
) -> Task<crate::Action<App::Message>> { ) -> Task<crate::Action<App::Message>> {
use iced_winit::SurfaceIdWrapper; use iced_winit::SurfaceIdWrapper;
*self.opened_surfaces.entry(id).or_insert(0) += 1;
self.surface_views.insert( self.surface_views.insert(
id.clone(), id,
( (
None, // TODO parent for window, platform specific option maybe? None, // TODO parent for window, platform specific option maybe?
SurfaceIdWrapper::Window(id), SurfaceIdWrapper::Window(id),

View file

@ -287,37 +287,37 @@ where
/// Displays a context drawer on the side of the application window when `Some`. /// Displays a context drawer on the side of the application window when `Some`.
/// Use the [`ApplicationExt::set_show_context`] function for this to take effect. /// Use the [`ApplicationExt::set_show_context`] function for this to take effect.
fn context_drawer(&self) -> Option<ContextDrawer<Self::Message>> { fn context_drawer(&self) -> Option<ContextDrawer<'_, Self::Message>> {
None None
} }
/// Displays a dialog in the center of the application window when `Some`. /// Displays a dialog in the center of the application window when `Some`.
fn dialog(&self) -> Option<Element<Self::Message>> { fn dialog(&self) -> Option<Element<'_, Self::Message>> {
None None
} }
/// Displays a footer at the bottom of the application window when `Some`. /// Displays a footer at the bottom of the application window when `Some`.
fn footer(&self) -> Option<Element<Self::Message>> { fn footer(&self) -> Option<Element<'_, Self::Message>> {
None None
} }
/// Attaches elements to the start section of the header. /// Attaches elements to the start section of the header.
fn header_start(&self) -> Vec<Element<Self::Message>> { fn header_start(&self) -> Vec<Element<'_, Self::Message>> {
Vec::new() Vec::new()
} }
/// Attaches elements to the center of the header. /// Attaches elements to the center of the header.
fn header_center(&self) -> Vec<Element<Self::Message>> { fn header_center(&self) -> Vec<Element<'_, Self::Message>> {
Vec::new() Vec::new()
} }
/// Attaches elements to the end section of the header. /// Attaches elements to the end section of the header.
fn header_end(&self) -> Vec<Element<Self::Message>> { fn header_end(&self) -> Vec<Element<'_, Self::Message>> {
Vec::new() Vec::new()
} }
/// Allows overriding the default nav bar widget. /// Allows overriding the default nav bar widget.
fn nav_bar(&self) -> Option<Element<crate::Action<Self::Message>>> { fn nav_bar(&self) -> Option<Element<'_, crate::Action<Self::Message>>> {
if !self.core().nav_bar_active() { if !self.core().nav_bar_active() {
return None; return None;
} }
@ -420,10 +420,10 @@ where
} }
/// Constructs the view for the main window. /// Constructs the view for the main window.
fn view(&self) -> Element<Self::Message>; fn view(&self) -> Element<'_, Self::Message>;
/// Constructs views for other windows. /// Constructs views for other windows.
fn view_window(&self, id: window::Id) -> Element<Self::Message> { fn view_window(&self, id: window::Id) -> Element<'_, Self::Message> {
panic!("no view for window {id:?}"); panic!("no view for window {id:?}");
} }
@ -485,7 +485,7 @@ pub trait ApplicationExt: Application {
fn set_window_title(&mut self, title: String, id: window::Id) -> Task<Self::Message>; fn set_window_title(&mut self, title: String, id: window::Id) -> Task<Self::Message>;
/// View template for the main window. /// View template for the main window.
fn view_main(&self) -> Element<crate::Action<Self::Message>>; fn view_main(&self) -> Element<'_, crate::Action<Self::Message>>;
fn watch_config<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone + PartialEq>( fn watch_config<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone + PartialEq>(
&self, &self,
@ -546,12 +546,11 @@ impl<App: Application> ApplicationExt for App {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
/// Creates the view for the main window. /// Creates the view for the main window.
fn view_main(&self) -> Element<crate::Action<Self::Message>> { fn view_main(&self) -> Element<'_, crate::Action<Self::Message>> {
let core = self.core(); let core = self.core();
let is_condensed = core.is_condensed(); let is_condensed = core.is_condensed();
// TODO: More granularity might be needed for different window border
// handling of maximized and tiled windows
let sharp_corners = core.window.sharp_corners; let sharp_corners = core.window.sharp_corners;
let maximized = core.window.is_maximized;
let content_container = core.window.content_container; let content_container = core.window.content_container;
let show_context = core.window.show_context; let show_context = core.window.show_context;
let nav_bar_active = core.nav_bar_active(); let nav_bar_active = core.nav_bar_active();
@ -560,7 +559,7 @@ impl<App: Application> ApplicationExt for App {
.iter() .iter()
.any(|i| Some(*i) == self.core().main_window_id()); .any(|i| Some(*i) == self.core().main_window_id());
let border_padding = if sharp_corners { 8 } else { 7 }; let border_padding = if maximized { 8 } else { 7 };
let main_content_padding = if !content_container { let main_content_padding = if !content_container {
[0, 0, 0, 0] [0, 0, 0, 0]
@ -698,17 +697,22 @@ impl<App: Application> ApplicationExt for App {
}; };
// Ensures visually aligned radii for content and window corners // Ensures visually aligned radii for content and window corners
let window_corner_radius = crate::theme::active() let window_corner_radius = if sharp_corners {
.cosmic() crate::theme::active().cosmic().radius_0()
.radius_s() } else {
.map(|x| if x < 4.0 { x } else { x + 4.0 }); crate::theme::active()
.cosmic()
.radius_s()
.map(|x| if x < 4.0 { x } else { x + 4.0 })
};
let view_column = crate::widget::column::with_capacity(2) let view_column = crate::widget::column::with_capacity(2)
.push_maybe(if core.window.show_headerbar { .push_maybe(if core.window.show_headerbar {
Some({ Some({
let mut header = crate::widget::header_bar() let mut header = crate::widget::header_bar()
.focused(focused) .focused(focused)
.maximized(sharp_corners) .maximized(maximized)
.sharp_corners(sharp_corners)
.title(&core.window.header_title) .title(&core.window.header_title)
.on_drag(crate::Action::Cosmic(Action::Drag)) .on_drag(crate::Action::Cosmic(Action::Drag))
.on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu)) .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu))
@ -766,16 +770,8 @@ impl<App: Application> ApplicationExt for App {
)), )),
border: iced::Border { border: iced::Border {
radius: [ radius: [
if sharp_corners { (window_corner_radius[0] - 1.0).max(0.0),
cosmic.radius_0()[0] (window_corner_radius[1] - 1.0).max(0.0),
} else {
window_corner_radius[0] - 1.0
},
if sharp_corners {
cosmic.radius_0()[1]
} else {
window_corner_radius[1] - 1.0
},
cosmic.radius_0()[2], cosmic.radius_0()[2],
cosmic.radius_0()[3], cosmic.radius_0()[3],
] ]
@ -794,7 +790,7 @@ impl<App: Application> ApplicationExt for App {
// The content element contains every element beneath the header. // The content element contains every element beneath the header.
.push(content) .push(content)
.apply(container) .apply(container)
.padding(if sharp_corners { 0 } else { 1 }) .padding(if maximized { 0 } else { 1 })
.class(crate::theme::Container::custom(move |theme| { .class(crate::theme::Container::custom(move |theme| {
container::Style { container::Style {
background: if content_container { background: if content_container {
@ -806,7 +802,7 @@ impl<App: Application> ApplicationExt for App {
}, },
border: iced::Border { border: iced::Border {
color: theme.cosmic().bg_divider().into(), color: theme.cosmic().bg_divider().into(),
width: if sharp_corners { 0.0 } else { 1.0 }, width: if maximized { 0.0 } else { 1.0 },
radius: window_corner_radius.into(), radius: window_corner_radius.into(),
}, },
..Default::default() ..Default::default()

View file

@ -38,6 +38,7 @@ pub struct Window {
pub show_close: bool, pub show_close: bool,
pub show_maximize: bool, pub show_maximize: bool,
pub show_minimize: bool, pub show_minimize: bool,
pub is_maximized: bool,
height: f32, height: f32,
width: f32, width: f32,
} }
@ -141,6 +142,7 @@ impl Default for Core {
show_maximize: true, show_maximize: true,
show_minimize: true, show_minimize: true,
show_window_menu: false, show_window_menu: false,
is_maximized: false,
height: 0., height: 0.,
width: 0., width: 0.,
}, },

View file

@ -136,7 +136,7 @@ pub fn load_applications_for_app_ids<'a>(
} }
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn load_desktop_file<'a>(locales: &'a [String], path: PathBuf) -> Option<DesktopEntryData> { pub fn load_desktop_file(locales: &[String], path: PathBuf) -> Option<DesktopEntryData> {
fde::DesktopEntry::from_path(path, Some(locales)) fde::DesktopEntry::from_path(path, Some(locales))
.ok() .ok()
.map(|de| DesktopEntryData::from_desktop_entry(locales, de)) .map(|de| DesktopEntryData::from_desktop_entry(locales, de))
@ -144,10 +144,7 @@ pub fn load_desktop_file<'a>(locales: &'a [String], path: PathBuf) -> Option<Des
#[cfg(not(windows))] #[cfg(not(windows))]
impl DesktopEntryData { impl DesktopEntryData {
pub fn from_desktop_entry<'a>( pub fn from_desktop_entry(locales: &[String], de: fde::DesktopEntry) -> DesktopEntryData {
locales: &'a [String],
de: fde::DesktopEntry,
) -> DesktopEntryData {
let name = de let name = de
.name(locales) .name(locales)
.unwrap_or(Cow::Borrowed(&de.appid)) .unwrap_or(Cow::Borrowed(&de.appid))

View file

@ -120,6 +120,12 @@ impl Dialog {
} }
} }
impl Default for Dialog {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "xdg-portal")] #[cfg(feature = "xdg-portal")]
mod portal { mod portal {
use super::Dialog; use super::Dialog;

View file

@ -22,15 +22,15 @@ pub type CosmicColor = ::palette::rgb::Srgba;
pub type CosmicComponent = cosmic_theme::Component; pub type CosmicComponent = cosmic_theme::Component;
pub type CosmicTheme = cosmic_theme::Theme; pub type CosmicTheme = cosmic_theme::Theme;
pub static COSMIC_DARK: LazyLock<CosmicTheme> = LazyLock::new(|| CosmicTheme::dark_default()); pub static COSMIC_DARK: LazyLock<CosmicTheme> = LazyLock::new(CosmicTheme::dark_default);
pub static COSMIC_HC_DARK: LazyLock<CosmicTheme> = pub static COSMIC_HC_DARK: LazyLock<CosmicTheme> =
LazyLock::new(|| CosmicTheme::high_contrast_dark_default()); LazyLock::new(CosmicTheme::high_contrast_dark_default);
pub static COSMIC_LIGHT: LazyLock<CosmicTheme> = LazyLock::new(|| CosmicTheme::light_default()); pub static COSMIC_LIGHT: LazyLock<CosmicTheme> = LazyLock::new(CosmicTheme::light_default);
pub static COSMIC_HC_LIGHT: LazyLock<CosmicTheme> = pub static COSMIC_HC_LIGHT: LazyLock<CosmicTheme> =
LazyLock::new(|| CosmicTheme::high_contrast_light_default()); LazyLock::new(CosmicTheme::high_contrast_light_default);
pub static TRANSPARENT_COMPONENT: LazyLock<Component> = LazyLock::new(|| Component { pub static TRANSPARENT_COMPONENT: LazyLock<Component> = LazyLock::new(|| Component {
base: CosmicColor::new(0.0, 0.0, 0.0, 0.0), base: CosmicColor::new(0.0, 0.0, 0.0, 0.0),

View file

@ -148,7 +148,7 @@ impl iced_button::Catalog for Theme {
impl Button { impl Button {
#[allow(clippy::trivially_copy_pass_by_ref)] #[allow(clippy::trivially_copy_pass_by_ref)]
#[allow(clippy::match_same_arms)] #[allow(clippy::match_same_arms)]
fn cosmic<'a>(&'a self, theme: &'a Theme) -> &CosmicComponent { fn cosmic<'a>(&'a self, theme: &'a Theme) -> &'a CosmicComponent {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
match self { match self {
Self::Primary => &cosmic.accent_button, Self::Primary => &cosmic.accent_button,
@ -898,12 +898,10 @@ impl toggler::Catalog for Theme {
let mut active = toggler::Style { let mut active = toggler::Style {
background: if matches!(status, toggler::Status::Active { is_toggled: true }) { background: if matches!(status, toggler::Status::Active { is_toggled: true }) {
cosmic.accent.base.into() cosmic.accent.base.into()
} else if cosmic.is_dark {
cosmic.palette.neutral_6.into()
} else { } else {
if cosmic.is_dark { cosmic.palette.neutral_5.into()
cosmic.palette.neutral_6.into()
} else {
cosmic.palette.neutral_5.into()
}
}, },
foreground: cosmic.palette.neutral_2.into(), foreground: cosmic.palette.neutral_2.into(),
border_radius: cosmic.radius_xl().into(), border_radius: cosmic.radius_xl().into(),
@ -1166,11 +1164,7 @@ impl scrollable::Catalog for Theme {
}, },
gap: None, gap: None,
}; };
let small_widget_container = self let small_widget_container = self.current_container().small_widget.with_alpha(0.7);
.current_container()
.small_widget
.clone()
.with_alpha(0.7);
if matches!(class, Scrollable::Permanent) { if matches!(class, Scrollable::Permanent) {
a.horizontal_rail.background = a.horizontal_rail.background =
@ -1233,11 +1227,8 @@ impl scrollable::Catalog for Theme {
}; };
if matches!(class, Scrollable::Permanent) { if matches!(class, Scrollable::Permanent) {
let small_widget_container = self let small_widget_container =
.current_container() self.current_container().small_widget.with_alpha(0.7);
.small_widget
.clone()
.with_alpha(0.7);
a.horizontal_rail.background = a.horizontal_rail.background =
Some(Background::Color(small_widget_container.into())); Some(Background::Color(small_widget_container.into()));

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
Element, fl, Apply, Element, fl,
iced::{Alignment, Length}, iced::{Alignment, Length},
widget::{self, horizontal_space}, widget::{self, horizontal_space},
}; };
@ -22,7 +22,7 @@ pub struct About {
copyright: Option<String>, copyright: Option<String>,
/// The license name. /// The license name.
license: Option<String>, license: Option<String>,
/// The license url. If None spdx.org url is used. /// The license url.
license_url: Option<String>, license_url: Option<String>,
/// Artists who contributed to the application. /// Artists who contributed to the application.
#[setters(skip)] #[setters(skip)]
@ -51,36 +51,28 @@ fn add_contributors(contributors: Vec<(&str, &str)>) -> Vec<(String, String)> {
.collect() .collect()
} }
macro_rules! set_contributors {
($field:ident, $doc:expr) => {
#[doc = $doc]
pub fn $field(mut self, contributors: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
self.$field = add_contributors(contributors.into());
self
}
};
}
impl<'a> About { impl<'a> About {
/// Artists who contributed to the application. set_contributors!(artists, "Artists who contributed to the application.");
pub fn artists(mut self, artists: impl Into<Vec<(&'a str, &'a str)>>) -> Self { set_contributors!(designers, "Designers who contributed to the application.");
self.artists = add_contributors(artists.into()); set_contributors!(developers, "Developers who contributed to the application.");
self set_contributors!(
} documenters,
"Documenters who contributed to the application."
/// Designers who contributed to the application. );
pub fn designers(mut self, designers: impl Into<Vec<(&'a str, &'a str)>>) -> Self { set_contributors!(
self.designers = add_contributors(designers.into()); translators,
self "Translators who contributed to the application."
} );
/// Developers who contributed to the application.
pub fn developers(mut self, developers: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
self.developers = add_contributors(developers.into());
self
}
/// Documenters who contributed to the application.
pub fn documenters(mut self, documenters: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
self.documenters = add_contributors(documenters.into());
self
}
/// Translators who contributed to the application.
pub fn translators(mut self, translators: impl Into<Vec<(&'a str, &'a str)>>) -> Self {
self.translators = add_contributors(translators.into());
self
}
/// Links associated with the application. /// Links associated with the application.
pub fn links<K: Into<String>, V: Into<String>>( pub fn links<K: Into<String>, V: Into<String>>(
@ -104,88 +96,84 @@ pub fn about<'a, Message: Clone + 'static>(
space_xxs, space_m, .. space_xxs, space_m, ..
} = crate::theme::spacing(); } = crate::theme::spacing();
let section_button = |name: &'a str, url: &'a str| -> Element<'a, Message> {
widget::row()
.push(widget::text(name))
.push(horizontal_space())
.push_maybe(
(!url.is_empty()).then_some(crate::widget::icon::from_name("link-symbolic").icon()),
)
.align_y(Alignment::Center)
.apply(widget::button::custom)
.class(crate::theme::Button::Link)
.on_press(on_url_press(url))
.width(Length::Fill)
.into()
};
let section = |list: &'a Vec<(String, String)>, title: String| { let section = |list: &'a Vec<(String, String)>, title: String| {
(!list.is_empty()).then_some({ (!list.is_empty()).then_some({
let items: Vec<Element<Message>> = let items: Vec<Element<Message>> = list
list.iter() .iter()
.map(|(name, url)| { .map(|(name, url)| section_button(name, url))
widget::button::custom( .collect();
widget::row()
.push(widget::text(name))
.push(horizontal_space())
.push_maybe((!url.is_empty()).then_some(
crate::widget::icon::from_name("link-symbolic").icon(),
))
.align_y(Alignment::Center),
)
.class(crate::theme::Button::Link)
.on_press(on_url_press(url))
.width(Length::Fill)
.into()
})
.collect();
widget::settings::section().title(title).extend(items) widget::settings::section().title(title).extend(items)
}) })
}; };
let application_name = about.name.as_ref().map(widget::text::title3); let header_children: Vec<Element<Message>> = [
let application_icon = about.icon.as_ref().map(|i| { about.icon.as_ref().map(|i| {
i.clone() i.clone()
.icon() .icon()
.content_fit(iced::ContentFit::Contain) .size(256)
.width(Length::Fixed(128.)) .width(Length::Fixed(128.))
.height(Length::Fixed(128.)) .height(Length::Fixed(128.))
}); .content_fit(iced::ContentFit::Contain)
let author = about.author.as_ref().map(widget::text::body); .into()
let version = about.version.as_ref().map(widget::button::standard); }),
about.name.as_ref().map(|n| widget::text::title3(n).into()),
about.author.as_ref().map(|a| widget::text::body(a).into()),
about.version.as_ref().map(|v| {
widget::button::standard(v)
.apply(widget::container)
.padding([space_xxs, 0, 0, 0])
.into()
}),
]
.into_iter()
.flatten()
.collect();
let header = (!header_children.is_empty())
.then_some(widget::column::with_children(header_children).align_x(Alignment::Center));
let links_section = section(&about.links, fl!("links")); let links_section = section(&about.links, fl!("links"));
let developers_section = section(&about.developers, fl!("developers")); let developers_section = section(&about.developers, fl!("developers"));
let designers_section = section(&about.designers, fl!("designers")); let designers_section = section(&about.designers, fl!("designers"));
let artists_section = section(&about.artists, fl!("artists")); let artists_section = section(&about.artists, fl!("artists"));
let translators_section = section(&about.translators, fl!("translators")); let translators_section = section(&about.translators, fl!("translators"));
let documenters_section = section(&about.documenters, fl!("documenters")); let documenters_section = section(&about.documenters, fl!("documenters"));
let license = about.license.as_ref().map(|license| { let license_section = about.license.as_ref().map(|license| {
let url = about.license_url.as_deref(); let url = about.license_url.as_deref().unwrap_or_default();
widget::settings::section().title(fl!("license")).add( widget::settings::section()
widget::button::custom( .title(fl!("license"))
widget::row() .add(section_button(license, url))
.push(widget::text(license))
.push(horizontal_space())
.push_maybe(
url.is_some()
.then_some(crate::widget::icon::from_name("link-symbolic").icon()),
)
.align_y(Alignment::Center),
)
.class(crate::theme::Button::Link)
.on_press(on_url_press(url.unwrap_or_default()))
.width(Length::Fill),
)
}); });
let copyright = about.copyright.as_ref().map(widget::text::body); let copyright = about.copyright.as_ref().map(widget::text::body);
let comments = about.comments.as_ref().map(widget::text::body); let comments = about.comments.as_ref().map(widget::text::body);
widget::column() widget::column()
.push( .push_maybe(header)
widget::column()
.push_maybe(application_icon)
.push_maybe(application_name)
.push_maybe(author)
.push_maybe(version)
.align_x(Alignment::Center)
.spacing(space_xxs),
)
.push_maybe(license)
.push_maybe(links_section) .push_maybe(links_section)
.push_maybe(developers_section) .push_maybe(developers_section)
.push_maybe(designers_section) .push_maybe(designers_section)
.push_maybe(artists_section) .push_maybe(artists_section)
.push_maybe(translators_section) .push_maybe(translators_section)
.push_maybe(documenters_section) .push_maybe(documenters_section)
.push_maybe(license_section)
.push_maybe(comments) .push_maybe(comments)
.push_maybe(copyright) .push_maybe(copyright)
.align_x(Alignment::Center)
.spacing(space_m) .spacing(space_m)
.width(Length::Fill) .width(Length::Fill)
.align_x(Alignment::Center)
.into() .into()
} }

View file

@ -793,7 +793,7 @@ pub fn update<'a, Message: Clone>(
} }
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) => { | Event::Touch(touch::Event::FingerLifted { .. }) => {
if let Some(on_press) = on_press.clone() { if let Some(on_press) = on_press {
let state = state(); let state = state();
if state.is_pressed { if state.is_pressed {
@ -816,9 +816,9 @@ pub fn update<'a, Message: Clone>(
#[cfg(feature = "a11y")] #[cfg(feature = "a11y")]
Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => { Event::A11y(event_id, iced_accessibility::accesskit::ActionRequest { action, .. }) => {
let state = state(); let state = state();
if let Some(Some(on_press)) = (event_id == event_id if let Some(on_press) = matches!(action, iced_accessibility::accesskit::Action::Default)
&& matches!(action, iced_accessibility::accesskit::Action::Default)) .then_some(on_press)
.then(|| on_press.clone()) .flatten()
{ {
state.is_pressed = false; state.is_pressed = false;
let msg = (on_press)(layout.virtual_offset(), layout.bounds()); let msg = (on_press)(layout.virtual_offset(), layout.bounds());
@ -828,7 +828,7 @@ pub fn update<'a, Message: Clone>(
return event::Status::Captured; return event::Status::Captured;
} }
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
if let Some(on_press) = on_press.clone() { if let Some(on_press) = on_press {
let state = state(); let state = state();
if state.is_focused && key == keyboard::Key::Named(keyboard::key::Named::Enter) { if state.is_focused && key == keyboard::Key::Named(keyboard::key::Named::Enter) {
state.is_pressed = true; state.is_pressed = true;

View file

@ -17,7 +17,7 @@ pub fn calendar<M>(
on_prev: impl Fn() -> M + 'static, on_prev: impl Fn() -> M + 'static,
on_next: impl Fn() -> M + 'static, on_next: impl Fn() -> M + 'static,
first_day_of_week: Weekday, first_day_of_week: Weekday,
) -> Calendar<M> { ) -> Calendar<'_, M> {
Calendar { Calendar {
model, model,
on_select: Box::new(on_select), on_select: Box::new(on_select),

View file

@ -233,7 +233,7 @@ impl ColorPickerModel {
pub fn builder<Message>( pub fn builder<Message>(
&self, &self,
on_update: fn(ColorPickerUpdate) -> Message, on_update: fn(ColorPickerUpdate) -> Message,
) -> ColorPickerBuilder<Message> { ) -> ColorPickerBuilder<'_, Message> {
ColorPickerBuilder { ColorPickerBuilder {
model: &self.segmented_model, model: &self.segmented_model,
active_color: self.active_color, active_color: self.active_color,

View file

@ -673,8 +673,8 @@ pub(super) enum OptionElement<'a, S, Item> {
} }
impl<S, Message> Model<S, Message> { impl<S, Message> Model<S, Message> {
pub(super) fn elements(&self) -> impl Iterator<Item = OptionElement<S, Message>> + '_ { pub(super) fn elements(&self) -> impl Iterator<Item = OptionElement<'_, S, Message>> + '_ {
let iterator = self.lists.iter().flat_map(|list| { self.lists.iter().flat_map(|list| {
let description = list let description = list
.description .description
.as_ref() .as_ref()
@ -686,9 +686,7 @@ impl<S, Message> Model<S, Message> {
description description
.chain(options) .chain(options)
.chain(std::iter::once(OptionElement::Separator)) .chain(std::iter::once(OptionElement::Separator))
}); })
iterator
} }
fn element_heights( fn element_heights(
@ -709,7 +707,7 @@ impl<S, Message> Model<S, Message> {
text_line_height: f32, text_line_height: f32,
offset: f32, offset: f32,
height: f32, height: f32,
) -> impl Iterator<Item = (OptionElement<S, Message>, f32)> + '_ { ) -> impl Iterator<Item = (OptionElement<'_, S, Message>, f32)> + '_ {
let heights = self.element_heights(padding_vertical, text_line_height); let heights = self.element_heights(padding_vertical, text_line_height);
let mut current = 0.0; let mut current = 0.0;

View file

@ -66,9 +66,7 @@ impl<S, Item: PartialEq> Model<S, Item> {
} }
pub(super) fn next(&self) -> Option<&(S, Item)> { pub(super) fn next(&self) -> Option<&(S, Item)> {
let Some(item) = self.selected.as_ref() else { let item = self.selected.as_ref()?;
return None;
};
let mut next = false; let mut next = false;
for list in &self.lists { for list in &self.lists {

View file

@ -24,6 +24,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
density: None, density: None,
focused: false, focused: false,
maximized: false, maximized: false,
sharp_corners: false,
is_ssd: false, is_ssd: false,
on_double_click: None, on_double_click: None,
is_condensed: false, is_condensed: false,
@ -83,6 +84,9 @@ pub struct HeaderBar<'a, Message> {
/// Maximized state of the window /// Maximized state of the window
maximized: bool, maximized: bool,
/// Whether the corners of the window should be sharp
sharp_corners: bool,
/// HeaderBar used for server-side decorations /// HeaderBar used for server-side decorations
is_ssd: bool, is_ssd: bool,
@ -289,11 +293,9 @@ impl<Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
) -> iced_accessibility::A11yTree { ) -> iced_accessibility::A11yTree {
let c_layout = layout.children().next().unwrap(); let c_layout = layout.children().next().unwrap();
let c_state = &state.children[0]; let c_state = &state.children[0];
let ret = self self.header_bar_inner
.header_bar_inner
.as_widget() .as_widget()
.a11y_nodes(c_layout, c_state, p); .a11y_nodes(c_layout, c_state, p)
ret
} }
} }
@ -409,7 +411,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
.apply(widget::container) .apply(widget::container)
.class(crate::theme::Container::HeaderBar { .class(crate::theme::Container::HeaderBar {
focused: self.focused, focused: self.focused,
sharp_corners: self.maximized, sharp_corners: self.sharp_corners,
}) })
.center_y(Length::Shrink) .center_y(Length::Shrink)
.apply(widget::mouse_area); .apply(widget::mouse_area);

View file

@ -97,7 +97,7 @@ impl Default for MenuBarStateInner {
} }
} }
pub(crate) fn menu_roots_children<Message>(menu_roots: &Vec<MenuTree<Message>>) -> Vec<Tree> pub(crate) fn menu_roots_children<Message>(menu_roots: &[MenuTree<Message>]) -> Vec<Tree>
where where
Message: Clone + 'static, Message: Clone + 'static,
{ {
@ -126,7 +126,7 @@ where
} }
#[allow(invalid_reference_casting)] #[allow(invalid_reference_casting)]
pub(crate) fn menu_roots_diff<Message>(menu_roots: &mut Vec<MenuTree<Message>>, tree: &mut Tree) pub(crate) fn menu_roots_diff<Message>(menu_roots: &mut [MenuTree<Message>], tree: &mut Tree)
where where
Message: Clone + 'static, Message: Clone + 'static,
{ {
@ -381,7 +381,7 @@ where
let surface_action = self.on_surface_action.as_ref().unwrap(); let surface_action = self.on_surface_action.as_ref().unwrap();
let old_active_root = my_state let old_active_root = my_state
.inner .inner
.with_data(|state| state.active_root.get(0).copied()); .with_data(|state| state.active_root.first().copied());
// if position is not on menu bar button skip. // if position is not on menu bar button skip.
let hovered_root = layout let hovered_root = layout

View file

@ -435,7 +435,7 @@ impl MenuState {
pub(crate) struct Menu<'b, Message: std::clone::Clone> { pub(crate) struct Menu<'b, Message: std::clone::Clone> {
pub(crate) tree: MenuBarState, pub(crate) tree: MenuBarState,
// Flattened menu tree // Flattened menu tree
pub(crate) menu_roots: Cow<'b, Vec<MenuTree<Message>>>, pub(crate) menu_roots: Cow<'b, [MenuTree<Message>]>,
pub(crate) bounds_expand: u16, pub(crate) bounds_expand: u16,
/// Allows menu overlay items to overlap the parent /// Allows menu overlay items to overlap the parent
pub(crate) menu_overlays_parent: bool, pub(crate) menu_overlays_parent: bool,
@ -740,7 +740,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let styling = theme.appearance(&self.style); let styling = theme.appearance(&self.style);
let roots = active_root.iter().skip(1).fold( let roots = active_root.iter().skip(1).fold(
&self.menu_roots[active_root[0]].children, &self.menu_roots[active_root[0]].children,
|mt, next_active_root| (&mt[*next_active_root].children), |mt, next_active_root| &mt[*next_active_root].children,
); );
let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>(); let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>();
state.menu_states[if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay { state.menu_states[if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay {

View file

@ -119,7 +119,7 @@ impl<Message: Clone + 'static> MenuTree<Message> {
}); });
mt.children.iter().for_each(|c| { mt.children.iter().for_each(|c| {
rec(&c, flat); rec(c, flat);
}); });
} }

View file

@ -26,7 +26,7 @@ pub type Model = segmented_button::SingleSelectModel;
pub fn nav_bar<Message: Clone + 'static>( pub fn nav_bar<Message: Clone + 'static>(
model: &segmented_button::SingleSelectModel, model: &segmented_button::SingleSelectModel,
on_activate: fn(segmented_button::Entity) -> Message, on_activate: fn(segmented_button::Entity) -> Message,
) -> NavBar<Message> { ) -> NavBar<'_, Message> {
NavBar { NavBar {
segmented_button: segmented_button::vertical(model).on_activate(on_activate), segmented_button: segmented_button::vertical(model).on_activate(on_activate),
} }
@ -41,7 +41,7 @@ pub fn nav_bar_dnd<Message, D: AllowedMimeTypes>(
on_dnd_leave: impl Fn(segmented_button::Entity) -> Message + 'static, on_dnd_leave: impl Fn(segmented_button::Entity) -> Message + 'static,
on_dnd_drop: impl Fn(segmented_button::Entity, Option<D>, DndAction) -> Message + 'static, on_dnd_drop: impl Fn(segmented_button::Entity, Option<D>, DndAction) -> Message + 'static,
id: DragId, id: DragId,
) -> NavBar<Message> ) -> NavBar<'_, Message>
where where
Message: Clone + 'static, Message: Clone + 'static,
{ {

View file

@ -132,7 +132,7 @@ impl ResponsiveMenuBar {
key_binds, key_binds,
trees trees
.into_iter() .into_iter()
.map(|mt| menu::Item::Folder(mt.0, mt.1.into())) .map(|mt| menu::Item::Folder(mt.0, mt.1))
.collect(), .collect(),
) )
.into_iter() .into_iter()

View file

@ -23,7 +23,7 @@ pub struct Horizontal;
/// For details on the model, see the [`segmented_button`](super) module for more details. /// For details on the model, see the [`segmented_button`](super) module for more details.
pub fn horizontal<SelectionMode: Default, Message>( pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode>,
) -> SegmentedButton<Horizontal, SelectionMode, Message> ) -> SegmentedButton<'_, Horizontal, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode>: Selectable,
{ {

View file

@ -292,7 +292,7 @@ where
/// ``` /// ```
#[must_use] #[must_use]
#[inline] #[inline]
pub fn insert(&mut self) -> EntityMut<SelectionMode> { pub fn insert(&mut self) -> EntityMut<'_, SelectionMode> {
let id = self.items.insert(Settings::default()); let id = self.items.insert(Settings::default());
self.order.push_back(id); self.order.push_back(id);
EntityMut { model: self, id } EntityMut { model: self, id }
@ -447,7 +447,11 @@ where
/// println!("{:?} had text {}", id, old_text) /// println!("{:?} had text {}", id, old_text)
/// } /// }
/// ``` /// ```
pub fn text_set(&mut self, id: Entity, text: impl Into<Cow<'static, str>>) -> Option<Cow<str>> { pub fn text_set(
&mut self,
id: Entity,
text: impl Into<Cow<'static, str>>,
) -> Option<Cow<'_, str>> {
if !self.contains_item(id) { if !self.contains_item(id) {
return None; return None;
} }

View file

@ -22,7 +22,7 @@ pub type VerticalSegmentedButton<'a, SelectionMode, Message> =
/// For details on the model, see the [`segmented_button`](super) module for more details. /// For details on the model, see the [`segmented_button`](super) module for more details.
pub fn vertical<SelectionMode, Message>( pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode>,
) -> SegmentedButton<Vertical, SelectionMode, Message> ) -> SegmentedButton<'_, Vertical, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode>: Selectable,
SelectionMode: Default, SelectionMode: Default,

View file

@ -263,7 +263,7 @@ where
/// Check if an item is enabled. /// Check if an item is enabled.
fn is_enabled(&self, key: Entity) -> bool { fn is_enabled(&self, key: Entity) -> bool {
self.model.items.get(key).map_or(false, |item| item.enabled) self.model.items.get(key).is_some_and(|item| item.enabled)
} }
/// Handle the dnd drop event. /// Handle the dnd drop event.
@ -987,7 +987,7 @@ where
let current = Instant::now(); let current = Instant::now();
// Permit successive scroll wheel events only after a given delay. // Permit successive scroll wheel events only after a given delay.
if state.wheel_timestamp.map_or(true, |previous| { if state.wheel_timestamp.is_none_or(|previous| {
current.duration_since(previous) > Duration::from_millis(250) current.duration_since(previous) > Duration::from_millis(250)
}) { }) {
state.wheel_timestamp = Some(current); state.wheel_timestamp = Some(current);
@ -1607,23 +1607,16 @@ where
let state = tree.state.downcast_ref::<LocalState>(); let state = tree.state.downcast_ref::<LocalState>();
let menu_state = state.menu_state.clone(); let menu_state = state.menu_state.clone();
let Some(entity) = state.show_context else { let entity = state.show_context?;
return None;
};
let bounds = self let mut bounds =
.variant_bounds(state, layout.bounds()) self.variant_bounds(state, layout.bounds())
.find_map(|item| match item { .find_map(|item| match item {
ItemBounds::Button(e, bounds) if e == entity => Some(bounds), ItemBounds::Button(e, bounds) if e == entity => Some(bounds),
_ => None, _ => None,
}); })?;
let Some(mut bounds) = bounds else {
return None;
};
let Some(context_menu) = self.context_menu.as_mut() else { let context_menu = self.context_menu.as_mut()?;
return None;
};
if !menu_state.inner.with_data(|data| data.open) { if !menu_state.inner.with_data(|data| data.open) {
// If the menu is not open, we don't need to show it. // If the menu is not open, we don't need to show it.
@ -1777,9 +1770,8 @@ impl LocalState {
impl operation::Focusable for LocalState { impl operation::Focusable for LocalState {
fn is_focused(&self) -> bool { fn is_focused(&self) -> bool {
self.focused.map_or(false, |f| { self.focused
f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get()) .is_some_and(|f| f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get()))
})
} }
fn focus(&mut self) { fn focus(&mut self) {

View file

@ -16,7 +16,7 @@ use super::segmented_button::{
/// For details on the model, see the [`segmented_button`] module for more details. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn horizontal<SelectionMode: Default, Message>( pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode>,
) -> HorizontalSegmentedButton<SelectionMode, Message> ) -> HorizontalSegmentedButton<'_, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode>: Selectable,
{ {
@ -39,7 +39,7 @@ where
/// For details on the model, see the [`segmented_button`] module for more details. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn vertical<SelectionMode, Message>( pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode>,
) -> VerticalSegmentedButton<SelectionMode, Message> ) -> VerticalSegmentedButton<'_, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode>: Selectable,
SelectionMode: Default, SelectionMode: Default,

View file

@ -131,7 +131,7 @@ impl<'a, Message: 'static> Item<'a, Message> {
contents.push(text(self.title).width(Length::Fill).into()); contents.push(text(self.title).width(Length::Fill).into());
} }
contents.push(widget.into()); contents.push(widget);
contents contents
} }

View file

@ -16,7 +16,7 @@ use super::segmented_button::{
/// For details on the model, see the [`segmented_button`] module for more details. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn horizontal<SelectionMode: Default, Message>( pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode>,
) -> HorizontalSegmentedButton<SelectionMode, Message> ) -> HorizontalSegmentedButton<'_, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode>: Selectable,
{ {
@ -37,7 +37,7 @@ where
/// For details on the model, see the [`segmented_button`] module for more details. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn vertical<SelectionMode, Message>( pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode>,
) -> VerticalSegmentedButton<SelectionMode, Message> ) -> VerticalSegmentedButton<'_, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode>: Selectable,
SelectionMode: Default, SelectionMode: Default,

View file

@ -221,7 +221,7 @@ where
/// let id = model.insert().text("Item A").icon("custom-icon").id(); /// let id = model.insert().text("Item A").icon("custom-icon").id();
/// ``` /// ```
#[must_use] #[must_use]
pub fn insert(&mut self, item: Item) -> EntityMut<SelectionMode, Item, Category> { pub fn insert(&mut self, item: Item) -> EntityMut<'_, SelectionMode, Item, Category> {
let id = self.items.insert(item); let id = self.items.insert(item);
self.order.push_back(id); self.order.push_back(id);
EntityMut { model: self, id } EntityMut { model: self, id }
@ -244,7 +244,7 @@ where
/// ``` /// ```
#[must_use] #[must_use]
pub fn is_enabled(&self, id: Entity) -> bool { pub fn is_enabled(&self, id: Entity) -> bool {
self.active.get(id).map_or(false, |e| *e) self.active.get(id).is_some_and(|e| *e)
} }
/// Iterates across items in the model in the order that they are displayed. /// Iterates across items in the model in the order that they are displayed.
@ -288,9 +288,7 @@ where
/// } /// }
/// ``` /// ```
pub fn position_set(&mut self, id: Entity, position: u16) -> Option<usize> { pub fn position_set(&mut self, id: Entity, position: u16) -> Option<usize> {
let Some(index) = self.position(id) else { let index = self.position(id)?;
return None;
};
self.order.remove(index as usize); self.order.remove(index as usize);

View file

@ -63,7 +63,7 @@ where
.map(|entity| { .map(|entity| {
let item = val.model.item(entity).unwrap(); let item = val.model.item(entity).unwrap();
let selected = val.model.is_active(entity); let selected = val.model.is_active(entity);
let context_menu = (val.item_context_builder)(&item); let context_menu = (val.item_context_builder)(item);
widget::column() widget::column()
.spacing(val.item_spacing) .spacing(val.item_spacing)
@ -89,14 +89,13 @@ where
.categories .categories
.iter() .iter()
.skip_while(|cat| **cat != Category::default()) .skip_while(|cat| **cat != Category::default())
.map(|category| { .flat_map(|category| {
vec![ [
widget::text::caption(item.get_text(*category)) widget::text::caption(item.get_text(*category))
.apply(Element::from), .apply(Element::from),
widget::text::caption("-").apply(Element::from), widget::text::caption("-").apply(Element::from),
] ]
}) })
.flatten()
.collect::<Vec<Element<'static, Message>>>(); .collect::<Vec<Element<'static, Message>>>();
elements.pop(); elements.pop();
elements elements
@ -201,7 +200,7 @@ where
divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs), divider_padding: Padding::from(0).left(space_xxxs).right(space_xxxs),
item_padding: Padding::from(space_xxs).into(), item_padding: Padding::from(space_xxs),
item_spacing: 0, item_spacing: 0,
icon_size: 48, icon_size: 48,

View file

@ -139,13 +139,13 @@ where
} else { } else {
val.model val.model
.iter() .iter()
.map(move |entity| { .flat_map(move |entity| {
let item = val.model.item(entity).unwrap(); let item = val.model.item(entity).unwrap();
let categories = &val.model.categories; let categories = &val.model.categories;
let selected = val.model.is_active(entity); let selected = val.model.is_active(entity);
let item_context = (val.item_context_builder)(&item); let item_context = (val.item_context_builder)(item);
vec![ [
divider::horizontal::default() divider::horizontal::default()
.apply(container) .apply(container)
.padding(val.divider_padding) .padding(val.divider_padding)
@ -233,13 +233,11 @@ where
.apply(Element::from), .apply(Element::from),
] ]
}) })
.flatten()
.collect::<Vec<Element<'a, Message>>>() .collect::<Vec<Element<'a, Message>>>()
}; };
vec![vec![header_row], items_full] let mut elements = items_full;
.into_iter() elements.insert(0, header_row);
.flatten() elements
.collect::<Vec<Element<'a, Message>>>()
.apply(widget::column::with_children) .apply(widget::column::with_children)
.width(val.width) .width(val.width)
.height(val.height) .height(val.height)
@ -272,7 +270,7 @@ where
width: Length::Fill, width: Length::Fill,
height: Length::Shrink, height: Length::Shrink,
item_padding: Padding::from(space_xxs).into(), item_padding: Padding::from(space_xxs),
item_spacing: 0, item_spacing: 0,
icon_spacing: space_xxxs, icon_spacing: space_xxxs,
icon_size: 24, icon_size: 24,

View file

@ -211,6 +211,7 @@ pub struct TextInput<'a, Message> {
always_active: bool, always_active: bool,
/// The text input tracks and manages the input value in its state. /// The text input tracks and manages the input value in its state.
manage_value: bool, manage_value: bool,
drag_threshold: f32,
} }
impl<'a, Message> TextInput<'a, Message> impl<'a, Message> TextInput<'a, Message>
@ -259,6 +260,7 @@ where
helper_text: None, helper_text: None,
always_active: false, always_active: false,
manage_value: false, manage_value: false,
drag_threshold: 20.0,
} }
} }
@ -546,7 +548,6 @@ where
} }
/// Get the layout node of the actual text input /// Get the layout node of the actual text input
fn text_layout<'b>(&'a self, layout: Layout<'b>) -> Layout<'b> { fn text_layout<'b>(&'a self, layout: Layout<'b>) -> Layout<'b> {
if self.dnd_icon { if self.dnd_icon {
layout layout
@ -558,6 +559,12 @@ where
layout.children().next().unwrap() layout.children().next().unwrap()
} }
} }
/// Set the drag threshold.
pub fn drag_threshold(mut self, drag_threshold: f32) -> Self {
self.drag_threshold = drag_threshold;
self
}
} }
impl<Message> Widget<Message, crate::Theme, crate::Renderer> for TextInput<'_, Message> impl<Message> Widget<Message, crate::Theme, crate::Renderer> for TextInput<'_, Message>
@ -927,6 +934,7 @@ where
line_height, line_height,
layout, layout,
self.manage_value, self.manage_value,
self.drag_threshold,
) )
} }
@ -1347,6 +1355,7 @@ pub fn update<'a, Message: Clone + 'static>(
line_height: text::LineHeight, line_height: text::LineHeight,
layout: Layout<'_>, layout: Layout<'_>,
manage_value: bool, manage_value: bool,
drag_threshold: f32,
) -> event::Status { ) -> event::Status {
let update_cache = |state, value| { let update_cache = |state, value| {
replace_paragraph( replace_paragraph(
@ -1389,8 +1398,8 @@ pub fn update<'a, Message: Clone + 'static>(
if let Some(cursor_position) = click_position { if let Some(cursor_position) = click_position {
// Check if the edit button was clicked. // Check if the edit button was clicked.
if state.dragging_state == None if state.dragging_state.is_none()
&& edit_button_layout.map_or(false, |l| cursor.is_over(l.bounds())) && edit_button_layout.is_some_and(|l| cursor.is_over(l.bounds()))
{ {
if is_editable_variant { if is_editable_variant {
state.is_read_only = !state.is_read_only; state.is_read_only = !state.is_read_only;
@ -1425,84 +1434,39 @@ pub fn update<'a, Message: Clone + 'static>(
) { ) {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
(None, click::Kind::Single, cursor::State::Selection { start, end }) => { (None, click::Kind::Single, cursor::State::Selection { start, end }) => {
// if something is already selected, we can start a drag and drop for a let left = start.min(end);
// single click that is on top of the selected text let right = end.max(start);
// is the click on selected text?
if on_input.is_some() || manage_value { let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
let left = start.min(end); state.value.raw(),
let right = end.max(start); text_layout.bounds(),
left,
);
let (left_position, _left_offset) = measure_cursor_and_scroll_offset( let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
state.value.raw(), state.value.raw(),
text_layout.bounds(), text_layout.bounds(),
left, right,
); );
let (right_position, _right_offset) = measure_cursor_and_scroll_offset( let width = right_position - left_position;
state.value.raw(), let selection_bounds = Rectangle {
text_layout.bounds(), x: text_layout.bounds().x + left_position,
right, y: text_layout.bounds().y,
); width,
height: text_layout.bounds().height,
};
let width = right_position - left_position; if cursor.is_over(selection_bounds) && (on_input.is_some() || manage_value)
let selection_bounds = Rectangle { {
x: text_layout.bounds().x + left_position, state.dragging_state = Some(DraggingState::PrepareDnd(cursor_position));
y: text_layout.bounds().y, return event::Status::Captured;
width,
height: text_layout.bounds().height,
};
if cursor.is_over(selection_bounds) {
// XXX never start a dnd if the input is secure
if is_secure {
return event::Status::Ignored;
}
let input_text =
state.selected_text(&value.to_string()).unwrap_or_default();
state.dragging_state = Some(DraggingState::Dnd(
DndAction::empty(),
input_text.clone(),
));
let mut editor = Editor::new(unsecured_value, &mut state.cursor);
editor.delete();
let contents = editor.contents();
let unsecured_value = Value::new(&contents);
state.tracked_value = unsecured_value.clone();
if let Some(on_input) = on_input {
let message = (on_input)(contents);
shell.publish(message);
}
if let Some(on_start_dnd) = on_start_dnd_source {
shell.publish(on_start_dnd(state.clone()));
}
let state_clone = state.clone();
iced_core::clipboard::start_dnd(
clipboard,
false,
id.map(iced_core::clipboard::DndSource::Widget),
Some(iced_core::clipboard::IconSurface::new(
Element::from(
TextInput::<'static, ()>::new("", input_text.clone())
.dnd_icon(true),
),
iced_core::widget::tree::State::new(state_clone),
Vector::ZERO,
)),
Box::new(TextInputString(input_text)),
DndAction::Move,
);
update_cache(state, &unsecured_value);
} else {
update_cache(state, value);
state.setting_selection(value, text_layout.bounds(), target);
}
} else {
state.setting_selection(value, text_layout.bounds(), target);
} }
// clear selection and place cursor at click position
update_cache(state, value);
state.setting_selection(value, text_layout.bounds(), target);
state.dragging_state = None;
return event::Status::Captured;
} }
(None, click::Kind::Single, _) => { (None, click::Kind::Single, _) => {
state.setting_selection(value, text_layout.bounds(), target); state.setting_selection(value, text_layout.bounds(), target);
@ -1576,6 +1540,15 @@ pub fn update<'a, Message: Clone + 'static>(
| Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => { | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
cold(); cold();
let state = state(); let state = state();
#[cfg(feature = "wayland")]
if matches!(state.dragging_state, Some(DraggingState::PrepareDnd(_))) {
// clear selection and place cursor at click position
update_cache(state, value);
if let Some(position) = cursor.position_over(layout.bounds()) {
let target = position.x - text_layout.bounds().x;
state.setting_selection(value, text_layout.bounds(), target);
}
}
state.dragging_state = None; state.dragging_state = None;
return if cursor.is_over(layout.bounds()) { return if cursor.is_over(layout.bounds()) {
@ -1599,6 +1572,58 @@ pub fn update<'a, Message: Clone + 'static>(
.cursor .cursor
.select_range(state.cursor.start(value), position); .select_range(state.cursor.start(value), position);
return event::Status::Captured;
}
#[cfg(feature = "wayland")]
if let Some(DraggingState::PrepareDnd(start_position)) = state.dragging_state {
let distance = ((position.x - start_position.x).powi(2)
+ (position.y - start_position.y).powi(2))
.sqrt();
if distance >= drag_threshold {
if is_secure {
return event::Status::Ignored;
}
let input_text = state.selected_text(&value.to_string()).unwrap_or_default();
state.dragging_state =
Some(DraggingState::Dnd(DndAction::empty(), input_text.clone()));
let mut editor = Editor::new(unsecured_value, &mut state.cursor);
editor.delete();
let contents = editor.contents();
let unsecured_value = Value::new(&contents);
state.tracked_value = unsecured_value.clone();
if let Some(on_input) = on_input {
let message = (on_input)(contents);
shell.publish(message);
}
if let Some(on_start_dnd) = on_start_dnd_source {
shell.publish(on_start_dnd(state.clone()));
}
let state_clone = state.clone();
iced_core::clipboard::start_dnd(
clipboard,
false,
id.map(iced_core::clipboard::DndSource::Widget),
Some(iced_core::clipboard::IconSurface::new(
Element::from(
TextInput::<'static, ()>::new("", input_text.clone())
.dnd_icon(true),
),
iced_core::widget::tree::State::new(state_clone),
Vector::ZERO,
)),
Box::new(TextInputString(input_text)),
DndAction::Move,
);
update_cache(state, &unsecured_value);
} else {
state.dragging_state = Some(DraggingState::PrepareDnd(start_position));
}
return event::Status::Captured; return event::Status::Captured;
} }
} }
@ -2277,7 +2302,7 @@ pub fn draw<'a, Message>(
let (cursor, offset) = if let Some(focus) = let (cursor, offset) = if let Some(focus) =
state.is_focused.filter(|f| f.focused).or_else(|| { state.is_focused.filter(|f| f.focused).or_else(|| {
let now = Instant::now(); let now = Instant::now();
handling_dnd_offer.then(|| Focus { handling_dnd_offer.then_some(Focus {
needs_update: false, needs_update: false,
updated_at: now, updated_at: now,
now, now,
@ -2520,10 +2545,12 @@ impl AsMimeTypes for TextInputString {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq)]
pub(crate) enum DraggingState { pub(crate) enum DraggingState {
Selection, Selection,
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
PrepareDnd(Point),
#[cfg(feature = "wayland")]
Dnd(DndAction, String), Dnd(DndAction, String),
} }

View file

@ -129,9 +129,7 @@ impl Value {
#[must_use] #[must_use]
pub fn secure(&self) -> Self { pub fn secure(&self) -> Self {
Self { Self {
graphemes: std::iter::repeat(String::from("•")) graphemes: std::iter::repeat_n(String::from("•"), self.graphemes.len()).collect(),
.take(self.graphemes.len())
.collect(),
} }
} }
} }