diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index f11b027173..a07532412d 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; }; BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; }; ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; }; + ED754D262A97306B0078B4DF /* PlatformRenderTimer.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */; }; EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; }; /* End PBXBuildFile section */ @@ -122,6 +123,7 @@ BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = ""; }; BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = ""; }; ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; + ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformRenderTimer.mm; sourceTree = ""; }; EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = ""; }; /* End PBXFileReference section */ @@ -162,6 +164,7 @@ AB7A61E62147C814003C5833 = { isa = PBXGroup; children = ( + ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */, 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */, 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */, 8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */, @@ -333,6 +336,7 @@ 18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */, 18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */, 18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */, + ED754D262A97306B0078B4DF /* PlatformRenderTimer.mm in Sources */, 1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */, 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */, 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */, diff --git a/native/Avalonia.Native/src/OSX/PlatformRenderTimer.mm b/native/Avalonia.Native/src/OSX/PlatformRenderTimer.mm new file mode 100644 index 0000000000..f372cc9047 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/PlatformRenderTimer.mm @@ -0,0 +1,84 @@ +#include "common.h" + +class PlatformRenderTimer : public ComSingleObject +{ +private: + ComPtr _callback; + CVDisplayLinkRef _displayLink; + +public: + FORWARD_IUNKNOWN() + virtual HRESULT RegisterTick ( + IAvnActionCallback* callback) override + { + START_COM_CALL; + + @autoreleasepool + { + if (_displayLink != nil) + { + return E_UNEXPECTED; + } + + _callback = callback; + auto result = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); + if (result != 0) + { + return E_FAIL; + } + + result = CVDisplayLinkSetOutputCallback(_displayLink, OnTick, this); + if (result != 0) + { + return E_FAIL; + } + } + return S_OK; + } + + virtual void Start () override + { + START_COM_CALL; + + @autoreleasepool + { + if (CVDisplayLinkIsRunning(_displayLink) == false) { + CVDisplayLinkStart(_displayLink); + } + } + } + + virtual void Stop () override + { + START_COM_CALL; + + @autoreleasepool + { + if (CVDisplayLinkIsRunning(_displayLink) == true) { + CVDisplayLinkStop(_displayLink); + } + } + } + + virtual bool RunsInBackground () override + { + START_COM_CALL; + + @autoreleasepool + { + return true; + } + } + + static CVReturn OnTick(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) + { + PlatformRenderTimer *object = (PlatformRenderTimer *)displayLinkContext; + object->_callback->Run(); + return kCVReturnSuccess; + } +}; + +extern IAvnPlatformRenderTimer* CreatePlatformRenderTimer() +{ + return new PlatformRenderTimer(); +} diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 672525c64a..44441ee15d 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -32,6 +32,7 @@ extern IAvnApplicationCommands* CreateApplicationCommands(); extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition(); extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent); extern IAvnPlatformSettings* CreatePlatformSettings(); +extern IAvnPlatformRenderTimer* CreatePlatformRenderTimer(); extern void SetAppMenu(IAvnMenu *menu); extern void SetServicesMenu (IAvnMenu* menu); extern IAvnMenu* GetAppMenu (); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 3fddb72529..41d6fd37ab 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -446,6 +446,17 @@ public: return S_OK; } } + + virtual HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv) override + { + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreatePlatformRenderTimer(); + return S_OK; + } + } }; extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index a2936f1857..085e60da5f 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1278,14 +1278,11 @@ namespace Avalonia.Media.TextFormatting var start = GetParagraphOffsetX(width, widthIncludingWhitespace); var overhangLeading = Math.Max(0, bounds.Left - start); var overhangTrailing = Math.Max(0, bounds.Width - widthIncludingWhitespace); - var hasOverflowed = overhangLeading + widthIncludingWhitespace + overhangTrailing > _paragraphWidth; + var hasOverflowed = width > _paragraphWidth; if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) { - if (lineHeight > height) - { - height = lineHeight; - } + height = lineHeight; } return new TextLineMetrics diff --git a/src/Avalonia.Controls/Documents/Inline.cs b/src/Avalonia.Controls/Documents/Inline.cs index 23b806583e..7de3ce9d98 100644 --- a/src/Avalonia.Controls/Documents/Inline.cs +++ b/src/Avalonia.Controls/Documents/Inline.cs @@ -10,12 +10,14 @@ namespace Avalonia.Controls.Documents /// public abstract class Inline : TextElement { + // TODO12: change the field type to an AttachedProperty for consistency (breaking change) /// /// AvaloniaProperty for property. /// public static readonly StyledProperty TextDecorationsProperty = - AvaloniaProperty.Register( - nameof(TextDecorations)); + AvaloniaProperty.RegisterAttached( + nameof(TextDecorations), + inherits: true); /// /// AvaloniaProperty for property. @@ -43,7 +45,27 @@ namespace Avalonia.Controls.Documents get { return GetValue(BaselineAlignmentProperty); } set { SetValue(BaselineAlignmentProperty, value); } } + + /// + /// Gets the value of the attached on a control. + /// + /// The control. + /// The font style. + public static TextDecorationCollection? GetTextDecorations(Control control) + { + return control.GetValue(TextDecorationsProperty); + } + /// + /// Sets the value of the attached on a control. + /// + /// The control. + /// The property value to set. + public static void SetTextDecorations(Control control, TextDecorationCollection? value) + { + control.SetValue(TextDecorationsProperty, value); + } + internal abstract void BuildTextRun(IList textRuns); internal abstract void AppendText(StringBuilder stringBuilder); diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 83aa88a7b6..41e9e7c111 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -67,6 +67,9 @@ namespace Avalonia.Controls public static readonly StyledProperty DisplayMemberBindingProperty = AvaloniaProperty.Register(nameof(DisplayMemberBinding)); + private static readonly AttachedProperty AppliedItemContainerTheme = + AvaloniaProperty.RegisterAttached("AppliedItemContainerTheme"); + /// /// Gets or sets the to use for binding to the display member of each item. /// @@ -663,13 +666,26 @@ namespace Avalonia.Controls internal void PrepareItemContainer(Control container, object? item, int index) { - var itemContainerTheme = ItemContainerTheme; - - if (itemContainerTheme is not null && - !container.IsSet(ThemeProperty) && - StyledElement.GetStyleKey(container) == itemContainerTheme.TargetType) + // If the container has no theme set, or we've already applied our ItemContainerTheme + // (and it hasn't changed since) then we're in control of the container's Theme and may + // need to update it. + if (!container.IsSet(ThemeProperty) || container.GetValue(AppliedItemContainerTheme) == container.Theme) { - container.Theme = itemContainerTheme; + var itemContainerTheme = ItemContainerTheme; + + if (itemContainerTheme?.TargetType?.IsAssignableFrom(GetStyleKey(container)) == true) + { + // We have an ItemContainerTheme and it matches the container. Set the Theme + // property, and mark the container as having had ItemContainerTheme applied. + container.SetCurrentValue(ThemeProperty, itemContainerTheme); + container.SetValue(AppliedItemContainerTheme, itemContainerTheme); + } + else + { + // Otherwise clear the theme and the AppliedItemContainerTheme property. + container.ClearValue(ThemeProperty); + container.ClearValue(AppliedItemContainerTheme); + } } if (item is not Control) diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 2e570e25c7..7f939f2d3a 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -3,6 +3,7 @@ using Avalonia.Input; using Avalonia.Reactive; using Avalonia.Media; using Avalonia.Media.TextFormatting; +using System; namespace Avalonia.Controls.Primitives { @@ -68,11 +69,15 @@ namespace Avalonia.Controls.Primitives if (underscore != -1 && ShowAccessKey) { var rect = TextLayout!.HitTestTextPosition(underscore); - var offset = new Vector(0, -1.5); + + var x1 = Math.Round(rect.Left, MidpointRounding.AwayFromZero); + var x2 = Math.Round(rect.Right, MidpointRounding.AwayFromZero); + var y = Math.Round(rect.Bottom, MidpointRounding.AwayFromZero) - 1.5; + context.DrawLine( new Pen(Foreground, 1), - rect.BottomLeft + offset, - rect.BottomRight + offset); + new Point(x1, y), + new Point(x2, y)); } } diff --git a/src/Avalonia.Controls/Shapes/Polygon.cs b/src/Avalonia.Controls/Shapes/Polygon.cs index 78def84448..e51e117e33 100644 --- a/src/Avalonia.Controls/Shapes/Polygon.cs +++ b/src/Avalonia.Controls/Shapes/Polygon.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Avalonia.Media; +using Avalonia.Data; namespace Avalonia.Controls.Shapes { @@ -15,9 +16,9 @@ namespace Avalonia.Controls.Shapes public Polygon() { - Points = new Points(); + SetValue(PointsProperty, new Points(), BindingPriority.Template); } - + public IList Points { get => GetValue(PointsProperty); diff --git a/src/Avalonia.Controls/Shapes/Polyline.cs b/src/Avalonia.Controls/Shapes/Polyline.cs index 2533794f89..84fccb66f1 100644 --- a/src/Avalonia.Controls/Shapes/Polyline.cs +++ b/src/Avalonia.Controls/Shapes/Polyline.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; using Avalonia.Media; +using Avalonia.Data; namespace Avalonia.Controls.Shapes { - public class Polyline: Shape + public class Polyline : Shape { public static readonly StyledProperty> PointsProperty = AvaloniaProperty.Register>("Points"); @@ -16,7 +17,7 @@ namespace Avalonia.Controls.Shapes public Polyline() { - Points = new Points(); + SetValue(PointsProperty, new Points(), BindingPriority.Template); } public IList Points diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 46503b9185..6af572cc7a 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -135,7 +135,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty TextDecorationsProperty = - AvaloniaProperty.Register(nameof(TextDecorations)); + Inline.TextDecorationsProperty.AddOwner(); /// /// Defines the property. diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Bold.ttf b/src/Avalonia.Fonts.Inter/Assets/Inter-Bold.ttf index a372c5fcca..8e82c70d10 100644 Binary files a/src/Avalonia.Fonts.Inter/Assets/Inter-Bold.ttf and b/src/Avalonia.Fonts.Inter/Assets/Inter-Bold.ttf differ diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf b/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf index 13ef261fff..64aee30a4e 100644 Binary files a/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf and b/src/Avalonia.Fonts.Inter/Assets/Inter-ExtraLight.ttf differ diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Light.ttf b/src/Avalonia.Fonts.Inter/Assets/Inter-Light.ttf index 9f75ff2780..9e265d8905 100644 Binary files a/src/Avalonia.Fonts.Inter/Assets/Inter-Light.ttf and b/src/Avalonia.Fonts.Inter/Assets/Inter-Light.ttf differ diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Medium.ttf b/src/Avalonia.Fonts.Inter/Assets/Inter-Medium.ttf index 2c5e25453c..b53fb1c4ac 100644 Binary files a/src/Avalonia.Fonts.Inter/Assets/Inter-Medium.ttf and b/src/Avalonia.Fonts.Inter/Assets/Inter-Medium.ttf differ diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Regular.ttf b/src/Avalonia.Fonts.Inter/Assets/Inter-Regular.ttf index c28fe4d744..8d4eebf206 100644 Binary files a/src/Avalonia.Fonts.Inter/Assets/Inter-Regular.ttf and b/src/Avalonia.Fonts.Inter/Assets/Inter-Regular.ttf differ diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.ttf b/src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.ttf index d085eb4994..c6aeeb16a6 100644 Binary files a/src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.ttf and b/src/Avalonia.Fonts.Inter/Assets/Inter-SemiBold.ttf differ diff --git a/src/Avalonia.Fonts.Inter/Assets/Inter-Thin.ttf b/src/Avalonia.Fonts.Inter/Assets/Inter-Thin.ttf index 016a13482b..7aed55d560 100644 Binary files a/src/Avalonia.Fonts.Inter/Assets/Inter-Thin.ttf and b/src/Avalonia.Fonts.Inter/Assets/Inter-Thin.ttf differ diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index b7c58cded9..f24c6fa96f 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -108,7 +108,7 @@ namespace Avalonia.Native .Bind().ToConstant(new NativePlatformSettings(_factory.CreatePlatformSettings())) .Bind().ToConstant(this) .Bind().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new AvaloniaNativeRenderTimer(_factory.CreatePlatformRenderTimer())) .Bind().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind().ToConstant(new AvaloniaNativeDragSource(_factory)) .Bind().ToConstant(applicationPlatform) diff --git a/src/Avalonia.Native/AvaloniaNativeRenderTimer.cs b/src/Avalonia.Native/AvaloniaNativeRenderTimer.cs new file mode 100644 index 0000000000..9021fdf345 --- /dev/null +++ b/src/Avalonia.Native/AvaloniaNativeRenderTimer.cs @@ -0,0 +1,58 @@ +using System; +using System.Diagnostics; +using Avalonia.Native.Interop; +using Avalonia.Rendering; +#nullable enable + +namespace Avalonia.Native; + +internal sealed class AvaloniaNativeRenderTimer : NativeCallbackBase, IRenderTimer, IAvnActionCallback +{ + private readonly IAvnPlatformRenderTimer _platformRenderTimer; + private readonly Stopwatch _stopwatch; + private Action? _tick; + private int _subscriberCount; + private bool registered; + + public AvaloniaNativeRenderTimer(IAvnPlatformRenderTimer platformRenderTimer) + { + _platformRenderTimer = platformRenderTimer; + _stopwatch = Stopwatch.StartNew(); + } + + public event Action Tick + { + add + { + _tick += value; + + if (!registered) + { + registered = true; + _platformRenderTimer.RegisterTick(this); + } + + if (_subscriberCount++ == 0) + { + _platformRenderTimer.Start(); + } + } + + remove + { + if (--_subscriberCount == 0) + { + _platformRenderTimer.Stop(); + } + + _tick -= value; + } + } + + public bool RunsInBackground => _platformRenderTimer.RunsInBackground().FromComBool(); + + public void Run() + { + _tick?.Invoke(_stopwatch.Elapsed); + } +} diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 0911e5ffff..7eac1d33a8 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -507,6 +507,7 @@ interface IAvaloniaNativeFactory : IUnknown HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv); HRESULT CreatePlatformSettings(IAvnPlatformSettings** ppv); HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv); + HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv); } [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] @@ -999,3 +1000,12 @@ interface IAvnPlatformBehaviorInhibition : IUnknown { void SetInhibitAppSleep(bool inhibitAppSleep, char* reason); } + +[uuid(22edf20d-5803-2d3f-9247-b4842e5e9322)] +interface IAvnPlatformRenderTimer : IUnknown +{ + HRESULT RegisterTick(IAvnActionCallback* callback); + void Start(); + void Stop(); + bool RunsInBackground(); +} diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 58e4ec1b75..7d3c1fe4f4 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -14,6 +14,7 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Markup.Xaml.Templates; +using Avalonia.Media; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -128,6 +129,155 @@ namespace Avalonia.Controls.UnitTests Assert.Same(container.Theme, theme); } + [Fact] + public void Container_Should_Have_Theme_Set_To_ItemContainerTheme_With_Base_TargetType() + { + using var app = Start(); + var theme = new ControlTheme { TargetType = typeof(Control) }; + var target = CreateTarget( + itemsSource: new[] { "Foo" }, + itemContainerTheme: theme); + + var container = GetContainer(target); + + Assert.Same(container.Theme, theme); + } + + [Fact] + public void ItemContainerTheme_Can_Be_Changed() + { + using var app = Start(); + + var theme1 = new ControlTheme + { + TargetType = typeof(ContentPresenter), + Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Red) } + }; + + var theme2 = new ControlTheme + { + TargetType = typeof(ContentPresenter), + Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Green) } + }; + + var target = CreateTarget( + itemsSource: new[] { "Foo" }, + itemContainerTheme: theme1); + + var container = GetContainer(target); + + Assert.Same(container.Theme, theme1); + Assert.Equal(container.Background, Brushes.Red); + + target.ItemContainerTheme = theme2; + + container = GetContainer(target); + Assert.Same(container.Theme, theme2); + Assert.Equal(container.Background, Brushes.Green); + } + + [Fact] + public void ItemContainerTheme_Can_Be_Changed_Virtualizing() + { + using var app = Start(); + + var theme1 = new ControlTheme + { + TargetType = typeof(ContentPresenter), + Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Red) } + }; + + var theme2 = new ControlTheme + { + TargetType = typeof(ContentPresenter), + Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Green) } + }; + + var itemsPanel = new FuncTemplate(() => new VirtualizingStackPanel()); + var target = CreateTarget( + itemsSource: new[] { "Foo" }, + itemContainerTheme: theme1, + itemsPanel: itemsPanel); + + var container = GetContainer(target); + + Assert.Same(container.Theme, theme1); + Assert.Equal(container.Background, Brushes.Red); + + target.ItemContainerTheme = theme2; + Layout(target); + + container = GetContainer(target); + Assert.Same(container.Theme, theme2); + Assert.Equal(container.Background, Brushes.Green); + } + + [Fact] + public void ItemContainerTheme_Can_Be_Cleared() + { + using var app = Start(); + + var theme = new ControlTheme + { + TargetType = typeof(ContentPresenter), + Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Red) } + }; + + var target = CreateTarget( + itemsSource: new[] { "Foo" }, + itemContainerTheme: theme); + + var container = GetContainer(target); + + Assert.Same(container.Theme, theme); + Assert.Equal(container.Background, Brushes.Red); + + target.ItemContainerTheme = null; + + container = GetContainer(target); + Assert.Null(container.Theme); + Assert.Null(container.Background); + } + + [Fact] + public void ItemContainerTheme_Should_Not_Override_LocalValue_Theme() + { + using var app = Start(); + + var theme1 = new ControlTheme + { + TargetType = typeof(ContentPresenter), + Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Red) } + }; + + var theme2 = new ControlTheme + { + TargetType = typeof(Control), + Setters = { new Setter(ContentPresenter.BackgroundProperty, Brushes.Green) } + }; + + var items = new object[] + { + new ContentPresenter(), + new ContentPresenter + { + Theme = theme2 + }, + }; + + var target = CreateTarget( + itemsSource: items, + itemContainerTheme: theme1); + + Assert.Same(theme1, GetContainer(target, 0).Theme); + Assert.Same(theme2, GetContainer(target, 1).Theme); + + target.ItemContainerTheme = null; + + Assert.Null(GetContainer(target, 0).Theme); + Assert.Same(theme2, GetContainer(target, 1).Theme); + } + [Fact] public void Container_Should_Have_LogicalParent_Set_To_ItemsControl() { @@ -851,6 +1001,7 @@ namespace Avalonia.Controls.UnitTests IList? itemsSource = null, ControlTheme? itemContainerTheme = null, IDataTemplate? itemTemplate = null, + ITemplate? itemsPanel = null, IEnumerable? dataTemplates = null, bool performLayout = true) { @@ -861,6 +1012,7 @@ namespace Avalonia.Controls.UnitTests itemsSource: itemsSource, itemContainerTheme: itemContainerTheme, itemTemplate: itemTemplate, + itemsPanel: itemsPanel, dataTemplates: dataTemplates, performLayout: performLayout); } diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index eb1d6f5ea4..fd3d80729a 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -202,5 +202,26 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(0, target.Inlines.Count); } } + + [Fact] + public void Setting_TextDecorations_Should_Update_Inlines() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new TextBlock(); + + target.Inlines.Add(new Run("Hello World")); + + Assert.Equal(1, target.Inlines.Count); + + Assert.Null(target.Inlines[0].TextDecorations); + + var underline = TextDecorations.Underline; + + target.TextDecorations = underline; + + Assert.Equal(underline, target.Inlines[0].TextDecorations); + } + } } }