chore: updates after iced rebase

This commit is contained in:
Ashley Wulber 2026-02-24 15:18:57 -05:00 committed by Ashley Wulber
parent 7bb5ae7cfe
commit a48c4fc47d
16 changed files with 753 additions and 736 deletions

937
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -20,14 +20,18 @@ cosmic-comp-config = { path = "cosmic-comp-config", features = [
cosmic-config = { git = "https://github.com/pop-os/libcosmic/", features = [ cosmic-config = { git = "https://github.com/pop-os/libcosmic/", features = [
"calloop", "calloop",
"macro", "macro",
] } ], branch = "iced-rebase" }
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", rev = "160b086", default-features = false, features = [ cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", rev = "160b086", default-features = false, features = [
"server", "server",
] } ] }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", branch = "iced-rebase" }
cosmic-settings-daemon-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", features = [ cosmic-settings-daemon-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", features = [
"greeter", "greeter",
] } ], branch = "iced-rebase" }
# cosmic-settings-config = { path = "../cosmic-settings-daemon/config" }
# cosmic-settings-daemon-config = { path = "../cosmic-settings-daemon/cosmic-settings-daemon-config", features = [
# "greeter",
# ] }
cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git", features = [ cosmic-text = { git = "https://github.com/pop-os/cosmic-text.git", features = [
"shape-run-cache", "shape-run-cache",
] } ] }
@ -39,11 +43,11 @@ i18n-embed = { version = "0.16", features = [
"desktop-requester", "desktop-requester",
] } ] }
i18n-embed-fl = "0.10" i18n-embed-fl = "0.10"
iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic/" } iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic", branch = "iced-rebase" }
indexmap = "2.13" indexmap = "2.13"
keyframe = "1.1.1" keyframe = "1.1.1"
libc = "0.2.182" libc = "0.2.182"
libcosmic = { git = "https://github.com/pop-os/libcosmic/", default-features = false } libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false, branch = "iced-rebase" }
libsystemd = { version = "0.7", optional = true } libsystemd = { version = "0.7", optional = true }
log-panics = { version = "2", features = ["with-backtrace"] } log-panics = { version = "2", features = ["with-backtrace"] }
ordered-float = "5.1" ordered-float = "5.1"

View file

@ -4,7 +4,7 @@ version = "1.0.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
cosmic-config = { git = "https://github.com/pop-os/libcosmic/" } cosmic-config = { git = "https://github.com/pop-os/libcosmic", branch = "iced-rebase" }
cosmic-randr-shell = { git = "https://github.com/pop-os/cosmic-randr/", optional = true } cosmic-randr-shell = { git = "https://github.com/pop-os/cosmic-randr/", optional = true }
input = "0.9.1" input = "0.9.1"
libdisplay-info = { version = "0.3.0", optional = true } libdisplay-info = { version = "0.3.0", optional = true }

View file

@ -12,7 +12,7 @@ use cosmic::{
Apply, Apply,
iced::{ iced::{
Alignment, Alignment,
widget::{column, container, horizontal_space, row, vertical_space}, widget::{column, container, row, space},
}, },
iced_core::{Background, Border, Color, Length}, iced_core::{Background, Border, Color, Length},
theme, theme,
@ -70,6 +70,7 @@ impl Program for ResizeIndicatorInternal {
let edges = self.edges.lock().unwrap(); let edges = self.edges.lock().unwrap();
let icon_container_style = || { let icon_container_style = || {
theme::Container::custom(|theme| container::Style { theme::Container::custom(|theme| container::Style {
snap: true,
icon_color: Some(Color::from(theme.cosmic().accent.on)), icon_color: Some(Color::from(theme.cosmic().accent.on)),
text_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())), background: Some(Background::Color(theme.cosmic().accent_color().into())),
@ -99,7 +100,7 @@ impl Program for ResizeIndicatorInternal {
.center_x(Length::Fill) .center_x(Length::Fill)
.into() .into()
} else { } else {
vertical_space().height(36).into() space::vertical().height(36).into()
}, },
row(vec![ row(vec![
if edges.contains(ResizeEdge::LEFT) { if edges.contains(ResizeEdge::LEFT) {
@ -118,12 +119,12 @@ impl Program for ResizeIndicatorInternal {
.center_y(Length::Fill) .center_y(Length::Fill)
.into() .into()
} else { } else {
horizontal_space().width(36).into() space::horizontal().width(36).into()
}, },
row(vec![ row(vec![
text::heading(&self.shortcut1).into(), text::heading(&self.shortcut1).into(),
text::body(fl!("grow-window")).into(), text::body(fl!("grow-window")).into(),
horizontal_space().width(40).into(), space::horizontal().width(40).into(),
text::heading(&self.shortcut2).into(), text::heading(&self.shortcut2).into(),
text::body(fl!("shrink-window")).into(), text::body(fl!("shrink-window")).into(),
]) ])
@ -155,7 +156,7 @@ impl Program for ResizeIndicatorInternal {
.center_y(Length::Fill) .center_y(Length::Fill)
.into() .into()
} else { } else {
horizontal_space().width(36).into() space::horizontal().width(36).into()
}, },
]) ])
.width(Length::Fill) .width(Length::Fill)
@ -177,7 +178,7 @@ impl Program for ResizeIndicatorInternal {
.center_x(Length::Fill) .center_x(Length::Fill)
.into() .into()
} else { } else {
vertical_space().height(36).into() space::vertical().height(36).into()
}, },
]) ])
.into() .into()

View file

