Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2ae0c93
Remove self-references from `component` implementation
hecrj Oct 22, 2025
c6fdaaa
Move `component` out of the `lazy` feature gate
hecrj Oct 22, 2025
fcc87f4
Disable `lazy` feature in `component` example
hecrj Oct 22, 2025
b72abd6
Request a redraw when `Component::view` is called
hecrj Oct 23, 2025
c0282ed
Add `diff` method to `Component` trait
hecrj Oct 30, 2025
e51e0c7
Add `listen` method to `Component`
hecrj Oct 30, 2025
8438c4e
Provide internal `State` to `Component::listen`
hecrj Oct 30, 2025
22982c8
Add `mouse_interaction` method to `Component`
hecrj Oct 30, 2025
64e65a8
Call `Component::operate` in `Widget` implementation
hecrj Nov 1, 2025
9c71f8a
Invalidate layout when `Component` resizes and overlay changes
hecrj Nov 1, 2025
ea38f00
Explain layout invalidation in `Component`
hecrj Nov 1, 2025
de22d44
Provide `bounds` and `cursor` to `Component::listen`
hecrj Nov 2, 2025
59461cf
Provide `Renderer` to `Component::update`
hecrj Nov 2, 2025
f8b662c
Make `allocate_image` immutable and add `Action::and_request_redraw_at`
hecrj Nov 2, 2025
dca3339
Avoid redundant redraw in `Component::update`
hecrj Nov 3, 2025
f5ab610
Feed `RedrawRequested` after `view` in `component`
hecrj Nov 3, 2025
84dfe7a
Process extra `Component` redraw events in different frames
hecrj Nov 4, 2025
b92e3bc
Merge branch 'master' into bless-component
hecrj Dec 26, 2025
83affa4
Merge branch 'master' into bless-component
hecrj Feb 6, 2026
32edbd7
Merge branch 'master' into bless-component
hecrj Feb 16, 2026
102081d
Merge branch 'master' into bless-component
hecrj Aug 16, 2026
8ab41e0
Remove `size_hint` method from `Component` trait
hecrj Aug 16, 2026
0b429d8
Forward `Component` messages for proper `Tracking`
hecrj Aug 16, 2026
8d01a2a
Merge branch 'master' into bless-component
hecrj Aug 16, 2026
00d7a81
Fix code snippets in `UserInterface` documentation
hecrj Aug 16, 2026
4fa8144
Fix missing export of `Lazy` widget
hecrj Aug 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub trait Renderer {

/// Creates an [`image::Allocation`] for the given [`image::Handle`] and calls the given callback with it.
fn allocate_image(
&mut self,
&self,
handle: &image::Handle,
callback: impl FnOnce(Result<image::Allocation, image::Error>) + Send + 'static,
);
Expand Down
2 changes: 1 addition & 1 deletion core/src/renderer/null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl Renderer for () {
fn fill_quad(&mut self, _quad: renderer::Quad, _background: impl Into<Background>) {}

fn allocate_image(
&mut self,
&self,
handle: &image::Handle,
callback: impl FnOnce(Result<image::Allocation, image::Error>) + Send + 'static,
) {
Expand Down
37 changes: 31 additions & 6 deletions core/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ impl<'a, Message> Shell<'a, Message> {
self.bus.push(message)
}

/// Forwards the given `Message` and fulfills the given [`Receipt`]
/// once processed.
pub fn forward(&mut self, message: Message, receipt: Receipt) {
self.bus.forward(message, receipt);
}

/// Marks the current event as captured. Prevents "event bubbling".
///
/// A widget should capture an event when no ancestor should
Expand Down Expand Up @@ -280,7 +286,7 @@ pub enum Diff {
/// A channel of messages published by a [`Shell`].
#[derive(Debug)]
pub struct Bus<T> {
messages: Vec<(T, Rc<()>)>,
messages: Vec<(T, Receipt)>,
}

impl<T> Bus<T> {
Expand All @@ -306,17 +312,22 @@ impl<T> Bus<T> {
/// The returned [`Tracking`] can be used to determine if the message
/// was processed.
pub fn push(&mut self, message: T) -> Tracking {
let receipt = Rc::new(());
let tracking = Tracking(Rc::downgrade(&receipt));
let receipt = Receipt::new();
let tracking = receipt.tracking();

self.messages.push((message, receipt));

tracking
}

/// Forward a new message to the [`Bus`] with the given [`Receipt`].
pub fn forward(&mut self, message: T, receipt: Receipt) {
self.messages.push((message, receipt));
}

/// Drains the [`Bus`].
pub fn drain(&mut self) -> impl Iterator<Item = T> {
self.messages.drain(..).map(|(message, _receipt)| message)
pub fn drain(&mut self) -> impl Iterator<Item = (T, Receipt)> {
self.messages.drain(..)
}
}

Expand All @@ -339,7 +350,7 @@ impl<T> IntoIterator for Bus<T> {

/// An iterator returned by the implementation of [`IntoIterator`] for [`Bus`].
pub struct IntoIter<T> {
iter: vec::IntoIter<(T, Rc<()>)>,
iter: vec::IntoIter<(T, Receipt)>,
}

impl<T> Iterator for IntoIter<T> {
Expand All @@ -350,6 +361,20 @@ impl<T> Iterator for IntoIter<T> {
}
}

/// Proof that a message has been received.
#[derive(Debug)]
pub struct Receipt(Rc<()>);

impl Receipt {
fn new() -> Self {
Self(Rc::new(()))
}

fn tracking(&self) -> Tracking {
Tracking(Rc::downgrade(&self.0))
}
}

/// A message tracking returned by [`Shell::publish`].
#[derive(Debug, Clone)]
pub struct Tracking(rc::Weak<()>);
Expand Down
10 changes: 10 additions & 0 deletions examples/component/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "component"
version = "0.1.0"
authors = ["Héctor Ramón Jiménez <hector@hecrj.dev>"]
edition = "2024"
publish = false

[dependencies]
iced.workspace = true
iced.features = ["debug"]
130 changes: 130 additions & 0 deletions examples/component/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use iced::Element;
use iced::widget::center;

use numeric_input::numeric_input;

pub fn main() -> iced::Result {
iced::run(Example::update, Example::view)
}

#[derive(Default)]
struct Example {
value: Option<i32>,
}

#[derive(Debug, Clone, Copy)]
enum Message {
NumericInputChanged(Option<i32>),
}

impl Example {
fn update(&mut self, message: Message) {
match message {
Message::NumericInputChanged(value) => {
self.value = value;
}
}
}

fn view(&self) -> Element<'_, Message> {
center(numeric_input(self.value, Message::NumericInputChanged))
.padding(20)
.into()
}
}

mod numeric_input {
use iced::widget::{Component, button, component, row, text, text_input};
use iced::{Center, Element, Fill, Renderer};

pub struct NumericInput<Message> {
value: Option<i32>,
on_change: Box<dyn Fn(Option<i32>) -> Message>,
}

pub fn numeric_input<Message>(
value: Option<i32>,
on_change: impl Fn(Option<i32>) -> Message + 'static,
) -> NumericInput<Message> {
NumericInput::new(value, on_change)
}

#[derive(Debug, Clone)]
pub enum Event {
InputChanged(String),
IncrementPressed,
DecrementPressed,
}

impl<Message> NumericInput<Message> {
pub fn new(
value: Option<i32>,
on_change: impl Fn(Option<i32>) -> Message + 'static,
) -> Self {
Self {
value,
on_change: Box::new(on_change),
}
}
}

impl<'a, Message> Component<'a, Message> for NumericInput<Message> {
type State = ();
type Event = Event;

fn update(
&mut self,
_state: &mut Self::State,
event: Event,
_renderer: &Renderer,
) -> Option<Message> {
match event {
Event::IncrementPressed => Some((self.on_change)(Some(
self.value.unwrap_or_default().saturating_add(1),
))),
Event::DecrementPressed => Some((self.on_change)(Some(
self.value.unwrap_or_default().saturating_sub(1),
))),
Event::InputChanged(value) => {
if value.is_empty() {
Some((self.on_change)(None))
} else {
value.parse().ok().map(Some).map(self.on_change.as_ref())
}
}
}
}

fn view(&self, _state: &Self::State) -> Element<'a, Event> {
let button = |label, on_press| {
button(text(label).width(Fill).height(Fill).center())
.width(40)
.height(40)
.on_press(on_press)
};

row![
button("-", Event::DecrementPressed),
text_input(
"Type a number",
self.value.as_ref().map(i32::to_string).unwrap_or_default(),
)
.on_input(Event::InputChanged)
.padding(10),
button("+", Event::IncrementPressed),
]
.align_y(Center)
.spacing(10)
.into()
}
}

impl<'a, Message> From<NumericInput<Message>> for Element<'a, Message>
where
Message: 'a,
{
fn from(numeric_input: NumericInput<Message>) -> Self {
component(numeric_input)
}
}
}
2 changes: 1 addition & 1 deletion renderer/src/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ where
}

fn allocate_image(
&mut self,
&self,
handle: &image::Handle,
callback: impl FnOnce(Result<image::Allocation, image::Error>) + Send + 'static,
) {
Expand Down
4 changes: 2 additions & 2 deletions runtime/src/user_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ where
/// cache = user_interface.into_cache();
///
/// // Process the produced messages
/// for message in messages.drain() {
/// for (message, _receipt) in messages.drain() {
/// counter.update(message);
/// }
/// }
Expand Down Expand Up @@ -487,7 +487,7 @@ where
///
/// cache = user_interface.into_cache();
///
/// for message in messages.drain() {
/// for (message, _receipt) in messages.drain() {
/// counter.update(message);
/// }
///
Expand Down
2 changes: 1 addition & 1 deletion tiny_skia/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ impl core::Renderer for Renderer {
}

fn allocate_image(
&mut self,
&self,
_handle: &core::image::Handle,
callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>) + Send + 'static,
) {
Expand Down
4 changes: 2 additions & 2 deletions wgpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,13 +685,13 @@ impl core::Renderer for Renderer {
}

fn allocate_image(
&mut self,
&self,
_handle: &core::image::Handle,
_callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>) + Send + 'static,
) {
#[cfg(feature = "image")]
self.image_cache
.get_mut()
.borrow_mut()
.allocate_image(_handle, _callback);
}

Expand Down
17 changes: 12 additions & 5 deletions widget/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ pub struct Action<Message> {
}

impl<Message> Action<Message> {
fn new() -> Self {
/// Creates an [`Action`] that does nothing.
pub fn none() -> Self {
Self {
message_to_publish: None,
redraw_request: window::RedrawRequest::Wait,
Expand All @@ -27,7 +28,7 @@ impl<Message> Action<Message> {
pub fn capture() -> Self {
Self {
event_status: event::Status::Captured,
..Self::new()
..Self::none()
}
}

Expand All @@ -38,7 +39,7 @@ impl<Message> Action<Message> {
pub fn publish(message: Message) -> Self {
Self {
message_to_publish: Some(message),
..Self::new()
..Self::none()
}
}

Expand All @@ -47,7 +48,7 @@ impl<Message> Action<Message> {
pub fn request_redraw() -> Self {
Self {
redraw_request: window::RedrawRequest::NextFrame,
..Self::new()
..Self::none()
}
}

Expand All @@ -59,7 +60,7 @@ impl<Message> Action<Message> {
pub fn request_redraw_at(at: Instant) -> Self {
Self {
redraw_request: window::RedrawRequest::At(at),
..Self::new()
..Self::none()
}
}

Expand All @@ -69,6 +70,12 @@ impl<Message> Action<Message> {
self
}

/// Requests a redraw at the given [`Instant`]. See [`Self::request_redraw_at`].
pub fn and_request_redraw_at(mut self, at: Instant) -> Self {
self.redraw_request = window::RedrawRequest::At(at);
self
}

/// Converts the [`Action`] into its internal parts.
///
/// This method is meant to be used by runtimes, libraries, or internal
Expand Down
Loading
Loading