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

Improve alignment options #1194

Open
richierh opened this issue Jan 24, 2021 · 19 comments
Open

Improve alignment options #1194

richierh opened this issue Jan 24, 2021 · 19 comments
Labels
enhancement New features, or improvements to existing features.

Comments

@richierh
Copy link

I don't find anyway to make a button widget in the center of a window/mainwindow

class TryWindow(toga.App):

    def startup(self):
        """
        Construct and show the Toga application.

        Usually, you would add your application to a main content box.
        We then create a main window (with a name matching the app), and
        show the main window.
        """

        self.boxa = toga.Box()
        self.main_box2 = toga.Box('box2',children=[self.boxa],style=Pack(direction='row'))
        self.main_box = toga.Box('box',children=[self.main_box2])

        self.button1 = toga.Button("Clicck", on_press = self.clickButton)#,style = Pack(direction=COLUMN,alignment=CENTER))

        self.main_box.add(self.button1)

        self.main_box.style.update(direction='row',alignment='center')

        self.main_window = toga.MainWindow(title=self.name)
        self.main_window.content = self.main_box
        self.main_window.show()
    
    def clickButton(self,event):
        print ("succeded")
        self.main_window.close()


def main():
    return TryWindow()

@richierh richierh added the enhancement New features, or improvements to existing features. label Jan 24, 2021
@freakboy3742 freakboy3742 added question and removed enhancement New features, or improvements to existing features. labels Jan 24, 2021
@freakboy3742
Copy link
Member

freakboy3742 commented Mar 29, 2022

Closed on the basis that the question has been answered.

[Edit by @mhsmith: I think there used to be another comment above with an attempted answer, but it must have been deleted.]

@nasoma
Copy link

nasoma commented Jul 26, 2022

Does this method o centering a button still work. I have tried to center a button using the tip above but it defaults to left.:

    def startup(self):
        main_box = toga.Box(style=Pack(direction=COLUMN))
        name_label = toga.Label('Your name: ', style=Pack(padding=(0, 5)))
        self.name_input = toga.TextInput(style=Pack(flex=1))

        name_box = toga.Box(style=Pack(direction=ROW, padding=5))
        name_box.add(name_label)
        name_box.add(self.name_input)

        button = toga.Button('Say Hello!', on_press=self.say_hello, style=Pack(padding=(0, 10), font_size=15))

        btn_box = toga.Box(style=Pack(direction=ROW, padding=15, alignment=CENTER))
        btn_box.add(button)

        main_box.add(name_box)
        main_box.add(btn_box)

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

@mhsmith
Copy link
Member

mhsmith commented Jul 26, 2022

Simplified and self-contained version of the code above:

import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW

class HelloWorld(toga.App):

    def startup(self):
        main_box = toga.Box(style=Pack(direction=COLUMN))
        name_label = toga.Label('Your name: ')
        self.name_input = toga.TextInput(style=Pack(flex=1))

        name_box = toga.Box(style=Pack(direction=ROW))
        name_box.add(name_label)
        name_box.add(self.name_input)

        button = toga.Button('Say Hello!', style=Pack(font_size=15))
        btn_box = toga.Box(style=Pack(direction=ROW))
        btn_box.add(button)

        main_box.add(name_box)
        main_box.add(btn_box)

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()


def main():
    return HelloWorld()

With the current development version of Toga, it looks like this on Windows:

left

If I change the button to be a child of the top-level COLUMN box instead:

        button = toga.Button('Say Hello!', style=Pack(font_size=15))

        main_box.add(name_box)
        main_box.add(button)

Then it expands to fill the available width:

fill

The only way I found to center it without filling the width is to use a ROW box with some dummy views on each side:

        btn_box = toga.Box(style=Pack(direction=ROW, alignment=CENTER))
        btn_box.add(toga.Label("", style=Pack(flex=1)))
        btn_box.add(button)
        btn_box.add(toga.Label("", style=Pack(flex=1)))

image

I'm not sure if this is a limitation of the layout algorithm itself or a bug in its implementation on Windows. @freakboy3742: any thoughts?

@freakboy3742
Copy link
Member

@mhsmith The behavior you're seeing is consistent with macOS, so it's not Windows-specific.

I believe this is behaving as designed/documented; the key to that interpretation is the second paragraph of point 3 from the Pack specification. Parent elements have a final width set to 0, even if they have available width.

That said:

  1. There's clearly a limitation in terms of what users are expecting. The original request isn't unreasonable, and while the "add extra flex boxes either side" hack works, it's not ideal as a solution.
  2. I think I have found a bug around alignment handling - but in the other axis. If you set a height on a ROW box, alignment=CENTER should vertically centre the content. That doesn't currently work as described in the spec.

@GuiTaek
Copy link

GuiTaek commented Feb 17, 2023

Probably a new feature, you can just use text_align instead of alignment if you are working with Labels instead of Buttons

@mhsmith mhsmith changed the title How to make Button in the center? Improve alignment options Feb 17, 2023
@mhsmith
Copy link
Member

mhsmith commented Feb 17, 2023

I'm going to reopen this issue and merge from #1544 so the discussion is all in one place.

@mhsmith mhsmith reopened this Feb 17, 2023
@mhsmith
Copy link
Member

mhsmith commented Feb 17, 2023

Originally posted by @mhsmith in #1544

The Pack layout algorithm already supports using text_direction to control whether to lay ROW boxes out from ltr or rtl. So you might expect it to also support text_align to control the alignment of its children, but that isn't currently implemented. If it is implemented, perhaps text_align and text_direction should be renamed, since they wouldn't just be about text anymore.

Russell suggested that a more general approach to this area would be to formally define the mapping between the Pack layout properties and the equivalent CSS. If that was done, there would no longer be any need for a separate Pack algorithm specification. It would also act as a step towards our long-term goal of supporting CSS directly.

@mhsmith
Copy link
Member

mhsmith commented Feb 17, 2023

Originally posted by @freakboy3742 in #1544 (comment)

To be clear - while we might be able to repurpose text_align to perform centering, I'm not 100% sure text_align is the right fix here.

Convergence on a subset of CSS is definitely the preferred solution, though; the current formal specification of the Pack algorithm would be much better as a specification of the subset of CSS flags we honour, and the conditions under which we honour them. The Pack specification should be mostly this at present (it should mostly map to CSS3 flexbox, with the removal of any line splitting rules) - but there are clearly areas that we are missing, because horizontal centering can be handled by flexbox.

@mhsmith
Copy link
Member

mhsmith commented Feb 17, 2023

Originally posted by @shape-warrior-t in #1544 (comment)

My understanding is that currently, the final width of a row box with children is always the sum of its children's full widths, and same thing with the column boxes in the other dimension.

In other words, right now, every row box is always left, center, and right aligned already, and same with the column boxes and vertical alignment.

So in order to implement this, we'll probably have to change how we determine the final width of a parent element. A change that may or may not be backwards-compatible, depending on how we go about it.

And if we change how the final width of a parent element is determined, we might also want to do the same thing with the final height. Right now, as far as I can tell, the final height of a row box is always the maximum of its children's full heights, and same with column boxes and width. I don't think the behaviour described in #1194 (comment) is actually a bug under current specifications -- if you have a row box with one child, then the final height of the row box will always be the same as the child's full height, so the row box will always already be top, center, and bottom aligned.

(I just noticed -- the documented Pack algorithm doesn't actually say what the final height of a parent row box / final width of a parent column box is! So... maybe it actually is a bug...?)

My brain currently needs a break from thinking, so I'm not going to propose anything right now. Maybe later. Just wanted to bring this issue up, because I think it's probably important.

@mhsmith
Copy link
Member

mhsmith commented Feb 17, 2023

Originally posted by @shape-warrior-t in #1544 (comment)

OK, one potential idea: CSS uses align-items for cross axis alignment and justify-content for main axis alignment, so maybe we could copy them with alignment (we already have that property, though we specify a specific direction instead of flex-start/flex-end) and justify or justification or something along those lines (new property).

@mhsmith
Copy link
Member

mhsmith commented Feb 17, 2023

Originally posted by @freakboy3742 in #1544 (comment)

As I said in my previous comment, my preferred approach here is not to introduce new properties, or custom interpretations - but to converge on a true "minimal CSS" interpretation - that is, a set of rules that are 100% consistent with true CSS, or have a 1-1 mapping with true CSS. Essentially, I would like to be able to answer any "is this a bug in Pack?" questions with "what does a HTML render of the same code do?".

@freakboy3742
Copy link
Member

Having spent some time with Quality Time (tm) with Pack this week, I can add 2 things to this discussion:

  1. There definitely is a class of bug around vertical alignment, stemming from how the root element of the app layout is treated with respect to the window/viewport. The patch in Add a formal mapping of Pack->CSS #1778 fixes the problem for GTK; a similar set of fixes will be needed for other backends.
  2. The issue described by this ticket (aligning buttons) has more to do with the definition of Button. Buttons are currently defined as having an "intrinsic" size that is as wide as the container they are in (technically - at_least(min_size)). There's no way to divorce the rendering of the widget from the layout of the box that contains the widget. However, buttons have a fixed intrinsic vertical height; so if the layout defines more space that the button requires, vertical alignment inside the box is taken into account.

On that basis, I think the issue isn't actually with Pack here. The box being allocated is the right size; the issue is what Button does within that box. We don't currently have a way to stylistically tell Button that it doesn't have a flexible intrinsic width. I'm not sure there's a good analog for this in CSS; this is almost a "lower level than CSS" problem.

The same core issue exists with Label. However, in that context, a label that is the width of the screen is mostly rendered as the background color, rather than "button body"; text-alignment gets you the rest of the way, so the problem isn't as visually apparent.

Four possible fixes I can see:

  1. Do nothing; call this intended behavior, and recommend the Box hack described above.
  2. Modify Button so that it doesn't have an intrinsically flexible width.
  3. Add a property to Button that defines whether it expands to fit, or is the minimum possible size.
  4. Add A new "cross-axis flex" style attribute; effectively (3), but at the level of Pack.

I could almost be convinced that (2) is the right answer. The current behaviour is likely a consequence of the "desirable" behaviour in Toga Tutorial 0 and 1, rather than a decision that is rooted in adherence to platform UX style guides. However - changing this would obviously be a huge backwards incompatibility. It would also mean there isn't a way to define an "as wide as the window" button; which some users may want, despite UX guidelines suggesting buttons shouldn't be used like that.

@proneon267
Copy link
Contributor

Based on @mhsmith 's box hack, I have extended it to implement all the values of alignment without using alignment pack style property itself. It is using a custom function:def CreateWidget(WidgetType,Position,**kwargs):.

Works great but more toga.Box() are involved. Since, toga.Box() seems to be modeled around <div>, I would love to see the css style flexbox options to become available. They would really simplify the layout design.

@mhsmith
Copy link
Member

mhsmith commented Feb 18, 2023

Yes, I think increasing the number of supported CSS attributes is the way to go here, and #1778 has already made a start at defining Pack in terms of CSS. And based on that definition, the current behavior is almost correct:

  • In the first example above, the button is in a row box. To support center alignment here, we would need an equivalent to CSS justify-content. Its default value in a flexbox container is start, which is consistent with the current behavior: the button is the minimum size that fits its label, and is left-aligned.
  • In the second example, the button is in a column box. To support center alignment here, we would need an equivalent to CSS align-self. Its default value is taken from align-items on the parent, and the default value of that is stretch, which is consistent with the current behavior: the button fills the available width.

Where I think we diverge from CSS is the way we implement alignment, which #1778 maps to align-items. If I take the second example and setalignment to left, center or right on the column box, it makes no difference: the children continue to be stretched to the full width. This may be consistent with the current Pack specification, but I don't think it's consistent with the proposed mapping to CSS.

@freakboy3742
Copy link
Member

Yes, I think increasing the number of supported CSS attributes is the way to go here, and #1778 has already made a start at defining Pack in terms of CSS. And based on that definition, the current behavior is almost correct:

  • In the first example above, the button is in a row box. To support center alignment here, we would need an equivalent to CSS justify-content. Its default value is start (MDN incorrectly says stretch), which is consistent with the current behavior: the button is the minimum size that fits its label, and is left-aligned.

justify-content is an interesting idea; however, we need to be careful about how it's interpreted. justify-content: stretch/start works well to describe what how Buttons behave in the row axis; but it doesn't in the column axis.

If you take the example where btn_box is a column box, and make that box flex:1, the box containing the button fills the remainder of the page. If you then make the button flex:1 as well, the button fills the remainder of the page as well. That doesn't result in buttons that are good matches for UX guidelines. You could argue that vertically stretching buttons is internally consistent, and the equivalent of what happens if you use an actual HTML <button> instead of pretending a div is a button), and just encourage putting buttons in row boxes... but buttons on macOS that are the wrong height make my teeth itch...

There will be analogous situations with other widgets (e.g., ActivitySpinner) that have a base fixed size, and don't have any meaningful "stretch" behavior. The handling of <input type="radio"> might be the analog here - the widget has an intrinsic size, and doesn't honour any "stretch"-based layouts.

Where I think we diverge from CSS is the way we implement alignment, which #1778 maps to align-items. If I take the second example and setalignment to left, center or right on the column box, it makes no difference: the children continue to be stretched to the full width. This may be consistent with the current Pack specification, but I don't think it's consistent with the proposed mapping to CSS.

I think the discrepancy here has more to do with the at_least handling in rehint(), rather than Pack itself.

An idle thought - what we need here is a Hypothesis test harness that can run comparisons of randomly generated HTML markups with a specific CSS mapping scheme and compare Pack's layouts with native CSS rendering...

@mhsmith
Copy link
Member

mhsmith commented Feb 20, 2023

If you take the example where btn_box is a column box, and make that box flex:1, the box containing the button fills the remainder of the page. If you then make the button flex:1 as well, the button fills the remainder of the page as well. That doesn't result in buttons that are good matches for UX guidelines.

Do you mean this is what the CSS mapping would do, or what Pack currently does? I tried both of the following structures with Pack on Cocoa, and neither of them stretched the button vertically:

  • COLUMN > Button(flex=1)
  • COLUMN > COLUMN(flex=1) > Button(flex=1)

One possible fix for the CSS mapping would be that if rehint returns a fixed height, then the widget gets height and max-height set to the same value. It looks like max-height sets a limit to how much flex will take effect.

@mhsmith
Copy link
Member

mhsmith commented Feb 20, 2023

I've accidentally discovered another way of centering a button:

    def startup(self):
        main_box = toga.Box(style=Pack(direction=COLUMN, alignment="center"))
        name_box = toga.Box(style=Pack(direction=ROW))

        button = toga.Button('Say Hello!', style=Pack(font_size=15))
        btn_box = toga.Box(style=Pack(direction=ROW))
        btn_box.add(button)

        main_box.add(name_box)
        main_box.add(btn_box)

        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()

Screenshot 2023-02-20 at 08 08 00

But if I comment out main_box.add(name_box), then the button goes back to the left.

I guess this is a variation of what was mentioned in #1778: name_box has no children, so it's taking the full width available to it, and that creates some space within which btn_box can be centered. Left and right alignment works too. But this only works if btn_box is a ROW: if it's a COLUMN, then the button fills the the width of the window.

@freakboy3742
Copy link
Member

If you take the example where btn_box is a column box, and make that box flex:1, the box containing the button fills the remainder of the page. If you then make the button flex:1 as well, the button fills the remainder of the page as well. That doesn't result in buttons that are good matches for UX guidelines.

Do you mean this is what the CSS mapping would do, or what Pack currently does? I tried both of the following structures with Pack on Cocoa, and neither of them stretched the button vertically:

I was referring to what a CSS rendering generates for a div in an equivalent layout, which is what the reference rendering would become if we adopted this. The current Pack implementation sets a fixed intrinsic height on the Button, which then constraints the height of the box that contains it.

One possible fix for the CSS mapping would be that if rehint returns a fixed height, then the widget gets height and max-height set to the same value. It looks like max-height sets a limit to how much flex will take effect.

I think I agree in spirit, but not quite in implementation. In terms of Pack, rehint() is setting the intrinsic size of the widget, which isn't a literal CSS property; the intrinsic height is then combined with CSS declarations to generate the final height. The radio button example indicates there's something analogous going on internal to CSS, although a HTML button clearly doesn't define a fixed intrinsic height. In terms of our mapping, we haven't quite got the Pack interpretation consistent with CSS behavior.

@bompaization
Copy link

bompaization commented Mar 13, 2023

Based on @mhsmith 's box hack, I have extended it to implement all the values of alignment without using alignment pack style property itself. It is using a custom function:def CreateWidget(WidgetType,Position,**kwargs):.

Care to share?

@freakboy3742 freakboy3742 added the enhancement New features, or improvements to existing features. label Jun 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New features, or improvements to existing features.
Projects
None yet
Development

No branches or pull requests

7 participants