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

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
}