beeware / toga

A Python native, OS native GUI toolkit.
https://toga.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
4.3k stars 668 forks source link

Ability to set label's opacity independent of color #2830

Closed hyuri closed 2 weeks ago

hyuri commented 2 weeks ago

What is the problem or limitation you are having?

I'm trying to establish a visually hierarchical relationship between some labels — namely: faded smaller text under full opacity larger ones. Like customized headings.

But right now I can only change a label's opacity if I also change its color, which, with the whole light/dark mode system, breaks my app. For example: if I set my H1 to rgba(0, 0, 0, 1) (pure black) and H2 to rgba(0, 0, 0, 0.7) (faded black), it works in light mode but breaks (can't see the text) in dark mode and vice-versa.

Describe the solution you'd like

Ability to set a label widget's opacity independently of color, like in CSS. So the color is inherited from the system, but the opacity is changed. This way, no matter what color the system sets the text — white or black — it will work in either light/dark mode and the relationship between H1 and H2 will be maintained.

Examples: Pack(color=black, opacity=1) Pack(color=black, opacity=0.7)

Describe alternatives you've considered

Add platform-native heading widgets to Toga — is there such a thing in desktops? So, instead of only creating Label, we could create H1, H2, H3, like in HTML.

Additional context

No response

freakboy3742 commented 2 weeks ago

I'm going to close this as a WONTFIX.

Toga uses a CSS-like style language. CSS doesn't provide the ability to "just change alpha"; you set the entire color. On that basis, the specific feature request isn't consistent with the design of Toga as a whole.

Stepping back from the specific request to the general use case: Pack is a deliberately simple style language; it's a minimal implementation of CSS that is enough to get going. Broadly speaking, I'm very hesitant to add new features to Pack, because it takes the oxygen out of what should be the real fix: getting Colosseum - a true CSS3 implementation - to the point where it's a viable replacement for Pack. Once there's a full CSS3 implementation, options like @media (prefers-color-scheme:...) and light-dark() become possible. That doesn't mean I won't support adding new features to Pack - but there's a high bar for acceptance.

In this case: If you're micro-optimising the colours of your Toga app, you're doing it wrong. Toga is a widget toolkit that goes to great lengths to present native user interfaces. The native color scheme is part of that user interface. Operating system vendors spend a lot of time designing the shape and color of their widget sets; when app designers fiddle with those colours, they are doing their users a disservice, because they are breaking an axis of consistency that exists as a usability affordance.

Using colors as a basis for differentiation in a UX is a bad idea. Look at all the official platform apps - and name one that uses color as the basis for feature differentiation. There's a reason for this - it's bad for accessibility. My son, for example, can't differentiate color shades - he requires special consideration in exams because lecturers set exam papers where subtle shades of grey are imparting important examination material, and he literally can't see the material he's being asked a question about.

There are limited cases where changing colours can be are useful - however, they usually in the context of drawing attention to specific elements (like buttons) - and those color changes are probably better served with a "role" helper (e.g., make this button a CANCEL button, indicating it should be rendered in a shade of red), and shouldn't be the sole determinant of UX elements. If you're at the point where you're concerned that your heading choices are conflicting with dark vs light mode, you're way outside that remit. Changing opacity is way outside that remit.

From other conversations on Discord, I'm led to believe you're writing a game. Toga isn't a not good match for that use case. Toga is deliberately designed to not give you micro-control over specific appearance of an app. It's designed to present an app that looks native - which by definition will look different on every platform. If you're trying to build a game, I'd suggest looking to other toolkits. Kivy would be my first suggestion, as it's philosophically the opposite to Toga - it explicitly tries to create the same appearance on every platform, which is a lot closer to the use case for games. However I can't comment any anything about Kivy beyond that, or on approaches for writing games more generally, as that's not a problem I've spent much time trying to solve.

hyuri commented 2 weeks ago

I'm not writing a game at this point. This feature request is for an unrelated regular app I'm writing first, and I certainly won't use Toga for any game graphics later.

Opacity Property in CSS

Are you sure CSS doesn't allow setting opacity independently? This seems to suggest otherwise: https://www.w3schools.com/cssref/css3_pr_opacity.php

And I've just tested it on this page: image

Platform Examples

What I'm asking to be able to do here is not only supported but literally what Apple recommends in their Human Interface Guidelines for all of their platforms: image image Source: https://developer.apple.com/design/human-interface-guidelines/typography

Probably all of Apple apps use this design pattern for differentiation, including their website:

Finder (macOS)

image

App Store (macOS)

image

Apple's Website (Web)

image

In fact, even BeeWare's monthly update emails use this pattern:

image

Larger, darker, bolder text for title; smaller, lighter, thinner text for paragraph.

It's everywhere and a pretty well-known and well-established design pattern. Not controversial at all. I'm surprised how opposed you are to this, especially when even BeeWare's monthly update emails follow this pattern.

It's good to know of cases like your son so we can include more people when designing our GUIs, but I don't think making it more difficult to use a common design pattern is how we solve that.

The Better Method

I totally agree that the Colosseum solution — full CSS implementation — is much better. I also suggested, in the "alternative solution" field, that the platform providing these pre-defined headings is another nice solution — and probably much better — but we just don't have that available right now, so this fact simply prevents us from cleanly implementing a well-known design pattern, or makes our code more complicated than it has to be.

I think it's totally understandable if you want to leave the old Pack alone, but the other points you raised are moot.

freakboy3742 commented 2 weeks ago

Opacity Property in CSS

Are you sure CSS doesn't allow setting opacity independently? This seems to suggest otherwise: https://www.w3schools.com/cssref/css3_pr_opacity.php

I'll admit that despite your original post suggesting opacity specifically, I mentally connected with "alpha on color", rather than a standalone opacity control.

That said - opacity on an element and the alpha of a color of an element are slightly different, especially when you're referring to any widget other than text or backgrounds. What does a "50% opaque" button look like? It's very different to a button whose color has 50% alpha.

Platform Examples

What I'm asking to be able to do here is not only supported but literally what Apple recommends in their Human Interface Guidelines for all of their platforms:

I can't argue the Apple HIG says this. However, I'd also suggest that opacity or alpha isn't what they're referring to - they're referring to contrast and base color. Tools like SASS and SCSS that offer lighten() and darken() operations don't touch opacity - they modify the underlying color; and the solution in pure CSS is to use HSL, not RGB - which Pack supports.

Probably all of Apple apps use this design pattern for differentiation, including their website:

An app isn't a website. I challenge the assertion that it's a design pattern that is used extensively in apps, outside of a specific subset that are really closer to being websites (Apple Music and the App Store being two notable examples).

Finder (macOS)

Using opacity at all, and using hand customised opacity on specific text elements are different things entirely.

Yes, in this example the finder is using opacity to highlight a sidebar item. That's a feature of a "menu like" widget that macOS provides. You don't need to do any customisation of colours there - you use the system widget, and the colors work. Menus, for example, also use a change of color and opacity to demonstrate which menu item is currently selected. However, Toga doesn't expose any mechanism to change the color of menu items.

Toga isn't a good option toolkit for building entirely new widgets. It's a toolkit that focuses on wrapping existing system widgets (providing reasonable shims when a platform doesn't have a native representation for a concept). This is primarily because new widgets need to reflect local system conditions, and that's not something that can really be done cross-platform. Many of these widgets use opacity in various ways to provide highlighting or to indicate state.

App Store (macOS)

It's almost arguable whether the App Store is a website or an app. The content you're highlight here is almost all HTML, not an app.

Apple's Website (Web) ... In fact, even BeeWare's monthly update emails use this pattern:

Again, a website isn't an app.

It's everywhere and a pretty well-known and well-established design pattern. Not controversial at all. I'm surprised how opposed you are to this, especially when even BeeWare's monthly update emails follow this pattern.

I agree this is true - on websites. It's much less prevalent in apps, especially when you exclude apps where the thing being displayed is "web" content (like the App Store). For example, I can only think of 1 location where a non-default font size is used in the entire macOS Settings app (the "About this Mac" page) - and there's no changes in color anywhere, outside of the system default colors for section headings (which, again, are system default sizes).

The Better Method

I also suggested, in the "alternative solution" field, that the platform providing these pre-defined headings is another nice solution — and probably much better — but we just don't have that available right now, so this fact simply prevents us from cleanly implementing a well-known design pattern, or makes our code more complicated than it has to be.

We don't have opacity or alpha-only color selection either. We do have the ability to set colors on widgets (including text), using both RGB, and HSL, so it is possible to do all the things that you've described in your response. It's not easy to set colours that work in both dark and light mode - but then, there's a lot of features that Toga doesn't have. Comparing one solution that doesn't exist with another solution that also doesn't exist isn't really a fair comparison.

I closed this ticket because it was a specific requested for a specific feature that I don't consider to be in scope for Pack, for the reasons I've outlined.

If you take this as a more general discussion about a possible feature, then (a) for the examples you've provided in support of your case, you don't want opacity - you want "darken()/lighten()"; and (b) the use case you originally reported also needs the ability to query whether an app is in dark or light mode so that color selection can adapt accordingly. As such, they should be separate tickets - they are two distinct features, that could be delivered independently, neither of which is adding the "ability to set alpha independent of color". Both of these features would make sense to me. darken()/lighten() is a straightforward utility that could exist on any color; and while the long game for supporting light/dark mode is that it should be managed by CSS media selectors, any implementation of that will require some API on the app to detect what the current mode is, so it makes sense to add a toga.App.dark_mode readonly property (or similar).

hyuri commented 2 weeks ago

Sorry, I mixed up text and widget. My concern is text but I think it has to be about the widget's opacity in this case. I understand your point that allowing us to change the opacity of widgets would dip into the realm of enabling arbitrary customizations and breaking the idea of Toga, but I think what I'm proposing here doesn't do that because it's only about the label widget to make it easier to follow certain design patterns or platform typography guidelines.

I can't argue the Apple HIG says this. However, I'd also suggest that opacity or alpha isn't what they're referring to - they're referring to contrast and base color.

That is true, they only mention color and contrast — and size —, but those are relative to the background. Reducing a label's opacity does change its color and contrast in relation to the background and other text with 100% opacity so it's a valid adjustment for that purpose. It's one technique of a few. The beauty of changing opacity is that it is a one value change that semantically establishes a hierarchical relationship — like "paragraph, be less prominent than the title" — and maintains that relationship no matter what the text or background colors are. This is also mentioned in Apple's HIG.

For example, I can only think of 1 location where a non-default font size is used in the entire macOS Settings app (the "About this Mac" page) - and there's no changes in color anywhere, outside of the system default colors for section headings (which, again, are system default sizes).

In Ventura's Settings app, this is almost everywhere: The "Apple ID" text under you name and photo is slightly smaller and faded.

In the Control Center pane:

image image

Siri & Spotlight:

image

Privacy & Security:

image

Display:

image

And more (I'm not gonna screenshot and list them all).

Plus, multiple other places in Finder, like in Gallery view:

image

Quick Look:

image

And Finder isn't even the best kind of app for an example... it depends on how prominent, front and center typography is in the given app.

In the end, I don't understand the implication here... "Wait for Apple to release the multiple heading labels in different shades, and keep breaking their HIG until then"? I mean, they explicitly tell us to test and make sure text color and contrast are legible in light and dark mode. The opacity solution simply makes that much easier as a smarter approach.

The only alternative to not ignoring Apple's typography guidelines is making the code messier via hardcoding different colors for light and dark mode, using a bunch of conditionals, and probably updating that mess every time Apple tweak their light/dark mode colors a bit.

But if you don't want to touch Pack, I guess we'll have to wait for Colosseum.

You said there was a "system default sizes for section headings"... are they accessible to us in Toga? And what about the HSL? I looked through the docs and couldn't find how to use it.

freakboy3742 commented 2 weeks ago

For example, I can only think of 1 location where a non-default font size is used in the entire macOS Settings app (the "About this Mac" page) - and there's no changes in color anywhere, outside of the system default colors for section headings (which, again, are system default sizes).

In Ventura's Settings app, this is almost everywhere:

Ok - these aren't as visually obvious to me in light mode; I concede that these examples exist.

In the end, I don't understand the implication here... "Wait for Apple to release the multiple heading labels in different shades, and keep breaking their HIG until then"? I mean, they explicitly tell us to test and make sure text color and contrast are legible in light and dark mode. The opacity solution simply makes that much easier as a smarter approach.

I fundamentally disagree that opacity is the easier approach. It might be easier for the end user under some circumstances, but it would be extremely complex to implement, because the background can have opacity, and the background of the background can have opacity...

It's much more complex to compute than darken()/lighten() on a single color.

The only alternative to not ignoring Apple's typography guidelines is making the code messier via hardcoding different colors for light and dark mode, using a bunch of conditionals, and probably updating that mess every time Apple tweak their light/dark mode colors a bit.

I'd argue that if you're customising colors, explicitly specifying a light and dark color is the right approach regardless.

And it's hardly "a bunch of conditionals" - it is, at worst, one conditional per color; and if you're smart about it, you'll define a single utility method that does "darken on light mode, lighten on dark mode" in a single call, meaning you'll have exactly 1 comparison in your entire codebase. I could even see that method being a good companion to darken/lighten, in the same way that there are independent ways to differentiate "Left" from "Primary" when specifying direction to accomodate RTL languages.

But if you don't want to touch Pack, I guess we'll have to wait for Colosseum.

Or, as I indicated in my previous post, implement darken() and lighten(), and an attribute to get the current dark/light mode status.

I'm having difficulty working out why you're advocating so strongly that Opacity Is The Only Way To Do This, and in the process ignoring an alternative that is (a) vastly simpler to implement, (b) not that much harder to use, and (c) something that I've already indicated I'm inclined to support.

And unless you implement it yourself, you're going to be waiting regardless, because I have a lot of other items on my TODO list that are significantly higher priority for me personally than modifying text colors.

You said there was a "system default sizes for section headings"... are they accessible to us in Toga? And what about the HSL? I looked through the docs and couldn't find how to use it.

I'm not sure what you're referring to - the "default sizes" I was referring to were the default font size of label widgets. The default sizes used by Toga reflect the system defaults; the size is also exposed as the constant toga.fonts.SYSTEM_DEFAULT_FONT_SIZE (with a literal value of -1).

As for hsl - all the color handling definitions come from Travertino, but exposed through toga.colors.

Admittedly, both of these aren't documented well (or... at all, apparently...)

hyuri commented 2 weeks ago

I fundamentally disagree that opacity is the easier approach. It might be easier for the end user under some circumstances, but it would be extremely complex to implement [...]

Yes, I acknowledge I'm asking for a feature that would make things easier for us Toga users when writing an app, not necessarily you Toga developers to implement. That's the crust of the confusion here. From a user's perspective, this is a no-brainer to implement because "implementing" means changing one property — remember we have this in CSS — so this is the simpler approach. I think you are thinking of "implementing" from a Toga developer's perspective, which I guess means adding a compositing stack to Toga and rewriting a lot of code, so it seems like the harder approach.

Anyways, one could argue it's extremely hard to make a multi-platform Python-native GUI toolkit that lets you avoid creating different versions of your app and multiple conditionals to accommodate for an "iOS mode" and an "Android mode", yet here you folks are making wonders 😉 I think we can draw a parallel here between "one codebase, multiple apps" and "one label style, multiple modes (as in light/dark modes)".

But leave it for Colosseum then, if ever, and whenever it happens. It's not a deal breaker, it's just less convenient. I didn't realize it was going to be so hard to implement.

I'm having difficulty working out why you're advocating so strongly that Opacity Is The Only Way To Do This, and in the process ignoring an alternative that is (a) vastly simpler to implement, (b) not that much harder to use, and (c) something that I've already indicated I'm inclined to support.

I'm not. I said it's one of a few known techniques:

image

But it is the simpler method (for us writing apps). I wouldn't need any extra methods, no light() or dark(), no conditionals... I would be able to simply set a label's opacity — one property change — and it would automatically work in both light and dark mode.

In any case, if we can at least have lighten() and darken(), that would definitely be much better already.

I'd argue that if you're customising colors, explicitly specifying a light and dark color is the right approach regardless.

The point is exactly that I don't want to customize colors. I want to leave the system-provided colors intact, and just dim them a bit, to establish visual hierarchy.

I had the impression the foundation was there in Toga (because we already have some opacity control, with colors) so it wouldn't be that hard, but at this point it's clear it's hard and low priority, so I guess I should just follow your suggestions and implement my own local solution.

Can I detect system light/dark mode via Toga? Or do I need to use another library or the native APIs?

I'm not sure what you're referring to - the "default sizes" I was referring to were the default font size of label widgets. The default sizes used by Toga reflect the system defaults; the size is also exposed as the constant toga.fonts.SYSTEM_DEFAULT_FONT_SIZE (with a literal value of -1).

What do you mean by "sizes"? Doesn't Toga only have one Label that you can set one size — or get the one default size? Or are you talking about the each default size that each platform offers for that one Label?

Maybe I misunderstood what you said. I was referring to what you said here, which seemed to indicate platform-provided defaults for h1, h2, h3 etc.:

image

Toga's Label doesn't provide us with system default sizes for headings to choose from, does it? I'll probe toga.fonts to check, but I found no mentions of this in the docs.

As for hsl - all the color handling definitions come from Travertino, but exposed through toga.colors.

Admittedly, both of these aren't documented well (or... at all, apparently...)

Ok, I'll poke around and see what I can do. Thanks.

freakboy3742 commented 2 weeks ago

I'd argue that if you're customising colors, explicitly specifying a light and dark color is the right approach regardless.

The point is exactly that I don't want to customize colors. I want to leave the system-provided colors intact, and just dim them a bit, to establish visual hierarchy.

Ok - so the actual problem is that you also need access to the default system color - or rather, colors, because there's potentially a lot of them. Here's the set available on macOS; we'd need to find a set of colors that are sufficiently common across platforms that we can find a good interpretation across platforms. Part of that would then be providing per-widget attributes so you can get the default system color for that widget (e.g, a Label defaults to "primary text color", or whatever name we end up with).

Can I detect system light/dark mode via Toga? Or do I need to use another library or the native APIs?

At present, no. That's why I've suggested that there is a need to add a property on the app to detect the current light/dark mode status.

Maybe I misunderstood what you said. I was referring to what you said here, which seemed to indicate platform-provided defaults for h1, h2, h3 etc.:

No, there's a single SYSTEM_DEFAULT_FONT_SIZE, but no analog of "h1" sizing etc. However, there's been discussion about adding support for relative font sizing keywords (#1814) which could be used to populate "h1"-type labels with a larger font that is adaptive to local conditions.

hyuri commented 2 weeks ago

[...] Part of that would then be providing per-widget attributes so you can get the default system color for that widget (e.g, a Label defaults to "primary text color", or whatever name we end up with).

[...] However, there's been discussion about adding support for relative font sizing keywords (https://github.com/beeware/toga/issues/1814) which could be used to populate "h1"-type labels with a larger font that is adaptive to local conditions.

Both would be excellent.