Avalonia
Avalonia copied to clipboard
Feature/media queries
What does the pull request do?
Prototype for implementation of css like media queries.
Essential to support multiple form factors.
What is the current behavior?
What is the updated/expected behavior with this PR?
How was the solution implemented (if it's not obvious)?
Checklist
- [ ] Added unit tests (if possible)?
- [ ] Added XML documentation to any related classes?
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Documentation with user documentation
Breaking changes
Obsoletions / Deprecations
Fixed issues
You can test this PR using the following package version. 0.10.999-cibuild0019708-beta
. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json) [PRBUILDID]
You can test this PR using the following package version. 0.10.999-cibuild0019738-beta
. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json) [PRBUILDID]
I certainly think this is useful in some cases. However, I'm a big fan of fully adaptive/responsive UI's that adjust based on resolution or orientation though. This type of functionality in my mind is needed for less-powerful UI frameworks. XAML is resolution independent, fully vector-based and composable. You can generally-speaking design a good UI that works everywhere and automatically adjusts itself.
It's a great addition, but I'd like to suggest an improvement
eg:
<Style Selector=":min-width(600) TextBlock#MinWidth">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":max-width(600) TextBox">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=":max-width(600) Rectangle">
<Setter Property="Fill" Value="Red" />
</Style>
<Style Selector=":min-width(800) TextBlock#MinWidth">
<Setter Property="Foreground" Value="Blue" />
</Style>
<Style Selector=":max-width(800) TextBox">
<Setter Property="Foreground" Value="Blue" />
</Style>
<Style Selector=":max-width(800) Rectangle">
<Setter Property="Fill" Value="Blue" />
</Style>
....
will became:
<Media Selector=":min-width(600)">
<Style "TextBlock#MinWidth">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector="TextBox">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=" Rectangle">
<Setter Property="Fill" Value="Red" />
</Style>
</Media>
<Media Selector=":min-width(800)">
<Style Selector="TextBlock#MinWidth">
<Setter Property="Foreground" Value="Blue" />
</Style>
<Style Selector="TextBox">
<Setter Property="Foreground" Value="Blue" />
</Style>
<Style Selector="Rectangle">
<Setter Property="Fill" Value="Blue" />
</Style>
</Media>
....
Other possible MediaSelector:
Selector | Description |
---|---|
resolution | select the style based on the DPI |
media-type | select the style based on media type (screen,printer, ecc.) |
orientation | The orientation of the viewport (landscape or portrait mode) |
light-level | Current ambient light level (added in Media Queries Level 4) |
aspectratio | The Current aspect ratio (4:3,16:9,...) |
This should just work
<Style Selector=":min-width(600)">
<Style "TextBlock#MinWidth">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector="TextBox">
<Setter Property="Foreground" Value="Red" />
</Style>
<Style Selector=" Rectangle">
<Setter Property="Fill" Value="Red" />
</Style>
</Style>
<Style Selector=":min-width(800)">
<Style Selector="TextBlock#MinWidth">
<Setter Property="Foreground" Value="Blue" />
</Style>
<Style Selector="TextBox">
<Setter Property="Foreground" Value="Blue" />
</Style>
<Style Selector="Rectangle">
<Setter Property="Fill" Value="Blue" />
</Style>
</Style>
I certainly think this is useful in some cases. However, I'm a big fan of fully adaptive/responsive UI's that adjust based on resolution or orientation though. This type of functionality in my mind is needed for less-powerful UI frameworks. XAML is resolution independent, fully vector-based and composable. You can generally-speaking design a good UI that works everywhere and automatically adjusts itself.
From my perspective it is quite hard to create truly responsive controls in pure XAML. For example TextBox
that shows/hides extra buttons based on available space. So this media query setup could be quite useful to remove code and move such behavior to styles. This is still no as flexible as doing this in C# though since there visual tree can be patched easily to adapt control to given size constraint.
So this media query setup could be quite useful to remove code and move such behavior to styles. This is still no as flexible as doing this in C# though since there visual tree can be patched easily to adapt control to given size constraint.
Yea, this can certainly be useful and I'm not against it all. I guess my point was more it isnt a blocker for supporting multiple devices and form factors. I generally found that there was a solution by rethinking designs and your app at a fundamental level. XAML with minimal C# code-behind is already powerful enough to do everything needed.
A desktop XAML app was made to work with mobile with the thinking I'm describing here. The only layout changes were triggered based on orientation and pixel size of the window -- very simple and done in code behind. It turned out quite well.
That said, the examples above based on width/height are good ones. AdaptiveTriggers in UWP were useful in several cases. I'm sure I can find a use for this too.
FYI: CSS spec for container queries: https://github.com/w3c/csswg-drafts/issues/5796
I had another thought about this. It's common for many views to share the same trigger width/height in pixels when the layout should change. Is there any way to declare a resource for width/height and then use it in a selector (I know the answer to this is no...) but is it possible in the future? Should it be?
<Style Selector=":min-width({StaticResource MinWindowWidth})">
Doing this would greatly help updating many views that use this type of selector.
With CSS syntax it's a bit of a pain. Yet another reason to split "selector" and "media-query" (as it is in CSS), so we will be able to write:
<Style MediaQuery="min-width(600) and pointer(mouse)" Selector="Border" />
as well as
<Style Selector="Border">
<Style.MediaQuery>
<AndMediaQuery>
<!-- Any other non-bindable markup extension should work here without any problem -->
<SizeMediaQuery MinWidth="{StaticResource MinWindowWidth}" />
<PointerMediaQuery PointerType="Mouse" />
</AndMediaQuery>
</Style.MediaQuery>
</Style>
Which also can be extended with custom MediaQuery types (like it's possible with AdaptiveTriggers).
CSS seems to not support variables in media queries, but only environment variables (consts), see https://github.com/w3c/csswg-drafts/issues/1693
Perhaps it is better to shift the conversation into a discussion so as not to dirty the PR.
With CSS syntax it's a bit of a pain. Yet another reason to split "selector" and "media-query" (as it is in CSS), so we will be able to write:
<Style MediaQuery="min-width(600) and pointer(mouse)" Selector="Border" />
as well as
<Style Selector="Border"> <Style.MediaQuery> <AndMediaQuery> <!-- Any other non-bindable markup extension should work here without any problem --> <SizeMediaQuery MinWidth="{StaticResource MinWindowWidth}" /> <PointerMediaQuery PointerType="Mouse" /> </AndMediaQuery> </Style.MediaQuery> </Style>
Which also can be extended with custom MediaQuery types (like it's possible with AdaptiveTriggers).
CSS seems to not support variables in media queries, but only environment variables (consts), see w3c/csswg-drafts#1693
I don't see the need to add a new attribute when it's really nothing more than a selector. The change I would like to propose to you is to add Selector to StyleInclude. So you can use it like this:
<StyleInclude Selector="max-width(600)" Source="avares://MyApp/Assets/screen_small.xaml"/>
<StyleInclude Selector="max-width(1024)" Source="avares://MyApp/Assets/screen_medium.xaml"/>
<StyleInclude Selector="min-width(1280)" Source="avares://MyApp/Assets/screen_high.xaml"/>
JFYI: This is just an initial prototype of how to make the functionality work, and its expected that the final syntax of media queries would be seperated from selectors.
We have identified this is a 2-stage process.
- implement the parsing and infrastructure / logic to implement queries themselves.
- implement child styles
JFYI: This is just an initial prototype of how to make the functionality work, and its expected that the final syntax of media queries would be seperated from selectors.
Why this choice? I don't see any difference between MediaQuery and Selector, they both allow you to activate / deactivate a style based on a condition. Adding an extra attribute would increase cofusion.
<Style Selector="Border"> <Style.MediaQuery> <AndMediaQuery> <!-- Any other non-bindable markup extension should work here without any problem --> <SizeMediaQuery MinWidth="{StaticResource MinWindowWidth}" /> <PointerMediaQuery PointerType="Mouse" /> </AndMediaQuery> </Style.MediaQuery> </Style>
Greats idea of syntax could also be extended to selector. It would make the approach easier for those like me who do not have a web background and at the beginning find it difficult to read and write a Selector.
Closing as inactive. Just in case, it's still something we want one day. Potentially can be added in 11.x as it doesn't introduce breaking changes.
You can test this PR using the following package version. 11.0.999-cibuild0036310-beta
. (feed url: https://pkgs.dev.azure.com/AvaloniaUI/AvaloniaUI/_packaging/avalonia-all/nuget/v3/index.json) [PRBUILDID]
You can test this PR using the following package version. 11.0.999-cibuild0036334-beta
. (feed url: https://pkgs.dev.azure.com/AvaloniaUI/AvaloniaUI/_packaging/avalonia-all/nuget/v3/index.json) [PRBUILDID]
CSS features that we need (some might be added over time):
Client area size (i.e. direct top-level content size):
@media (width: 360px) // exact
@media (min-width: 35rem)
@media (max-width: 50rem)
@media (400px <= width <= 700px)
Or deprecated alternative, which does the same:
@media (device-width: 360px) // exact
@media (min-device-width: 35rem)
@media (max-device-width: 50rem)
In our case we really should distinct screen size from top-level client area size. On mobile, it won't be a problem. On desktop, it will, as window can take only small part of the screen.
Control size:
@container (width > 400px)
@container (width > 400px) and (height > 400px)
@container sidebar (width > 400px)
Important: CSS doesn't use "media" keyword or terminology when it's about styling specific control/container.
Orientation
@media (orientation: landscape)
@media (orientation: portrait)
Pointer type:
@media (pointer: fine) // mouse
@media (pointer: coarse) // touch
@media (pointer: none) // no input device
In Avalonia we use Mouse, Touch and Pen enum values for the pointer type, so we should continue using that in styling. Note, it's not precisely about device capabilities, but more about the last used pointer. It's also only about primary device.
Dark/Light theme
@media (prefers-color-scheme: light)
@media (prefers-color-scheme: dark)
In our case it can be something like :theme-variant(Dark)
OnPlatform
I don't see anything like this in CSS, but we have OnPlatform markup extensions, as well as MAUI does. So we probably can reuse the same naming instead of using "OS" there.
a better aproach to support an responsive ui in avalonia is port Autolayout control ideas from uno toolkit and Figma
Figma: https://help.figma.com/hc/en-us/articles/5731482952599-Using-auto-layout
Uno: https://github.com/unoplatform/uno.toolkit.ui
ai repeat @robloo speeaks
"However, I'm a big fan of fully adaptive/responsive UI's that adjust based on resolution or orientation though. This type of functionality in my mind is needed for less-powerful UI frameworks. XAML is resolution independent, fully vector-based and composable. You can generally-speaking design a good UI that works everywhere and automatically adjusts itself."
this can be obtained using AutoLayout
and Textbox is adjusted like all other controls inside autolayout regarding @MarchingCube concers
you can see autolayout in action on
https://platform.uno/uno-toolkit/#autolayout
Personally, I'm a fan of both the updated proposal here and an AutoLayout control. On my end, apps automatically adapt between desktop and mobile sizes using auto-layout-like concepts in several places (lots of custom controls).
I often hear though (even very recently in a presentation) that desktop apps just can't be scaled to mobile devices. If you do, it's going to be a very poor user experience. I know that isn't true though and since UWP it's been fairly straightforward as long as developers and designers think of both scenarios. My past comment was trying to emphasize this as well because even some in Avalonia's core team sometimes miss it. It's not a limitation of the tech, its a limitation of traditional desktop developers and designer creativity.
Anyway, for reducing code-behind in the custom controls on my end the above proposal by @maxkatz6 seems great. It's a good first step usable in specific cases and custom controls and views. From here more generalized things like AutoLayout can be built in the future.
Edit: so whereas before it sounded more like this feature was going to swap out views based on form factor, it can be used more creatively now and adapt existing controls and views based on screen size and things. For example docking from the left to the bottom on mobile seems trivial now as long as style setters can be triggered by the proposed syntax.
a better aproach to support an responsive ui in avalonia is port Autolayout control ideas from uno toolkit and Figma
It's not a better approach, but an alternative. And is quite a limiting alternative for the most part. As it only replaces container-size queries. Still quite useful, I can agree.
I often hear though (even very recently in a presentation) that desktop apps just can't be scaled to mobile devices. If you do, it's going to be a very poor user experience
Most of these issues can/should be solved with a separate view for the mobile. Which also reduces the spaghetti of adaptive code as well. UWP also recommended this approach at some point (early win10 days I think?).
so whereas before it sounded more like this feature was going to swap out views based on form factor
Yeah, we already have this with OnFormFactor/OnPlatform markup extension. This PR is more about improving styling capabilities.
Note, my comment isn't exactly an API proposal, but a recommendation and comparison with CSS. Actual proposal could look like this:
<Media Query=“min-width: 600”>
<Style Selector=“Panel.example”>
<Setter Property=“FontSize” Value=“80px” />
Or:
<Media Query=“size(600 < width), platform(Windows), pointer(Pen)”>
<Style Selector=“Panel.example”>
<Setter Property=“FontSize” Value=“80px” />
Or:
<Media Query=“size(600 < width) or (platform(Windows) and pointer(Pen))”>
<Style Selector=“Panel.example”>
<Setter Property=“FontSize” Value=“80px” />
Or:
<Media Query=“size(600 < width), platform(Windows) pointer(Pen)”>
<Style Selector=“Panel.example”>
<Setter Property=“FontSize” Value=“80px” />
Or without Media class at all:
<Style Selector=“:media-size(width > 300) Panel.example”> // should force media selectors to be always first in the selectors (+nested) hierarchy
<Setter Property=“FontSize” Value=“80px” />
A bit too many syntax ideas we had in the chat recently.
You can test this PR using the following package version. 11.0.999-cibuild0036426-beta
. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]
This is the current proposal I've done for media queries. Implementation is already functional in this draft. Media Queries General syntax
<Styles>
<Media Query="...">
<Style ...>
...
</Style>
</Media>
</Styles>
Types of Queries
Screen Queries: width, height
<Media Query="width > 100">
<Style ...>
<Media Query="50 < width < 100">
<Style ...>
<Media Query="50 < height">
<Style ...>
Value Queries: orientation, platform
<Media Query="orientation:portrait">
<Style ...>
<Media Query="orientation:landscape">
<Style ...>
<Media Query="platform:windows">
<Style ...>
<Media Query="platform:osx">
<Style ...>
etc
Combining Queries Queries are combined with the and and ,(for or) keywords.
<Media Query="platform:android and platform:ios">
<Style ...>
<Media Query="width > 640, width < 480">
<Style ...>
<Media Query="width > 640, width < 480">
<Style ...>
<Media Query="platform:android and width > 600, platform:ios and width > 600">
<Style ...>
The above is equivalent to (platform == android || platform == ios) && width > 600. and queries are evaluated as 1 query, then or queries are evaluated
As we are limited by xml syntax rules, <
is replaced by <
. There's been talks of using keywords as synonyms to the comparison operators, but none have currently been implemented in this draft.
Does this already consider that user/library defined media queries can be defined?
How could disambiguation look like for library defined media queries? Will it be the same namespace prefixing like e.g. "mylib|time: afternoon" (just a silly example)
Also something to consider for the implementation is that the IDE extensions can offer completions for both the media query name (e.g. orientation) and it's values (when it offers discrete values like e.g. "landscape"). So maybe for the name an attribute (like for pseudo classes) would be better as it forces to be a constant and easier readable by extensions compared to the ToString() which could be dynamically computed and also is not obvious that it should return the identifyer. An analyzer could remind not to forget that attribute.
Does this already consider that user/library defined media queries can be defined?
How could disambiguation look like for library defined media queries? Will it be the same namespace prefixing like e.g. "mylib|time: afternoon" (just a silly example)
Also something to consider for the implementation is that the IDE extensions can offer completions for both the media query name (e.g. orientation) and it's values (when it offers discrete values like e.g. "landscape"). So maybe for the name an attribute (like for pseudo classes) would be better as it forces to be a constant and easier readable by extensions compared to the ToString() which could be dynamically computed and also is not obvious that it should return the identifyer. An analyzer could remind not to forget that attribute.
It is not possible to extend or add new Query features, without updating the markup parser, just like it is not possible to add more selector types for Style. There is no provision for custom queries. This, to my knowledge is true for css. IDE support would have to be added in the extensions for the respective IDEs. With the limited possible options for Queries, adding support won't be hard.
This is the current proposal I've done for media queries. Implementation is already functional in this draft. Media Queries General syntax
<Styles> <Media Query="..."> <Style ...> ... </Style> </Media> </Styles>
Types of Queries
Screen Queries: width, height
<Media Query="width > 100"> <Style ...> <Media Query="50 < width < 100"> <Style ...> <Media Query="50 < height"> <Style ...>
Value Queries: orientation, platform
<Media Query="orientation:portrait"> <Style ...> <Media Query="orientation:landscape"> <Style ...> <Media Query="platform:windows"> <Style ...> <Media Query="platform:osx"> <Style ...>
etc
Combining Queries Queries are combined with the and and ,(for or) keywords.
<Media Query="platform:android and platform:ios"> <Style ...> <Media Query="width > 640, width < 480"> <Style ...> <Media Query="width > 640, width < 480"> <Style ...>
<Media Query="platform:android and width > 600, platform:ios and width > 600"> <Style ...>
The above is equivalent to (platform == android || platform == ios) && width > 600. and queries are evaluated as 1 query, then or queries are evaluated
As we are limited by xml syntax rules,
<
is replaced by<
. There's been talks of using keywords as synonyms to the comparison operators, but none have currently been implemented in this draft.
🤔
I don't understand why you propose this new syntax, it would create confusion. I think it would be appropriate to stick with the css-like syntax of the style selectors.
using <
decreases readability.
This is the current proposal I've done for media queries. Implementation is already functional in this draft. Media Queries General syntax
<Styles> <Media Query="..."> <Style ...> ... </Style> </Media> </Styles>
Types of Queries Screen Queries: width, height
<Media Query="width > 100"> <Style ...> <Media Query="50 < width < 100"> <Style ...> <Media Query="50 < height"> <Style ...>
Value Queries: orientation, platform
<Media Query="orientation:portrait"> <Style ...> <Media Query="orientation:landscape"> <Style ...> <Media Query="platform:windows"> <Style ...> <Media Query="platform:osx"> <Style ...>
etc Combining Queries Queries are combined with the and and ,(for or) keywords.
<Media Query="platform:android and platform:ios"> <Style ...> <Media Query="width > 640, width < 480"> <Style ...> <Media Query="width > 640, width < 480"> <Style ...>
<Media Query="platform:android and width > 600, platform:ios and width > 600"> <Style ...>
The above is equivalent to (platform == android || platform == ios) && width > 600. and queries are evaluated as 1 query, then or queries are evaluated As we are limited by xml syntax rules,
<
is replaced by<
. There's been talks of using keywords as synonyms to the comparison operators, but none have currently been implemented in this draft.🤔
I don't understand why you propose this new syntax, it would create confusion. I think it would be appropriate to stick with the css-like syntax of the style selectors.
using
<
decreases readability.
This is css-like syntax, specifically CSS Media Query Level 4 specifications, which most modern browsers supports. This was chosen over CSS3 min/max syntax for simplicity. It's easier to specify a range with Level 4 as it looks just like maths, over chaining and
and mix/max
queries. It also reduces the screen queries from 6 to 2.
< part can be a problem only with third party formatters like XAMLStyler. They already had configuration for ">" used in our style selectors - https://github.com/Xavalon/XamlStyler/blob/938e378c6cebb70db8951857078f451bb5f832db/src/XamlStyler.Extension.Windows.Shared/Extensions/DocumentExtensions.cs#L47 So I assume "<" will be a problem there as well. Shouldn't be critical though.
Typical experience with VS or Rider doesn't involve having < everywhere.
XAML is designed to be initially parseable as XML. We shouldn't break XML and allow unescaped special characters. This is pretty fundamental to the design... changing it should be very widely discussed and likely not done for a single feature.
We had a similar problem with selectors and agree to use "^" instead of ">".
My two cents contribution is saying (again) that waste energy to implement an autolayout control is better than waste energy with media queries and we have already one functional implementation in uno.toolkit that can be easily ported to avalonia.
Not saying that media queries isnt important, but saying that it is more easy implement one cintrol than modifying entire style system...
Also autolayout will made avalonia compatible to Figma and other desing online services that produces content with requirements of autolayout