Browse Source

Merge branch 'master' into fixes/1614-tile-brush-dpi-compatibility

pull/2115/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
74294b2cb3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .ncrunch/Avalonia.Native.v3.ncrunchproject
  2. 68
      samples/ControlCatalog/MainView.xaml
  3. 17
      samples/ControlCatalog/MainView.xaml.cs
  4. 97
      samples/ControlCatalog/Pages/TabControlPage.xaml
  5. 80
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  6. 65
      samples/ControlCatalog/Pages/ViewboxPage.xaml
  7. 18
      samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
  8. 110
      samples/ControlCatalog/SideBar.xaml
  9. 5
      samples/RenderDemo/MainWindow.xaml
  10. 113
      samples/RenderDemo/SideBar.xaml
  11. 12
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  12. 87
      src/Avalonia.Controls/DropDown.cs
  13. 32
      src/Avalonia.Controls/DropDownItem.cs
  14. 59
      src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
  15. 2
      src/Avalonia.Controls/ItemsControl.cs
  16. 41
      src/Avalonia.Controls/LayoutTransformControl.cs
  17. 21
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
  18. 9
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  19. 19
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  20. 164
      src/Avalonia.Controls/TabControl.cs
  21. 67
      src/Avalonia.Controls/TabItem.cs
  22. 6
      src/Avalonia.Controls/Templates/TemplateExtensions.cs
  23. 124
      src/Avalonia.Controls/Viewbox.cs
  24. 12
      src/Avalonia.Styling/StyledElement.cs
  25. 2
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  26. 2
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  27. 2
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  28. 20
      src/Avalonia.Themes.Default/DropDown.xaml
  29. 2
      src/Avalonia.Themes.Default/DropDownItem.xaml
  30. 5
      src/Avalonia.Themes.Default/FocusAdorner.xaml
  31. 13
      src/Avalonia.Themes.Default/LayoutTransformControl.xaml
  32. 22
      src/Avalonia.Themes.Default/ScrollBar.xaml
  33. 111
      src/Avalonia.Themes.Default/TabControl.xaml
  34. 45
      src/Avalonia.Themes.Default/TabItem.xaml
  35. 164
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  36. 16
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
  37. 8
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  38. 7
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  39. 3
      src/Windows/Avalonia.Win32/WindowImpl.cs
  40. 18
      tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs
  41. 145
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  42. 105
      tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
  43. 2
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  44. 100
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs
  45. 58
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs
  46. 4
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs

5
.ncrunch/Avalonia.Native.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

68
samples/ControlCatalog/MainView.xaml

@ -1,33 +1,41 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<TabControl Classes="sidebar" Name="Sidebar"> Background="{DynamicResource ThemeBackgroundBrush}"
<TabControl.PageTransition> Foreground="{DynamicResource ThemeForegroundBrush}"
<CrossFade Duration="0.25"/> FontSize="{DynamicResource FontSizeNormal}">
</TabControl.PageTransition> <Grid>
<TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem> <DropDown x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<TabItem Header="Border"><pages:BorderPage/></TabItem> <DropDownItem>Light</DropDownItem>
<TabItem Header="Button"><pages:ButtonPage/></TabItem> <DropDownItem>Dark</DropDownItem>
<TabItem Header="ButtonSpinner"><pages:ButtonSpinnerPage/></TabItem> </DropDown>
<TabItem Header="Calendar"><pages:CalendarPage/></TabItem> <TabControl Classes="sidebar" Name="Sidebar">
<TabItem Header="Canvas"><pages:CanvasPage/></TabItem> <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem> <TabItem Header="Border"><pages:BorderPage/></TabItem>
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem> <TabItem Header="Button"><pages:ButtonPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem> <TabItem Header="ButtonSpinner"><pages:ButtonSpinnerPage/></TabItem>
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem> <TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem> <TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
<TabItem Header="DropDown"><pages:DropDownPage/></TabItem> <TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem> <TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem> <TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem> <TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
<TabItem Header="ListBox"><pages:ListBoxPage/></TabItem> <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem> <TabItem Header="DropDown"><pages:DropDownPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem> <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem> <TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem> <TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem> <TabItem Header="ListBox"><pages:ListBoxPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem> <TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem> <TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem> <TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
</TabControl> <TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
</TabControl>
</Grid>
</UserControl> </UserControl>

17
samples/ControlCatalog/MainView.xaml.cs

@ -2,6 +2,7 @@ using System.Collections;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform; using Avalonia.Platform;
using ControlCatalog.Pages; using ControlCatalog.Pages;
@ -27,6 +28,22 @@ namespace ControlCatalog
}); });
} }
var light = AvaloniaXamlLoader.Parse<StyleInclude>(@"<StyleInclude xmlns='https://github.com/avaloniaui' Source='resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default'/>");
var dark = AvaloniaXamlLoader.Parse<StyleInclude>(@"<StyleInclude xmlns='https://github.com/avaloniaui' Source='resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default'/>");
var themes = this.Find<DropDown>("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() private void InitializeComponent()

97
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -0,0 +1,97 @@
<UserControl xmlns="https://github.com/avaloniaui">
<DockPanel>
<TextBlock
DockPanel.Dock="Top"
Classes="h1"
Text="TabControl"
Margin="4">
</TextBlock>
<TextBlock
DockPanel.Dock="Top"
Classes="h2"
Text="A tab control that displays a tab strip along with the content of the selected tab"
Margin="4">
</TextBlock>
<Grid
ColumnDefinitions="*,*"
RowDefinitions="*,100">
<DockPanel
Grid.Column="0"
Margin="4">
<TextBlock
DockPanel.Dock="Top"
Classes="h1"
Text="From Inline TabItems">
</TextBlock>
<TabControl
Margin="0 16"
TabStripPlacement="{Binding TabPlacement}">
<TabItem Header="Arch">
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock>This is the first page in the TabControl.</TextBlock>
<Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg" Width="300"/>
</StackPanel>
</TabItem>
<TabItem Header="Leaf">
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock>This is the second page in the TabControl.</TextBlock>
<Image Source="resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg" Width="300"/>
</StackPanel>
</TabItem>
<TabItem Header="Disabled" IsEnabled="False">
<TextBlock>You should not see this.</TextBlock>
</TabItem>
</TabControl>
</DockPanel>
<DockPanel
Grid.Column="1"
Margin="4">
<TextBlock
DockPanel.Dock="Top"
Classes="h1"
Text="From DataTemplate">
</TextBlock>
<TabControl
Items="{Binding Tabs}"
Margin="0 16"
TabStripPlacement="{Binding TabPlacement}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Header}">
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Spacing="8">
<TextBlock Text="{Binding Text}"/>
<Image Source="{Binding Image}" Width="300"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.Styles>
<Style Selector="TabItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
</TabControl.Styles>
</TabControl>
</DockPanel>
<StackPanel
Grid.Row="1"
Grid.ColumnSpan="2"
Orientation="Horizontal"
Spacing="8"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Tab Placement:</TextBlock>
<DropDown SelectedIndex="{Binding TabPlacement, Mode=TwoWay}">
<DropDownItem>Left</DropDownItem>
<DropDownItem>Bottom</DropDownItem>
<DropDownItem>Right</DropDownItem>
<DropDownItem>Top</DropDownItem>
</DropDown>
</StackPanel>
</Grid>
</DockPanel>
</UserControl>

80
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<IAssetLoader>();
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;
}
}
}

65
samples/ControlCatalog/Pages/ViewboxPage.xaml

@ -0,0 +1,65 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Resources>
<StreamGeometry x:Key="Acorn">
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
</StreamGeometry>
</UserControl.Resources>
<Grid RowDefinitions="Auto,*">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Viewbox</TextBlock>
<TextBlock Classes="h2">A control used to scale single child.</TextBlock>
</StackPanel>
<Grid ColumnDefinitions="Auto,*,*"
RowDefinitions="*,*,*,*"
Grid.Row="1" Margin="48"
MaxWidth="400">
<TextBlock Grid.Row="0" VerticalAlignment="Center">None</TextBlock>
<TextBlock Grid.Row="1" VerticalAlignment="Center">Fill</TextBlock>
<TextBlock Grid.Row="2" VerticalAlignment="Center">Uniform</TextBlock>
<TextBlock Grid.Row="3" VerticalAlignment="Center">UniformToFill</TextBlock>
<Viewbox Grid.Row="0" Grid.Column="1" Stretch="None">
<TextBlock>Hello World!</TextBlock>
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="1" Stretch="Fill">
<TextBlock>Hello World!</TextBlock>
</Viewbox>
<Viewbox Grid.Row="2" Grid.Column="1" Stretch="Uniform">
<TextBlock>Hello World!</TextBlock>
</Viewbox>
<Viewbox Grid.Row="3" Grid.Column="1" Stretch="UniformToFill">
<TextBlock>Hello World!</TextBlock>
</Viewbox>
<Viewbox Grid.Row="0" Grid.Column="2" Stretch="None">
<Path Fill="Blue" Data="{StaticResource Acorn}"/>
</Viewbox>
<Viewbox Grid.Row="1" Grid.Column="2" Stretch="Fill">
<Path Fill="Blue" Data="{StaticResource Acorn}"/>
</Viewbox>
<Viewbox Grid.Row="2" Grid.Column="2" Stretch="Uniform">
<Path Fill="Blue" Data="{StaticResource Acorn}"/>
</Viewbox>
<Viewbox Grid.Row="3" Grid.Column="2" Stretch="UniformToFill">
<Path Fill="Blue" Data="{StaticResource Acorn}"/>
</Viewbox>
</Grid>
</Grid>
</UserControl>

18
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);
}
}
}

110
samples/ControlCatalog/SideBar.xaml

@ -1,52 +1,66 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Style Selector="TabControl.sidebar"> <Style Selector="TabControl.sidebar">
<Setter Property="Template"> <Setter Property="TabStripPlacement" Value="Left"/>
<ControlTemplate> <Setter Property="Padding" Value="8 0 0 0"/>
<DockPanel> <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
<ScrollViewer MinWidth="190" Background="{DynamicResource ThemeAccentBrush}" DockPanel.Dock="Left"> <Setter Property="Template">
<TabStrip Name="PART_TabStrip" <ControlTemplate>
MemberSelector="{x:Static TabControl.HeaderSelector}" <Border
Items="{TemplateBinding Items}" Margin="{TemplateBinding Margin}"
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"> BorderBrush="{TemplateBinding BorderBrush}"
<TabStrip.ItemsPanel> BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPanelTemplate> <DockPanel>
<StackPanel Orientation="Vertical"/> <ScrollViewer
</ItemsPanelTemplate> Name="PART_ScrollViewer"
</TabStrip.ItemsPanel> HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
</TabStrip> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
</ScrollViewer> Background="{TemplateBinding Background}">
<Carousel Name="PART_Content" <ItemsPresenter
Margin="8 0 0 0" Name="PART_ItemsPresenter"
MemberSelector="{x:Static TabControl.ContentSelector}" Items="{TemplateBinding Items}"
Items="{TemplateBinding Items}" ItemsPanel="{TemplateBinding ItemsPanel}"
SelectedIndex="{TemplateBinding SelectedIndex}" ItemTemplate="{TemplateBinding ItemTemplate}"
PageTransition="{TemplateBinding PageTransition}" MemberSelector="{TemplateBinding MemberSelector}">
Grid.Row="1"/> </ItemsPresenter>
</DockPanel> </ScrollViewer>
</ControlTemplate> <ContentPresenter
</Setter> Name="PART_Content"
</Style> Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabControl.sidebar TabStripItem"> <Style Selector="TabControl.sidebar > TabItem">
<Setter Property="Foreground" Value="White"/> <Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/> <Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0"/> <Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="16"/> <Setter Property="Margin" Value="0"/>
<Setter Property="Opacity" Value="0.5"/> <Setter Property="Padding" Value="16"/>
<Setter Property="Transitions"> <Setter Property="Opacity" Value="0.5"/>
<Transitions> <Setter Property="Transitions">
<DoubleTransition Property="Opacity" Duration="0:0:0.2"/> <Transitions>
</Transitions> <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
</Setter> </Transitions>
</Style> </Setter>
</Style>
<Style Selector="TabControl.sidebar TabStripItem:pointerover"> <Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Opacity" Value="1"/> <Setter Property="Opacity" Value="1"/>
</Style> </Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabControl.sidebar TabStripItem:selected"> <Setter Property="Background" Value="Transparent"/>
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/> </Style>
<Setter Property="Opacity" Value="1"/> <Style Selector="TabControl.sidebar > TabItem:selected">
</Style> <Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
</Style>
</Styles> </Styles>

5
samples/RenderDemo/MainWindow.xaml

@ -24,9 +24,6 @@
</MenuItem> </MenuItem>
</Menu> </Menu>
<TabControl Classes="sidebar"> <TabControl Classes="sidebar">
<TabControl.PageTransition>
<CrossFade Duration="0.25"/>
</TabControl.PageTransition>
<TabItem Header="Animations"> <TabItem Header="Animations">
<pages:AnimationsPage/> <pages:AnimationsPage/>
</TabItem> </TabItem>
@ -38,4 +35,4 @@
</TabItem> </TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>
</Window> </Window>

113
samples/RenderDemo/SideBar.xaml

@ -1,53 +1,66 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
xmlns:a="clr-namespace:Avalonia.Media.Animations;assembly=Avalonia.Media.Animations"> <Style Selector="TabControl.sidebar">
<Style Selector="TabControl.sidebar"> <Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="Template"> <Setter Property="Padding" Value="8 0 0 0"/>
<ControlTemplate> <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
<DockPanel> <Setter Property="Template">
<ScrollViewer MinWidth="190" Background="{DynamicResource ThemeAccentBrush}" DockPanel.Dock="Left"> <ControlTemplate>
<TabStrip Name="PART_TabStrip" <Border
MemberSelector="{x:Static TabControl.HeaderSelector}" Margin="{TemplateBinding Margin}"
Items="{TemplateBinding Items}" BorderBrush="{TemplateBinding BorderBrush}"
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"> BorderThickness="{TemplateBinding BorderThickness}">
<TabStrip.ItemsPanel> <DockPanel>
<ItemsPanelTemplate> <ScrollViewer
<StackPanel Orientation="Vertical"/> Name="PART_ScrollViewer"
</ItemsPanelTemplate> HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
</TabStrip.ItemsPanel> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
</TabStrip> Background="{TemplateBinding Background}">
</ScrollViewer> <ItemsPresenter
<Carousel Name="PART_Content" Name="PART_ItemsPresenter"
Margin="8 0 0 0" Items="{TemplateBinding Items}"
MemberSelector="{x:Static TabControl.ContentSelector}" ItemsPanel="{TemplateBinding ItemsPanel}"
Items="{TemplateBinding Items}" ItemTemplate="{TemplateBinding ItemTemplate}"
SelectedIndex="{TemplateBinding SelectedIndex}" MemberSelector="{TemplateBinding MemberSelector}">
PageTransition="{TemplateBinding PageTransition}" </ItemsPresenter>
Grid.Row="1"/> </ScrollViewer>
</DockPanel> <ContentPresenter
</ControlTemplate> Name="PART_Content"
</Setter> Margin="{TemplateBinding Padding}"
</Style> HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabControl.sidebar TabStripItem"> <Style Selector="TabControl.sidebar > TabItem">
<Setter Property="Foreground" Value="White"/> <Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/> <Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0"/> <Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="16"/> <Setter Property="Margin" Value="0"/>
<Setter Property="Opacity" Value="0.5"/> <Setter Property="Padding" Value="16"/>
<Setter Property="Transitions"> <Setter Property="Opacity" Value="0.5"/>
<Transitions> <Setter Property="Transitions">
<DoubleTransition Property="Opacity" Duration="0:0:0.2"/> <Transitions>
</Transitions> <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
</Setter> </Transitions>
</Style> </Setter>
</Style>
<Style Selector="TabControl.sidebar TabStripItem:pointerover"> <Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Opacity" Value="1"/> <Setter Property="Opacity" Value="1"/>
</Style> </Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="TabControl.sidebar TabStripItem:selected"> <Setter Property="Background" Value="Transparent"/>
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/> </Style>
<Setter Property="Opacity" Value="1"/> <Style Selector="TabControl.sidebar > TabItem:selected">
</Style> <Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
</Style>
</Styles> </Styles>

12
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -88,18 +88,21 @@ namespace Avalonia.Data.Core
_subscriber(value); _subscriber(value);
} }
protected void ValueChanged(object value) protected void ValueChanged(object value) => ValueChanged(value, true);
private void ValueChanged(object value, bool notify)
{ {
var notification = value as BindingNotification; var notification = value as BindingNotification;
if (notification == null) if (notification == null)
{ {
LastValue = new WeakReference(value); LastValue = new WeakReference(value);
if (Next != null) if (Next != null)
{ {
Next.Target = new WeakReference(value); Next.Target = LastValue;
} }
else else if (notify)
{ {
_subscriber(value); _subscriber(value);
} }
@ -110,7 +113,7 @@ namespace Avalonia.Data.Core
if (Next != null) if (Next != null)
{ {
Next.Target = new WeakReference(notification.Value); Next.Target = LastValue;
} }
if (Next == null || notification.Error != null) if (Next == null || notification.Error != null)
@ -136,6 +139,7 @@ namespace Avalonia.Data.Core
} }
else else
{ {
ValueChanged(AvaloniaProperty.UnsetValue, notify:false);
_listening = false; _listening = false;
} }
} }

87
src/Avalonia.Controls/DropDown.cs

@ -2,9 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
@ -12,12 +15,17 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// A drop-down list control. /// A drop-down list control.
/// </summary> /// </summary>
public class DropDown : SelectingItemsControl public class DropDown : SelectingItemsControl
{ {
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new VirtualizingStackPanel());
/// <summary> /// <summary>
/// Defines the <see cref="IsDropDownOpen"/> property. /// Defines the <see cref="IsDropDownOpen"/> property.
/// </summary> /// </summary>
@ -39,6 +47,12 @@ namespace Avalonia.Controls
public static readonly DirectProperty<DropDown, object> SelectionBoxItemProperty = public static readonly DirectProperty<DropDown, object> SelectionBoxItemProperty =
AvaloniaProperty.RegisterDirect<DropDown, object>(nameof(SelectionBoxItem), o => o.SelectionBoxItem); AvaloniaProperty.RegisterDirect<DropDown, object>(nameof(SelectionBoxItem), o => o.SelectionBoxItem);
/// <summary>
/// Defines the <see cref="VirtualizationMode"/> property.
/// </summary>
public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty =
ItemsPresenter.VirtualizationModeProperty.AddOwner<DropDown>();
private bool _isDropDownOpen; private bool _isDropDownOpen;
private Popup _popup; private Popup _popup;
private object _selectionBoxItem; private object _selectionBoxItem;
@ -48,6 +62,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
static DropDown() static DropDown()
{ {
ItemsPanelProperty.OverrideDefaultValue<DropDown>(DefaultPanel);
FocusableProperty.OverrideDefaultValue<DropDown>(true); FocusableProperty.OverrideDefaultValue<DropDown>(true);
SelectedItemProperty.Changed.AddClassHandler<DropDown>(x => x.SelectedItemChanged); SelectedItemProperty.Changed.AddClassHandler<DropDown>(x => x.SelectedItemChanged);
KeyDownEvent.AddClassHandler<DropDown>(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel); KeyDownEvent.AddClassHandler<DropDown>(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel);
@ -80,6 +95,15 @@ namespace Avalonia.Controls
set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); }
} }
/// <summary>
/// Gets or sets the virtualization mode for the items.
/// </summary>
public ItemVirtualizationMode VirtualizationMode
{
get { return GetValue(VirtualizationModeProperty); }
set { SetValue(VirtualizationModeProperty, value); }
}
/// <inheritdoc/> /// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()
{ {
@ -138,6 +162,16 @@ namespace Avalonia.Controls
e.Handled = true; e.Handled = true;
} }
} }
else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 &&
(e.Key == Key.Up || e.Key == Key.Down))
{
var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
if (firstChild != null)
{
FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional);
e.Handled = true;
}
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -159,6 +193,7 @@ namespace Avalonia.Controls
e.Handled = true; e.Handled = true;
} }
} }
base.OnPointerPressed(e); base.OnPointerPressed(e);
} }
@ -168,28 +203,65 @@ namespace Avalonia.Controls
if (_popup != null) if (_popup != null)
{ {
_popup.Opened -= PopupOpened; _popup.Opened -= PopupOpened;
_popup.Closed -= PopupClosed;
} }
_popup = e.NameScope.Get<Popup>("PART_Popup"); _popup = e.NameScope.Get<Popup>("PART_Popup");
_popup.Opened += PopupOpened; _popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
base.OnTemplateApplied(e);
} }
private void PopupOpened(object sender, EventArgs e) internal void ItemFocused(DropDownItem dropDownItem)
{ {
var selectedIndex = SelectedIndex; if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid)
{
dropDownItem.BringIntoView();
}
}
if (selectedIndex != -1) private void PopupClosed(object sender, EventArgs e)
{
if (CanFocus(this))
{ {
var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex); Focus();
container?.Focus();
} }
} }
private void PopupOpened(object sender, EventArgs e)
{
TryFocusSelectedItem();
}
private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e) private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e)
{ {
UpdateSelectionBoxItem(e.NewValue); UpdateSelectionBoxItem(e.NewValue);
TryFocusSelectedItem();
}
private void TryFocusSelectedItem()
{
var selectedIndex = SelectedIndex;
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();
}
}
} }
private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible;
private void UpdateSelectionBoxItem(object item) private void UpdateSelectionBoxItem(object item)
{ {
var contentControl = item as IContentControl; var contentControl = item as IContentControl;
@ -219,7 +291,8 @@ namespace Avalonia.Controls
} }
else else
{ {
SelectionBoxItem = item; var selector = MemberSelector;
SelectionBoxItem = selector != null ? selector.Select(item) : item;
} }
} }

32
src/Avalonia.Controls/DropDownItem.cs

@ -2,43 +2,19 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reactive.Linq;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// A selectable item in a <see cref="DropDown"/>. /// A selectable item in a <see cref="DropDown"/>.
/// </summary> /// </summary>
public class DropDownItem : ContentControl, ISelectable public class DropDownItem : ListBoxItem
{ {
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsSelectedProperty =
AvaloniaProperty.Register<DropDownItem, bool>(nameof(IsSelected));
/// <summary>
/// Initializes static members of the <see cref="DropDownItem"/> class.
/// </summary>
static DropDownItem()
{
FocusableProperty.OverrideDefaultValue<DropDownItem>(true);
}
public DropDownItem() public DropDownItem()
{ {
this.GetObservable(DropDownItem.IsFocusedProperty).Subscribe(focused => this.GetObservable(DropDownItem.IsFocusedProperty).Where(focused => focused)
{ .Subscribe(_ => (Parent as DropDown)?.ItemFocused(this));
PseudoClasses.Set(":selected", focused);
});
}
/// <summary>
/// Gets or sets the selection state of the item.
/// </summary>
public bool IsSelected
{
get { return GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
} }
} }
} }

59
src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs

@ -0,0 +1,59 @@
// 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.Primitives;
namespace Avalonia.Controls.Generators
{
public class TabItemContainerGenerator : ItemContainerGenerator<TabItem>
{
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;
tabItem[~TabControl.TabStripPlacementProperty] = Owner[~TabControl.TabStripPlacementProperty];
if (tabItem.HeaderTemplate == null)
{
tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty];
}
if (tabItem.Header == null)
{
if (item is IHeadered headered)
{
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;
}
}
}

2
src/Avalonia.Controls/ItemsControl.cs

@ -249,8 +249,6 @@ namespace Avalonia.Controls
if (containerControl != null) if (containerControl != null)
{ {
((ISetLogicalParent)containerControl).SetParent(this); ((ISetLogicalParent)containerControl).SetParent(this);
containerControl.SetValue(TemplatedParentProperty, null);
containerControl.UpdateChild(); containerControl.UpdateChild();
if (containerControl.Child != null) if (containerControl.Child != null)

41
src/Avalonia.Controls/LayoutTransformControl.cs

@ -5,28 +5,30 @@
// http://silverlight.codeplex.com/SourceControl/changeset/view/74775#Release/Silverlight4/Source/Controls.Layout.Toolkit/LayoutTransformer/LayoutTransformer.cs // 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;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Media;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// Control that implements support for transformations as if applied by LayoutTransform. /// Control that implements support for transformations as if applied by LayoutTransform.
/// </summary> /// </summary>
public class LayoutTransformControl : ContentControl public class LayoutTransformControl : Decorator
{ {
public static readonly AvaloniaProperty<Transform> LayoutTransformProperty = public static readonly AvaloniaProperty<Transform> LayoutTransformProperty =
AvaloniaProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform)); AvaloniaProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform));
static LayoutTransformControl() static LayoutTransformControl()
{ {
ClipToBoundsProperty.OverrideDefaultValue<LayoutTransformControl>(true);
LayoutTransformProperty.Changed LayoutTransformProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged); .AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged);
ChildProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnChildChanged);
} }
/// <summary> /// <summary>
@ -38,8 +40,7 @@ namespace Avalonia.Controls
set { SetValue(LayoutTransformProperty, value); } set { SetValue(LayoutTransformProperty, value); }
} }
public Control TransformRoot => _transformRoot ?? public IControl TransformRoot => Child;
(_transformRoot = this.GetVisualChildren().OfType<Control>().FirstOrDefault());
/// <summary> /// <summary>
/// Provides the behavior for the "Arrange" pass of layout. /// Provides the behavior for the "Arrange" pass of layout.
@ -132,16 +133,8 @@ namespace Avalonia.Controls
return transformedDesiredSize; return transformedDesiredSize;
} }
/// <summary> private void OnChildChanged(AvaloniaPropertyChangedEventArgs e)
/// Builds the visual tree for the LayoutTransformerControl when a new
/// template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{ {
base.OnTemplateApplied(e);
_matrixTransform = new MatrixTransform();
if (null != TransformRoot) if (null != TransformRoot)
{ {
TransformRoot.RenderTransform = _matrixTransform; TransformRoot.RenderTransform = _matrixTransform;
@ -169,14 +162,14 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// RenderTransform/MatrixTransform applied to TransformRoot. /// RenderTransform/MatrixTransform applied to TransformRoot.
/// </summary> /// </summary>
private MatrixTransform _matrixTransform; private MatrixTransform _matrixTransform = new MatrixTransform();
/// <summary> /// <summary>
/// Transformation matrix corresponding to _matrixTransform. /// Transformation matrix corresponding to _matrixTransform.
/// </summary> /// </summary>
private Matrix _transformation; private Matrix _transformation;
private IDisposable _transformChangedEvent = null; private IDisposable _transformChangedEvent = null;
private Control _transformRoot;
/// <summary> /// <summary>
/// Returns true if Size a is smaller than Size b in either dimension. /// Returns true if Size a is smaller than Size b in either dimension.
/// </summary> /// </summary>
@ -215,7 +208,8 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
private void ApplyLayoutTransform() private void ApplyLayoutTransform()
{ {
if (LayoutTransform == null) return; if (LayoutTransform == null)
return;
// Get the transform matrix and apply it // Get the transform matrix and apply it
_transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound);
@ -376,11 +370,8 @@ namespace Avalonia.Controls
{ {
var newTransform = e.NewValue as Transform; var newTransform = e.NewValue as Transform;
if (_transformChangedEvent != null) _transformChangedEvent?.Dispose();
{ _transformChangedEvent = null;
_transformChangedEvent.Dispose();
_transformChangedEvent = null;
}
if (newTransform != null) if (newTransform != null)
{ {
@ -392,4 +383,4 @@ namespace Avalonia.Controls
ApplyLayoutTransform(); ApplyLayoutTransform();
} }
} }
} }

21
src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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 namespace Avalonia.Controls.Primitives
{ {
/// <summary> /// <summary>
@ -12,7 +14,13 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="Header"/> property. /// Defines the <see cref="Header"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<object> HeaderProperty = public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<ContentControl, object>(nameof(Header)); AvaloniaProperty.Register<HeaderedContentControl, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(HeaderTemplate));
/// <summary> /// <summary>
/// Gets or sets the header content. /// Gets or sets the header content.
@ -21,6 +29,15 @@ namespace Avalonia.Controls.Primitives
{ {
get { return GetValue(HeaderProperty); } get { return GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); } set { SetValue(HeaderProperty, value); }
}
/// <summary>
/// Gets or sets the data template used to display the header content of the control.
/// </summary>
public IDataTemplate HeaderTemplate
{
get { return GetValue(HeaderTemplateProperty); }
set { SetValue(HeaderTemplateProperty, value); }
} }
} }
} }

9
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -431,9 +431,12 @@ namespace Avalonia.Controls.Primitives
{ {
if (i.ContainerControl != null && i.Item != null) if (i.ContainerControl != null && i.Item != null)
{ {
MarkContainerSelected( var ms = MemberSelector;
i.ContainerControl, bool selected = ms == null ?
SelectedItems.Contains(i.Item)); SelectedItems.Contains(i.Item) :
SelectedItems.OfType<object>().Any(v => Equals(ms.Select(v), i.Item));
MarkContainerSelected(i.ContainerControl, selected);
} }
} }
} }

19
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -260,7 +260,7 @@ namespace Avalonia.Controls.Primitives
var child = template.Build(this); var child = template.Build(this);
var nameScope = new NameScope(); var nameScope = new NameScope();
NameScope.SetNameScope((Control)child, nameScope); NameScope.SetNameScope((Control)child, nameScope);
child.SetValue(TemplatedParentProperty, this); ApplyTemplatedParent(child);
RegisterNames(child, nameScope); RegisterNames(child, nameScope);
((ISetLogicalParent)child).SetParent(this); ((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child); VisualChildren.Add(child);
@ -326,6 +326,23 @@ namespace Avalonia.Controls.Primitives
InvalidateMeasure(); InvalidateMeasure();
} }
/// <summary>
/// Sets the TemplatedParent property for the created template children.
/// </summary>
/// <param name="control">The control.</param>
private void ApplyTemplatedParent(IControl control)
{
control.SetValue(TemplatedParentProperty, this);
foreach (var child in control.LogicalChildren)
{
if (child is IControl c)
{
ApplyTemplatedParent(c);
}
}
}
/// <summary> /// <summary>
/// Registers each control with its name scope. /// Registers each control with its name scope.
/// </summary> /// </summary>

164
src/Avalonia.Controls/TabControl.cs

@ -1,10 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Animation;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -14,28 +16,46 @@ namespace Avalonia.Controls
public class TabControl : SelectingItemsControl public class TabControl : SelectingItemsControl
{ {
/// <summary> /// <summary>
/// Defines the <see cref="PageTransition"/> property. /// Defines the <see cref="TabStripPlacement"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IPageTransition> PageTransitionProperty = public static readonly StyledProperty<Dock> TabStripPlacementProperty =
Avalonia.Controls.Carousel.PageTransitionProperty.AddOwner<TabControl>(); AvaloniaProperty.Register<TabControl, Dock>(nameof(TabStripPlacement), defaultValue: Dock.Top);
/// <summary> /// <summary>
/// Defines an <see cref="IMemberSelector"/> that selects the content of a <see cref="TabItem"/>. /// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary> /// </summary>
public static readonly IMemberSelector ContentSelector = public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
new FuncMemberSelector<object, object>(SelectContent); ContentControl.HorizontalContentAlignmentProperty.AddOwner<TabControl>();
/// <summary> /// <summary>
/// Defines an <see cref="IMemberSelector"/> that selects the header of a <see cref="TabItem"/>. /// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary> /// </summary>
public static readonly IMemberSelector HeaderSelector = public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
new FuncMemberSelector<object, object>(SelectHeader); ContentControl.VerticalContentAlignmentProperty.AddOwner<TabControl>();
/// <summary> /// <summary>
/// Defines the <see cref="TabStripPlacement"/> property. /// Defines the <see cref="ContentTemplate"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Dock> TabStripPlacementProperty = public static readonly StyledProperty<IDataTemplate> ContentTemplateProperty =
AvaloniaProperty.Register<TabControl, Dock>(nameof(TabStripPlacement), defaultValue: Dock.Top); ContentControl.ContentTemplateProperty.AddOwner<TabControl>();
/// <summary>
/// The selected content property
/// </summary>
public static readonly StyledProperty<object> SelectedContentProperty =
AvaloniaProperty.Register<TabControl, object>(nameof(SelectedContent));
/// <summary>
/// The selected content template property
/// </summary>
public static readonly StyledProperty<IDataTemplate> SelectedContentTemplateProperty =
AvaloniaProperty.Register<TabControl, IDataTemplate>(nameof(SelectedContentTemplate));
/// <summary>
/// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
/// </summary>
private static readonly FuncTemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new WrapPanel());
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TabControl"/> class. /// Initializes static members of the <see cref="TabControl"/> class.
@ -43,107 +63,107 @@ namespace Avalonia.Controls
static TabControl() static TabControl()
{ {
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected); SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
FocusableProperty.OverrideDefaultValue<TabControl>(false); ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure<TabControl>(TabStripPlacementProperty); AffectsMeasure<TabControl>(TabStripPlacementProperty);
} }
/// <summary> /// <summary>
/// Gets the pages portion of the <see cref="TabControl"/>'s template. /// Gets or sets the horizontal alignment of the content within the control.
/// </summary> /// </summary>
public IControl Pages public HorizontalAlignment HorizontalContentAlignment
{ {
get; get { return GetValue(HorizontalContentAlignmentProperty); }
private set; set { SetValue(HorizontalContentAlignmentProperty, value); }
} }
/// <summary> /// <summary>
/// Gets the tab strip portion of the <see cref="TabControl"/>'s template. /// Gets or sets the vertical alignment of the content within the control.
/// </summary> /// </summary>
public IControl TabStrip public VerticalAlignment VerticalContentAlignment
{ {
get; get { return GetValue(VerticalContentAlignmentProperty); }
private set; set { SetValue(VerticalContentAlignmentProperty, value); }
} }
/// <summary> /// <summary>
/// Gets or sets the transition to use when switching tabs. /// Gets or sets the tabstrip placement of the TabControl.
/// </summary> /// </summary>
public IPageTransition PageTransition public Dock TabStripPlacement
{ {
get { return GetValue(PageTransitionProperty); } get { return GetValue(TabStripPlacementProperty); }
set { SetValue(PageTransitionProperty, value); } set { SetValue(TabStripPlacementProperty, value); }
} }
/// <summary> /// <summary>
/// Gets or sets the tabstrip placement of the tabcontrol. /// Gets or sets the default data template used to display the content of the selected tab.
/// </summary> /// </summary>
public Dock TabStripPlacement public IDataTemplate ContentTemplate
{ {
get { return GetValue(TabStripPlacementProperty); } get { return GetValue(ContentTemplateProperty); }
set { SetValue(TabStripPlacementProperty, value); } set { SetValue(ContentTemplateProperty, value); }
} }
/// <summary>
/// Gets or sets the content of the selected tab.
/// </summary>
/// <value>
/// The content of the selected tab.
/// </value>
public object SelectedContent
{
get { return GetValue(SelectedContentProperty); }
internal set { SetValue(SelectedContentProperty, value); }
}
/// <summary>
/// Gets or sets the content template for the selected tab.
/// </summary>
/// <value>
/// The content template of the selected tab.
/// </value>
public IDataTemplate SelectedContentTemplate
{
get { return GetValue(SelectedContentTemplateProperty); }
internal set { SetValue(SelectedContentTemplateProperty, value); }
}
internal ItemsPresenter ItemsPresenterPart { get; private set; }
internal ContentPresenter ContentPart { get; private set; }
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()
{ {
// TabControl doesn't actually create items - instead its TabStrip and Carousel return new TabItemContainerGenerator(this);
// 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;
} }
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{ {
base.OnTemplateApplied(e); base.OnTemplateApplied(e);
TabStrip = e.NameScope.Find<IControl>("PART_TabStrip"); ItemsPresenterPart = e.NameScope.Get<ItemsPresenter>("PART_ItemsPresenter");
Pages = e.NameScope.Find<IControl>("PART_Content");
ContentPart = e.NameScope.Get<ContentPresenter>("PART_Content");
} }
/// <summary> /// <inheritdoc/>
/// Selects the content of a tab item. protected override void OnGotFocus(GotFocusEventArgs e)
/// </summary>
/// <param name="o">The tab item.</param>
/// <returns>The content.</returns>
private static object SelectContent(object o)
{ {
var content = o as IContentControl; base.OnGotFocus(e);
if (content != null) if (e.NavigationMethod == NavigationMethod.Directional)
{ {
return content.Content; e.Handled = UpdateSelectionFromEventSource(e.Source);
} }
else
{
return o;
}
} }
/// <summary> /// <inheritdoc/>
/// Selects the header of a tab item. protected override void OnPointerPressed(PointerPressedEventArgs e)
/// </summary>
/// <param name="o">The tab item.</param>
/// <returns>The content.</returns>
private static object SelectHeader(object o)
{ {
var headered = o as IHeadered; base.OnPointerPressed(e);
var control = o as IControl;
if (headered != null) if (e.MouseButton == MouseButton.Left)
{
return headered.Header ?? string.Empty;
}
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
{ {
return o; e.Handled = UpdateSelectionFromEventSource(e.Source);
} }
} }
} }

67
src/Avalonia.Controls/TabItem.cs

