Skip to content

Commit cecc928

Browse files
authored
Feature - Container Queries (#16846)
* add container queries * make visual query provider SetSize virtual * fix container name matching * add tests * move border container implementation up to decorator * move container query demo to ControlCatalog * make QueryProvider internal * update container behavior in toplevelRename Query to StyleQuery and make IContainer internal * fix comment typos * remove unused usings * isolate container tests * update api * fix tests * fix no-selector styles in containers being applied all the time * simplify container search * add docs to container properties * addressed api review * remove weird unmerge cooments * remove width and height event container subscriptions when visual is detached
1 parent b09e0d5 commit cecc928

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2439
-11
lines changed

Avalonia.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tizen", "Tizen", "{D1300000
274274
EndProject
275275
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Tizen", "src\Tizen\Avalonia.Tizen\Avalonia.Tizen.csproj", "{DFFBDBF5-5DBE-47ED-9EAE-D40B75AC99E8}"
276276
EndProject
277-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Tizen", "samples\ControlCatalog.Tizen\ControlCatalog.Tizen.csproj", "{A0B29221-2B6F-4B29-A4D5-2227811B5915}"
277+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Tizen", "samples\ControlCatalog.Tizen\ControlCatalog.Tizen.csproj", "{A0B29221-2B6F-4B29-A4D5-2227811B5915}"
278278
EndProject
279279
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Metal", "src\Avalonia.Metal\Avalonia.Metal.csproj", "{60B4ED1F-ECFA-453B-8A70-1788261C8355}"
280280
EndProject
69.9 KB
Loading
52.6 KB
Loading
52.9 KB
Loading
95 KB
Loading
67.5 KB
Loading
107 KB
Loading
60.1 KB
Loading

samples/ControlCatalog/ControlCatalog.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
<AvaloniaResource Include="Assets\*" />
1616
<AvaloniaResource Include="Assets\Fonts\*" />
1717
</ItemGroup>
18+
<ItemGroup>
19+
<None Remove="Assets\image1.jpg" />
20+
<None Remove="Assets\image2.jpg" />
21+
<None Remove="Assets\image3.jpg" />
22+
<None Remove="Assets\image4.jpg" />
23+
<None Remove="Assets\image5.jpg" />
24+
<None Remove="Assets\image6.jpg" />
25+
<None Remove="Assets\image7.jpg" />
26+
</ItemGroup>
1827
<ItemGroup>
1928
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
2029
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />

samples/ControlCatalog/MainView.xaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
<TabItem Header="ComboBox">
6161
<pages:ComboBoxPage />
6262
</TabItem>
63+
<TabItem Header="Container Queries">
64+
<pages:ContainerQueryPage />
65+
</TabItem>
6366
<TabItem Header="ContextFlyout">
6467
<pages:ContextFlyoutPage />
6568
</TabItem>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<UserControl x:Class="ControlCatalog.Pages.ContainerQueryPage"
2+
xmlns="https://github.com/avaloniaui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:viewModels="using:ControlCatalog.ViewModels"
7+
d:DesignHeight="800"
8+
d:DesignWidth="400"
9+
mc:Ignorable="d">
10+
<StackPanel Spacing="10">
11+
<StackPanel.Styles>
12+
<ContainerQuery Name="UniformGrid"
13+
Query="max-width:400">
14+
<Style Selector="UniformGrid#ContentGrid">
15+
<Setter Property="Columns"
16+
Value="1"/>
17+
</Style>
18+
</ContainerQuery>
19+
<ContainerQuery Name="UniformGrid"
20+
Query="min-width:400">
21+
<Style Selector="UniformGrid#ContentGrid">
22+
<Setter Property="Columns"
23+
Value="2"/>
24+
</Style>
25+
</ContainerQuery>
26+
<ContainerQuery Name="UniformGrid"
27+
Query="min-width:800">
28+
<Style Selector="UniformGrid#ContentGrid">
29+
<Setter Property="Columns"
30+
Value="3"/>
31+
</Style>
32+
</ContainerQuery>
33+
<ContainerQuery Name="UniformGrid"
34+
Query="min-width:1200">
35+
<Style Selector="UniformGrid#ContentGrid">
36+
<Setter Property="Columns"
37+
Value="4"/>
38+
</Style>
39+
</ContainerQuery>
40+
</StackPanel.Styles>
41+
<TextBlock Text="Dynamically change properties of controls based on the size of a parent container."/>
42+
<Border Container.Name="UniformGrid"
43+
VerticalAlignment="Stretch"
44+
HorizontalAlignment="Stretch"
45+
Container.Sizing="Width">
46+
<ScrollViewer VerticalScrollBarVisibility="Auto"
47+
HorizontalScrollBarVisibility="Disabled">
48+
<Grid RowDefinitions="Auto,*">
49+
<UniformGrid Name="ContentGrid">
50+
<Border Margin="10"
51+
HorizontalAlignment="Stretch"
52+
CornerRadius="20"
53+
ClipToBounds="True">
54+
<Image Stretch="Uniform"
55+
HorizontalAlignment="Stretch"
56+
Source="/Assets/image1.jpg"/>
57+
</Border>
58+
<Border Margin="10"
59+
HorizontalAlignment="Stretch"
60+
CornerRadius="20"
61+
ClipToBounds="True">
62+
<Image Stretch="Uniform"
63+
HorizontalAlignment="Stretch"
64+
Source="/Assets/image2.jpg"/>
65+
</Border>
66+
<Border Margin="10"
67+
HorizontalAlignment="Stretch"
68+
CornerRadius="20"
69+
ClipToBounds="True">
70+
<Image Stretch="Uniform"
71+
HorizontalAlignment="Stretch"
72+
Source="/Assets/image3.jpg"/>
73+
</Border>
74+
<Border Margin="10"
75+
HorizontalAlignment="Stretch"
76+
CornerRadius="20"
77+
ClipToBounds="True">
78+
<Image Stretch="Uniform"
79+
HorizontalAlignment="Stretch"
80+
Source="/Assets/image4.jpg"/>
81+
</Border>
82+
<Border Margin="10"
83+
HorizontalAlignment="Stretch"
84+
CornerRadius="20"
85+
ClipToBounds="True">
86+
<Image Stretch="Uniform"
87+
HorizontalAlignment="Stretch"
88+
Source="/Assets/image5.jpg"/>
89+
</Border>
90+
<Border Margin="10"
91+
HorizontalAlignment="Stretch"
92+
CornerRadius="20"
93+
ClipToBounds="True">
94+
<Image Stretch="Uniform"
95+
HorizontalAlignment="Stretch"
96+
Source="/Assets/image6.jpg"/>
97+
</Border>
98+
<Border HorizontalAlignment="Stretch"
99+
CornerRadius="20"
100+
ClipToBounds="True">
101+
<Image Stretch="Uniform"
102+
HorizontalAlignment="Stretch"
103+
Source="/Assets/image7.jpg"/>
104+
</Border>
105+
</UniformGrid>
106+
</Grid>
107+
</ScrollViewer>
108+
</Border>
109+
</StackPanel>
110+
</UserControl>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Markup.Xaml;
3+
4+
namespace ControlCatalog.Pages
5+
{
6+
public class ContainerQueryPage : UserControl
7+
{
8+
public ContainerQueryPage()
9+
{
10+
this.InitializeComponent();
11+
}
12+
13+
private void InitializeComponent()
14+
{
15+
AvaloniaXamlLoader.Load(this);
16+
}
17+
}
18+
}

src/Avalonia.Base/Layout/Layoutable.cs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Avalonia.Diagnostics;
33
using Avalonia.Logging;
44
using Avalonia.Reactive;
5+
using Avalonia.Styling;
56
using Avalonia.Utilities;
67
using Avalonia.VisualTree;
78

@@ -553,14 +554,43 @@ protected virtual Size MeasureCore(Size availableSize)
553554

554555
var minMax = new MinMax(this);
555556

556-
var constrained = LayoutHelper.ApplyLayoutConstraints(
557+
var constrainedSize = LayoutHelper.ApplyLayoutConstraints(
557558
minMax,
558559
availableSize.Deflate(margin));
559-
var measured = MeasureOverride(constrained);
560+
561+
var isContainer = false;
562+
ContainerSizing containerSizing = ContainerSizing.Normal;
563+
564+
if (Container.GetQueryProvider(this) is { } queryProvider && Container.GetSizing(this) is { } sizing && sizing != ContainerSizing.Normal)
565+
{
566+
isContainer = true;
567+
containerSizing = sizing;
568+
queryProvider.SetSize(constrainedSize.Width, constrainedSize.Height, containerSizing);
569+
}
570+
571+
var measured = MeasureOverride(constrainedSize);
560572

561573
var width = MathUtilities.Clamp(measured.Width, minMax.MinWidth, minMax.MaxWidth);
562574
var height = MathUtilities.Clamp(measured.Height, minMax.MinHeight, minMax.MaxHeight);
563575

576+
if (isContainer)
577+
{
578+
switch (containerSizing)
579+
{
580+
case ContainerSizing.Width:
581+
width = double.IsInfinity(constrainedSize.Width) ? width : constrainedSize.Width;
582+
break;
583+
case ContainerSizing.Height:
584+
width = measured.Width;
585+
height = double.IsInfinity(constrainedSize.Height) ? height : constrainedSize.Height;
586+
break;
587+
case ContainerSizing.WidthAndHeight:
588+
width = double.IsInfinity(constrainedSize.Width) ? width : constrainedSize.Width;
589+
height = double.IsInfinity(constrainedSize.Height) ? height : constrainedSize.Height;
590+
break;
591+
}
592+
}
593+
564594
if (useLayoutRounding)
565595
{
566596
(width, height) = LayoutHelper.RoundLayoutSizeUp(new Size(width, height), scale);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace Avalonia.Platform
5+
{
6+
internal class VisualQueryProvider
7+
{
8+
private readonly Visual _visual;
9+
10+
public double Width { get; private set; } = double.PositiveInfinity;
11+
12+
public double Height { get; private set; } = double.PositiveInfinity;
13+
14+
public VisualQueryProvider(Visual visual)
15+
{
16+
_visual = visual;
17+
}
18+
19+
public event EventHandler? WidthChanged;
20+
public event EventHandler? HeightChanged;
21+
22+
public virtual void SetSize(double width, double height, Styling.ContainerSizing containerType)
23+
{
24+
var currentWidth = Width;
25+
var currentHeight = Height;
26+
27+
Width = width;
28+
Height = height;
29+
30+
if (currentWidth != Width && (containerType == Styling.ContainerSizing.Width || containerType == Styling.ContainerSizing.WidthAndHeight))
31+
WidthChanged?.Invoke(this, EventArgs.Empty);
32+
if (currentHeight != Height && (containerType == Styling.ContainerSizing.Height || containerType == Styling.ContainerSizing.WidthAndHeight))
33+
HeightChanged?.Invoke(this, EventArgs.Empty);
34+
}
35+
}
36+
}

src/Avalonia.Base/StyledElement.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,9 @@ private void ApplyStyles(IStyleHost host)
840840

841841
private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type)
842842
{
843+
if (style is Styling.ContainerQuery m)
844+
m.TryAttach(this, host, type);
845+
843846
if (style is Style s)
844847
s.TryAttach(this, host, type);
845848

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System.Collections.Generic;
2+
3+
namespace Avalonia.Styling.Activators
4+
{
5+
/// <summary>
6+
/// An aggregate <see cref="ContainerQueryActivatorBase"/> which is active when all of its inputs are
7+
/// active.
8+
/// </summary>
9+
internal class AndQueryActivator : ContainerQueryActivatorBase, IStyleActivatorSink
10+
{
11+
private List<IStyleActivator>? _sources;
12+
13+
public AndQueryActivator(Visual visual) : base(visual)
14+
{
15+
}
16+
17+
public int Count => _sources?.Count ?? 0;
18+
19+
public void Add(IStyleActivator activator)
20+
{
21+
if (IsSubscribed)
22+
throw new AvaloniaInternalException("AndActivator is already subscribed.");
23+
_sources ??= new List<IStyleActivator>();
24+
_sources.Add(activator);
25+
}
26+
27+
void IStyleActivatorSink.OnNext(bool value) => ReevaluateIsActive();
28+
29+
protected override bool EvaluateIsActive()
30+
{
31+
if (_sources is null || _sources.Count == 0)
32+
return true;
33+
34+
var count = _sources.Count;
35+
var mask = (1ul << count) - 1;
36+
var flags = 0UL;
37+
38+
for (var i = 0; i < count; ++i)
39+
{
40+
if (_sources[i].GetIsActive())
41+
flags |= 1ul << i;
42+
}
43+
44+
return flags == mask;
45+
}
46+
47+
protected override void Initialize()
48+
{
49+
if (_sources is object)
50+
{
51+
foreach (var source in _sources)
52+
{
53+
source.Subscribe(this);
54+
}
55+
}
56+
}
57+
58+
protected override void Deinitialize()
59+
{
60+
if (_sources is object)
61+
{
62+
foreach (var source in _sources)
63+
{
64+
source.Unsubscribe(this);
65+
}
66+
}
67+
}
68+
}
69+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#nullable enable
2+
3+
namespace Avalonia.Styling.Activators
4+
{
5+
/// <summary>
6+
/// Builds an <see cref="AndActivator"/>.
7+
/// </summary>
8+
/// <remarks>
9+
/// When ANDing style activators, if there is more than one input then creates an instance of
10+
/// <see cref="AndActivator"/>. If there is only one input, returns the input directly.
11+
/// </remarks>
12+
internal struct AndQueryActivatorBuilder
13+
{
14+
private readonly Visual _visual;
15+
private IStyleActivator? _single;
16+
private AndQueryActivator? _multiple;
17+
18+
public AndQueryActivatorBuilder(Visual visual) : this()
19+
{
20+
_visual = visual;
21+
}
22+
23+
public int Count => _multiple?.Count ?? (_single is object ? 1 : 0);
24+
25+
public void Add(IStyleActivator? activator)
26+
{
27+
if (activator == null)
28+
{
29+
return;
30+
}
31+
32+
if (_single is null && _multiple is null)
33+
{
34+
_single = activator;
35+
}
36+
else
37+
{
38+
if (_multiple is null)
39+
{
40+
_multiple = new AndQueryActivator(_visual);
41+
_multiple.Add(_single!);
42+
_single = null;
43+
}
44+
45+
_multiple.Add(activator);
46+
}
47+
}
48+
49+
public IStyleActivator Get() => _single ?? _multiple!;
50+
}
51+
}

0 commit comments

Comments
 (0)