| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 |
- using System.Linq;
- using Avalonia.Controls;
- using Avalonia.Controls.Primitives;
- using Avalonia.Controls.Templates;
- using Avalonia.Media;
- using Avalonia.Styling;
- using Avalonia.UnitTests;
- using Avalonia.VisualTree;
- using Xunit;
- #nullable enable
- namespace Avalonia.Base.UnitTests.Styling;
- public class StyledElementTests_Theming
- {
- public class InlineTheme
- {
- [Fact]
- public void Theme_Is_Applied_When_Attached_To_Logical_Tree()
- {
- var target = CreateTarget();
- Assert.Null(target.Template);
- CreateRoot(target);
- Assert.NotNull(target.Template);
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal(Brushes.Red, border.Background);
- target.Classes.Add("foo");
- Assert.Equal(Brushes.Green, border.Background);
- }
- [Fact]
- public void Theme_Is_Applied_To_Derived_Class_When_Attached_To_Logical_Tree()
- {
- var target = new DerivedThemedControl
- {
- Theme = CreateTheme(),
- };
- Assert.Null(target.Template);
- CreateRoot(target);
- Assert.NotNull(target.Template);
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal(Brushes.Red, border.Background);
- target.Classes.Add("foo");
- Assert.Equal(Brushes.Green, border.Background);
- }
- [Fact]
- public void Theme_Is_Detached_When_Theme_Property_Cleared()
- {
- var target = CreateTarget();
- CreateRoot(target);
- Assert.NotNull(target.Template);
- target.Theme = null;
- Assert.Null(target.Template);
- }
- [Fact]
- public void Setting_Explicit_Theme_Detaches_Default_Theme()
- {
- var target = new ThemedControl();
- var root = new TestRoot
- {
- Resources = { { typeof(ThemedControl), CreateTheme() } },
- Child = target,
- };
- root.LayoutManager.ExecuteInitialLayoutPass();
- Assert.Equal("theme", target.Tag);
- target.Theme = new ControlTheme(typeof(ThemedControl))
- {
- Setters =
- {
- new Setter(ThemedControl.BackgroundProperty, Brushes.Yellow),
- }
- };
- root.LayoutManager.ExecuteLayoutPass();
- Assert.Null(target.Tag);
- Assert.Equal(Brushes.Yellow, target.Background);
- }
- [Fact]
- public void Unrelated_Styles_Are_Not_Detached_When_Theme_Property_Cleared()
- {
- var target = CreateTarget();
- CreateRoot(target, createAdditionalStyles: true);
- Assert.Equal("style", target.Tag);
- target.Theme = null;
- Assert.Equal("style", target.Tag);
- }
- [Fact]
- public void TemplatedParent_Theme_Is_Detached_From_Template_Controls_When_Theme_Property_Cleared()
- {
- var theme = new ControlTheme
- {
- TargetType = typeof(ThemedControl),
- Children =
- {
- new Style(x => x.Nesting().Template().OfType<Canvas>())
- {
- Setters =
- {
- new Setter(Panel.BackgroundProperty, Brushes.Red),
- }
- },
- }
- };
- var target = CreateTarget(theme);
- target.Template = new FuncControlTemplate<ThemedControl>((o, n) => new Canvas());
- var root = CreateRoot(target);
- var canvas = Assert.IsType<Canvas>(target.VisualChild);
- Assert.Equal(Brushes.Red, canvas.Background);
- target.Theme = null;
- Assert.Same(canvas, target.VisualChild);
- Assert.Null(canvas.Background);
- }
- [Fact]
- public void Primary_Theme_Is_Not_Detached_From_Template_Controls_When_Theme_Property_Cleared()
- {
- var templatedParentTheme = new ControlTheme
- {
- TargetType = typeof(ThemedControl),
- Children =
- {
- new Style(x => x.Nesting().Template().OfType<Button>())
- {
- Setters =
- {
- new Setter(Panel.BackgroundProperty, Brushes.Red),
- }
- },
- }
- };
- var childTheme = new ControlTheme
- {
- TargetType = typeof(Button),
- Setters =
- {
- new Setter(TemplatedControl.ForegroundProperty, Brushes.Green),
- }
- };
- var target = CreateTarget(templatedParentTheme);
- target.Template = new FuncControlTemplate<ThemedControl>((o, n) => new Button
- {
- Theme = childTheme,
- });
- var root = CreateRoot(target, createAdditionalStyles: true);
- var templateChild = Assert.IsType<Button>(target.VisualChild);
- Assert.Equal(Brushes.Red, templateChild.Background);
- Assert.Equal(Brushes.Green, templateChild.Foreground);
- target.Theme = null;
- Assert.Null(templateChild.Background);
- Assert.Equal(Brushes.Green, templateChild.Foreground);
- }
- [Fact]
- public void TemplatedParent_Theme_Is_Not_Detached_From_Template_Controls_When_Primary_Theme_Property_Cleared()
- {
- var templatedParentTheme = new ControlTheme
- {
- TargetType = typeof(ThemedControl),
- Children =
- {
- new Style(x => x.Nesting().Template().OfType<Button>())
- {
- Setters =
- {
- new Setter(Panel.BackgroundProperty, Brushes.Red),
- }
- },
- }
- };
- var childTheme = new ControlTheme
- {
- TargetType = typeof(Button),
- Setters =
- {
- new Setter(Button.TagProperty, "childTheme"),
- }
- };
- var target = CreateTarget(templatedParentTheme);
- target.Template = new FuncControlTemplate<ThemedControl>((o, n) => new Button
- {
- Theme = childTheme,
- });
- var root = CreateRoot(target, createAdditionalStyles: true);
- var templateChild = Assert.IsType<Button>(target.VisualChild);
- Assert.Equal(Brushes.Red, templateChild.Background);
- Assert.Equal("childTheme", templateChild.Tag);
- templateChild.Theme = null;
- Assert.Equal(Brushes.Red, templateChild.Background);
- Assert.Null(templateChild.Tag);
- }
- [Fact]
- public void Unrelated_Styles_Are_Not_Detached_From_Template_Controls_When_Theme_Property_Cleared()
- {
- var target = CreateTarget();
- var root = CreateRoot(target, createAdditionalStyles: true);
- var canvas = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal("style", canvas.Tag);
- target.Theme = null;
- Assert.Same(canvas, target.VisualChild);
- Assert.Equal("style", canvas.Tag);
- }
- [Fact]
- public void Theme_Is_Applied_On_Layout_After_Theme_Property_Changes()
- {
- var target = new ThemedControl();
- var root = CreateRoot(target);
- Assert.Null(target.Template);
- target.Theme = CreateTheme();
- Assert.Null(target.Template);
- root.LayoutManager.ExecuteLayoutPass();
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.NotNull(target.Template);
- Assert.Equal(Brushes.Red, border.Background);
- }
- [Fact]
- public void BasedOn_Theme_Is_Applied_When_Attached_To_Logical_Tree()
- {
- var target = CreateTarget(CreateDerivedTheme());
- Assert.Null(target.Template);
- CreateRoot(target);
- Assert.NotNull(target.Template);
- Assert.Equal(Brushes.Blue, target.BorderBrush);
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal(Brushes.Red, border.Background);
- Assert.Equal(Brushes.Yellow, border.BorderBrush);
- target.Classes.Add("foo");
- Assert.Equal(Brushes.Green, border.Background);
- Assert.Equal(Brushes.Cyan, border.BorderBrush);
- }
- [Fact]
- public void Theme_Has_Lower_Priority_Than_Style()
- {
- var target = CreateTarget();
- CreateRoot(target, createAdditionalStyles: true);
- Assert.Equal("style", target.Tag);
- }
- [Fact]
- public void Theme_Has_Lower_Priority_Than_Style_After_Change()
- {
- var target = CreateTarget();
- var theme = target.Theme;
- CreateRoot(target, createAdditionalStyles: true);
- target.Theme = null;
- target.Theme = theme;
- target.ApplyStyling();
- Assert.Equal("style", target.Tag);
- }
- private static ThemedControl CreateTarget(ControlTheme? theme = null)
- {
- return new ThemedControl
- {
- Theme = theme ?? CreateTheme(),
- };
- }
- private static TestRoot CreateRoot(
- Control child,
- bool createAdditionalStyles = false)
- {
- var result = new TestRoot();
- if (createAdditionalStyles)
- {
- result.Styles.Add(new Style(x => x.OfType<ThemedControl>())
- {
- Setters =
- {
- new Setter(Control.TagProperty, "style"),
- }
- });
- result.Styles.Add(new Style(x => x.OfType<Border>())
- {
- Setters =
- {
- new Setter(Control.TagProperty, "style"),
- }
- });
- }
- result.Child = child;
- result.LayoutManager.ExecuteInitialLayoutPass();
- return result;
- }
- }
- public class ImplicitTheme
- {
- [Fact]
- public void Implicit_Theme_Is_Applied_When_Attached_To_Logical_Tree()
- {
- var target = CreateTarget();
- CreateRoot(target);
- Assert.NotNull(target.Template);
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal(Brushes.Red, border.Background);
- target.Classes.Add("foo");
- Assert.Equal(Brushes.Green, border.Background);
- }
- [Fact]
- public void Implicit_Theme_Is_Not_Detached_When_Removed_From_Logical_Tree()
- {
- var target = CreateTarget();
- var root = CreateRoot(target);
- Assert.Equal("theme", target.Tag);
- root.Child = null;
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal("theme", target.Tag);
- Assert.Equal("theme", border.Tag);
- }
- [Fact]
- public void Can_Attach_Then_Reattach_To_Same_Logical_Tree()
- {
- var target = CreateTarget();
- var root = CreateRoot(target);
- Assert.Equal("theme", target.Tag);
- root.Child = null;
- root.Child = target;
- Assert.Equal("theme", target.Tag);
- }
- [Fact]
- public void Implicit_Theme_Is_Reevaluated_When_Removed_And_Added_To_Different_Logical_Tree()
- {
- var target = CreateTarget();
- var root1 = CreateRoot(target, "theme1");
- var root2 = CreateRoot(null, "theme2");
- Assert.Equal("theme1", target.Tag);
- root1.Child = null;
- root2.Child = target;
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal("theme2", target.Tag);
- Assert.Equal("theme2", border.Tag);
- }
- [Fact]
- public void Nested_Style_Can_Override_Property_In_Inner_Templated_Control()
- {
- var target = new ThemedControl2
- {
- Theme = new ControlTheme(typeof(ThemedControl2))
- {
- Setters =
- {
- new Setter(
- Controls.Primitives.TemplatedControl.TemplateProperty,
- new FuncControlTemplate<ThemedControl2>((o, n) => new ThemedControl())),
- },
- Children =
- {
- new Style(x => x.Nesting().Template().OfType<ThemedControl>())
- {
- Setters = { new Setter(Controls.Primitives.TemplatedControl.CornerRadiusProperty, new CornerRadius(7)), }
- },
- }
- },
- };
- var root = CreateRoot(target);
- var inner = Assert.IsType<ThemedControl>(target.VisualChild);
- Assert.Equal(new CornerRadius(7), inner.CornerRadius);
- }
- private static ThemedControl CreateTarget() => new ThemedControl();
- private static TestRoot CreateRoot(Control? child, string themeTag = "theme")
- {
- var result = new TestRoot();
- result.Resources.Add(typeof(ThemedControl), CreateTheme(themeTag));
- result.Child = child;
- result.LayoutManager.ExecuteInitialLayoutPass();
- return result;
- }
- }
- public class ThemeFromStyle
- {
- [Fact]
- public void Theme_Is_Applied_When_Attached_To_Logical_Tree()
- {
- var target = CreateTarget();
- Assert.Null(target.Theme);
- Assert.Null(target.Template);
- CreateRoot(target);
- Assert.NotNull(target.Theme);
- Assert.NotNull(target.Template);
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal(Brushes.Red, border.Background);
- target.Classes.Add("foo");
- Assert.Equal(Brushes.Green, border.Background);
- }
- [Fact]
- public void Theme_Can_Be_Changed_By_Style_Class()
- {
- var target = CreateTarget();
- var theme1 = CreateTheme();
- var theme2 = new ControlTheme(typeof(ThemedControl));
- var root = new TestRoot()
- {
- Styles =
- {
- new Style(x => x.OfType<ThemedControl>())
- {
- Setters = { new Setter(StyledElement.ThemeProperty, theme1) }
- },
- new Style(x => x.OfType<ThemedControl>().Class("bar"))
- {
- Setters = { new Setter(StyledElement.ThemeProperty, theme2) }
- },
- }
- };
- root.Child = target;
- root.LayoutManager.ExecuteInitialLayoutPass();
- Assert.Same(theme1, target.Theme);
- Assert.NotNull(target.Template);
- target.Classes.Add("bar");
- Assert.Same(theme2, target.Theme);
- Assert.Null(target.Template);
- }
- [Fact]
- public void Theme_Can_Be_Set_To_LocalValue_While_Updating_Due_To_Style_Class()
- {
- var target = CreateTarget();
- var theme1 = CreateTheme();
- var theme2 = new ControlTheme(typeof(ThemedControl));
- var theme3 = new ControlTheme(typeof(ThemedControl));
- var root = new TestRoot()
- {
- Styles =
- {
- new Style(x => x.OfType<ThemedControl>())
- {
- Setters = { new Setter(StyledElement.ThemeProperty, theme1) }
- },
- new Style(x => x.OfType<ThemedControl>().Class("bar"))
- {
- Setters = { new Setter(StyledElement.ThemeProperty, theme2) }
- },
- }
- };
- root.Child = target;
- root.LayoutManager.ExecuteInitialLayoutPass();
- Assert.Same(theme1, target.Theme);
- Assert.NotNull(target.Template);
- target.Classes.Add("bar");
- // At this point, theme2 has been promoted to a local value internally in StyledElement;
- // make sure that setting a new local value here doesn't cause it to be cleared when we
- // do a layout pass because StyledElement thinks its clearing the promoted theme.
- target.Theme = theme3;
- root.LayoutManager.ExecuteLayoutPass();
- Assert.Same(target.Theme, theme3);
- }
- [Fact]
- public void TemplatedParent_Theme_Change_Applies_To_Children()
- {
- var theme = CreateDerivedTheme();
- var target = CreateTarget();
- Assert.Null(target.Theme);
- Assert.Null(target.Template);
- var root = CreateRoot(target, theme.BasedOn);
- Assert.NotNull(target.Theme);
- Assert.NotNull(target.Template);
- root.Styles.Add(new Style(x => x.OfType<ThemedControl>().Class("foo"))
- {
- Setters = { new Setter(StyledElement.ThemeProperty, theme) }
- });
- root.LayoutManager.ExecuteLayoutPass();
- var border = Assert.IsType<Border>(target.VisualChild);
- Assert.Equal(Brushes.Red, border.Background);
- target.Classes.Add("foo");
- root.LayoutManager.ExecuteLayoutPass();
- Assert.Equal(Brushes.Green, border.Background);
- }
- [Fact]
- public void TemplatedParent_Theme_Change_Applies_Recursively_To_VisualChildren()
- {
- var theme = CreateDerivedTheme();
- var target = CreateTarget();
- Assert.Null(target.Theme);
- Assert.Null(target.Template);
- var root = CreateRoot(target, theme.BasedOn);
- Assert.NotNull(target.Theme);
- Assert.NotNull(target.Template);
- root.Styles.Add(new Style(x => x.OfType<ThemedControl>().Class("foo"))
- {
- Setters = { new Setter(StyledElement.ThemeProperty, theme) }
- });
- root.LayoutManager.ExecuteLayoutPass();
- var border = Assert.IsType<Border>(target.VisualChild);
- var inner = Assert.IsType<Border>(border.Child);
- Assert.Equal(Brushes.Red, border.Background);
- Assert.Equal(Brushes.Red, inner.Background);
- Assert.Equal(null, inner.BorderBrush);
- Assert.Equal(null, inner.BorderBrush);
- target.Classes.Add("foo");
- root.LayoutManager.ExecuteLayoutPass();
- Assert.Equal(Brushes.Green, border.Background);
- Assert.Equal(Brushes.Green, inner.Background);
- Assert.Equal(Brushes.Cyan, inner.BorderBrush);
- Assert.Equal(Brushes.Cyan, inner.BorderBrush);
- }
- private static ThemedControl CreateTarget()
- {
- return new ThemedControl();
- }
- private static TestRoot CreateRoot(Control child, ControlTheme? theme = null)
- {
- var result = new TestRoot()
- {
- Styles =
- {
- new Style(x => x.OfType<ThemedControl>())
- {
- Setters = { new Setter(StyledElement.ThemeProperty, theme ?? CreateTheme()) }
- }
- }
- };
- result.Child = child;
- result.LayoutManager.ExecuteInitialLayoutPass();
- return result;
- }
- }
- private static ControlTheme CreateTheme(string tag = "theme")
- {
- var template = new FuncControlTemplate<ThemedControl>(
- (o, n) => new Border() { Child = new Border() });
- return new ControlTheme
- {
- TargetType = typeof(ThemedControl),
- Setters =
- {
- new Setter(Control.TagProperty, tag),
- new Setter(TemplatedControl.TemplateProperty, template),
- new Setter(TemplatedControl.CornerRadiusProperty, new CornerRadius(5)),
- },
- Children =
- {
- new Style(x => x.Nesting().Template().OfType<Border>())
- {
- Setters =
- {
- new Setter(Border.BackgroundProperty, Brushes.Red),
- new Setter(Control.TagProperty, tag),
- }
- },
- new Style(x => x.Nesting().Class("foo").Template().OfType<Border>())
- {
- Setters = { new Setter(Border.BackgroundProperty, Brushes.Green) }
- },
- }
- };
- }
- private static ControlTheme CreateDerivedTheme()
- {
- return new ControlTheme
- {
- TargetType = typeof(ThemedControl),
- BasedOn = CreateTheme(),
- Setters =
- {
- new Setter(Border.BorderBrushProperty, Brushes.Blue),
- },
- Children =
- {
- new Style(x => x.Nesting().Template().OfType<Border>())
- {
- Setters = { new Setter(Border.BorderBrushProperty, Brushes.Yellow) }
- },
- new Style(x => x.Nesting().Class("foo").Template().OfType<Border>())
- {
- Setters = { new Setter(Border.BorderBrushProperty, Brushes.Cyan) }
- },
- }
- };
- }
- private class ThemedControl : Controls.Primitives.TemplatedControl
- {
- public Visual? VisualChild => VisualChildren?.SingleOrDefault();
- }
- private class ThemedControl2 : Controls.Primitives.TemplatedControl
- {
- public Visual? VisualChild => VisualChildren?.SingleOrDefault();
- }
- private class DerivedThemedControl : ThemedControl
- {
- }
- }
|