From 4f549c16fc35d527da751167b19467f5bd51918a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 31 Aug 2018 14:43:48 +0200 Subject: [PATCH 01/55] Initial --- samples/ControlCatalog/ControlCatalog.csproj | 15 ++ samples/ControlCatalog/MainView.xaml | 6 +- .../ControlCatalog/Pages/TabControlPage.xaml | 124 ++++++++++ .../Pages/TabControlPage.xaml.cs | 80 +++++++ samples/ControlCatalog/SideBar.xaml | 104 +++++---- src/Avalonia.Controls/ContentControl.cs | 4 +- .../Generators/TabItemContainerGenerator.cs | 60 +++++ .../Primitives/HeaderedContentControl.cs | 21 +- src/Avalonia.Controls/TabControl.cs | 176 ++++++++------ src/Avalonia.Controls/TabItem.cs | 73 ++++++ .../Avalonia.Themes.Default.csproj | 8 + src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 + src/Avalonia.Themes.Default/TabControl.xaml | 220 ++++++++++++++---- src/Avalonia.Themes.Default/TabItem.xaml | 45 ++++ .../TabControlTests.cs | 145 ++++++------ 15 files changed, 830 insertions(+), 252 deletions(-) create mode 100644 samples/ControlCatalog/Pages/TabControlPage.xaml create mode 100644 samples/ControlCatalog/Pages/TabControlPage.xaml.cs create mode 100644 src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs create mode 100644 src/Avalonia.Themes.Default/TabItem.xaml diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index dea9b35e24..8eab683049 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,6 +11,9 @@ + + + @@ -33,6 +36,18 @@ + + + + TabControlPage.xaml + + + + + + MSBuild:Compile + + \ No newline at end of file diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 87cb5e9c5c..1b613aee9a 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -2,9 +2,6 @@ xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - - - @@ -21,12 +18,13 @@ - + + diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml new file mode 100644 index 0000000000..5b10e7d790 --- /dev/null +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + This is the first page in the TabControl. + + + + + + + + + + This is the second page in the TabControl. + + + + + + + + + You should not see this. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tab Placement: + + Left + Bottom + Right + Top + + + + + diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs new file mode 100644 index 0000000000..808d90a49c --- /dev/null +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -0,0 +1,80 @@ +using System; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; +using Avalonia.Platform; + +using ReactiveUI; + +namespace ControlCatalog.Pages +{ + using System.Collections.Generic; + + public class TabControlPage : UserControl + { + public TabControlPage() + { + InitializeComponent(); + + DataContext = new PageViewModel + { + Tabs = new[] + { + new TabItemViewModel + { + Header = "Arch", + Text = "This is the first templated tab page.", + Image = LoadBitmap("resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg?assembly=ControlCatalog"), + }, + new TabItemViewModel + { + Header = "Leaf", + Text = "This is the second templated tab page.", + Image = LoadBitmap("resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg?assembly=ControlCatalog"), + }, + new TabItemViewModel + { + Header = "Disabled", + Text = "You should not see this.", + IsEnabled = false, + }, + }, + TabPlacement = Dock.Top, + }; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private IBitmap LoadBitmap(string uri) + { + var assets = AvaloniaLocator.Current.GetService(); + return new Bitmap(assets.Open(new Uri(uri))); + } + + private class PageViewModel : ReactiveObject + { + private Dock _tabPlacement; + + public TabItemViewModel[] Tabs { get; set; } + + public Dock TabPlacement + { + get { return _tabPlacement; } + set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); } + } + } + + private class TabItemViewModel + { + public string Header { get; set; } + public string Text { get; set; } + public IBitmap Image { get; set; } + public bool IsEnabled { get; set; } = true; + } + } +} diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 7d72d1821b..dbab4e4a27 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -1,52 +1,64 @@ - + - + - + - + diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index 6da6da54a5..4621524bdc 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -45,7 +45,7 @@ namespace Avalonia.Controls static ContentControl() { ContentControlMixin.Attach(ContentProperty, x => x.LogicalChildren); - } + } /// /// Gets or sets the content to display. @@ -65,7 +65,7 @@ namespace Avalonia.Controls { get { return GetValue(ContentTemplateProperty); } set { SetValue(ContentTemplateProperty, value); } - } + } /// /// Gets the presenter from the control's template. diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs new file mode 100644 index 0000000000..75788e393f --- /dev/null +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -0,0 +1,60 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Avalonia.Controls.Generators +{ + using Avalonia.Controls.Primitives; + + public class TabItemContainerGenerator : ItemContainerGenerator + { + public TabItemContainerGenerator(TabControl owner) + : base(owner, ContentControl.ContentProperty, ContentControl.ContentTemplateProperty) + { + Owner = owner; + } + + public new TabControl Owner { get; } + + protected override IControl CreateContainer(object item) + { + var tabItem = (TabItem)base.CreateContainer(item); + + tabItem.ParentTabControl = Owner; + + if (tabItem.HeaderTemplate == null) + { + tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty]; + } + + if (tabItem.Header == null) + { + if (item is IHeadered headered) + { + if (tabItem.Header != headered.Header) + { + tabItem.Header = headered.Header; + } + } + else + { + if (!(tabItem.DataContext is IControl)) + { + tabItem.Header = tabItem.DataContext; + } + } + } + + if (!(tabItem.Content is IControl)) + { + tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty]; + } + + if (tabItem.Content == null) + { + tabItem[~ContentControl.ContentProperty] = tabItem[~StyledElement.DataContextProperty]; + } + + return tabItem; + } + } +} diff --git a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs index d67ebfd489..b0517c23f1 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Controls.Templates; + namespace Avalonia.Controls.Primitives { /// @@ -12,7 +14,13 @@ namespace Avalonia.Controls.Primitives /// Defines the property. /// public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); + AvaloniaProperty.Register(nameof(Header)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty HeaderTemplateProperty = + AvaloniaProperty.Register(nameof(HeaderTemplate)); /// /// Gets or sets the header content. @@ -21,6 +29,15 @@ namespace Avalonia.Controls.Primitives { get { return GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } + } + + /// + /// Gets or sets the data template used to display the content of the control. + /// + public IDataTemplate HeaderTemplate + { + get { return GetValue(HeaderTemplateProperty); } + set { SetValue(HeaderTemplateProperty, value); } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 70cf8b4e05..043242543b 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -1,10 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Animation; +using System; + using Avalonia.Controls.Generators; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; +using Avalonia.Input; +using Avalonia.Layout; namespace Avalonia.Controls { @@ -14,28 +18,50 @@ namespace Avalonia.Controls public class TabControl : SelectingItemsControl { /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty PageTransitionProperty = - Avalonia.Controls.Carousel.PageTransitionProperty.AddOwner(); + public static readonly StyledProperty TabStripPlacementProperty = + AvaloniaProperty.Register(nameof(TabStripPlacement), defaultValue: Dock.Top); /// - /// Defines an that selects the content of a . + /// Defines the property. /// - public static readonly IMemberSelector ContentSelector = - new FuncMemberSelector(SelectContent); + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); /// - /// Defines an that selects the header of a . + /// Defines the property. /// - public static readonly IMemberSelector HeaderSelector = - new FuncMemberSelector(SelectHeader); + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty TabStripPlacementProperty = - AvaloniaProperty.Register(nameof(TabStripPlacement), defaultValue: Dock.Top); + public static readonly StyledProperty ContentTemplateProperty = + ContentControl.ContentTemplateProperty.AddOwner(); + + /// + /// The selected content property + /// + public static readonly StyledProperty SelectedContentProperty = + AvaloniaProperty.Register(nameof(SelectedContent)); + + /// + /// The selected content template property + /// + public static readonly StyledProperty SelectedContentTemplateProperty = + AvaloniaProperty.Register(nameof(SelectedContentTemplate)); + + /// + /// The default value for the property. + /// + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new WrapPanel { Orientation = Orientation.Horizontal }); + + internal ItemsPresenter ItemsPresenterPart { get; private set; } + + internal ContentPresenter ContentPart { get; private set; } /// /// Initializes static members of the class. @@ -43,35 +69,26 @@ namespace Avalonia.Controls static TabControl() { SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); - FocusableProperty.OverrideDefaultValue(false); + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); AffectsMeasure(TabStripPlacementProperty); } /// - /// Gets the pages portion of the 's template. + /// Gets or sets the horizontal alignment of the content within the control. /// - public IControl Pages + public HorizontalAlignment HorizontalContentAlignment { - get; - private set; + get { return GetValue(HorizontalContentAlignmentProperty); } + set { SetValue(HorizontalContentAlignmentProperty, value); } } /// - /// Gets the tab strip portion of the 's template. + /// Gets or sets the vertical alignment of the content within the control. /// - public IControl TabStrip + public VerticalAlignment VerticalContentAlignment { - get; - private set; - } - - /// - /// Gets or sets the transition to use when switching tabs. - /// - public IPageTransition PageTransition - { - get { return GetValue(PageTransitionProperty); } - set { SetValue(PageTransitionProperty, value); } + get { return GetValue(VerticalContentAlignmentProperty); } + set { SetValue(VerticalContentAlignmentProperty, value); } } /// @@ -83,67 +100,82 @@ namespace Avalonia.Controls set { SetValue(TabStripPlacementProperty, value); } } - protected override IItemContainerGenerator CreateItemContainerGenerator() + /// + /// Gets or sets the data template used to display the content of the control. + /// + public IDataTemplate ContentTemplate { - // TabControl doesn't actually create items - instead its TabStrip and Carousel - // children create the items. However we want it to be a SelectingItemsControl - // so that it has the Items/SelectedItem etc properties. In this case, we can - // return a null ItemContainerGenerator to disable the creation of item containers. - return null; + get { return GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } } - protected override void OnTemplateApplied(TemplateAppliedEventArgs e) + /// + /// Gets or sets the currently selected content. + /// + /// + /// The content of the selected. + /// + public object SelectedContent { - base.OnTemplateApplied(e); - - TabStrip = e.NameScope.Find("PART_TabStrip"); - Pages = e.NameScope.Find("PART_Content"); + get { return GetValue(SelectedContentProperty); } + set { SetValue(SelectedContentProperty, value); } } /// - /// Selects the content of a tab item. + /// Gets or sets the template for the currently selected content. /// - /// The tab item. - /// The content. - private static object SelectContent(object o) + /// + /// The selected content template. + /// + public IDataTemplate SelectedContentTemplate + { + get { return GetValue(SelectedContentTemplateProperty); } + set { SetValue(SelectedContentTemplateProperty, value); } + } + + protected override IItemContainerGenerator CreateItemContainerGenerator() + { + return new TabItemContainerGenerator(this); + } + + protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { - var content = o as IContentControl; + base.OnTemplateApplied(e); + + ItemsPresenterPart = e.NameScope.Find("PART_ItemsPresenter"); - if (content != null) + if (ItemsPresenterPart == null) { - return content.Content; + throw new NotSupportedException("ItemsPresenter not found."); } - else + + ContentPart = e.NameScope.Find("PART_Content"); + + if (ContentPart == null) { - return o; - } + throw new NotSupportedException("ContentPresenter not found."); + } } - /// - /// Selects the header of a tab item. - /// - /// The tab item. - /// The content. - private static object SelectHeader(object o) + /// + protected override void OnGotFocus(GotFocusEventArgs e) { - var headered = o as IHeadered; - var control = o as IControl; + base.OnGotFocus(e); - if (headered != null) + if (e.NavigationMethod == NavigationMethod.Directional) { - return headered.Header ?? string.Empty; + e.Handled = UpdateSelectionFromEventSource(e.Source); } - else if (control != null) - { - // Non-headered control items should result in TabStripItems with empty content. - // If a TabStrip is created with non IHeadered controls as its items, don't try to - // display the control in the TabStripItem: the content portion will also try to - // display this control, resulting in dual-parentage breakage. - return string.Empty; - } - else + } + + /// + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + base.OnPointerPressed(e); + + if (e.MouseButton == MouseButton.Left) { - return o; + e.Handled = UpdateSelectionFromEventSource(e.Source); } } } diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 4fb68c8b6f..80a3846ab2 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -11,12 +11,20 @@ namespace Avalonia.Controls /// public class TabItem : HeaderedContentControl, ISelectable { + /// + /// Defines the property. + /// + public static readonly StyledProperty TabStripPlacementProperty = + TabControl.TabStripPlacementProperty.AddOwner(); + /// /// Defines the property. /// public static readonly StyledProperty IsSelectedProperty = ListBoxItem.IsSelectedProperty.AddOwner(); + private TabControl _parentTabControl; + /// /// Initializes static members of the class. /// @@ -24,6 +32,19 @@ namespace Avalonia.Controls { SelectableMixin.Attach(IsSelectedProperty); FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); + IsSelectedProperty.Changed.AddClassHandler(x => x.UpdateSelectedContent); + DataContextProperty.Changed.AddClassHandler(x => x.UpdateHeader); + } + + /// + /// Gets the tab strip placement. + /// + /// + /// The tab strip placement. + /// + public Dock TabStripPlacement + { + get { return GetValue(TabStripPlacementProperty); } } /// @@ -34,5 +55,57 @@ namespace Avalonia.Controls get { return GetValue(IsSelectedProperty); } set { SetValue(IsSelectedProperty, value); } } + + internal TabControl ParentTabControl + { + get => _parentTabControl; + set => _parentTabControl = value; + } + + private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) + { + if (Header == null) + { + if (obj.NewValue is IHeadered headered) + { + if (Header != headered.Header) + { + Header = headered.Header; + } + } + else + { + if (!(obj.NewValue is IControl)) + { + Header = obj.NewValue; + } + } + } + else + { + if (Header == obj.OldValue) + { + Header = obj.NewValue; + } + } + } + + private void UpdateSelectedContent(AvaloniaPropertyChangedEventArgs e) + { + if (!IsSelected) + { + return; + } + + if (ParentTabControl.SelectedContentTemplate != ContentTemplate) + { + ParentTabControl.SelectedContentTemplate = ContentTemplate; + } + + if (ParentTabControl.SelectedContent != Content) + { + ParentTabControl.SelectedContent = Content; + } + } } } diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index eceafc2371..4e73afb755 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -2,6 +2,9 @@ netstandard2.0 + + + @@ -15,4 +18,9 @@ + + + MSBuild:Compile + + \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 2b9132ee56..7b8710866a 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -30,6 +30,7 @@ + diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index 76cf190151..43265764ec 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -1,50 +1,172 @@ - - - - - - \ No newline at end of file + + + + + diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml new file mode 100644 index 0000000000..311fb2b973 --- /dev/null +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 322c14c6bd..a5c3881d37 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests TabItem selected; var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = new[] { (selected = new TabItem @@ -61,7 +61,7 @@ namespace Avalonia.Controls.UnitTests var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = items, }; @@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = collection, }; @@ -147,7 +147,7 @@ namespace Avalonia.Controls.UnitTests }, Child = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = collection, } }; @@ -172,7 +172,7 @@ namespace Avalonia.Controls.UnitTests var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), DataContext = "Base", DataTemplates = { @@ -182,41 +182,39 @@ namespace Avalonia.Controls.UnitTests }; ApplyTemplate(target); - var carousel = (Carousel)target.Pages; - var container = (ContentPresenter)carousel.Presenter.Panel.Children.Single(); - container.UpdateChild(); - var dataContext = ((TextBlock)container.Child).DataContext; + target.ContentPart.UpdateChild(); + var dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal(items[0], dataContext); target.SelectedIndex = 1; - container = (ContentPresenter)carousel.Presenter.Panel.Children.Single(); - container.UpdateChild(); - dataContext = ((Button)container.Child).DataContext; + target.ContentPart.UpdateChild(); + dataContext = ((Button)target.ContentPart.Child).DataContext; Assert.Equal(items[1], dataContext); target.SelectedIndex = 2; - dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext; + target.ContentPart.UpdateChild(); + dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal("Base", dataContext); target.SelectedIndex = 3; - container = (ContentPresenter)carousel.Presenter.Panel.Children[0]; - container.UpdateChild(); - dataContext = ((TextBlock)container.Child).DataContext; + target.ContentPart.UpdateChild(); + dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal("Qux", dataContext); target.SelectedIndex = 4; - dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext; + target.ContentPart.UpdateChild(); + dataContext = target.ContentPart.DataContext; Assert.Equal("Base", dataContext); } /// - /// Non-headered control items should result in TabStripItems with empty content. + /// Non-headered control items should result in TabItems with empty header. /// /// - /// If a TabStrip is created with non IHeadered controls as its items, don't try to - /// display the control in the TabStripItem: if the TabStrip is part of a TabControl - /// then *that* will also try to display the control, resulting in dual-parentage + /// If a TabControl is created with non IHeadered controls as its items, don't try to + /// display the control in the header: if the control is part of the header then + /// *that* control would also end up in the content region, resulting in dual-parentage /// breakage. /// [Fact] @@ -230,18 +228,20 @@ namespace Avalonia.Controls.UnitTests var target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = items, }; ApplyTemplate(target); - var result = target.TabStrip.GetLogicalChildren() - .OfType() - .Select(x => x.Content) + var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren(); + + var result = logicalChildren + .OfType() + .Select(x => x.Header) .ToList(); - Assert.Equal(new object[] { string.Empty, string.Empty }, result); + Assert.Equal(new object[] { null, null }, result); } [Fact] @@ -249,7 +249,7 @@ namespace Avalonia.Controls.UnitTests { TabControl target = new TabControl { - Template = new FuncControlTemplate(CreateTabControlTemplate), + Template = TabControlTemplate(), Items = new[] { new TabItem { Header = "Foo" }, @@ -262,70 +262,61 @@ namespace Avalonia.Controls.UnitTests target.SelectedIndex = 2; - var carousel = (Carousel)target.Pages; - var page = (TabItem)carousel.SelectedItem; + var page = (TabItem)target.SelectedItem; Assert.Null(page.Content); } - private Control CreateTabControlTemplate(TabControl parent) + private IControlTemplate TabControlTemplate() { - return new StackPanel - { - Children = - { - new TabStrip - { - Name = "PART_TabStrip", - Template = new FuncControlTemplate(CreateTabStripTemplate), - MemberSelector = TabControl.HeaderSelector, - [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], - [!!TabStrip.SelectedIndexProperty] = parent[!!TabControl.SelectedIndexProperty] - }, - new Carousel - { - Name = "PART_Content", - Template = new FuncControlTemplate(CreateCarouselTemplate), - MemberSelector = TabControl.ContentSelector, - [!Carousel.ItemsProperty] = parent[!TabControl.ItemsProperty], - [!Carousel.SelectedItemProperty] = parent[!TabControl.SelectedItemProperty], - } - } - }; - } + return new FuncControlTemplate(parent => - private Control CreateTabStripTemplate(TabStrip parent) - { - return new ItemsPresenter - { - Name = "PART_ItemsPresenter", - [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], - [!CarouselPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty], - }; + new StackPanel + { + Children = { + new ItemsPresenter + { + Name = "PART_ItemsPresenter", + [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], + [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty], + }, + new ContentPresenter + { + Name = "PART_Content", + [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty], + [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty], + } + } + }); } - private Control CreateCarouselTemplate(Carousel control) + private IControlTemplate TabItemTemplate() { - return new CarouselPresenter - { - Name = "PART_ItemsPresenter", - [!CarouselPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty], - [!CarouselPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty], - [!CarouselPresenter.MemberSelectorProperty] = control[!ItemsControl.MemberSelectorProperty], - [!CarouselPresenter.SelectedIndexProperty] = control[!SelectingItemsControl.SelectedIndexProperty], - [~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty], - }; + return new FuncControlTemplate(parent => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty], + [!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty] + }); } private void ApplyTemplate(TabControl target) { target.ApplyTemplate(); - var carousel = (Carousel)target.Pages; - carousel.ApplyTemplate(); - carousel.Presenter.ApplyTemplate(); - var tabStrip = (TabStrip)target.TabStrip; - tabStrip.ApplyTemplate(); - tabStrip.Presenter.ApplyTemplate(); + + target.Presenter.ApplyTemplate(); + + foreach (var tabItem in target.GetLogicalChildren().OfType()) + { + tabItem.Template = TabItemTemplate(); + + tabItem.ApplyTemplate(); + + ((ContentPresenter)tabItem.Presenter).UpdateChild(); + } + + target.ContentPart.ApplyTemplate(); } private class Item From 63de9f3ddf60ce26f6eadf75f228c8f166012bcb Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 16:22:01 +0200 Subject: [PATCH 02/55] Rework default style --- src/Avalonia.Themes.Default/TabControl.xaml | 114 ++------------------ 1 file changed, 8 insertions(+), 106 deletions(-) diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index 43265764ec..1e5b5c8841 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -44,79 +44,10 @@ - + + From ab97dbb12c87f2188eebd8bd22add64c6464ef81 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 20:28:20 +0200 Subject: [PATCH 03/55] Selector rework --- samples/ControlCatalog/SideBar.xaml | 5 ++--- src/Avalonia.Themes.Default/TabItem.xaml | 16 +++++++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index dbab4e4a27..3552f224fc 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -39,7 +39,7 @@ - - - - - - - - - - - - From 00cc08f979b38625393bd87dd06fe0070fff1faa Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 23:44:04 +0200 Subject: [PATCH 04/55] New selectors --- samples/ControlCatalog/SideBar.xaml | 4 ++-- src/Avalonia.Themes.Default/TabItem.xaml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 3552f224fc..62c200d8d6 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -52,11 +52,11 @@ - - diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml index b51d1426ef..8b30d40d7e 100644 --- a/src/Avalonia.Themes.Default/TabItem.xaml +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -25,19 +25,19 @@ - - - - - From 8985af21ba685036740e8d75d3a8b57b46c0e53d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 7 Sep 2018 00:24:24 +0200 Subject: [PATCH 05/55] Selector fix --- samples/ControlCatalog/SideBar.xaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 62c200d8d6..3ec8e43b07 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -52,12 +52,16 @@ + + From fc709c3a0fc9b72b6eec7038b2be3adf8917c036 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 7 Sep 2018 00:51:25 +0200 Subject: [PATCH 06/55] Partially fix tab strip orientation. --- src/Avalonia.Controls/TabControl.cs | 2 +- src/Avalonia.Themes.Default/TabControl.xaml | 30 +++++++-------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 043242543b..f81e1560c2 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -57,7 +57,7 @@ namespace Avalonia.Controls /// The default value for the property. /// private static readonly FuncTemplate DefaultPanel = - new FuncTemplate(() => new WrapPanel { Orientation = Orientation.Horizontal }); + new FuncTemplate(() => new WrapPanel()); internal ItemsPresenter ItemsPresenterPart { get; private set; } diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index 1e5b5c8841..e551b2e841 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -24,7 +24,6 @@ + - - + From ed460795716b3df233fec43ed791a5a114ebf498 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 7 Sep 2018 01:30:49 +0200 Subject: [PATCH 07/55] Share the same style between TabItem and TabStripItem --- src/Avalonia.Themes.Default/TabItem.xaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml index 8b30d40d7e..6d7cdea1fd 100644 --- a/src/Avalonia.Themes.Default/TabItem.xaml +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -3,12 +3,9 @@ - - - - From e5cac827b18c00e06dbe9c0088225994b7259e82 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Oct 2018 09:39:52 +0200 Subject: [PATCH 08/55] Make TemplatedParent a direct property. During initial layout, `TemplatedParent` is read tens of thousands of times. This being a styled property that used `inherits: true` meant that it was showing up in profiling as taking up a significant amount of time. Make `TemplatedParent` a direct property so that reading it is a simple property access. This doesn't actually complicate the code much at all as once set the property value doesn't change, so `inherited: true` semantics are not that important. --- src/Avalonia.Controls/ItemsControl.cs | 2 -- .../Primitives/TemplatedControl.cs | 19 ++++++++++++++++++- .../Templates/TemplateExtensions.cs | 6 ++++-- src/Avalonia.Styling/StyledElement.cs | 12 ++++++++---- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 9d4cbb9260..d74078c712 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -249,8 +249,6 @@ namespace Avalonia.Controls if (containerControl != null) { ((ISetLogicalParent)containerControl).SetParent(this); - containerControl.SetValue(TemplatedParentProperty, null); - containerControl.UpdateChild(); if (containerControl.Child != null) diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 296134ca48..ba4c5027d0 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -260,7 +260,7 @@ namespace Avalonia.Controls.Primitives var child = template.Build(this); var nameScope = new NameScope(); NameScope.SetNameScope((Control)child, nameScope); - child.SetValue(TemplatedParentProperty, this); + ApplyTemplatedParent(child); RegisterNames(child, nameScope); ((ISetLogicalParent)child).SetParent(this); VisualChildren.Add(child); @@ -326,6 +326,23 @@ namespace Avalonia.Controls.Primitives InvalidateMeasure(); } + /// + /// Sets the TemplatedParent property for the created template children. + /// + /// The control. + private void ApplyTemplatedParent(IControl control) + { + control.SetValue(TemplatedParentProperty, this); + + foreach (var child in control.LogicalChildren) + { + if (child is IControl c) + { + ApplyTemplatedParent(c); + } + } + } + /// /// Registers each control with its name scope. /// diff --git a/src/Avalonia.Controls/Templates/TemplateExtensions.cs b/src/Avalonia.Controls/Templates/TemplateExtensions.cs index 09da737836..18c8bfdeda 100644 --- a/src/Avalonia.Controls/Templates/TemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/TemplateExtensions.cs @@ -24,12 +24,14 @@ namespace Avalonia.Controls.Templates { foreach (IControl child in control.GetVisualChildren()) { - if (child.TemplatedParent == templatedParent) + var childTemplatedParent = child.TemplatedParent; + + if (childTemplatedParent == templatedParent) { yield return child; } - if (child.TemplatedParent != null) + if (childTemplatedParent != null) { foreach (var descendant in GetTemplateChildren(child, templatedParent)) { diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 3d0c840040..e52a1961ba 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -49,8 +49,11 @@ namespace Avalonia /// /// Defines the property. /// - public static readonly StyledProperty TemplatedParentProperty = - AvaloniaProperty.Register(nameof(TemplatedParent), inherits: true); + public static readonly DirectProperty TemplatedParentProperty = + AvaloniaProperty.RegisterDirect( + nameof(TemplatedParent), + o => o.TemplatedParent, + (o ,v) => o.TemplatedParent = v); private int _initCount; private string _name; @@ -62,6 +65,7 @@ namespace Avalonia private Styles _styles; private bool _styled; private Subject _styleDetach = new Subject(); + private ITemplatedControl _templatedParent; private bool _dataContextUpdating; /// @@ -269,8 +273,8 @@ namespace Avalonia /// public ITemplatedControl TemplatedParent { - get { return GetValue(TemplatedParentProperty); } - internal set { SetValue(TemplatedParentProperty, value); } + get => _templatedParent; + internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value); } /// From 44e12491eab44ca65673f83b594f1c558b68e1f8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Oct 2018 09:40:08 +0200 Subject: [PATCH 09/55] Ignore Avalonia.Native in ncrunch. --- .ncrunch/Avalonia.Native.v3.ncrunchproject | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .ncrunch/Avalonia.Native.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Native.v3.ncrunchproject b/.ncrunch/Avalonia.Native.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Native.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From e4838842a11024837936a1016604f42835bb6b30 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Nov 2018 16:24:53 +0200 Subject: [PATCH 10/55] simplify Layout transform no need to be ContentControl --- .../LayoutTransformControl.cs | 39 +++++++------------ src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 - .../LayoutTransformControl.xaml | 13 ------- .../LayoutTransformControlTests.cs | 18 ++------- 4 files changed, 17 insertions(+), 54 deletions(-) delete mode 100644 src/Avalonia.Themes.Default/LayoutTransformControl.xaml diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 87e3853643..73d5e61e03 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -5,20 +5,17 @@ // http://silverlight.codeplex.com/SourceControl/changeset/view/74775#Release/Silverlight4/Source/Controls.Layout.Toolkit/LayoutTransformer/LayoutTransformer.cs // -using Avalonia.Controls.Primitives; -using Avalonia.Media; -using Avalonia.VisualTree; using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reactive.Linq; +using Avalonia.Media; namespace Avalonia.Controls { /// /// Control that implements support for transformations as if applied by LayoutTransform. /// - public class LayoutTransformControl : ContentControl + public class LayoutTransformControl : Decorator { public static readonly AvaloniaProperty LayoutTransformProperty = AvaloniaProperty.Register(nameof(LayoutTransform)); @@ -27,6 +24,9 @@ namespace Avalonia.Controls { LayoutTransformProperty.Changed .AddClassHandler(x => x.OnLayoutTransformChanged); + + ChildProperty.Changed + .AddClassHandler(x => x.OnChildChanged); } /// @@ -38,8 +38,7 @@ namespace Avalonia.Controls set { SetValue(LayoutTransformProperty, value); } } - public Control TransformRoot => _transformRoot ?? - (_transformRoot = this.GetVisualChildren().OfType().FirstOrDefault()); + public IControl TransformRoot => Child; /// /// Provides the behavior for the "Arrange" pass of layout. @@ -132,16 +131,8 @@ namespace Avalonia.Controls return transformedDesiredSize; } - /// - /// Builds the visual tree for the LayoutTransformerControl when a new - /// template is applied. - /// - protected override void OnTemplateApplied(TemplateAppliedEventArgs e) + private void OnChildChanged(AvaloniaPropertyChangedEventArgs e) { - base.OnTemplateApplied(e); - - _matrixTransform = new MatrixTransform(); - if (null != TransformRoot) { TransformRoot.RenderTransform = _matrixTransform; @@ -169,14 +160,14 @@ namespace Avalonia.Controls /// /// RenderTransform/MatrixTransform applied to TransformRoot. /// - private MatrixTransform _matrixTransform; + private MatrixTransform _matrixTransform = new MatrixTransform(); /// /// Transformation matrix corresponding to _matrixTransform. /// private Matrix _transformation; private IDisposable _transformChangedEvent = null; - private Control _transformRoot; + /// /// Returns true if Size a is smaller than Size b in either dimension. /// @@ -215,7 +206,8 @@ namespace Avalonia.Controls /// private void ApplyLayoutTransform() { - if (LayoutTransform == null) return; + if (LayoutTransform == null) + return; // Get the transform matrix and apply it _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); @@ -376,11 +368,8 @@ namespace Avalonia.Controls { var newTransform = e.NewValue as Transform; - if (_transformChangedEvent != null) - { - _transformChangedEvent.Dispose(); - _transformChangedEvent = null; - } + _transformChangedEvent?.Dispose(); + _transformChangedEvent = null; if (newTransform != null) { @@ -392,4 +381,4 @@ namespace Avalonia.Controls ApplyLayoutTransform(); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 2b9132ee56..16705f91c2 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -12,7 +12,6 @@ - diff --git a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml b/src/Avalonia.Themes.Default/LayoutTransformControl.xaml deleted file mode 100644 index b26f053622..0000000000 --- a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml +++ /dev/null @@ -1,13 +0,0 @@ - \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs index d5f9818f89..13c946b549 100644 --- a/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs @@ -1,6 +1,4 @@ -using Avalonia.Controls.Presenters; using Avalonia.Controls.Shapes; -using Avalonia.Controls.Templates; using Avalonia.Media; using Xunit; @@ -311,20 +309,10 @@ namespace Avalonia.Controls.UnitTests { var lt = new LayoutTransformControl() { - LayoutTransform = transform, - Template = new FuncControlTemplate( - p => new ContentPresenter() { Content = p.Content }) + LayoutTransform = transform }; - lt.Content = new Rectangle() { Width = width, Height = height }; - - lt.ApplyTemplate(); - - //we need to force create visual child - //so the measure after is correct - (lt.Presenter as ContentPresenter).UpdateChild(); - - Assert.NotNull(lt.Presenter?.Child); + lt.Child = new Rectangle() { Width = width, Height = height }; lt.Measure(Size.Infinity); lt.Arrange(new Rect(lt.DesiredSize)); @@ -332,4 +320,4 @@ namespace Avalonia.Controls.UnitTests return lt; } } -} \ No newline at end of file +} From 2b98500aaf9c2e0a86761b33695d65d27d2709cb Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Nov 2018 18:00:45 +0200 Subject: [PATCH 11/55] add simple viewbox implementation --- src/Avalonia.Controls/ViewBox.cs | 123 +++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/Avalonia.Controls/ViewBox.cs diff --git a/src/Avalonia.Controls/ViewBox.cs b/src/Avalonia.Controls/ViewBox.cs new file mode 100644 index 0000000000..7492e32937 --- /dev/null +++ b/src/Avalonia.Controls/ViewBox.cs @@ -0,0 +1,123 @@ +using System; +using Avalonia.Media; + +namespace Avalonia.Controls +{ + /// + /// Viewbox is used to scale single child. + /// + /// + public class Viewbox : Decorator + { + /// + /// The stretch property + /// + public static AvaloniaProperty StretchProperty = + AvaloniaProperty.RegisterDirect(nameof(Stretch), + v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform); + + private Stretch _stretch = Stretch.Uniform; + + /// + /// Gets or sets the stretch mode, + /// which determines how child fits into the available space. + /// + /// + /// The stretch. + /// + public Stretch Stretch + { + get => _stretch; + set => SetAndRaise(StretchProperty, ref _stretch, value); + } + + static Viewbox() + { + AffectsMeasure(StretchProperty); + } + + protected override Size MeasureOverride(Size availableSize) + { + var child = Child; + + if (child != null) + { + child.Measure(Size.Infinity); + + var childSize = child.DesiredSize; + + var scale = GetScale(availableSize, childSize, Stretch); + + return childSize * scale; + } + + return new Size(); + } + + protected override Size ArrangeOverride(Size finalSize) + { + var child = Child; + + if (child != null) + { + var childSize = child.DesiredSize; + var scale = GetScale(finalSize, childSize, Stretch); + var scaleTransform = child.RenderTransform as ScaleTransform; + + if (scaleTransform == null) + { + child.RenderTransform = scaleTransform = new ScaleTransform(scale.X, scale.Y); + child.RenderTransformOrigin = RelativePoint.TopLeft; + } + + scaleTransform.ScaleX = scale.X; + scaleTransform.ScaleY = scale.Y; + + child.Arrange(new Rect(childSize)); + + return childSize * scale; + } + + return new Size(); + } + + private static Vector GetScale(Size availableSize, Size childSize, Stretch stretch) + { + double scaleX = 1.0; + double scaleY = 1.0; + + bool validWidth = !double.IsPositiveInfinity(availableSize.Width); + bool validHeight = !double.IsPositiveInfinity(availableSize.Height); + + if (stretch != Stretch.None && (validWidth || validHeight)) + { + scaleX = childSize.Width <= 0.0 ? 0.0 : availableSize.Width / childSize.Width; + scaleY = childSize.Height <= 0.0 ? 0.0 : availableSize.Height / childSize.Height; + + if (!validWidth) + { + scaleX = scaleY; + } + else if (!validHeight) + { + scaleY = scaleX; + } + else + { + switch (stretch) + { + case Stretch.Uniform: + scaleX = scaleY = Math.Min(scaleX, scaleY); + break; + + case Stretch.UniformToFill: + scaleX = scaleY = Math.Max(scaleX, scaleY); + break; + } + } + } + + return new Vector(scaleX, scaleY); + } + } +} From 37d7a49fee2edfbf4ae3c12aa54d2643e0a7c33d Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Fri, 2 Nov 2018 18:51:46 +0200 Subject: [PATCH 12/55] add some tests for Viewbox --- .../ViewboxTests.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 tests/Avalonia.Controls.UnitTests/ViewboxTests.cs diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs new file mode 100644 index 0000000000..ad0f318d2f --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs @@ -0,0 +1,105 @@ +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class ViewboxTests + { + [Fact] + public void Viewbox_Stretch_Uniform_Child() + { + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(200, 100), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(2.0, scaleTransform.ScaleX); + Assert.Equal(2.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_None_Child() + { + var target = new Viewbox() { Stretch = Stretch.None, Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(100, 50), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(1.0, scaleTransform.ScaleX); + Assert.Equal(1.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_Fill_Child() + { + var target = new Viewbox() { Stretch = Stretch.Fill, Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(200, 200), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(2.0, scaleTransform.ScaleX); + Assert.Equal(4.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_UniformToFill_Child() + { + var target = new Viewbox() { Stretch = Stretch.UniformToFill, Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(200, 200), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(4.0, scaleTransform.ScaleX); + Assert.Equal(4.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_Uniform_Child_With_Unrestricted_Width() + { + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(double.PositiveInfinity, 200)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(400, 200), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(4.0, scaleTransform.ScaleX); + Assert.Equal(4.0, scaleTransform.ScaleY); + } + + [Fact] + public void Viewbox_Stretch_Uniform_Child_With_Unrestricted_Height() + { + var target = new Viewbox() { Child = new Rectangle() { Width = 100, Height = 50 } }; + + target.Measure(new Size(200, double.PositiveInfinity)); + target.Arrange(new Rect(new Point(0, 0), target.DesiredSize)); + + Assert.Equal(new Size(200, 100), target.DesiredSize); + var scaleTransform = target.Child.RenderTransform as ScaleTransform; + + Assert.NotNull(scaleTransform); + Assert.Equal(2.0, scaleTransform.ScaleX); + Assert.Equal(2.0, scaleTransform.ScaleY); + } + } +} From 9d5b607707406e0124eac1b6a14694d171024440 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 7 Nov 2018 18:03:43 +0100 Subject: [PATCH 13/55] csproj fix --- src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index b408f2a0be..d854a136e9 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -25,4 +25,3 @@ - From e87967a4ad8e943a2214d74370786edd05ee8504 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 7 Nov 2018 18:14:21 +0100 Subject: [PATCH 14/55] Fix csproj --- samples/ControlCatalog/ControlCatalog.csproj | 14 +++----------- .../Avalonia.Themes.Default.csproj | 13 +++++-------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 13b79d75b5..4af6171a89 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,9 +11,7 @@ - - - + @@ -27,15 +25,9 @@ - - TabControlPage.xaml - - - - - + MSBuild:Compile - + diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index d854a136e9..af1899bab1 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -3,9 +3,6 @@ netstandard2.0 false - - - @@ -16,12 +13,12 @@ + + + + MSBuild:Compile + - - - MSBuild:Compile - - From e1fae9692df4a9ec143b0891e7b9ad1f811c72d8 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 7 Nov 2018 18:19:21 +0100 Subject: [PATCH 15/55] Remove extra ItemGroup --- samples/ControlCatalog/ControlCatalog.csproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 4af6171a89..7b30951d61 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -23,12 +23,6 @@ - - - - MSBuild:Compile - - From e92ff04bba30d2895ae3d85bab821daa5550dfd1 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sat, 10 Nov 2018 22:17:56 +0200 Subject: [PATCH 16/55] set defaut cliptobounds to true for layouttransform and viewbox --- src/Avalonia.Controls/LayoutTransformControl.cs | 2 ++ src/Avalonia.Controls/ViewBox.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 73d5e61e03..3616e7b574 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -22,6 +22,8 @@ namespace Avalonia.Controls static LayoutTransformControl() { + ClipToBoundsProperty.OverrideDefaultValue(true); + LayoutTransformProperty.Changed .AddClassHandler(x => x.OnLayoutTransformChanged); diff --git a/src/Avalonia.Controls/ViewBox.cs b/src/Avalonia.Controls/ViewBox.cs index 7492e32937..94d72f3e6c 100644 --- a/src/Avalonia.Controls/ViewBox.cs +++ b/src/Avalonia.Controls/ViewBox.cs @@ -34,6 +34,7 @@ namespace Avalonia.Controls static Viewbox() { AffectsMeasure(StretchProperty); + ClipToBoundsProperty.OverrideDefaultValue(true); } protected override Size MeasureOverride(Size availableSize) From bf833a5926cafb32a888b38aa68490c2106c5337 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sat, 10 Nov 2018 22:19:35 +0200 Subject: [PATCH 17/55] rename Viewbox file --- src/Avalonia.Controls/{ViewBox.cs => Viewbox.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Avalonia.Controls/{ViewBox.cs => Viewbox.cs} (100%) diff --git a/src/Avalonia.Controls/ViewBox.cs b/src/Avalonia.Controls/Viewbox.cs similarity index 100% rename from src/Avalonia.Controls/ViewBox.cs rename to src/Avalonia.Controls/Viewbox.cs From 4a5ac3f4bc47d318ebe2c95f1e188686245e3f43 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 11 Nov 2018 01:55:27 +0200 Subject: [PATCH 18/55] fix tests --- src/Avalonia.Controls/LayoutTransformControl.cs | 2 +- src/Avalonia.Controls/Viewbox.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 3616e7b574..950d4f34da 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls static LayoutTransformControl() { - ClipToBoundsProperty.OverrideDefaultValue(true); + ClipToBoundsProperty.OverrideDefaultValue(true); LayoutTransformProperty.Changed .AddClassHandler(x => x.OnLayoutTransformChanged); diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index 94d72f3e6c..db753f4ab4 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -33,8 +33,8 @@ namespace Avalonia.Controls static Viewbox() { - AffectsMeasure(StretchProperty); ClipToBoundsProperty.OverrideDefaultValue(true); + AffectsMeasure(StretchProperty); } protected override Size MeasureOverride(Size availableSize) From dbfb9288d15d0f2023019ae1a7ae82b24f61a185 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Sun, 11 Nov 2018 18:03:14 +0200 Subject: [PATCH 19/55] add viewbox page to control catalog --- samples/ControlCatalog/MainView.xaml | 1 + samples/ControlCatalog/Pages/ViewboxPage.xaml | 65 +++++++++++++++++++ .../ControlCatalog/Pages/ViewboxPage.xaml.cs | 18 +++++ 3 files changed, 84 insertions(+) create mode 100644 samples/ControlCatalog/Pages/ViewboxPage.xaml create mode 100644 samples/ControlCatalog/Pages/ViewboxPage.xaml.cs diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index ec3bf799b4..f2630b3c18 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -20,6 +20,7 @@ + diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml new file mode 100644 index 0000000000..89a82c4791 --- /dev/null +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml @@ -0,0 +1,65 @@ + + + + F1 M 16.6309,18.6563C 17.1309, + 8.15625 29.8809,14.1563 29.8809, + 14.1563C 30.8809,11.1563 34.1308, + 11.4063 34.1308,11.4063C 33.5,12 + 34.6309,13.1563 34.6309,13.1563C + 32.1309,13.1562 31.1309,14.9062 + 31.1309,14.9062C 41.1309,23.9062 + 32.6309,27.9063 32.6309,27.9062C + 24.6309,24.9063 21.1309,22.1562 + 16.6309,18.6563 Z M 16.6309,19.9063C + 21.6309,24.1563 25.1309,26.1562 + 31.6309,28.6562C 31.6309,28.6562 + 26.3809,39.1562 18.3809,36.1563C + 18.3809,36.1563 18,38 16.3809,36.9063C + 15,36 16.3809,34.9063 16.3809,34.9063C + 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z + + + + + + Viewbox + A control used to scale single child. + + + None + Fill + Uniform + UniformToFill + + + Hello World! + + + Hello World! + + + Hello World! + + + Hello World! + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs new file mode 100644 index 0000000000..1b5f4bc7f4 --- /dev/null +++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class ViewboxPage : UserControl + { + public ViewboxPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} From f8b196a39b6443ad537b40c99460256a10e7fb1f Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 12 Nov 2018 18:00:22 +0100 Subject: [PATCH 20/55] Minor fixes --- samples/ControlCatalog/ControlCatalog.csproj | 1 - src/Avalonia.Controls/ContentControl.cs | 4 +-- .../Generators/TabItemContainerGenerator.cs | 15 ++++------ .../Primitives/HeaderedContentControl.cs | 2 +- src/Avalonia.Controls/TabControl.cs | 28 ++++++------------- .../Avalonia.Themes.Default.csproj | 5 ---- src/Avalonia.Themes.Default/TabControl.xaml | 1 - 7 files changed, 17 insertions(+), 39 deletions(-) diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 7b30951d61..d0a746f87d 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -11,7 +11,6 @@ - diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index f5e2c14f39..20ec581108 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls { get { return GetValue(ContentTemplateProperty); } set { SetValue(ContentTemplateProperty, value); } - } + } /// /// Gets the presenter from the control's template. @@ -102,4 +102,4 @@ namespace Avalonia.Controls Presenter = presenter; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs index 75788e393f..088f9e30ea 100644 --- a/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs @@ -1,10 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -namespace Avalonia.Controls.Generators -{ - using Avalonia.Controls.Primitives; +using Avalonia.Controls.Primitives; +namespace Avalonia.Controls.Generators +{ public class TabItemContainerGenerator : ItemContainerGenerator { public TabItemContainerGenerator(TabControl owner) @@ -30,10 +30,7 @@ namespace Avalonia.Controls.Generators { if (item is IHeadered headered) { - if (tabItem.Header != headered.Header) - { - tabItem.Header = headered.Header; - } + tabItem.Header = headered.Header; } else { @@ -50,9 +47,9 @@ namespace Avalonia.Controls.Generators } if (tabItem.Content == null) - { + { tabItem[~ContentControl.ContentProperty] = tabItem[~StyledElement.DataContextProperty]; - } + } return tabItem; } diff --git a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs index b0517c23f1..7a46e0f776 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs @@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets or sets the data template used to display the content of the control. + /// Gets or sets the data template used to display the header content of the control. /// public IDataTemplate HeaderTemplate { diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index f81e1560c2..bf57133120 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -1,8 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; - using Avalonia.Controls.Generators; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; @@ -92,7 +90,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the tabstrip placement of the tabcontrol. + /// Gets or sets the tabstrip placement of the TabControl. /// public Dock TabStripPlacement { @@ -101,7 +99,7 @@ namespace Avalonia.Controls } /// - /// Gets or sets the data template used to display the content of the control. + /// Gets or sets the default data template used to display the content of the selected tab. /// public IDataTemplate ContentTemplate { @@ -110,10 +108,10 @@ namespace Avalonia.Controls } /// - /// Gets or sets the currently selected content. + /// Gets or sets the content of the selected tab. /// /// - /// The content of the selected. + /// The content of the selected tab. /// public object SelectedContent { @@ -122,10 +120,10 @@ namespace Avalonia.Controls } /// - /// Gets or sets the template for the currently selected content. + /// Gets or sets the content template for the selected tab. /// /// - /// The selected content template. + /// The content template of the selected tab. /// public IDataTemplate SelectedContentTemplate { @@ -142,19 +140,9 @@ namespace Avalonia.Controls { base.OnTemplateApplied(e); - ItemsPresenterPart = e.NameScope.Find("PART_ItemsPresenter"); - - if (ItemsPresenterPart == null) - { - throw new NotSupportedException("ItemsPresenter not found."); - } + ItemsPresenterPart = e.NameScope.Get("PART_ItemsPresenter"); - ContentPart = e.NameScope.Find("PART_Content"); - - if (ContentPart == null) - { - throw new NotSupportedException("ContentPresenter not found."); - } + ContentPart = e.NameScope.Get("PART_Content"); } /// diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index af1899bab1..638aba4d69 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -13,11 +13,6 @@ - - - - MSBuild:Compile - diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index e551b2e841..969887b38e 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -2,7 +2,6 @@ diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index 4d1c4b1ab0..e94653a3d6 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -52,5 +52,8 @@ 10 12 16 + + 10 + 10 diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 2f0d48db27..e22a62ee7f 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -54,7 +54,7 @@ diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index e94653a3d6..c40305151e 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -53,7 +53,6 @@ 12 16 - 10 - 10 + 10 diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 982af43587..2cb8ce2d24 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -53,7 +53,7 @@ - + + + From b8b7de237d50f44c4925c4787e12215cf4084f41 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 01:06:05 +0200 Subject: [PATCH 47/55] fit dropdown popup in screen --- src/Avalonia.Themes.Default/DropDown.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 451f1c2f23..4684e1c872 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -35,6 +35,7 @@ MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}" MaxHeight="{TemplateBinding MaxDropDownHeight}" PlacementTarget="{TemplateBinding}" + ObeyScreenEdges="True" StaysOpen="False"> From 59a286ba103ff673779e8d58ea95151d3d95d696 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 01:10:29 +0200 Subject: [PATCH 48/55] let dropdown support virtualization if needed --- src/Avalonia.Controls/DropDown.cs | 25 ++++++++++++++++++++++- src/Avalonia.Themes.Default/DropDown.xaml | 5 ++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index a30cc231b7..d17089c127 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -4,8 +4,10 @@ using System; using System.Linq; using Avalonia.Controls.Generators; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; +using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Media; @@ -13,12 +15,17 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { - /// /// A drop-down list control. /// public class DropDown : SelectingItemsControl { + /// + /// The default value for the property. + /// + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new VirtualizingStackPanel()); + /// /// Defines the property. /// @@ -40,6 +47,12 @@ namespace Avalonia.Controls public static readonly DirectProperty SelectionBoxItemProperty = AvaloniaProperty.RegisterDirect(nameof(SelectionBoxItem), o => o.SelectionBoxItem); + /// + /// Defines the property. + /// + public static readonly StyledProperty VirtualizationModeProperty = + ItemsPresenter.VirtualizationModeProperty.AddOwner(); + private bool _isDropDownOpen; private Popup _popup; private object _selectionBoxItem; @@ -49,6 +62,7 @@ namespace Avalonia.Controls /// static DropDown() { + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); KeyDownEvent.AddClassHandler(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel); @@ -81,6 +95,15 @@ namespace Avalonia.Controls set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } } + /// + /// Gets or sets the virtualization mode for the items. + /// + public ItemVirtualizationMode VirtualizationMode + { + get { return GetValue(VirtualizationModeProperty); } + set { SetValue(VirtualizationModeProperty, value); } + } + /// protected override IItemContainerGenerator CreateItemContainerGenerator() { diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 4684e1c872..9603bba107 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -42,8 +42,11 @@ + MemberSelector="{TemplateBinding MemberSelector}" + VirtualizationMode="{TemplateBinding VirtualizationMode}" + /> From c4ab6648338e14489c27e5aef49f2f863e34c3c7 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 13:34:29 +0200 Subject: [PATCH 49/55] Use Adorner Focus styling for focused DropDownItem --- src/Avalonia.Controls/DropDown.cs | 10 ++++++---- src/Avalonia.Themes.Default/DropDown.xaml | 20 ++++++++++--------- src/Avalonia.Themes.Default/DropDownItem.xaml | 5 ----- src/Avalonia.Themes.Default/FocusAdorner.xaml | 5 +++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index d17089c127..2197047936 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -165,10 +165,10 @@ namespace Avalonia.Controls else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 && (e.Key == Key.Up || e.Key == Key.Down)) { - var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => c.Focusable); + var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c)); if (firstChild != null) { - firstChild.Focus(); + FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional); e.Handled = true; } } @@ -223,7 +223,7 @@ namespace Avalonia.Controls private void PopupClosed(object sender, EventArgs e) { - if (Focusable) + if (CanFocus(this)) { Focus(); } @@ -246,13 +246,15 @@ namespace Avalonia.Controls if (IsDropDownOpen && selectedIndex != -1) { var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); - if (container != null && container.Focusable) + if (container != null && CanFocus(container)) { container.Focus(); } } } + private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible; + private void UpdateSelectionBoxItem(object item) { var contentControl = item as IContentControl; diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 9603bba107..ad2be275d6 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -39,15 +39,17 @@ StaysOpen="False"> - - + + - + + @@ -58,4 +60,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/DropDownItem.xaml b/src/Avalonia.Themes.Default/DropDownItem.xaml index a15e770bf5..f542a34d71 100644 --- a/src/Avalonia.Themes.Default/DropDownItem.xaml +++ b/src/Avalonia.Themes.Default/DropDownItem.xaml @@ -4,7 +4,6 @@ - - - diff --git a/src/Avalonia.Themes.Default/FocusAdorner.xaml b/src/Avalonia.Themes.Default/FocusAdorner.xaml index 573c43dc8d..2d5e369573 100644 --- a/src/Avalonia.Themes.Default/FocusAdorner.xaml +++ b/src/Avalonia.Themes.Default/FocusAdorner.xaml @@ -3,7 +3,8 @@ + StrokeDashArray="1,2" + Margin="1"/> - \ No newline at end of file + From 850cbfdbf0b251f92c7b8a37fd8996fd437ee1fd Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 13:42:57 +0200 Subject: [PATCH 50/55] Support out of the box Member Selector in DropDown --- src/Avalonia.Controls/DropDown.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 2197047936..f7cb0aa0ef 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -284,7 +284,8 @@ namespace Avalonia.Controls } else { - SelectionBoxItem = item; + var selector = MemberSelector; + SelectionBoxItem = selector != null ? selector.Select(item) : item; } } From daadd9c674ce27da2a4496bbd62223b865a4dc68 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 14:02:57 +0200 Subject: [PATCH 51/55] support member selector when recycling Dropdown/listboxitems --- .../Primitives/SelectingItemsControl.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index ccbdc71b1d..c40ddc37ad 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -431,9 +431,12 @@ namespace Avalonia.Controls.Primitives { if (i.ContainerControl != null && i.Item != null) { - MarkContainerSelected( - i.ContainerControl, - SelectedItems.Contains(i.Item)); + var ms = MemberSelector; + bool selected = ms == null ? + SelectedItems.Contains(i.Item) : + SelectedItems.OfType().Any(v => Equals(ms.Select(v), i.Item)); + + MarkContainerSelected(i.ContainerControl, selected); } } } From cd7c5eb762aa493a1d73a0d1474ee8e0d40f3477 Mon Sep 17 00:00:00 2001 From: Andrey Kunchev Date: Tue, 20 Nov 2018 14:15:57 +0200 Subject: [PATCH 52/55] Ensure selected DropDownItem is visible when dropdown is open --- src/Avalonia.Controls/DropDown.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index f7cb0aa0ef..93b33e0589 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -246,6 +246,13 @@ namespace Avalonia.Controls if (IsDropDownOpen && selectedIndex != -1) { var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); + + if(container == null && SelectedItems.Count > 0) + { + ScrollIntoView(SelectedItems[0]); + container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); + } + if (container != null && CanFocus(container)) { container.Focus(); From dd571f131d753a86dde6492d342d366f1f4293b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Tue, 20 Nov 2018 17:27:37 +0100 Subject: [PATCH 53/55] Added theme switching support for ControlCatalog --- samples/ControlCatalog/MainView.xaml | 67 ++++++++++++++----------- samples/ControlCatalog/MainView.xaml.cs | 17 +++++++ 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 7dd8098300..7c2ae441d0 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -1,32 +1,41 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + Background="{DynamicResource ThemeBackgroundBrush}" + Foreground="{DynamicResource ThemeForegroundBrush}" + FontSize="{DynamicResource FontSizeNormal}"> + + + Light + Dark + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 0be5d25a09..a498b17bdd 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -2,6 +2,7 @@ using System.Collections; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml.Styling; using Avalonia.Platform; using ControlCatalog.Pages; @@ -27,6 +28,22 @@ namespace ControlCatalog }); } + var light = AvaloniaXamlLoader.Parse(@""); + var dark = AvaloniaXamlLoader.Parse(@""); + var themes = this.Find("Themes"); + themes.SelectionChanged += (sender, e) => + { + switch (themes.SelectedIndex) + { + case 0: + Styles[0] = light; + break; + case 1: + Styles[0] = dark; + break; + } + }; + Styles.Add(light); } private void InitializeComponent() From f55388a2476f1aca75e20c6428c5a247820a48ff Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 22 Nov 2018 11:32:34 +0100 Subject: [PATCH 54/55] Fixes render demo with new tab control --- samples/RenderDemo/MainWindow.xaml | 5 +- samples/RenderDemo/SideBar.xaml | 113 ++++++++++++++++------------- 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index fc5b1ce94d..41164c7780 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -24,9 +24,6 @@ - - - @@ -38,4 +35,4 @@ - \ No newline at end of file + diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml index 26da2cc556..624c1a7b28 100644 --- a/samples/RenderDemo/SideBar.xaml +++ b/samples/RenderDemo/SideBar.xaml @@ -1,53 +1,66 @@ - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > + - - - - - + + + + + From e44eaf9a24a97f4fce0614aea6517366aa22ed0c Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 22 Nov 2018 12:07:47 +0100 Subject: [PATCH 55/55] Improved default control template for TabControl --- .../ControlCatalog/Pages/TabControlPage.xaml | 35 +++---------------- samples/ControlCatalog/SideBar.xaml | 3 +- .../Generators/TabItemContainerGenerator.cs | 2 ++ src/Avalonia.Controls/TabItem.cs | 8 +---- src/Avalonia.Themes.Default/TabControl.xaml | 19 ++++++---- src/Avalonia.Themes.Default/TabItem.xaml | 8 ++++- 6 files changed, 28 insertions(+), 47 deletions(-) diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index 5b10e7d790..430ac28347 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -26,43 +26,19 @@ - - - - - + This is the first page in the TabControl. - - - - - + This is the second page in the TabControl. - - - - - + You should not see this. @@ -82,10 +58,7 @@ + Text="{Binding Header}"> diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 3ec8e43b07..ae9ab7f6a6 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -17,8 +17,7 @@ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" Background="{TemplateBinding Background}"> IsSelectedProperty = ListBoxItem.IsSelectedProperty.AddOwner(); - private TabControl _parentTabControl; - /// /// Initializes static members of the class. /// @@ -56,11 +54,7 @@ namespace Avalonia.Controls set { SetValue(IsSelectedProperty, value); } } - internal TabControl ParentTabControl - { - get => _parentTabControl; - set => _parentTabControl = value; - } + internal TabControl ParentTabControl { get; set; } private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) { diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index 28de70ebb6..a492698feb 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -1,10 +1,5 @@ - + + + + diff --git a/src/Avalonia.Themes.Default/TabItem.xaml b/src/Avalonia.Themes.Default/TabItem.xaml index 6d7cdea1fd..fcdb76524e 100644 --- a/src/Avalonia.Themes.Default/TabItem.xaml +++ b/src/Avalonia.Themes.Default/TabItem.xaml @@ -3,9 +3,11 @@ + + - @@ -36,4 +39,7 @@ +