-
Notifications
You must be signed in to change notification settings - Fork 155
Add a FlexElement
to support explicit spacers and flex parameters for Flex
in xilem
#428
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
Conversation
Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
xilem/src/lib.rs
Outdated
@@ -139,6 +141,13 @@ pub trait WidgetView<State, Action = ()>: | |||
View<State, Action, ViewCtx, Element = Pod<Self::Widget>> + Send + Sync | |||
{ | |||
type Widget: Widget; | |||
|
|||
fn flex(self, params: impl Into<FlexParams>) -> FlexItem<Self, State, Action> |
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.
Having a builder method for this on the WidgetView
trait, is obviously open for discussion, I'm not sure of it yet:
What may or may not make sense from an API standpoint is to support these kinds of methods, when the returned element is also a WidgetView
, FlexItem
is not a WidgetView
, so it can't be used outside of the context of a Flex
container view.
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 if we have this, it should be a separate FlexExt
trait. That is, this method shouldn't be here.
I agree that the methods on WidgetView
should return other WidgetView
s.
(I wonder if we should have boxed(self) -> Box<dyn AnyWidgetView<State, Action>>
)
// TODO | ||
self.ctx.widget_state.children_changed = true; | ||
self.ctx.widget_state.needs_layout = true; | ||
self.ctx.children_changed(); | ||
} | ||
|
||
// TODO - remove |
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 that could be removed now?
any_use_baseline |= alignment == CrossAxisAlignment::Baseline; | ||
|
||
let old_size = ctx.widget_state.layout_rect().size(); | ||
let child_bc = self.direction.constraints(&loosened_bc, 0.0, 9000.0); // TODO Infinity does lead to NaN and then crashes the app, if this is too big, it leads to floating point issues |
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 I remember discussions about this, to avoid crashes (NaN
and the likes), I set it to a high but not too high number (as vello goes crazy then, with weird floating point artifacts), the issue also exists for the current layout
method
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.
to avoid crashes (NaN and the likes), I set it to a high but not too high number
Pretty sure there's a Codeless Code fable about this. 😃
The fundamental problem is that Masonry's layout model is confused, not that Flex had the wrong maximum constraint. Though I guess this works until we fix the underlying problem.
} | ||
} | ||
|
||
pub struct FlexItem<V, State, Action> { |
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.
Because of parameterization issues, I needed to add these generic parameters.
I guess we need to do this a lot more for other views as well, to seamlessly support stuff like Adapt
or in general inference for these variables, when it's not explicit somewhere...
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, this is a choice we have to make, and I think adding them to the View type is the right one, to keep the type inference failures local.
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 that's better.
I think in the future it would be great to somehow implement a similar mechanism as WidgetMut
to access the Child
to avoid needing access to the parent/flex-container, but I don't think it should be done in this PR, and I'm still not sure if it's worth it...
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 haven't carefully examined the Masonry level changes, but I also think that's fine, because we're planning on changing Flex
to use Taffy at some point anyway.
I think that Masonry flex needs to support something akin to CSS gap
, because currently this significantly regresses all of our examples. That doesn't need to happen in this PR, but it does need to happen extremely soon after this lands.
The Xilem level changes look as I'd expect. If you want to add AnyFlexView
in this PR, that would be very useful @Philipp-M, but I'm not going to block on it.
I would like to get @PoignardAzur's sign-off on WidgetMut::reborrow
before landing.
xilem/src/lib.rs
Outdated
@@ -139,6 +141,13 @@ pub trait WidgetView<State, Action = ()>: | |||
View<State, Action, ViewCtx, Element = Pod<Self::Widget>> + Send + Sync | |||
{ | |||
type Widget: Widget; | |||
|
|||
fn flex(self, params: impl Into<FlexParams>) -> FlexItem<Self, State, Action> |
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 if we have this, it should be a separate FlexExt
trait. That is, this method shouldn't be here.
I agree that the methods on WidgetView
should return other WidgetView
s.
(I wonder if we should have boxed(self) -> Box<dyn AnyWidgetView<State, Action>>
)
Re: In theory it could have surprising effects if some other code reads the parent state and doesn't expect it to change; in practice the fact that WidgetMut holds a mut reference to said parent state means that will never happen. |
Otherwise, LGTM. |
The |
It's not that heavy. About a dozen boolean flags getting OR-ed. (That's 16 bytes with bools, 2 with bitblags). The two non-trivial bits of logic are text registrations and cursor changes, both of which should probably be moved out of merge_up. The more concerning part is this: self.text_registrations
.append(&mut child_state.text_registrations); Which we probably don't want to duplicate. Once we remove that line, we can remove the |
Does it hurt otherwise, I mean it's just little bit of extra mental overhead, and I assume Btw. I have started with the And I will likely also add the |
Fair enough, it's a minor point.
I don't expect it to get much more expensive, but it's a fair point too. |
… that can either be a spacer or a flex item, also documented a lot of the flex implementation
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've started documenting (including doc tests, which at least check if all the typing checks out), added the WidgetView::boxed
as I think it's an uncontroversal change, and the FlexExt
trait, and of course added an AnyFlexChild
view, which in its impl View
logic is very similar to impl ViewSequence for Option<impl ViewSequence>
. I've also pulled the Axis
into View
and reexported relevant enums for flex_item
from masonry.
Since this is a bigger update, @DJMcNab I think it would make sense if you check at least the impl View for AnyFlexChild
.
/// # } | ||
/// | ||
/// ``` | ||
fn into_any_flex(self) -> AnyFlexChild<State, Action> |
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've tried to do this via a blanket impl, but it doesn't work, since I can't specialize over View<Element = FlexElement>
and WidgetView
, and otherwise that impl intersects, so I hardcoded these directly on either V: WidgetView
as well as for either FlexItem
and FlexSpacer
...
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.
Did you try View<Element = Pod<W>>
and View<Element = FlexElement>
? The first should be equivalent to WidgetView
, but might be more friendly to the compiler
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.
Yep I tried, I did not make it to work, unfortunately...
xilem/examples/mason.rs
Outdated
.collect::<Vec<_>>(); | ||
|
||
let flexy_flex_sequence = [2, 3, 4].map(|c| { |
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 this is our catch-all playground example? This also uses [impl ViewSequence; N]
btw.
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 - it's fine to stick anything in here 😆
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.
Thanks! A few minor feedback things, then we can land this.
/// )) | ||
/// # } | ||
/// | ||
/// ``` |
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.
Ideally we'd have an image here. We really should set up linebender.org/assets
at some point
(To be clear, this is fine as-is)
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 our documentation story can certainly be improved, but I hope this PR does improve it a little bit 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.
I also wonder, if the doc-tests could be combined with snapshots, such that this really also tests the logic (while reducing the amount of manual tests) (But I doubt that this is simple, if at all possible within in reason)
this.rebuild(prev, view_state.inner.as_mut().unwrap(), ctx, element) | ||
}), | ||
(AnyFlexChild::Spacer(prev), AnyFlexChild::Spacer(this)) => { | ||
View::<(), (), ViewCtx>::rebuild(this, prev, &mut (), ctx, element) |
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.
We should have a comment at the spacer view that it is an invariant that this view doesn't deal with messages.
xilem/src/view/flex.rs
Outdated
scratch: AppendVec<Pod<Box<dyn Widget>>>, | ||
pub enum FlexElement { | ||
// Avoid making the enum massive for the spacer cases by boxing | ||
Child(Box<Pod<Box<dyn Widget>>>, FlexParams), |
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 FlexElement
is only transitory, so it doesn't matter if the spacer case is huge.
That is, we're never storing a Vec<FlexElement>
for very long.
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.
Well actually I had it before without double boxing, this is your code from #235, but I'm also fine without it.
} | ||
} | ||
|
||
pub struct FlexItem<V, State, Action> { |
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, this is a choice we have to make, and I think adding them to the View type is the right one, to keep the type inference failures local.
xilem/examples/mason.rs
Outdated
.collect::<Vec<_>>(); | ||
|
||
let flexy_flex_sequence = [2, 3, 4].map(|c| { |
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 - it's fine to stick anything in here 😆
xilem/examples/mason.rs
Outdated
let flexy_flex_sequence = [2, 3, 4].map(|c| { | ||
if data.count.abs() % c == 0 { | ||
FlexSpacer::Fixed(30.0 * c as f64).into_any_flex() | ||
} else { | ||
button(format!("flexy +{c}"), move |data: &mut AppData| { | ||
data.count += c; | ||
}) | ||
.into_any_flex() | ||
} | ||
}); |
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 feels FizzBuzz
adjacent - not sure that we should action that though
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.
Ah yeah, lets make it FizzBuzz
^^
Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
See https://developer.mozilla.org/en-US/docs/Web/CSS/gap Since #428, we no longer have default spacing between flex elements in Masonry. This makes the examples quite ugly. Choices made based on [#xilem > Porting Taffy Integration to New Xilem](https://xi.zulipchat.com/#narrow/stream/354396-xilem/topic/Porting.20Taffy.20Integration.20to.20New.20Xilem). Of particular note is the choice to not have this be overwritten by spacers, due to: > My first thought is that users are going to be very confused when they add a space between two items and the result is the items are closer together. There is no such concept of a spacer in CSS parlance
params: impl Into<FlexParams>, | ||
) { | ||
let p = params.into(); | ||
tracing::info!("{:?}", p); |
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 needs to be removed.
Takes ideas from #235 (in particular the masonry side).
This implements support for explicit spacers in the
Flex
view and flex parameters for views (see examplesflex
andmason
for more details).It also adds a way (
AnyFlexChild
) to dynamically switch between spacers and widgets (with optional flex parameters).masonry::widget::Flex::layout
is also updated to be in sync with current druid.