-
Notifications
You must be signed in to change notification settings - Fork 135
Add Canvas widget #875
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
base: main
Are you sure you want to change the base?
Add Canvas widget #875
Conversation
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.
Apart from the pointer event TODO comment, this seems reasonable as an addition to Masonry.
masonry/src/widgets/canvas.rs
Outdated
// --- MARK: IMPL WIDGET --- | ||
impl Widget for Canvas { | ||
fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &PointerEvent) { | ||
// TODO: ensure coordinates are correct and pass to callback |
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.
This comment is misleading. Something has gone wrong if the canvas widget is managing the pointer event. At that point, the user should be writing their own widget, I believe.
A canvas is basically equivalent to an Image, but containing a vector scene (ish).
masonry/src/widgets/canvas.rs
Outdated
} | ||
|
||
fn accessibility(&mut self, _ctx: &mut AccessCtx, _node: &mut Node) { | ||
// TODO: should probably give the caller the opportunity to handle accessibility |
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.
This would probably just be an alt-text like mechanism, although maybe we should also be strongly encouraging that a caption exist?
We haven't handled this for Image, which this is very similar to.
One question I have: can a scene fragment be retained and appended to the scene on paint? |
Masonry is already doing that internally; your paint callback will only be called once, except when it gets replaced or the layout changes |
594c48d
to
0326db5
Compare
OK ready for review. |
How is the CI so fast!?!?! |
masonry/src/widgets/canvas.rs
Outdated
fn accepts_pointer_interaction(&self) -> bool { | ||
false | ||
} |
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.
I don't think you should set this flag to false. It makes Canvas transparent to pointers, meaning for example that if there's a widget behind the canvas, that widget will get pointer events, affect the pointer icon, etc.
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.
I'm not certain that I agree; I can see arguments either way (since the canvas isn't filled by default, for example). But since if it did intercept pointer events, they would still bubble up to the parents, then either way is fine.
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.
I suggest setting it to true
for now, then seeing how that works in practice.
masonry/src/widgets/canvas.rs
Outdated
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { | ||
(self.draw)(scene, ctx.size()); | ||
} |
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.
So, to be clear, this is a static canvas, right? The draw function isn't expected to paint different things from frame to frame?
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.
I think so - it could be used for things like icons. But what should be different if it were more dynamic?
Co-authored-by: Olivier FAURE <couteaubleu@gmail.com>
fb12fbe
to
ec5c653
Compare
Should be ready for review again (assuming CI is green). I will squash before merging, as I don't think there is anything to be gained by keeping the commits. |
Not necessary though, as this is the default anyways when merging (with the title/text in the PR top comment as commit description), so keeping the commits should give a better timeline of this PR shouldn't it? |
/// A non-interactive text element. | ||
/// # Example | ||
/// | ||
/// ```ignore | ||
/// use xilem::palette; | ||
/// use xilem::view::label; | ||
/// use masonry::TextAlignment; | ||
/// use masonry::parley::fontique; | ||
/// | ||
/// label("Text example.") | ||
/// .brush(palette::css::RED) | ||
/// .alignment(TextAlignment::Middle) | ||
/// .text_size(24.0) | ||
/// .weight(FontWeight::BOLD) | ||
/// .with_font(fontique::GenericFamily::Serif) | ||
/// ``` |
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.
I don't think these docs are right...
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.
Oops sorry I missed this!
_ctx: &mut ViewCtx, | ||
element: Mut<Self::Element>, | ||
) { | ||
if !Arc::ptr_eq(&self.draw, &prev.draw) { |
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.
This is... always true, because canvas
always allocates. I think the only sound way to handle this currently is to force the user to provide an already Arc
ed function. That's... far from ideal from an ergonomics perspective, but it matches the semantics you need.
Essentially, you'd need to combine it with something like memoize
for this API to be reasonable; it's the unfortunate ergonomics papercut around closures being incomparable (rust-lang/rfcs#3538)
let mut harness = TestHarness::create(canvas); | ||
|
||
assert_debug_snapshot!(harness.root_widget()); | ||
assert_render_snapshot!(harness, "hello"); |
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.
assert_render_snapshot!(harness, "hello"); | |
assert_render_snapshot!(harness, "stroked_triangle"); |
(I'm not sure if _
or -
is more idiomatic here)
this.ctx.request_render(); | ||
} | ||
|
||
pub fn set_alt_text(mut this: WidgetMut<'_, Self>, alt_text: String) { |
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.
This not getting a warning for missing_docs
suggests that something is wrong here. (It's fine for the docs to just point to Canvas::with_alt_text
)
} | ||
|
||
pub fn remove_alt_text(mut this: WidgetMut<'_, Self>) { | ||
this.widget.alt_text = None; |
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.
What are the semantics of no alt text as opposed to setting it to ""
? (cc @PoignardAzur, as you suggest setting it to Some(String::new())
if it's not a semantically meaningful 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.
You'd have to ask @mwcampbell for an authoritative answer, but from what I remember:
- Setting no alt-text means you "forgot" to add it, and some screen reader tools might add a default one for you.
- Setting an empty alt text means you consider that the item shouldn't be described by screen readers.
At least I think that's how ARIA treats it.
/// 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 `""`. |
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.
@PoignardAzur, do you have an example of something which is "too hard" to describe through text, but which should be a canvas?
That is, if the "too hard" clause is for highly dynamic content, then it probably should be a bespoke widget with a proper accessibility implementation, and so the "too hard" clause doesn't apply. Would it be better for the alt text to say "we've chosen not to make this accessible because we can't be bothered" in that case?
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.
The canonical way to say "We've chosen to not include this in the accessibility tree" is to add an empty string as alt-text.
Would it be better for the alt text to say "we've chosen not to make this accessible because we can't be bothered" in that case?
For this kind of question, you must keep in the mind the experience of reading a page with a screen reader. Like, if you're reading an article or using an app, does it make sense to have the reader say "Menu, Help, Files, New Project, Open Project, We're sorry but we've chosen to not make this app accessible"? Does it help you navigate the app better?
I don't have answers for the general case, and I'd advise against giving one. I want to discourage the mode of thinking that making an app accessible is about giving each widget a maximally descriptive alt text.
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.
It's a good point. My thinking here does need to be focused on the user experience, and having a snarky message isn't that. But I still think there is an important distinction between the two cases, and using the same handling for both is doing a disservice to the end user. That is, the cases are:
- Not in the accessibility tree/only for visual display purposes. In that case setting to the empty string is the right behaviour. I agree completely with that.
- The content of this widget is potentially important to all users, but we've decided not to make it accessible because doing so would be "too hard". Add Canvas widget #875 (comment) seems to answer this; that if it's "too hard" to add alt text, then we should just not add it. That would then use the screen reader's default handling of "developer didn't bother to make this accessible".
This sentence in the docs conflates those two cases, I think. Having both be the same case has one of two consequences:
- It just completely hides that important information is missing from the user; i.e. creates an unknown unknown; OR
- It forces the user to doubt all items which are indicated as display-only
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.
This comment is entirely correct.
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.
Which one? You mean Daniel's?
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.
Yes. You were also correct about the distinction between no alt text and empty alt text.
panic!("Snapshot test '{test_name}' failed: No reference file"); | ||
panic!( | ||
"Snapshot test '{test_name}' failed: No reference file\n\ | ||
New screenshot created with `.new.png` extension. If correct, change to `.png`" |
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.
I think it would be slightly better style to indent this to match the start of the string, rather than the panic
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.
(Marking as request changes for the sake of GitHub's UI, to indicate that this is waiting on the author)
Any new widget code needs to have full docs. Not having `missing_docs` trigger by default for new widgets is quite bad. See #875 and #882 for cases where this over-broad allow has bitten us. The comment about not using `expect(missing_docs)` because of rust-analyzer is confusing to me; I don't run into an issue. It might have been rust-lang/rust#130021, which is now fixed. That comment should have had a link to an upstream issue for more context.
I'm playing with making a vector path editor in
xilem
. This requires some extra stuff over what is currently available so I'm going to maintain a fork. I will try to merge upstream anything I think is more widely useful.This PR allows the user to draw directly onto the scene fragment for a widget. Currently, the user must recreate the draw function every time there is a change in state. I wonder if there should be separated out state and draw parts, so the function needs to be updated less often.