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 = [
"calloop",
"macro",
] }
], branch = "iced-rebase" }
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", rev = "160b086", default-features = false, features = [
"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 = [
"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 = [
"shape-run-cache",
] }
@ -39,11 +43,11 @@ i18n-embed = { version = "0.16", features = [
"desktop-requester",
] }
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"
keyframe = "1.1.1"
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 }
log-panics = { version = "2", features = ["with-backtrace"] }
ordered-float = "5.1"

View file

@ -4,7 +4,7 @@ version = "1.0.0"
edition = "2024"
[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 }
input = "0.9.1"
libdisplay-info = { version = "0.3.0", optional = true }

View file

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

View file

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

View file

@ -30,19 +30,19 @@ impl From<TabRuleTheme> for theme::Rule {
match theme {
TabRuleTheme::ActiveActivated => Self::custom(|theme| widget::rule::Style {
color: theme.cosmic().accent_color().into(),
width: 4,
snap: true,
radius: 0.0.into(),
fill_mode: FillMode::Full,
}),
TabRuleTheme::ActiveDeactivated => Self::custom(|theme| widget::rule::Style {
color: theme.cosmic().palette.neutral_5.into(),
width: 4,
snap: true,
radius: 0.0.into(),
fill_mode: FillMode::Full,
}),
TabRuleTheme::Default => Self::custom(|theme| widget::rule::Style {
color: theme.cosmic().palette.neutral_5.into(),
width: 4,
snap: true,
radius: 8.0.into(),
fill_mode: FillMode::Padded(4),
}),
@ -62,6 +62,7 @@ impl From<TabBackgroundTheme> for theme::Container<'_> {
match background_theme {
TabBackgroundTheme::ActiveActivated => {
Self::custom(move |theme| widget::container::Style {
snap: true,
icon_color: Some(Color::from(theme.cosmic().accent_text_color())),
text_color: Some(Color::from(theme.cosmic().accent_text_color())),
background: Some(Background::Color(
@ -77,6 +78,7 @@ impl From<TabBackgroundTheme> for theme::Container<'_> {
}
TabBackgroundTheme::ActiveDeactivated => {
Self::custom(move |theme| widget::container::Style {
snap: true,
icon_color: None,
text_color: None,
background: Some(Background::Color(
@ -186,7 +188,7 @@ impl<Message: TabMessage + 'static> Tab<Message> {
}
let items = vec![
widget::vertical_rule(4).class(self.rule_theme).into(),
widget::rule::vertical(4).class(self.rule_theme).into(),
self.app_icon
.clone()
.apply(widget::container)
@ -262,7 +264,7 @@ where
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 {
height: TAB_HEIGHT as f32,
width: if self.active {
@ -295,74 +297,68 @@ where
8.,
cosmic::iced::Alignment::Center,
if size.width >= CLOSE_BREAKPOINT as f32 {
&self.elements
&mut self.elements
} else if size.width >= TEXT_BREAKPOINT as f32 {
&self.elements[0..3]
&mut self.elements[0..3]
} else {
&self.elements[0..2]
&mut self.elements[0..2]
},
&mut tree.children,
)
}
fn operate(
&self,
&mut self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &cosmic::Renderer,
operation: &mut dyn Operation<()>,
) {
operation.container(None, layout.bounds(), &mut |operation| {
operation.container(None, layout.bounds());
operation.traverse(&mut |operation| {
self.elements
.iter()
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.for_each(|((child, state), layout)| {
child
.as_widget()
.as_widget_mut()
.operate(state, layout, renderer, operation);
});
});
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: event::Event,
event: &event::Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &cosmic::Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
) {
let status = self
.elements
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget_mut().on_event(
state,
event.clone(),
layout,
cursor,
renderer,
clipboard,
shell,
viewport,
child.as_widget_mut().update(
state, event, 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!(
event,
event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
) {
if let Some(message) = self.press_message.clone() {
shell.publish(message);
return event::Status::Captured;
shell.capture_event();
return;
}
}
if matches!(
@ -371,7 +367,8 @@ where
) {
if let Some(message) = self.right_click_message.clone() {
shell.publish(message);
return event::Status::Captured;
shell.capture_event();
return;
}
}
if matches!(
@ -379,11 +376,10 @@ where
event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
) {
shell.publish(Message::activate(self.idx));
return event::Status::Captured;
shell.capture_event();
return;
}
}
status
}
fn mouse_interaction(
@ -447,10 +443,18 @@ where
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
layout: Layout<'b>,
renderer: &cosmic::Renderer,
viewport: &Rectangle,
translation: cosmic::iced::Vector,
) -> 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 {
content: &self.text,
size: cosmic::iced_core::Pixels(self.font_size),
bounds: Size::INFINITY,
bounds: Size::INFINITE,
font: self.font,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Center,
align_x: cosmic::iced_core::text::Alignment::Left,
align_y: alignment::Vertical::Center,
shaping: Shaping::Advanced,
line_height: LineHeight::default(),
wrapping: Wrapping::None,
@ -107,7 +107,7 @@ impl<Message> Widget<Message, cosmic::Theme, cosmic::Renderer> for TabText {
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 text_bounds = state.paragraph.min_bounds();
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::Quad {
snap: true,
bounds: Rectangle {
x: (bounds.x + bounds.width - 24.).max(bounds.x),
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 cosmic::{
Apply,
@ -64,12 +66,12 @@ pub struct State {
}
impl Scrollable for State {
fn snap_to(&mut self, offset: RelativeOffset) {
self.offset_x = Offset::Relative(offset.x.clamp(0.0, 1.0));
fn snap_to(&mut self, offset: RelativeOffset<Option<f32>>) {
self.offset_x = Offset::Relative(offset.x.unwrap_or(0.0).clamp(0.0, 1.0));
}
fn scroll_to(&mut self, offset: AbsoluteOffset) {
let new_offset = Offset::Absolute(offset.x.max(0.0));
fn scroll_to(&mut self, offset: AbsoluteOffset<Option<f32>>) {
let new_offset = Offset::Absolute(offset.x.unwrap_or(0.0).max(0.0));
self.scroll_animation = Some(ScrollAnimationState {
start_time: Instant::now(),
start: self.offset_x,
@ -159,7 +161,7 @@ where
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 {
TabRuleTheme::ActiveActivated
} else {
@ -193,12 +195,12 @@ where
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.extend(tabs);
elements.push(tabs_rule.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 {
elements,
@ -340,9 +342,9 @@ where
}
#[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 element_count = self.elements.len();
// calculate the smallest possible size
let child_limits = Limits::new(
Size::new(0.0, limits.min().height),
@ -351,10 +353,13 @@ where
.width(Length::Shrink)
.height(Length::Shrink);
let mut nodes = self.elements[2..self.elements.len() - 2]
.iter()
let mut nodes = self.elements[2..element_count - 2]
.iter_mut()
.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<_>>();
// sum up
@ -370,8 +375,9 @@ where
if min_size.width <= size.width {
// 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?
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
{
// just use a flex layout
@ -384,20 +390,20 @@ where
0.into(),
0.,
cosmic::iced::Alignment::Center,
&self.elements[2..self.elements.len() - 2],
&mut tree.children[2..self.elements.len() - 2],
&mut self.elements[2..element_count - 2],
&mut tree.children[2..element_count - 2],
)
.children()
.to_vec()
} else {
// otherwise we need a more manual approach
let min_width = (size.width - MIN_ACTIVE_TAB_WIDTH as f32 - 4.)
/ (self.elements.len() as f32 - 6.);
let min_width =
(size.width - MIN_ACTIVE_TAB_WIDTH as f32 - 4.) / (element_count as f32 - 6.);
let mut offset = 0.;
let mut nodes = self.elements[2..self.elements.len() - 3]
.iter()
.zip(tree.children[2..].iter_mut())
let mut nodes = self.elements[2..element_count - 3]
.iter_mut()
.zip(tree.children[2..element_count - 3].iter_mut())
.map(|(tab, tab_tree)| {
let child_limits = Limits::new(
Size::new(min_width, limits.min().height),
@ -406,7 +412,9 @@ where
.width(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.));
offset += node.bounds().width;
node
@ -508,6 +516,7 @@ where
let background_style = Catalog::style(
theme,
&theme::Container::custom(|theme| widget::container::Style {
snap: true,
icon_color: None,
text_color: None,
background: Some(Background::Color(
@ -605,6 +614,9 @@ where
let cursor = match cursor {
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
mouse::Cursor::Unavailable => mouse::Cursor::Unavailable,
mouse::Cursor::Levitating(point) => {
mouse::Cursor::Levitating(point + offset)
}
};
renderer.with_layer(bounds, |renderer| {
@ -677,7 +689,7 @@ where
}
fn operate(
&self,
&mut self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &cosmic::Renderer,
@ -691,38 +703,38 @@ where
state.cleanup_old_animations();
operation.scrollable(
state,
self.id.as_ref(),
bounds,
content_bounds,
Vector { x: 0.0, y: 0.0 }, /* seemingly unused */
Vector { x: 0.0, y: 0.0 },
state,
);
operation.container(self.id.as_ref(), bounds, &mut |operation| {
self.elements[2..self.elements.len() - 3]
.iter()
let element_count = self.elements.len();
operation.traverse(&mut |operation| {
self.elements[2..element_count - 3]
.iter_mut()
.zip(tree.children.iter_mut().skip(2))
.zip(layout.children().skip(2))
.for_each(|((child, state), layout)| {
child
.as_widget()
.as_widget_mut()
.operate(state, layout, renderer, operation);
});
});
}
#[allow(clippy::too_many_lines)]
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: event::Event,
event: &event::Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &cosmic::Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
) {
let state = tree.state.downcast_mut::<State>();
state.cleanup_old_animations();
@ -849,15 +861,15 @@ where
let mut internal_shell = Shell::new(&mut messages);
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]
.iter_mut()
.zip(&mut tree.children)
.zip(layout.children())
.map(|((child, state), layout)| {
child.as_widget_mut().on_event(
child.as_widget_mut().update(
state,
event.clone(),
event,
layout,
cursor,
renderer,
@ -865,8 +877,7 @@ where
&mut internal_shell,
viewport,
)
})
.fold(event::Status::Ignored, event::Status::merge)
});
} else if scrolling
&& cursor
.position()
@ -877,9 +888,9 @@ where
.zip(tree.children.iter_mut().skip(len - 3))
.zip(layout.children().skip(len - 3))
.map(|((child, state), layout)| {
child.as_widget_mut().on_event(
child.as_widget_mut().update(
state,
event.clone(),
event,
layout,
cursor,
renderer,
@ -887,8 +898,7 @@ where
&mut internal_shell,
viewport,
)
})
.fold(event::Status::Ignored, event::Status::merge)
});
} else {
self.elements[2..len - 3]
.iter_mut()
@ -898,11 +908,14 @@ where
let cursor = match cursor {
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
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,
event.clone(),
event,
layout,
cursor,
renderer,
@ -910,8 +923,7 @@ where
&mut internal_shell,
viewport,
)
})
.fold(event::Status::Ignored, event::Status::merge)
});
};
for mut message in messages {
@ -919,14 +931,12 @@ where
x: state.offset_x.absolute(bounds.width, content_bounds.width),
y: 0.,
}) {
state.scroll_to(offset);
state.scroll_to(offset.into());
continue;
}
shell.publish(message);
}
result
}
fn mouse_interaction(
@ -992,6 +1002,9 @@ where
let cursor = match cursor {
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
mouse::Cursor::Unavailable => mouse::Cursor::Unavailable,
mouse::Cursor::Levitating(point) => {
mouse::Cursor::Levitating(point + offset)
}
};
child.as_widget().mouse_interaction(
@ -1010,10 +1023,18 @@ where
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
layout: Layout<'b>,
renderer: &cosmic::Renderer,
viewport: &Rectangle,
translation: cosmic::iced::Vector,
) -> 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},
theme,
widget::{horizontal_space, icon::from_name, text},
widget::{icon::from_name, space, text},
};
use smithay::utils::{Logical, Size};
@ -38,7 +38,7 @@ impl Program for StackHoverInternal {
.prefer_svg(true)
.icon()
.into(),
horizontal_space().width(16).into(),
space::horizontal().width(16).into(),
text::title3(fl!("stack-windows")).into(),
])
.align_y(Alignment::Center)
@ -48,6 +48,7 @@ impl Program for StackHoverInternal {
.padding(16)
.apply(container)
.class(theme::Container::custom(|theme| container::Style {
snap: true,
icon_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())),

View file

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

View file

@ -52,13 +52,13 @@ where
}
fn layout(
&self,
&mut self,
state: &mut Tree,
renderer: &cosmic::Renderer,
limits: &layout::Limits,
) -> layout::Node {
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])
}
@ -81,6 +81,7 @@ where
renderer.fill_quad(
Quad {
snap: true,
bounds: layout.bounds(),
border: Border {
radius: styling.border_radius,
@ -128,7 +129,7 @@ where
}
fn operate(
&self,
&mut self,
state: &mut Tree,
layout: Layout<'_>,
renderer: &cosmic::Renderer,
@ -137,21 +138,21 @@ where
let state = &mut state.children[0];
let layout = layout.children().next().unwrap();
self.elem
.as_widget()
.as_widget_mut()
.operate(state, layout, renderer, operation)
}
fn on_event(
fn update(
&mut self,
state: &mut Tree,
event: Event,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &cosmic::Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
) {
let mut bounds = layout.bounds();
// fix padding 1 and event... don't ask.
@ -180,9 +181,9 @@ where
let state = &mut state.children[0];
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,
)
);
}
fn mouse_interaction(
@ -203,15 +204,16 @@ where
fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
layout: Layout<'b>,
renderer: &cosmic::Renderer,
viewport: &Rectangle,
translation: cosmic::iced::Vector,
) -> Option<overlay::Element<'b, Message, cosmic::Theme, cosmic::Renderer>> {
let state = &mut state.children[0];
let layout = layout.children().next().unwrap();
self.elem
.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_widget::{self, Column, Row, text::Style as TextStyle},
theme,
widget::{button, divider, horizontal_space, icon::from_name, text},
widget::{button, divider, icon::from_name, space, text},
};
use smithay::{
backend::{
@ -392,7 +392,7 @@ impl Program for ContextMenu {
match item {
Item::Separator => divider::horizontal::light().into(),
Item::Submenu { title, .. } => Row::with_children(vec![
horizontal_space().width(16).into(),
space::horizontal().width(16).into(),
text::body(title).width(mode).into(),
from_name("go-next-symbolic")
.size(16)
@ -425,7 +425,7 @@ impl Program for ContextMenu {
}))
.into()
} else {
horizontal_space().width(16).into()
space::horizontal().width(16).into()
},
text::body(title)
.width(mode)
@ -441,7 +441,7 @@ impl Program for ContextMenu {
theme::Text::Default
})
.into(),
horizontal_space().width(16).into(),
space::horizontal().width(16).into(),
];
if let Some(shortcut) = shortcut.as_ref() {
components.push(
@ -479,6 +479,7 @@ impl Program for ContextMenu {
let cosmic = theme.cosmic();
let component = &cosmic.background.component;
iced_widget::container::Style {
snap: true,
icon_color: Some(cosmic.accent.base.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),

View file

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

View file

@ -18,11 +18,7 @@ use cosmic::{
window::Event as WindowEvent,
},
iced_core::{Color, Length, Pixels, clipboard::Null as NullClipboard, id::Id, renderer::Style},
iced_runtime::{
Action, Debug,
program::{Program as IcedProgram, State},
task::into_stream,
},
iced_runtime::{Action, task::into_stream},
};
use iced_tiny_skia::{
Layer,
@ -66,6 +62,8 @@ use smithay::{
},
};
use crate::utils::state::State;
static ID: LazyLock<Id> = LazyLock::new(|| Id::new("Program"));
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 {
type Message: std::fmt::Debug + Send;
fn update(
@ -135,8 +153,6 @@ struct ProgramWrapper<P: Program> {
impl<P: Program> IcedProgram for ProgramWrapper<P> {
type Message = <P as Program>::Message;
type Renderer = cosmic::Renderer;
type Theme = cosmic::Theme;
fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
let last_seat = self.last_seat.lock().unwrap();
@ -165,7 +181,6 @@ pub(crate) struct IcedElementInternal<P: Program + Send + 'static> {
theme: Theme,
renderer: cosmic::Renderer,
state: State<ProgramWrapper<P>>,
debug: Debug,
// futures
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");
}
let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0));
let mut debug = Debug::new();
let state = State::new(
ID.clone(),
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),
&mut renderer,
&mut debug,
);
IcedElementInternal {
@ -214,7 +227,6 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
theme: self.theme.clone(),
renderer,
state,
debug,
handle,
scheduler,
executor_token,
@ -240,7 +252,6 @@ impl<P: Program + Send + 'static> fmt::Debug for IcedElementInternal<P> {
.field("theme", &"...")
.field("renderer", &"...")
.field("state", &"...")
.field("debug", &self.debug)
.field("handle", &self.handle)
.field("scheduler", &self.scheduler)
.field("executor_token", &self.executor_token)
@ -265,7 +276,6 @@ impl<P: Program + Send + 'static> IcedElement<P> {
let size = size.into();
let last_seat = Arc::new(Mutex::new(None));
let mut renderer = cosmic::Renderer::new(cosmic::font::default(), Pixels(16.0));
let mut debug = Debug::new();
let state = State::new(
ID.clone(),
@ -276,7 +286,6 @@ impl<P: Program + Send + 'static> IcedElement<P> {
},
IcedSize::new(size.w as f32, size.h as f32),
&mut renderer,
&mut debug,
);
let (executor, scheduler) = calloop::futures::executor().expect("Out of file descriptors");
@ -299,7 +308,6 @@ impl<P: Program + Send + 'static> IcedElement<P> {
theme,
renderer,
state,
debug,
handle,
scheduler,
executor_token,
@ -317,14 +325,16 @@ impl<P: Program + Send + 'static> IcedElement<P> {
pub fn minimum_size(&self) -> Size<i32, Logical> {
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
.as_widget()
.as_widget_mut()
.layout(
// TODO Avoid creating a new tree here?
&mut Tree::new(element.as_widget()),
&mut tree,
&internal.renderer,
&Limits::new(IcedSize::ZERO, IcedSize::INFINITY)
&Limits::new(IcedSize::ZERO, IcedSize::INFINITE)
.width(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(),
},
&mut NullClipboard,
&mut self.debug,
)
.1;
@ -922,7 +931,6 @@ where
if size.w > 0 && size.h > 0 {
let state_ref = &internal_ref.state;
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;
_ = buffer.render().draw(|buf| {
@ -977,7 +985,6 @@ where
&viewport,
&damage,
background_color,
&overlay,
);
}

View file

@ -9,4 +9,5 @@ pub mod prelude;
pub mod quirks;
pub mod rlimit;
pub mod screenshot;
pub mod state;
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
}