Skip to content
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

Merged
merged 14 commits into from
Sep 28, 2020

Conversation

rjwittams
Copy link
Collaborator

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:

  • The way Rotated messes with the invalidation region is a bit rough and ready.
    I didn't want to come up with an elaborate scheme as the best way to do it needs discussion
  • Axis probably should be promoted out of Flex
  • CrossAxisAlignment for tabs might be an over-re-use. But not sure.
  • Possibly the event methods in tabs (when events should be passed on to children) should move onto the Event enum.
  • Possibly the event transform in Rotated should move there too.

I left both where they were as its probably easier to review

@rhzk
Copy link
Collaborator

rhzk commented Aug 27, 2020

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
a
g
e

3

otherwise it feels like I should tilt my head to the side to read it

@rjwittams
Copy link
Collaborator Author

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 have not seen the behaviour you describe at all wrt tabs. Possibly this is a platform issue? I'll test it again but it sounds very odd.
I don't get why you wouldn't add your own spacing to the child, ie put it in a Flex or whatever. Having dead space invented by the tab widget seems strange to me.

@rjwittams
Copy link
Collaborator Author

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?

@rhzk
Copy link
Collaborator

rhzk commented Aug 28, 2020

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.

@rjwittams
Copy link
Collaborator Author

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.

@rhzk
Copy link
Collaborator

rhzk commented Aug 28, 2020

What I ment was this, top one being a textbox in viewswitcher and bottom one in a tab

img

@rjwittams
Copy link
Collaborator Author

rjwittams commented Aug 28, 2020

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'll investigate tomorrow.

@rjwittams
Copy link
Collaborator Author

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.
@rjwittams
Copy link
Collaborator Author

@rhzk I think both of those issues might be fixed now - no longer use expand, the tab body expands itself (which leaves children alone).
The animation one was a bit odd, I had to add in another call to request_paint. I think it might be that the mac shell ends up painting anyway when animation frames happen and I was accidentally relying on that. So possibly a bug in the mac shell.
I guess the lesson is to always try at least two platforms!
I haven't tested Windows as I don't have it.
I will try out the web again at some point

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

@rhzk
Copy link
Collaborator

rhzk commented Aug 29, 2020

Looks like its all working on Widows now!

@rjwittams
Copy link
Collaborator Author

http://chil.rice.edu/research/pdf/byrne2002hfes.pdf <- On rotated text. There is always a creaky old paper to be wheeled out

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);
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator

@jneem jneem left a 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...

druid/src/widget/rotated.rs Outdated Show resolved Hide resolved
jneem
jneem previously requested changes Sep 2, 2020
Copy link
Collaborator

@jneem jneem left a 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 :)

druid/examples/widget_gallery.rs Outdated Show resolved Hide resolved
druid/src/lib.rs Outdated Show resolved Hide resolved
druid/src/widget/flex.rs Show resolved Hide resolved
druid/src/widget/tabs.rs Outdated Show resolved Hide resolved
druid/src/widget/tabs.rs Outdated Show resolved Hide resolved
druid/src/widget/tabs.rs Show resolved Hide resolved
druid/src/widget/tabs.rs Show resolved Hide resolved
druid/src/widget/tabs.rs Outdated Show resolved Hide resolved
druid/src/widget/tabs.rs Show resolved Hide resolved
druid/src/widget/tabs.rs Show resolved Hide resolved
@cmyr
Copy link
Member

cmyr commented Sep 8, 2020

@rjwittams and I discussed this earlier today, and we agreed to split out the rotation work into a subsequent PR.

@cmyr cmyr added the S-waiting-on-author waits for changes from the submitter label Sep 8, 2020
Copy link
Member

@cmyr cmyr left a 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 using LabelText 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!

CHANGELOG.md Outdated Show resolved Hide resolved
druid-shell/src/region.rs Outdated Show resolved Hide resolved
druid/examples/tabs.rs Show resolved Hide resolved
druid/examples/tabs.rs Outdated Show resolved Hide resolved
druid/examples/tabs.rs Show resolved Hide resolved
druid/src/widget/tabs.rs Outdated Show resolved Hide resolved
druid/src/widget/tabs.rs Outdated Show resolved Hide resolved
druid/src/widget/tabs.rs Outdated Show resolved Hide resolved
druid/src/widget/tabs.rs Show resolved Hide resolved
///
pub struct Tabs<TP: TabsPolicy> {
axis: Axis,
cross: CrossAxisAlignment, // Not sure if this should have another enum. Middle means nothing here
Copy link
Member

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?

Copy link
Collaborator Author

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.

Copy link
Member

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,
}

🤷

Copy link
Collaborator Author

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

@rjwittams rjwittams added S-needs-review waits for review and removed S-waiting-on-author waits for changes from the submitter labels Sep 28, 2020
@rjwittams rjwittams requested a review from cmyr September 28, 2020 17:29
Copy link
Member

@cmyr cmyr left a 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 Show resolved Hide resolved
fn build_root_widget() -> impl Widget<AppState> {
fn decor<T: Data>(label: Label<T>) -> SizedBox<T> {
label
.padding(5.)
Copy link
Member

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/lib.rs Show resolved Hide resolved
druid/src/widget/flex.rs Show resolved Hide resolved
druid/src/widget/tabs.rs Show resolved Hide resolved
druid/src/widget/tabs.rs Show resolved Hide resolved
druid/src/widget/tabs.rs Show resolved Hide resolved

/// 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 {
Copy link
Member

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

Comment on lines 955 to 956
let mut temp = TabsContent::Swapping;
std::mem::swap(&mut self.content, &mut temp);
Copy link
Member

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);

Copy link
Collaborator Author

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!

@cmyr cmyr added S-waiting-on-author waits for changes from the submitter and removed S-needs-review waits for review labels Sep 28, 2020
@rjwittams rjwittams added S-needs-review waits for review and removed S-waiting-on-author waits for changes from the submitter labels Sep 28, 2020
@rjwittams
Copy link
Collaborator Author

I think I have changed the essential bits.

@rjwittams rjwittams requested a review from cmyr September 28, 2020 21:16
Copy link
Member

@cmyr cmyr left a 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!

@rjwittams rjwittams merged commit abf28d3 into linebender:master Sep 28, 2020
@rjwittams rjwittams deleted the tabs branch September 28, 2020 21:41
@jneem jneem mentioned this pull request May 26, 2021
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-needs-review waits for review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants