-
Notifications
You must be signed in to change notification settings - Fork 189
Add Canvas widget - cont. #1445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 30 commits
3967c6e
e30fcfc
b4dfea6
ec5c653
033e5bd
b63a2c8
39e178b
81bd724
0c20108
80f5026
e01277d
e33f0d2
0141e2f
085b383
68fc4a8
787b131
a2d1136
bf710e5
1af0887
32bc30b
964355d
05e8eef
54fef9d
a3e9942
6b06356
7485faa
a3e4b29
3013312
4d6c88e
2c13fab
0020cee
38b11e7
98c43dc
07cd12d
ab4e1f7
5c40096
d52a528
989d2cb
0199af7
26310d8
cc85d51
f2cc252
8b33331
c0dc096
95fdb3d
4419765
86a78a3
2966586
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,217 @@ | ||||||||||||
| // Copyright 2025 the Xilem Authors and the Druid Authors | ||||||||||||
| // SPDX-License-Identifier: Apache-2.0 | ||||||||||||
|
|
||||||||||||
| //! A canvas widget. | ||||||||||||
|
|
||||||||||||
| use std::sync::Arc; | ||||||||||||
|
|
||||||||||||
| use accesskit::{Node, Role}; | ||||||||||||
| use masonry_core::core::{ChildrenIds, NoAction}; | ||||||||||||
| use tracing::{Span, trace_span}; | ||||||||||||
| use vello::Scene; | ||||||||||||
| use vello::kurbo::Size; | ||||||||||||
|
|
||||||||||||
| use crate::core::{ | ||||||||||||
| AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, PaintCtx, PointerEvent, | ||||||||||||
| PropertiesMut, PropertiesRef, RegisterCtx, TextEvent, Update, UpdateCtx, Widget, WidgetId, | ||||||||||||
| WidgetMut, | ||||||||||||
| }; | ||||||||||||
|
|
||||||||||||
| /// A widget allowing custom drawing. | ||||||||||||
| pub struct Canvas { | ||||||||||||
| draw: Arc<dyn Fn(&mut Scene, Size) + Send + Sync + 'static>, | ||||||||||||
| alt_text: Option<String>, | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // --- MARK: BUILDERS --- | ||||||||||||
| impl Canvas { | ||||||||||||
| /// Create a new canvas with the given draw function. | ||||||||||||
| pub fn new(draw: impl Fn(&mut Scene, Size) + Send + Sync + 'static) -> Self { | ||||||||||||
| Self::from_arc(Arc::new(draw)) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// Create a new canvas from a function already contained in an [`Arc`]. | ||||||||||||
| pub fn from_arc(draw: Arc<dyn Fn(&mut Scene, Size) + Send + Sync + 'static>) -> Self { | ||||||||||||
| Self { | ||||||||||||
| draw, | ||||||||||||
| alt_text: None, | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// Set the text that will be used to communicate the meaning of the canvas to | ||||||||||||
| /// those using screen readers. | ||||||||||||
| /// | ||||||||||||
| /// Users are encouraged to set alt text for the canvas. | ||||||||||||
| /// If possible, the alt-text should succinctly describe what the canvas represents. | ||||||||||||
| /// | ||||||||||||
| /// If the canvas is decorative or too hard to describe through text, users should set alt text to `""`. | ||||||||||||
| pub fn with_alt_text(mut self, alt_text: impl Into<String>) -> Self { | ||||||||||||
| self.alt_text = Some(alt_text.into()); | ||||||||||||
| self | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // --- MARK: WIDGETMUT --- | ||||||||||||
| impl Canvas { | ||||||||||||
| /// Update the draw function | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
And the same for the other items. We want all doc comments to end with a full stop, for consistency. |
||||||||||||
| pub fn set_painter( | ||||||||||||
| this: WidgetMut<'_, Self>, | ||||||||||||
| draw: impl Fn(&mut Scene, Size) + Send + Sync + 'static, | ||||||||||||
| ) { | ||||||||||||
| Self::set_painter_arc(this, Arc::new(draw)); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// Update the draw function | ||||||||||||
| pub fn set_painter_arc( | ||||||||||||
| mut this: WidgetMut<'_, Self>, | ||||||||||||
| draw: Arc<dyn Fn(&mut Scene, Size) + Send + Sync + 'static>, | ||||||||||||
| ) { | ||||||||||||
| this.widget.draw = draw; | ||||||||||||
| this.ctx.request_render(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /// Set the alternative text for this widget | ||||||||||||
| 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 existing alternative text on this widget (If there is any) | ||||||||||||
philocalyst marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||
| pub fn remove_alt_text(mut this: WidgetMut<'_, Self>) { | ||||||||||||
| this.widget.alt_text = None; | ||||||||||||
| this.ctx.request_accessibility_update(); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // --- MARK: IMPL WIDGET --- | ||||||||||||
| impl Widget for Canvas { | ||||||||||||
| fn on_pointer_event( | ||||||||||||
| &mut self, | ||||||||||||
| _ctx: &mut EventCtx<'_>, | ||||||||||||
| _props: &mut PropertiesMut<'_>, | ||||||||||||
| _event: &PointerEvent, | ||||||||||||
| ) { | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn accepts_pointer_interaction(&self) -> bool { | ||||||||||||
| true | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previous discussion at #875 (comment). The only case this really impacts is using a canvas within a button; see for example #1429. I think that in most cases, we would want this to be That is concretely, please leave this as-is! |
||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn on_text_event( | ||||||||||||
| &mut self, | ||||||||||||
| _ctx: &mut EventCtx<'_>, | ||||||||||||
| _props: &mut PropertiesMut<'_>, | ||||||||||||
| _event: &TextEvent, | ||||||||||||
| ) { | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn on_access_event( | ||||||||||||
| &mut self, | ||||||||||||
| _ctx: &mut EventCtx<'_>, | ||||||||||||
| _props: &mut PropertiesMut<'_>, | ||||||||||||
| _event: &AccessEvent, | ||||||||||||
| ) { | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn register_children(&mut self, _ctx: &mut RegisterCtx<'_>) {} | ||||||||||||
|
|
||||||||||||
| fn update( | ||||||||||||
| &mut self, | ||||||||||||
| _ctx: &mut UpdateCtx<'_>, | ||||||||||||
| _props: &mut PropertiesMut<'_>, | ||||||||||||
| _event: &Update, | ||||||||||||
| ) { | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn layout( | ||||||||||||
| &mut self, | ||||||||||||
| _ctx: &mut LayoutCtx<'_>, | ||||||||||||
| _props: &mut PropertiesMut<'_>, | ||||||||||||
| bc: &BoxConstraints, | ||||||||||||
| ) -> Size { | ||||||||||||
| // use as much space as possible - caller can size it as necessary | ||||||||||||
| bc.max() | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn paint(&mut self, ctx: &mut PaintCtx<'_>, _props: &PropertiesRef<'_>, scene: &mut Scene) { | ||||||||||||
| (self.draw)(scene, ctx.size()); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn accessibility_role(&self) -> Role { | ||||||||||||
| Role::Canvas | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn accessibility( | ||||||||||||
| &mut self, | ||||||||||||
| _ctx: &mut AccessCtx<'_>, | ||||||||||||
| _props: &PropertiesRef<'_>, | ||||||||||||
| node: &mut Node, | ||||||||||||
| ) { | ||||||||||||
| if let Some(text) = &self.alt_text { | ||||||||||||
| node.set_description(text.clone()); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn children_ids(&self) -> ChildrenIds { | ||||||||||||
| ChildrenIds::new() | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn make_trace_span(&self, widget_id: WidgetId) -> Span { | ||||||||||||
| trace_span!("Canvas", id = widget_id.trace()) | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| fn get_debug_text(&self) -> Option<String> { | ||||||||||||
| self.alt_text.clone() | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| type Action | ||||||||||||
| = NoAction | ||||||||||||
| where | ||||||||||||
| Self: Sized; | ||||||||||||
|
||||||||||||
| type Action | |
| = NoAction | |
| where | |
| Self: Sized; | |
| type Action = NoAction; |
For consistency with the rest of the widgets, I'd probably also move this to the start of this implementation. But that isn't blocking.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,112 @@ | ||||||||||
| // Copyright 2024 the Xilem Authors | ||||||||||
| // SPDX-License-Identifier: Apache-2.0 | ||||||||||
|
|
||||||||||
| use std::sync::Arc; | ||||||||||
|
|
||||||||||
| use masonry::core::WidgetMut; | ||||||||||
| use masonry::widgets; | ||||||||||
| use vello::Scene; | ||||||||||
| use vello::kurbo::Size; | ||||||||||
| use xilem_core::MessageContext; | ||||||||||
|
|
||||||||||
| use crate::core::{Mut, ViewMarker}; | ||||||||||
| use crate::{MessageResult, Pod, View, ViewCtx}; | ||||||||||
|
|
||||||||||
| /// Creates a non-interactive drawing surface. | ||||||||||
| /// | ||||||||||
| /// The `canvas` function provides a way to render custom graphics using a | ||||||||||
| /// user-supplied drawing callback. | ||||||||||
| /// | ||||||||||
| /// # Example | ||||||||||
| /// | ||||||||||
| /// ``` | ||||||||||
| /// use xilem::view::canvas; | ||||||||||
| /// use vello::{ | ||||||||||
|
||||||||||
| /// kurbo::{Affine, Rect}, | ||||||||||
| /// peniko::{Color, Fill}, | ||||||||||
| /// Scene, | ||||||||||
| /// }; | ||||||||||
| /// | ||||||||||
| /// let my_canvas = canvas(|scene: &mut Scene, size| { | ||||||||||
|
||||||||||
| /// // Define a rectangle that fills the entire canvas. | ||||||||||
| /// let rect = Rect::new(0.0, 0.0, size.width, size.height); | ||||||||||
| /// | ||||||||||
| /// // Fill the rectangle with a solid color. | ||||||||||
| /// scene.fill( | ||||||||||
| /// Fill::NonZero, | ||||||||||
| /// Affine::IDENTITY, | ||||||||||
| /// &Color::from_rgb8(51, 102, 204), | ||||||||||
| /// None, | ||||||||||
| /// &rect, | ||||||||||
| /// ); | ||||||||||
| /// }); | ||||||||||
| /// ``` | ||||||||||
| pub fn canvas(draw: impl Fn(&mut Scene, Size) + Send + Sync + 'static) -> Canvas { | ||||||||||
|
||||||||||
| pub fn canvas(draw: impl Fn(&mut Scene, Size) + Send + Sync + 'static) -> Canvas { | |
| pub fn canvas(draw: Arc<dyn Fn(&mut Scene, Size) + Send + Sync + 'static>) -> Canvas | |
| // or | |
| pub fn canvas(draw: &Arc<dyn Fn(&mut Scene, Size) + Send + Sync + 'static>) -> Canvas |
Because we want this to allow not resetting the draw function every rebuild. Currently the !Arc::ptr_eq line will always return true (i.e. that the items are not equal) because a new Arc is allocated in each call to canvas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, the same comment applies as in #875, namely: