Browse Source

Merge branch 'master' into window-ownership

pull/3833/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
7e77d7b384
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build.sh
  2. 3
      native/Avalonia.Native/inc/avalonia-native.h
  3. 1
      native/Avalonia.Native/src/OSX/AvnString.h
  4. 12
      native/Avalonia.Native/src/OSX/AvnString.mm
  5. 34
      native/Avalonia.Native/src/OSX/clipboard.mm
  6. 6
      samples/ControlCatalog/App.xaml
  7. 53
      samples/ControlCatalog/App.xaml.cs
  8. 6
      samples/ControlCatalog/MainView.xaml
  9. 23
      samples/ControlCatalog/MainView.xaml.cs
  10. 2
      samples/ControlCatalog/MainWindow.xaml
  11. 5
      samples/ControlCatalog/Pages/ButtonPage.xaml
  12. 20
      samples/ControlCatalog/Pages/ButtonPage.xaml.cs
  13. 2
      samples/ControlCatalog/Pages/SliderPage.xaml
  14. 1
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  15. 11
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  16. 6
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  17. 16
      src/Avalonia.Animation/Animatable.cs
  18. 8
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  19. 93
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  20. 20
      src/Avalonia.Controls/IndexPath.cs
  21. 47
      src/Avalonia.Controls/IndexRange.cs
  22. 14
      src/Avalonia.Controls/Primitives/Track.cs
  23. 76
      src/Avalonia.Controls/SelectionModel.cs
  24. 2
      src/Avalonia.Controls/SelectionModelChangeSet.cs
  25. 22
      src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs
  26. 27
      src/Avalonia.Controls/SelectionNode.cs
  27. 160
      src/Avalonia.Controls/Slider.cs
  28. 416
      src/Avalonia.Controls/TickBar.cs
  29. 26
      src/Avalonia.Controls/ToolTip.cs
  30. 18
      src/Avalonia.Controls/TreeView.cs
  31. 8
      src/Avalonia.Controls/Utils/SelectionTreeHelper.cs
  32. 4
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  33. 17
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  34. 38
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  35. 24
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  36. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
  37. 6
      src/Avalonia.Input/Platform/IClipboard.cs
  38. 15
      src/Avalonia.Native/AvnString.cs
  39. 32
      src/Avalonia.Native/ClipboardImpl.cs
  40. 41
      src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
  41. 41
      src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
  42. 94
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  43. 94
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  44. 1
      src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml
  45. 1
      src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml
  46. 61
      src/Avalonia.Themes.Fluent/Button.xaml
  47. 15
      src/Avalonia.Themes.Fluent/Calendar.xaml
  48. 138
      src/Avalonia.Themes.Fluent/CalendarButton.xaml
  49. 164
      src/Avalonia.Themes.Fluent/CalendarDayButton.xaml
  50. 275
      src/Avalonia.Themes.Fluent/CalendarItem.xaml
  51. 1
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  52. 40
      src/Avalonia.Themes.Fluent/RepeatButton.xaml
  53. 323
      src/Avalonia.Themes.Fluent/Slider.xaml
  54. 95
      src/Avalonia.Themes.Fluent/ToolTip.xaml
  55. 35
      src/Avalonia.X11/X11Atoms.cs
  56. 135
      src/Avalonia.X11/X11Clipboard.cs
  57. 4
      src/Avalonia.X11/XLib.cs
  58. 8
      src/Markup/Avalonia.Markup/Data/MultiBinding.cs
  59. 3
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  60. 76
      src/Windows/Avalonia.Win32/ClipboardImpl.cs
  61. 6
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  62. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  63. 6
      src/iOS/Avalonia.iOS/Clipboard.cs
  64. 65
      tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
  65. 88
      tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs
  66. 82
      tests/Avalonia.Controls.UnitTests/IndexRangeTests.cs
  67. 146
      tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs
  68. 24
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  69. 5
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  70. 71
      tests/Avalonia.Controls.UnitTests/ToolTipTests.cs
  71. 548
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  72. 44
      tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs
  73. 53
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs

2
build.sh

@ -67,6 +67,8 @@ else
fi fi
fi fi
export PATH=$DOTNET_DIRECTORY:$PATH
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}

3
native/Avalonia.Native/inc/avalonia-native.h

@ -387,6 +387,9 @@ AVNCOM(IAvnClipboard, 0f) : IUnknown
virtual HRESULT SetText (char* type, void* utf8Text) = 0; virtual HRESULT SetText (char* type, void* utf8Text) = 0;
virtual HRESULT ObtainFormats(IAvnStringArray**ppv) = 0; virtual HRESULT ObtainFormats(IAvnStringArray**ppv) = 0;
virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) = 0; virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) = 0;
virtual HRESULT SetBytes(char* type, void* utf8Text, int len) = 0;
virtual HRESULT GetBytes(char* type, IAvnString**ppv) = 0;
virtual HRESULT Clear() = 0; virtual HRESULT Clear() = 0;
}; };

1
native/Avalonia.Native/src/OSX/AvnString.h

@ -12,4 +12,5 @@
extern IAvnString* CreateAvnString(NSString* string); extern IAvnString* CreateAvnString(NSString* string);
extern IAvnStringArray* CreateAvnStringArray(NSArray<NSString*>* array); extern IAvnStringArray* CreateAvnStringArray(NSArray<NSString*>* array);
extern IAvnStringArray* CreateAvnStringArray(NSString* string); extern IAvnStringArray* CreateAvnStringArray(NSString* string);
extern IAvnString* CreateByteArray(void* data, int len);
#endif /* AvnString_h */ #endif /* AvnString_h */

12
native/Avalonia.Native/src/OSX/AvnString.mm

@ -29,6 +29,13 @@ public:
memcpy((void*)_cstring, (void*)cstring, _length); memcpy((void*)_cstring, (void*)cstring, _length);
} }
AvnStringImpl(void*ptr, int len)
{
_length = len;
_cstring = (const char*)malloc(_length);
memcpy((void*)_cstring, ptr, len);
}
virtual ~AvnStringImpl() virtual ~AvnStringImpl()
{ {
free((void*)_cstring); free((void*)_cstring);
@ -114,3 +121,8 @@ IAvnStringArray* CreateAvnStringArray(NSString* string)
{ {
return new AvnStringArrayImpl(string); return new AvnStringArrayImpl(string);
} }
IAvnString* CreateByteArray(void* data, int len)
{
return new AvnStringImpl(data, len);
}

34
native/Avalonia.Native/src/OSX/clipboard.mm

@ -82,6 +82,40 @@ public:
return S_OK; return S_OK;
} }
virtual HRESULT SetBytes(char* type, void* bytes, int len) override
{
auto typeString = [NSString stringWithUTF8String:(const char*)type];
auto data = [NSData dataWithBytes:bytes length:len];
if(_item == nil)
[_pb setData:data forType:typeString];
else
[_item setData:data forType:typeString];
return S_OK;
}
virtual HRESULT GetBytes(char* type, IAvnString**ppv) override
{
*ppv = nil;
auto typeString = [NSString stringWithUTF8String:(const char*)type];
NSData*data;
@try
{
if(_item)
data = [_item dataForType:typeString];
else
data = [_pb dataForType:typeString];
if(data == nil)
return E_FAIL;
}
@catch(NSException* e)
{
return E_FAIL;
}
*ppv = CreateByteArray((void*)data.bytes, (int)data.length);
return S_OK;
}
virtual HRESULT Clear() override virtual HRESULT Clear() override
{ {

6
samples/ControlCatalog/App.xaml

@ -1,10 +1,8 @@
<Application xmlns="https://github.com/avaloniaui" <Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.App"> x:Class="ControlCatalog.App">
<Application.Styles> <Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/> <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
<Style Selector="TextBlock.h1"> <Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/> <Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
<Setter Property="FontWeight" Value="Medium"/> <Setter Property="FontWeight" Value="Medium"/>

53
samples/ControlCatalog/App.xaml.cs

@ -1,14 +1,67 @@
using System;
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
namespace ControlCatalog namespace ControlCatalog
{ {
public class App : Application public class App : Application
{ {
public static Styles FluentDark = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Fluent.Accents.FluentDark.xaml?assembly=Avalonia.Themes.Fluent")
},
};
public static Styles FluentLight = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Fluent.Accents.FluentLight.xaml?assembly=Avalonia.Themes.Fluent")
},
};
public static Styles DefaultLight = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default")
},
};
public static Styles DefaultDark = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default")
},
};
public override void Initialize() public override void Initialize()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
Styles.Insert(0, FluentDark);
} }
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()

6
samples/ControlCatalog/MainView.xaml

@ -67,8 +67,10 @@
<ComboBoxItem>Full Decorations</ComboBoxItem> <ComboBoxItem>Full Decorations</ComboBoxItem>
</ComboBox> </ComboBox>
<ComboBox x:Name="Themes" SelectedIndex="0"> <ComboBox x:Name="Themes" SelectedIndex="0">
<ComboBoxItem>Light</ComboBoxItem> <ComboBoxItem>Fluent - Dark</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem> <ComboBoxItem>Fluent - Light</ComboBoxItem>
<ComboBoxItem>Simple - Light</ComboBoxItem>
<ComboBoxItem>Simple - Dark</ComboBoxItem>
</ComboBox> </ComboBox>
<ComboBox x:Name="TransparencyLevels" SelectedIndex="0"> <ComboBox x:Name="TransparencyLevels" SelectedIndex="0">
<ComboBoxItem>None</ComboBoxItem> <ComboBoxItem>None</ComboBoxItem>

23
samples/ControlCatalog/MainView.xaml.cs

@ -32,30 +32,25 @@ namespace ControlCatalog
} }
var light = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
};
var dark = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
};
var themes = this.Find<ComboBox>("Themes"); var themes = this.Find<ComboBox>("Themes");
themes.SelectionChanged += (sender, e) => themes.SelectionChanged += (sender, e) =>
{ {
switch (themes.SelectedIndex) switch (themes.SelectedIndex)
{ {
case 0: case 0:
Styles[0] = light; Application.Current.Styles[0] = App.FluentDark;
break; break;
case 1: case 1:
Styles[0] = dark; Application.Current.Styles[0] = App.FluentLight;
break;
case 2:
Application.Current.Styles[0] = App.DefaultLight;
break;
case 3:
Application.Current.Styles[0] = App.DefaultDark;
break; break;
} }
}; };
Styles.Add(light);
var decorations = this.Find<ComboBox>("Decorations"); var decorations = this.Find<ComboBox>("Decorations");
decorations.SelectionChanged += (sender, e) => decorations.SelectionChanged += (sender, e) =>

2
samples/ControlCatalog/MainWindow.xaml

@ -7,7 +7,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views" xmlns:v="clr-namespace:ControlCatalog.Views"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="Transparent"> x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{DynamicResource SystemControlPageBackgroundAltHighBrush}">
<NativeMenu.Menu> <NativeMenu.Menu>
<NativeMenu> <NativeMenu>
<NativeMenuItem Header="File"> <NativeMenuItem Header="File">

5
samples/ControlCatalog/Pages/ButtonPage.xaml

@ -24,7 +24,10 @@
</Style> </Style>
</Button.Styles> </Button.Styles>
</Button> </Button>
</StackPanel> <RepeatButton Name="RepeatButton">
<TextBlock Name="RepeatButtonTextBlock" Text="Repeat Button: 0" />
</RepeatButton>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8" Width="150"> <StackPanel Orientation="Vertical" Spacing="8" Width="150">
<Button BorderThickness="0">No Border</Button> <Button BorderThickness="0">No Border</Button>

20
samples/ControlCatalog/Pages/ButtonPage.xaml.cs

@ -5,5 +5,25 @@ namespace ControlCatalog.Pages
{ {
public class ButtonPage : UserControl public class ButtonPage : UserControl
{ {
private int repeatButtonClickCount = 0;
public ButtonPage()
{
InitializeComponent();
this.FindControl<RepeatButton>("RepeatButton").Click += OnRepeatButtonClick;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void OnRepeatButtonClick(object sender, object args)
{
repeatButtonClickCount++;
var textBlock = this.FindControl<TextBlock>("RepeatButtonTextBlock");
textBlock.Text = $"Repeat Button: {repeatButtonClickCount}";
}
} }
} }

2
samples/ControlCatalog/Pages/SliderPage.xaml

@ -9,12 +9,14 @@
<Slider Value="0" <Slider Value="0"
Minimum="0" Minimum="0"
Maximum="100" Maximum="100"
TickFrequency="10"
Width="300"/> Width="300"/>
<Slider Value="0" <Slider Value="0"
Minimum="0" Minimum="0"
Maximum="100" Maximum="100"
Orientation="Vertical" Orientation="Vertical"
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
TickPlacement="Outside"
TickFrequency="10" TickFrequency="10"
Height="300"/> Height="300"/>
</StackPanel> </StackPanel>

1
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -20,6 +20,7 @@
<Button Command="{Binding AddItemCommand}">Add</Button> <Button Command="{Binding AddItemCommand}">Add</Button>
<Button Command="{Binding RemoveItemCommand}">Remove</Button> <Button Command="{Binding RemoveItemCommand}">Remove</Button>
<Button Command="{Binding SelectRandomItemCommand}">Select Random</Button>
<ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}"> <ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">
<ComboBoxItem>Single</ComboBoxItem> <ComboBoxItem>Single</ComboBoxItem>

11
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@ -23,12 +23,14 @@ namespace ControlCatalog.ViewModels
AddItemCommand = ReactiveCommand.Create(AddItem); AddItemCommand = ReactiveCommand.Create(AddItem);
RemoveItemCommand = ReactiveCommand.Create(RemoveItem); RemoveItemCommand = ReactiveCommand.Create(RemoveItem);
SelectRandomItemCommand = ReactiveCommand.Create(SelectRandomItem);
} }
public ObservableCollection<Node> Items { get; } public ObservableCollection<Node> Items { get; }
public SelectionModel Selection { get; } public SelectionModel Selection { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; } public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; } public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public ReactiveCommand<Unit, Unit> SelectRandomItemCommand { get; }
public SelectionMode SelectionMode public SelectionMode SelectionMode
{ {
@ -74,6 +76,15 @@ namespace ControlCatalog.ViewModels
} }
} }
private void SelectRandomItem()
{
var random = new Random();
var depth = random.Next(4);
var indexes = Enumerable.Range(0, 4).Select(x => random.Next(10));
var path = new IndexPath(indexes);
Selection.SelectedIndex = path;
}
private void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) private void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{ {
var selected = string.Join(",", e.SelectedIndices); var selected = string.Join(",", e.SelectedIndices);

6
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -43,5 +43,11 @@ namespace Avalonia.Android.Platform
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
} }
public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException();
public Task<string[]> GetFormatsAsync() => throw new PlatformNotSupportedException();
public Task<object> GetDataAsync(string format) => throw new PlatformNotSupportedException();
} }
} }

16
src/Avalonia.Animation/Animatable.cs

@ -93,17 +93,17 @@ namespace Avalonia.Animation
var oldTransitions = change.OldValue.GetValueOrDefault<Transitions>(); var oldTransitions = change.OldValue.GetValueOrDefault<Transitions>();
var newTransitions = change.NewValue.GetValueOrDefault<Transitions>(); var newTransitions = change.NewValue.GetValueOrDefault<Transitions>();
if (oldTransitions is object)
{
oldTransitions.CollectionChanged -= TransitionsCollectionChanged;
RemoveTransitions(oldTransitions);
}
if (newTransitions is object) if (newTransitions is object)
{ {
newTransitions.CollectionChanged += TransitionsCollectionChanged; newTransitions.CollectionChanged += TransitionsCollectionChanged;
AddTransitions(newTransitions); AddTransitions(newTransitions);
} }
if (oldTransitions is object)
{
oldTransitions.CollectionChanged -= TransitionsCollectionChanged;
RemoveTransitions(oldTransitions);
}
} }
else if (_transitionsEnabled && else if (_transitionsEnabled &&
Transitions is object && Transitions is object &&
@ -111,8 +111,10 @@ namespace Avalonia.Animation
!change.Property.IsDirect && !change.Property.IsDirect &&
change.Priority > BindingPriority.Animation) change.Priority > BindingPriority.Animation)
{ {
foreach (var transition in Transitions) for (var i = Transitions.Count -1; i >= 0; --i)
{ {
var transition = Transitions[i];
if (transition.Property == change.Property) if (transition.Property == change.Property)
{ {
var state = _transitionState[transition]; var state = _transitionState[transition];

8
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -1,7 +1,5 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -31,7 +29,11 @@ namespace Avalonia.Data.Core.Plugins
Contract.Requires<ArgumentNullException>(propertyName != null); Contract.Requires<ArgumentNullException>(propertyName != null);
reference.TryGetTarget(out object instance); reference.TryGetTarget(out object instance);
var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name == propertyName);
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Static | BindingFlags.Instance;
var p = instance.GetType().GetProperty(propertyName, bindingFlags);
if (p != null) if (p != null)
{ {

93
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -1,13 +1,16 @@
using System; using System;
using System.Linq; using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace Avalonia.Data.Core.Plugins namespace Avalonia.Data.Core.Plugins
{ {
class MethodAccessorPlugin : IPropertyAccessorPlugin public class MethodAccessorPlugin : IPropertyAccessorPlugin
{ {
public bool Match(object obj, string methodName) private readonly Dictionary<(Type, string), MethodInfo> _methodLookup =
=> obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName); new Dictionary<(Type, string), MethodInfo>();
public bool Match(object obj, string methodName) => GetFirstMethodWithName(obj.GetType(), methodName) != null;
public IPropertyAccessor Start(WeakReference<object> reference, string methodName) public IPropertyAccessor Start(WeakReference<object> reference, string methodName)
{ {
@ -15,17 +18,22 @@ namespace Avalonia.Data.Core.Plugins
Contract.Requires<ArgumentNullException>(methodName != null); Contract.Requires<ArgumentNullException>(methodName != null);
reference.TryGetTarget(out object instance); reference.TryGetTarget(out object instance);
var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName);
var method = GetFirstMethodWithName(instance.GetType(), methodName);
if (method != null) if (method != null)
{ {
if (method.GetParameters().Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) var parameters = method.GetParameters();
if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8)
{ {
var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(methodName)); var exception = new ArgumentException(
"Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.",
nameof(methodName));
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
} }
return new Accessor(reference, method); return new Accessor(reference, method, parameters);
} }
else else
{ {
@ -35,31 +43,72 @@ namespace Avalonia.Data.Core.Plugins
} }
} }
private MethodInfo GetFirstMethodWithName(Type type, string methodName)
{
var key = (type, methodName);
if (!_methodLookup.TryGetValue(key, out MethodInfo methodInfo))
{
methodInfo = TryFindAndCacheMethod(type, methodName);
}
return methodInfo;
}
private MethodInfo TryFindAndCacheMethod(Type type, string methodName)
{
MethodInfo found = null;
const BindingFlags bindingFlags =
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
var methods = type.GetMethods(bindingFlags);
foreach (MethodInfo methodInfo in methods)
{
if (methodInfo.Name == methodName)
{
found = methodInfo;
break;
}
}
_methodLookup.Add((type, methodName), found);
return found;
}
private sealed class Accessor : PropertyAccessorBase private sealed class Accessor : PropertyAccessorBase
{ {
public Accessor(WeakReference<object> reference, MethodInfo method) public Accessor(WeakReference<object> reference, MethodInfo method, ParameterInfo[] parameters)
{ {
Contract.Requires<ArgumentNullException>(reference != null); Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(method != null); Contract.Requires<ArgumentNullException>(method != null);
var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray();
var returnType = method.ReturnType; var returnType = method.ReturnType;
bool hasReturn = returnType != typeof(void);
if (returnType == typeof(void))
var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length;
var paramTypes = new Type[signatureTypeCount];
for (var i = 0; i < parameters.Length; i++)
{ {
if (paramTypes.Length == 0) ParameterInfo parameter = parameters[i];
{
PropertyType = typeof(Action); paramTypes[i] = parameter.ParameterType;
} }
else
{ if (hasReturn)
PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes); {
} paramTypes[paramTypes.Length - 1] = returnType;
PropertyType = Expression.GetFuncType(paramTypes);
} }
else else
{ {
var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); PropertyType = Expression.GetActionType(paramTypes);
PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters);
} }
if (method.IsStatic) if (method.IsStatic)

20
src/Avalonia.Controls/IndexPath.cs

@ -123,6 +123,26 @@ namespace Avalonia.Controls
} }
} }
public bool IsAncestorOf(in IndexPath other)
{
if (other.GetSize() <= GetSize())
{
return false;
}
var size = GetSize();
for (int i = 0; i < size; i++)
{
if (GetAt(i) != other.GetAt(i))
{
return false;
}
}
return true;
}
public override string ToString() public override string ToString()
{ {
if (_path != null) if (_path != null)

47
src/Avalonia.Controls/IndexRange.cs

@ -132,6 +132,53 @@ namespace Avalonia.Controls
return result; return result;
} }
public static int Intersect(
IList<IndexRange> ranges,
IndexRange range,
IList<IndexRange>? removed = null)
{
var result = 0;
for (var i = 0; i < ranges.Count && range != s_invalid; ++i)
{
var existing = ranges[i];
if (existing.End < range.Begin || existing.Begin > range.End)
{
removed?.Add(existing);
ranges.RemoveAt(i--);
result += existing.Count;
}
else
{
if (existing.Begin < range.Begin)
{
var except = new IndexRange(existing.Begin, range.Begin - 1);
removed?.Add(except);
ranges[i] = existing = new IndexRange(range.Begin, existing.End);
result += except.Count;
}
if (existing.End > range.End)
{
var except = new IndexRange(range.End + 1, existing.End);
removed?.Add(except);
ranges[i] = new IndexRange(existing.Begin, range.End);
result += except.Count;
}
}
}
MergeRanges(ranges);
if (removed is object)
{
MergeRanges(removed);
}
return result;
}
public static int Remove( public static int Remove(
IList<IndexRange> ranges, IList<IndexRange> ranges,
IndexRange range, IndexRange range,

14
src/Avalonia.Controls/Primitives/Track.cs

@ -41,13 +41,16 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> IsDirectionReversedProperty = public static readonly StyledProperty<bool> IsDirectionReversedProperty =
AvaloniaProperty.Register<Track, bool>(nameof(IsDirectionReversed)); AvaloniaProperty.Register<Track, bool>(nameof(IsDirectionReversed));
public static readonly StyledProperty<bool> IgnoreThumbDragProperty =
AvaloniaProperty.Register<Track, bool>(nameof(IsThumbDragHandled));
private double _minimum; private double _minimum;
private double _maximum = 100.0; private double _maximum = 100.0;
private double _value; private double _value;
static Track() static Track()
{ {
ThumbProperty.Changed.AddClassHandler<Track>((x,e) => x.ThumbChanged(e)); ThumbProperty.Changed.AddClassHandler<Track>((x, e) => x.ThumbChanged(e));
IncreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e)); IncreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
DecreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e)); DecreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty); AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
@ -113,6 +116,12 @@ namespace Avalonia.Controls.Primitives
set { SetValue(IsDirectionReversedProperty, value); } set { SetValue(IsDirectionReversedProperty, value); }
} }
public bool IsThumbDragHandled
{
get { return GetValue(IgnoreThumbDragProperty); }
set { SetValue(IgnoreThumbDragProperty, value); }
}
private double ThumbCenterOffset { get; set; } private double ThumbCenterOffset { get; set; }
private double Density { get; set; } private double Density { get; set; }
@ -422,6 +431,9 @@ namespace Avalonia.Controls.Primitives
private void ThumbDragged(object sender, VectorEventArgs e) private void ThumbDragged(object sender, VectorEventArgs e)
{ {
if (IsThumbDragHandled)
return;
Value = MathUtilities.Clamp( Value = MathUtilities.Clamp(
Value + ValueFromDistance(e.Vector.X, e.Vector.Y), Value + ValueFromDistance(e.Vector.X, e.Vector.Y),
Minimum, Minimum,

76
src/Avalonia.Controls/SelectionModel.cs

@ -46,17 +46,25 @@ namespace Avalonia.Controls
if (_rootNode.Source != null) if (_rootNode.Source != null)
{ {
if (_rootNode.Source != null) // Temporarily prevent auto-select when switching source.
var restoreAutoSelect = _autoSelect;
_autoSelect = false;
try
{ {
using (var operation = new Operation(this)) using (var operation = new Operation(this))
{ {
ClearSelection(resetAnchor: true); ClearSelection(resetAnchor: true);
} }
} }
finally
{
_autoSelect = restoreAutoSelect;
}
} }
_rootNode.Source = value; _rootNode.Source = value;
ApplyAutoSelect(); ApplyAutoSelect(true);
RaisePropertyChanged("Source"); RaisePropertyChanged("Source");
@ -114,7 +122,7 @@ namespace Avalonia.Controls
if (_autoSelect != value) if (_autoSelect != value)
{ {
_autoSelect = value; _autoSelect = value;
ApplyAutoSelect(); ApplyAutoSelect(true);
} }
} }
} }
@ -133,7 +141,7 @@ namespace Avalonia.Controls
while (current?.AnchorIndex >= 0) while (current?.AnchorIndex >= 0)
{ {
path.Add(current.AnchorIndex); path.Add(current.AnchorIndex);
current = current.GetAt(current.AnchorIndex, false); current = current.GetAt(current.AnchorIndex, false, default);
} }
anchor = new IndexPath(path); anchor = new IndexPath(path);
@ -188,7 +196,6 @@ namespace Avalonia.Controls
using var operation = new Operation(this); using var operation = new Operation(this);
ClearSelection(resetAnchor: true); ClearSelection(resetAnchor: true);
SelectWithPathImpl(value, select: true); SelectWithPathImpl(value, select: true);
ApplyAutoSelect();
} }
} }
} }
@ -384,21 +391,18 @@ namespace Avalonia.Controls
{ {
using var operation = new Operation(this); using var operation = new Operation(this);
SelectImpl(index, select: false); SelectImpl(index, select: false);
ApplyAutoSelect();
} }
public void Deselect(int groupIndex, int itemIndex) public void Deselect(int groupIndex, int itemIndex)
{ {
using var operation = new Operation(this); using var operation = new Operation(this);
SelectWithGroupImpl(groupIndex, itemIndex, select: false); SelectWithGroupImpl(groupIndex, itemIndex, select: false);
ApplyAutoSelect();
} }
public void DeselectAt(IndexPath index) public void DeselectAt(IndexPath index)
{ {
using var operation = new Operation(this); using var operation = new Operation(this);
SelectWithPathImpl(index, select: false); SelectWithPathImpl(index, select: false);
ApplyAutoSelect();
} }
public bool IsSelected(int index) => _rootNode.IsSelected(index); public bool IsSelected(int index) => _rootNode.IsSelected(index);
@ -416,7 +420,7 @@ namespace Avalonia.Controls
for (int i = 0; i < path.GetSize() - 1; i++) for (int i = 0; i < path.GetSize() - 1; i++)
{ {
var childIndex = path.GetAt(i); var childIndex = path.GetAt(i);
node = node.GetAt(childIndex, realizeChild: false); node = node.GetAt(childIndex, false, default);
if (node == null) if (node == null)
{ {
@ -451,7 +455,7 @@ namespace Avalonia.Controls
} }
var isSelected = (bool?)false; var isSelected = (bool?)false;
var childNode = _rootNode.GetAt(groupIndex, realizeChild: false); var childNode = _rootNode.GetAt(groupIndex, false, default);
if (childNode != null) if (childNode != null)
{ {
@ -470,7 +474,7 @@ namespace Avalonia.Controls
for (int i = 0; i < path.GetSize() - 1; i++) for (int i = 0; i < path.GetSize() - 1; i++)
{ {
var childIndex = path.GetAt(i); var childIndex = path.GetAt(i);
node = node.GetAt(childIndex, realizeChild: false); node = node.GetAt(childIndex, false, default);
if (node == null) if (node == null)
{ {
@ -565,7 +569,6 @@ namespace Avalonia.Controls
{ {
using var operation = new Operation(this); using var operation = new Operation(this);
ClearSelection(resetAnchor: true); ClearSelection(resetAnchor: true);
ApplyAutoSelect();
} }
public IDisposable Update() => new Operation(this); public IDisposable Update() => new Operation(this);
@ -592,10 +595,13 @@ namespace Avalonia.Controls
} }
OnSelectionChanged(e); OnSelectionChanged(e);
ApplyAutoSelect(); ApplyAutoSelect(true);
} }
internal IObservable<object?>? ResolvePath(object data, IndexPath dataIndexPath) internal IObservable<object?>? ResolvePath(
object data,
IndexPath dataIndexPath,
IndexPath finalIndexPath)
{ {
IObservable<object?>? resolved = null; IObservable<object?>? resolved = null;
@ -604,18 +610,22 @@ namespace Avalonia.Controls
{ {
if (_childrenRequestedEventArgs == null) if (_childrenRequestedEventArgs == null)
{ {
_childrenRequestedEventArgs = new SelectionModelChildrenRequestedEventArgs(data, dataIndexPath, false); _childrenRequestedEventArgs = new SelectionModelChildrenRequestedEventArgs(
data,
dataIndexPath,
finalIndexPath,
false);
} }
else else
{ {
_childrenRequestedEventArgs.Initialize(data, dataIndexPath, false); _childrenRequestedEventArgs.Initialize(data, dataIndexPath, finalIndexPath, false);
} }
ChildrenRequested(this, _childrenRequestedEventArgs); ChildrenRequested(this, _childrenRequestedEventArgs);
resolved = _childrenRequestedEventArgs.Children; resolved = _childrenRequestedEventArgs.Children;
// Clear out the values in the args so that it cannot be used after the event handler call. // Clear out the values in the args so that it cannot be used after the event handler call.
_childrenRequestedEventArgs.Initialize(null, default, true); _childrenRequestedEventArgs.Initialize(null, default, default, true);
} }
return resolved; return resolved;
@ -632,6 +642,8 @@ namespace Avalonia.Controls
{ {
AnchorIndex = default; AnchorIndex = default;
} }
OnSelectionChanged();
} }
private void OnSelectionChanged(SelectionModelSelectionChangedEventArgs? e = null) private void OnSelectionChanged(SelectionModelSelectionChangedEventArgs? e = null)
@ -667,6 +679,8 @@ namespace Avalonia.Controls
{ {
AnchorIndex = new IndexPath(index); AnchorIndex = new IndexPath(index);
} }
OnSelectionChanged();
} }
private void SelectWithGroupImpl(int groupIndex, int itemIndex, bool select) private void SelectWithGroupImpl(int groupIndex, int itemIndex, bool select)
@ -676,13 +690,15 @@ namespace Avalonia.Controls
ClearSelection(resetAnchor: true); ClearSelection(resetAnchor: true);
} }
var childNode = _rootNode.GetAt(groupIndex, realizeChild: true); var childNode = _rootNode.GetAt(groupIndex, true, new IndexPath(groupIndex, itemIndex));
var selected = childNode!.Select(itemIndex, select); var selected = childNode!.Select(itemIndex, select);
if (selected) if (selected)
{ {
AnchorIndex = new IndexPath(groupIndex, itemIndex); AnchorIndex = new IndexPath(groupIndex, itemIndex);
} }
OnSelectionChanged();
} }
private void SelectWithPathImpl(IndexPath index, bool select) private void SelectWithPathImpl(IndexPath index, bool select)
@ -711,6 +727,8 @@ namespace Avalonia.Controls
{ {
AnchorIndex = index; AnchorIndex = index;
} }
OnSelectionChanged();
} }
private void SelectRangeFromAnchorImpl(int index, bool select) private void SelectRangeFromAnchorImpl(int index, bool select)
@ -724,6 +742,7 @@ namespace Avalonia.Controls
} }
_rootNode.SelectRange(new IndexRange(anchorIndex, index), select); _rootNode.SelectRange(new IndexRange(anchorIndex, index), select);
OnSelectionChanged();
} }
private void SelectRangeFromAnchorWithGroupImpl(int endGroupIndex, int endItemIndex, bool select) private void SelectRangeFromAnchorWithGroupImpl(int endGroupIndex, int endItemIndex, bool select)
@ -752,11 +771,13 @@ namespace Avalonia.Controls
for (int groupIdx = startGroupIndex; groupIdx <= endGroupIndex; groupIdx++) for (int groupIdx = startGroupIndex; groupIdx <= endGroupIndex; groupIdx++)
{ {
var groupNode = _rootNode.GetAt(groupIdx, realizeChild: true)!; var groupNode = _rootNode.GetAt(groupIdx, true, new IndexPath(endGroupIndex, endItemIndex))!;
int startIndex = groupIdx == startGroupIndex ? startItemIndex : 0; int startIndex = groupIdx == startGroupIndex ? startItemIndex : 0;
int endIndex = groupIdx == endGroupIndex ? endItemIndex : groupNode.DataCount - 1; int endIndex = groupIdx == endGroupIndex ? endItemIndex : groupNode.DataCount - 1;
groupNode.SelectRange(new IndexRange(startIndex, endIndex), select); groupNode.SelectRange(new IndexRange(startIndex, endIndex), select);
} }
OnSelectionChanged();
} }
private void SelectRangeImpl(IndexPath start, IndexPath end, bool select) private void SelectRangeImpl(IndexPath start, IndexPath end, bool select)
@ -784,6 +805,8 @@ namespace Avalonia.Controls
info.ParentNode!.Select(info.Path.GetAt(info.Path.GetSize() - 1), select); info.ParentNode!.Select(info.Path.GetAt(info.Path.GetSize() - 1), select);
} }
}); });
OnSelectionChanged();
} }
private void BeginOperation() private void BeginOperation()
@ -806,6 +829,8 @@ namespace Avalonia.Controls
if (--_operationCount == 0) if (--_operationCount == 0)
{ {
ApplyAutoSelect(false);
var changes = new List<SelectionNodeOperation>(); var changes = new List<SelectionNodeOperation>();
_rootNode.EndOperation(changes); _rootNode.EndOperation(changes);
@ -827,7 +852,7 @@ namespace Avalonia.Controls
} }
} }
private void ApplyAutoSelect() private void ApplyAutoSelect(bool createOperation)
{ {
if (AutoSelect) if (AutoSelect)
{ {
@ -835,8 +860,15 @@ namespace Avalonia.Controls
if (SelectedIndex == default && _rootNode.ItemsSourceView?.Count > 0) if (SelectedIndex == default && _rootNode.ItemsSourceView?.Count > 0)
{ {
using var operation = new Operation(this); if (createOperation)
SelectImpl(0, true); {
using var operation = new Operation(this);
SelectImpl(0, true);
}
else
{
SelectImpl(0, true);
}
} }
} }
} }

2
src/Avalonia.Controls/SelectionModelChangeSet.cs

@ -135,7 +135,7 @@ namespace Avalonia.Controls
if (index >= currentIndex && index < currentIndex + currentCount) if (index >= currentIndex && index < currentIndex + currentCount)
{ {
int targetIndex = GetIndexAt(getRanges(info), index - currentIndex); int targetIndex = GetIndexAt(getRanges(info), index - currentIndex);
item = info.Items?.GetAt(targetIndex); item = info.Items?.Count > targetIndex ? info.Items?.GetAt(targetIndex) : null;
break; break;
} }

22
src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs

@ -16,15 +16,17 @@ namespace Avalonia.Controls
{ {
private object? _source; private object? _source;
private IndexPath _sourceIndexPath; private IndexPath _sourceIndexPath;
private IndexPath _finalIndexPath;
private bool _throwOnAccess; private bool _throwOnAccess;
internal SelectionModelChildrenRequestedEventArgs( internal SelectionModelChildrenRequestedEventArgs(
object source, object source,
IndexPath sourceIndexPath, IndexPath sourceIndexPath,
IndexPath finalIndexPath,
bool throwOnAccess) bool throwOnAccess)
{ {
source = source ?? throw new ArgumentNullException(nameof(source)); source = source ?? throw new ArgumentNullException(nameof(source));
Initialize(source, sourceIndexPath, throwOnAccess); Initialize(source, sourceIndexPath, finalIndexPath, throwOnAccess);
} }
/// <summary> /// <summary>
@ -65,9 +67,26 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Gets the index of the final object which is being attempted to be retrieved.
/// </summary>
public IndexPath FinalIndex
{
get
{
if (_throwOnAccess)
{
throw new ObjectDisposedException(nameof(SelectionModelChildrenRequestedEventArgs));
}
return _finalIndexPath;
}
}
internal void Initialize( internal void Initialize(
object? source, object? source,
IndexPath sourceIndexPath, IndexPath sourceIndexPath,
IndexPath finalIndexPath,
bool throwOnAccess) bool throwOnAccess)
{ {
if (!throwOnAccess && source == null) if (!throwOnAccess && source == null)
@ -77,6 +96,7 @@ namespace Avalonia.Controls
_source = source; _source = source;
_sourceIndexPath = sourceIndexPath; _sourceIndexPath = sourceIndexPath;
_finalIndexPath = finalIndexPath;
_throwOnAccess = throwOnAccess; _throwOnAccess = throwOnAccess;
} }
} }

27
src/Avalonia.Controls/SelectionNode.cs

@ -101,6 +101,7 @@ namespace Avalonia.Controls
ItemsSourceView = newDataSource; ItemsSourceView = newDataSource;
TrimInvalidSelections();
PopulateSelectedItemsFromSelectedIndices(); PopulateSelectedItemsFromSelectedIndices();
HookupCollectionChangedHandler(); HookupCollectionChangedHandler();
OnSelectionChanged(); OnSelectionChanged();
@ -108,6 +109,26 @@ namespace Avalonia.Controls
} }
} }
private void TrimInvalidSelections()
{
if (_selected == null || ItemsSourceView == null)
{
return;
}
var validRange = ItemsSourceView.Count > 0 ? new IndexRange(0, ItemsSourceView.Count - 1) : new IndexRange(-1, -1);
var removed = new List<IndexRange>();
var removedCount = IndexRange.Intersect(_selected, validRange, removed);
if (removedCount > 0)
{
using var operation = _manager.Update();
SelectedCount -= removedCount;
OnSelectionChanged();
_operation!.Deselected(removed);
}
}
public ItemsSourceView? ItemsSourceView { get; private set; } public ItemsSourceView? ItemsSourceView { get; private set; }
public int DataCount => ItemsSourceView?.Count ?? 0; public int DataCount => ItemsSourceView?.Count ?? 0;
public int ChildrenNodeCount => _childrenNodes.Count; public int ChildrenNodeCount => _childrenNodes.Count;
@ -141,7 +162,7 @@ namespace Avalonia.Controls
// create a bunch of leaf node instances - instead i use the same instance m_leafNode to avoid // create a bunch of leaf node instances - instead i use the same instance m_leafNode to avoid
// an explosion of node objects. However, I'm still creating the m_childrenNodes // an explosion of node objects. However, I'm still creating the m_childrenNodes
// collection unfortunately. // collection unfortunately.
public SelectionNode? GetAt(int index, bool realizeChild) public SelectionNode? GetAt(int index, bool realizeChild, IndexPath finalIndexPath)
{ {
SelectionNode? child = null; SelectionNode? child = null;
@ -171,7 +192,7 @@ namespace Avalonia.Controls
if (childData != null) if (childData != null)
{ {
var childDataIndexPath = IndexPath.CloneWithChildIndex(index); var childDataIndexPath = IndexPath.CloneWithChildIndex(index);
resolver = _manager.ResolvePath(childData, childDataIndexPath); resolver = _manager.ResolvePath(childData, childDataIndexPath, finalIndexPath);
} }
if (resolver != null) if (resolver != null)
@ -843,7 +864,7 @@ namespace Avalonia.Controls
int notSelectedCount = 0; int notSelectedCount = 0;
for (int i = 0; i < ChildrenNodeCount; i++) for (int i = 0; i < ChildrenNodeCount; i++)
{ {
var child = GetAt(i, realizeChild: false); var child = GetAt(i, false, default);
if (child != null) if (child != null)
{ {

160
src/Avalonia.Controls/Slider.cs

@ -1,12 +1,40 @@
using System; using System;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary>
/// Enum which describes how to position the ticks in a <see cref="Slider"/>.
/// </summary>
public enum TickPlacement
{
/// <summary>
/// No tick marks will appear.
/// </summary>
None,
/// <summary>
/// Tick marks will appear above the track for a horizontal <see cref="Slider"/>, or to the left of the track for a vertical <see cref="Slider"/>.
/// </summary>
TopLeft,
/// <summary>
/// Tick marks will appear below the track for a horizontal <see cref="Slider"/>, or to the right of the track for a vertical <see cref="Slider"/>.
/// </summary>
BottomRight,
/// <summary>
/// Tick marks appear on both sides of either a horizontal or vertical <see cref="Slider"/>.
/// </summary>
Outside
}
/// <summary> /// <summary>
/// A control that lets the user select from a range of values by moving a Thumb control along a Track. /// A control that lets the user select from a range of values by moving a Thumb control along a Track.
/// </summary> /// </summary>
@ -30,19 +58,31 @@ namespace Avalonia.Controls
public static readonly StyledProperty<double> TickFrequencyProperty = public static readonly StyledProperty<double> TickFrequencyProperty =
AvaloniaProperty.Register<Slider, double>(nameof(TickFrequency), 0.0); AvaloniaProperty.Register<Slider, double>(nameof(TickFrequency), 0.0);
/// <summary>
/// Defines the <see cref="TickPlacement"/> property.
/// </summary>
public static readonly StyledProperty<TickPlacement> TickPlacementProperty =
AvaloniaProperty.Register<TickBar, TickPlacement>(nameof(TickPlacement), 0d);
// Slider required parts // Slider required parts
private bool _isDragging = false;
private Track _track; private Track _track;
private Button _decreaseButton; private Button _decreaseButton;
private Button _increaseButton; private Button _increaseButton;
private IDisposable _decreaseButtonPressDispose;
private IDisposable _decreaseButtonReleaseDispose;
private IDisposable _increaseButtonSubscription;
private IDisposable _increaseButtonReleaseDispose;
private IDisposable _pointerMovedDispose;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Slider"/> class. /// Initializes static members of the <see cref="Slider"/> class.
/// </summary> /// </summary>
static Slider() static Slider()
{ {
PressedMixin.Attach<Slider>();
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
Thumb.DragDeltaEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble);
} }
@ -81,57 +121,88 @@ namespace Avalonia.Controls
set { SetValue(TickFrequencyProperty, value); } set { SetValue(TickFrequencyProperty, value); }
} }
/// <summary>
/// Gets or sets a value that indicates where to draw
/// tick marks in relation to the track.
/// </summary>
public TickPlacement TickPlacement
{
get { return GetValue(TickPlacementProperty); }
set { SetValue(TickPlacementProperty, value); }
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
if (_decreaseButton != null) base.OnApplyTemplate(e);
{
_decreaseButton.Click -= DecreaseClick; _decreaseButtonPressDispose?.Dispose();
} _decreaseButtonReleaseDispose?.Dispose();
_increaseButtonSubscription?.Dispose();
if (_increaseButton != null) _increaseButtonReleaseDispose?.Dispose();
{ _pointerMovedDispose?.Dispose();
_increaseButton.Click -= IncreaseClick;
}
_decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton"); _decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
_track = e.NameScope.Find<Track>("PART_Track"); _track = e.NameScope.Find<Track>("PART_Track");
_increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton"); _increaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
if (_track != null)
{
_track.IsThumbDragHandled = true;
}
if (_decreaseButton != null) if (_decreaseButton != null)
{ {
_decreaseButton.Click += DecreaseClick; _decreaseButtonPressDispose = _decreaseButton.AddDisposableHandler(PointerPressedEvent, TrackPressed, RoutingStrategies.Tunnel);
_decreaseButtonReleaseDispose = _decreaseButton.AddDisposableHandler(PointerReleasedEvent, TrackReleased, RoutingStrategies.Tunnel);
} }
if (_increaseButton != null) if (_increaseButton != null)
{ {
_increaseButton.Click += IncreaseClick; _increaseButtonSubscription = _increaseButton.AddDisposableHandler(PointerPressedEvent, TrackPressed, RoutingStrategies.Tunnel);
_increaseButtonReleaseDispose = _increaseButton.AddDisposableHandler(PointerReleasedEvent, TrackReleased, RoutingStrategies.Tunnel);
} }
_pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel);
} }
private void DecreaseClick(object sender, RoutedEventArgs e) private void TrackMoved(object sender, PointerEventArgs e)
{ {
ChangeValueBy(-LargeChange); if (_isDragging)
{
MoveToPoint(e.GetCurrentPoint(_track));
}
} }
private void IncreaseClick(object sender, RoutedEventArgs e) private void TrackReleased(object sender, PointerReleasedEventArgs e)
{ {
ChangeValueBy(LargeChange); _isDragging = false;
} }
private void ChangeValueBy(double by) private void TrackPressed(object sender, PointerPressedEventArgs e)
{ {
if (IsSnapToTickEnabled) if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{ {
by = by < 0 ? Math.Min(-TickFrequency, by) : Math.Max(TickFrequency, by); MoveToPoint(e.GetCurrentPoint(_track));
_isDragging = true;
} }
}
var value = Value; private void MoveToPoint(PointerPoint x)
var next = SnapToTick(Math.Max(Math.Min(value + by, Maximum), Minimum)); {
if (next != value) var orient = Orientation == Orientation.Horizontal;
{
Value = next; var pointDen = orient ? _track.Bounds.Width : _track.Bounds.Height;
} pointDen += double.Epsilon; // Just add epsilon to avoid divide by zero exceptions.
var pointNum = orient ? x.Position.X : x.Position.Y;
var logicalPos = MathUtilities.Clamp(pointNum / pointDen, 0.0d, 1.0d);
var invert = orient ? 0 : 1;
var calcVal = Math.Abs(invert - logicalPos);
var range = Maximum - Minimum;
var finalValue = calcVal * range;
Value = IsSnapToTickEnabled ? SnapToTick(finalValue) : finalValue;
} }
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change) protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
@ -150,19 +221,7 @@ namespace Avalonia.Controls
/// <param name="e"></param> /// <param name="e"></param>
protected virtual void OnThumbDragStarted(VectorEventArgs e) protected virtual void OnThumbDragStarted(VectorEventArgs e)
{ {
} _isDragging = true;
/// <summary>
/// Called when user dragging the <see cref="Thumb"/>.
/// </summary>
/// <param name="e"></param>
protected virtual void OnThumbDragDelta(VectorEventArgs e)
{
Thumb thumb = e.Source as Thumb;
if (thumb != null && _track?.Thumb == thumb)
{
MoveToNextTick(_track.Value);
}
} }
/// <summary> /// <summary>
@ -171,15 +230,7 @@ namespace Avalonia.Controls
/// <param name="e"></param> /// <param name="e"></param>
protected virtual void OnThumbDragCompleted(VectorEventArgs e) protected virtual void OnThumbDragCompleted(VectorEventArgs e)
{ {
} _isDragging = false;
/// <summary>
/// Searches for the closest tick and sets Value to that tick.
/// </summary>
/// <param name="value">Value that want to snap to closest Tick.</param>
private void MoveToNextTick(double value)
{
Value = SnapToTick(Math.Max(Minimum, Math.Min(Maximum, value)));
} }
/// <summary> /// <summary>
@ -188,14 +239,17 @@ namespace Avalonia.Controls
/// <param name="value">Value that want to snap to closest Tick.</param> /// <param name="value">Value that want to snap to closest Tick.</param>
private double SnapToTick(double value) private double SnapToTick(double value)
{ {
if (IsSnapToTickEnabled && TickFrequency > 0.0) var previous = Minimum;
var next = Maximum;
if (TickFrequency > 0.0)
{ {
double previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency); previous = Minimum + (Math.Round((value - Minimum) / TickFrequency) * TickFrequency);
double next = Math.Min(Maximum, previous + TickFrequency); next = Math.Min(Maximum, previous + TickFrequency);
value = value > (previous + next) * 0.5 ? next : previous;
} }
return value; // Choose the closest value between previous and next. If tie, snap to 'next'.
return MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
} }
private void UpdatePseudoClasses(Orientation o) private void UpdatePseudoClasses(Orientation o)

416
src/Avalonia.Controls/TickBar.cs

@ -0,0 +1,416 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
/// <summary>
/// Enum which describes how to position the TickBar.
/// </summary>
public enum TickBarPlacement
{
/// <summary>
/// Position this tick at the left of target element.
/// </summary>
Left,
/// <summary>
/// Position this tick at the top of target element.
/// </summary>
Top,
/// <summary>
/// Position this tick at the right of target element.
/// </summary>
Right,
/// <summary>
/// Position this tick at the bottom of target element.
/// </summary>
Bottom,
}
/// <summary>
/// An element that is used for drawing <see cref="Slider"/>'s Ticks.
/// </summary>
public class TickBar : Control
{
static TickBar()
{
AffectsRender<TickBar>(ReservedSpaceProperty,
MaximumProperty,
MinimumProperty,
OrientationProperty,
TickFrequencyProperty);
}
public TickBar() : base()
{
}
/// <summary>
/// Defines the <see cref="Fill"/> property.
/// </summary>
public static readonly StyledProperty<IBrush> FillProperty =
AvaloniaProperty.Register<TickBar, IBrush>(nameof(Fill));
/// <summary>
/// Brush used to fill the TickBar's Ticks.
/// </summary>
public IBrush Fill
{
get { return GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
/// <summary>
/// Defines the <see cref="Minimum"/> property.
/// </summary>
public static readonly StyledProperty<double> MinimumProperty =
AvaloniaProperty.Register<TickBar, double>(nameof(Minimum), 0d);
/// <summary>
/// Logical position where the Minimum Tick will be drawn
/// </summary>
public double Minimum
{
get { return GetValue(MinimumProperty); }
set
{
SetValue(MinimumProperty, value);
}
}
/// <summary>
/// Defines the <see cref="Maximum"/> property.
/// </summary>
public static readonly StyledProperty<double> MaximumProperty =
AvaloniaProperty.Register<TickBar, double>(nameof(Maximum), 0d);
/// <summary>
/// Logical position where the Maximum Tick will be drawn
/// </summary>
public double Maximum
{
get { return GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
/// <summary>
/// Defines the <see cref="TickFrequency"/> property.
/// </summary>
public static readonly StyledProperty<double> TickFrequencyProperty =
AvaloniaProperty.Register<TickBar, double>(nameof(TickFrequency), 0d);
/// <summary>
/// TickFrequency property defines how the tick will be drawn.
/// </summary>
public double TickFrequency
{
get { return GetValue(TickFrequencyProperty); }
set { SetValue(TickFrequencyProperty, value); }
}
/// <summary>
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<TickBar, Orientation>(nameof(Orientation));
/// <summary>
/// TickBar parent's orientation.
/// </summary>
public Orientation Orientation
{
get { return GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// Defines the <see cref="Ticks"/> property.
/// </summary>
public static readonly StyledProperty<List<double>> TicksProperty =
AvaloniaProperty.Register<TickBar, List<double>>(nameof(Ticks));
/// <summary>
/// The Ticks property contains collection of value of type Double which
/// are the logical positions use to draw the ticks.
/// The property value is a <see cref="DoubleCollection" />.
/// </summary>
public List<double> Ticks
{
get { return GetValue(TicksProperty); }
set { SetValue(TicksProperty, value); }
}
/// <summary>
/// Defines the <see cref="IsDirectionReversed"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsDirectionReversedProperty =
AvaloniaProperty.Register<TickBar, bool>(nameof(IsDirectionReversed), false);
/// <summary>
/// The IsDirectionReversed property defines the direction of value incrementation.
/// By default, if Tick's orientation is Horizontal, ticks will be drawn from left to right.
/// (And, bottom to top for Vertical orientation).
/// If IsDirectionReversed is 'true' the direction of the drawing will be in opposite direction.
/// </summary>
public bool IsDirectionReversed
{
get { return GetValue(IsDirectionReversedProperty); }
set { SetValue(IsDirectionReversedProperty, value); }
}
/// <summary>
/// Defines the <see cref="Placement"/> property.
/// </summary>
public static readonly StyledProperty<TickBarPlacement> PlacementProperty =
AvaloniaProperty.Register<TickBar, TickBarPlacement>(nameof(Placement), 0d);
/// <summary>
/// Placement property specified how the Tick will be placed.
/// This property affects the way ticks are drawn.
/// This property has type of <see cref="TickBarPlacement" />.
/// </summary>
public TickBarPlacement Placement
{
get { return GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
}
/// <summary>
/// Defines the <see cref="ReservedSpace"/> property.
/// </summary>
public static readonly StyledProperty<Rect> ReservedSpaceProperty =
AvaloniaProperty.Register<TickBar, Rect>(nameof(ReservedSpace));
/// <summary>
/// TickBar will use ReservedSpaceProperty for left and right spacing (for horizontal orientation) or
/// tob and bottom spacing (for vertical orienation).
/// The space on both sides of TickBar is half of specified ReservedSpace.
/// This property has type of <see cref="Rect" />.
/// </summary>
public Rect ReservedSpace
{
get { return GetValue(ReservedSpaceProperty); }
set { SetValue(ReservedSpaceProperty, value); }
}
/// <summary>
/// Draw ticks.
/// Ticks can be draw in 8 diffrent ways depends on Placment property and IsDirectionReversed property.
///
/// This function also draw selection-tick(s) if IsSelectionRangeEnabled is 'true' and
/// SelectionStart and SelectionEnd are valid.
///
/// The primary ticks (for Mininum and Maximum value) height will be 100% of TickBar's render size (use Width or Height
/// depends on Placement property).
///
/// The secondary ticks (all other ticks, including selection-tics) height will be 75% of TickBar's render size.
///
/// Brush that use to fill ticks is specified by Shape.Fill property.
///
/// Pen that use to draw ticks is specified by Shape.Pen property.
/// </summary>
public override void Render(DrawingContext dc)
{
var size = new Size(Bounds.Width, Bounds.Height);
var range = Maximum - Minimum;
var tickLen = 0.0d; // Height for Primary Tick (for Mininum and Maximum value)
var tickLen2 = 0.0d; // Height for Secondary Tick
var logicalToPhysical = 1.0;
var progression = 1.0d;
var startPoint = new Point();
var endPoint = new Point();
var rSpace = Orientation == Orientation.Horizontal ? ReservedSpace.Width : ReservedSpace.Height;
// Take Thumb size in to account
double halfReservedSpace = rSpace * 0.5;
switch (Placement)
{
case TickBarPlacement.Top:
if (MathUtilities.GreaterThanOrClose(rSpace, size.Width))
{
return;
}
size = new Size(size.Width - rSpace, size.Height);
tickLen = -size.Height;
startPoint = new Point(halfReservedSpace, size.Height);
endPoint = new Point(halfReservedSpace + size.Width, size.Height);
logicalToPhysical = size.Width / range;
progression = 1;
break;
case TickBarPlacement.Bottom:
if (MathUtilities.GreaterThanOrClose(rSpace, size.Width))
{
return;
}
size = new Size(size.Width - rSpace, size.Height);
tickLen = size.Height;
startPoint = new Point(halfReservedSpace, 0d);
endPoint = new Point(halfReservedSpace + size.Width, 0d);
logicalToPhysical = size.Width / range;
progression = 1;
break;
case TickBarPlacement.Left:
if (MathUtilities.GreaterThanOrClose(rSpace, size.Height))
{
return;
}
size = new Size(size.Width, size.Height - rSpace);
tickLen = -size.Width;
startPoint = new Point(size.Width, size.Height + halfReservedSpace);
endPoint = new Point(size.Width, halfReservedSpace);
logicalToPhysical = size.Height / range * -1;
progression = -1;
break;
case TickBarPlacement.Right:
if (MathUtilities.GreaterThanOrClose(rSpace, size.Height))
{
return;
}
size = new Size(size.Width, size.Height - rSpace);
tickLen = size.Width;
startPoint = new Point(0d, size.Height + halfReservedSpace);
endPoint = new Point(0d, halfReservedSpace);
logicalToPhysical = size.Height / range * -1;
progression = -1;
break;
};
tickLen2 = tickLen * 0.75;
// Invert direciton of the ticks
if (IsDirectionReversed)
{
progression *= -progression;
logicalToPhysical *= -1;
// swap startPoint & endPoint
var pt = startPoint;
startPoint = endPoint;
endPoint = pt;
}
var pen = new Pen(Fill, 1.0d);
// Is it Vertical?
if (Placement == TickBarPlacement.Left || Placement == TickBarPlacement.Right)
{
// Reduce tick interval if it is more than would be visible on the screen
double interval = TickFrequency;
if (interval > 0.0)
{
double minInterval = (Maximum - Minimum) / size.Height;
if (interval < minInterval)
{
interval = minInterval;
}
}
// Draw Min & Max tick
dc.DrawLine(pen, startPoint, new Point(startPoint.X + tickLen, startPoint.Y));
dc.DrawLine(pen, new Point(startPoint.X, endPoint.Y),
new Point(startPoint.X + tickLen, endPoint.Y));
// This property is rarely set so let's try to avoid the GetValue
// caching of the mutable default value
var ticks = Ticks ?? null;
// Draw ticks using specified Ticks collection
if (ticks?.Count > 0)
{
for (int i = 0; i < ticks.Count; i++)
{
if (MathUtilities.LessThanOrClose(ticks[i], Minimum) || MathUtilities.GreaterThanOrClose(ticks[i], Maximum))
{
continue;
}
double adjustedTick = ticks[i] - Minimum;
double y = adjustedTick * logicalToPhysical + startPoint.Y;
dc.DrawLine(pen,
new Point(startPoint.X, y),
new Point(startPoint.X + tickLen2, y));
}
}
// Draw ticks using specified TickFrequency
else if (interval > 0.0)
{
for (double i = interval; i < range; i += interval)
{
double y = i * logicalToPhysical + startPoint.Y;
dc.DrawLine(pen,
new Point(startPoint.X, y),
new Point(startPoint.X + tickLen2, y));
}
}
}
else // Placement == Top || Placement == Bottom
{
// Reduce tick interval if it is more than would be visible on the screen
double interval = TickFrequency;
if (interval > 0.0)
{
double minInterval = (Maximum - Minimum) / size.Width;
if (interval < minInterval)
{
interval = minInterval;
}
}
// Draw Min & Max tick
dc.DrawLine(pen, startPoint, new Point(startPoint.X, startPoint.Y + tickLen));
dc.DrawLine(pen, new Point(endPoint.X, startPoint.Y),
new Point(endPoint.X, startPoint.Y + tickLen));
// This property is rarely set so let's try to avoid the GetValue
// caching of the mutable default value
var ticks = Ticks ?? null;
// Draw ticks using specified Ticks collection
if (ticks?.Count > 0)
{
for (int i = 0; i < ticks.Count; i++)
{
if (MathUtilities.LessThanOrClose(ticks[i], Minimum) || MathUtilities.GreaterThanOrClose(ticks[i], Maximum))
{
continue;
}
double adjustedTick = ticks[i] - Minimum;
double x = adjustedTick * logicalToPhysical + startPoint.X;
dc.DrawLine(pen,
new Point(x, startPoint.Y),
new Point(x, startPoint.Y + tickLen2));
}
}
// Draw ticks using specified TickFrequency
else if (interval > 0.0)
{
for (double i = interval; i < range; i += interval)
{
double x = i * logicalToPhysical + startPoint.X;
dc.DrawLine(pen,
new Point(x, startPoint.Y),
new Point(x, startPoint.Y + tickLen2));
}
}
}
}
}
}

26
src/Avalonia.Controls/ToolTip.cs

@ -204,13 +204,15 @@ namespace Avalonia.Controls
private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e) private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e)
{ {
var control = (Control)e.Sender; var control = (Control)e.Sender;
var newValue = (bool)e.NewValue;
ToolTip toolTip;
if ((bool)e.NewValue) if (newValue)
{ {
var tip = GetTip(control); var tip = GetTip(control);
if (tip == null) return; if (tip == null) return;
var toolTip = control.GetValue(ToolTipProperty); toolTip = control.GetValue(ToolTipProperty);
if (toolTip == null || (tip != toolTip && tip != toolTip.Content)) if (toolTip == null || (tip != toolTip && tip != toolTip.Content))
{ {
toolTip?.Close(); toolTip?.Close();
@ -223,9 +225,11 @@ namespace Avalonia.Controls
} }
else else
{ {
var toolTip = control.GetValue(ToolTipProperty); toolTip = control.GetValue(ToolTipProperty);
toolTip?.Close(); toolTip?.Close();
} }
toolTip?.UpdatePseudoClasses(newValue);
} }
private void Open(Control control) private void Open(Control control)
@ -238,6 +242,9 @@ namespace Avalonia.Controls
_popup.ConfigurePosition(control, GetPlacement(control), _popup.ConfigurePosition(control, GetPlacement(control),
new Point(GetHorizontalOffset(control), GetVerticalOffset(control))); new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
WindowManagerAddShadowHintChanged(_popup, false);
_popup.Show(); _popup.Show();
} }
@ -250,5 +257,18 @@ namespace Avalonia.Controls
_popup = null; _popup = null;
} }
} }
private void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint)
{
if (host is PopupRoot pr)
{
pr.PlatformImpl.SetWindowManagerAddShadowHint(hint);
}
}
private void UpdatePseudoClasses(bool newValue)
{
PseudoClasses.Set(":open", newValue);
}
} }
} }

18
src/Avalonia.Controls/TreeView.cs

@ -119,9 +119,10 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the selected item. /// Gets or sets the selected item.
/// </summary> /// </summary>
/// <summary> /// <remarks>
/// Gets or sets the selected item. /// Note that setting this property only currently works if the item is expanded to be visible.
/// </summary> /// To select non-expanded nodes use `Selection.SelectedIndex`.
/// </remarks>
public object SelectedItem public object SelectedItem
{ {
get => Selection.SelectedItem; get => Selection.SelectedItem;
@ -346,7 +347,7 @@ namespace Avalonia.Controls
if (container != null) if (container != null)
{ {
container.BringIntoView(); DispatcherTimer.RunOnce(container.BringIntoView, TimeSpan.Zero);
} }
} }
} }
@ -395,10 +396,17 @@ namespace Avalonia.Controls
private void OnSelectionModelChildrenRequested(object sender, SelectionModelChildrenRequestedEventArgs e) private void OnSelectionModelChildrenRequested(object sender, SelectionModelChildrenRequestedEventArgs e)
{ {
var container = ItemContainerGenerator.Index.ContainerFromItem(e.Source) as ItemsControl; var container = ItemContainerGenerator.Index.ContainerFromItem(e.Source) as TreeViewItem;
if (container is object) if (container is object)
{ {
if (e.SourceIndex.IsAncestorOf(e.FinalIndex))
{
container.IsExpanded = true;
container.ApplyTemplate();
container.Presenter?.ApplyTemplate();
}
e.Children = Observable.CombineLatest( e.Children = Observable.CombineLatest(
container.GetObservable(TreeViewItem.IsExpandedProperty), container.GetObservable(TreeViewItem.IsExpandedProperty),
container.GetObservable(ItemsProperty), container.GetObservable(ItemsProperty),

8
src/Avalonia.Controls/Utils/SelectionTreeHelper.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls.Utils
if (depth < path.GetSize() - 1) if (depth < path.GetSize() - 1)
{ {
node = node.GetAt(childIndex, realizeChildren)!; node = node.GetAt(childIndex, realizeChildren, path)!;
} }
} }
} }
@ -50,7 +50,7 @@ namespace Avalonia.Controls.Utils
int count = realizeChildren ? nextNode.Node.DataCount : nextNode.Node.ChildrenNodeCount; int count = realizeChildren ? nextNode.Node.DataCount : nextNode.Node.ChildrenNodeCount;
for (int i = count - 1; i >= 0; i--) for (int i = count - 1; i >= 0; i--)
{ {
var child = nextNode.Node.GetAt(i, realizeChildren); var child = nextNode.Node.GetAt(i, realizeChildren, nextNode.Path);
var childPath = nextNode.Path.CloneWithChildIndex(i); var childPath = nextNode.Path.CloneWithChildIndex(i);
if (child != null) if (child != null)
{ {
@ -90,7 +90,7 @@ namespace Avalonia.Controls.Utils
for (int i = endIndex; i >= startIndex; i--) for (int i = endIndex; i >= startIndex; i--)
{ {
var child = node.GetAt(i, realizeChild: true); var child = node.GetAt(i, true, end);
if (child != null) if (child != null)
{ {
var childPath = currentPath.CloneWithChildIndex(i); var childPath = currentPath.CloneWithChildIndex(i);
@ -112,7 +112,7 @@ namespace Avalonia.Controls.Utils
int endIndex = depth < end.GetSize() && isEndPath ? end.GetAt(depth) : info.Node.DataCount - 1; int endIndex = depth < end.GetSize() && isEndPath ? end.GetAt(depth) : info.Node.DataCount - 1;
for (int i = endIndex; i >= startIndex; i--) for (int i = endIndex; i >= startIndex; i--)
{ {
var child = info.Node.GetAt(i, realizeChild: true); var child = info.Node.GetAt(i, true, end);
if (child != null) if (child != null)
{ {
var childPath = info.Path.CloneWithChildIndex(i); var childPath = info.Path.CloneWithChildIndex(i);

4
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -160,6 +160,10 @@ namespace Avalonia.DesignerSupport.Remote
public Task SetTextAsync(string text) => Task.CompletedTask; public Task SetTextAsync(string text) => Task.CompletedTask;
public Task ClearAsync() => Task.CompletedTask; public Task ClearAsync() => Task.CompletedTask;
public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
public Task<string[]> GetFormatsAsync() => Task.FromResult(new string[0]);
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
} }
class CursorFactoryStub : IStandardCursorFactory class CursorFactoryStub : IStandardCursorFactory

17
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -2,6 +2,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Diagnostics.Models; using Avalonia.Diagnostics.Models;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Threading;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -49,7 +50,21 @@ namespace Avalonia.Diagnostics.ViewModels
value is TreePageViewModel newTree && value is TreePageViewModel newTree &&
oldTree?.SelectedNode?.Visual is IControl control) oldTree?.SelectedNode?.Visual is IControl control)
{ {
newTree.SelectControl(control); // HACK: We want to select the currently selected control in the new tree, but
// to select nested nodes in TreeView, currently the TreeView has to be able to
// expand the parent nodes. Because at this point the TreeView isn't visible,
// this will fail unless we schedule the selection to run after layout.
DispatcherTimer.RunOnce(
() =>
{
try
{
newTree.SelectControl(control);
}
catch { }
},
TimeSpan.FromMilliseconds(0),
DispatcherPriority.ApplicationIdle);
} }
RaiseAndSetIfChanged(ref _content, value); RaiseAndSetIfChanged(ref _content, value);

38
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
@ -82,5 +82,41 @@ namespace Avalonia.Diagnostics.ViewModels
get; get;
private set; private set;
} }
public IndexPath Index
{
get
{
var indices = new List<int>();
var child = this;
var parent = Parent;
while (parent is object)
{
indices.Add(IndexOf(parent.Children, child));
child = child.Parent;
parent = parent.Parent;
}
indices.Add(0);
indices.Reverse();
return new IndexPath(indices);
}
}
private static int IndexOf(IReadOnlyList<TreeNode> collection, TreeNode item)
{
var count = collection.Count;
for (var i = 0; i < count; ++i)
{
if (collection[i] == item)
{
return i;
}
}
throw new AvaloniaInternalException("TreeNode was not present in parent Children collection.");
}
} }
} }

24
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -6,28 +6,40 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
internal class TreePageViewModel : ViewModelBase, IDisposable internal class TreePageViewModel : ViewModelBase, IDisposable
{ {
private TreeNode _selected; private TreeNode _selectedNode;
private ControlDetailsViewModel _details; private ControlDetailsViewModel _details;
private string _propertyFilter; private string _propertyFilter;
public TreePageViewModel(TreeNode[] nodes) public TreePageViewModel(TreeNode[] nodes)
{ {
Nodes = nodes; Nodes = nodes;
} Selection = new SelectionModel
{
SingleSelect = true,
Source = Nodes
};
Selection.SelectionChanged += (s, e) =>
{
SelectedNode = (TreeNode)Selection.SelectedItem;
};
}
public TreeNode[] Nodes { get; protected set; } public TreeNode[] Nodes { get; protected set; }
public SelectionModel Selection { get; }
public TreeNode SelectedNode public TreeNode SelectedNode
{ {
get => _selected; get => _selectedNode;
set private set
{ {
if (Details != null) if (Details != null)
{ {
_propertyFilter = Details.PropertyFilter; _propertyFilter = Details.PropertyFilter;
} }
if (RaiseAndSetIfChanged(ref _selected, value)) if (RaiseAndSetIfChanged(ref _selectedNode, value))
{ {
Details = value != null ? Details = value != null ?
new ControlDetailsViewModel(value.Visual, _propertyFilter) : new ControlDetailsViewModel(value.Visual, _propertyFilter) :
@ -83,8 +95,8 @@ namespace Avalonia.Diagnostics.ViewModels
if (node != null) if (node != null)
{ {
SelectedNode = node;
ExpandNode(node.Parent); ExpandNode(node.Parent);
Selection.SelectedIndex = node.Index;
} }
} }

2
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

@ -6,7 +6,7 @@
<TreeView Name="tree" <TreeView Name="tree"
BorderThickness="0" BorderThickness="0"
Items="{Binding Nodes}" Items="{Binding Nodes}"
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"> Selection="{Binding Selection}">
<TreeView.DataTemplates> <TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TreeNode" <TreeDataTemplate DataType="vm:TreeNode"
ItemsSource="{Binding Children}"> ItemsSource="{Binding Children}">

6
src/Avalonia.Input/Platform/IClipboard.cs

@ -9,5 +9,11 @@ namespace Avalonia.Input.Platform
Task SetTextAsync(string text); Task SetTextAsync(string text);
Task ClearAsync(); Task ClearAsync();
Task SetDataObjectAsync(IDataObject data);
Task<string[]> GetFormatsAsync();
Task<object> GetDataAsync(string format);
} }
} }

15
src/Avalonia.Native/AvnString.cs

@ -5,6 +5,7 @@ namespace Avalonia.Native.Interop
unsafe partial class IAvnString unsafe partial class IAvnString
{ {
private string _managed; private string _managed;
private byte[] _bytes;
public string String public string String
{ {
@ -22,6 +23,20 @@ namespace Avalonia.Native.Interop
} }
} }
public byte[] Bytes
{
get
{
if (_bytes == null)
{
_bytes = new byte[Length()];
Marshal.Copy(Pointer(), _bytes, 0, _bytes.Length);
}
return _bytes;
}
}
public override string ToString() => String; public override string ToString() => String;
} }

32
src/Avalonia.Native/ClipboardImpl.cs

@ -82,6 +82,38 @@ namespace Avalonia.Native
using (var strings = _native.GetStrings(NSFilenamesPboardType)) using (var strings = _native.GetStrings(NSFilenamesPboardType))
return strings.ToStringArray(); return strings.ToStringArray();
} }
public unsafe Task SetDataObjectAsync(IDataObject data)
{
_native.Clear();
foreach (var fmt in data.GetDataFormats())
{
var o = data.Get(fmt);
if(o is string s)
using (var b = new Utf8Buffer(s))
_native.SetText(fmt, b.DangerousGetHandle());
else if(o is byte[] bytes)
fixed (byte* pbytes = bytes)
_native.SetBytes(fmt, new IntPtr(pbytes), bytes.Length);
}
return Task.CompletedTask;
}
public Task<string[]> GetFormatsAsync()
{
using (var n = _native.ObtainFormats())
return Task.FromResult(n.ToStringArray());
}
public async Task<object> GetDataAsync(string format)
{
if (format == DataFormats.Text)
return await GetTextAsync();
if (format == DataFormats.FileNames)
return GetFileNames();
using (var n = _native.GetBytes(format))
return n.Bytes;
}
} }
class ClipboardDataObject : IDataObject, IDisposable class ClipboardDataObject : IDataObject, IDisposable

41
src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml

@ -1,4 +1,6 @@
<Style xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard"> <Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources> <Style.Resources>
<Color x:Key="SystemAccentColor">#FF0078D7</Color> <Color x:Key="SystemAccentColor">#FF0078D7</Color>
<Color x:Key="SystemAltHighColor">#FF000000</Color> <Color x:Key="SystemAltHighColor">#FF000000</Color>
@ -31,6 +33,10 @@
<Color x:Key="SystemRevealListLowColor">#FF1D1D1D</Color> <Color x:Key="SystemRevealListLowColor">#FF1D1D1D</Color>
<Color x:Key="SystemRevealListMediumColor">#FF333333</Color> <Color x:Key="SystemRevealListMediumColor">#FF333333</Color>
<!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />--> <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
<!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
<!--<RevealBorderBrush x:Key="SystemControlTransparentRevealBorderBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBorderBrush" Color="Transparent" />
<!-- Override system shape defaults --> <!-- Override system shape defaults -->
<CornerRadius x:Key="ControlCornerRadius">2,2,2,2</CornerRadius> <CornerRadius x:Key="ControlCornerRadius">2,2,2,2</CornerRadius>
@ -312,5 +318,38 @@
<StaticResource x:Key="RadioButtonCheckGlyphStrokePointerOver" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="RadioButtonCheckGlyphStrokePointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="RadioButtonCheckGlyphStrokePressed" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="RadioButtonCheckGlyphStrokePressed" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="RadioButtonCheckGlyphStrokeDisabled" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="RadioButtonCheckGlyphStrokeDisabled" ResourceKey="SystemControlTransparentBrush" />
<!-- BaseResources for Slider.xaml -->
<StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderThumbBackground" ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="SliderThumbBackgroundPointerOver" ResourceKey="SystemControlHighlightChromeAltLowBrush" />
<StaticResource x:Key="SliderThumbBackgroundPressed" ResourceKey="SystemControlHighlightChromeHighBrush" />
<StaticResource x:Key="SliderThumbBackgroundDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillPointerOver" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="SliderTrackFillPressed" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackValueFill" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SliderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" />
<!-- Resources for ToolTip.xaml -->
<x:Double x:Key="ToolTipContentThemeFontSize">12</x:Double>
<Thickness x:Key="ToolTipBorderThemeThickness">1</Thickness>
<StaticResource x:Key="ToolTipForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="ToolTipBackground" ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="ToolTipBorderBrush" ResourceKey="SystemControlForegroundChromeHighBrush" />
<SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" />
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
</Style.Resources> </Style.Resources>
</Style> </Style>

41
src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml

@ -1,4 +1,6 @@
<Style xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard"> <Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources> <Style.Resources>
<Color x:Key="SystemAccentColor">#FF0078D7</Color> <Color x:Key="SystemAccentColor">#FF0078D7</Color>
<Color x:Key="SystemAltHighColor">#FFFFFFFF</Color> <Color x:Key="SystemAltHighColor">#FFFFFFFF</Color>
@ -31,6 +33,10 @@
<Color x:Key="SystemRevealListLowColor">#FFE6E6E6</Color> <Color x:Key="SystemRevealListLowColor">#FFE6E6E6</Color>
<Color x:Key="SystemRevealListMediumColor">#FFCCCCCC</Color> <Color x:Key="SystemRevealListMediumColor">#FFCCCCCC</Color>
<!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />--> <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
<!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
<!--<RevealBorderBrush x:Key="SystemControlTransparentRevealBorderBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBorderBrush" Color="Transparent" />
<!-- Override system shape defaults --> <!-- Override system shape defaults -->
<CornerRadius x:Key="ControlCornerRadius">2,2,2,2</CornerRadius> <CornerRadius x:Key="ControlCornerRadius">2,2,2,2</CornerRadius>
@ -313,5 +319,38 @@
<StaticResource x:Key="RadioButtonCheckGlyphStrokePointerOver" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="RadioButtonCheckGlyphStrokePointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="RadioButtonCheckGlyphStrokePressed" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="RadioButtonCheckGlyphStrokePressed" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="RadioButtonCheckGlyphStrokeDisabled" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="RadioButtonCheckGlyphStrokeDisabled" ResourceKey="SystemControlTransparentBrush" />
<!-- BaseResources for Slider.xaml -->
<StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderThumbBackground" ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="SliderThumbBackgroundPointerOver" ResourceKey="SystemControlHighlightChromeAltLowBrush" />
<StaticResource x:Key="SliderThumbBackgroundPressed" ResourceKey="SystemControlHighlightChromeHighBrush" />
<StaticResource x:Key="SliderThumbBackgroundDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillPointerOver" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="SliderTrackFillPressed" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackValueFill" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SliderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" />
<!-- Resources for ToolTip.xaml -->
<x:Double x:Key="ToolTipContentThemeFontSize">12</x:Double>
<Thickness x:Key="ToolTipBorderThemeThickness">1</Thickness>
<StaticResource x:Key="ToolTipForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="ToolTipBackground" ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="ToolTipBorderBrush" ResourceKey="SystemControlForegroundChromeHighBrush" />
<SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" />
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
</Style.Resources> </Style.Resources>
</Style> </Style>

94
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -1,6 +1,6 @@
<Style xmlns="https://github.com/avaloniaui" <Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources> <Style.Resources>
<!-- Resources for Button.xaml --> <!-- Resources for Button.xaml -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlBackgroundAccentBrush" /> <StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlBackgroundAccentBrush" />
@ -290,6 +290,33 @@
<SolidColorBrush x:Key="CheckBoxPressedBorderThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="CheckBoxPressedBorderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="CheckBoxPressedForegroundThemeBrush" Color="#FF000000" /> <SolidColorBrush x:Key="CheckBoxPressedForegroundThemeBrush" Color="#FF000000" />
<!-- Resources for Calendar.xaml, CalendarButton.xaml, CalendarDayButton.xaml, CalendarItem.xaml -->
<StaticResource x:Key="CalendarViewFocusBorderBrush" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarViewSelectedHoverBorderBrush" ResourceKey="SystemControlHighlightListAccentMediumBrush" />
<StaticResource x:Key="CalendarViewSelectedPressedBorderBrush" ResourceKey="SystemControlHighlightListAccentHighBrush" />
<StaticResource x:Key="CalendarViewSelectedBorderBrush" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="CalendarViewHoverBorderBrush" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
<StaticResource x:Key="CalendarViewPressedBorderBrush" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="CalendarViewTodayForeground" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
<StaticResource x:Key="CalendarViewBlackoutForeground" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarViewSelectedForeground" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="CalendarViewPressedForeground" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="CalendarViewOutOfScopeForeground" ResourceKey="SystemControlHyperlinkBaseHighBrush" />
<StaticResource x:Key="CalendarViewCalendarItemForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarViewOutOfScopeBackground" ResourceKey="SystemControlDisabledChromeMediumLowBrush" />
<StaticResource x:Key="CalendarViewCalendarItemBackground" ResourceKey="SystemControlBackgroundAltHighBrush" />
<StaticResource x:Key="CalendarViewForeground" ResourceKey="SystemControlHyperlinkBaseMediumHighBrush" />
<StaticResource x:Key="CalendarViewBackground" ResourceKey="SystemControlBackgroundAltHighBrush" />
<StaticResource x:Key="CalendarViewBorderBrush" ResourceKey="SystemControlForegroundChromeMediumBrush" />
<StaticResource x:Key="CalendarViewWeekDayForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonForegroundPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonForegroundPressed" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarViewCalendarItemRevealBackground" ResourceKey="SystemControlTransparentRevealBackgroundBrush" />
<StaticResource x:Key="CalendarViewCalendarItemRevealBorderBrush" ResourceKey="SystemControlTransparentRevealBorderBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for RadioButton.xaml --> <!-- Resources for RadioButton.xaml -->
<x:Double x:Key="RadioButtonBorderThemeThickness">1</x:Double> <x:Double x:Key="RadioButtonBorderThemeThickness">1</x:Double>
<StaticResource x:Key="RadioButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="RadioButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
@ -343,5 +370,66 @@
<SolidColorBrush x:Key="RadioButtonPressedBorderThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="RadioButtonPressedBorderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="RadioButtonPressedForegroundThemeBrush" Color="#FF000000" /> <SolidColorBrush x:Key="RadioButtonPressedForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="RadioButtonContentPointerOverForegroundThemeBrush" Color="{DynamicResource SystemColorHighlightTextColor}" /> <SolidColorBrush x:Key="RadioButtonContentPointerOverForegroundThemeBrush" Color="{DynamicResource SystemColorHighlightTextColor}" />
<!-- Resources for Slider.xaml -->
<x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double>
<x:Double x:Key="SliderTrackThemeHeight">2</x:Double>
<Thickness x:Key="SliderBorderThemeThickness">0</Thickness>
<Thickness x:Key="SliderHeaderThemeMargin">0,0,0,4</Thickness>
<FontWeight x:Key="SliderHeaderThemeFontWeight">Normal</FontWeight>
<StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderThumbBackground" ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="SliderThumbBackgroundPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="SliderThumbBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<StaticResource x:Key="SliderThumbBackgroundDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillPointerOver" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="SliderTrackFillPressed" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackValueFill" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SliderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" />
<SolidColorBrush x:Key="SliderBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="SliderDisabledBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="SliderThumbBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="SliderThumbBorderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="SliderThumbDisabledBackgroundThemeBrush" Color="#FF7E7E7E" />
<SolidColorBrush x:Key="SliderThumbPointerOverBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="SliderThumbPointerOverBorderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="SliderThumbPressedBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="SliderThumbPressedBorderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="SliderTickMarkInlineBackgroundThemeBrush" Color="Black" />
<SolidColorBrush x:Key="SliderTickMarkInlineDisabledForegroundThemeBrush" Color="Black" />
<SolidColorBrush x:Key="SliderTickmarkOutsideBackgroundThemeBrush" Color="#80FFFFFF" />
<SolidColorBrush x:Key="SliderTickMarkOutsideDisabledForegroundThemeBrush" Color="#80FFFFFF" />
<SolidColorBrush x:Key="SliderTrackBackgroundThemeBrush" Color="#29FFFFFF" />
<SolidColorBrush x:Key="SliderTrackDecreaseBackgroundThemeBrush" Color="#FF5B2EC5" />
<SolidColorBrush x:Key="SliderTrackDecreaseDisabledBackgroundThemeBrush" Color="#1FFFFFFF" />
<SolidColorBrush x:Key="SliderTrackDecreasePointerOverBackgroundThemeBrush" Color="#FF724BCD" />
<SolidColorBrush x:Key="SliderTrackDecreasePressedBackgroundThemeBrush" Color="#FF8152EF" />
<SolidColorBrush x:Key="SliderTrackDisabledBackgroundThemeBrush" Color="#29FFFFFF" />
<SolidColorBrush x:Key="SliderTrackPointerOverBackgroundThemeBrush" Color="#46FFFFFF" />
<SolidColorBrush x:Key="SliderTrackPressedBackgroundThemeBrush" Color="#59FFFFFF" />
<SolidColorBrush x:Key="SliderHeaderForegroundThemeBrush" Color="#FFFFFFFF" />
<!-- Resources for ToolTip.xaml -->
<x:Double x:Key="ToolTipContentThemeFontSize">12</x:Double>
<Thickness x:Key="ToolTipBorderThemeThickness">1</Thickness>
<StaticResource x:Key="ToolTipForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="ToolTipBackground" ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="ToolTipBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
<SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" />
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness>
</Style.Resources> </Style.Resources>
</Style> </Style>

94
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -1,6 +1,6 @@
<Style xmlns="https://github.com/avaloniaui" <Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources> <Style.Resources>
<!-- Resources for Button.xaml --> <!-- Resources for Button.xaml -->
<StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlBackgroundAccentBrush" /> <StaticResource x:Key="AccentButtonBackground" ResourceKey="SystemControlBackgroundAccentBrush" />
@ -290,6 +290,33 @@
<SolidColorBrush x:Key="CheckBoxPressedBorderThemeBrush" Color="#FF000000" /> <SolidColorBrush x:Key="CheckBoxPressedBorderThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="CheckBoxPressedForegroundThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="CheckBoxPressedForegroundThemeBrush" Color="#FFFFFFFF" />
<!-- Resources for Calendar.xaml, CalendarButton.xaml, CalendarDayButton.xaml, CalendarItem.xaml -->
<StaticResource x:Key="CalendarViewFocusBorderBrush" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarViewSelectedHoverBorderBrush" ResourceKey="SystemControlHighlightListAccentMediumBrush" />
<StaticResource x:Key="CalendarViewSelectedPressedBorderBrush" ResourceKey="SystemControlHighlightListAccentHighBrush" />
<StaticResource x:Key="CalendarViewSelectedBorderBrush" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="CalendarViewHoverBorderBrush" ResourceKey="SystemControlHighlightBaseMediumLowBrush" />
<StaticResource x:Key="CalendarViewPressedBorderBrush" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="CalendarViewTodayForeground" ResourceKey="SystemControlHighlightAltChromeWhiteBrush" />
<StaticResource x:Key="CalendarViewBlackoutForeground" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarViewSelectedForeground" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="CalendarViewPressedForeground" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="CalendarViewOutOfScopeForeground" ResourceKey="SystemControlHyperlinkBaseHighBrush" />
<StaticResource x:Key="CalendarViewCalendarItemForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarViewOutOfScopeBackground" ResourceKey="SystemControlDisabledChromeMediumLowBrush" />
<StaticResource x:Key="CalendarViewCalendarItemBackground" ResourceKey="SystemControlBackgroundAltHighBrush" />
<StaticResource x:Key="CalendarViewForeground" ResourceKey="SystemControlHyperlinkBaseMediumHighBrush" />
<StaticResource x:Key="CalendarViewBackground" ResourceKey="SystemControlBackgroundAltHighBrush" />
<StaticResource x:Key="CalendarViewBorderBrush" ResourceKey="SystemControlForegroundChromeMediumBrush" />
<StaticResource x:Key="CalendarViewWeekDayForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonForegroundPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonForegroundPressed" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="CalendarViewCalendarItemRevealBackground" ResourceKey="SystemControlTransparentRevealBackgroundBrush" />
<StaticResource x:Key="CalendarViewCalendarItemRevealBorderBrush" ResourceKey="SystemControlTransparentRevealBorderBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" />
<!-- Resources for RadioButton.xaml --> <!-- Resources for RadioButton.xaml -->
<x:Double x:Key="RadioButtonBorderThemeThickness">1</x:Double> <x:Double x:Key="RadioButtonBorderThemeThickness">1</x:Double>
<StaticResource x:Key="RadioButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="RadioButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
@ -343,5 +370,66 @@
<SolidColorBrush x:Key="RadioButtonPressedBorderThemeBrush" Color="#FF000000" /> <SolidColorBrush x:Key="RadioButtonPressedBorderThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="RadioButtonPressedForegroundThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="RadioButtonPressedForegroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="RadioButtonContentPointerOverForegroundThemeBrush" Color="{DynamicResource SystemColorHighlightTextColor}" /> <SolidColorBrush x:Key="RadioButtonContentPointerOverForegroundThemeBrush" Color="{DynamicResource SystemColorHighlightTextColor}" />
<!-- Resources for Slider.xaml -->
<x:Double x:Key="SliderOutsideTickBarThemeHeight">4</x:Double>
<x:Double x:Key="SliderTrackThemeHeight">2</x:Double>
<Thickness x:Key="SliderBorderThemeThickness">0</Thickness>
<Thickness x:Key="SliderHeaderThemeMargin">0,0,0,4</Thickness>
<FontWeight x:Key="SliderHeaderThemeFontWeight">Normal</FontWeight>
<StaticResource x:Key="SliderContainerBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPointerOver" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundPressed" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderContainerBackgroundDisabled" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="SliderThumbBackground" ResourceKey="SystemControlForegroundAccentBrush" />
<StaticResource x:Key="SliderThumbBackgroundPointerOver" ResourceKey="SystemAccentColorLight1" />
<StaticResource x:Key="SliderThumbBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<StaticResource x:Key="SliderThumbBackgroundDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillPointerOver" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="SliderTrackFillPressed" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTrackFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderTrackValueFill" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPointerOver" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillPressed" ResourceKey="SystemControlHighlightAccentBrush" />
<StaticResource x:Key="SliderTrackValueFillDisabled" ResourceKey="SystemControlDisabledChromeDisabledHighBrush" />
<StaticResource x:Key="SliderHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="SliderHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFill" ResourceKey="SystemControlForegroundBaseMediumLowBrush" />
<StaticResource x:Key="SliderTickBarFillDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="SliderInlineTickBarFill" ResourceKey="SystemControlBackgroundAltHighBrush" />
<SolidColorBrush x:Key="SliderBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="SliderDisabledBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="SliderThumbBackgroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="SliderThumbBorderThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="SliderThumbDisabledBackgroundThemeBrush" Color="#FF929292" />
<SolidColorBrush x:Key="SliderThumbPointerOverBackgroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="SliderThumbPointerOverBorderThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="SliderThumbPressedBackgroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="SliderThumbPressedBorderThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="SliderTickMarkInlineBackgroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="SliderTickMarkInlineDisabledForegroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="SliderTickmarkOutsideBackgroundThemeBrush" Color="#80000000" />
<SolidColorBrush x:Key="SliderTickMarkOutsideDisabledForegroundThemeBrush" Color="#80000000" />
<SolidColorBrush x:Key="SliderTrackBackgroundThemeBrush" Color="#1A000000" />
<SolidColorBrush x:Key="SliderTrackDecreaseBackgroundThemeBrush" Color="#FF4617B4" />
<SolidColorBrush x:Key="SliderTrackDecreaseDisabledBackgroundThemeBrush" Color="#1C000000" />
<SolidColorBrush x:Key="SliderTrackDecreasePointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
<SolidColorBrush x:Key="SliderTrackDecreasePressedBackgroundThemeBrush" Color="#FF7241E4" />
<SolidColorBrush x:Key="SliderTrackDisabledBackgroundThemeBrush" Color="#1A000000" />
<SolidColorBrush x:Key="SliderTrackPointerOverBackgroundThemeBrush" Color="#26000000" />
<SolidColorBrush x:Key="SliderTrackPressedBackgroundThemeBrush" Color="#33000000" />
<SolidColorBrush x:Key="SliderHeaderForegroundThemeBrush" Color="#FF000000" />
<!-- Resources for ToolTip.xaml -->
<x:Double x:Key="ToolTipContentThemeFontSize">12</x:Double>
<Thickness x:Key="ToolTipBorderThemeThickness">1</Thickness>
<StaticResource x:Key="ToolTipForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="ToolTipBackground" ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="ToolTipBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
<SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="#FF808080" />
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness>
</Style.Resources> </Style.Resources>
</Style> </Style>

1
src/Avalonia.Themes.Fluent/Accents/FluentDark.xaml

@ -5,4 +5,5 @@
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.Base.xaml?assembly=Avalonia.Themes.Fluent" /> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.Base.xaml?assembly=Avalonia.Themes.Fluent" />
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentBaseDark.xaml?assembly=Avalonia.Themes.Fluent" /> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentBaseDark.xaml?assembly=Avalonia.Themes.Fluent" />
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentControlResourcesDark.xaml?assembly=Avalonia.Themes.Fluent" /> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentControlResourcesDark.xaml?assembly=Avalonia.Themes.Fluent" />
<StyleInclude Source="resm:Avalonia.Themes.Fluent.FluentTheme.xaml?assembly=Avalonia.Themes.Fluent" />
</Styles> </Styles>

1
src/Avalonia.Themes.Fluent/Accents/FluentLight.xaml

@ -5,4 +5,5 @@
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.Base.xaml?assembly=Avalonia.Themes.Fluent" /> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.Base.xaml?assembly=Avalonia.Themes.Fluent" />
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentBaseLight.xaml?assembly=Avalonia.Themes.Fluent" /> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentBaseLight.xaml?assembly=Avalonia.Themes.Fluent" />
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentControlResourcesLight.xaml?assembly=Avalonia.Themes.Fluent" /> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Accents.FluentControlResourcesLight.xaml?assembly=Avalonia.Themes.Fluent" />
<StyleInclude Source="resm:Avalonia.Themes.Fluent.FluentTheme.xaml?assembly=Avalonia.Themes.Fluent" />
</Styles> </Styles>

61
src/Avalonia.Themes.Fluent/Button.xaml

@ -4,13 +4,14 @@
<StackPanel Spacing="20"> <StackPanel Spacing="20">
<Button Content="Click Me!" /> <Button Content="Click Me!" />
<Button Classes="accent" Content="Click Me!" /> <Button Classes="accent" Content="Click Me!" />
</StackPanel> <RepeatButton Content="RepeatButton" Width="100" />
</StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Styles.Resources> <Styles.Resources>
<Thickness x:Key="ButtonPadding">8,5,8,6</Thickness> <Thickness x:Key="ButtonPadding">8,5,8,6</Thickness>
</Styles.Resources> </Styles.Resources>
<Style Selector="Button"> <Style Selector="Button, RepeatButton">
<Setter Property="Background" Value="{DynamicResource ButtonBackground}" /> <Setter Property="Background" Value="{DynamicResource ButtonBackground}" />
<!--<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />--> <!--<Setter Property="BackgroundSizing" Value="OuterBorderEdge" />-->
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" /> <Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
@ -24,65 +25,63 @@
<Setter Property="FontWeight" Value="Normal" /> <Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<!--<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" /> <!--<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-3" />--> <Setter Property="FocusVisualMargin" Value="-3" />-->
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<ContentPresenter <ContentPresenter x:Name="PART_ContentPresenter"
x:Name="PART_ContentPresenter" Background="{TemplateBinding Background}"
Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}"
Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplate="{TemplateBinding ContentTemplate}" CornerRadius="{DynamicResource ControlCornerRadius}"
CornerRadius="{DynamicResource ControlCornerRadius}" Padding="{TemplateBinding Padding}"
Padding="{TemplateBinding Padding}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
</ContentPresenter>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<!-- PointerOverState --> <!-- PointerOverState -->
<Style Selector="Button:pointerover /template/ ContentPresenter"> <Style Selector="Button:pointerover /template/ ContentPresenter, RepeatButton:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" /> <Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}"/> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style> </Style>
<Style Selector="Button:pressed /template/ ContentPresenter"> <Style Selector="Button:pressed /template/ ContentPresenter, RepeatButton:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" /> <Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}"/> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundPressed}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style> </Style>
<Style Selector="Button:disabled"> <Style Selector="Button:disabled, RepeatButton:disabled">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" /> <Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}"/> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style> </Style>
<Style Selector="Button.accent"> <Style Selector="Button.accent, RepeatButton.accent">
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" /> <Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" /> <Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
</Style> </Style>
<Style Selector="Button.accent:pointerover /template/ ContentPresenter"> <Style Selector="Button.accent:pointerover /template/ ContentPresenter, RepeatButton.accent:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" /> <Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}"/> <Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style> </Style>
<Style Selector="Button.accent:pressed /template/ ContentPresenter"> <Style Selector="Button.accent:pressed /template/ ContentPresenter, RepeatButton.accent:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" /> <Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}"/> <Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
</Style> </Style>
<Style Selector="Button.accent:disabled"> <Style Selector="Button.accent:disabled, RepeatButton.accent:disabled">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" /> <Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}"/> <Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" /> <Setter Property="TextBlock.Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style> </Style>
</Styles> </Styles>

15
src/Avalonia.Themes.Fluent/Calendar.xaml

@ -5,13 +5,14 @@
// All other rights reserved. // All other rights reserved.
--> -->
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Style Selector="Calendar"> <Style Selector="Calendar">
<!--<Setter Property="Focusable" Value="False" />--> <Setter Property="Foreground" Value="{DynamicResource CalendarViewForeground}" />
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" /> <Setter Property="Background" Value="{DynamicResource CalendarViewBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewBorderBrush}" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="HeaderBackground" Value="{DynamicResource ThemeAccentBrush2}" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<StackPanel Name="Root" <StackPanel Name="Root"
@ -28,4 +29,4 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
</Styles> </Styles>

138
src/Avalonia.Themes.Fluent/CalendarButton.xaml

@ -7,75 +7,115 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Style Selector="CalendarButton"> <Style Selector="CalendarButton">
<Setter Property="Foreground" Value="{DynamicResource ThemeBorderHighBrush}" /> <Setter Property="ClickMode" Value="Release"/>
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}" /> <Setter Property="MinWidth" Value="40"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}" /> <Setter Property="MinHeight" Value="40"/>
<Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="Margin" Value="1"/>
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="Padding" Value="0,0,0,4"/>
<Setter Property="MinWidth" Value="37" /> <!--These are actually set on the CalendarView in WinUI-->
<Setter Property="MinHeight" Value="38" /> <Setter Property="Foreground" Value="{DynamicResource CalendarViewCalendarItemForeground}"/>
<Setter Property="Focusable" Value="False"/> <Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource CalendarViewCalendarItemRevealBorderBrush}"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="ClipToBounds" Value="False"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid> <Panel>
<!-- To mimic WinUI SystemFocusVisual, Focus visual is drawn outside the bounds of the item -->
<Border Name="Root" Background="{TemplateBinding Background}"
BorderThickness="0" ClipToBounds="True">
<Rectangle Name="SelectedBackground" <ContentControl Name="Content"
Opacity="0.75" ContentTemplate="{TemplateBinding ContentTemplate}"
Fill="{TemplateBinding Background}"/> Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
Margin="{TemplateBinding Padding}"/>
<Rectangle Name="Background" </Border>
Opacity="0.5"
Fill="{TemplateBinding Background}"/>
<!--Focusable="False"--> <!-- Drawn Border should render on top of background to preserve the 1px
<ContentControl Name="Content" margin between items-->
Foreground="{TemplateBinding Foreground}" <Border Name="Border"
ContentTemplate="{TemplateBinding ContentTemplate}" BorderThickness="2"
Content="{TemplateBinding Content}" BorderBrush="{TemplateBinding BorderBrush}"/>
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
Margin="1,0,1,1"/>
<Rectangle Name="FocusVisual" <!--Removed for now...WinUI doesn't have selection follow focus, and only uses
StrokeThickness="1" focus visual w/ keyboard focus
Stroke="{DynamicResource HighlightBrush}" <Border Name="FocusVisual" BorderThickness="2"
IsHitTestVisible="False"/> BorderBrush="{DynamicResource SystemControlHighlightBaseHighBrush}"
IsHitTestVisible="False"
</Grid> Margin="-2" CornerRadius="{DynamicResource ControlCornerRadius}"/>-->
</Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<!--<Style Selector="CalendarButton /template/ Border#FocusVisual">
<Style Selector="CalendarButton /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="False"/> <Setter Property="IsVisible" Value="False"/>
</Style>-->
<Style Selector="CalendarButton:pointerover /template/ Border#Border">
<Setter Property="BorderBrush" Value="{DynamicResource CalendarViewHoverBorderBrush}"/>
</Style> </Style>
<Style Selector="CalendarButton:pointerover /template/ Rectangle#Background"> <Style Selector="CalendarButton:pressed /template/ Border#Border">
<Setter Property="IsVisible" Value="True"/> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewPressedBorderBrush}"/>
</Style>
<Style Selector="CalendarButton:pressed /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="True"/>
</Style> </Style>
<Style Selector="CalendarButton /template/ Rectangle#SelectedBackground"> <!-- Adjusted :selected to look like :today from DayItem -->
<Setter Property="IsVisible" Value="False"/> <Style Selector="CalendarButton:selected /template/ Border#Root">
<Setter Property="Background" Value="{DynamicResource SystemAccentBrush}"/>
</Style> </Style>
<Style Selector="CalendarButton:selected /template/ Rectangle#SelectedBackground"> <Style Selector="CalendarButton:selected /template/ Border#Border">
<Setter Property="IsVisible" Value="True"/> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewSelectedBorderBrush}"/>
</Style>
<Style Selector="CalendarButton:selected:pointerover /template/ Border#Root">
<Setter Property="Background">
<SolidColorBrush Color="{DynamicResource SystemAccentColor}"/>
</Setter>
</Style>
<Style Selector="CalendarButton:selected:pointerover /template/ Border#Border">
<Setter Property="BorderBrush">
<SolidColorBrush Color="{DynamicResource SystemAccentColorDark1}"/>
</Setter>
</Style>
<Style Selector="CalendarButton:selected:pressed /template/ Border#Root">
<Setter Property="Background">
<SolidColorBrush Color="{DynamicResource SystemAccentColor}"/>
</Setter>
</Style>
<Style Selector="CalendarButton:pressed /template/ Border#Border">
<Setter Property="BorderBrush">
<SolidColorBrush Color="{DynamicResource SystemAccentColorDark2}"/>
</Setter>
</Style>
<Style Selector="CalendarButton:selected /template/ ContentControl#Content">
<Setter Property="Foreground" Value="{DynamicResource CalendarViewTodayForeground}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Style> </Style>
<Style Selector="CalendarButton /template/ ContentControl#Content"> <!-- WinUI calls this OutOfFocus -->
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/> <Style Selector="CalendarButton:inactive /template/ Border#Root">
<!-- These are probably set in code, but consistent -->
<Setter Property="Background" Value="{DynamicResource CalendarViewOutOfScopeBackground}"/>
</Style> </Style>
<Style Selector="CalendarButton:inactive /template/ ContentControl#Content"> <Style Selector="CalendarButton:inactive /template/ ContentControl#Content">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLowBrush}"/> <Setter Property="Foreground" Value="{DynamicResource CalendarViewOutOfScopeForeground}"/>
</Style> </Style>
<Style Selector="CalendarButton:blackout /template/ ContentControl#Content">
<Style Selector="CalendarButton /template/ Rectangle#FocusVisual"> <Setter Property="Foreground" Value="{DynamicResource CalendarViewBlackoutForeground}"/>
<Setter Property="IsVisible" Value="False"/>
</Style> </Style>
<Style Selector="CalendarButton:btnfocused /template/ Rectangle#FocusVisual">
<Setter Property="IsVisible" Value="True"/> <!--<Style Selector="CalendarButton:dayfocused /template/ Border#FocusVisual">
<Setter Property="IsVisible" Value="True"/>
</Style>-->
<Style Selector="CalendarDayButton:disabled /template/ ContentControl#Content">
<Setter Property="Foreground" Value="{DynamicResource CalendarViewWeekDayForegroundDisabled}"/>
</Style> </Style>
</Styles> </Styles>

164
src/Avalonia.Themes.Fluent/CalendarDayButton.xaml

@ -7,110 +7,110 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Style Selector="CalendarDayButton"> <Style Selector="CalendarDayButton">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}" /> <Setter Property="ClickMode" Value="Release"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}" /> <Setter Property="MinWidth" Value="40"/>
<Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="MinHeight" Value="40"/>
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="Margin" Value="1"/>
<Setter Property="MinWidth" Value="5" /> <Setter Property="Padding" Value="0,0,0,4"/>
<Setter Property="MinHeight" Value="5" /> <!--These are actually set on the CalendarView in WinUI-->
<Setter Property="Focusable" Value="False"/> <Setter Property="Foreground" Value="{DynamicResource CalendarViewCalendarItemForeground}"/>
<Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource CalendarViewCalendarItemRevealBorderBrush}"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="ClipToBounds" Value="False"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Panel Background="Transparent"> <Panel>
<!-- To mimic WinUI SystemFocusVisual, Focus visual is drawn outside the bounds of the item -->
<Rectangle Name="TodayBackground" <Border Name="Root" Background="{TemplateBinding Background}"
Fill="{DynamicResource HighlightBrush}"/> BorderThickness="0" ClipToBounds="True">
<Rectangle Name="SelectedBackground" <ContentControl Name="Content"
Opacity="0.75" ContentTemplate="{TemplateBinding ContentTemplate}"
Fill="{TemplateBinding Background}"/> Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
<Rectangle Name="Background" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Opacity="0.5" FontSize="{TemplateBinding FontSize}"
Fill="{TemplateBinding Background}"/> Margin="{TemplateBinding Padding}"/>
<ContentControl Name="Content" </Border>
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" <!-- Drawn Border should render on top of background to preserve the 1px
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" margin between items-->
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" <Border Name="Border"
FontSize="{TemplateBinding FontSize}" BorderThickness="2"
Margin="5,1,5,1"/> BorderBrush="{TemplateBinding BorderBrush}"/>
<Path Name="BlackoutVisual" <!--Removed for now...WinUI doesn't have selection follow focus, and only uses
Margin="3" focus visual w/ keyboard focus
HorizontalAlignment="Stretch" <Border Name="FocusVisual" BorderThickness="2"
VerticalAlignment="Stretch" BorderBrush="{DynamicResource SystemControlHighlightBaseHighBrush}"
RenderTransformOrigin="0.5,0.5" IsHitTestVisible="False"
Fill="{DynamicResource ThemeForegroundBrush}" Margin="-2" CornerRadius="2"/>-->
Stretch="Fill"
Data="M8.1772461,11.029181 L10.433105,11.029181 L11.700684,12.801641 L12.973633,11.029181 L15.191895,11.029181 L12.844727,13.999395 L15.21875,17.060919 L12.962891,17.060919 L11.673828,15.256231 L10.352539,17.060919 L8.1396484,17.060919 L10.519043,14.042364 z" />
<Rectangle Name="FocusVisual"
StrokeThickness="1"
Stroke="{DynamicResource HighlightBrush}"
IsHitTestVisible="False"/>
</Panel> </Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<!--<Style Selector="CalendarDayButton /template/ Border#FocusVisual">
<Style Selector="CalendarDayButton /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="False"/> <Setter Property="IsVisible" Value="False"/>
</Style> </Style>-->
<Style Selector="CalendarDayButton:pointerover /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarDayButton:pressed /template/ Rectangle#Background">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="CalendarDayButton /template/ Rectangle#SelectedBackground"> <Style Selector="CalendarDayButton:pointerover /template/ Border#Border">
<Setter Property="IsVisible" Value="False"/> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewHoverBorderBrush}"/>
</Style> </Style>
<Style Selector="CalendarDayButton:selected /template/ Rectangle#SelectedBackground"> <Style Selector="CalendarDayButton:pressed /template/ Border#Border">
<Setter Property="IsVisible" Value="True"/> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewPressedBorderBrush}"/>
</Style> </Style>
<Style Selector="CalendarDayButton:selected /template/ Border#Border">
<Style Selector="CalendarDayButton /template/ ContentControl#Content"> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewSelectedBorderBrush}"/>
<Setter Property="Opacity" Value="1"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style> </Style>
<Style Selector="CalendarDayButton:disabled /template/ Rectangle#Background"> <Style Selector="CalendarDayButton:selected:pointerover /template/ Border#Border">
<Setter Property="IsVisible" Value="False"/> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewSelectedHoverBorderBrush}"/>
</Style> </Style>
<Style Selector="CalendarDayButton:disabled /template/ ContentControl#Content"> <Style Selector="CalendarDayButton:selected:pressed /template/ Border#Border">
<Setter Property="Opacity" Value="0.3"/> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewSelectedPressedBorderBrush}"/>
</Style> </Style>
<Style Selector="CalendarDayButton /template/ Rectangle#FocusVisual"> <Style Selector="CalendarDayButton:today /template/ Border#Root">
<Setter Property="IsVisible" Value="False"/> <!-- These are probably set in code, but consistent -->
<Setter Property="Background">
<SolidColorBrush Color="{DynamicResource SystemAccentColor}"/>
</Setter>
</Style> </Style>
<Style Selector="CalendarDayButton:dayfocused /template/ Rectangle#FocusVisual"> <Style Selector="CalendarDayButton:today:pointerover /template/ Border#Border">
<Setter Property="IsVisible" Value="True"/> <Setter Property="BorderBrush">
<SolidColorBrush Color="{DynamicResource SystemAccentColorDark1}"/>
</Setter>
</Style> </Style>
<Style Selector="CalendarDayButton:today:pressed /template/ Border#Border">
<Style Selector="CalendarDayButton /template/ Rectangle#TodayBackground"> <Setter Property="BorderBrush">
<Setter Property="IsVisible" Value="False"/> <SolidColorBrush Color="{DynamicResource SystemAccentColorDark2}"/>
</Setter>
</Style> </Style>
<Style Selector="CalendarDayButton:today /template/ Rectangle#TodayBackground"> <Style Selector="CalendarDayButton:today /template/ ContentControl#Content">
<Setter Property="IsVisible" Value="True"/> <Setter Property="Foreground" Value="{DynamicResource CalendarViewTodayForeground}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Style> </Style>
<!-- WinUI calls this OutOfFocus -->
<Style Selector="CalendarDayButton:inactive /template/ ContentControl#Content"> <Style Selector="CalendarDayButton:inactive /template/ Border#Root">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLowBrush}"/> <Setter Property="Background" Value="{DynamicResource CalendarViewOutOfScopeBackground}"/>
</Style> </Style>
<Style Selector="CalendarDayButton:today /template/ ContentControl#Content"> <Style Selector="CalendarDayButton:inactive /template/ ContentControl#Content">
<Setter Property="Foreground" Value="{DynamicResource ThemeControlLowBrush}"/> <Setter Property="Foreground" Value="{DynamicResource CalendarViewOutOfScopeForeground}"/>
</Style> </Style>
<Style Selector="CalendarDayButton /template/ Path#BlackoutVisual"> <Style Selector="CalendarDayButton:blackout /template/ ContentControl#Content">
<Setter Property="Opacity" Value="0"/> <Setter Property="Foreground" Value="{DynamicResource CalendarViewBlackoutForeground}"/>
</Style> </Style>
<Style Selector="CalendarDayButton:blackout /template/ Path#BlackoutVisual">
<Setter Property="Opacity" Value="0.3"/>
</Style>
<!--<Style Selector="CalendarDayButton:dayfocused /template/ Border#FocusVisual">
<Setter Property="IsVisible" Value="True"/>
</Style>-->
<Style Selector="CalendarDayButton:disabled /template/ ContentControl#Content">
<Setter Property="Foreground" Value="{DynamicResource CalendarViewWeekDayForegroundDisabled}"/>
</Style>
</Styles> </Styles>

275
src/Avalonia.Themes.Fluent/CalendarItem.xaml

@ -8,176 +8,127 @@
<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="CalendarItem"> <Style Selector="CalendarItem">
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Margin="0,2,0,2"
CornerRadius="1">
<Border CornerRadius="1"
BorderBrush="{DynamicResource ThemeBackgroundBrush}"
BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.Styles>
<Style Selector="Button.CalendarHeader">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
<Style Selector="Button.CalendarHeader:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="Button.CalendarNavigation">
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="28"/>
</Style>
<Style Selector="Button.CalendarNavigation > Path">
<Setter Property="Fill" Value="{DynamicResource ThemeForegroundBrush}"/>
</Style>
<Style Selector="Button.CalendarNavigation:pointerover > Path">
<Setter Property="Fill" Value="{DynamicResource HighlightBrush}"/>
</Style>
<Style Selector="Button#HeaderButton:pointerover">
<Setter Property="Foreground" Value="{DynamicResource HighlightBrush}"/>
</Style>
</Grid.Styles>
<Rectangle Grid.ColumnSpan="3"
Fill="{TemplateBinding HeaderBackground}"
Stretch="Fill"
VerticalAlignment="Top"
Height="22"/>
<Button Name="PreviousButton"
Classes="CalendarHeader CalendarNavigation"
IsVisible="False"
HorizontalAlignment="Left">
<Path Margin="14,-6,0,0"
Height="10"
Width="6"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Stretch="Fill"
Data="M288.75,232.25 L288.75,240.625 L283,236.625 z" />
</Button>
<Button Name="HeaderButton"
Classes="CalendarHeader"
Grid.Column="1"
FontWeight="Bold"
FontSize="10.5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Padding="1,5,1,9"/>
<Button Name="NextButton"
Classes="CalendarHeader CalendarNavigation"
Grid.Column="2"
IsVisible="False"
HorizontalAlignment="Right" >
<Path Margin="0,-6,14,0"
Height="10"
Width="6"
Stretch="Fill"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Data="M282.875,231.875 L282.875,240.375 L288.625,236 z" />
</Button>
<Grid Name="MonthView"
Grid.Row="1"
Grid.ColumnSpan="3"
IsVisible="False"
Margin="6,-1,6,6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
<Grid Name="YearView"
Grid.Row="1"
Grid.ColumnSpan="3"
IsVisible="False"
Margin="6,-3,7,6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
</Grid>
</Border>
</Border>
<Rectangle Name="DisabledVisual"
Stretch="Fill"
Fill="{DynamicResource ThemeControlLowBrush}"
Opacity="{DynamicResource ThemeDisabledOpacity}"
Margin="0,2,0,2" />
</Panel>
</ControlTemplate>
</Setter>
<Setter Property="DayTitleTemplate"> <Setter Property="DayTitleTemplate">
<Template> <Template>
<TextBlock FontWeight="Bold" <TextBlock Text="{Binding}"
FontSize="9.5"
Margin="0,4,0,4"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding}" /> FontSize="12"/>
</Template> </Template>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Border BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{DynamicResource ControlCornerRadius}">
<Border.Styles>
<Style Selector="Button.CalendarHeader">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="20" />
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/>
<Setter Property="Template">
<ControlTemplate>
<!-- HCA was changed here to ensure nav arrows display correctly -->
<ContentPresenter Name="Text" Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource CalendarViewNavigationButtonBorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Button.CalendarNavigation > Path">
<Setter Property="Stroke" Value="{DynamicResource CalendarViewForeground}"/>
</Style>
<Style Selector="Button.CalendarNavigation:pointerover > Path">
<Setter Property="Stroke" Value="{DynamicResource CalendarViewNavigationButtonForegroundPointerOver}"/>
</Style>
<Style Selector="Button.CalendarNavigation:pressed > Path">
<Setter Property="Stroke" Value="{DynamicResource CalendarViewNavigationButtonForegroundPressed}"/>
</Style>
<Style Selector="Button.CalendarHeader:pointerover /template/ ContentPresenter#Text">
<Setter Property="BorderBrush" Value="{DynamicResource CalendarViewNavigationButtonBorderBrushPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPointerOver}"/>
<!-- Prevent base button template:pointerover from overriding background here -->
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/>
</Style>
<Style Selector="Button.CalendarHeader:pressed /template/ ContentPresenter#Text">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CalendarViewNavigationButtonForegroundPressed}"/>
<!-- Prevent base button template:pointerover from overriding background here -->
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/>
</Style>
<Style Selector="Button.CalendarHeader:disabled /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CalendarViewWeekDayForegroundDisabled}"/>
</Style>
</Border.Styles>
<!-- To keep calendar from resizing when switching DisplayMode
In WinUI Min-Width from TemplateSettings
basically...MinWidth of DayItem = 40, 40 * 7 = 280 + margins/padding = ~294
Viewport height is set from # of rows displayed (2-8) in Month mode, = ~290 for 6 weeks (+ day names)
-->
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch" RowDefinitions="40,*" MinWidth="294">
<Grid ColumnDefinitions="5*,*,*">
<Button Name="HeaderButton" Classes="CalendarHeader" Foreground="{TemplateBinding Foreground}" Padding="12,0,0,0" HorizontalContentAlignment="Left" />
<Button Name="PreviousButton" Grid.Column="1" Classes="CalendarHeader CalendarNavigation" Foreground="{TemplateBinding Foreground}" Padding="1" HorizontalContentAlignment="Left">
<!--Path mimics Segoe MDL2 Assets font glyph used in WinUI-->
<Path StrokeThickness="1.5" Data="M 0,9 L 9,0 L 18,9" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
<Button Name="NextButton" Grid.Column="2" Classes="CalendarHeader CalendarNavigation" Foreground="{TemplateBinding Foreground}" Padding="1" HorizontalContentAlignment="Left">
<!--Path mimics Segoe MDL2 Assets font glyph used in WinUI-->
<Path StrokeThickness="1.5" Data="M 0,0 L 9,9 L 18,0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Button>
</Grid>
<Border Name="BackgroundLayer" Background="{TemplateBinding BorderBrush}" Grid.Row="1" />
<Grid Name="MonthView" Grid.Row="1" IsVisible="False" MinHeight="290" >
<Grid.RowDefinitions>
<!--This should always be the week day names??-->
<RowDefinition Height="38" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
<Grid Name="YearView" MinHeight="290"
Grid.Row="1" IsVisible="False">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="CalendarItem /template/ Rectangle#DisabledVisual"> <!-- BackgroundLayer shouldn't show in the week day names row-->
<Setter Property="IsVisible" Value="False"/> <Style Selector="Calendar[DisplayMode=Month] CalendarItem /template/ Border#BackgroundLayer">
</Style> <Setter Property="Margin" Value="0,38,0,0"/>
<Style Selector="CalendarItem:calendardisabled /template/ Rectangle#DisabledVisual">
<Setter Property="IsVisible" Value="True"/>
</Style> </Style>
</Styles> </Styles>

1
src/Avalonia.Themes.Fluent/FluentTheme.xaml

@ -23,7 +23,6 @@
<StyleInclude Source="resm:Avalonia.Themes.Fluent.PopupRoot.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.PopupRoot.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.ProgressBar.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.ProgressBar.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.RadioButton.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.RadioButton.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.RepeatButton.xaml?assembly=Avalonia.Themes.Fluent" />
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Separator.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Separator.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Slider.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.Slider.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.ScrollBar.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.ScrollBar.xaml?assembly=Avalonia.Themes.Fluent"/>

40
src/Avalonia.Themes.Fluent/RepeatButton.xaml

@ -1,40 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="RepeatButton">
<Setter Property="Background"
Value="{DynamicResource ThemeControlMidBrush}" />
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeBorderLowBrush}" />
<Setter Property="BorderThickness"
Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="Foreground"
Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="Padding"
Value="4" />
<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}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="RepeatButton:pointerover /template/ ContentPresenter">
<Setter Property="BorderBrush"
Value="{DynamicResource ThemeBorderMidBrush}" />
</Style>
<Style Selector="RepeatButton:disabled">
<Setter Property="Opacity"
Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
</Styles>

323
src/Avalonia.Themes.Fluent/Slider.xaml

@ -1,93 +1,264 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Margin="20" Width="200" Height="200">
<DockPanel LastChildFill="True">
<StackPanel Spacing="10" DockPanel.Dock="Top">
<Slider Value="50" />
<Slider IsEnabled="False" Value="50" />
</StackPanel>
<StackPanel Spacing="10" Orientation="Horizontal">
<Slider Value="50" Orientation="Vertical" />
<Slider IsEnabled="False" Orientation="Vertical" Value="50" />
</StackPanel>
</DockPanel>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<Thickness x:Key="SliderTopHeaderMargin">0,0,0,4</Thickness>
<GridLength x:Key="SliderPreContentMargin">15</GridLength>
<GridLength x:Key="SliderPostContentMargin">15</GridLength>
<x:Double x:Key="SliderHorizontalHeight">32</x:Double>
<x:Double x:Key="SliderVerticalWidth">32</x:Double>
<CornerRadius x:Key="SliderThumbCornerRadius">10</CornerRadius>
<x:Double x:Key="SliderHorizontalThumbWidth">20</x:Double>
<x:Double x:Key="SliderHorizontalThumbHeight">20</x:Double>
<x:Double x:Key="SliderVerticalThumbWidth">20</x:Double>
<x:Double x:Key="SliderVerticalThumbHeight">20</x:Double>
</Styles.Resources>
<Style Selector="Thumb.SliderThumbStyle">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{DynamicResource SliderThumbCornerRadius}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style Selector="Slider:horizontal"> <Style Selector="Slider:horizontal">
<Setter Property="MinWidth" Value="40"/> <Setter Property="Background" Value="{DynamicResource SliderTrackFill}" />
<Setter Property="MinHeight" Value="20"/> <Setter Property="BorderThickness" Value="{DynamicResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{DynamicResource SliderTrackValueFill}" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid Name="grid"> <Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid.RowDefinitions> <Grid Name="grid" Margin="{TemplateBinding Padding}" RowDefinitions="Auto, *">
<RowDefinition Height="Auto"/> <ContentPresenter x:Name="HeaderContentPresenter" Grid.Row="0" TextBlock.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}" TextBlock.Foreground="{DynamicResource SliderHeaderForeground}"
<RowDefinition Height="Auto" MinHeight="20"/> Margin="{DynamicResource SliderTopHeaderMargin}" />
<RowDefinition Height="Auto"/> <Grid x:Name="SliderContainer" Grid.Row="1">
</Grid.RowDefinitions> <Grid.Styles>
<Border Name="TrackBackground" Grid.Row="1" Height="4" Margin="6,0" VerticalAlignment="Center"/> <Style Selector="TickBar">
<Track Name="PART_Track" Grid.Row="1" Orientation="Horizontal"> <Setter Property="ReservedSpace" Value="{Binding #PART_Track.Thumb.Bounds}" />
<Track.DecreaseButton> </Style>
<RepeatButton Name="PART_DecreaseButton" </Grid.Styles>
Classes="repeattrack" /> <Grid x:Name="HorizontalTemplate" ColumnDefinitions="Auto,Auto,*" MinHeight="{DynamicResource SliderHorizontalHeight}">
</Track.DecreaseButton> <Grid.RowDefinitions>
<Track.IncreaseButton> <RowDefinition Height="{DynamicResource SliderPreContentMargin}" />
<RepeatButton Name="PART_IncreaseButton" <RowDefinition Height="Auto" />
Classes="repeattrack" /> <RowDefinition Height="{DynamicResource SliderPostContentMargin}" />
</Track.IncreaseButton> </Grid.RowDefinitions>
<Thumb Name="thumb" MinWidth="20" MinHeight="20"> <TickBar Name="TopTickBar" Placement="Top" Height="{DynamicResource SliderOutsideTickBarThemeHeight}" VerticalAlignment="Bottom" Margin="0,0,0,4" Grid.ColumnSpan="3" />
<Thumb.Template> <!-- <TickBar Name="HorizontalInlineTickBar" Placement="Top" Fill="{DynamicResource SliderInlineTickBarFill}" Height="{DynamicResource SliderTrackThemeHeight}" Grid.Row="1" Grid.ColumnSpan="3" /> -->
<ControlTemplate> <TickBar Name="BottomTickBar" Placement="Bottom" Height="{DynamicResource SliderOutsideTickBarThemeHeight}" VerticalAlignment="Top" Margin="0,4,0,0" Grid.Row="2" Grid.ColumnSpan="3" />
<Grid> <Track Name="PART_Track" Grid.Row="1" Grid.ColumnSpan="3" Orientation="Horizontal">
<Ellipse Width="12" Height="12" Fill="{DynamicResource ThemeAccentBrush}"/> <Track.DecreaseButton>
</Grid> <RepeatButton Name="PART_DecreaseButton" Background="{TemplateBinding Foreground}">
</ControlTemplate> <RepeatButton.Template>
</Thumb.Template> <ControlTemplate>
</Thumb> <Grid>
</Track> <Border Name="FocusTarget" Background="Transparent" Margin="0,-10" />
</Grid> <Border Name="TrackBackground" Background="{TemplateBinding Background}" CornerRadius="{DynamicResource ControlCornerRadius}" Height="{DynamicResource SliderTrackThemeHeight}" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton" Background="{TemplateBinding Background}">
<RepeatButton.Template>
<ControlTemplate>
<Grid>
<Border Name="FocusTarget" Background="Transparent" Margin="0,-10" />
<Border Name="TrackBackground" Background="{TemplateBinding Background}" CornerRadius="{DynamicResource ControlCornerRadius}" Height="{DynamicResource SliderTrackThemeHeight}" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseButton>
<Thumb Classes="SliderThumbStyle" Name="thumb" Margin="0" Padding="0" DataContext="{TemplateBinding Value}" Height="{DynamicResource SliderHorizontalThumbHeight}" Width="{DynamicResource SliderHorizontalThumbWidth}" />
</Track>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Slider:vertical"> <Style Selector="Slider:vertical">
<Setter Property="MinWidth" Value="20"/> <Setter Property="Background" Value="{DynamicResource SliderTrackFill}" />
<Setter Property="MinHeight" Value="40"/> <Setter Property="BorderThickness" Value="{DynamicResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{DynamicResource SliderTrackValueFill}" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid> <Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Grid.ColumnDefinitions> <Grid Name="grid" Margin="{TemplateBinding Padding}" RowDefinitions="Auto, *">
<ColumnDefinition Width="Auto"/> <ContentPresenter x:Name="HeaderContentPresenter" Grid.Row="0" TextBlock.FontWeight="{DynamicResource SliderHeaderThemeFontWeight}" TextBlock.Foreground="{DynamicResource SliderHeaderForeground}"
<ColumnDefinition Width="Auto" MinWidth="26"/> Margin="{DynamicResource SliderTopHeaderMargin}" />
<ColumnDefinition Width="Auto"/> <Grid x:Name="SliderContainer" Grid.Row="1">
</Grid.ColumnDefinitions> <Grid.Styles>
<Border Name="TrackBackground" Grid.Column="1" Width="4" Margin="0,6" HorizontalAlignment="Center"/> <Style Selector="TickBar">
<Track Name="PART_Track" Grid.Column="1" Orientation="Vertical" IsDirectionReversed="True"> <Setter Property="ReservedSpace" Value="{Binding #PART_Track.Thumb.Bounds}" />
<Track.DecreaseButton> </Style>
<RepeatButton Name="PART_DecreaseButton" </Grid.Styles>
Classes="repeattrack" /> <Grid x:Name="VerticalTemplate" RowDefinitions="*,Auto,Auto" MinWidth="{DynamicResource SliderVerticalWidth}">
</Track.DecreaseButton> <Grid.ColumnDefinitions>
<Track.IncreaseButton> <ColumnDefinition Width="{DynamicResource SliderPreContentMargin}" />
<RepeatButton Name="PART_IncreaseButton" <ColumnDefinition Width="Auto" />
Classes="repeattrack" /> <ColumnDefinition Width="{DynamicResource SliderPostContentMargin}" />
</Track.IncreaseButton> </Grid.ColumnDefinitions>
<Thumb Name="thumb" MinWidth="20" MinHeight="20"> <TickBar Name="LeftTickBar" Placement="Left" Width="{DynamicResource SliderOutsideTickBarThemeHeight}" HorizontalAlignment="Right" Margin="0,0,4,0" Grid.RowSpan="3" />
<Thumb.Template> <!-- <TickBar Name="VerticalInlineTickBar" Placement="Inline" Fill="{DynamicResource SliderInlineTickBarFill}" Width="{DynamicResource SliderTrackThemeHeight}" Grid.Column="1" Grid.RowSpan="3" /> -->
<ControlTemplate> <TickBar Name="RightTickBar" Placement="Right" Width="{DynamicResource SliderOutsideTickBarThemeHeight}" HorizontalAlignment="Left" Margin="4,0,0,0" Grid.Column="2" Grid.RowSpan="3" />
<Grid> <Track Name="PART_Track" Grid.Column="1" Grid.ColumnSpan="1" Grid.RowSpan="3" Orientation="Vertical">
<Ellipse Width="12" Height="12" Fill="{DynamicResource ThemeAccentBrush}"/> <Track.DecreaseButton>
</Grid> <RepeatButton Name="PART_DecreaseButton" Background="{TemplateBinding Foreground}">
</ControlTemplate> <RepeatButton.Template>
</Thumb.Template> <ControlTemplate>
</Thumb> <Grid>
</Track> <Border Name="FocusTarget" Background="Transparent" Margin="0,-10" />
</Grid> <Border Name="TrackBackground" Background="{TemplateBinding Background}" CornerRadius="{DynamicResource ControlCornerRadius}" Width="{DynamicResource SliderTrackThemeHeight}" HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton" Background="{TemplateBinding Background}">
<RepeatButton.Template>
<ControlTemplate>
<Grid>
<Border Name="FocusTarget" Background="Transparent" Margin="0,-10" />
<Border Name="TrackBackground" Background="{TemplateBinding Background}" CornerRadius="{DynamicResource ControlCornerRadius}" Width="{DynamicResource SliderTrackThemeHeight}" HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>
</RepeatButton.Template>
</RepeatButton>
</Track.IncreaseButton>
<Thumb Classes="SliderThumbStyle" Name="SliderThumb" Margin="0" Padding="0" DataContext="{TemplateBinding Value}" Height="{DynamicResource SliderVerticalThumbHeight}" Width="{DynamicResource SliderVerticalThumbWidth}" />
</Track>
</Grid>
</Grid>
</Grid>
</Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Slider /template/ Track#PART_Track"> <Style Selector="Slider /template/ Track#PART_Track">
<Setter Property="Minimum" Value="{TemplateBinding Minimum}"/> <Setter Property="Minimum" Value="{TemplateBinding Minimum}" />
<Setter Property="Maximum" Value="{TemplateBinding Maximum}"/> <Setter Property="Maximum" Value="{TemplateBinding Maximum}" />
<Setter Property="Value" Value="{TemplateBinding Value, Mode=TwoWay}"/> <Setter Property="Value" Value="{TemplateBinding Value, Mode=TwoWay}" />
</Style> </Style>
<Style Selector="Slider /template/ Border#TrackBackground"> <Style Selector="Slider /template/ TickBar">
<Setter Property="BorderThickness" Value="2"/> <Setter Property="Fill" Value="{DynamicResource SliderTickBarFill}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLowBrush}"/> <Setter Property="TickFrequency" Value="{TemplateBinding Slider.TickFrequency}" />
<Setter Property="Orientation" Value="{TemplateBinding Slider.Orientation}" />
<Setter Property="Minimum" Value="{TemplateBinding Slider.Minimum}" />
<Setter Property="Maximum" Value="{TemplateBinding Slider.Maximum}" />
</Style> </Style>
<Style Selector="Slider /template/ RepeatButton.repeattrack">
<Setter Property="Background" Value="Transparent"/> <!-- Normal State -->
<Setter Property="Foreground" Value="{DynamicResource ThemeBorderLowBrush}"/>
<Setter Property="Template"> <Style Selector="Slider /template/ Thumb.SliderThumbStyle">
<ControlTemplate> <Setter Property="Background" Value="{DynamicResource SliderThumbBackground}" />
<Border Background="{TemplateBinding Background}" /> </Style>
</ControlTemplate>
</Setter> <Style Selector="Slider /template/ Grid#SliderContainer">
<Setter Property="Background" Value="{DynamicResource SliderContainerBackground}" />
</Style>
<Style Selector="Slider /template/ TickBar">
<Setter Property="IsVisible" Value="False" />
</Style>
<!-- TickBar Placement States -->
<Style Selector="Slider[TickPlacement=TopLeft] /template/ TickBar#LeftTickBar, Slider[TickPlacement=Outside] /template/ TickBar#LeftTickBar">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="Slider[TickPlacement=TopLeft] /template/ TickBar#TopTickBar, Slider[TickPlacement=Outside] /template/ TickBar#TopTickBar">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="Slider[TickPlacement=BottomRight] /template/ TickBar#BottomTickBar, Slider[TickPlacement=Outside] /template/ TickBar#BottomTickBar">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="Slider[TickPlacement=BottomRight] /template/ TickBar#RightTickBar, Slider[TickPlacement=Outside] /template/ TickBar#RightTickBar">
<Setter Property="IsVisible" Value="True" />
</Style>
<!-- Disabled State -->
<Style Selector="Slider:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SliderHeaderForegroundDisabled}" />
</Style>
<Style Selector="Slider:disabled /template/ RepeatButton#PART_DecreaseButton">
<Setter Property="Background" Value="{DynamicResource SliderTrackValueFillDisabled}" />
</Style>
<Style Selector="Slider:disabled /template/ RepeatButton#PART_IncreaseButton">
<Setter Property="Background" Value="{DynamicResource SliderTrackFillDisabled}" />
</Style>
<Style Selector="Slider:disabled /template/ Thumb.SliderThumbStyle">
<Setter Property="Background" Value="{DynamicResource SliderThumbBackgroundDisabled}" />
</Style>
<Style Selector="Slider:disabled /template/ TickBar">
<Setter Property="Fill" Value="{DynamicResource SliderTickBarFillDisabled}" />
</Style>
<Style Selector="Slider:pointerover /template/ Grid#SliderContainer">
<Setter Property="Background" Value="{DynamicResource SliderContainerBackgroundDisabled}" />
</Style>
<!-- PointerOver State -->
<Style Selector="Slider:pointerover /template/ RepeatButton#PART_IncreaseButton">
<Setter Property="Background" Value="{DynamicResource SliderTrackFillPointerOver}" />
</Style>
<Style Selector="Slider:pointerover /template/ Thumb.SliderThumbStyle">
<Setter Property="Background" Value="{DynamicResource SliderThumbBackgroundPointerOver}" />
</Style>
<Style Selector="Slider:pointerover /template/ Grid#SliderContainer">
<Setter Property="Background" Value="{DynamicResource SliderContainerBackgroundPointerOver}" />
</Style>
<Style Selector="Slider:pointerover /template/ RepeatButton#PART_DecreaseButton">
<Setter Property="Background" Value="{DynamicResource SliderTrackValueFillPointerOver}" />
</Style>
<!-- Pressed State -->
<Style Selector="Slider:pressed /template/ RepeatButton#PART_IncreaseButton">
<Setter Property="Background" Value="{DynamicResource SliderTrackFillPressed}" />
</Style>
<Style Selector="Slider:pressed /template/ Thumb.SliderThumbStyle">
<Setter Property="Background" Value="{DynamicResource SliderThumbBackgroundPressed}" />
</Style>
<Style Selector="Slider:pointerover /template/ Grid#SliderContainer">
<Setter Property="Background" Value="{DynamicResource SliderContainerBackgroundPressed}" />
</Style>
<Style Selector="Slider:pointerover /template/ RepeatButton#PART_DecreaseButton">
<Setter Property="Background" Value="{DynamicResource SliderTrackValueFillPressed}" />
</Style> </Style>
<Style Selector="Slider:disabled /template/ Grid#grid">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
</Styles> </Styles>

95
src/Avalonia.Themes.Fluent/ToolTip.xaml

@ -1,17 +1,78 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ToolTip"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/> <Design.PreviewWith>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Grid RowDefinitions="Auto,Auto"
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/> ColumnDefinitions="Auto,Auto"
<Setter Property="Padding" Value="4,2"/> HorizontalAlignment="Center">
<Setter Property="Template"> <Border Grid.Column="0"
<ControlTemplate> Grid.Row="1"
<ContentPresenter Name="PART_ContentPresenter" Background="{DynamicResource ThemeAccentBrush}"
Background="{TemplateBinding Background}" Margin="5"
BorderBrush="{TemplateBinding BorderBrush}" Padding="50"
BorderThickness="{TemplateBinding BorderThickness}" ToolTip.Tip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.">
ContentTemplate="{TemplateBinding ContentTemplate}" <TextBlock>Hover Here</TextBlock>
Content="{TemplateBinding Content}" </Border>
Padding="{TemplateBinding Padding}"/> <CheckBox Grid.Column="1"
</ControlTemplate> Margin="5"
</Setter> Grid.Row="0"
</Style> IsChecked="{Binding ElementName=Border, Path=(ToolTip.IsOpen)}"
Content="ToolTip Open" />
<Border Name="Border"
Grid.Column="1"
Grid.Row="1"
Background="{DynamicResource ThemeAccentBrush}"
Margin="5"
Padding="50"
ToolTip.Placement="Bottom">
<ToolTip.Tip>
<StackPanel>
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
</StackPanel>
</ToolTip.Tip>
<TextBlock>ToolTip bottom placement</TextBlock>
</Border>
</Grid>
</Design.PreviewWith>
<Style Selector="ToolTip">
<Setter Property="Foreground" Value="{DynamicResource ToolTipForeground}" />
<Setter Property="Background" Value="{DynamicResource ToolTipBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ToolTipBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ToolTipBorderThemeThickness}" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ToolTipContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource ToolTipBorderThemePadding}" />
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.15" />
</Transitions>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Border Name="PART_LayoutRoot"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
Padding="{TemplateBinding Padding}"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<ContentPresenter Name="PART_ContentPresenter"
MaxWidth="320"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ToolTip > TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
<Style Selector="ToolTip">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="ToolTip:open">
<Setter Property="Opacity" Value="1" />
</Style>
</Styles>

35
src/Avalonia.X11/X11Atoms.cs

@ -22,6 +22,7 @@
// //
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using static Avalonia.X11.XLib; using static Avalonia.X11.XLib;
// ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable FieldCanBeMadeReadOnly.Global
@ -40,8 +41,9 @@ namespace Avalonia.X11
internal class X11Atoms internal class X11Atoms
{ {
private readonly IntPtr _display;
// Our atoms // Our atoms
public readonly IntPtr AnyPropertyType = (IntPtr)0; public readonly IntPtr AnyPropertyType = (IntPtr)0;
public readonly IntPtr XA_PRIMARY = (IntPtr)1; public readonly IntPtr XA_PRIMARY = (IntPtr)1;
public readonly IntPtr XA_SECONDARY = (IntPtr)2; public readonly IntPtr XA_SECONDARY = (IntPtr)2;
@ -187,10 +189,13 @@ namespace Avalonia.X11
public readonly IntPtr ATOM_PAIR; public readonly IntPtr ATOM_PAIR;
public readonly IntPtr MANAGER; public readonly IntPtr MANAGER;
public readonly IntPtr _KDE_NET_WM_BLUR_BEHIND_REGION; public readonly IntPtr _KDE_NET_WM_BLUR_BEHIND_REGION;
public readonly IntPtr INCR;
private readonly Dictionary<string, IntPtr> _namesToAtoms = new Dictionary<string, IntPtr>();
private readonly Dictionary<IntPtr, string> _atomsToNames = new Dictionary<IntPtr, string>();
public X11Atoms(IntPtr display) public X11Atoms(IntPtr display)
{ {
_display = display;
// make sure this array stays in sync with the statements below // make sure this array stays in sync with the statements below
@ -204,7 +209,33 @@ namespace Avalonia.X11
XInternAtoms(display, atomNames, atomNames.Length, true, atoms); XInternAtoms(display, atomNames, atomNames.Length, true, atoms);
for (var c = 0; c < fields.Length; c++) for (var c = 0; c < fields.Length; c++)
{
_namesToAtoms[fields[c].Name] = atoms[c];
_atomsToNames[atoms[c]] = fields[c].Name;
fields[c].SetValue(this, atoms[c]); fields[c].SetValue(this, atoms[c]);
}
}
public IntPtr GetAtom(string name)
{
if (_namesToAtoms.TryGetValue(name, out var rv))
return rv;
var atom = XInternAtom(_display, name, false);
_namesToAtoms[name] = atom;
_atomsToNames[atom] = name;
return atom;
}
public string GetAtomName(IntPtr atom)
{
if (_atomsToNames.TryGetValue(atom, out var rv))
return rv;
var name = XLib.GetAtomName(_display, atom);
if (name == null)
return null;
_atomsToNames[atom] = name;
_namesToAtoms[name] = atom;
return name;
} }
} }
} }

135
src/Avalonia.X11/X11Clipboard.cs

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using static Avalonia.X11.XLib; using static Avalonia.X11.XLib;
namespace Avalonia.X11 namespace Avalonia.X11
@ -10,12 +12,14 @@ namespace Avalonia.X11
class X11Clipboard : IClipboard class X11Clipboard : IClipboard
{ {
private readonly X11Info _x11; private readonly X11Info _x11;
private string _storedString; private IDataObject _storedDataObject;
private IntPtr _handle; private IntPtr _handle;
private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs; private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs;
private TaskCompletionSource<string> _requestedTextTcs; private TaskCompletionSource<object> _requestedDataTcs;
private readonly IntPtr[] _textAtoms; private readonly IntPtr[] _textAtoms;
private readonly IntPtr _avaloniaSaveTargetsAtom; private readonly IntPtr _avaloniaSaveTargetsAtom;
private readonly Dictionary<string, IntPtr> _formatAtoms = new Dictionary<string, IntPtr>();
private readonly Dictionary<IntPtr, string> _atomFormats = new Dictionary<IntPtr, string>();
public X11Clipboard(AvaloniaX11Platform platform) public X11Clipboard(AvaloniaX11Platform platform)
{ {
@ -31,6 +35,11 @@ namespace Avalonia.X11
}.Where(a => a != IntPtr.Zero).ToArray(); }.Where(a => a != IntPtr.Zero).ToArray();
} }
bool IsStringAtom(IntPtr atom)
{
return _textAtoms.Contains(atom);
}
Encoding GetStringEncoding(IntPtr atom) Encoding GetStringEncoding(IntPtr atom)
{ {
return (atom == _x11.Atoms.XA_STRING return (atom == _x11.Atoms.XA_STRING
@ -75,21 +84,31 @@ namespace Avalonia.X11
Encoding textEnc; Encoding textEnc;
if (target == _x11.Atoms.TARGETS) if (target == _x11.Atoms.TARGETS)
{ {
var atoms = _textAtoms; var atoms = new HashSet<IntPtr> { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE };
atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE}) foreach (var fmt in _storedDataObject.GetDataFormats())
.ToArray(); {
if (fmt == DataFormats.Text)
foreach (var ta in _textAtoms)
atoms.Add(ta);
else
atoms.Add(_x11.Atoms.GetAtom(fmt));
}
XChangeProperty(_x11.Display, window, property, XChangeProperty(_x11.Display, window, property,
_x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length); _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms.ToArray(), atoms.Count);
return property; return property;
} }
else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{ {
return property; return property;
} }
else if ((textEnc = GetStringEncoding(target)) != null) else if ((textEnc = GetStringEncoding(target)) != null
&& _storedDataObject?.Contains(DataFormats.Text) == true)
{ {
var text = _storedDataObject.GetText();
var data = textEnc.GetBytes(_storedString ?? ""); if(text == null)
return IntPtr.Zero;
var data = textEnc.GetBytes(text);
fixed (void* pdata = data) fixed (void* pdata = data)
XChangeProperty(_x11.Display, window, property, target, 8, XChangeProperty(_x11.Display, window, property, target, 8,
PropertyMode.Replace, PropertyMode.Replace,
@ -121,6 +140,23 @@ namespace Avalonia.X11
return property; return property;
} }
else if(_storedDataObject?.Contains(_x11.Atoms.GetAtomName(target)) == true)
{
var objValue = _storedDataObject.Get(_x11.Atoms.GetAtomName(target));
if(!(objValue is byte[] bytes))
{
if (objValue is string s)
bytes = Encoding.UTF8.GetBytes(s);
else
return IntPtr.Zero;
}
XChangeProperty(_x11.Display, window, property, target, 8,
PropertyMode.Replace,
bytes, bytes.Length);
return property;
}
else else
return IntPtr.Zero; return IntPtr.Zero;
} }
@ -131,15 +167,15 @@ namespace Avalonia.X11
if (sel.property == IntPtr.Zero) if (sel.property == IntPtr.Zero)
{ {
_requestedFormatsTcs?.TrySetResult(null); _requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null); _requestedDataTcs?.TrySetResult(null);
} }
XGetWindowProperty(_x11.Display, _handle, sel.property, IntPtr.Zero, new IntPtr (0x7fffffff), true, (IntPtr)Atom.AnyPropertyType, XGetWindowProperty(_x11.Display, _handle, sel.property, IntPtr.Zero, new IntPtr (0x7fffffff), true, (IntPtr)Atom.AnyPropertyType,
out var actualAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop); out var actualTypeAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop);
Encoding textEnc = null; Encoding textEnc = null;
if (nitems == IntPtr.Zero) if (nitems == IntPtr.Zero)
{ {
_requestedFormatsTcs?.TrySetResult(null); _requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null); _requestedDataTcs?.TrySetResult(null);
} }
else else
{ {
@ -154,10 +190,24 @@ namespace Avalonia.X11
_requestedFormatsTcs?.TrySetResult(formats); _requestedFormatsTcs?.TrySetResult(formats);
} }
} }
else if ((textEnc = GetStringEncoding(sel.property)) != null) else if ((textEnc = GetStringEncoding(actualTypeAtom)) != null)
{ {
var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32()); var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32());
_requestedTextTcs?.TrySetResult(text); _requestedDataTcs?.TrySetResult(text);
}
else
{
if (actualTypeAtom == _x11.Atoms.INCR)
{
// TODO: Actually implement that monstrosity
_requestedDataTcs.TrySetResult(null);
}
else
{
var data = new byte[(int)nitems * (actualFormat / 8)];
Marshal.Copy(prop, data, 0, data.Length);
_requestedDataTcs?.TrySetResult(data);
}
} }
} }
@ -174,17 +224,19 @@ namespace Avalonia.X11
return _requestedFormatsTcs.Task; return _requestedFormatsTcs.Task;
} }
Task<string> SendTextRequest(IntPtr format) Task<object> SendDataRequest(IntPtr format)
{ {
if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted) if (_requestedDataTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedTextTcs = new TaskCompletionSource<string>(); _requestedDataTcs = new TaskCompletionSource<object>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero); XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero);
return _requestedTextTcs.Task; return _requestedDataTcs.Task;
} }
bool HasOwner => XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) != IntPtr.Zero;
public async Task<string> GetTextAsync() public async Task<string> GetTextAsync()
{ {
if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero) if (!HasOwner)
return null; return null;
var res = await SendFormatRequest(); var res = await SendFormatRequest();
var target = _x11.Atoms.UTF8_STRING; var target = _x11.Atoms.UTF8_STRING;
@ -199,7 +251,7 @@ namespace Avalonia.X11
} }
} }
return await SendTextRequest(target); return (string)await SendDataRequest(target);
} }
void StoreAtomsInClipboardManager(IntPtr[] atoms) void StoreAtomsInClipboardManager(IntPtr[] atoms)
@ -220,15 +272,52 @@ namespace Avalonia.X11
public Task SetTextAsync(string text) public Task SetTextAsync(string text)
{ {
_storedString = text; var data = new DataObject();
data.Set(DataFormats.Text, text);
return SetDataObjectAsync(data);
}
public Task ClearAsync()
{
return SetTextAsync(null);
}
public Task SetDataObjectAsync(IDataObject data)
{
_storedDataObject = data;
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero); XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero);
StoreAtomsInClipboardManager(_textAtoms); StoreAtomsInClipboardManager(_textAtoms);
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task ClearAsync() public async Task<string[]> GetFormatsAsync()
{ {
return SetTextAsync(null); if (!HasOwner)
return null;
var res = await SendFormatRequest();
if (res == null)
return null;
var rv = new List<string>();
if (_textAtoms.Any(res.Contains))
rv.Add(DataFormats.Text);
foreach (var t in res)
rv.Add(_x11.Atoms.GetAtomName(t));
return rv.ToArray();
}
public async Task<object> GetDataAsync(string format)
{
if (!HasOwner)
return null;
if (format == DataFormats.Text)
return await GetTextAsync();
var formatAtom = _x11.Atoms.GetAtom(format);
var res = await SendFormatRequest();
if (!res.Contains(formatAtom))
return null;
return await SendDataRequest(formatAtom);
} }
} }
} }

4
src/Avalonia.X11/XLib.cs

@ -236,6 +236,10 @@ namespace Avalonia.X11
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, ref IntPtr value, int nelements); int format, PropertyMode mode, ref IntPtr value, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, byte[] data, int nelements);
[DllImport(libX11)] [DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, uint[] data, int nelements); int format, PropertyMode mode, uint[] data, int nelements);

8
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@ -107,6 +107,14 @@ namespace Avalonia.Data
private object ConvertValue(IList<object> values, Type targetType, IMultiValueConverter converter) private object ConvertValue(IList<object> values, Type targetType, IMultiValueConverter converter)
{ {
for (var i = 0; i < values.Count; ++i)
{
if (values[i] is BindingNotification notification)
{
values[i] = notification.Value;
}
}
var culture = CultureInfo.CurrentCulture; var culture = CultureInfo.CurrentCulture;
var converted = converter.Convert(values, targetType, ConverterParameter, culture); var converted = converter.Convert(values, targetType, ConverterParameter, culture);

3
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -6,6 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" /> <Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" />
</Project> </Project>

76
src/Windows/Avalonia.Win32/ClipboardImpl.cs

@ -1,25 +1,30 @@
using System; using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Threading;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
internal class ClipboardImpl : IClipboard internal class ClipboardImpl : IClipboard
{ {
private async Task OpenClipboard() private async Task<IDisposable> OpenClipboard()
{ {
while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero)) while (!UnmanagedMethods.OpenClipboard(IntPtr.Zero))
{ {
await Task.Delay(100); await Task.Delay(100);
} }
return Disposable.Create(() => UnmanagedMethods.CloseClipboard());
} }
public async Task<string> GetTextAsync() public async Task<string> GetTextAsync()
{ {
await OpenClipboard(); using(await OpenClipboard())
try
{ {
IntPtr hText = UnmanagedMethods.GetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT); IntPtr hText = UnmanagedMethods.GetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT);
if (hText == IntPtr.Zero) if (hText == IntPtr.Zero)
@ -37,10 +42,6 @@ namespace Avalonia.Win32
UnmanagedMethods.GlobalUnlock(hText); UnmanagedMethods.GlobalUnlock(hText);
return rv; return rv;
} }
finally
{
UnmanagedMethods.CloseClipboard();
}
} }
public async Task SetTextAsync(string text) public async Task SetTextAsync(string text)
@ -50,31 +51,66 @@ namespace Avalonia.Win32
throw new ArgumentNullException(nameof(text)); throw new ArgumentNullException(nameof(text));
} }
await OpenClipboard(); using(await OpenClipboard())
UnmanagedMethods.EmptyClipboard();
try
{ {
UnmanagedMethods.EmptyClipboard();
var hGlobal = Marshal.StringToHGlobalUni(text); var hGlobal = Marshal.StringToHGlobalUni(text);
UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal); UnmanagedMethods.SetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, hGlobal);
} }
finally
{
UnmanagedMethods.CloseClipboard();
}
} }
public async Task ClearAsync() public async Task ClearAsync()
{ {
await OpenClipboard(); using(await OpenClipboard())
try
{ {
UnmanagedMethods.EmptyClipboard(); UnmanagedMethods.EmptyClipboard();
} }
finally }
public async Task SetDataObjectAsync(IDataObject data)
{
Dispatcher.UIThread.VerifyAccess();
var wrapper = new DataObject(data);
while (true)
{ {
UnmanagedMethods.CloseClipboard(); if (UnmanagedMethods.OleSetClipboard(wrapper) == 0)
break;
await Task.Delay(100);
}
}
public async Task<string[]> GetFormatsAsync()
{
Dispatcher.UIThread.VerifyAccess();
while (true)
{
if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0)
{
var wrapper = new OleDataObject(dataObject);
var formats = wrapper.GetDataFormats().ToArray();
Marshal.ReleaseComObject(dataObject);
return formats;
}
await Task.Delay(100);
}
}
public async Task<object> GetDataAsync(string format)
{
Dispatcher.UIThread.VerifyAccess();
while (true)
{
if (UnmanagedMethods.OleGetClipboard(out var dataObject) == 0)
{
var wrapper = new OleDataObject(dataObject);
var rv = wrapper.Get(format);
Marshal.ReleaseComObject(dataObject);
return rv;
}
await Task.Delay(100);
} }
} }
} }

6
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1169,6 +1169,12 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem); public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern int OleGetClipboard(out IOleDataObject dataObject);
[DllImport("ole32.dll", PreserveSig = true)]
public static extern int OleSetClipboard(IOleDataObject dataObject);
[DllImport("kernel32.dll", ExactSpelling = true)] [DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalLock(IntPtr handle); public static extern IntPtr GlobalLock(IntPtr handle);

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -36,7 +36,7 @@ namespace Avalonia
public class Win32PlatformOptions public class Win32PlatformOptions
{ {
public bool UseDeferredRendering { get; set; } = true; public bool UseDeferredRendering { get; set; } = true;
public bool AllowEglInitialization { get; set; } public bool AllowEglInitialization { get; set; } = true;
public bool? EnableMultitouch { get; set; } public bool? EnableMultitouch { get; set; }
public bool OverlayPopups { get; set; } public bool OverlayPopups { get; set; }
} }

6
src/iOS/Avalonia.iOS/Clipboard.cs

@ -22,5 +22,11 @@ namespace Avalonia.iOS
UIPasteboard.General.String = ""; UIPasteboard.General.String = "";
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SetDataObjectAsync(IDataObject data) => throw new PlatformNotSupportedException();
public Task<string[]> GetFormatsAsync() => throw new PlatformNotSupportedException();
public Task<object> GetDataAsync(string format) => throw new PlatformNotSupportedException();
} }
} }

65
tests/Avalonia.Animation.UnitTests/AnimatableTests.cs

@ -2,6 +2,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
@ -265,6 +266,70 @@ namespace Avalonia.Animation.UnitTests
} }
} }
[Fact]
public void Replacing_Transitions_During_Animation_Does_Not_Throw_KeyNotFound()
{
// Issue #4059
using (UnitTestApplication.Start(TestServices.RealStyler))
{
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles =
{
new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.TransitionsProperty,
new Transitions
{
new DoubleTransition
{
Property = Border.OpacityProperty,
Duration = TimeSpan.FromSeconds(1),
},
}),
},
},
new Style(x => x.OfType<Border>().Class("foo"))
{
Setters =
{
new Setter(Border.TransitionsProperty,
new Transitions
{
new DoubleTransition
{
Property = Border.OpacityProperty,
Duration = TimeSpan.FromSeconds(1),
},
}),
new Setter(Border.OpacityProperty, 0.0),
},
},
},
Child = target = new Border
{
Background = Brushes.Red,
}
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
target.Classes.Add("foo");
clock.Step(TimeSpan.FromSeconds(0));
clock.Step(TimeSpan.FromSeconds(0.5));
Assert.Equal(0.5, target.Opacity);
target.Classes.Remove("foo");
}
}
private static Mock<ITransition> CreateTarget() private static Mock<ITransition> CreateTarget()
{ {
return CreateTransition(Visual.OpacityProperty); return CreateTransition(Visual.OpacityProperty);

88
tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs

@ -0,0 +1,88 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Data.Core.Plugins;
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
namespace Avalonia.Benchmarks.Data
{
[MemoryDiagnoser, InProcess]
public class PropertyAccessorBenchmarks
{
private readonly InpcPropertyAccessorPlugin _inpcPlugin = new InpcPropertyAccessorPlugin();
private readonly MethodAccessorPlugin _methodPlugin = new MethodAccessorPlugin();
private readonly TestObject _targetStrongRef = new TestObject();
private readonly WeakReference<object> _targetWeakRef;
public PropertyAccessorBenchmarks()
{
_targetWeakRef = new WeakReference<object>(_targetStrongRef);
}
[Benchmark]
public void InpcAccessorMatch()
{
_inpcPlugin.Match(_targetWeakRef, nameof(TestObject.Test));
}
[Benchmark]
public void InpcAccessorStart()
{
_inpcPlugin.Start(_targetWeakRef, nameof(TestObject.Test));
}
[Benchmark]
public void MethodAccessorMatch()
{
_methodPlugin.Match(_targetWeakRef, nameof(TestObject.Execute));
}
[Benchmark]
public void MethodAccessorStart()
{
_methodPlugin.Start(_targetWeakRef, nameof(TestObject.Execute));
}
private class TestObject : INotifyPropertyChanged
{
private string _test;
public string Test
{
get => _test;
set
{
if (_test == value)
{
return;
}
_test = value;
OnPropertyChanged();
}
}
public void Execute()
{
}
public void Execute(object p0)
{
}
public void Execute(object p0, object p1)
{
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

82
tests/Avalonia.Controls.UnitTests/IndexRangeTests.cs

@ -127,6 +127,88 @@ namespace Avalonia.Controls.UnitTests
Assert.Empty(selected); Assert.Empty(selected);
} }
[Fact]
public void Intersect_Should_Remove_Items_From_Beginning()
{
var ranges = new List<IndexRange> { new IndexRange(0, 10) };
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(2, 12), removed);
Assert.Equal(2, result);
Assert.Equal(new[] { new IndexRange(2, 10) }, ranges);
Assert.Equal(new[] { new IndexRange(0, 1) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Items_From_End()
{
var ranges = new List<IndexRange> { new IndexRange(0, 10) };
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(0, 8), removed);
Assert.Equal(2, result);
Assert.Equal(new[] { new IndexRange(0, 8) }, ranges);
Assert.Equal(new[] { new IndexRange(9, 10) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Entire_Range_Start()
{
var ranges = new List<IndexRange> { new IndexRange(0, 5), new IndexRange(6, 10) };
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(6, 10), removed);
Assert.Equal(6, result);
Assert.Equal(new[] { new IndexRange(6, 10) }, ranges);
Assert.Equal(new[] { new IndexRange(0, 5) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Entire_Range_End()
{
var ranges = new List<IndexRange> { new IndexRange(0, 5), new IndexRange(6, 10) };
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(0, 4), removed);
Assert.Equal(6, result);
Assert.Equal(new[] { new IndexRange(0, 4) }, ranges);
Assert.Equal(new[] { new IndexRange(5, 10) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Entire_Range_Start_End()
{
var ranges = new List<IndexRange>
{
new IndexRange(0, 2),
new IndexRange(3, 7),
new IndexRange(8, 10)
};
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(3, 7), removed);
Assert.Equal(6, result);
Assert.Equal(new[] { new IndexRange(3, 7) }, ranges);
Assert.Equal(new[] { new IndexRange(0, 2), new IndexRange(8, 10) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Entire_And_Partial_Range_Start_End()
{
var ranges = new List<IndexRange>
{
new IndexRange(0, 2),
new IndexRange(3, 7),
new IndexRange(8, 10)
};
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(4, 6), removed);
Assert.Equal(8, result);
Assert.Equal(new[] { new IndexRange(4, 6) }, ranges);
Assert.Equal(new[] { new IndexRange(0, 3), new IndexRange(7, 10) }, removed);
}
[Fact] [Fact]
public void Remove_Should_Remove_Entire_Range() public void Remove_Should_Remove_Entire_Range()
{ {

146
tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs

@ -1512,6 +1512,47 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, raised); Assert.Equal(0, raised);
} }
[Fact]
public void Batch_Update_Selection_Is_Correct_Throughout()
{
var data = new[] { "foo", "bar", "baz", "qux" };
var target = new SelectionModel { Source = data };
var raised = 0;
using (target.Update())
{
target.Select(1);
Assert.Equal(new IndexPath(1), target.SelectedIndex);
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
target.Deselect(1);
Assert.Equal(new IndexPath(), target.SelectedIndex);
Assert.Empty(target.SelectedIndices);
Assert.Null(target.SelectedItem);
Assert.Empty(target.SelectedItems);
target.SelectRange(new IndexPath(1), new IndexPath(1));
Assert.Equal(new IndexPath(1), target.SelectedIndex);
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
target.ClearSelection();
Assert.Equal(new IndexPath(), target.SelectedIndex);
Assert.Empty(target.SelectedIndices);
Assert.Null(target.SelectedItem);
Assert.Empty(target.SelectedItems);
}
Assert.Equal(0, raised);
}
[Fact] [Fact]
public void AutoSelect_Selects_When_Enabled() public void AutoSelect_Selects_When_Enabled()
{ {
@ -1713,6 +1754,30 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, raised); Assert.Equal(0, raised);
} }
[Fact]
public void AutoSelect_Is_Applied_At_End_Of_Batch_Update()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
using (target.Update())
{
target.ClearSelection();
Assert.Equal(new IndexPath(), target.SelectedIndex);
Assert.Empty(target.SelectedIndices);
Assert.Null(target.SelectedItem);
Assert.Empty(target.SelectedItems);
}
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(new[] { new IndexPath(0) }, target.SelectedIndices);
Assert.Equal("foo", target.SelectedItem);
Assert.Equal(new[] { "foo" }, target.SelectedItems);
Assert.Equal(new IndexPath(0), target.SelectedIndex);
}
[Fact] [Fact]
public void Can_Replace_Parent_Children_Collection() public void Can_Replace_Parent_Children_Collection()
{ {
@ -1806,6 +1871,87 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, node.PropertyChangedSubscriptions); Assert.Equal(0, node.PropertyChangedSubscriptions);
} }
[Fact]
public void Setting_SelectedIndex_To_Minus_1_Clears_Selection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data };
target.SelectedIndex = new IndexPath(1);
target.SelectedIndex = new IndexPath(-1);
Assert.Empty(target.SelectedIndices);
}
[Fact]
public void Assigning_Source_With_Less_Items_Than_Previous_Clears_Selection()
{
var data = new[] { "foo", "bar", "baz", "boo", "hoo" };
var smallerData = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { RetainSelectionOnReset = true };
target.Source = data;
target.SelectedIndex = new IndexPath(4);
target.Source = smallerData;
Assert.Empty(target.SelectedIndices);
}
[Fact]
public void Initializing_Source_With_Less_Items_Than_Selection_Trims_Selection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel();
target.SelectedIndex = new IndexPath(4);
target.Source = data;
Assert.Empty(target.SelectedIndices);
}
[Fact]
public void Initializing_Source_With_Less_Items_Than_Selection_Trims_Selection_RetainSelection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { RetainSelectionOnReset = true };
target.SelectedIndex = new IndexPath(4);
target.Source = data;
Assert.Empty(target.SelectedIndices);
}
[Fact]
public void Initializing_Source_With_Less_Items_Than_Multiple_Selection_Trims_Selection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { RetainSelectionOnReset = true };
target.Select(4);
target.Select(2);
target.Source = data;
Assert.Equal(1, target.SelectedIndices.Count);
Assert.Equal(new IndexPath(2), target.SelectedIndices.First());
}
[Fact]
public void Initializing_Source_With_Less_Items_Than_Selection_Raises_SelectionChanged()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel();
var raised = 0;
target.SelectedIndex = new IndexPath(4);
target.SelectionChanged += (s, e) =>
{
if (raised == 0)
{
Assert.Equal(new[] { Path(4) }, e.DeselectedIndices);
Assert.Equal(new object[] { null }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
}
++raised;
};
target.Source = data;
Assert.Equal(2, raised);
}
private int GetSubscriberCount(AvaloniaList<object> list) private int GetSubscriberCount(AvaloniaList<object> list)
{ {
return ((INotifyCollectionChangedDebug)list).GetCollectionChangedSubscribers()?.Length ?? 0; return ((INotifyCollectionChangedDebug)list).GetCollectionChangedSubscribers()?.Length ?? 0;

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

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
@ -325,6 +327,28 @@ namespace Avalonia.Controls.UnitTests
Assert.NotEqual(dataContext, tabItem.Content); Assert.NotEqual(dataContext, tabItem.Content);
} }
[Fact]
public void Can_Have_Empty_Tab_Control()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TabControl Name='tabs' Items='{Binding Tabs}'/>
</Window>";
var loader = new Markup.Xaml.AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var tabControl = window.FindControl<TabControl>("tabs");
tabControl.DataContext = new { Tabs = new List<string>() };
window.ApplyTemplate();
Assert.Equal(0, tabControl.Items.Count());
}
}
private IControlTemplate TabControlTemplate() private IControlTemplate TabControlTemplate()
{ {
return new FuncControlTemplate<TabControl>((parent, scope) => return new FuncControlTemplate<TabControl>((parent, scope) =>

5
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -654,6 +654,11 @@ namespace Avalonia.Controls.UnitTests
public Task SetTextAsync(string text) => Task.CompletedTask; public Task SetTextAsync(string text) => Task.CompletedTask;
public Task ClearAsync() => Task.CompletedTask; public Task ClearAsync() => Task.CompletedTask;
public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
} }
} }
} }

71
tests/Avalonia.Controls.UnitTests/ToolTipTests.cs

@ -102,5 +102,76 @@ namespace Avalonia.Controls.UnitTests
Assert.True(ToolTip.GetIsOpen(target)); Assert.True(ToolTip.GetIsOpen(target));
} }
} }
[Fact]
public void Open_Class_Should_Not_Initially_Be_Added()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var toolTip = new ToolTip();
var window = new Window();
var decorator = new Decorator()
{
[ToolTip.TipProperty] = toolTip
};
window.Content = decorator;
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.Empty(toolTip.Classes);
}
}
[Fact]
public void Setting_IsOpen_Should_Add_Open_Class()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var toolTip = new ToolTip();
var window = new Window();
var decorator = new Decorator()
{
[ToolTip.TipProperty] = toolTip
};
window.Content = decorator;
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
ToolTip.SetIsOpen(decorator, true);
Assert.Equal(new[] { ":open" }, toolTip.Classes);
}
}
[Fact]
public void Clearing_IsOpen_Should_Remove_Open_Class()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var toolTip = new ToolTip();
var window = new Window();
var decorator = new Decorator()
{
[ToolTip.TipProperty] = toolTip
};
window.Content = decorator;
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
ToolTip.SetIsOpen(decorator, true);
ToolTip.SetIsOpen(decorator, false);
Assert.Empty(toolTip.Classes);
}
}
} }
} }

548
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -118,313 +118,340 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Clicking_Item_Should_Select_It() public void Clicking_Item_Should_Select_It()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
}; {
Template = CreateTreeViewTemplate(),
Items = tree,
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var item = tree[0].Children[1].Children[0]; var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
Assert.NotNull(container); Assert.NotNull(container);
_mouse.Click(container); _mouse.Click(container);
Assert.Equal(item, target.SelectedItem); Assert.Equal(item, target.SelectedItem);
Assert.True(container.IsSelected); Assert.True(container.IsSelected);
}
} }
[Fact] [Fact]
public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_It() public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_It()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree var target = new TreeView
}; {
Template = CreateTreeViewTemplate(),
Items = tree
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var item = tree[0].Children[1].Children[0]; var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
Assert.NotNull(container); Assert.NotNull(container);
target.SelectedItem = item; target.SelectedItem = item;
Assert.True(container.IsSelected); Assert.True(container.IsSelected);
_mouse.Click(container, modifiers: KeyModifiers.Control); _mouse.Click(container, modifiers: KeyModifiers.Control);
Assert.Null(target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.False(container.IsSelected); Assert.False(container.IsSelected);
}
} }
[Fact] [Fact]
public void Clicking_WithControlModifier_Not_Selected_Item_Should_Select_It() public void Clicking_WithControlModifier_Not_Selected_Item_Should_Select_It()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree var target = new TreeView
}; {
Template = CreateTreeViewTemplate(),
Items = tree
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var item1 = tree[0].Children[1].Children[0]; var item1 = tree[0].Children[1].Children[0];
var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item2 = tree[0].Children[1]; var item2 = tree[0].Children[1];
var container2 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); var container2 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
Assert.NotNull(container1); Assert.NotNull(container1);
Assert.NotNull(container2); Assert.NotNull(container2);
target.SelectedItem = item1; target.SelectedItem = item1;
Assert.True(container1.IsSelected); Assert.True(container1.IsSelected);
_mouse.Click(container2, modifiers: KeyModifiers.Control); _mouse.Click(container2, modifiers: KeyModifiers.Control);
Assert.Equal(item2, target.SelectedItem); Assert.Equal(item2, target.SelectedItem);
Assert.False(container1.IsSelected); Assert.False(container1.IsSelected);
Assert.True(container2.IsSelected); Assert.True(container2.IsSelected);
}
} }
[Fact] [Fact]
public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_And_Remove_From_SelectedItems() public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_And_Remove_From_SelectedItems()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
SelectionMode = SelectionMode.Multiple {
}; Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var rootNode = tree[0]; var rootNode = tree[0];
var item1 = rootNode.Children[0]; var item1 = rootNode.Children[0];
var item2 = rootNode.Children.Last(); var item2 = rootNode.Children.Last();
var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1); var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2); var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
ClickContainer(item1Container, KeyModifiers.Control); ClickContainer(item1Container, KeyModifiers.Control);
Assert.True(item1Container.IsSelected); Assert.True(item1Container.IsSelected);
ClickContainer(item2Container, KeyModifiers.Control); ClickContainer(item2Container, KeyModifiers.Control);
Assert.True(item2Container.IsSelected); Assert.True(item2Container.IsSelected);
Assert.Equal(new[] {item1, item2}, target.Selection.SelectedItems.OfType<Node>()); Assert.Equal(new[] { item1, item2 }, target.Selection.SelectedItems.OfType<Node>());
ClickContainer(item1Container, KeyModifiers.Control); ClickContainer(item1Container, KeyModifiers.Control);
Assert.False(item1Container.IsSelected); Assert.False(item1Container.IsSelected);
Assert.DoesNotContain(item1, target.Selection.SelectedItems.OfType<Node>()); Assert.DoesNotContain(item1, target.Selection.SelectedItems.OfType<Node>());
}
} }
[Fact] [Fact]
public void Clicking_WithShiftModifier_DownDirection_Should_Select_Range_Of_Items() public void Clicking_WithShiftModifier_DownDirection_Should_Select_Range_Of_Items()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
SelectionMode = SelectionMode.Multiple {
}; Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var rootNode = tree[0]; var rootNode = tree[0];
var from = rootNode.Children[0]; var from = rootNode.Children[0];
var to = rootNode.Children.Last(); var to = rootNode.Children.Last();
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(fromContainer, KeyModifiers.None);
Assert.True(fromContainer.IsSelected); Assert.True(fromContainer.IsSelected);
ClickContainer(toContainer, KeyModifiers.Shift); ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode); AssertChildrenSelected(target, rootNode);
}
} }
[Fact] [Fact]
public void Clicking_WithShiftModifier_UpDirection_Should_Select_Range_Of_Items() public void Clicking_WithShiftModifier_UpDirection_Should_Select_Range_Of_Items()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
SelectionMode = SelectionMode.Multiple {
}; Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var rootNode = tree[0]; var rootNode = tree[0];
var from = rootNode.Children.Last(); var from = rootNode.Children.Last();
var to = rootNode.Children[0]; var to = rootNode.Children[0];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(fromContainer, KeyModifiers.None);
Assert.True(fromContainer.IsSelected); Assert.True(fromContainer.IsSelected);
ClickContainer(toContainer, KeyModifiers.Shift); ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode); AssertChildrenSelected(target, rootNode);
}
} }
[Fact] [Fact]
public void Clicking_First_Item_Of_SelectedItems_Should_Select_Only_It() public void Clicking_First_Item_Of_SelectedItems_Should_Select_Only_It()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
SelectionMode = SelectionMode.Multiple {
}; Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var rootNode = tree[0]; var rootNode = tree[0];
var from = rootNode.Children.Last(); var from = rootNode.Children.Last();
var to = rootNode.Children[0]; var to = rootNode.Children[0];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(toContainer, KeyModifiers.Shift); ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode); AssertChildrenSelected(target, rootNode);
ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(fromContainer, KeyModifiers.None);
Assert.True(fromContainer.IsSelected); Assert.True(fromContainer.IsSelected);
foreach (var child in rootNode.Children) foreach (var child in rootNode.Children)
{
if (child == from)
{ {
continue; if (child == from)
} {
continue;
}
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child); var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child);
Assert.False(container.IsSelected); Assert.False(container.IsSelected);
}
} }
} }
[Fact] [Fact]
public void Setting_SelectedItem_Should_Set_Container_Selected() public void Setting_SelectedItem_Should_Set_Container_Selected()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
}; {
Template = CreateTreeViewTemplate(),
Items = tree,
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var item = tree[0].Children[1].Children[0]; var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item); var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
Assert.NotNull(container); Assert.NotNull(container);
target.SelectedItem = item; target.SelectedItem = item;
Assert.True(container.IsSelected); Assert.True(container.IsSelected);
}
} }
[Fact] [Fact]
public void Setting_SelectedItem_Should_Raise_SelectedItemChanged_Event() public void Setting_SelectedItem_Should_Raise_SelectedItemChanged_Event()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
}; {
Template = CreateTreeViewTemplate(),
Items = tree,
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
ExpandAll(target); ExpandAll(target);
var item = tree[0].Children[1].Children[0]; var item = tree[0].Children[1].Children[0];
var called = false; var called = false;
target.SelectionChanged += (s, e) => target.SelectionChanged += (s, e) =>
{ {
Assert.Empty(e.RemovedItems); Assert.Empty(e.RemovedItems);
Assert.Equal(1, e.AddedItems.Count); Assert.Equal(1, e.AddedItems.Count);
Assert.Same(item, e.AddedItems[0]); Assert.Same(item, e.AddedItems[0]);
called = true; called = true;
}; };
target.SelectedItem = item; target.SelectedItem = item;
Assert.True(called); Assert.True(called);
}
} }
[Fact] [Fact]
@ -564,7 +591,7 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node() public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node()
{ {
using (UnitTestApplication.Start(TestServices.RealFocus)) using (Application())
{ {
var focus = FocusManager.Instance; var focus = FocusManager.Instance;
var navigation = AvaloniaLocator.Current.GetService<IKeyboardNavigationHandler>(); var navigation = AvaloniaLocator.Current.GetService<IKeyboardNavigationHandler>();
@ -647,7 +674,7 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes() public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes()
{ {
using (UnitTestApplication.Start()) using (Application())
{ {
var tree = CreateTestTreeData(); var tree = CreateTestTreeData();
var target = new TreeView var target = new TreeView
@ -694,7 +721,7 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes() public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes()
{ {
using (UnitTestApplication.Start()) using (Application())
{ {
var tree = CreateTestTreeData(); var tree = CreateTestTreeData();
var target = new TreeView var target = new TreeView
@ -768,97 +795,106 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
SelectionMode = SelectionMode.Multiple, {
}; Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
var rootNode = tree[0]; var rootNode = tree[0];
var to = rootNode.Children[0]; var to = rootNode.Children[0];
var then = rootNode.Children[1]; var then = rootNode.Children[1];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode); var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
var thenContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(then); var thenContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(then);
ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(toContainer, KeyModifiers.Shift); ClickContainer(toContainer, KeyModifiers.Shift);
Assert.Equal(2, target.Selection.SelectedItems.Count); Assert.Equal(2, target.Selection.SelectedItems.Count);
_mouse.Click(thenContainer, MouseButton.Right); _mouse.Click(thenContainer, MouseButton.Right);
Assert.Equal(1, target.Selection.SelectedItems.Count); Assert.Equal(1, target.Selection.SelectedItems.Count);
}
} }
[Fact] [Fact]
public void Shift_Right_Click_Should_Not_Select_Multiple() public void Shift_Right_Click_Should_Not_Select_Multiple()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
SelectionMode = SelectionMode.Multiple, {
}; Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
var rootNode = tree[0]; var rootNode = tree[0];
var from = rootNode.Children[0]; var from = rootNode.Children[0];
var to = rootNode.Children[1]; var to = rootNode.Children[1];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
_mouse.Click(fromContainer); _mouse.Click(fromContainer);
_mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Shift); _mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Shift);
Assert.Equal(1, target.Selection.SelectedItems.Count); Assert.Equal(1, target.Selection.SelectedItems.Count);
}
} }
[Fact] [Fact]
public void Ctrl_Right_Click_Should_Not_Select_Multiple() public void Ctrl_Right_Click_Should_Not_Select_Multiple()
{ {
var tree = CreateTestTreeData(); using (Application())
var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), var tree = CreateTestTreeData();
Items = tree, var target = new TreeView
SelectionMode = SelectionMode.Multiple, {
}; Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target); CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]); target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
var rootNode = tree[0]; var rootNode = tree[0];
var from = rootNode.Children[0]; var from = rootNode.Children[0];
var to = rootNode.Children[1]; var to = rootNode.Children[1];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from); var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to); var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
_mouse.Click(fromContainer); _mouse.Click(fromContainer);
_mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Control); _mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Control);
Assert.Equal(1, target.Selection.SelectedItems.Count); Assert.Equal(1, target.Selection.SelectedItems.Count);
}
} }
[Fact] [Fact]
@ -944,7 +980,7 @@ namespace Avalonia.Controls.UnitTests
public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection() public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection()
{ {
/// Issue #2980. /// Issue #2980.
using (UnitTestApplication.Start(TestServices.RealStyler)) using (Application())
{ {
var target = new DerivedTreeView var target = new DerivedTreeView
{ {
@ -1183,12 +1219,12 @@ namespace Avalonia.Controls.UnitTests
} }
} }
void ClickContainer(IControl container, KeyModifiers modifiers) private void ClickContainer(IControl container, KeyModifiers modifiers)
{ {
_mouse.Click(container, modifiers: modifiers); _mouse.Click(container, modifiers: modifiers);
} }
void AssertChildrenSelected(TreeView treeView, Node rootNode) private void AssertChildrenSelected(TreeView treeView, Node rootNode)
{ {
foreach (var child in rootNode.Children) foreach (var child in rootNode.Children)
{ {
@ -1198,6 +1234,16 @@ namespace Avalonia.Controls.UnitTests
} }
} }
private IDisposable Application()
{
return UnitTestApplication.Start(
TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager()));
}
private class Node : NotifyingBase private class Node : NotifyingBase
{ {
private IAvaloniaList<Node> _children; private IAvaloniaList<Node> _children;

44
tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs

@ -113,6 +113,50 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("(null)", target.Text); Assert.Equal("(null)", target.Text);
} }
[Fact]
public void Should_Pass_UnsetValue_To_Converter_For_Broken_Binding()
{
var source = new { A = 1, B = 2, C = 3 };
var target = new TextBlock { DataContext = source };
var binding = new MultiBinding
{
Converter = new ConcatConverter(),
Bindings = new[]
{
new Binding { Path = "A" },
new Binding { Path = "B" },
new Binding { Path = "Missing" },
},
};
target.Bind(TextBlock.TextProperty, binding);
Assert.Equal("1,2,(unset)", target.Text);
}
[Fact]
public void Should_Pass_FallbackValue_To_Converter_For_Broken_Binding()
{
var source = new { A = 1, B = 2, C = 3 };
var target = new TextBlock { DataContext = source };
var binding = new MultiBinding
{
Converter = new ConcatConverter(),
Bindings = new[]
{
new Binding { Path = "A" },
new Binding { Path = "B" },
new Binding { Path = "Missing", FallbackValue = "Fallback" },
},
};
target.Bind(TextBlock.TextProperty, binding);
Assert.Equal("1,2,Fallback", target.Text);
}
private class ConcatConverter : IMultiValueConverter private class ConcatConverter : IMultiValueConverter
{ {
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture) public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)

53
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs

@ -1,6 +1,13 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit; using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Data namespace Avalonia.Markup.Xaml.UnitTests.Data
@ -54,5 +61,51 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Assert.Equal("bar", textBlock.Text); Assert.Equal("bar", textBlock.Text);
} }
} }
[Fact]
public void MultiBinding_TemplatedParent_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Data;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TextBox Name='textBox' Text='Foo' Watermark='Bar'>
<TextBox.Template>
<ControlTemplate>
<TextPresenter Name='PART_TextPresenter'>
<TextPresenter.Text>
<MultiBinding Converter='{x:Static local:ConcatConverter.Instance}'>
<Binding RelativeSource='{RelativeSource TemplatedParent}' Path='Text'/>
<Binding RelativeSource='{RelativeSource TemplatedParent}' Path='Watermark'/>
</MultiBinding>
</TextPresenter.Text>
</TextPresenter>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBox = window.FindControl<TextBox>("textBox");
window.ApplyTemplate();
textBox.ApplyTemplate();
var target = (TextPresenter)textBox.GetVisualChildren().Single();
Assert.Equal("Foo,Bar", target.Text);
}
}
}
public class ConcatConverter : IMultiValueConverter
{
public static ConcatConverter Instance { get; } = new ConcatConverter();
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
return string.Join(",", values);
}
} }
} }

Loading…
Cancel
Save