@ -11,6 +11,12 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class TabItem : HeaderedContentControl, ISelectable public class TabItem : HeaderedContentControl, ISelectable
{ {
/// <summary>
/// Defines the <see cref="TabStripPlacement"/> property.
/// </summary>
public static readonly StyledProperty<Dock> TabStripPlacementProperty =
TabControl.TabStripPlacementProperty.AddOwner<TabItem>();
/// <summary> /// <summary>
/// Defines the <see cref="IsSelected"/> property. /// Defines the <see cref="IsSelected"/> property.
/// </summary> /// </summary>
@ -24,6 +30,19 @@ namespace Avalonia.Controls
{ {
SelectableMixin.Attach<TabItem>(IsSelectedProperty); SelectableMixin.Attach<TabItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); FocusableProperty.OverrideDefaultValue(typeof(TabItem), true);
IsSelectedProperty.Changed.AddClassHandler<TabItem>(x => x.UpdateSelectedContent);
DataContextProperty.Changed.AddClassHandler<TabItem>(x => x.UpdateHeader);
}
/// <summary>
/// Gets the tab strip placement.
/// </summary>
/// <value>
/// The tab strip placement.
/// </value>
public Dock TabStripPlacement
{
get { return GetValue(TabStripPlacementProperty); }
} }
/// <summary> /// <summary>
@ -34,5 +53,53 @@ namespace Avalonia.Controls
get { return GetValue(IsSelectedProperty); } get { return GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); } set { SetValue(IsSelectedProperty, value); }
} }
internal TabControl ParentTabControl { get; set; }
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;
}
}
} }
} }

6
src/Avalonia.Controls/Templates/TemplateExtensions.cs

@ -24,12 +24,14 @@ namespace Avalonia.Controls.Templates
{ {
foreach (IControl child in control.GetVisualChildren()) foreach (IControl child in control.GetVisualChildren())
{ {
if (child.TemplatedParent == templatedParent) var childTemplatedParent = child.TemplatedParent;
if (childTemplatedParent == templatedParent)
{ {
yield return child; yield return child;
} }
if (child.TemplatedParent != null) if (childTemplatedParent != null)
{ {
foreach (var descendant in GetTemplateChildren(child, templatedParent)) foreach (var descendant in GetTemplateChildren(child, templatedParent))
{ {

124
src/Avalonia.Controls/Viewbox.cs

@ -0,0 +1,124 @@
using System;
using Avalonia.Media;
namespace Avalonia.Controls
{
/// <summary>
/// Viewbox is used to scale single child.
/// </summary>
/// <seealso cref="Avalonia.Controls.Decorator" />
public class Viewbox : Decorator
{
/// <summary>
/// The stretch property
/// </summary>
public static AvaloniaProperty<Stretch> StretchProperty =
AvaloniaProperty.RegisterDirect<Viewbox, Stretch>(nameof(Stretch),
v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform);
private Stretch _stretch = Stretch.Uniform;
/// <summary>
/// Gets or sets the stretch mode,
/// which determines how child fits into the available space.
/// </summary>
/// <value>
/// The stretch.
/// </value>
public Stretch Stretch
{
get => _stretch;
set => SetAndRaise(StretchProperty, ref _stretch, value);
}
static Viewbox()
{
ClipToBoundsProperty.OverrideDefaultValue<Viewbox>(true);
AffectsMeasure<Viewbox>(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);
}
}
}

12
src/Avalonia.Styling/StyledElement.cs

@ -49,8 +49,11 @@ namespace Avalonia
/// <summary> /// <summary>
/// Defines the <see cref="TemplatedParent"/> property. /// Defines the <see cref="TemplatedParent"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<ITemplatedControl> TemplatedParentProperty = public static readonly DirectProperty<StyledElement, ITemplatedControl> TemplatedParentProperty =
AvaloniaProperty.Register<StyledElement, ITemplatedControl>(nameof(TemplatedParent), inherits: true); AvaloniaProperty.RegisterDirect<StyledElement, ITemplatedControl>(
nameof(TemplatedParent),
o => o.TemplatedParent,
(o ,v) => o.TemplatedParent = v);
private int _initCount; private int _initCount;
private string _name; private string _name;
@ -62,6 +65,7 @@ namespace Avalonia
private Styles _styles; private Styles _styles;
private bool _styled; private bool _styled;
private Subject<IStyleable> _styleDetach = new Subject<IStyleable>(); private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
private ITemplatedControl _templatedParent;
private bool _dataContextUpdating; private bool _dataContextUpdating;
/// <summary> /// <summary>
@ -269,8 +273,8 @@ namespace Avalonia
/// </summary> /// </summary>
public ITemplatedControl TemplatedParent public ITemplatedControl TemplatedParent
{ {
get { return GetValue(TemplatedParentProperty); } get => _templatedParent;
internal set { SetValue(TemplatedParentProperty, value); } internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value);
} }
/// <summary> /// <summary>

2
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@ -52,5 +52,7 @@
<sys:Double x:Key="FontSizeSmall">10</sys:Double> <sys:Double x:Key="FontSizeSmall">10</sys:Double>
<sys:Double x:Key="FontSizeNormal">12</sys:Double> <sys:Double x:Key="FontSizeNormal">12</sys:Double>
<sys:Double x:Key="FontSizeLarge">16</sys:Double> <sys:Double x:Key="FontSizeLarge">16</sys:Double>
<sys:Double x:Key="ScrollBarThickness">10</sys:Double>
</Style.Resources> </Style.Resources>
</Style> </Style>

2
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -52,5 +52,7 @@
<sys:Double x:Key="FontSizeSmall">10</sys:Double> <sys:Double x:Key="FontSizeSmall">10</sys:Double>
<sys:Double x:Key="FontSizeNormal">12</sys:Double> <sys:Double x:Key="FontSizeNormal">12</sys:Double>
<sys:Double x:Key="FontSizeLarge">16</sys:Double> <sys:Double x:Key="FontSizeLarge">16</sys:Double>
<sys:Double x:Key="ScrollBarThickness">10</sys:Double>
</Style.Resources> </Style.Resources>
</Style> </Style>

2
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -12,7 +12,6 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.DropDownItem.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.DropDownItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.GridSplitter.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.GridSplitter.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ItemsControl.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.ItemsControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.LayoutTransformControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ListBox.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.ListBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ListBoxItem.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.ListBoxItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Menu.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.Menu.xaml?assembly=Avalonia.Themes.Default"/>
@ -30,6 +29,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.TabStripItem.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TabStripItem.xaml?assembly=Avalonia.Themes.Default"/>
<!-- TabControl needs to come after TabStrip as it redefines the inner TabStrip.ItemsPanel--> <!-- TabControl needs to come after TabStrip as it redefines the inner TabStrip.ItemsPanel-->
<StyleInclude Source="resm:Avalonia.Themes.Default.TabControl.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TabControl.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TabItem.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TextBox.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.TextBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ToggleButton.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.ToggleButton.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Expander.xaml?assembly=Avalonia.Themes.Default"/> <StyleInclude Source="resm:Avalonia.Themes.Default.Expander.xaml?assembly=Avalonia.Themes.Default"/>

20
src/Avalonia.Themes.Default/DropDown.xaml

@ -35,15 +35,21 @@
MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}" MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
MaxHeight="{TemplateBinding MaxDropDownHeight}" MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="{TemplateBinding}" PlacementTarget="{TemplateBinding}"
ObeyScreenEdges="True"
StaysOpen="False"> StaysOpen="False">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer> <AdornerDecorator Margin="-1 -1 0 0">
<ItemsPresenter Name="PART_ItemsPresenter" <ScrollViewer>
Items="{TemplateBinding Items}" <ItemsPresenter Name="PART_ItemsPresenter"
ItemTemplate="{TemplateBinding ItemTemplate}" Items="{TemplateBinding Items}"
MemberSelector="{TemplateBinding MemberSelector}"/> ItemsPanel="{TemplateBinding ItemsPanel}"
</ScrollViewer> ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}"
VirtualizationMode="{TemplateBinding VirtualizationMode}"
/>
</ScrollViewer>
</AdornerDecorator>
</Border> </Border>
</Popup> </Popup>
</Grid> </Grid>
@ -54,4 +60,4 @@
<Style Selector="DropDown:pointerover /template/ Border#border"> <Style Selector="DropDown:pointerover /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style> </Style>
</Styles> </Styles>

2
src/Avalonia.Themes.Default/DropDownItem.xaml

@ -18,7 +18,7 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="DropDownItem:pointerover /template/ ContentPresenter"> <Style Selector="DropDownItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
</Style> </Style>

5
src/Avalonia.Themes.Default/FocusAdorner.xaml

@ -3,7 +3,8 @@
<FocusAdornerTemplate> <FocusAdornerTemplate>
<Rectangle Stroke="Black" <Rectangle Stroke="Black"
StrokeThickness="1" StrokeThickness="1"
StrokeDashArray="1,2"/> StrokeDashArray="1,2"
Margin="1"/>
</FocusAdornerTemplate> </FocusAdornerTemplate>
</Setter> </Setter>
</Style> </Style>

13
src/Avalonia.Themes.Default/LayoutTransformControl.xaml

@ -1,13 +0,0 @@
<Style xmlns="https://github.com/avaloniaui" Selector="LayoutTransformControl">
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>
</Style>

22
src/Avalonia.Themes.Default/ScrollBar.xaml

@ -3,7 +3,7 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{DynamicResource ThemeControlMidBrush}"> <Border Background="{DynamicResource ThemeControlMidBrush}">
<Grid RowDefinitions="10,*,10"> <Grid RowDefinitions="Auto,*,Auto">
<RepeatButton Name="PART_LineUpButton" <RepeatButton Name="PART_LineUpButton"
Classes="repeat" Classes="repeat"
Grid.Row="0" Grid.Row="0"
@ -53,12 +53,11 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="ScrollBar:horizontal"> <Style Selector="ScrollBar:horizontal">
<Setter Property="Height" <Setter Property="Height" Value="{DynamicResource ScrollBarThickness}" />
Value="10" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{DynamicResource ThemeControlMidBrush}"> <Border Background="{DynamicResource ThemeControlMidBrush}">
<Grid ColumnDefinitions="10,*,10"> <Grid ColumnDefinitions="Auto,*,Auto">
<RepeatButton Name="PART_LineUpButton" <RepeatButton Name="PART_LineUpButton"
Classes="repeat" Classes="repeat"
Grid.Row="0" Grid.Row="0"
@ -108,22 +107,17 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="ScrollBar:horizontal /template/ Thumb#thumb"> <Style Selector="ScrollBar:horizontal /template/ Thumb#thumb">
<Setter Property="MinWidth" <Setter Property="MinWidth" Value="{DynamicResource ScrollBarThickness}" />
Value="10" />
</Style> </Style>
<Style Selector="ScrollBar:vertical"> <Style Selector="ScrollBar:vertical">
<Setter Property="Width" <Setter Property="Width" Value="{DynamicResource ScrollBarThickness}" />
Value="10" />
</Style> </Style>
<Style Selector="ScrollBar:vertical /template/ Thumb#thumb"> <Style Selector="ScrollBar:vertical /template/ Thumb#thumb">
<Setter Property="MinHeight" <Setter Property="MinHeight" Value="{DynamicResource ScrollBarThickness}" />
Value="10" />
</Style> </Style>
<Style Selector="ScrollBar /template/ RepeatButton.repeat"> <Style Selector="ScrollBar /template/ RepeatButton.repeat">
<Setter Property="Padding" <Setter Property="Padding" Value="2" />
Value="2" /> <Setter Property="BorderThickness" Value="0" />
<Setter Property="BorderThickness"
Value="0" />
</Style> </Style>
<Style Selector="ScrollBar /template/ RepeatButton.repeattrack"> <Style Selector="ScrollBar /template/ RepeatButton.repeattrack">
<Setter Property="Template"> <Setter Property="Template">

111
src/Avalonia.Themes.Default/TabControl.xaml

@ -1,50 +1,63 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TabControl"> <Style Selector="TabControl">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}" <Border
BorderBrush="{TemplateBinding BorderBrush}" Margin="{TemplateBinding Margin}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderBrush="{TemplateBinding BorderBrush}"
<DockPanel> BorderThickness="{TemplateBinding BorderThickness}"
<TabStrip Name="PART_TabStrip" Background="{TemplateBinding Background}"
MemberSelector="{x:Static TabControl.HeaderSelector}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Items="{TemplateBinding Items}" VerticalAlignment="{TemplateBinding VerticalAlignment}">
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"/> <DockPanel>
<Carousel Name="PART_Content" <ItemsPresenter
MemberSelector="{x:Static TabControl.ContentSelector}" Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding SelectedIndex}" ItemsPanel="{TemplateBinding ItemsPanel}"
PageTransition="{TemplateBinding PageTransition}" ItemTemplate="{TemplateBinding ItemTemplate}"
Grid.Row="1"/> MemberSelector="{TemplateBinding MemberSelector}" >
</DockPanel> </ItemsPresenter>
</Border> <ContentPresenter
</ControlTemplate> Name="PART_Content"
</Setter> Margin="{TemplateBinding Padding}"
</Style> HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
<Style Selector="TabControl[TabStripPlacement=Top] /template/ TabStrip"> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
<Setter Property="DockPanel.Dock" Value="Top"/> Content="{TemplateBinding SelectedContent}"
</Style> ContentTemplate="{TemplateBinding SelectedContentTemplate}">
<Style Selector="TabControl[TabStripPlacement=Bottom] /template/ TabStrip"> </ContentPresenter>
<Setter Property="DockPanel.Dock" Value="Bottom"/> </DockPanel>
</Style> </Border>
<Style Selector="TabControl[TabStripPlacement=Left] /template/ TabStrip"> </ControlTemplate>
<Setter Property="DockPanel.Dock" Value="Left"/> </Setter>
<Setter Property="ItemsPanel"> </Style>
<Setter.Value> <Style Selector="TabControl[TabStripPlacement=Top]">
<ItemsPanelTemplate> <Setter Property="Padding" Value="0 4 0 0"/>
<StackPanel Orientation="Vertical"/> </Style>
</ItemsPanelTemplate> <Style Selector="TabControl[TabStripPlacement=Top] /template/ ItemsPresenter#PART_ItemsPresenter">
</Setter.Value> <Setter Property="DockPanel.Dock" Value="Top"/>
</Setter> </Style>
</Style> <Style Selector="TabControl[TabStripPlacement=Bottom] /template/ ItemsPresenter#PART_ItemsPresenter">
<Style Selector="TabControl[TabStripPlacement=Right] /template/ TabStrip"> <Setter Property="DockPanel.Dock" Value="Bottom"/>
<Setter Property="DockPanel.Dock" Value="Right"/> </Style>
<Setter Property="ItemsPanel"> <Style Selector="TabControl[TabStripPlacement=Bottom]">
<Setter.Value> <Setter Property="Padding" Value="0 0 0 4"/>
<ItemsPanelTemplate> </Style>
<StackPanel Orientation="Vertical"/> <Style Selector="TabControl[TabStripPlacement=Left] /template/ ItemsPresenter#PART_ItemsPresenter">
</ItemsPanelTemplate> <Setter Property="DockPanel.Dock" Value="Left"/>
</Setter.Value> </Style>
</Setter> <Style Selector="TabControl[TabStripPlacement=Left] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
</Style> <Setter Property="Orientation" Value="Vertical"/>
</Styles> </Style>
<Style Selector="TabControl[TabStripPlacement=Left]">
<Setter Property="Padding" Value="4 0 0 0"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Right] /template/ ItemsPresenter#PART_ItemsPresenter">
<Setter Property="DockPanel.Dock" Value="Right"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Right] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
<Setter Property="Orientation" Value="Vertical"/>
</Style>
<Style Selector="TabControl[TabStripPlacement=Right]">
<Setter Property="Padding" Value="0 0 0 4"/>
</Style>
</Styles>

45
src/Avalonia.Themes.Default/TabItem.xaml

@ -0,0 +1,45 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="TabItem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLightBrush}"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Padding" Value="8"/>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter
Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Content="{TemplateBinding Header}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Margin}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabItem:disabled">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>
<Style Selector="TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
</Style>
<Style Selector="TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
</Style>
<Style Selector="TabItem:selected:focus /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
</Style>
<Style Selector="TabItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
</Style>
<Style Selector="TabItem:selected:focus:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
</Style>
<Style Selector="TabItem[TabStripPlacement=Right]">
<Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style>
</Styles>

164
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -37,6 +37,7 @@ namespace Avalonia.Rendering
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IRef<IDrawOperation> _currentDraw; private IRef<IDrawOperation> _currentDraw;
private readonly IDeferredRendererLock _lock; private readonly IDeferredRendererLock _lock;
private readonly object _sceneLock = new object();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class. /// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
@ -84,6 +85,7 @@ namespace Avalonia.Rendering
RenderTarget = renderTarget; RenderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); _sceneBuilder = sceneBuilder ?? new SceneBuilder();
Layers = new RenderLayers(); Layers = new RenderLayers();
_lock = new ManagedDeferredRendererLock();
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -118,8 +120,13 @@ namespace Avalonia.Rendering
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
var scene = Interlocked.Exchange(ref _scene, null); lock (_sceneLock)
scene?.Dispose(); {
var scene = _scene;
_scene = null;
scene?.Dispose();
}
Stop(); Stop();
Layers.Clear(); Layers.Clear();
@ -134,7 +141,8 @@ namespace Avalonia.Rendering
// When unit testing the renderLoop may be null, so update the scene manually. // When unit testing the renderLoop may be null, so update the scene manually.
UpdateScene(); UpdateScene();
} }
//It's safe to access _scene here without a lock since
//it's only changed from UI thread which we are currently on
return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty<IVisual>(); return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty<IVisual>();
} }
@ -172,7 +180,8 @@ namespace Avalonia.Rendering
} }
} }
bool IRenderLoopTask.NeedsUpdate => _dirty == null || _dirty.Count > 0; bool NeedsUpdate => _dirty == null || _dirty.Count > 0;
bool IRenderLoopTask.NeedsUpdate => NeedsUpdate;
void IRenderLoopTask.Update(TimeSpan time) => UpdateScene(); void IRenderLoopTask.Update(TimeSpan time) => UpdateScene();
@ -197,79 +206,105 @@ namespace Avalonia.Rendering
internal void UnitTestUpdateScene() => UpdateScene(); internal void UnitTestUpdateScene() => UpdateScene();
internal void UnitTestRender() => Render(_scene.Item, false); internal void UnitTestRender() => Render(false);
private void Render(bool forceComposite) private void Render(bool forceComposite)
{ {
using (var l = _lock.TryLock()) using (var l = _lock.TryLock())
if (l != null)
using (var scene = _scene?.Clone())
{
Render(scene?.Item, forceComposite);
}
}
private void Render(Scene scene, bool forceComposite)
{
bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false;
if (RenderTarget == null)
{ {
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget(); if (l == null)
} return;
if (renderOverlay) IDrawingContextImpl context = null;
{ try
_dirtyRectsDisplay.Tick();
}
try
{
if (scene != null && scene.Size != Size.Empty)
{ {
IDrawingContextImpl context = null; try
if (scene.Generation != _lastSceneId)
{ {
context = RenderTarget.CreateDrawingContext(this); IDrawingContextImpl GetContext()
Layers.Update(scene, context); {
if (context != null)
return context;
if (RenderTarget == null)
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
return context = RenderTarget.CreateDrawingContext(this);
RenderToLayers(scene); }
if (DebugFramesPath != null) var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
using (scene)
{ {
SaveDebugFrames(scene.Generation); var overlay = DrawDirtyRects || DrawFps;
if (DrawDirtyRects)
_dirtyRectsDisplay.Tick();
if (overlay)
RenderOverlay(scene.Item, GetContext());
if (updated || forceComposite || overlay)
RenderComposite(scene.Item, GetContext());
} }
}
finally
{
context?.Dispose();
}
}
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
RenderTarget?.Dispose();
RenderTarget = null;
}
}
}
_lastSceneId = scene.Generation; private (IRef<Scene> scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(Func<IDrawingContextImpl> contextFactory,
bool recursiveCall = false)
{
IRef<Scene> sceneRef;
lock (_sceneLock)
sceneRef = _scene?.Clone();
if (sceneRef == null)
return (null, false);
using (sceneRef)
{
var scene = sceneRef.Item;
if (scene.Generation != _lastSceneId)
{
var context = contextFactory();
Layers.Update(scene, context);
composite = true; RenderToLayers(scene);
}
if (renderOverlay) if (DebugFramesPath != null)
{ {
context = context ?? RenderTarget.CreateDrawingContext(this); SaveDebugFrames(scene.Generation);
RenderOverlay(scene, context);
RenderComposite(scene, context);
} }
else if (composite || forceComposite)
lock (_sceneLock)
_lastSceneId = scene.Generation;
// We have consumed the previously available scene, but there might be some dirty
// rects since the last update. *If* we are on UI thread, we can force immediate scene
// rebuild before rendering anything on-screen
// We are calling the same method recursively here
if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate)
{ {
context = context ?? RenderTarget.CreateDrawingContext(this); UpdateScene();
RenderComposite(scene, context); var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true);
return (rs, true);
} }
context?.Dispose(); // Indicate that we have updated the layers
return (sceneRef.Clone(), true);
} }
// Just return scene, layers weren't updated
return (sceneRef.Clone(), false);
} }
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
RenderTarget?.Dispose();
RenderTarget = null;
}
} }
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
{ {
if (layer == null || node.LayerRoot == layer) if (layer == null || node.LayerRoot == layer)
@ -405,6 +440,11 @@ namespace Avalonia.Rendering
private void UpdateScene() private void UpdateScene()
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
lock (_sceneLock)
{
if (_scene?.Item.Generation > _lastSceneId)
return;
}
if (_root.IsVisible) if (_root.IsVisible)
{ {
var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root)); var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root));
@ -423,15 +463,23 @@ namespace Avalonia.Rendering
} }
} }
var oldScene = Interlocked.Exchange(ref _scene, sceneRef); lock (_sceneLock)
oldScene?.Dispose(); {
var oldScene = _scene;
_scene = sceneRef;
oldScene?.Dispose();
}
_dirty.Clear(); _dirty.Clear();
} }
else else
{ {
var oldScene = Interlocked.Exchange(ref _scene, null); lock (_sceneLock)
oldScene?.Dispose(); {
var oldScene = _scene;
_scene = null;
oldScene?.Dispose();
}
} }
} }

16
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs

@ -1,13 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Data; using Avalonia.Data;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions namespace Avalonia.Markup.Xaml.MarkupExtensions
{ {
using Portable.Xaml.Markup;
using System;
public class RelativeSourceExtension : MarkupExtension public class RelativeSourceExtension : MarkupExtension
{ {
public RelativeSourceExtension() public RelativeSourceExtension()
@ -24,10 +23,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
return new RelativeSource return new RelativeSource
{ {
Mode = Mode, Mode = Mode,
AncestorType = AncestorType,
AncestorLevel = AncestorLevel,
Tree = Tree,
}; };
} }
[ConstructorArgument("mode")] [ConstructorArgument("mode")]
public RelativeSourceMode Mode { get; set; } public RelativeSourceMode Mode { get; set; } = RelativeSourceMode.FindAncestor;
public Type AncestorType { get; set; }
public TreeType Tree { get; set; }
public int AncestorLevel { get; set; } = 1;
} }
} }

8
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -89,9 +89,11 @@ namespace Avalonia.Shared.PlatformSupport
#if DEBUG #if DEBUG
if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId) if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
{ {
Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: " lock(_lock)
+ Environment.StackTrace if (!IsDisposed)
+ "\n\nBlob created by " + _backtrace); Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
+ Environment.StackTrace
+ "\n\nBlob created by " + _backtrace);
} }
#endif #endif
DoDispose(); DoDispose();

7
src/Windows/Avalonia.Win32/FramebufferManager.cs

@ -5,7 +5,7 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
class FramebufferManager : IFramebufferPlatformSurface, IDisposable class FramebufferManager : IFramebufferPlatformSurface
{ {
private readonly IntPtr _hwnd; private readonly IntPtr _hwnd;
private WindowFramebuffer _fb; private WindowFramebuffer _fb;
@ -29,10 +29,5 @@ namespace Avalonia.Win32
} }
return _fb; return _fb;
} }
public void Dispose()
{
_fb?.Deallocate();
}
} }
} }

3
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -13,6 +13,7 @@ using Avalonia.Input.Raw;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Win32.Input; using Avalonia.Win32.Input;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
@ -234,8 +235,6 @@ namespace Avalonia.Win32
public void Dispose() public void Dispose()
{ {
_framebuffer?.Dispose();
_framebuffer = null;
if (_hwnd != IntPtr.Zero) if (_hwnd != IntPtr.Zero)
{ {
UnmanagedMethods.DestroyWindow(_hwnd); UnmanagedMethods.DestroyWindow(_hwnd);

18
tests/Avalonia.Controls.UnitTests/LayoutTransformControlTests.cs

@ -1,6 +1,4 @@
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Media; using Avalonia.Media;
using Xunit; using Xunit;
@ -311,20 +309,10 @@ namespace Avalonia.Controls.UnitTests
{ {
var lt = new LayoutTransformControl() var lt = new LayoutTransformControl()
{ {
LayoutTransform = transform, LayoutTransform = transform
Template = new FuncControlTemplate<LayoutTransformControl>(
p => new ContentPresenter() { Content = p.Content })
}; };
lt.Content = new Rectangle() { Width = width, Height = height }; lt.Child = 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.Measure(Size.Infinity); lt.Measure(Size.Infinity);
lt.Arrange(new Rect(lt.DesiredSize)); lt.Arrange(new Rect(lt.DesiredSize));
@ -332,4 +320,4 @@ namespace Avalonia.Controls.UnitTests
return lt; return lt;
} }
} }
} }

145
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests
TabItem selected; TabItem selected;
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = new[] Items = new[]
{ {
(selected = new TabItem (selected = new TabItem
@ -61,7 +61,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = items, Items = items,
}; };
@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = collection, Items = collection,
}; };
@ -147,7 +147,7 @@ namespace Avalonia.Controls.UnitTests
}, },
Child = new TabControl Child = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = collection, Items = collection,
} }
}; };
@ -172,7 +172,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
DataContext = "Base", DataContext = "Base",
DataTemplates = DataTemplates =
{ {
@ -182,41 +182,39 @@ namespace Avalonia.Controls.UnitTests
}; };
ApplyTemplate(target); ApplyTemplate(target);
var carousel = (Carousel)target.Pages;
var container = (ContentPresenter)carousel.Presenter.Panel.Children.Single(); target.ContentPart.UpdateChild();
container.UpdateChild(); var dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
var dataContext = ((TextBlock)container.Child).DataContext;
Assert.Equal(items[0], dataContext); Assert.Equal(items[0], dataContext);
target.SelectedIndex = 1; target.SelectedIndex = 1;
container = (ContentPresenter)carousel.Presenter.Panel.Children.Single(); target.ContentPart.UpdateChild();
container.UpdateChild(); dataContext = ((Button)target.ContentPart.Child).DataContext;
dataContext = ((Button)container.Child).DataContext;
Assert.Equal(items[1], dataContext); Assert.Equal(items[1], dataContext);
target.SelectedIndex = 2; target.SelectedIndex = 2;
dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext; target.ContentPart.UpdateChild();
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Base", dataContext); Assert.Equal("Base", dataContext);
target.SelectedIndex = 3; target.SelectedIndex = 3;
container = (ContentPresenter)carousel.Presenter.Panel.Children[0]; target.ContentPart.UpdateChild();
container.UpdateChild(); dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
dataContext = ((TextBlock)container.Child).DataContext;
Assert.Equal("Qux", dataContext); Assert.Equal("Qux", dataContext);
target.SelectedIndex = 4; target.SelectedIndex = 4;
dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext; target.ContentPart.UpdateChild();
dataContext = target.ContentPart.DataContext;
Assert.Equal("Base", dataContext); Assert.Equal("Base", dataContext);
} }
/// <summary> /// <summary>
/// Non-headered control items should result in TabStripItems with empty content. /// Non-headered control items should result in TabItems with empty header.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// If a TabStrip is created with non IHeadered controls as its items, don't try to /// If a TabControl 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 /// display the control in the header: if the control is part of the header then
/// then *that* will also try to display the control, resulting in dual-parentage /// *that* control would also end up in the content region, resulting in dual-parentage
/// breakage. /// breakage.
/// </remarks> /// </remarks>
[Fact] [Fact]
@ -230,18 +228,20 @@ namespace Avalonia.Controls.UnitTests
var target = new TabControl var target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = items, Items = items,
}; };
ApplyTemplate(target); ApplyTemplate(target);
var result = target.TabStrip.GetLogicalChildren() var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren();
.OfType<TabStripItem>()
.Select(x => x.Content) var result = logicalChildren
.OfType<TabItem>()
.Select(x => x.Header)
.ToList(); .ToList();
Assert.Equal(new object[] { string.Empty, string.Empty }, result); Assert.Equal(new object[] { null, null }, result);
} }
[Fact] [Fact]
@ -249,7 +249,7 @@ namespace Avalonia.Controls.UnitTests
{ {
TabControl target = new TabControl TabControl target = new TabControl
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = TabControlTemplate(),
Items = new[] Items = new[]
{ {
new TabItem { Header = "Foo" }, new TabItem { Header = "Foo" },
@ -262,70 +262,61 @@ namespace Avalonia.Controls.UnitTests
target.SelectedIndex = 2; target.SelectedIndex = 2;
var carousel = (Carousel)target.Pages; var page = (TabItem)target.SelectedItem;
var page = (TabItem)carousel.SelectedItem;
Assert.Null(page.Content); Assert.Null(page.Content);
} }
private Control CreateTabControlTemplate(TabControl parent) private IControlTemplate TabControlTemplate()
{ {
return new StackPanel return new FuncControlTemplate<TabControl>(parent =>
{
Children =
{
new TabStrip
{
Name = "PART_TabStrip",
Template = new FuncControlTemplate<TabStrip>(CreateTabStripTemplate),
MemberSelector = TabControl.HeaderSelector,
[!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
[!!TabStrip.SelectedIndexProperty] = parent[!!TabControl.SelectedIndexProperty]
},
new Carousel
{
Name = "PART_Content",
Template = new FuncControlTemplate<Carousel>(CreateCarouselTemplate),
MemberSelector = TabControl.ContentSelector,
[!Carousel.ItemsProperty] = parent[!TabControl.ItemsProperty],
[!Carousel.SelectedItemProperty] = parent[!TabControl.SelectedItemProperty],
}
}
};
}
private Control CreateTabStripTemplate(TabStrip parent) new StackPanel
{ {
return new ItemsPresenter Children = {
{ new ItemsPresenter
Name = "PART_ItemsPresenter", {
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], Name = "PART_ItemsPresenter",
[!CarouselPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty], [!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 return new FuncControlTemplate<TabItem>(parent =>
{ new ContentPresenter
Name = "PART_ItemsPresenter", {
[!CarouselPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty], Name = "PART_ContentPresenter",
[!CarouselPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty], [!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty],
[!CarouselPresenter.MemberSelectorProperty] = control[!ItemsControl.MemberSelectorProperty], [!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty]
[!CarouselPresenter.SelectedIndexProperty] = control[!SelectingItemsControl.SelectedIndexProperty], });
[~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty],
};
} }
private void ApplyTemplate(TabControl target) private void ApplyTemplate(TabControl target)
{ {
target.ApplyTemplate(); target.ApplyTemplate();
var carousel = (Carousel)target.Pages;
carousel.ApplyTemplate(); target.Presenter.ApplyTemplate();
carousel.Presenter.ApplyTemplate();
var tabStrip = (TabStrip)target.TabStrip; foreach (var tabItem in target.GetLogicalChildren().OfType<TabItem>())
tabStrip.ApplyTemplate(); {
tabStrip.Presenter.ApplyTemplate(); tabItem.Template = TabItemTemplate();
tabItem.ApplyTemplate();
((ContentPresenter)tabItem.Presenter).UpdateChild();
}
target.ContentPart.ApplyTemplate();
} }
private class Item private class Item

105
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);
}
}
}

2
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@ -99,6 +99,8 @@ namespace Avalonia.Layout.UnitTests
} }
}; };
window.Resources["ScrollBarThickness"] = 10.0;
window.Show(); window.Show();
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass(window);

100
tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs

@ -1,9 +1,13 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reactive.Subjects;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Markup.Data; using Avalonia.Data.Core;
using Avalonia.Markup.Parsers;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -162,5 +166,99 @@ namespace Avalonia.Markup.UnitTests.Data
decorator2.Child = target; decorator2.Child = target;
Assert.Equal("decorator2", target.Text); Assert.Equal("decorator2", target.Text);
} }
[Fact]
public void Should_Update_When_Detached_And_Attached_To_Visual_Tree_With_BindingPath()
{
TextBlock target;
Decorator decorator1;
Decorator decorator2;
var viewModel = new { Value = "Foo" };
var root1 = new TestRoot
{
Child = decorator1 = new Decorator
{
Name = "decorator1",
Child = target = new TextBlock(),
},
DataContext = viewModel
};
var root2 = new TestRoot
{
Child = decorator2 = new Decorator
{
Name = "decorator2",
},
DataContext = viewModel
};
var binding = new Binding
{
Path = "DataContext.Value",
RelativeSource = new RelativeSource
{
AncestorType = typeof(Decorator),
}
};
target.Bind(TextBox.TextProperty, binding);
Assert.Equal("Foo", target.Text);
decorator1.Child = null;
Assert.Null(target.Text);
decorator2.Child = target;
Assert.Equal("Foo", target.Text);
}
[Fact]
public void Should_Update_When_Detached_And_Attached_To_Visual_Tree_With_ComplexBindingPath()
{
TextBlock target;
Decorator decorator1;
Decorator decorator2;
var vm = new { Foo = new { Value = "Foo" } };
var root1 = new TestRoot
{
Child = decorator1 = new Decorator
{
Name = "decorator1",
Child = target = new TextBlock(),
},
DataContext = vm
};
var root2 = new TestRoot
{
Child = decorator2 = new Decorator
{
Name = "decorator2",
},
DataContext = vm
};
var binding = new Binding
{
Path = "DataContext.Foo.Value",
RelativeSource = new RelativeSource
{
AncestorType = typeof(Decorator),
}
};
target.Bind(TextBox.TextProperty, binding);
Assert.Equal("Foo", target.Text);
decorator1.Child = null;
Assert.Null(target.Text);
decorator2.Child = target;
Assert.Equal("Foo", target.Text);
}
} }
} }

58
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs

@ -1,10 +1,10 @@
using Avalonia.Data; using System;
using Avalonia.Markup.Parsers;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Text; using System.Reactive.Subjects;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Data;
using Avalonia.Markup.Parsers;
using Xunit; using Xunit;
namespace Avalonia.Markup.UnitTests.Parsers namespace Avalonia.Markup.UnitTests.Parsers
@ -38,5 +38,55 @@ namespace Avalonia.Markup.UnitTests.Parsers
GC.KeepAlive(data); GC.KeepAlive(data);
} }
[Fact]
public void Should_Update_Value_After_Root_Changes()
{
var root = new { DataContext = new { Value = "Foo" } };
var subject = new Subject<object>();
var obs = ExpressionObserverBuilder.Build(subject, "DataContext.Value");
var values = new List<object>();
obs.Subscribe(v => values.Add(v));
subject.OnNext(root);
subject.OnNext(null);
subject.OnNext(root);
Assert.Equal("Foo", values[0]);
Assert.IsType<BindingNotification>(values[1]);
var bn = values[1] as BindingNotification;
Assert.Equal(AvaloniaProperty.UnsetValue, bn.Value);
Assert.Equal(BindingErrorType.Error, bn.ErrorType);
Assert.Equal(3, values.Count);
Assert.Equal("Foo", values[2]);
}
[Fact]
public void Should_Update_Value_After_Root_Changes_With_ComplexPath()
{
var root = new { DataContext = new { Foo = new { Value = "Foo" } } };
var subject = new Subject<object>();
var obs = ExpressionObserverBuilder.Build(subject, "DataContext.Foo.Value");
var values = new List<object>();
obs.Subscribe(v => values.Add(v));
subject.OnNext(root);
subject.OnNext(null);
subject.OnNext(root);
Assert.Equal("Foo", values[0]);
Assert.IsType<BindingNotification>(values[1]);
var bn = values[1] as BindingNotification;
Assert.Equal(AvaloniaProperty.UnsetValue, bn.Value);
Assert.Equal(BindingErrorType.Error, bn.ErrorType);
Assert.Equal(3, values.Count);
Assert.Equal("Foo", values[2]);
}
} }
} }

4
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs

@ -405,7 +405,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Renderer = new DeferredRenderer(root, null); root.Renderer = new DeferredRenderer(root, null);
root.Measure(Size.Infinity); root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize)); root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(Rect.Empty);
var result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); var result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
Assert.Equal(item1, result); Assert.Equal(item1, result);
@ -421,6 +422,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
container.InvalidateArrange(); container.InvalidateArrange();
container.Arrange(new Rect(container.DesiredSize)); container.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(Rect.Empty);
result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
Assert.Equal(item2, result); Assert.Equal(item2, result);

Loading…
Cancel
Save