-
Notifications
You must be signed in to change notification settings - Fork 126
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
xilem_masonry: Add Label widget #226
Changes from all commits
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 |
---|---|---|
@@ -1,45 +1,61 @@ | ||
// On Windows platform, don't show a console when opening the app. | ||
#![windows_subsystem = "windows"] | ||
|
||
use xilem_masonry::view::{button, checkbox, flex}; | ||
use xilem_masonry::{BoxedMasonryView, MasonryView, Xilem}; | ||
use xilem_masonry::view::{button, checkbox, flex, label}; | ||
use xilem_masonry::{Axis, BoxedMasonryView, Color, MasonryView, TextAlignment, Xilem}; | ||
|
||
fn app_logic(data: &mut AppData) -> impl MasonryView<AppData> { | ||
// here's some logic, deriving state for the view from our state | ||
let count = data.count; | ||
let label = if count == 1 { | ||
let button_label = if count == 1 { | ||
"clicked 1 time".to_string() | ||
} else { | ||
format!("clicked {count} times") | ||
}; | ||
|
||
// The actual UI Code starts here | ||
|
||
let axis = if data.active { | ||
Axis::Horizontal | ||
} else { | ||
Axis::Vertical | ||
}; | ||
|
||
let sequence = (0..count) | ||
.map(|x| button(format!("+{x}"), move |data: &mut AppData| data.count += x)) | ||
.collect::<Vec<_>>(); | ||
flex(( | ||
button(label, |data: &mut AppData| data.count += 1), | ||
flex(( | ||
label("Label") | ||
.color(Color::REBECCA_PURPLE) | ||
.alignment(TextAlignment::Start), | ||
label("Disabled label").disabled(), | ||
)) | ||
.direction(Axis::Horizontal), | ||
button(button_label, |data: &mut AppData| data.count += 1), | ||
checkbox("Check me", data.active, |data: &mut AppData, checked| { | ||
data.active = checked; | ||
}), | ||
toggleable(data), | ||
button("Decrement", |data: &mut AppData| data.count -= 1), | ||
button("Reset", |data: &mut AppData| data.count = 0), | ||
sequence, | ||
flex(sequence).direction(axis), | ||
)) | ||
} | ||
|
||
fn toggleable(data: &mut AppData) -> impl MasonryView<AppData> { | ||
let inner_view: BoxedMasonryView<_, _> = if data.active { | ||
Box::new(flex(( | ||
button("Deactivate", |data: &mut AppData| { | ||
data.active = false; | ||
}), | ||
button("Unlimited Power", |data: &mut AppData| { | ||
data.count = -1_000_000; | ||
}), | ||
))) | ||
Box::new( | ||
flex(( | ||
button("Deactivate", |data: &mut AppData| { | ||
data.active = false; | ||
}), | ||
button("Unlimited Power", |data: &mut AppData| { | ||
data.count = -1_000_000; | ||
}), | ||
)) | ||
.direction(Axis::Horizontal), | ||
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. Should we have a dynamically toggling axis in the same widget here? 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. Not sure, it jumps around the layout, which I didn't find satisfying, but I added it for the |
||
) | ||
} else { | ||
Box::new(button("Activate", |data: &mut AppData| data.active = true)) | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ use masonry::{ | |
BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, PointerEvent, | ||
Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod, | ||
}; | ||
pub use masonry::{widget::Axis, Color, TextAlignment}; | ||
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. I'm shamelessly reexporting all these attributes to the root of the library (since I think they're mostly unambiguous), not sure what our policy here is. 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. This is probably #223 again. I think this addition is fine, but we need to reason out a final policy for this, rather than making ad-hoc decisions |
||
use smallvec::SmallVec; | ||
use vello::Scene; | ||
use winit::{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,34 @@ | ||
use std::marker::PhantomData; | ||
|
||
use masonry::{ | ||
widget::{self, WidgetMut}, | ||
widget::{self, Axis, WidgetMut}, | ||
Widget, WidgetPod, | ||
}; | ||
|
||
use crate::{ElementSplice, MasonryView, VecSplice, ViewSequence}; | ||
use crate::{ChangeFlags, ElementSplice, MasonryView, VecSplice, ViewSequence}; | ||
|
||
// TODO: Allow configuring flex properties. I think this actually needs its own view trait? | ||
pub fn flex<VT, Marker>(sequence: VT) -> Flex<VT, Marker> { | ||
Flex { | ||
phantom: PhantomData, | ||
sequence, | ||
axis: Axis::Vertical, | ||
} | ||
} | ||
|
||
pub struct Flex<VT, Marker> { | ||
sequence: VT, | ||
axis: Axis, | ||
phantom: PhantomData<fn() -> Marker>, | ||
} | ||
|
||
impl<VT, Marker> Flex<VT, Marker> { | ||
pub fn direction(mut self, axis: Axis) -> Self { | ||
self.axis = axis; | ||
self | ||
} | ||
} | ||
|
||
impl<State, Action, Marker: 'static, Seq> MasonryView<State, Action> for Flex<Seq, Marker> | ||
where | ||
Seq: ViewSequence<State, Action, Marker>, | ||
|
@@ -35,7 +44,7 @@ where | |
let mut scratch = Vec::new(); | ||
let mut splice = VecSplice::new(&mut elements, &mut scratch); | ||
let seq_state = self.sequence.build(cx, &mut splice); | ||
let mut view = widget::Flex::column(); | ||
let mut view = widget::Flex::for_axis(self.axis); | ||
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. I didn't expose this intentionally, because Masonry doesn't support changing the axis at runtime. We do not want to have views which give different final trees in 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. Oh, haven't checked that closely yet. That's unfortunate, and I agree, all attributes that are able to set in the view should of course change the widget tree. 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. Or, well just implement it in masonry, checking that now... 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. So I've implemented it in masonry, and checked it via let axis = if count % 2 == 0 {
Axis::Horizontal
} else {
Axis::Vertical
};
...
flex(()).direction(axis) (but have hardcoded the values in the example) |
||
debug_assert!( | ||
scratch.is_empty(), | ||
// TODO: Not at all confident about this, but linear_layout makes this assumption | ||
|
@@ -63,11 +72,19 @@ where | |
view_state: &mut Self::ViewState, | ||
cx: &mut crate::ViewCx, | ||
prev: &Self, | ||
element: widget::WidgetMut<Self::Element>, | ||
) -> crate::ChangeFlags { | ||
mut element: widget::WidgetMut<Self::Element>, | ||
) -> ChangeFlags { | ||
let mut changeflags = ChangeFlags::UNCHANGED; | ||
if prev.axis != self.axis { | ||
element.set_direction(self.axis); | ||
changeflags.changed |= ChangeFlags::CHANGED.changed; | ||
} | ||
let mut splice = FlexSplice { ix: 0, element }; | ||
self.sequence | ||
changeflags.changed |= self | ||
.sequence | ||
.rebuild(view_state, cx, &prev.sequence, &mut splice) | ||
.changed; | ||
changeflags | ||
} | ||
} | ||
|
||
|
@@ -121,7 +138,6 @@ impl ElementSplice for FlexSplice<'_> { | |
} | ||
|
||
fn len(&self) -> usize { | ||
// This is not correct because of the spacer items. Is `len` actually needed? | ||
self.element.len() - self.ix | ||
self.ix | ||
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. Sure! I suspect that we'll probably just remove 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. Yeah probably makes sense, doesn't seem to be needed anymore (I'll probably look into this more deeply, whether that is indeed the case). 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. It's used in old 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. I don't think its use in |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
use masonry::{widget::WidgetMut, ArcStr, WidgetPod}; | ||
|
||
use crate::{ChangeFlags, Color, MasonryView, MessageResult, TextAlignment, ViewCx, ViewId}; | ||
|
||
pub fn label(label: impl Into<ArcStr>) -> Label { | ||
Label { | ||
label: label.into(), | ||
text_color: Color::BLACK, | ||
alignment: TextAlignment::default(), | ||
disabled: false, | ||
} | ||
} | ||
|
||
pub struct Label { | ||
label: ArcStr, | ||
text_color: Color, | ||
alignment: TextAlignment, | ||
disabled: bool, | ||
// TODO: add more attributes of `masonry::widget::Label` | ||
} | ||
|
||
impl Label { | ||
pub fn color(mut self, color: Color) -> Self { | ||
self.text_color = color; | ||
self | ||
} | ||
|
||
pub fn alignment(mut self, alignment: TextAlignment) -> Self { | ||
self.alignment = alignment; | ||
self | ||
} | ||
|
||
pub fn disabled(mut self) -> Self { | ||
self.disabled = true; | ||
self | ||
} | ||
} | ||
|
||
impl<State, Action> MasonryView<State, Action> for Label { | ||
type Element = masonry::widget::Label; | ||
type ViewState = (); | ||
|
||
fn build(&self, _cx: &mut ViewCx) -> (WidgetPod<Self::Element>, Self::ViewState) { | ||
( | ||
WidgetPod::new( | ||
masonry::widget::Label::new(self.label.clone()) | ||
.with_text_color(self.text_color) | ||
.with_text_alignment(self.alignment) | ||
.with_disabled(self.disabled), | ||
), | ||
(), | ||
) | ||
} | ||
|
||
fn rebuild( | ||
&self, | ||
_view_state: &mut Self::ViewState, | ||
_cx: &mut ViewCx, | ||
prev: &Self, | ||
mut element: WidgetMut<Self::Element>, | ||
) -> crate::ChangeFlags { | ||
let mut changeflags = ChangeFlags::UNCHANGED; | ||
|
||
if prev.label != self.label { | ||
element.set_text(self.label.clone()); | ||
changeflags.changed |= ChangeFlags::CHANGED.changed; | ||
} | ||
if prev.disabled != self.disabled { | ||
element.set_disabled(self.disabled); | ||
changeflags.changed |= ChangeFlags::CHANGED.changed; | ||
} | ||
if prev.text_color != self.text_color { | ||
element.set_text_color(self.text_color); | ||
changeflags.changed |= ChangeFlags::CHANGED.changed; | ||
} | ||
if prev.alignment != self.alignment { | ||
element.set_text_alignment(self.alignment); | ||
changeflags.changed |= ChangeFlags::CHANGED.changed; | ||
} | ||
changeflags | ||
} | ||
|
||
fn message( | ||
&self, | ||
_view_state: &mut Self::ViewState, | ||
_id_path: &[ViewId], | ||
message: Box<dyn std::any::Any>, | ||
_app_state: &mut State, | ||
) -> crate::MessageResult<Action> { | ||
tracing::error!("Message arrived in Label::message, but Label doesn't consume any messages, this is a bug"); | ||
MessageResult::Stale(message) | ||
Philipp-M marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,6 @@ pub use checkbox::*; | |
|
||
mod flex; | ||
pub use flex::*; | ||
|
||
mod label; | ||
pub use label::*; |
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 does it even mean for a label to be
disabled
?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 guess it makes more sense in combination with a checkbox. But basically, that it's not clickable in HTML jargon at least
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.
Right, so e.g. the contents can't be clicked and dragged? I guess it also would have consequences for the accessibility metadata
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.
Yeah I guess that is indeed the case.