-
Notifications
You must be signed in to change notification settings - Fork 567
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
Tabs widget and the Rotated widget that it uses. #1160
Conversation
Some things I noticed from the example: If I click on a new tab I see the content of the previous tab I was on. I.e if I click on "Advanced" I see the content of "Basic" and If I then click on "Page 3" I see the content of "Advanced". But then the button does not work, I guess because I am on "Page 3". I am not sure that the child widget size should be set to the entire tab space, now I cant have fixed width on a label, and if I add a background color to the label it fills the tab. I am not a fan of downward text but if we where to have it I dont like to have each letter rotated, I think it would be better in that case for it to be like this: P 3 otherwise it feels like I should tilt my head to the side to read it |
On rotation: this is fairly standard. E.g IntelliJ does it. I have literally never seen your recommendation outside of motel signs. If you 'don't like' the rotation, don't use it. |
I can't recreate the behaviour you describe. I hope its fairly obvious I would have noticed that... is this on Windows? I wonder if its a platform event handling difference or something? Really doesn't make a lot of sense. Does the same happen with the dynamic tabs? |
The issue is also with the dynamic tabs, I am on windows. Sure you can put widgets inside a flex but I tought it was odd to overwrite the widgets behavior, ie a textbox filling the entire width of the tab instead of the width you'd expect it to have. For the suggestion to have downwards tabs without the text being rotated, if you dont want to have that thats fine, but saying "if you dont like it dont use it" is not the wording I would use if somebody asked for a feature. |
You are asking to remove a feature. You literally don't have to use it - leave the tabs not rotated by setting the rotation to None. I have no idea what you mean by overriding the widgets behaviour. It does exactly the same thing as e.g ViewSwitcher. If this is a Windows only issue it must be fairly fundamental. |
On the tab changing, I managed to get something like it to happen on GTK in a linux VM. It might be some difference in the way the animations work - in fact they don't seem to be happening at all... I think it means that it gets stuck at the start of the animation that should be happening - ie with the previous tab. |
I think its because I do .expand() on the tabs body (which does work like ViewSwitcher internally), this was I think also to make the animations look sensible (using the whole width of the tab). I'll see if there is a better way to do it... |
Add in a call to request_paint to make the tabs transition animation work on Linux (and hopefully Windows)
…self, and just draw its child in the origin. Seems to work ok with the animations.
@rhzk I think both of those issues might be fixed now - no longer use expand, the tab body expands itself (which leaves children alone). I also noticed on Linux that the circle with an X on it symbol wasn't in the default font. This was pretty much a placeholder anyway. I may paint it directly or use images - but not sure we have a good answer on shipping around images in the druid library? Probably painting is better. Can do some kind of hover then too |
Looks like its all working on Widows now! |
http://chil.rice.edu/research/pdf/byrne2002hfes.pdf <- On rotated text. There is always a creaky old paper to be wheeled out |
druid/src/widget/rotated.rs
Outdated
impl<T, W: Widget<T>> Widget<T> for Rotated<W> { | ||
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) { | ||
if let Some((transform, inverse)) = self.transforms { | ||
ctx.widget_state.invalid.transform_by(inverse); |
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.
Every context with a request_paint
method can modify the invalid region, so this also needs to be in update
and lifecycle
. But I think it would actually be nicer (and maybe less fragile) to handle this in WidgetState
. That is, replace WidgetState::viewport_offset
by, say, WidgetState::viewport_transform
,(which would be an Affine
), and then handle the transformation of invalid regions during WidgetState::merge_up
.
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 - as I mentioned in the description, I didn't want to do too much with this region stuff before the general idea was thought to make sense.
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 just looked at Rotated
so far...
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.
Very nice! My main complaint, as usual, is that it's missing API docs. I gave up on pointing them out one-by-one, but anyway I think that the CI is now configured to complain about them :)
@rjwittams and I discussed this earlier today, and we agreed to split out the rotation work into a subsequent PR. |
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.
Great, happy to see the ambition here. I have a bunch of questions and comments inline, but overall I think this is close to ready to get in?
Some additional unordered thoughts:
-
I'd like to see some clarification around the various types whose names begin with
Tab
; this means some docs, but also some careful consideration around what needs to be public and what does not. -
I'm also seeing a bunch of warnings when I play around with the example, which should get looked at; either we're missing some event or the warnings are too aggressive.
-
I haven't done a careful review of the layout and drawing code; if there are issues in there they'll shake out over time.
-
I'm concerned about
String
in places; ideally we would be usingLabelText
for any text that is shown the user. -
we're still missing docs on public items; if these aren't showing up in CI it probably means the items aren't actually being exported, and shouldn't be
pub
. -
I think this is an interesting exploration of some new patterns, which I'm happy about. It's also nice that it is quite compartmentalized, and so we can always revisit in the future without much disruption.
@rjwittams if you'd like to go through the various comments and either reply or mark them as resolved that would be great, and then please ping me and switch the label back to 'ready-for-review' when you think you've addressed everything, and I'll take another pass. thanks!
druid/src/widget/tabs.rs
Outdated
/// | ||
pub struct Tabs<TP: TabsPolicy> { | ||
axis: Axis, | ||
cross: CrossAxisAlignment, // Not sure if this should have another enum. Middle means nothing 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.
let's talk about this: what is this type used for, exactly?
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 end of the cross axis does your tab bar live on. Should be fairly easy to get from running the example.
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, gotcha. I might call it something like "tabs_location"? And I agree, CrossAxisAlignment
doesn't feel like exactly the right enum for this, I might just shrug and add something like,
enum TabsEdge {
Leading,
Trailing,
}
🤷
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 think thats better
Use that in examples.
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.
Okay, I think this is pretty much there. a few more comments are inline, but I don't think anything is blocking, except maybe for using something other than CrossAxisAlignment
.
Feel free to address other stuff as you see fit; I'll take one more look when you're done and then I think we're good to go. Thanks!
druid/examples/tabs.rs
Outdated
fn build_root_widget() -> impl Widget<AppState> { | ||
fn decor<T: Data>(label: Label<T>) -> SizedBox<T> { | ||
label | ||
.padding(5.) |
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 problem is ensuring widgets look correct when used beside one another; this is especially an issue when using a RadioGroup
. If you use the default_spacer
methods, you will have the same space between widgets as is used in RadioGroup
(and other built-in components) wheras if you pick your own padding values you'll end up with items that don't line up correctly.
This isn't currently a practical issue in this example, but given that examples in general tend to be copied by new users, I want to discourage people from using arbitrary padding values, and encourage them to use these other mechanisms that will create more consistent layouts.
I agree about the increased code noise, and don't have a good answer there. Perhaps in the future we will apply auto-padding to items in collection by default or something? But this is where we are for the time being. 😒
druid/src/widget/tabs.rs
Outdated
|
||
/// Put the tab bar at the corresponding end of the cross axis. | ||
/// Defaults to Start. Note that Middle has the same effect as Start. | ||
pub fn with_cross_axis_alignment(mut self, cross: CrossAxisAlignment) -> Self { |
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.
echo my earlier comment I would prefer to call this something like with_tab_bar_location
or whatever.
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
druid/src/widget/tabs.rs
Outdated
let mut temp = TabsContent::Swapping; | ||
std::mem::swap(&mut self.content, &mut temp); |
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.
nit, but I think the idiomatic way to do this would be to use std::mem::replace
, something like,
let content = std::mem::replace(&mut self.content, TabsContent::Swapping);
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 think its another case where I didn't know about replace when I wrote this!
I think I have changed the essential bits. |
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.
Okay this looks good to me, happy to get this in as-is and we can shake out any issues as they arise. Thank you for your patience!
The tabs widget and dependencies extracted from binding-scroll.
This does static and dynamic tabs, and also includes a Rotated widget that it uses to rotate tab labels.
Things that maybe should change:
I didn't want to come up with an elaborate scheme as the best way to do it needs discussion
I left both where they were as its probably easier to review