@ -1303,7 +1303,7 @@ impl Decorations<CosmicStackInternal, Message> for DefaultDecorations {
.height(Length::Fill) .height(Length::Fill)
.width(Length::Fill), .width(Length::Fill),
), ),
iced_widget::horizontal_space() iced_widget::space::horizontal()
.width(Length::Fixed(0.0)) .width(Length::Fixed(0.0))
.apply(iced_widget::container) .apply(iced_widget::container)
.padding([64, 24]) .padding([64, 24])
@ -1345,6 +1345,7 @@ impl Decorations<CosmicStackInternal, Message> for DefaultDecorations {
}; };
iced_widget::container::Style { iced_widget::container::Style {
snap: true,
icon_color: Some(cosmic_theme.background.on.into()), icon_color: Some(cosmic_theme.background.on.into()),
text_color: Some(cosmic_theme.background.on.into()), text_color: Some(cosmic_theme.background.on.into()),
background: Some(Background::Color(background.into())), background: Some(Background::Color(background.into())),

View file

@ -30,19 +30,19 @@ impl From<TabRuleTheme> for theme::Rule {
match theme { match theme {
TabRuleTheme::ActiveActivated => Self::custom(|theme| widget::rule::Style { TabRuleTheme::ActiveActivated => Self::custom(|theme| widget::rule::Style {
color: theme.cosmic().accent_color().into(), color: theme.cosmic().accent_color().into(),
width: 4, snap: true,
radius: 0.0.into(), radius: 0.0.into(),
fill_mode: FillMode::Full, fill_mode: FillMode::Full,
}), }),
TabRuleTheme::ActiveDeactivated => Self::custom(|theme| widget::rule::Style { TabRuleTheme::ActiveDeactivated => Self::custom(|theme| widget::rule::Style {
color: theme.cosmic().palette.neutral_5.into(), color: theme.cosmic().palette.neutral_5.into(),
width: 4, snap: true,
radius: 0.0.into(), radius: 0.0.into(),
fill_mode: FillMode::Full, fill_mode: FillMode::Full,
}), }),
TabRuleTheme::Default => Self::custom(|theme| widget::rule::Style { TabRuleTheme::Default => Self::custom(|theme| widget::rule::Style {
color: theme.cosmic().palette.neutral_5.into(), color: theme.cosmic().palette.neutral_5.into(),
width: 4, snap: true,
radius: 8.0.into(), radius: 8.0.into(),
fill_mode: FillMode::Padded(4), fill_mode: FillMode::Padded(4),
}), }),
@ -62,6 +62,7 @@ impl From<TabBackgroundTheme> for theme::Container<'_> {
match background_theme { match background_theme {
TabBackgroundTheme::ActiveActivated => { TabBackgroundTheme::ActiveActivated => {
Self::custom(move |theme| widget::container::Style { Self::custom(move |theme| widget::container::Style {
snap: true,
icon_color: Some(Color::from(theme.cosmic().accent_text_color())), icon_color: Some(Color::from(theme.cosmic().accent_text_color())),
text_color: Some(Color::from(theme.cosmic().accent_text_color())), text_color: Some(Color::from(theme.cosmic().accent_text_color())),
background: Some(Background::Color( background: Some(Background::Color(
@ -77,6 +78,7 @@ impl From<TabBackgroundTheme> for theme::Container<'_> {
} }
TabBackgroundTheme::ActiveDeactivated => { TabBackgroundTheme::ActiveDeactivated => {
Self::custom(move |theme| widget::container::Style { Self::custom(move |theme| widget::container::Style {
snap: true,
icon_color: None, icon_color: None,
text_color: None, text_color: None,
background: Some(Background::Color( background: Some(Background::Color(
@ -186,7 +188,7 @@ impl<Message: TabMessage + 'static> Tab<Message> {
} }
let items = vec![ let items = vec![
widget::vertical_rule(4).class(self.rule_theme).into(), widget::rule::vertical(4).class(self.rule_theme).into(),
self.app_icon self.app_icon
.clone() .clone()
.apply(widget::container) .apply(widget::container)
@ -262,7 +264,7 @@ where
Size::new(Length::Fill, Length::Fill) Size::new(Length::Fill, Length::Fill)
} }
fn layout(&self, tree: &mut Tree, renderer: &cosmic::Renderer, limits: &Limits) -> Node { fn layout(&mut self, tree: &mut Tree, renderer: &cosmic::Renderer, limits: &Limits) -> Node {
let min_size = Size { let min_size = Size {
height: TAB_HEIGHT as f32, height: TAB_HEIGHT as f32,
width: if self.active { width: if self.active {
@ -295,74 +297,68 @@ where
8., 8.,
cosmic::iced::Alignment::Center, cosmic::iced::Alignment::Center,
if size.width >= CLOSE_BREAKPOINT as f32 { if size.width >= CLOSE_BREAKPOINT as f32 {
&self.elements &mut self.elements
} else if size.width >= TEXT_BREAKPOINT as f32 { } else if size.width >= TEXT_BREAKPOINT as f32 {
&self.elements[0..3] &mut self.elements[0..3]
} else { } else {
&self.elements[0..2] &mut self.elements[0..2]
}, },
&mut tree.children, &mut tree.children,
) )
} }
fn operate( fn operate(
&self, &mut self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
operation: &mut dyn Operation<()>, operation: &mut dyn Operation<()>,
) { ) {
operation.container(None, layout.bounds(), &mut |operation| { operation.container(None, layout.bounds());
operation.traverse(&mut |operation| {
self.elements self.elements
.iter() .iter_mut()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.for_each(|((child, state), layout)| { .for_each(|((child, state), layout)| {
child child
.as_widget() .as_widget_mut()
.operate(state, layout, renderer, operation); .operate(state, layout, renderer, operation);
}); });
}); });
} }
fn on_event( fn update(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: event::Event, event: &event::Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) {
let status = self let status = self
.elements .elements
.iter_mut() .iter_mut()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.map(|((child, state), layout)| { .map(|((child, state), layout)| {
child.as_widget_mut().on_event( child.as_widget_mut().update(
state, state, event, layout, cursor, renderer, clipboard, shell, viewport,
event.clone(),
layout,
cursor,
renderer,
clipboard,
shell,
viewport,
) )
}) });
.fold(event::Status::Ignored, event::Status::merge);
if status == event::Status::Ignored && cursor.is_over(layout.bounds()) { if !shell.is_event_captured() && cursor.is_over(layout.bounds()) {
if matches!( if matches!(
event, event,
event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
) { ) {
if let Some(message) = self.press_message.clone() { if let Some(message) = self.press_message.clone() {
shell.publish(message); shell.publish(message);
return event::Status::Captured; shell.capture_event();
return;
} }
} }
if matches!( if matches!(
@ -371,7 +367,8 @@ where
) { ) {
if let Some(message) = self.right_click_message.clone() { if let Some(message) = self.right_click_message.clone() {
shell.publish(message); shell.publish(message);
return event::Status::Captured; shell.capture_event();
return;
} }
} }
if matches!( if matches!(
@ -379,11 +376,10 @@ where
event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
) { ) {
shell.publish(Message::activate(self.idx)); shell.publish(Message::activate(self.idx));
return event::Status::Captured; shell.capture_event();
return;
} }
} }
status
} }
fn mouse_interaction( fn mouse_interaction(
@ -447,10 +443,18 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'b>,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
viewport: &Rectangle,
translation: cosmic::iced::Vector, translation: cosmic::iced::Vector,
) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> { ) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> {
overlay::from_children(&mut self.elements, tree, layout, renderer, translation) overlay::from_children(
&mut self.elements,
tree,
layout,
renderer,
viewport,
translation,
)
} }
} }

View file

@ -78,10 +78,10 @@ impl TabText {
<cosmic::Renderer as TextRenderer>::Paragraph::with_text(Text { <cosmic::Renderer as TextRenderer>::Paragraph::with_text(Text {
content: &self.text, content: &self.text,
size: cosmic::iced_core::Pixels(self.font_size), size: cosmic::iced_core::Pixels(self.font_size),
bounds: Size::INFINITY, bounds: Size::INFINITE,
font: self.font, font: self.font,
horizontal_alignment: alignment::Horizontal::Left, align_x: cosmic::iced_core::text::Alignment::Left,
vertical_alignment: alignment::Vertical::Center, align_y: alignment::Vertical::Center,
shaping: Shaping::Advanced, shaping: Shaping::Advanced,
line_height: LineHeight::default(), line_height: LineHeight::default(),
wrapping: Wrapping::None, wrapping: Wrapping::None,
@ -107,7 +107,7 @@ impl<Message> Widget<Message, cosmic::Theme, cosmic::Renderer> for TabText {
Size::new(self.width, self.height) Size::new(self.width, self.height)
} }
fn layout(&self, tree: &mut Tree, _renderer: &cosmic::Renderer, limits: &Limits) -> Node { fn layout(&mut self, tree: &mut Tree, _renderer: &cosmic::Renderer, limits: &Limits) -> Node {
let state = tree.state.downcast_mut::<LocalState>(); let state = tree.state.downcast_mut::<LocalState>();
let text_bounds = state.paragraph.min_bounds(); let text_bounds = state.paragraph.min_bounds();
state.overflowed = limits.max().width < text_bounds.width; state.overflowed = limits.max().width < text_bounds.width;
@ -166,6 +166,7 @@ impl<Message> Widget<Message, cosmic::Theme, cosmic::Renderer> for TabText {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
snap: true,
bounds: Rectangle { bounds: Rectangle {
x: (bounds.x + bounds.width - 24.).max(bounds.x), x: (bounds.x + bounds.width - 24.).max(bounds.x),
width: 24.0_f32.min(bounds.width), width: 24.0_f32.min(bounds.width),

View file

@ -1,3 +1,5 @@
use crate::backend::render::element;
use super::tab::{MIN_ACTIVE_TAB_WIDTH, Tab, TabBackgroundTheme, TabMessage, TabRuleTheme}; use super::tab::{MIN_ACTIVE_TAB_WIDTH, Tab, TabBackgroundTheme, TabMessage, TabRuleTheme};
use cosmic::{ use cosmic::{
Apply, Apply,
@ -64,12 +66,12 @@ pub struct State {
} }
impl Scrollable for State { impl Scrollable for State {
fn snap_to(&mut self, offset: RelativeOffset) { fn snap_to(&mut self, offset: RelativeOffset<Option<f32>>) {
self.offset_x = Offset::Relative(offset.x.clamp(0.0, 1.0)); self.offset_x = Offset::Relative(offset.x.unwrap_or(0.0).clamp(0.0, 1.0));
} }
fn scroll_to(&mut self, offset: AbsoluteOffset) { fn scroll_to(&mut self, offset: AbsoluteOffset<Option<f32>>) {
let new_offset = Offset::Absolute(offset.x.max(0.0)); let new_offset = Offset::Absolute(offset.x.unwrap_or(0.0).max(0.0));
self.scroll_animation = Some(ScrollAnimationState { self.scroll_animation = Some(ScrollAnimationState {
start_time: Instant::now(), start_time: Instant::now(),
start: self.offset_x, start: self.offset_x,
@ -159,7 +161,7 @@ where
Element::new(tab.internal(i)) Element::new(tab.internal(i))
}); });
let tabs_rule = widget::vertical_rule(4).class(if tabs.len() - 1 == active { let tabs_rule = widget::rule::vertical(4).class(if tabs.len() - 1 == active {
if activated { if activated {
TabRuleTheme::ActiveActivated TabRuleTheme::ActiveActivated
} else { } else {
@ -193,12 +195,12 @@ where
let mut elements = Vec::with_capacity(tabs.len() + 5); let mut elements = Vec::with_capacity(tabs.len() + 5);
elements.push(widget::vertical_rule(4).class(rule_style).into()); elements.push(widget::rule::vertical(4).class(rule_style).into());
elements.push(prev_button.into()); elements.push(prev_button.into());
elements.extend(tabs); elements.extend(tabs);
elements.push(tabs_rule.into()); elements.push(tabs_rule.into());
elements.push(next_button.into()); elements.push(next_button.into());
elements.push(widget::vertical_rule(4).class(rule_style).into()); elements.push(widget::rule::vertical(4).class(rule_style).into());
Tabs { Tabs {
elements, elements,
@ -340,9 +342,9 @@ where
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn layout(&self, tree: &mut Tree, renderer: &cosmic::Renderer, limits: &Limits) -> Node { fn layout(&mut self, tree: &mut Tree, renderer: &cosmic::Renderer, limits: &Limits) -> Node {
let limits = limits.width(self.width).height(self.height); let limits = limits.width(self.width).height(self.height);
let element_count = self.elements.len();
// calculate the smallest possible size // calculate the smallest possible size
let child_limits = Limits::new( let child_limits = Limits::new(
Size::new(0.0, limits.min().height), Size::new(0.0, limits.min().height),
@ -351,10 +353,13 @@ where
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Shrink); .height(Length::Shrink);
let mut nodes = self.elements[2..self.elements.len() - 2] let mut nodes = self.elements[2..element_count - 2]
.iter() .iter_mut()
.zip(tree.children.iter_mut().skip(2)) .zip(tree.children.iter_mut().skip(2))
.map(|(tab, tab_tree)| tab.as_widget().layout(tab_tree, renderer, &child_limits)) .map(|(tab, tab_tree)| {
tab.as_widget_mut()
.layout(tab_tree, renderer, &child_limits)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// sum up // sum up
@ -370,8 +375,9 @@ where
if min_size.width <= size.width { if min_size.width <= size.width {
// we don't need to scroll // we don't need to scroll
let element_count = self.elements.len();
// can we make every tab equal weight and keep the active large enough? // can we make every tab equal weight and keep the active large enough?
let children = if (size.width / (self.elements.len() as f32 - 5.)).ceil() as i32 let children = if (size.width / (element_count as f32 - 5.)).ceil() as i32
>= MIN_ACTIVE_TAB_WIDTH >= MIN_ACTIVE_TAB_WIDTH
{ {
// just use a flex layout // just use a flex layout
@ -384,20 +390,20 @@ where
0.into(), 0.into(),
0., 0.,
cosmic::iced::Alignment::Center, cosmic::iced::Alignment::Center,
&self.elements[2..self.elements.len() - 2], &mut self.elements[2..element_count - 2],
&mut tree.children[2..self.elements.len() - 2], &mut tree.children[2..element_count - 2],
) )
.children() .children()
.to_vec() .to_vec()
} else { } else {
// otherwise we need a more manual approach // otherwise we need a more manual approach
let min_width = (size.width - MIN_ACTIVE_TAB_WIDTH as f32 - 4.) let min_width =
/ (self.elements.len() as f32 - 6.); (size.width - MIN_ACTIVE_TAB_WIDTH as f32 - 4.) / (element_count as f32 - 6.);
let mut offset = 0.; let mut offset = 0.;
let mut nodes = self.elements[2..self.elements.len() - 3] let mut nodes = self.elements[2..element_count - 3]
.iter() .iter_mut()
.zip(tree.children[2..].iter_mut()) .zip(tree.children[2..element_count - 3].iter_mut())
.map(|(tab, tab_tree)| { .map(|(tab, tab_tree)| {
let child_limits = Limits::new( let child_limits = Limits::new(
Size::new(min_width, limits.min().height), Size::new(min_width, limits.min().height),
@ -406,7 +412,9 @@ where
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Shrink); .height(Length::Shrink);
let mut node = tab.as_widget().layout(tab_tree, renderer, &child_limits); let mut node =
tab.as_widget_mut()
.layout(tab_tree, renderer, &child_limits);
node = node.move_to(Point::new(offset, 0.)); node = node.move_to(Point::new(offset, 0.));
offset += node.bounds().width; offset += node.bounds().width;
node node
@ -508,6 +516,7 @@ where
let background_style = Catalog::style( let background_style = Catalog::style(
theme, theme,
&theme::Container::custom(|theme| widget::container::Style { &theme::Container::custom(|theme| widget::container::Style {
snap: true,
icon_color: None, icon_color: None,
text_color: None, text_color: None,
background: Some(Background::Color( background: Some(Background::Color(
@ -605,6 +614,9 @@ where
let cursor = match cursor { let cursor = match cursor {
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset), mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
mouse::Cursor::Unavailable => mouse::Cursor::Unavailable, mouse::Cursor::Unavailable => mouse::Cursor::Unavailable,
mouse::Cursor::Levitating(point) => {
mouse::Cursor::Levitating(point + offset)
}
}; };
renderer.with_layer(bounds, |renderer| { renderer.with_layer(bounds, |renderer| {
@ -677,7 +689,7 @@ where
} }
fn operate( fn operate(
&self, &mut self,
tree: &mut Tree, tree: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
@ -691,38 +703,38 @@ where
state.cleanup_old_animations(); state.cleanup_old_animations();
operation.scrollable( operation.scrollable(
state,
self.id.as_ref(), self.id.as_ref(),
bounds, bounds,
content_bounds, content_bounds,
Vector { x: 0.0, y: 0.0 }, /* seemingly unused */ Vector { x: 0.0, y: 0.0 },
state,
); );
let element_count = self.elements.len();
operation.container(self.id.as_ref(), bounds, &mut |operation| { operation.traverse(&mut |operation| {
self.elements[2..self.elements.len() - 3] self.elements[2..element_count - 3]
.iter() .iter_mut()
.zip(tree.children.iter_mut().skip(2)) .zip(tree.children.iter_mut().skip(2))
.zip(layout.children().skip(2)) .zip(layout.children().skip(2))
.for_each(|((child, state), layout)| { .for_each(|((child, state), layout)| {
child child
.as_widget() .as_widget_mut()
.operate(state, layout, renderer, operation); .operate(state, layout, renderer, operation);
}); });
}); });
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn on_event( fn update(
&mut self, &mut self,
tree: &mut Tree, tree: &mut Tree,
event: event::Event, event: &event::Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) {
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
state.cleanup_old_animations(); state.cleanup_old_animations();
@ -849,15 +861,15 @@ where
let mut internal_shell = Shell::new(&mut messages); let mut internal_shell = Shell::new(&mut messages);
let len = self.elements.len(); let len = self.elements.len();
let result = if scrolling && cursor.position().is_some_and(|pos| pos.x < bounds.x) { if scrolling && cursor.position().is_some_and(|pos| pos.x < bounds.x) {
self.elements[0..2] self.elements[0..2]
.iter_mut() .iter_mut()
.zip(&mut tree.children) .zip(&mut tree.children)
.zip(layout.children()) .zip(layout.children())
.map(|((child, state), layout)| { .map(|((child, state), layout)| {
child.as_widget_mut().on_event( child.as_widget_mut().update(
state, state,
event.clone(), event,
layout, layout,
cursor, cursor,
renderer, renderer,
@ -865,8 +877,7 @@ where
&mut internal_shell, &mut internal_shell,
viewport, viewport,
) )
}) });
.fold(event::Status::Ignored, event::Status::merge)
} else if scrolling } else if scrolling
&& cursor && cursor
.position() .position()
@ -877,9 +888,9 @@ where
.zip(tree.children.iter_mut().skip(len - 3)) .zip(tree.children.iter_mut().skip(len - 3))
.zip(layout.children().skip(len - 3)) .zip(layout.children().skip(len - 3))
.map(|((child, state), layout)| { .map(|((child, state), layout)| {
child.as_widget_mut().on_event( child.as_widget_mut().update(
state, state,
event.clone(), event,
layout, layout,
cursor, cursor,
renderer, renderer,
@ -887,8 +898,7 @@ where
&mut internal_shell, &mut internal_shell,
viewport, viewport,
) )
}) });
.fold(event::Status::Ignored, event::Status::merge)
} else { } else {
self.elements[2..len - 3] self.elements[2..len - 3]
.iter_mut() .iter_mut()
@ -898,11 +908,14 @@ where
let cursor = match cursor { let cursor = match cursor {
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset), mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
mouse::Cursor::Unavailable => mouse::Cursor::Unavailable, mouse::Cursor::Unavailable => mouse::Cursor::Unavailable,
mouse::Cursor::Levitating(point) => {
mouse::Cursor::Levitating(point + offset)
}
}; };
child.as_widget_mut().on_event( child.as_widget_mut().update(
state, state,
event.clone(), event,
layout, layout,
cursor, cursor,
renderer, renderer,
@ -910,8 +923,7 @@ where
&mut internal_shell, &mut internal_shell,
viewport, viewport,
) )
}) });
.fold(event::Status::Ignored, event::Status::merge)
}; };
for mut message in messages { for mut message in messages {
@ -919,14 +931,12 @@ where
x: state.offset_x.absolute(bounds.width, content_bounds.width), x: state.offset_x.absolute(bounds.width, content_bounds.width),
y: 0., y: 0.,
}) { }) {
state.scroll_to(offset); state.scroll_to(offset.into());
continue; continue;
} }
shell.publish(message); shell.publish(message);
} }
result
} }
fn mouse_interaction( fn mouse_interaction(
@ -992,6 +1002,9 @@ where
let cursor = match cursor { let cursor = match cursor {
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset), mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
mouse::Cursor::Unavailable => mouse::Cursor::Unavailable, mouse::Cursor::Unavailable => mouse::Cursor::Unavailable,
mouse::Cursor::Levitating(point) => {
mouse::Cursor::Levitating(point + offset)
}
}; };
child.as_widget().mouse_interaction( child.as_widget().mouse_interaction(
@ -1010,10 +1023,18 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
tree: &'b mut Tree, tree: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'b>,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
viewport: &Rectangle,
translation: cosmic::iced::Vector, translation: cosmic::iced::Vector,
) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> { ) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> {
overlay::from_children(&mut self.elements, tree, layout, renderer, translation) overlay::from_children(
&mut self.elements,
tree,
layout,
renderer,
viewport,
translation,
)
} }
} }

View file

@ -12,7 +12,7 @@ use cosmic::{
}, },
iced_core::{Background, Border, Color, Length}, iced_core::{Background, Border, Color, Length},
theme, theme,
widget::{horizontal_space, icon::from_name, text}, widget::{icon::from_name, space, text},
}; };
use smithay::utils::{Logical, Size}; use smithay::utils::{Logical, Size};
@ -38,7 +38,7 @@ impl Program for StackHoverInternal {
.prefer_svg(true) .prefer_svg(true)
.icon() .icon()
.into(), .into(),
horizontal_space().width(16).into(), space::horizontal().width(16).into(),
text::title3(fl!("stack-windows")).into(), text::title3(fl!("stack-windows")).into(),
]) ])
.align_y(Alignment::Center) .align_y(Alignment::Center)
@ -48,6 +48,7 @@ impl Program for StackHoverInternal {
.padding(16) .padding(16)
.apply(container) .apply(container)
.class(theme::Container::custom(|theme| container::Style { .class(theme::Container::custom(|theme| container::Style {
snap: true,
icon_color: Some(Color::from(theme.cosmic().accent.on)), icon_color: Some(Color::from(theme.cosmic().accent.on)),
text_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())), background: Some(Background::Color(theme.cosmic().accent_color().into())),

View file

@ -6,7 +6,7 @@ use crate::{
use calloop::LoopHandle; use calloop::LoopHandle;
use cosmic::{ use cosmic::{
Apply, Apply,
iced::widget::{container, horizontal_space, row}, iced::widget::{container, row, space},
iced_core::{Alignment, Background, Border, Color, Length}, iced_core::{Alignment, Background, Border, Color, Length},
theme, theme,
widget::{icon::from_name, text}, widget::{icon::from_name, text},
@ -34,7 +34,7 @@ impl Program for SwapIndicatorInternal {
.prefer_svg(true) .prefer_svg(true)
.icon() .icon()
.into(), .into(),
horizontal_space().width(16).into(), space::horizontal().width(16).into(),
text::title3(fl!("swap-windows")).into(), text::title3(fl!("swap-windows")).into(),
]) ])
.align_y(Alignment::Center) .align_y(Alignment::Center)
@ -44,6 +44,7 @@ impl Program for SwapIndicatorInternal {
.padding(16) .padding(16)
.apply(container) .apply(container)
.class(theme::Container::custom(|theme| container::Style { .class(theme::Container::custom(|theme| container::Style {
snap: true,
icon_color: Some(Color::from(theme.cosmic().accent.on)), icon_color: Some(Color::from(theme.cosmic().accent.on)),
text_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())), background: Some(Background::Color(theme.cosmic().accent_color().into())),

View file

@ -52,13 +52,13 @@ where
} }
fn layout( fn layout(
&self, &mut self,
state: &mut Tree, state: &mut Tree,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
limits: &layout::Limits, limits: &layout::Limits,
) -> layout::Node { ) -> layout::Node {
let state = &mut state.children[0]; let state = &mut state.children[0];
let node = self.elem.as_widget().layout(state, renderer, limits); let node = self.elem.as_widget_mut().layout(state, renderer, limits);
layout::Node::with_children(node.size(), vec![node]) layout::Node::with_children(node.size(), vec![node])
} }
@ -81,6 +81,7 @@ where
renderer.fill_quad( renderer.fill_quad(
Quad { Quad {
snap: true,
bounds: layout.bounds(), bounds: layout.bounds(),
border: Border { border: Border {
radius: styling.border_radius, radius: styling.border_radius,
@ -128,7 +129,7 @@ where
} }
fn operate( fn operate(
&self, &mut self,
state: &mut Tree, state: &mut Tree,
layout: Layout<'_>, layout: Layout<'_>,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
@ -137,21 +138,21 @@ where
let state = &mut state.children[0]; let state = &mut state.children[0];
let layout = layout.children().next().unwrap(); let layout = layout.children().next().unwrap();
self.elem self.elem
.as_widget() .as_widget_mut()
.operate(state, layout, renderer, operation) .operate(state, layout, renderer, operation)
} }
fn on_event( fn update(
&mut self, &mut self,
state: &mut Tree, state: &mut Tree,
event: Event, event: &Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) {
let mut bounds = layout.bounds(); let mut bounds = layout.bounds();
// fix padding 1 and event... don't ask. // fix padding 1 and event... don't ask.
@ -180,9 +181,9 @@ where
let state = &mut state.children[0]; let state = &mut state.children[0];
let layout = layout.children().next().unwrap(); let layout = layout.children().next().unwrap();
self.elem.as_widget_mut().on_event( self.elem.as_widget_mut().update(
state, event, layout, cursor, renderer, clipboard, shell, viewport, state, event, layout, cursor, renderer, clipboard, shell, viewport,
) );
} }
fn mouse_interaction( fn mouse_interaction(
@ -203,15 +204,16 @@ where
fn overlay<'b>( fn overlay<'b>(
&'b mut self, &'b mut self,
state: &'b mut Tree, state: &'b mut Tree,
layout: Layout<'_>, layout: Layout<'b>,
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
viewport: &Rectangle,
translation: cosmic::iced::Vector, translation: cosmic::iced::Vector,
) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> { ) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> {
let state = &mut state.children[0]; let state = &mut state.children[0];
let layout = layout.children().next().unwrap(); let layout = layout.children().next().unwrap();
self.elem self.elem
.as_widget_mut() .as_widget_mut()
.overlay(state, layout, renderer, translation) .overlay(state, layout, renderer, viewport, translation)
} }
} }

View file

@ -13,7 +13,7 @@ use cosmic::{
iced_core::{Border, Length, Rectangle as IcedRectangle, alignment::Horizontal}, iced_core::{Border, Length, Rectangle as IcedRectangle, alignment::Horizontal},
iced_widget::{self, Column, Row, text::Style as TextStyle}, iced_widget::{self, Column, Row, text::Style as TextStyle},
theme, theme,
widget::{button, divider, horizontal_space, icon::from_name, text}, widget::{button, divider, icon::from_name, space, text},
}; };
use smithay::{ use smithay::{
backend::{ backend::{
@ -392,7 +392,7 @@ impl Program for ContextMenu {
match item { match item {
Item::Separator => divider::horizontal::light().into(), Item::Separator => divider::horizontal::light().into(),
Item::Submenu { title, .. } => Row::with_children(vec![ Item::Submenu { title, .. } => Row::with_children(vec![
horizontal_space().width(16).into(), space::horizontal().width(16).into(),
text::body(title).width(mode).into(), text::body(title).width(mode).into(),
from_name("go-next-symbolic") from_name("go-next-symbolic")
.size(16) .size(16)
@ -425,7 +425,7 @@ impl Program for ContextMenu {
})) }))
.into() .into()
} else { } else {
horizontal_space().width(16).into() space::horizontal().width(16).into()
}, },
text::body(title) text::body(title)
.width(mode) .width(mode)
@ -441,7 +441,7 @@ impl Program for ContextMenu {
theme::Text::Default theme::Text::Default
}) })
.into(), .into(),
horizontal_space().width(16).into(), space::horizontal().width(16).into(),
]; ];
if let Some(shortcut) = shortcut.as_ref() { if let Some(shortcut) = shortcut.as_ref() {
components.push( components.push(
@ -479,6 +479,7 @@ impl Program for ContextMenu {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
let component = &cosmic.background.component; let component = &cosmic.background.component;
iced_widget::container::Style { iced_widget::container::Style {
snap: true,
icon_color: Some(cosmic.accent.base.into()), icon_color: Some(cosmic.accent.base.into()),
text_color: Some(component.on.into()), text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())), background: Some(Background::Color(component.base.into())),

View file

@ -514,6 +514,7 @@ impl Program for ZoomProgram {
let cosmic = theme.cosmic(); let cosmic = theme.cosmic();
let component = &cosmic.background.component; let component = &cosmic.background.component;
iced_widget::container::Style { iced_widget::container::Style {
snap: true,
icon_color: Some(component.on.into()), icon_color: Some(component.on.into()),
text_color: Some(component.on.into()), text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())), background: Some(Background::Color(component.base.into())),

View file

@ -18,11 +18,7 @@ use cosmic::{
window::Event as WindowEvent, window::Event as WindowEvent,
}, },
iced_core::{Color, Length, Pixels, clipboard::Null as NullClipboard, id::Id, renderer::Style}, iced_core::{Color, Length, Pixels, clipboard::Null as NullClipboard, id::Id, renderer::Style},
iced_runtime::{ iced_runtime::{Action, task::into_stream},
Action, Debug,
program::{Program as IcedProgram, State},
task::into_stream,
},
}; };
use iced_tiny_skia::{ use iced_tiny_skia::{
Layer, Layer,
@ -66,6 +62,8 @@ use smithay::{
}, },
}; };
use crate::utils::state::State;
static ID: LazyLock<Id> = LazyLock::new(|| Id::new("Program")); static ID: LazyLock<Id> = LazyLock::new(|| Id::new("Program"));
pub struct IcedElement<P: Program + Send + 'static>(pub(crate) Arc<Mutex<IcedElementInternal<P>>>); pub struct IcedElement<P: Program + Send + 'static>(pub(crate) Arc<Mutex<IcedElementInternal<P>>>);
@ -99,6 +97,26 @@ impl<P: Program + Send + 'static> Hash for IcedElement<P> {
} }
} }
pub trait IcedProgram {
type Message: std::fmt::Debug + Send;
fn update(&mut self, _message: Self::Message) -> Task<Self::Message> {
Task::none()
}
fn view(&self) -> cosmic::Element<'_, Self::Message>;
fn background_color(&self) -> Color {
Color::TRANSPARENT
}
fn foreground(
&self,
_pixels: &mut tiny_skia::PixmapMut<'_>,
_damage: &[Rectangle<i32, BufferCoords>],
_scale: f32,
) {
}
}
pub trait Program { pub trait Program {
type Message: std::fmt::Debug + Send; type Message: std::fmt::Debug + Send;
fn update( fn update(
@ -135,8 +153,6 @@ struct ProgramWrapper<P: Program> {
impl<P: Program> IcedProgram for ProgramWrapper<P> { impl<P: Program> IcedProgram for ProgramWrapper<P> {
type Message = <P as Program>::Message; type Message = <P as Program>::Message;
type Renderer = cosmic::Renderer;
type Theme = cosmic::Theme;
fn update(&mut self, message: Self::Message) -> Task<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
let last_seat = self.last_seat.lock().unwrap(); let last_seat = self.last_seat.lock().unwrap();
@ -165,7 +181,6 @@ pub(crate) struct IcedElementInternal<P: Program + Send + 'static> {
theme: Theme, theme: Theme,
renderer: cosmic::Renderer, renderer: cosmic::Renderer,
state: State<ProgramWrapper<P>>, state: State<ProgramWrapper<P>>,
debug: Debug,
// futures // futures
handle: LoopHandle<'static, crate::state::State>, handle: LoopHandle<'static, crate::state::State>,
@ -189,7 +204,6 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
tracing::warn!("Missing force_update call"); tracing::warn!("Missing force_update call");
} }
let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0)); let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0));
let mut debug = Debug::new();
let state = State::new( let state = State::new(
ID.clone(), ID.clone(),
ProgramWrapper { ProgramWrapper {
@ -199,7 +213,6 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
}, },
IcedSize::new(self.size.w as f32, self.size.h as f32), IcedSize::new(self.size.w as f32, self.size.h as f32),
&mut renderer, &mut renderer,
&mut debug,
); );
IcedElementInternal { IcedElementInternal {
@ -214,7 +227,6 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
theme: self.theme.clone(), theme: self.theme.clone(),
renderer, renderer,
state, state,
debug,
handle, handle,
scheduler, scheduler,
executor_token, executor_token,
@ -240,7 +252,6 @@ impl<P: Program + Send + 'static> fmt::Debug for IcedElementInternal<P> {
.field("theme", &"...") .field("theme", &"...")
.field("renderer", &"...") .field("renderer", &"...")
.field("state", &"...") .field("state", &"...")
.field("debug", &self.debug)
.field("handle", &self.handle) .field("handle", &self.handle)
.field("scheduler", &self.scheduler) .field("scheduler", &self.scheduler)
.field("executor_token", &self.executor_token) .field("executor_token", &self.executor_token)
@ -265,7 +276,6 @@ impl<P: Program + Send + 'static> IcedElement<P> {
let size = size.into(); let size = size.into();
let last_seat = Arc::new(Mutex::new(None)); let last_seat = Arc::new(Mutex::new(None));
let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0)); let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0));
let mut debug = Debug::new();
let state = State::new( let state = State::new(
ID.clone(), ID.clone(),
@ -276,7 +286,6 @@ impl<P: Program + Send + 'static> IcedElement<P> {
}, },
IcedSize::new(size.w as f32, size.h as f32), IcedSize::new(size.w as f32, size.h as f32),
&mut renderer, &mut renderer,
&mut debug,
); );
let (executor, scheduler) = calloop::futures::executor().expect("Out of file descriptors"); let (executor, scheduler) = calloop::futures::executor().expect("Out of file descriptors");
@ -299,7 +308,6 @@ impl<P: Program + Send + 'static> IcedElement<P> {
theme, theme,
renderer, renderer,
state, state,
debug,
handle, handle,
scheduler, scheduler,
executor_token, executor_token,
@ -317,14 +325,16 @@ impl<P: Program + Send + 'static> IcedElement<P> {
pub fn minimum_size(&self) -> Size<i32, Logical> { pub fn minimum_size(&self) -> Size<i32, Logical> {
let internal = self.0.lock().unwrap(); let internal = self.0.lock().unwrap();
let element = internal.state.program().program.view(); let mut element = internal.state.program().program.view();
let mut tree = Tree::new(element.as_widget());
let node = element let node = element
.as_widget() .as_widget_mut()
.layout( .layout(
// TODO Avoid creating a new tree here? // TODO Avoid creating a new tree here?
&mut Tree::new(element.as_widget()), &mut tree,
&internal.renderer, &internal.renderer,
&Limits::new(IcedSize::ZERO, IcedSize::INFINITY) &Limits::new(IcedSize::ZERO, IcedSize::INFINITE)
.width(Length::Shrink) .width(Length::Shrink)
.height(Length::Shrink), .height(Length::Shrink),
) )
@ -432,7 +442,6 @@ impl<P: Program + Send + 'static> IcedElementInternal<P> {
text_color: self.theme.cosmic().on_bg_color().into(), text_color: self.theme.cosmic().on_bg_color().into(),
}, },
&mut NullClipboard, &mut NullClipboard,
&mut self.debug,
) )
.1; .1;
@ -922,7 +931,6 @@ where
if size.w > 0 && size.h > 0 { if size.w > 0 && size.h > 0 {
let state_ref = &internal_ref.state; let state_ref = &internal_ref.state;
let mut clip_mask = tiny_skia::Mask::new(size.w as u32, size.h as u32).unwrap(); let mut clip_mask = tiny_skia::Mask::new(size.w as u32, size.h as u32).unwrap();
let overlay = internal_ref.debug.overlay();
let theme = &internal_ref.theme; let theme = &internal_ref.theme;
_ = buffer.render().draw(|buf| { _ = buffer.render().draw(|buf| {
@ -977,7 +985,6 @@ where
&viewport, &viewport,
&damage, &damage,
background_color, background_color,
&overlay,
); );
} }

View file

@ -9,4 +9,5 @@ pub mod prelude;
pub mod quirks; pub mod quirks;
pub mod rlimit; pub mod rlimit;
pub mod screenshot; pub mod screenshot;
pub mod state;
pub mod tween; pub mod tween;

222
src/utils/state.rs Normal file
View file

@ -0,0 +1,222 @@
use super::iced::IcedProgram as Program;
use cosmic::iced::core::event::{self, Event};
use cosmic::iced::core::mouse;
use cosmic::iced::core::renderer;
use cosmic::iced::core::widget::operation::{self, Operation};
use cosmic::iced::core::{Clipboard, Size};
use cosmic::iced_core;
use cosmic::iced_runtime::Task;
use cosmic::iced_runtime::user_interface::{self, UserInterface};
/// The execution state of a [`Program`]. It leverages caching, event
/// processing, and rendering primitive storage.
#[allow(missing_debug_implementations)]
pub struct State<P>
where
P: Program + 'static,
{
program: P,
cache: Option<user_interface::Cache>,
queued_events: Vec<Event>,
queued_messages: Vec<P::Message>,
mouse_interaction: mouse::Interaction,
}
impl<P> State<P>
where
P: Program + 'static,
{
/// Creates a new [`State`] with the provided [`Program`], initializing its
/// primitive with the given logical bounds and renderer.
pub fn new(
id: iced_core::id::Id,
mut program: P,
bounds: Size,
renderer: &mut cosmic::Renderer,
) -> Self {
let user_interface = build_user_interface(
id,
&mut program,
user_interface::Cache::default(),
renderer,
bounds,
);
let cache = Some(user_interface.into_cache());
State {
program,
cache,
queued_events: Vec::new(),
queued_messages: Vec::new(),
mouse_interaction: mouse::Interaction::None,
}
}
/// Returns a reference to the [`Program`] of the [`State`].
pub fn program(&self) -> &P {
&self.program
}
/// Queues an event in the [`State`] for processing during an [`update`].
///
/// [`update`]: Self::update
pub fn queue_event(&mut self, event: Event) {
self.queued_events.push(event);
}
/// Queues a message in the [`State`] for processing during an [`update`].
///
/// [`update`]: Self::update
pub fn queue_message(&mut self, message: P::Message) {
self.queued_messages.push(message);
}
/// Returns whether the event queue of the [`State`] is empty or not.
pub fn is_queue_empty(&self) -> bool {
self.queued_events.is_empty() && self.queued_messages.is_empty()
}
/// Returns the current [`mouse::Interaction`] of the [`State`].
pub fn mouse_interaction(&self) -> mouse::Interaction {
self.mouse_interaction
}
/// Processes all the queued events and messages, rebuilding and redrawing
/// the widgets of the linked [`Program`] if necessary.
///
/// Returns a list containing the instances of [`Event`] that were not
/// captured by any widget, and the [`Task`] obtained from [`Program`]
/// after updating it, only if an update was necessary.
pub fn update(
&mut self,
id: iced_core::id::Id,
bounds: Size,
cursor: mouse::Cursor,
renderer: &mut cosmic::Renderer,
theme: &cosmic::Theme,
style: &renderer::Style,
clipboard: &mut dyn Clipboard,
) -> (Vec<Event>, Option<Task<P::Message>>) {
let mut user_interface = build_user_interface(
id.clone(),
&mut self.program,
self.cache.take().unwrap(),
renderer,
bounds,
);
let mut messages = Vec::new();
let (state, event_statuses) = user_interface.update(
&self.queued_events,
cursor,
renderer,
clipboard,
&mut messages,
);
let uncaptured_events = self
.queued_events
.iter()
.zip(event_statuses)
.filter_map(|(event, status)| matches!(status, event::Status::Ignored).then_some(event))
.cloned()
.collect();
self.queued_events.clear();
messages.append(&mut self.queued_messages);
let task = if messages.is_empty() {
if let cosmic::iced_runtime::user_interface::State::Updated {
mouse_interaction, ..
} = state
{
self.mouse_interaction = mouse_interaction;
}
user_interface.draw(renderer, theme, style, cursor);
self.cache = Some(user_interface.into_cache());
None
} else {
// When there are messages, we are forced to rebuild twice
// for now :^)
let temp_cache = user_interface.into_cache();
let tasks = Task::batch(messages.into_iter().map(|message| {
let task = self.program.update(message);
task
}));
let mut user_interface =
build_user_interface(id, &mut self.program, temp_cache, renderer, bounds);
if let cosmic::iced_runtime::user_interface::State::Updated {
mouse_interaction, ..
} = state
{
self.mouse_interaction = mouse_interaction;
}
user_interface.draw(renderer, theme, style, cursor);
self.cache = Some(user_interface.into_cache());
Some(tasks)
};
(uncaptured_events, task)
}
/// Applies [`Operation`]s to the [`State`]
pub fn operate(
&mut self,
id: iced_core::id::Id,
renderer: &mut cosmic::Renderer,
operations: impl Iterator<Item = Box<dyn Operation>>,
bounds: Size,
) {
let mut user_interface = build_user_interface(
id,
&mut self.program,
self.cache.take().unwrap(),
renderer,
bounds,
);
for operation in operations {
let mut current_operation = Some(operation);
while let Some(mut operation) = current_operation.take() {
user_interface.operate(renderer, operation.as_mut());
match operation.finish() {
operation::Outcome::None => {}
operation::Outcome::Some(()) => {}
operation::Outcome::Chain(next) => {
current_operation = Some(next);
}
};
}
}
self.cache = Some(user_interface.into_cache());
}
}
fn build_user_interface<'a, P: Program>(
_id: iced_core::id::Id,
program: &'a mut P,
cache: user_interface::Cache,
renderer: &mut cosmic::Renderer,
size: Size,
) -> UserInterface<'a, P::Message, cosmic::Theme, cosmic::Renderer> {
let view = program.view();
let user_interface = UserInterface::build(view, size, cache, renderer);
user_interface
}