-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Container Queries #16949
Comments
I think it's important to mention that queries sytnax also provides possibility to bring more queries to the table eventually. |
I would generally vote against UWP-style AdaptiveTriggers (or more broadly - StateTriggers) for reasons of it being alien to Avalonia style system and generally hard to use. Although since we got ControlThemes, StateTriggers could be technically adapted (personally I would just use behaviors instead). StateTriggers also relatively extendable - something we don't have with our styling at all, like. Avalonia already solved many of these usecases for custom triggers via selectors syntax. |
Specifically, on ContainerQuery. I would say we got an agreement in the team, how this feature should work internally. With mentioned limitations on But higher-level API and usage with styles can be discussed. Currently opened PR mentions As an alternative design, we can have something like this: <ControlTheme>
<Setter Property="Template">
<ControlTemplate>
<Border x:Name="PART_Container" ContainerType="Width">
<Border x:Name="PART_Child" />
</Border>
</ControlTemplate>
</Setter>
<!-- Container is either a direct .NET reference, or with some help from XAML compiler - part of the template -->
<ContainerQuery Container="PART_Container" Query="min-width:400">
<Style Selector="^ /template/ Border#PART_Container">
<Setter Property="Width" Value="400"/>
</Style>
</ContainerQuery>
</ControlTheme> This should work well for closed control themes, ensuring that ContainerQuery won't leak or access anything outside of ControlTheme. We even can enforce explicit container for control themes. This feature also should be well usable outside of ControlTheme's. For example, in the user control where we have a proper namescope set-up: <UserControl.Styles>
<Style Selector="Border.b">
<Setter Property="Background" Value="Red" />
<Setter Property="Width" Value="200"/>
</Style>
<ContainerQuery Container="MyContainer" Query="min-width:400">
<Style Selector="Border.b">
<Setter Property="Width" Value="400"/>
</Style>
</ContainerQuery>
</UserControl.Styles>
<Grid ColumnDefinitions="*,200">
<ContentControl x:Name="MyContainer" ContainerType="Width">
<StackPanel>
<Border Classes="b"/>
</StackPanel>
</ContentControl>
<Button Grid.Column="1"/>
</Grid> And I would prefer not allowing |
And while media queries are less powerful than container queries, we might have some discussion on naming in general. |
In general I think this is a good idea. However I think for it to really work, Avalonia needs to add relative units based on the container size which can then be applied to controls contained within the container. Much like how CSS has them. It would avoid having to create dozens of very similar container queries to handle various different screen/container sizes. EG something like this: <ContainerQuery Query="min-width:400">
<Style Selector="Border.b">
<Setter Property="Width" Value="80cw"/>
</Style>
</ContainerQuery> Where |
My questions or opinions:
<Style Selector="@container[width>400 && width < 800] Border.b">
<Setter Property="Width" Value="300" />
</Style>
<Style Selector="@container[width>800 ] Border.b">
<Setter Property="Width" Value="600" />
</Style> |
In CSS the document hierarchy applies. So the direct parent is generally all that really matters. If the parent itself has a parent then the direct parent rule applies to that. Most of the time, you don't really care though since everything will automatically resize anyway based on the available space as long as you use relative units. But if you only have fixed units then it matters a lot more.
CSS sort of actually allows this by explicitly overriding how to treat an element. https://developer.mozilla.org/en-US/docs/Web/CSS/display
The problem I see with using |
|
@rabbitism Introducing relative units for control sizes is a great idea, but currently out of the scope of this feature. Could you create an issue for that so we all least have something to track. Thanks |
My two cents. I don't see the point of overcomplicating our lives. We already have a great system for managing styles, the Classes, it just needs a few tweaks. First, allow element classes to inherit from their container, allow defining media classes that identify the media. Example of use: <Window ....>
<Window.Resources>
<Media Name="SmallDesktop">
<Media.Rules>
<ScreenSizeRule Value="640x480"/>
<!-- other rule. Each dev cab be create own -->
</Media.Rules/>
</Media>
<Media Name="HiContrast">
<Media.Rules>
<AmbientLightRule From='.7' To='1'/>
</Media.Rules/>
</Media>
</Window.Resources>
<Window.Styles>
<Style Selector="Border.SmallDesktop">
...
</Style>
<Style Selector="Border.SmallDesktop.HiContrast">
...
</Style>
</Window.Styles> |
Is this only limited to sizing basing on the toplevel? Or based on the parent of the control? |
TopLevel because based on its characteristics it constrains all the rest of the UI. |
I have created #16962 for discussing relative units. |
Why not make MarkupExtensions (or maybe a new variant VisualMarkupExtension) receive the Visual element they are applied to? If we were to have access to the Visual element in the extension, it would be very easy to write customized extensions, which are "sensitive" to other elements in the (logical/visual)tree. public class RelativeWidth : VisualMarkupExtension
{
Visual element;
string value;
public RelativeSize(Visual element, string value)
{
this.element = element;
this.value = value;
}
public double ProvideValue(IServiceProvider provider)
{
// Omitting all error handling, etc.
return element.Parent.Width * Double.Parse(value);
}
} (The visual element could of course also be property injected, so that the constructor syntax remains unchanged.) Usage would be something like this: <Button Width="{RelativeWidth 0.8}" /> An extension for container-size dependent layout could (for example) be done like this: <Panel Width="400">
<Button Width="{AdaptiveSize SnapPoints='400:150;800:200;1200:250'}" />
</Panel> where width is 150 (for container >= 400), 200 (for >= 800), etc. They actual design would probably more involving (query like, '>' '<' etc.), but I hope you get the point. or even with optional container reference <Panel x:Name="MyContainer" Width="850">
...
<Panel Width="450">
<Button Width="{AdaptiveSize Points='400:150;800:200;1200:250' RelativeTo='MyContainer'"}" />
...
</Panel>
</Panel> My point is that such a design would be very versatile and easily extendable (also in user code) and customizable, without the need of changing the XAML compiler for each specific new syntax. It's also in line with existing concepts and syntax in Avalonia. And (from my point of view) can be easy to read as well. Just a crazy idea ... |
If you want to discuss relative units, please do so in #16962. This discussion is for container queries. A related, but very different concept that allows for applying different values to use in different situations (EG when the container gets too small, you could switch a horizontal stack panel into a vertical one). |
Well, I missed the start of this discussion. Thanks for opening an issue! (a discussion might be better for this type of thing in the future so we can use threads to manage subtopics). As usual with things like this I have a different viewpoint than the core team. My instinct here is that while ContainerQuery is certainly functional, it's also unnecessary. I'm not sure the original reasoning the CSS working group had to do the things they did though. I also realize there is a goal for MediaQuery and other queries in the future. However, at least in the case of MediaQuery, it doesn't make a lot of sense when the top level size can be queried with something like ContainerQuery. So really, what other queries are needed in the future and how will this provide functionality that couldn't actually be done a different way? What justifies a brand-new concept (based on CSS)? Anyway, starting from the beginning. I'm basing my viewpoint on two main things:
So the logic flows that if style selectors were the fundamental replacement for triggers. And triggers are the fundamental design element in XAML for things like this: why don't we just use style selectors that we already have the concept for? A third aspect of this is style selectors are appropriately generic whereas ContainerQuery just doesn't seem to fit in well and seems overly specific. It doesn't fall into place with existing ideas and seems like a hack on top of what is already there. Now what would a style selector look like that could do this? Well, please see my other comments in the PR for even more background:
Long story short though, we need something like this:
This syntax has a few things to talk about:
Is this syntax 100% ready? No. But with enough input I'm confident a selector syntax could be decided that was workable for everyone. It would also likely extend the power of selectors across the board so it would be a general-purpose addition to what we already have today. With API design its very easy to take the easy route and invent (or copy) something new to add on to what you have. Unfortunately, over time that doesn't make a great API. The true skill and benefit to everyone is fully integrating APIs together. |
As mentioned before, I really don't think it's a good idea to allow arbitrary comparisons in selector text. I'll go into more detail on this here I guess: It's hard to readOnce we've added a few comparisons, people will start asking for more and before we know it we'll have a turing-complete language in selectors. The goal of the styling/selector system is not to become a separate programming language. I will note that even CSS (which is generally regarded as being turing-complete) does not have this greater-than/less-than etc selectors. I don't ever want to see this in a selector:
Your example syntax is 90% of the way to this. It's bad for performanceOnce people start using these expressions, and the expressions start to become more complicated, people will start complaining that performance is bad. And yes, it will be bad unless we start compiling code, at which point we have a fully fledged new language. It's going to cause layout cycles where a selector creates a circular dependency on anotherThis will happen, and there are no debugging tools. We'll then be forced to write these debugging tools. |
Selectors were designed to select, and not to query. Even the property match selector's role was to select based on a styled property on itself. Selectors aren't generic. They just have a wide range of uses due to its nature. |
@grokys @emmauss Thanks as always for the comments and background. I did duplicate some of what was said in the PR here not so much to repeat ourselves but more so everyone could see it. Regardless of the final outcome the discussion is sometimes just as important. Years later, the reasons why things were done are sometimes worth more than what was actually done. Side note: All laws should have a document describing the reason for the law along with it!
We have a strong disagreement here. I think XAML would be far more useful and functional if we could do simple arithmetic and mathematical comparisons. People end up doing all this in code behind or with custom extensions for the most part. It's far more complicated than it needs to be (from a dev/user standpoint). Disagreements are fine and don't need to be resolved.
I think you are taking my example to the extreme and then judging the whole based on that extreme. I can abuse anything and make it a performance problem if I want to. That said, a less-than or greater-than comparison is hardly more of a performance impact than an equality comparison. The biggest issue is figuring out what syntax would be readable within the constraints of XML attributes. Keep in mind my comment in the PR for a full expression syntax was NOT what I'm actually proposing here or what is needed. I was simply saying wouldn't it be awesome to have! (yes, you disagree with this). I think eventually we are just going to need a Razor type syntax to bring all this together.
Overall, this criticism seems fair. Without thinking it through fully I suspect most cases in the "switch styles based on container size" wouldn't be an issue though.
Again, nth-child selector is already bridging the divide here. It's a small jump (from a user standpoint) to generalize this to select based on parent properties. We already violated the rule "selector's role was to select based on a styled property on itself" for good reasons.
Yes, that's a nice feature for sure. But it's awful similar in concept to a style group -- nested style -- isn't it? And we already have nested styles in control themes. The parent selector is matched first for all the children. So if we applied that concept here it's an already solved problem. The parent selector could be something like Note: It still doesn't sit right with me that we have ControlTheme vs Style and Styles vs Resources collection. Again there were good reasons for it but we aren't integrating like we should be IMO (and what was done in WPF). There are always an infinite number of ways to do things and if we wanted to go a different direction it's always possible. This is just the viewpoint I'm coming from: simpler, generic, re-use of ideas is better.
The idea would be, just like a selector, to target a specific base control or class. TextBox was just an example. Of course something like Finally, I do have some humility here. I certainly don't know better than @grokys who decided on Selectors to begin with. And I certainly don't know more than the core team who is unanimous here. I'm just pointing out from my viewpoint it seems like selectors should have been extended instead. I'm sure someone in the future might have a similar thought and can come here to see the reasons why things are the way they are. I don't expect you to take the time to follow-up anymore to this thread of the discussion. |
@robloo I didn't say selectors must select based on a property of itself. I chose the property match selector because that's one of the non-css selectors we have, and even with that, it selects a group of controls that matches the selector.
nth-child is still a css selector. But when a selector requires checking the status of an unrelated control to match, it becomes a query. |
Is your feature request related to a problem? Please describe.
With Avalonia supporting browser and mobile platforms, there are very few ways to adapt a view for different screen sizes. Users currently have to design their own responsive controls, use mobile designs for desktop or use different views for desktop, mobile and browser.
We require a simple mechanism for adjusting layouts based on the size of the screen, window or the space available to a control.
Describe the solution you'd like
One way to solve this is to create styles that are activated based on the size of a parent or the window. That's where Container Queries come in. Certain controls can behave as container for child controls to query them and activate styles based in the current sizes
This feature is similar to css's container queries. By specifying a control as a container, when that control size changes, any style affecting its descendants that's declared in the container query will be activated.
In the above style, if the
Grid
has a width of 600px or more, thus providing the ContentControl with an available space of >= 400, theBorder
with classb
will have a fixed Width of 400px instead of 200px.Containers can not be styled by the styles declared in their ContainerQuery.
One main difference between css and Avalonia is the concept of Inline Layouts. In css, inline layout refers to the direction inline elements are laid out. In a normal document, this refers to the Width or horizontal direction. Its inverse is Block, which refers to the direction block elements are laid out. As such, we can't have a 1:1 api implementation of container queries in Avalonia. CSS allows user to declare which direction to allow querying the container's size, and containers sizes can't be affected by children on the direction declared in the ContainerType. Thus I propose the follow enum to match with CSS container type.
All suggestions are greatly appreciated.
Describe alternatives you've considered
Media Queries
A precursor to Container Queries in css. This allows styles to be activated based on data from the media or device running, such as screen size, orientation, color mode, etc. For the data we are interesting in, which is the size of the device, it is less powerful that container queries. Container queries perform the same function when set on the toplevel, and for complex views, say a screen of charts, knowing the full device size isn't as useful as knowing how much space each chart has to arrange its children using styles.
Adaptive Triggers
Adaptive Triggers were implemented in WinUI to perform a similar function to media queries with relation to size. But they are limited to only triggering on Window size changes, and our styling system differs from WinUI's with how less verbose ours is.
Additional context
There's an RFC available that exhibits how this feature can be implemented in Avalonia here #16846
The text was updated successfully, but these errors were encountered: