Skip to content

Add Canvas widget - take 3#1506

Closed
PoignardAzur wants to merge 47 commits intolinebender:mainfrom
PoignardAzur:canvas_widget_3
Closed

Add Canvas widget - take 3#1506
PoignardAzur wants to merge 47 commits intolinebender:mainfrom
PoignardAzur:canvas_widget_3

Conversation

@PoignardAzur
Copy link
Copy Markdown
Contributor

Based on @richard-uk1's #875 and @philocalyst's #1445.

Add Canvas widget to Masonry.
Add canvas element to Xilem.

richard-uk1 and others added 30 commits November 30, 2025 12:37
Co-authored-by: Olivier FAURE <couteaubleu@gmail.com>
Returning childids now
Made no op as it previosly did not exist
Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@RagibHasin RagibHasin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Sorry for the late review.

WidgetMut,
};

// TODO - Add background color?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is better done in the draw function?

Copy link
Copy Markdown
Member

@Philipp-M Philipp-M Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I would just remove TODO, I think it's a good default to draw nothing when nothing is specified in draw.

Comment on lines +101 to +107
fn on_pointer_event(
&mut self,
_ctx: &mut EventCtx<'_>,
_props: &mut PropertiesMut<'_>,
_event: &PointerEvent,
) {
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't methods with default impls be omitted?

Copy link
Copy Markdown
Member

@Philipp-M Philipp-M left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an issue in the View::rebuild implementation, otherwise looks good.

WidgetMut,
};

// TODO - Add background color?
Copy link
Copy Markdown
Member

@Philipp-M Philipp-M Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I would just remove TODO, I think it's a good default to draw nothing when nothing is specified in draw.

/// That Scene is then used as the canvas' contents.
pub struct Canvas {
draw: Arc<dyn Fn(&mut Scene, Size) + Send + Sync + 'static>,
alt_text: Option<String>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't hurt to have an Option here, but it's not necessary, we can just use the property is_empty for that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is, "alt text is unset" and "alt text is set to empty string" have different semantic meanings, though I'm not sure whether screen readers really interpret them that differently in practice.

Comment on lines +76 to +77
this.widget.draw = draw;
this.ctx.request_render();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something I'm wondering for a longer time already, is whether we should check within these setters whether really something changed (i.e. in this case if the arc pointer has changed), and only then request a render.

As we often (always?) do this in Xilem it's not necessary here for Xilem at least, but I think it might be nice to have this built into masonry?

See also https://github.com/ricvelozo/xilem/pull/1/files#diff-7d717e7fd640e50cef624a6fa2afb9c20968470e5849c8b3b0abe9782499814eR67-R72
where I did this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. In general, that kind of reasoning is how you end up with multiple layers of caching in your app that all check whether some parameter changed.

In practice, repainting is usually cheap and having Masonry always repaint something when a parameter is set is more predictable, so I'd lean towards the current design.

Comment on lines +80 to +94
/// Set the text that will describe the canvas to screen readers.
///
/// See [`Canvas::with_alt_text`] for details.
pub fn set_alt_text(mut this: WidgetMut<'_, Self>, alt_text: String) {
this.widget.alt_text = Some(alt_text);
this.ctx.request_accessibility_update();
}

/// Remove the canvas' alt text.
///
/// See [`Canvas::with_alt_text`] for details.
pub fn remove_alt_text(mut this: WidgetMut<'_, Self>) {
this.widget.alt_text = None;
this.ctx.request_accessibility_update();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment below, I think we can just sum this up in set_alt_text with alt_text: String (or ArcStr)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might just want to pass an Option<String> and merge them.

Comment thread xilem/src/view/canvas.rs
Comment on lines +90 to +94
if self.alt_text != prev.alt_text
&& let Some(alt_text) = &self.alt_text
{
widgets::Canvas::set_alt_text(element, alt_text.clone());
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is safe?

When alt_test was Some and changed to None it won't be updated in masonry AFAICS.
So in the current state of this PR remove_alt_text needs to be called (but as described above, I think we can simplify this and the masonry code by not using an Option at all)

Comment thread xilem/src/view/canvas.rs
/// # my_canvas
/// # }
/// ```
pub fn canvas(draw: Arc<dyn Fn(&mut Scene, Size) + Send + Sync + 'static>) -> Canvas {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw. I think we should probably specify in the doc-comment, that this Arc<dyn Fn> should be ideally saved within the app state, otherwise it always results in a rerender. Ideally show how this is done better in the doc-comment example

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can also use a function pointer instead, to avoid allocs and (mostly) unnecessary generics?

@Philipp-M Philipp-M mentioned this pull request Dec 7, 2025
github-merge-queue bot pushed a commit that referenced this pull request Dec 12, 2025
Alternative to #1506 (actually it builds on top of it again and applies
the review comments/suggestions, quite a group effort :))

I was bugged by the `Arc<dyn Fn>` and the way it was handled.

IMO it's a little bit better layered this way and more predictable when
the canvas draw function is actually changed, and allows the app state
in the canvas draw function by using `Fn(Arg<'_, State>, &mut Scene,
Size)` instead of just `Arc<dyn Fn(&mut Scene, Size)>` which would
require capturing in any realistic use-case (accessing the user state)
and thus the `Arc` would be somewhat useless, as always a new one needs
to be created.

Additionally I've added an example (as I think this is good practice
when adding new widgets).
(also to check whether this results in latency, i.e. when pressing the
button, that the scene is updated immediately instead of a frame-delay
(fortunately this doesn't result in frame-delay))

---------

Co-authored-by: Richard Dodd <richard.o.dodd@gmail.com>
Co-authored-by: Olivier FAURE <couteaubleu@gmail.com>
Co-authored-by: Myles Wirth <regime_skylark.0y@icloud.com>
Co-authored-by: Miles Wirht <114884788+philocalyst@users.noreply.github.com>
Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
Co-authored-by: Muhammad Ragib Hasin <ragib.hasin@gmail.com>
@RagibHasin
Copy link
Copy Markdown
Contributor

Completed in #1519

@RagibHasin RagibHasin closed this Dec 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants