Browse Source

Merge branch 'master' into linux-fb-evdev-touch

pull/5894/head
Nikita Tsukanov 5 years ago
committed by GitHub
parent
commit
2daadd3d2d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/PULL_REQUEST_TEMPLATE.md
  2. 1
      native/Avalonia.Native/src/OSX/window.h
  3. 19
      native/Avalonia.Native/src/OSX/window.mm
  4. 8
      samples/ControlCatalog/App.xaml.cs
  5. 7
      samples/ControlCatalog/Pages/ViewboxPage.xaml
  6. 16
      samples/ControlCatalog/Pages/ViewboxPage.xaml.cs
  7. 7
      samples/RenderDemo/Pages/AnimationsPage.xaml
  8. 65
      samples/RenderDemo/Pages/TransitionsPage.xaml
  9. 10
      src/Avalonia.Animation/Transitions/DoubleTransition.cs
  10. 7
      src/Avalonia.Animation/Transitions/FloatTransition.cs
  11. 7
      src/Avalonia.Animation/Transitions/IntegerTransition.cs
  12. 6
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  13. 3
      src/Avalonia.Controls/ApiCompatBaseline.txt
  14. 2
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  15. 21
      src/Avalonia.Controls/Expander.cs
  16. 2
      src/Avalonia.Controls/Grid.cs
  17. 18
      src/Avalonia.Controls/Primitives/ScrollBarVisibility.cs
  18. 3
      src/Avalonia.Controls/RadioButton.cs
  19. 3
      src/Avalonia.Controls/RelativePanel.cs
  20. 3
      src/Avalonia.Controls/RepeatButton.cs
  21. 15
      src/Avalonia.Controls/TextBox.cs
  22. 8
      src/Avalonia.Controls/Viewbox.cs
  23. 87
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml
  24. 25
      src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs
  25. 25
      src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs
  26. 4
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  27. 130
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
  28. 313
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  29. 109
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
  30. 210
      src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
  31. 128
      src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs
  32. 1
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
  33. 3
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
  34. 19
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
  35. 22
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  36. 9
      src/Avalonia.Layout/WrapLayout/WrapLayout.cs
  37. 4
      src/Avalonia.Native/WindowImplBase.cs
  38. 39
      src/Avalonia.Styling/Controls/Classes.cs
  39. 8
      src/Avalonia.Themes.Fluent/Accents/Base.xaml
  40. 317
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  41. 10
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  42. 23
      src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs
  43. 21
      src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
  44. 10
      src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
  45. 10
      src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
  46. 10
      src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
  47. 10
      src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
  48. 5
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  49. 45
      src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs
  50. 6
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  51. 44
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  52. 32
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  53. 5
      src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs
  54. 2
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  55. 6
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  56. 25
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  57. 65
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  58. 22
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  59. 17
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
  60. 22
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  61. 21
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  62. 22
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  63. 17
      tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs
  64. 43
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
  65. 37
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
  66. 22
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

2
.github/PULL_REQUEST_TEMPLATE.md

@ -18,7 +18,7 @@
- [ ] Added unit tests (if possible)?
- [ ] Added XML documentation to any related classes?
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Documentation with user documentation
## Breaking changes
<!--- List any breaking changes here. When the PR is merged please add an entry to https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes -->

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

@ -34,6 +34,7 @@ class WindowBaseImpl;
-(double) getScaling;
-(double) getExtendedTitleBarHeight;
-(void) setIsExtended:(bool)value;
-(void) updateShadow;
@end
struct INSWindowHolder

19
native/Avalonia.Native/src/OSX/window.mm

@ -124,7 +124,11 @@ public:
[Window setTitle:_lastTitle];
_shown = true;
dispatch_async(dispatch_get_main_queue(), ^{
[Window updateShadow];
});
return S_OK;
}
}
@ -1838,6 +1842,19 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
double _lastScaling;
}
- (void)updateShadow
{
// Common problem in Cocoa where [invalidateShadow] does work,
// This hack forces Cocoa to invalidate the shadow.
NSRect frame = [self frame];
NSRect updatedFrame = NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width + 1.0, frame.size.height + 1.0);
[self setFrame:updatedFrame display:YES];
[self setFrame:frame display:YES];
[self invalidateShadow];
}
-(void) setIsExtended:(bool)value;
{
_isExtended = value;

8
samples/ControlCatalog/App.xaml.cs

@ -39,6 +39,10 @@ namespace ControlCatalog
public static Styles DefaultLight = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
@ -60,6 +64,10 @@ namespace ControlCatalog
public static Styles DefaultDark = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")

7
samples/ControlCatalog/Pages/ViewboxPage.xaml

@ -12,8 +12,9 @@
<Border HorizontalAlignment="Center" Grid.Column="0" BorderThickness="1" BorderBrush="Orange" Width="200" Height="200">
<Border VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0" BorderThickness="1" BorderBrush="CornflowerBlue" Width="{Binding #WidthSlider.Value}" Height="{Binding #HeightSlider.Value}" >
<Viewbox x:Name="Viewbox"
StretchDirection="{Binding #StretchDirectionSelector.SelectedItem}">
<Viewbox
Stretch="{Binding #StretchSelector.SelectedItem}"
StretchDirection="{Binding #StretchDirectionSelector.SelectedItem}">
<Ellipse Width="50" Height="50" Fill="CornflowerBlue" />
</Viewbox>
</Border>
@ -25,7 +26,7 @@
<TextBlock Text="Height" />
<Slider Minimum="10" Maximum="200" Value="100" x:Name="HeightSlider" TickFrequency="25" TickPlacement="TopLeft" />
<TextBlock Text="Stretch" />
<ComboBox x:Name="StretchSelector" HorizontalAlignment="Stretch" Margin="0,0,0,2" SelectionChanged="StretchSelector_OnSelectionChanged" />
<ComboBox x:Name="StretchSelector" HorizontalAlignment="Stretch" Margin="0,0,0,2" />
<TextBlock Text="Stretch Direction" />
<ComboBox x:Name="StretchDirectionSelector" HorizontalAlignment="Stretch" />
</StackPanel>

16
samples/ControlCatalog/Pages/ViewboxPage.xaml.cs

@ -6,23 +6,18 @@ namespace ControlCatalog.Pages
{
public class ViewboxPage : UserControl
{
private readonly Viewbox _viewbox;
private readonly ComboBox _stretchSelector;
public ViewboxPage()
{
InitializeComponent();
_viewbox = this.FindControl<Viewbox>("Viewbox");
_stretchSelector = this.FindControl<ComboBox>("StretchSelector");
var stretchSelector = this.FindControl<ComboBox>("StretchSelector");
_stretchSelector.Items = new[]
stretchSelector.Items = new[]
{
Stretch.Uniform, Stretch.UniformToFill, Stretch.Fill, Stretch.None
};
_stretchSelector.SelectedIndex = 0;
stretchSelector.SelectedIndex = 0;
var stretchDirectionSelector = this.FindControl<ComboBox>("StretchDirectionSelector");
@ -38,10 +33,5 @@ namespace ControlCatalog.Pages
{
AvaloniaXamlLoader.Load(this);
}
private void StretchSelector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
_viewbox.Stretch = (Stretch) _stretchSelector.SelectedItem!;
}
}
}

7
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -1,7 +1,8 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.AnimationsPage">
x:Class="RenderDemo.Pages.AnimationsPage"
MaxWidth="600">
<UserControl.Styles>
<Styles>
<Styles.Resources>
@ -167,8 +168,8 @@
<StackPanel.Clock>
<Clock />
</StackPanel.Clock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
<TextBlock VerticalAlignment="Center">Hover to activate Keyframe Animations.</TextBlock>
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
</StackPanel>
<WrapPanel ClipToBounds="False">

65
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -1,7 +1,8 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.TransitionsPage">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.Pages.TransitionsPage"
MaxWidth="600">
<UserControl.Styles>
<Styles>
<Styles.Resources>
@ -90,6 +91,56 @@
<Setter Property="RenderTransform" Value="none" />
</Style>
<Style Selector="Border.Rect7">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Height" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style>
<Style Selector="Border.Rect7:pointerover">
<Setter Property="Height" Value="50" />
</Style>
<Style Selector="Border.Rect8">
<Setter Property="Transitions">
<Transitions>
<CornerRadiusTransition Property="CornerRadius" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style>
<Style Selector="Border.Rect8:pointerover">
<Setter Property="CornerRadius" Value="50" />
</Style>
<Style Selector="Border.Rect9">
<Setter Property="Transitions">
<Transitions>
<ThicknessTransition Property="Padding" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style>
<Style Selector="Border.Rect9:pointerover">
<Setter Property="Padding" Value="10" />
</Style>
<Style Selector="Border.Shadow">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BoxShadow" Value="inset 0 0 0 2 Red, -15 -15 Green"/>
<Setter Property="Transitions">
<Transitions>
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style>
<Style Selector="Border.Shadow:pointerover">
<Setter Property="BoxShadow" Value="inset 30 30 20 30 Green, 20 40 20 10 Red"/>
</Style>
</Styles>
</UserControl.Styles>
@ -98,8 +149,8 @@
<StackPanel.Clock>
<Clock />
</StackPanel.Clock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock VerticalAlignment="Center">Hover to activate Transform Keyframe Animations.</TextBlock>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
<TextBlock VerticalAlignment="Center">Hover to activate Transitions.</TextBlock>
<Button Content="{Binding PlayStateText}" Command="{Binding TogglePlayState}" Click="ToggleClock" />
</StackPanel>
<WrapPanel ClipToBounds="False">
@ -109,6 +160,12 @@
<Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Orange"/>
<Border Classes="Test Rect7" Background="Gold"/>
<Border Classes="Test Rect8" Background="Gray" />
<Border Classes="Test Rect9" Background="Red" />
<Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" />
<Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" />
</WrapPanel>
</StackPanel>
</Grid>

10
src/Avalonia.Animation/Transitions/DoubleTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class DoubleTransition : Transition<double>
{
private static readonly DoubleAnimator s_animator = new DoubleAnimator();
/// <inheritdocs/>
public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

7
src/Avalonia.Animation/Transitions/FloatTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,12 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class FloatTransition : Transition<float>
{
private static readonly FloatAnimator s_animator = new FloatAnimator();
/// <inheritdocs/>
public override IObservable<float> DoTransition(IObservable<double> progress, float oldValue, float newValue)
{
var delta = newValue - oldValue;
return progress
.Select(p => (float)Easing.Ease(p) * delta + oldValue);
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

7
src/Avalonia.Animation/Transitions/IntegerTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,12 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class IntegerTransition : Transition<int>
{
private static readonly Int32Animator s_animator = new Int32Animator();
/// <inheritdocs/>
public override IObservable<int> DoTransition(IObservable<double> progress, int oldValue, int newValue)
{
var delta = newValue - oldValue;
return progress
.Select(p => (int)(Easing.Ease(p) * delta + oldValue));
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

6
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -535,7 +535,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DataGrid, int>(
nameof(SelectedIndex),
o => o.SelectedIndex,
(o, v) => o.SelectedIndex = v);
(o, v) => o.SelectedIndex = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets the index of the current selection.
@ -553,7 +554,8 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect<DataGrid, object>(
nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v);
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Gets or sets the data item corresponding to the selected row.

3
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -4,10 +4,11 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 11
Total Issues: 12

2
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -8,7 +8,7 @@ using System.Globalization;
namespace Avalonia.Controls
{
/// <summary>
/// A control to allow the user to select a time
/// A control to allow the user to select a time.
/// </summary>
[PseudoClasses(":hasnotime")]
public class TimePicker : TemplatedControl

21
src/Avalonia.Controls/Expander.cs

@ -4,14 +4,35 @@ using Avalonia.Controls.Primitives;
namespace Avalonia.Controls
{
/// <summary>
/// Direction in which an <see cref="Expander"/> control opens.
/// </summary>
public enum ExpandDirection
{
/// <summary>
/// Opens down.
/// </summary>
Down,
/// <summary>
/// Opens up.
/// </summary>
Up,
/// <summary>
/// Opens left.
/// </summary>
Left,
/// <summary>
/// Opens right.
/// </summary>
Right
}
/// <summary>
/// A control with a header that has a collapsible content section.
/// </summary>
[PseudoClasses(":expanded", ":up", ":down", ":left", ":right")]
public class Expander : HeaderedContentControl
{

2
src/Avalonia.Controls/Grid.cs

@ -17,7 +17,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls
{
/// <summary>
/// Grid
/// Defines a flexible grid area that consists of columns and rows.
/// </summary>
public class Grid : Panel
{

18
src/Avalonia.Controls/Primitives/ScrollBarVisibility.cs

@ -1,10 +1,28 @@
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Specifies the visibility of a <see cref="ScrollBar"/> for scrollable content.
/// </summary>
public enum ScrollBarVisibility
{
/// <summary>
/// No scrollbars and no scrolling in this dimension.
/// </summary>
Disabled,
/// <summary>
/// The scrollbar should be visible only if there is more content than fits in the viewport.
/// </summary>
Auto,
/// <summary>
/// The scrollbar should never be visible. No space should ever be reserved for the scrollbar.
/// </summary>
Hidden,
/// <summary>
/// The scrollbar should always be visible. Space should always be reserved for the scrollbar.
/// </summary>
Visible,
}
}

3
src/Avalonia.Controls/RadioButton.cs

@ -8,6 +8,9 @@ using Avalonia.VisualTree;
namespace Avalonia.Controls
{
/// <summary>
/// Represents a button that allows a user to select a single option from a group of options.
/// </summary>
public class RadioButton : ToggleButton
{
private class RadioButtonGroupManager

3
src/Avalonia.Controls/RelativePanel.cs

@ -8,6 +8,9 @@ using Avalonia.Layout;
namespace Avalonia.Controls
{
/// <summary>
/// Defines an area within which you can position and align child objects in relation to each other or the parent panel.
/// </summary>
public partial class RelativePanel : Panel
{
private readonly Graph _childGraph;

3
src/Avalonia.Controls/RepeatButton.cs

@ -4,6 +4,9 @@ using Avalonia.Threading;
namespace Avalonia.Controls
{
/// <summary>
/// Represents a control that raises its <see cref="Button.Click"/> event repeatedly when it is pressed and held.
/// </summary>
public class RepeatButton : Button
{
/// <summary>

15
src/Avalonia.Controls/TextBox.cs

@ -17,6 +17,9 @@ using Avalonia.Controls.Metadata;
namespace Avalonia.Controls
{
/// <summary>
/// Represents a control that can be used to display or edit unformatted text.
/// </summary>
[PseudoClasses(":empty")]
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
{
@ -260,7 +263,11 @@ namespace Avalonia.Controls
set
{
value = CoerceCaretIndex(value);
SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
if (changed)
{
UpdateCommandStates();
}
if (SelectionStart == SelectionEnd)
{
CaretIndex = SelectionStart;
@ -278,7 +285,11 @@ namespace Avalonia.Controls
set
{
value = CoerceCaretIndex(value);
SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
if (changed)
{
UpdateCommandStates();
}
if (SelectionStart == SelectionEnd)
{
CaretIndex = SelectionEnd;

8
src/Avalonia.Controls/Viewbox.cs

@ -11,7 +11,7 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Stretch"/> property.
/// </summary>
public static readonly AvaloniaProperty<Stretch> StretchProperty =
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
/// <summary>
@ -20,8 +20,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
AvaloniaProperty.Register<Viewbox, StretchDirection>(nameof(StretchDirection), StretchDirection.Both);
private Stretch _stretch = Stretch.Uniform;
static Viewbox()
{
ClipToBoundsProperty.OverrideDefaultValue<Viewbox>(true);
@ -34,8 +32,8 @@ namespace Avalonia.Controls
/// </summary>
public Stretch Stretch
{
get => _stretch;
set => SetAndRaise(StretchProperty, ref _stretch, value);
get => GetValue(StretchProperty);
set => SetValue(StretchProperty, value);
}
/// <summary>

87
src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml

@ -0,0 +1,87 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Avalonia.Diagnostics.Controls">
<Styles.Resources>
<SolidColorBrush x:Key="HighlightBorderBrush" Color="CornflowerBlue" />
<SolidColorBrush x:Key="ThicknessBorderBrush" Color="#666666" />
</Styles.Resources>
<Style Selector="controls|ThicknessEditor">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{StaticResource ThicknessBorderBrush}" />
<Setter Property="Template">
<ControlTemplate>
<Panel>
<Rectangle x:Name="PART_Background"
Classes.no-content-pointerover="{Binding !#PART_ContentPresenter.IsPointerOver}" />
<Border
x:Name="PART_Border"
Classes.no-content-pointerover="{Binding !#PART_ContentPresenter.IsPointerOver}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*,Auto">
<Grid.Styles>
<Style Selector="TextBox.thickness-edit">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)"
Value="Disabled" />
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Disabled" />
<Setter Property="IsVisible"
Value="{Binding $parent[controls:ThicknessEditor].IsPresent}" />
</Style>
</Grid.Styles>
<TextBlock IsVisible="{TemplateBinding IsPresent}" Margin="4,0,0,0"
Text="{TemplateBinding Header}" Grid.Row="0" Grid.Column="0"
Grid.ColumnSpan="2" />
<TextBox Grid.Row="1" Grid.Column="0"
Text="{Binding Left, RelativeSource={RelativeSource TemplatedParent}}"
Classes="thickness-edit" />
<TextBox x:Name="Right" Grid.Row="0" Grid.Column="1"
Text="{Binding Top, RelativeSource={RelativeSource TemplatedParent}}"
Classes="thickness-edit" />
<TextBox Grid.Row="1" Grid.Column="2"
Text="{Binding Right, RelativeSource={RelativeSource TemplatedParent}}"
Classes="thickness-edit" />
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding Bottom, RelativeSource={RelativeSource TemplatedParent}}"
Classes="thickness-edit" />
<ContentPresenter Grid.Row="1" Grid.Column="1"
Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Grid>
</Border>
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="controls|ThicknessEditor[IsPresent=False]">
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="controls|ThicknessEditor /template/ Rectangle#PART_Background">
<Setter Property="Fill" Value="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
<Style Selector="controls|ThicknessEditor:pointerover /template/ Rectangle#PART_Background.no-content-pointerover">
<Setter Property="Fill" Value="{Binding Highlight, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
<Style Selector="controls|ThicknessEditor:pointerover /template/ Border#PART_Border.no-content-pointerover">
<Setter Property="BorderBrush" Value="{StaticResource HighlightBorderBrush}" />
</Style>
</Styles>

25
src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs → src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs

@ -1,21 +1,9 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace Avalonia.Diagnostics.Views
namespace Avalonia.Diagnostics.Controls
{
internal static class Converters
{
public static IValueConverter HasConstraintConverter =
new FuncValueConverter<object, TextDecorationCollection>(ConvertToDecoration);
private static TextDecorationCollection ConvertToDecoration(object arg)
{
return arg != null ? TextDecorations.Underline : null;
}
}
internal class ThicknessEditor : ContentControl
{
public static readonly DirectProperty<ThicknessEditor, Thickness> ThicknessProperty =
@ -44,7 +32,9 @@ namespace Avalonia.Diagnostics.Views
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Bottom), o => o.Bottom,
(o, v) => o.Bottom = v);
public static readonly StyledProperty<IBrush> HighlightProperty =
AvaloniaProperty.Register<ThicknessEditor, IBrush>(nameof(Highlight));
private Thickness _thickness;
private string _header;
private bool _isPresent = true;
@ -52,7 +42,6 @@ namespace Avalonia.Diagnostics.Views
private double _top;
private double _right;
private double _bottom;
private bool _isUpdatingThickness;
public Thickness Thickness
@ -97,6 +86,12 @@ namespace Avalonia.Diagnostics.Views
set => SetAndRaise(BottomProperty, ref _bottom, value);
}
public IBrush Highlight
{
get => GetValue(HighlightProperty);
set => SetValue(HighlightProperty, value);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);

25
src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs

@ -0,0 +1,25 @@
using System;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
namespace Avalonia.Diagnostics.Converters
{
internal class EnumToCheckedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Equals(value, parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool isChecked && isChecked)
{
return parameter;
}
return BindingOperations.DoNothing;
}
}
}

4
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -19,8 +19,8 @@ namespace Avalonia.Diagnostics
public bool ShowAsChildWindow { get; set; } = true;
/// <summary>
/// Gets or sets the initial size of the DevTools window. The default value is 1024x512.
/// Gets or sets the initial size of the DevTools window. The default value is 1280x720.
/// </summary>
public Size Size { get; set; } = new Size(1024, 512);
public Size Size { get; set; } = new Size(1280, 720);
}
}

130
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs

@ -1,4 +1,6 @@
using System;
using System.ComponentModel;
using System.Text;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.VisualTree;
@ -8,27 +10,58 @@ namespace Avalonia.Diagnostics.ViewModels
internal class ControlLayoutViewModel : ViewModelBase
{
private readonly IVisual _control;
private Thickness _marginThickness;
private Thickness _borderThickness;
private Thickness _paddingThickness;
private double _width;
private double _height;
private string _widthConstraint;
private string _heightConstraint;
private HorizontalAlignment _horizontalAlignment;
private Thickness _marginThickness;
private Thickness _paddingThickness;
private bool _updatingFromControl;
private VerticalAlignment _verticalAlignment;
private double _width;
private string _widthConstraint;
public ControlLayoutViewModel(IVisual control)
{
_control = control;
HasPadding = AvaloniaPropertyRegistry.Instance.IsRegistered(control, Decorator.PaddingProperty);
HasBorder = AvaloniaPropertyRegistry.Instance.IsRegistered(control, Border.BorderThicknessProperty);
if (control is AvaloniaObject ao)
{
MarginThickness = ao.GetValue(Layoutable.MarginProperty);
if (HasPadding)
{
PaddingThickness = ao.GetValue(Decorator.PaddingProperty);
}
if (HasBorder)
{
BorderThickness = ao.GetValue(Border.BorderThicknessProperty);
}
HorizontalAlignment = ao.GetValue(Layoutable.HorizontalAlignmentProperty);
VerticalAlignment = ao.GetValue(Layoutable.VerticalAlignmentProperty);
}
UpdateSize();
UpdateSizeConstraints();
}
public Thickness MarginThickness
{
get => _marginThickness;
set => RaiseAndSetIfChanged(ref _marginThickness, value);
}
public Thickness BorderThickness
{
get => _borderThickness;
set => RaiseAndSetIfChanged(ref _borderThickness, value);
}
public Thickness PaddingThickness
{
get => _paddingThickness;
@ -59,35 +92,21 @@ namespace Avalonia.Diagnostics.ViewModels
private set => RaiseAndSetIfChanged(ref _heightConstraint, value);
}
public bool HasPadding { get; }
public bool HasBorder { get; }
public ControlLayoutViewModel(IVisual control)
public HorizontalAlignment HorizontalAlignment
{
_control = control;
HasPadding = AvaloniaPropertyRegistry.Instance.IsRegistered(control, Decorator.PaddingProperty);
HasBorder = AvaloniaPropertyRegistry.Instance.IsRegistered(control, Border.BorderThicknessProperty);
if (control is AvaloniaObject ao)
{
MarginThickness = ao.GetValue(Layoutable.MarginProperty);
get => _horizontalAlignment;
private set => RaiseAndSetIfChanged(ref _horizontalAlignment, value);
}
if (HasPadding)
{
PaddingThickness = ao.GetValue(Decorator.PaddingProperty);
}
public VerticalAlignment VerticalAlignment
{
get => _verticalAlignment;
private set => RaiseAndSetIfChanged(ref _verticalAlignment, value);
}
if (HasBorder)
{
BorderThickness = ao.GetValue(Border.BorderThicknessProperty);
}
}
public bool HasPadding { get; }
UpdateSize();
UpdateSizeConstraints();
}
public bool HasBorder { get; }
private void UpdateSizeConstraints()
{
@ -95,12 +114,27 @@ namespace Avalonia.Diagnostics.ViewModels
{
string CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty)
{
if (ao.IsSet(minProperty) || ao.IsSet(maxProperty))
bool hasMin = ao.IsSet(minProperty);
bool hasMax = ao.IsSet(maxProperty);
if (hasMin || hasMax)
{
var minValue = ao.GetValue(minProperty);
var maxValue = ao.GetValue(maxProperty);
var builder = new StringBuilder();
if (hasMin)
{
var minValue = ao.GetValue(minProperty);
builder.AppendFormat("Min: {0}", Math.Round(minValue, 2));
builder.AppendLine();
}
return $"{minValue} < size < {maxValue}";
if (hasMax)
{
var maxValue = ao.GetValue(maxProperty);
builder.AppendFormat("Max: {0}", Math.Round(maxValue, 2));
}
return builder.ToString();
}
return null;
@ -134,6 +168,14 @@ namespace Avalonia.Diagnostics.ViewModels
{
ao.SetValue(Border.BorderThicknessProperty, BorderThickness);
}
else if (e.PropertyName == nameof(HorizontalAlignment))
{
ao.SetValue(Layoutable.HorizontalAlignmentProperty, HorizontalAlignment);
}
else if (e.PropertyName == nameof(VerticalAlignment))
{
ao.SetValue(Layoutable.VerticalAlignmentProperty, VerticalAlignment);
}
}
}
@ -154,15 +196,15 @@ namespace Avalonia.Diagnostics.ViewModels
if (e.Property == Layoutable.MarginProperty)
{
MarginThickness = ao.GetValue(Layoutable.MarginProperty);
}
}
else if (e.Property == Decorator.PaddingProperty)
{
PaddingThickness = ao.GetValue(Decorator.PaddingProperty);
}
}
else if (e.Property == Border.BorderThicknessProperty)
{
BorderThickness = ao.GetValue(Border.BorderThicknessProperty);
}
}
else if (e.Property == Layoutable.MinWidthProperty ||
e.Property == Layoutable.MaxWidthProperty ||
e.Property == Layoutable.MinHeightProperty ||
@ -170,6 +212,14 @@ namespace Avalonia.Diagnostics.ViewModels
{
UpdateSizeConstraints();
}
else if (e.Property == Layoutable.HorizontalAlignmentProperty)
{
HorizontalAlignment = ao.GetValue(Layoutable.HorizontalAlignmentProperty);
}
else if (e.Property == Layoutable.VerticalAlignmentProperty)
{
VerticalAlignment = ao.GetValue(Layoutable.VerticalAlignmentProperty);
}
}
}
}
@ -183,8 +233,8 @@ namespace Avalonia.Diagnostics.ViewModels
{
var size = _control.Bounds;
Width = size.Width;
Height = size.Height;
Width = Math.Round(size.Width, 2);
Height = Math.Round(size.Height, 2);
}
}
}

313
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -7,66 +7,10 @@
x:Name="Main">
<UserControl.Resources>
<SolidColorBrush x:Key="ThicknessBorderBrush" Color="#666666" />
<SolidColorBrush x:Key="SizeGuidelineBrush" Color="#333333" />
<SolidColorBrush x:Key="MarginBackgroundBrush" Color="#D78965" />
<SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" />
<SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" />
<SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" />
<conv:BoolToOpacityConverter x:Key="BoolToOpacity" Opacity="0.6"/>
</UserControl.Resources>
<UserControl.Styles>
<Style Selector="local|ThicknessEditor">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{StaticResource ThicknessBorderBrush}" />
<Setter Property="Template">
<ControlTemplate>
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*,Auto">
<Grid.Styles>
<Style Selector="TextBox.thickness-edit">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Disabled" />
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Disabled" />
<Setter Property="IsVisible" Value="{Binding $parent[local:ThicknessEditor].IsPresent}" />
</Style>
</Grid.Styles>
<TextBlock IsVisible="{TemplateBinding IsPresent}" Margin="4,0,0,0" Text="{TemplateBinding Header}" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Left, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox x:Name="Right" Grid.Row="0" Grid.Column="1" Text="{Binding Top, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Right, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Bottom, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" />
<ContentPresenter Grid.Row="1" Grid.Column="1"
Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="local|ThicknessEditor[IsPresent=False]">
<Setter Property="BorderThickness" Value="0" />
</Style>
</UserControl.Styles>
<Grid ColumnDefinitions="*,Auto,280">
<Grid ColumnDefinitions="*,Auto,320">
<Grid Grid.Column="0" ColumnDefinitions="*,Auto,Auto,Auto" RowDefinitions="Auto,*">
@ -108,164 +52,133 @@
<GridSplitter Grid.Column="1" />
<Grid Grid.Column="2" RowDefinitions="Auto,*, Auto,*,Auto" >
<TextBlock Grid.Row="0" Text="Layout Visualizer" Margin="4" />
<Grid Grid.Row="1" x:Name="LayoutRoot" Margin="8,0,8,8" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto">
<Border x:Name="VerticalSize" Grid.Row="0" Grid.Column="1" >
<TextBlock VerticalAlignment="Center" FontWeight="Bold"
TextDecorations="{Binding Layout.HeightConstraint, Converter={x:Static local:Converters.HasConstraintConverter}}"
Text="{Binding Layout.Height}"
ToolTip.Tip="{Binding Layout.HeightConstraint}" />
</Border>
<Border x:Name="HorizontalSize" Grid.Row="1" Grid.Column="0" >
<TextBlock HorizontalAlignment="Center" FontWeight="Bold"
TextDecorations="{Binding Layout.WidthConstraint, Converter={x:Static local:Converters.HasConstraintConverter}}"
Text="{Binding Layout.Width}"
ToolTip.Tip="{Binding Layout.WidthConstraint}" />
</Border>
<local:ThicknessEditor Grid.Row="0" Grid.Column="0" Header="margin" VerticalAlignment="Top" HorizontalAlignment="Center" Background="{StaticResource MarginBackgroundBrush}" Thickness="{Binding Layout.MarginThickness}">
<local:ThicknessEditor x:Name="BorderArea" Header="border" Background="{StaticResource BorderBackgroundBrush}" Thickness="{Binding Layout.BorderThickness}" IsPresent="{Binding Layout.HasBorder}">
<local:ThicknessEditor x:Name="PaddingArea" Header="padding" Background="{StaticResource PaddingBackgroundBrush}" Thickness="{Binding Layout.PaddingThickness}" IsPresent="{Binding Layout.HasPadding}">
<Border x:Name="ContentArea" BorderThickness="1" BorderBrush="{StaticResource ThicknessBorderBrush}" MinWidth="100" MinHeight="16" Background="{StaticResource SizeBackgroundBrush}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TextBlock Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" Text="content" />
</Border>
</local:ThicknessEditor>
</local:ThicknessEditor>
</local:ThicknessEditor>
<Grid Grid.Column="2" RowDefinitions="*,Auto,*" >
<Canvas Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2">
<Canvas.Styles>
<Style Selector="Rectangle">
<Setter Property="StrokeDashArray" Value="1,3" />
<Setter Property="Stroke" Value="{StaticResource SizeGuidelineBrush}" />
<Setter Property="StrokeThickness" Value="1" />
</Style>
</Canvas.Styles>
<Rectangle x:Name="HorizontalSizeBegin" />
<Rectangle x:Name="HorizontalSizeEnd" />
<Rectangle x:Name="VerticalSizeBegin" />
<Rectangle x:Name="VerticalSizeEnd" />
</Canvas>
<Grid RowDefinitions="Auto,*" Grid.Row="0">
<TextBlock FontWeight="Bold" Grid.Row="0" Text="Layout Visualizer" Margin="4" />
<local:LayoutExplorerView Grid.Row="1" DataContext="{Binding Layout}" />
</Grid>
<Grid Grid.Row="2" Margin="4" RowDefinitions="Auto,Auto">
<GridSplitter Grid.Row="1" />
<Grid Grid.Row="0" Margin="2" ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock FontWeight="Bold" Grid.Column="0" Text="{Binding StyleStatus}" VerticalAlignment="Center" />
<CheckBox Margin="2,0,0,0" Grid.Column="2" Content="Show inactive" IsChecked="{Binding ShowInactiveStyles}" ToolTip.Tip="Show styles that are currently inactive" />
<ToggleButton Margin="2,0,0,0" Grid.Column="3" ToolTip.Tip="Snapshot current styles (Alt+S/Alt+D to enable/disable within debugged window)" Content="Snapshot" IsChecked="{Binding SnapshotStyles}" />
</Grid>
<Grid RowDefinitions="Auto,*,Auto" Grid.Row="2">
<TextBox Grid.Row="1" Margin="2" Grid.Column="0" Watermark="Filter" Text="{Binding StyleFilter}" />
</Grid>
<Grid Grid.Row="0" Margin="4" RowDefinitions="Auto,Auto">
<ScrollViewer Grid.Row="3" HorizontalScrollBarVisibility="Disabled">
<ItemsControl Items="{Binding AppliedStyles}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="#6C6C6C" Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}">
<Border.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<MultiBinding Converter="{x:Static BoolConverters.Or}" >
<Binding Path="IsActive" />
<Binding Path="#Main.DataContext.ShowInactiveStyles" />
</MultiBinding>
<Binding Path="IsVisible" />
</MultiBinding>
</Border.IsVisible>
<Expander IsExpanded="True" Margin="0" Padding="8,0" ContentTransition="{x:Null}" >
<Expander.Header>
<TextBlock Grid.Row="0" Text="{Binding Name}" />
</Expander.Header>
<Grid Grid.Row="0" Margin="2" ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock FontWeight="Bold" Grid.Column="0" Text="{Binding StyleStatus}" VerticalAlignment="Center" />
<CheckBox Margin="2,0,0,0" Grid.Column="2" Content="Show inactive" IsChecked="{Binding ShowInactiveStyles}" ToolTip.Tip="Show styles that are currently inactive" />
<ToggleButton Margin="2,0,0,0" Grid.Column="3" ToolTip.Tip="Snapshot current styles (Alt+S/Alt+D to enable/disable within debugged window)" Content="Snapshot" IsChecked="{Binding SnapshotStyles}" />
</Grid>
<ItemsControl Margin="20,0,0,0" Grid.Row="1" Items="{Binding Setters}">
<ItemsControl.DataTemplates>
<TextBox Grid.Row="1" Margin="2" Grid.Column="0" Watermark="Filter" Text="{Binding StyleFilter}" />
</Grid>
<DataTemplate DataType="IBrush">
<StackPanel Orientation="Horizontal" Spacing="2">
<Border BorderThickness="1" BorderBrush="Black" Background="{Binding}" Width="8" Height="8"/>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled">
<ItemsControl Items="{Binding AppliedStyles}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="#6C6C6C" Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}">
<Border.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<MultiBinding Converter="{x:Static BoolConverters.Or}" >
<Binding Path="IsActive" />
<Binding Path="#Main.DataContext.ShowInactiveStyles" />
</MultiBinding>
<Binding Path="IsVisible" />
</MultiBinding>
</Border.IsVisible>
<Expander IsExpanded="True" Margin="0" Padding="8,0" ContentTransition="{x:Null}" >
<Expander.Header>
<TextBlock Grid.Row="0" Text="{Binding Name}" />
</Expander.Header>
<DataTemplate DataType="Color">
<StackPanel Orientation="Horizontal" Spacing="2">
<Border BorderThickness="1" BorderBrush="Black" Width="8" Height="8">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
<ItemsControl Margin="20,0,0,0" Grid.Row="1" Items="{Binding Setters}">
<ItemsControl.DataTemplates>
<DataTemplate DataType="vm:ResourceSetterViewModel">
<Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
<Panel.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy property name" Command="{Binding CopyPropertyName} "/>
<MenuItem Header="Copy value" Command="{Binding CopyValue} "/>
<MenuItem Header="Copy resource key" Command="{Binding CopyResourceKey}" />
</ContextMenu>
</Panel.ContextMenu>
<StackPanel Orientation="Horizontal" Spacing="2" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
<TextBlock Text=":" />
<ContentControl Content="{Binding Value}"/>
<TextBlock>(</TextBlock>
<Ellipse Height="8" Width="8" VerticalAlignment="Center" Fill="{Binding Tint}"/>
<TextBlock FontStyle="Italic" Text="{Binding Key}" />
<TextBlock>)</TextBlock>
<DataTemplate DataType="IBrush">
<StackPanel Orientation="Horizontal" Spacing="2">
<Border BorderThickness="1" BorderBrush="Black" Background="{Binding}" Width="8" Height="8"/>
<TextBlock Text="{Binding}" />
</StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" IsVisible="{Binding !IsActive}" />
</Panel>
</DataTemplate>
</DataTemplate>
<DataTemplate DataType="vm:SetterViewModel">
<Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
<Panel.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy property name" Command="{Binding CopyPropertyName} "/>
<MenuItem Header="Copy value" Command="{Binding CopyValue} "/>
</ContextMenu>
</Panel.ContextMenu>
<DataTemplate DataType="Color">
<StackPanel Orientation="Horizontal" Spacing="2">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
<TextBlock Text=":" />
<ContentControl Content="{Binding Value}"/>
<Border BorderThickness="1" BorderBrush="Black" Width="8" Height="8">
<Border.Background>
<SolidColorBrush Color="{Binding}" />
</Border.Background>
</Border>
<TextBlock Text="{Binding}" />
</StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" VerticalAlignment="Center" IsVisible="{Binding !IsActive}" />
</Panel>
</DataTemplate>
</DataTemplate>
<DataTemplate DataType="vm:ResourceSetterViewModel">
<Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
<Panel.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy property name" Command="{Binding CopyPropertyName} "/>
<MenuItem Header="Copy value" Command="{Binding CopyValue} "/>
<MenuItem Header="Copy resource key" Command="{Binding CopyResourceKey}" />
</ContextMenu>
</Panel.ContextMenu>
<StackPanel Orientation="Horizontal" Spacing="2" HorizontalAlignment="Left">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
<TextBlock Text=":" />
<ContentControl Content="{Binding Value}"/>
<TextBlock>(</TextBlock>
<Ellipse Height="8" Width="8" VerticalAlignment="Center" Fill="{Binding Tint}"/>
<TextBlock FontStyle="Italic" Text="{Binding Key}" />
<TextBlock>)</TextBlock>
</StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" IsVisible="{Binding !IsActive}" />
</Panel>
</DataTemplate>
<DataTemplate DataType="vm:SetterViewModel">
<Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
<Panel.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy property name" Command="{Binding CopyPropertyName} "/>
<MenuItem Header="Copy value" Command="{Binding CopyValue} "/>
</ContextMenu>
</Panel.ContextMenu>
<StackPanel Orientation="Horizontal" Spacing="2">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
<TextBlock Text=":" />
<ContentControl Content="{Binding Value}"/>
</StackPanel>
<Rectangle Height="1" Fill="#6C6C6C" VerticalAlignment="Center" IsVisible="{Binding !IsActive}" />
</Panel>
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>
</Expander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Expander Header="Pseudo Classes" Grid.Row="2">
<ItemsControl Items="{Binding PseudoClasses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="2" Content="{Binding Name}" IsChecked="{Binding IsActive, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</ItemsControl.DataTemplates>
</ItemsControl>
</Expander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Expander Header="Pseudo Classes" Grid.Row="4">
<ItemsControl Items="{Binding PseudoClasses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="2" Content="{Binding Name}" IsChecked="{Binding IsActive, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</Grid>
</Grid>
</Grid>

109
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs

@ -1,24 +1,10 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.Views
{
internal class ControlDetailsView : UserControl
{
private ThicknessEditor _borderArea;
private ThicknessEditor _paddingArea;
private Rectangle _horizontalSizeBegin;
private Rectangle _horizontalSizeEnd;
private Rectangle _verticalSizeBegin;
private Rectangle _verticalSizeEnd;
private Grid _layoutRoot;
private Border _horizontalSize;
private Border _verticalSize;
private Border _contentArea;
public ControlDetailsView()
{
InitializeComponent();
@ -27,101 +13,6 @@ namespace Avalonia.Diagnostics.Views
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_borderArea = this.FindControl<ThicknessEditor>("BorderArea");
_paddingArea = this.FindControl<ThicknessEditor>("PaddingArea");
_horizontalSizeBegin = this.FindControl<Rectangle>("HorizontalSizeBegin");
_horizontalSizeEnd = this.FindControl<Rectangle>("HorizontalSizeEnd");
_verticalSizeBegin = this.FindControl<Rectangle>("VerticalSizeBegin");
_verticalSizeEnd = this.FindControl<Rectangle>("VerticalSizeEnd");
_horizontalSize = this.FindControl<Border>("HorizontalSize");
_verticalSize = this.FindControl<Border>("VerticalSize");
_contentArea = this.FindControl<Border>("ContentArea");
_layoutRoot = this.FindControl<Grid>("LayoutRoot");
void SubscribeToBounds(Visual visual)
{
visual.GetPropertyChangedObservable(TransformedBoundsProperty)
.Subscribe(UpdateSizeGuidelines);
}
SubscribeToBounds(_borderArea);
SubscribeToBounds(_paddingArea);
SubscribeToBounds(_contentArea);
}
private void UpdateSizeGuidelines(AvaloniaPropertyChangedEventArgs e)
{
void UpdateGuidelines(Visual area)
{
if (area.TransformedBounds is TransformedBounds bounds)
{
// Horizontal guideline
{
var sizeArea = TranslateToRoot((_horizontalSize.TransformedBounds ?? default).Bounds.BottomLeft,
_horizontalSize);
var start = TranslateToRoot(bounds.Bounds.BottomLeft, area);
SetPosition(_horizontalSizeBegin, start);
var end = TranslateToRoot(bounds.Bounds.BottomRight, area);
SetPosition(_horizontalSizeEnd, end.WithX(end.X - 1));
var height = sizeArea.Y - start.Y + 2;
_horizontalSizeBegin.Height = height;
_horizontalSizeEnd.Height = height;
}
// Vertical guideline
{
var sizeArea = TranslateToRoot((_verticalSize.TransformedBounds ?? default).Bounds.TopRight, _verticalSize);
var start = TranslateToRoot(bounds.Bounds.TopRight, area);
SetPosition(_verticalSizeBegin, start);
var end = TranslateToRoot(bounds.Bounds.BottomRight, area);
SetPosition(_verticalSizeEnd, end.WithY(end.Y - 1));
var width = sizeArea.X - start.X + 2;
_verticalSizeBegin.Width = width;
_verticalSizeEnd.Width = width;
}
}
}
Point TranslateToRoot(Point point, IVisual from)
{
return from.TranslatePoint(point, _layoutRoot) ?? default;
}
static void SetPosition(Rectangle rect, Point start)
{
Canvas.SetLeft(rect, start.X);
Canvas.SetTop(rect, start.Y);
}
if (_borderArea.IsPresent)
{
UpdateGuidelines(_borderArea);
}
else if (_paddingArea.IsPresent)
{
UpdateGuidelines(_paddingArea);
}
else
{
UpdateGuidelines(_contentArea);
}
}
}
}

210
src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml

@ -0,0 +1,210 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Avalonia.Diagnostics.Views.LayoutExplorerView"
xmlns:local="clr-namespace:Avalonia.Diagnostics.Views"
xmlns:controls="clr-namespace:Avalonia.Diagnostics.Controls"
xmlns:converters="clr-namespace:Avalonia.Diagnostics.Converters">
<UserControl.Resources>
<SolidColorBrush x:Key="SizeGuidelineBrush" Color="#333333" />
<SolidColorBrush x:Key="MarginBackgroundBrush" Color="#D78965" />
<SolidColorBrush x:Key="MarginHighlightBrush" Color="#EA966F" />
<SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" />
<SolidColorBrush x:Key="BorderHighlightBrush" Color="#EFCD88" />
<SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" />
<SolidColorBrush x:Key="PaddingHighlightBrush" Color="#CEDA8E" />
<SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" />
<SolidColorBrush x:Key="SizeHighlightBrush" Color="#9ED0DC" />
</UserControl.Resources>
<Grid RowDefinitions="*,Auto">
<Viewbox Grid.Row="0" StretchDirection="DownOnly">
<Grid x:Name="LayoutRoot" Margin="8,0,8,8" RowDefinitions="Auto,Auto"
ColumnDefinitions="Auto,Auto">
<Grid.Styles>
<Style Selector="TextBlock.with-constraint">
<Setter Property="TextDecorations" Value="Underline" />
</Style>
</Grid.Styles>
<Border x:Name="VerticalSize" Grid.Row="0" Grid.Column="1">
<TextBlock VerticalAlignment="Center" FontWeight="Bold"
Classes.with-constraint="{Binding HeightConstraint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Text="{Binding Height}"
ToolTip.Tip="{Binding HeightConstraint}" />
</Border>
<Border x:Name="HorizontalSize" Grid.Row="1" Grid.Column="0">
<TextBlock HorizontalAlignment="Center" FontWeight="Bold"
Classes.with-constraint="{Binding WidthConstraint, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Text="{Binding Width}"
ToolTip.Tip="{Binding WidthConstraint}" />
</Border>
<controls:ThicknessEditor Grid.Row="0" Grid.Column="0" Header="margin" VerticalAlignment="Top"
HorizontalAlignment="Center"
Background="{StaticResource MarginBackgroundBrush}"
Highlight="{StaticResource MarginHighlightBrush}"
Thickness="{Binding MarginThickness}">
<controls:ThicknessEditor x:Name="BorderArea" Header="border"
Background="{StaticResource BorderBackgroundBrush}"
Highlight="{StaticResource BorderHighlightBrush}"
Thickness="{Binding BorderThickness}"
IsPresent="{Binding HasBorder}">
<controls:ThicknessEditor x:Name="PaddingArea" Header="padding"
Background="{StaticResource PaddingBackgroundBrush}"
Highlight="{StaticResource PaddingHighlightBrush}"
Thickness="{Binding PaddingThickness}"
IsPresent="{Binding HasPadding}">
<Border x:Name="ContentArea" BorderThickness="1" MinWidth="100" MinHeight="16"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border.Styles>
<Style Selector="Border">
<Setter Property="Background" Value="{StaticResource SizeBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThicknessBorderBrush}" />
</Style>
<Style Selector="Border:pointerover">
<Setter Property="Background" Value="{StaticResource SizeHighlightBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource HighlightBorderBrush}" />
</Style>
</Border.Styles>
<TextBlock Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="content" />
</Border>
</controls:ThicknessEditor>
</controls:ThicknessEditor>
</controls:ThicknessEditor>
<Canvas Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2">
<Canvas.Styles>
<Style Selector="Rectangle">
<Setter Property="StrokeDashArray" Value="1,3" />
<Setter Property="Stroke" Value="{StaticResource SizeGuidelineBrush}" />
<Setter Property="StrokeThickness" Value="1" />
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
</Canvas.Styles>
<Rectangle x:Name="HorizontalSizeBegin" />
<Rectangle x:Name="HorizontalSizeEnd" />
<Rectangle x:Name="VerticalSizeBegin" />
<Rectangle x:Name="VerticalSizeEnd" />
</Canvas>
</Grid>
</Viewbox>
<StackPanel Orientation="Vertical" Grid.Column="0" Grid.Row="1" Spacing="4"
Margin="4" HorizontalAlignment="Stretch">
<StackPanel.Resources>
<converters:EnumToCheckedConverter x:Key="AlignmentConverter" />
</StackPanel.Resources>
<StackPanel.Styles>
<Style Selector="RadioButton">
<Setter Property="Template">
<ControlTemplate>
<Border
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Padding="{TemplateBinding Padding}">
<Path x:Name="PART_Path" Fill="Black" Width="16" Height="16" Stretch="Uniform" />
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="RadioButton">
<Setter Property="Background" Value="WhiteSmoke" />
<Setter Property="Padding" Value="2" />
</Style>
<Style Selector="RadioButton:pointerover">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}" />
</Style>
<Style Selector="RadioButton:checked">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}" />
</Style>
</StackPanel.Styles>
<DockPanel HorizontalAlignment="Stretch" LastChildFill="True">
<DockPanel.Styles>
<Style Selector="RadioButton.h-left /template/ Path#PART_Path">
<Setter Property="Data" Value="M22 13V19H6V13H22M6 5V11H16V5H6M2 2V22H4V2H2" />
</Style>
<Style Selector="RadioButton.h-center /template/ Path#PART_Path">
<Setter Property="Data"
Value="M20 19H13V22H11V19H4V13H11V11H7V5H11V2H13V5H17V11H13V13H20V19Z" />
</Style>
<Style Selector="RadioButton.h-right /template/ Path#PART_Path">
<Setter Property="Data" Value="M18 13V19H2V13H18M8 5V11H18V5H8M20 2V22H22V2H20Z" />
</Style>
<Style Selector="RadioButton.h-stretch /template/ Path#PART_Path">
<Setter Property="Data" Value="M4,22H2V2h2V22z M22,2h-2v20h2V2z M13.5,7h-3v10h3V7z" />
</Style>
</DockPanel.Styles>
<Label DockPanel.Dock="Left" Content="Horizontal Alignment" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<RadioButton GroupName="HAlignment" Classes="h-left"
ToolTip.Tip="Left"
IsChecked="{Binding HorizontalAlignment, Converter={StaticResource AlignmentConverter}, ConverterParameter={x:Static HorizontalAlignment.Left}}" />
<RadioButton GroupName="HAlignment" Classes="h-center" BorderThickness="0,1,1,1"
ToolTip.Tip="Center"
IsChecked="{Binding HorizontalAlignment, Converter={StaticResource AlignmentConverter}, ConverterParameter={x:Static HorizontalAlignment.Center}}" />
<RadioButton GroupName="HAlignment" Classes="h-right" BorderThickness="0,1,1,1"
ToolTip.Tip="Right"
IsChecked="{Binding HorizontalAlignment, Converter={StaticResource AlignmentConverter}, ConverterParameter={x:Static HorizontalAlignment.Right}}" />
<RadioButton GroupName="HAlignment" Classes="h-stretch"
ToolTip.Tip="Stretch"
IsChecked="{Binding HorizontalAlignment, Converter={StaticResource AlignmentConverter}, ConverterParameter={x:Static HorizontalAlignment.Stretch}}" />
</StackPanel>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" LastChildFill="True">
<DockPanel.Styles>
<Style Selector="RadioButton.v-top /template/ Path#PART_Path">
<Setter Property="Data" Value="M11 22H5V6H11V22M19 6H13V16H19V6M22 2H2V4H22V2Z" />
</Style>
<Style Selector="RadioButton.v-center /template/ Path#PART_Path">
<Setter Property="Data"
Value="M5 20V13H2V11H5V4H11V11H13V7H19V11H22V13H19V17H13V13H11V20H5Z" />
</Style>
<Style Selector="RadioButton.v-bottom /template/ Path#PART_Path">
<Setter Property="Data" Value="M11 18H5V2H11V18M19 8H13V18H19V8M22 20H2V22H22V20Z" />
</Style>
<Style Selector="RadioButton.v-stretch /template/ Path#PART_Path">
<Setter Property="Data" Value="M22,2v2H2V2H22z M7,10.5v3h10v-3H7z M2,20v2h20v-2H2z" />
</Style>
</DockPanel.Styles>
<Label DockPanel.Dock="Left" Content="Vertical Alignment" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<RadioButton GroupName="VAlignment" Classes="v-top"
ToolTip.Tip="Top"
IsChecked="{Binding VerticalAlignment, Converter={StaticResource AlignmentConverter}, ConverterParameter={x:Static VerticalAlignment.Top}}" />
<RadioButton GroupName="VAlignment" Classes="v-center" BorderThickness="0,1,1,1"
ToolTip.Tip="Center"
IsChecked="{Binding VerticalAlignment, Converter={StaticResource AlignmentConverter}, ConverterParameter={x:Static VerticalAlignment.Center}}" />
<RadioButton GroupName="VAlignment" Classes="v-bottom" BorderThickness="0,1,1,1"
ToolTip.Tip="Bottom"
IsChecked="{Binding VerticalAlignment, Converter={StaticResource AlignmentConverter}, ConverterParameter={x:Static VerticalAlignment.Bottom}}" />
<RadioButton GroupName="VAlignment" Classes="v-stretch"
ToolTip.Tip="Stretch"
IsChecked="{Binding VerticalAlignment, Converter={StaticResource AlignmentConverter}, ConverterParameter={x:Static VerticalAlignment.Stretch}}" />
</StackPanel>
</DockPanel>
</StackPanel>
</Grid>
</UserControl>

128
src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs

@ -0,0 +1,128 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Diagnostics.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.Views
{
internal class LayoutExplorerView : UserControl
{
private readonly ThicknessEditor _borderArea;
private readonly ThicknessEditor _paddingArea;
private readonly Rectangle _horizontalSizeBegin;
private readonly Rectangle _horizontalSizeEnd;
private readonly Rectangle _verticalSizeBegin;
private readonly Rectangle _verticalSizeEnd;
private readonly Grid _layoutRoot;
private readonly Border _horizontalSize;
private readonly Border _verticalSize;
private readonly Border _contentArea;
public LayoutExplorerView()
{
InitializeComponent();
_borderArea = this.FindControl<ThicknessEditor>("BorderArea");
_paddingArea = this.FindControl<ThicknessEditor>("PaddingArea");
_horizontalSizeBegin = this.FindControl<Rectangle>("HorizontalSizeBegin");
_horizontalSizeEnd = this.FindControl<Rectangle>("HorizontalSizeEnd");
_verticalSizeBegin = this.FindControl<Rectangle>("VerticalSizeBegin");
_verticalSizeEnd = this.FindControl<Rectangle>("VerticalSizeEnd");
_horizontalSize = this.FindControl<Border>("HorizontalSize");
_verticalSize = this.FindControl<Border>("VerticalSize");
_contentArea = this.FindControl<Border>("ContentArea");
_layoutRoot = this.FindControl<Grid>("LayoutRoot");
void SubscribeToBounds(Visual visual)
{
visual.GetPropertyChangedObservable(TransformedBoundsProperty)
.Subscribe(UpdateSizeGuidelines);
}
SubscribeToBounds(_borderArea);
SubscribeToBounds(_paddingArea);
SubscribeToBounds(_contentArea);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void UpdateSizeGuidelines(AvaloniaPropertyChangedEventArgs e)
{
void UpdateGuidelines(Visual area)
{
if (area.TransformedBounds is TransformedBounds bounds)
{
// Horizontal guideline
{
var sizeArea = TranslateToRoot((_horizontalSize.TransformedBounds ?? default).Bounds.BottomLeft,
_horizontalSize);
var start = TranslateToRoot(bounds.Bounds.BottomLeft, area);
SetPosition(_horizontalSizeBegin, start);
var end = TranslateToRoot(bounds.Bounds.BottomRight, area);
SetPosition(_horizontalSizeEnd, end.WithX(end.X - 1));
var height = sizeArea.Y - start.Y + 2;
_horizontalSizeBegin.Height = height;
_horizontalSizeEnd.Height = height;
}
// Vertical guideline
{
var sizeArea = TranslateToRoot((_verticalSize.TransformedBounds ?? default).Bounds.TopRight, _verticalSize);
var start = TranslateToRoot(bounds.Bounds.TopRight, area);
SetPosition(_verticalSizeBegin, start);
var end = TranslateToRoot(bounds.Bounds.BottomRight, area);
SetPosition(_verticalSizeEnd, end.WithY(end.Y - 1));
var width = sizeArea.X - start.X + 2;
_verticalSizeBegin.Width = width;
_verticalSizeEnd.Width = width;
}
}
}
Point TranslateToRoot(Point point, IVisual from)
{
return from.TranslatePoint(point, _layoutRoot) ?? default;
}
static void SetPosition(Rectangle rect, Point start)
{
Canvas.SetLeft(rect, start.X);
Canvas.SetTop(rect, start.Y);
}
if (_borderArea.IsPresent)
{
UpdateGuidelines(_borderArea);
}
else if (_paddingArea.IsPresent)
{
UpdateGuidelines(_paddingArea);
}
else
{
UpdateGuidelines(_contentArea);
}
}
}
}

1
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml

@ -15,6 +15,7 @@
<Style Selector="DataGrid ContextMenu">
<Setter Property="Foreground" Value="Black"/>
</Style>
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml" />
</Window.Styles>
<views:MainView/>

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

@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
x:Class="Avalonia.Diagnostics.Views.TreePageView">
<Grid ColumnDefinitions="0.4*,4,0.6*">
<Grid ColumnDefinitions="0.35*,4,0.65*">
<TreeView Name="tree"
BorderThickness="0"
Items="{Binding Nodes}"
@ -20,6 +20,7 @@
<TreeView.Styles>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="Background" Value="Transparent" />
</Style>
</TreeView.Styles>
</TreeView>

19
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs

@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Generators;
@ -6,6 +7,7 @@ using Avalonia.Diagnostics.ViewModels;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.Views
{
@ -100,12 +102,19 @@ namespace Avalonia.Diagnostics.Views
private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e)
{
var item = (TreeViewItem)sender;
var headerPresenter = item.HeaderPresenter;
headerPresenter.ApplyTemplate();
var header = headerPresenter.Child;
header.PointerEnter += AddAdorner;
header.PointerLeave += RemoveAdorner;
// This depends on the default tree item template.
// We want to handle events in the item header but exclude events coming from children.
var header = item.FindDescendantOfType<Border>();
Debug.Assert(header != null);
if (header != null)
{
header.PointerEnter += AddAdorner;
header.PointerLeave += RemoveAdorner;
}
item.TemplateApplied -= TreeViewItemTemplateApplied;
}
}

22
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -70,6 +70,28 @@ namespace Avalonia.Headless
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));
}
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
return new HeadlessBitmapStub(new Size(1, 1), new Vector(96, 96));

9
src/Avalonia.Layout/WrapLayout/WrapLayout.cs

@ -236,6 +236,15 @@ namespace Avalonia.Layout
// for the last condition it is zeros so adding it will make no difference
// this way is faster than an if condition in every loop for checking the last item
totalMeasure.U = parentMeasure.U;
// Propagating an infinite size causes a crash. This can happen if the parent is scrollable and infinite in the opposite
// axis to the panel. Clearing to zero prevents the crash.
// This is likely an incorrect use of the control by the developer, however we need stability here so setting a default that wont crash.
if (double.IsInfinity(totalMeasure.U))
{
totalMeasure.U = 0.0;
}
totalMeasure.V = state.GetHeight();
totalMeasure.U = Math.Ceiling(totalMeasure.U);

4
src/Avalonia.Native/WindowImplBase.cs

@ -414,9 +414,7 @@ namespace Avalonia.Native
public Action<RawInputEventArgs> Input { get; set; }
Action<double> ScalingChanged { get; set; }
Action<double> ITopLevelImpl.ScalingChanged { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }

39
src/Avalonia.Styling/Controls/Classes.cs

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using Avalonia.Collections;
#nullable enable
namespace Avalonia.Controls
{
/// <summary>
@ -90,7 +92,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Remvoes all non-pseudoclasses from the collection.
/// Removes all non-pseudoclasses from the collection.
/// </summary>
public override void Clear()
{
@ -135,7 +137,7 @@ namespace Avalonia.Controls
/// </remarks>
public override void InsertRange(int index, IEnumerable<string> names)
{
var c = new List<string>();
List<string>? toInsert = null;
foreach (var name in names)
{
@ -143,11 +145,16 @@ namespace Avalonia.Controls
if (!Contains(name))
{
c.Add(name);
toInsert ??= new List<string>();
toInsert.Add(name);
}
}
base.InsertRange(index, c);
if (toInsert != null)
{
base.InsertRange(index, toInsert);
}
}
/// <summary>
@ -176,19 +183,21 @@ namespace Avalonia.Controls
/// </remarks>
public override void RemoveAll(IEnumerable<string> names)
{
var c = new List<string>();
List<string>? toRemove = null;
foreach (var name in names)
{
ThrowIfPseudoclass(name, "removed");
if (Contains(name))
{
c.Add(name);
}
toRemove ??= new List<string>();
toRemove.Add(name);
}
base.RemoveAll(c);
if (toRemove != null)
{
base.RemoveAll(toRemove);
}
}
/// <summary>
@ -223,7 +232,7 @@ namespace Avalonia.Controls
/// <param name="source">The new contents of the collection.</param>
public void Replace(IList<string> source)
{
var toRemove = new List<string>();
List<string>? toRemove = null;
foreach (var name in source)
{
@ -234,11 +243,17 @@ namespace Avalonia.Controls
{
if (!name.StartsWith(":"))
{
toRemove ??= new List<string>();
toRemove.Add(name);
}
}
base.RemoveAll(toRemove);
if (toRemove != null)
{
base.RemoveAll(toRemove);
}
base.AddRange(source);
}

8
src/Avalonia.Themes.Fluent/Accents/Base.xaml

@ -1,6 +1,7 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
xmlns:sys="using:System"
xmlns:converters="using:Avalonia.Controls.Converters">
<Style.Resources>
<!-- https://docs.microsoft.com/en-us/previous-versions/windows/apps/dn518235(v=win.10)?redirectedfrom=MSDN -->
<!-- SystemColor Color Resources (Reflect OS High Contrast Settings) -->
@ -30,5 +31,10 @@
<!-- ScrollBar animation -->
<TransformOperations x:Key="VerticalSmallScrollThumbScaleTransform">scaleX(0.125) translateX(-2px)</TransformOperations>
<TransformOperations x:Key="HorizontalSmallScrollThumbScaleTransform">scaleY(0.125) translateY(-2px)</TransformOperations>
<converters:CornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" Filter="TopLeft, TopRight"/>
<converters:CornerRadiusFilterConverter x:Key="RightCornerRadiusFilterConverter" Filter="TopRight, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" Filter="BottomLeft, BottomRight"/>
<converters:CornerRadiusFilterConverter x:Key="LeftCornerRadiusFilterConverter" Filter="TopLeft, BottomLeft"/>
</Style.Resources>
</Style>

317
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -1,141 +1,232 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Orientation="Vertical" Spacing="20">
<Expander ExpandDirection="Up" Header="Expand Up">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Down" Header="Expand Down">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Left" Header="Expand Left">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander ExpandDirection="Right" Header="Expand Right">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
</StackPanel>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<Thickness x:Key="ExpanderHeaderPadding">16</Thickness>
<Thickness x:Key="ExpanderContentPadding">16</Thickness>
<Thickness x:Key="ExpanderBorderThickness">1</Thickness>
<Thickness x:Key="ExpanderDropdownLeftBorderThickness">1,1,0,1</Thickness>
<Thickness x:Key="ExpanderDropdownUpBorderThickness">1,1,1,0</Thickness>
<Thickness x:Key="ExpanderDropdownRightBorderThickness">0,1,1,1</Thickness>
<Thickness x:Key="ExpanderDropdownDownBorderThickness">1,0,1,1</Thickness>
<SolidColorBrush x:Key="ExpanderBackground" Color="{DynamicResource SystemAltMediumHighColor}" />
<SolidColorBrush x:Key="ExpanderBorderBrush" Color="{DynamicResource SystemBaseLowColor}" />
<SolidColorBrush x:Key="ExpanderDropDownBackground" Color="{DynamicResource SystemChromeMediumLowColor}" />
<SolidColorBrush x:Key="ExpanderDropDownBorderBrush" Color="{DynamicResource SystemBaseLowColor}" />
<SolidColorBrush x:Key="ExpanderForeground" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="ExpanderChevronForeground" Color="{DynamicResource SystemBaseHighColor}" />
</Styles.Resources>
<Style Selector="Expander">
<Setter Property="ContentTransition">
<Setter.Value>
<CrossFade Duration="00:00:00.25" />
</Setter.Value>
</Setter>
<Setter Property="Background" Value="{DynamicResource ExpanderBackground}" />
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderBorderThickness}" />
<Setter Property="BorderBrush" Value="{DynamicResource ExpanderBorderBrush}" />
<Setter Property="Padding" Value="{DynamicResource ExpanderHeaderPadding}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="Expander[ExpandDirection=Down]">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Grid RowDefinitions="Auto,*">
<ToggleButton Name="PART_toggle" Grid.Row="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Row="1"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander[IsExpanded=true] /template/ ToggleButton#PART_toggle /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForeground}"/>
</Style>
<Style Selector="Expander[ExpandDirection=Up]">
<Style Selector="Expander">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Grid RowDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Row="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Row="0"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
<DockPanel>
<ToggleButton x:Name="ExpanderHeader"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsEnabled="{TemplateBinding IsEnabled}" />
<Border x:Name="ExpanderContent"
Padding="{DynamicResource ExpanderContentPadding}"
Background="{DynamicResource ExpanderDropDownBackground}"
BorderBrush="{DynamicResource ExpanderDropDownBorderBrush}"
IsVisible="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter x:Name="PART_ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander[ExpandDirection=Right]">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Grid ColumnDefinitions="Auto,*">
<ToggleButton Name="PART_toggle" Grid.Column="0" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander[ExpandDirection=Left]">
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader">
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<Border x:Name="ToggleButtonBackground">
<Grid ColumnDefinitions="*,Auto">
<ToggleButton Name="PART_toggle" Grid.Column="1" Content="{TemplateBinding Header}" IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="0"
IsVisible="{TemplateBinding IsExpanded}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
<ContentPresenter x:Name="PART_ContentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander /template/ ToggleButton#PART_toggle">
<Setter Property="Template">
<ControlTemplate>
<Border BorderThickness="1" Background="Transparent">
<Grid ColumnDefinitions="Auto,Auto">
<Border Grid.Column="0" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 2 L 4 6 L 0 10 Z" />
VerticalContentAlignment="Center"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
TextBlock.Foreground="{DynamicResource ExpanderForeground}" />
<Border x:Name="ExpandCollapseChevronBorder"
Grid.Column="1"
Width="32"
Height="32"
Margin="7"
RenderTransformOrigin="50%,50%">
<Path x:Name="ExpandCollapseChevron"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="50%,50%"
Stretch="None"
Stroke="{DynamicResource ExpanderChevronForeground}"
StrokeThickness="1" />
<Border.RenderTransform>
<RotateTransform />
</Border.RenderTransform>
</Border>
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1"
Background="Transparent"
Content="{TemplateBinding Content}"
VerticalAlignment="Center"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Expander /template/ ToggleButton#PART_toggle:pointerover /template/ Border">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumBrush}" />
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader:pressed">
<Setter Property="RenderTransform" Value="{x:Null}" />
</Style>
<Style Selector="Expander:down:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="90" />
</Setter>
<Style Selector="Expander:left /template/ ToggleButton#ExpanderHeader, Expander:right /template/ ToggleButton#ExpanderHeader">
<Setter Property="VerticalAlignment" Value="Stretch" />
</Style>
<Style Selector="Expander:up:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="-90" />
</Setter>
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="Background" Value="{TemplateBinding Background}" />
<Setter Property="BorderBrush" Value="{TemplateBinding BorderBrush}" />
<Setter Property="BorderThickness" Value="{TemplateBinding BorderThickness}" />
</Style>
<Style Selector="Expander:left:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="180" />
</Setter>
<Style Selector="Expander:not(:expanded) /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style>
<Style Selector="Expander:right /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="180" />
</Setter>
<Style Selector="Expander:expanded:up /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:right:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">
<RotateTransform Angle="0" />
</Setter>
<Style Selector="Expander:expanded:up /template/ Border#ExpanderContent">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:down /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:down /template/ Border#ExpanderContent">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:left /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource RightCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:left /template/ Border#ExpanderContent">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource LeftCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:right /template/ ToggleButton#ExpanderHeader /template/ Border#ToggleButtonBackground">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource LeftCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:expanded:right /template/ Border#ExpanderContent">
<Setter Property="CornerRadius" Value="{Binding Source={StaticResource ControlCornerRadius}, Converter={StaticResource RightCornerRadiusFilterConverter}}" />
</Style>
<Style Selector="Expander:left /template/ ToggleButton#ExpanderHeader">
<Setter Property="DockPanel.Dock" Value="Right" />
</Style>
<Style Selector="Expander:up /template/ ToggleButton#ExpanderHeader">
<Setter Property="DockPanel.Dock" Value="Bottom" />
</Style>
<Style Selector="Expander:right /template/ ToggleButton#ExpanderHeader">
<Setter Property="DockPanel.Dock" Value="Left" />
</Style>
<Style Selector="Expander:down /template/ ToggleButton#ExpanderHeader">
<Setter Property="DockPanel.Dock" Value="Top" />
</Style>
<Style Selector="Expander:expanded /template/ ToggleButton#ExpanderHeader /template/ Border#ExpandCollapseChevronBorder">
<Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625">
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="180" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Expander:not(:expanded) /template/ ToggleButton#ExpanderHeader /template/ Border#ExpandCollapseChevronBorder">
<Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="180" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="0" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Expander:left /template/ ToggleButton#ExpanderHeader /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 7 0 L 0 7 L 7 14" />
</Style>
<Style Selector="Expander:up /template/ ToggleButton#ExpanderHeader /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 7 L 7 0 L 14 7" />
</Style>
<Style Selector="Expander:right /template/ ToggleButton#ExpanderHeader /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 0 L 7 7 L 0 14" />
</Style>
<Style Selector="Expander:down /template/ ToggleButton#ExpanderHeader /template/ Path#ExpandCollapseChevron">
<Setter Property="Data" Value="M 0 0 L 7 7 L 14 0" />
</Style>
<Style Selector="Expander:left /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownLeftBorderThickness}" />
</Style>
<Style Selector="Expander:up /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownUpBorderThickness}" />
</Style>
<Style Selector="Expander:right /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownRightBorderThickness}" />
</Style>
<Style Selector="Expander:down /template/ Border#ExpanderContent">
<Setter Property="BorderThickness" Value="{DynamicResource ExpanderDropdownDownBorderThickness}" />
</Style>
</Styles>

10
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -12,6 +12,11 @@ namespace Avalonia.Animation.Animators
{
public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue)
{
if (oldValue is null || newValue is null)
{
return oldValue;
}
return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
}
@ -26,6 +31,11 @@ namespace Avalonia.Animation.Animators
{
public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue)
{
if (oldValue is null || newValue is null)
{
return oldValue;
}
return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
}
}

23
src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs

@ -0,0 +1,23 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Media;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="BoxShadows"/> type.
/// </summary>
public class BoxShadowsTransition : Transition<BoxShadows>
{
private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator();
/// <inheritdocs/>
public override IObservable<BoxShadows> DoTransition(IObservable<double> progress, BoxShadows oldValue, BoxShadows newValue)
{
return progress
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

21
src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,26 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class CornerRadiusTransition : Transition<CornerRadius>
{
private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator();
/// <inheritdocs/>
public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
var deltaTL = newValue.TopLeft - oldValue.TopLeft;
var deltaTR = newValue.TopRight - oldValue.TopRight;
var deltaBR = newValue.BottomRight - oldValue.BottomRight;
var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
var nTL = f * deltaTL + oldValue.TopLeft;
var nTR = f * deltaTR + oldValue.TopRight;
var nBR = f * deltaBR + oldValue.BottomRight;
var nBL = f * deltaBL + oldValue.BottomLeft;
return new CornerRadius(nTL, nTR, nBR, nBL);
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

10
src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class PointTransition : Transition<Point>
{
private static readonly PointAnimator s_animator = new PointAnimator();
/// <inheritdocs/>
public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

10
src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class SizeTransition : Transition<Size>
{
private static readonly SizeAnimator s_animator = new SizeAnimator();
/// <inheritdocs/>
public override IObservable<Size> DoTransition(IObservable<double> progress, Size oldValue, Size newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

10
src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class ThicknessTransition : Transition<Thickness>
{
private static readonly ThicknessAnimator s_animator = new ThicknessAnimator();
/// <inheritdocs/>
public override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

10
src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs

@ -1,6 +1,8 @@
using System;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
@ -8,15 +10,13 @@ namespace Avalonia.Animation
/// </summary>
public class VectorTransition : Transition<Vector>
{
private static readonly VectorAnimator s_animator = new VectorAnimator();
/// <inheritdocs/>
public override IObservable<Vector> DoTransition(IObservable<double> progress, Vector oldValue, Vector newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
.Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue));
}
}
}

5
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -64,3 +64,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGl
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract.
Total Issues: 64
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 11

45
src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs

@ -1,5 +1,8 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media.Imaging
{
@ -34,8 +37,50 @@ namespace Avalonia.Media.Imaging
{
}
private WriteableBitmap(IWriteableBitmapImpl impl) : base(impl)
{
}
public ILockedFramebuffer Lock() => ((IWriteableBitmapImpl) PlatformImpl.Item).Lock();
public static WriteableBitmap Decode(Stream stream)
{
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return new WriteableBitmap(ri.LoadWriteableBitmap(stream));
}
/// <summary>
/// Loads a WriteableBitmap from a stream and decodes at the desired width. Aspect ratio is maintained.
/// This is more efficient than loading and then resizing.
/// </summary>
/// <param name="stream">The stream to read the bitmap from. This can be any supported image format.</param>
/// <param name="width">The desired width of the resulting bitmap.</param>
/// <param name="interpolationMode">The <see cref="BitmapInterpolationMode"/> to use should any scaling be required.</param>
/// <returns>An instance of the <see cref="WriteableBitmap"/> class.</returns>
public new static WriteableBitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return new WriteableBitmap(ri.LoadWriteableBitmapToWidth(stream, width, interpolationMode));
}
/// <summary>
/// Loads a Bitmap from a stream and decodes at the desired height. Aspect ratio is maintained.
/// This is more efficient than loading and then resizing.
/// </summary>
/// <param name="stream">The stream to read the bitmap from. This can be any supported image format.</param>
/// <param name="height">The desired height of the resulting bitmap.</param>
/// <param name="interpolationMode">The <see cref="BitmapInterpolationMode"/> to use should any scaling be required.</param>
/// <returns>An instance of the <see cref="WriteableBitmap"/> class.</returns>
public new static WriteableBitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode));
}
private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat)
{
var ri = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();

6
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -109,7 +109,8 @@ namespace Avalonia.Media.TextFormatting
GlyphRun.GlyphAdvances.Take(glyphCount),
GlyphRun.GlyphOffsets.Take(glyphCount),
GlyphRun.Characters.Take(length),
GlyphRun.GlyphClusters.Take(glyphCount));
GlyphRun.GlyphClusters.Take(glyphCount),
GlyphRun.BiDiLevel);
var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties);
@ -120,7 +121,8 @@ namespace Avalonia.Media.TextFormatting
GlyphRun.GlyphAdvances.Skip(glyphCount),
GlyphRun.GlyphOffsets.Skip(glyphCount),
GlyphRun.Characters.Skip(length),
GlyphRun.GlyphClusters.Skip(glyphCount));
GlyphRun.GlyphClusters.Skip(glyphCount),
GlyphRun.BiDiLevel);
var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties);

44
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -135,20 +135,20 @@ namespace Avalonia.Media.TextFormatting
count = 0;
var script = Script.Common;
//var direction = BiDiClass.LeftToRight;
var direction = BiDiClass.LeftToRight;
var font = typeface.GlyphTypeface;
var defaultFont = defaultTypeface.GlyphTypeface;
var enumerator = new GraphemeEnumerator(text);
while (enumerator.MoveNext())
{
var grapheme = enumerator.Current;
var currentGrapheme = enumerator.Current;
var currentScript = grapheme.FirstCodepoint.Script;
var currentScript = currentGrapheme.FirstCodepoint.Script;
//var currentDirection = grapheme.FirstCodepoint.BiDiClass;
var currentDirection = currentGrapheme.FirstCodepoint.BiDiClass;
//// ToDo: Implement BiDi algorithm
//if (currentScript.HorizontalDirection != direction)
@ -161,36 +161,44 @@ namespace Avalonia.Media.TextFormatting
if (currentScript != script)
{
if (currentScript != Script.Inherited && currentScript != Script.Common)
if (script == Script.Inherited || script == Script.Common)
{
if (script == Script.Inherited || script == Script.Common)
{
script = currentScript;
}
else
script = currentScript;
}
else
{
if (currentScript != Script.Inherited && currentScript != Script.Common)
{
break;
}
}
}
if (isFallback)
if (currentScript != Script.Common && currentScript != Script.Inherited)
{
if (defaultFont.TryGetGlyph(grapheme.FirstCodepoint, out _))
if (isFallback && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
}
}
if (!font.TryGetGlyph(grapheme.FirstCodepoint, out _))
{
if (!grapheme.FirstCodepoint.IsWhiteSpace)
if (!font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
}
}
count += grapheme.Text.Length;
if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && !font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
}
if (direction == BiDiClass.RightToLeft && currentDirection == BiDiClass.CommonSeparator)
{
break;
}
count += currentGrapheme.Text.Length;
direction = currentDirection;
}
return count > 0;

32
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -100,6 +100,38 @@ namespace Avalonia.Platform
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(Stream stream);
/// <summary>
/// Loads a WriteableBitmap implementation from a stream to a specified width maintaining aspect ratio.
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <param name="width">The desired width of the resulting bitmap.</param>
/// <param name="interpolationMode">The <see cref="BitmapInterpolationMode"/> to use should resizing be required.</param>
/// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality);
/// <summary>
/// Loads a WriteableBitmap implementation from a stream to a specified height maintaining aspect ratio.
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <param name="height">The desired height of the resulting bitmap.</param>
/// <param name="interpolationMode">The <see cref="BitmapInterpolationMode"/> to use should resizing be required.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality);
/// <summary>
/// Loads a WriteableBitmap implementation from a file.
/// </summary>
/// <param name="fileName">The filename of the bitmap.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IWriteableBitmapImpl LoadWriteableBitmap(string fileName);
/// <summary>
/// Loads a WriteableBitmap implementation from a file.
/// </summary>
/// <param name="stream">The stream to read the bitmap from.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IWriteableBitmapImpl LoadWriteableBitmap(Stream stream);
/// <summary>
/// Loads a bitmap implementation from a stream to a specified width maintaining aspect ratio.
/// </summary>

5
src/Avalonia.Visuals/Rendering/ICustomSimpleHitTest.cs

@ -11,16 +11,13 @@ namespace Avalonia.Rendering
/// </summary>
public interface ICustomSimpleHitTest
{
/// <param name="point">The point to hit test in global coordinate space.</param>
bool HitTest(Point point);
}
/// <summary>
/// Allows customization of hit-testing for all renderers.
/// </summary>
/// <remarks>
/// Note that this interface can only used to make a portion of a control non-hittable, it
/// cannot expand the hittable area of a control.
/// </remarks>
public interface ICustomHitTest : ICustomSimpleHitTest
{
}

2
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -225,7 +225,7 @@ namespace Avalonia.Rendering
if (filter?.Invoke(visual) != false)
{
bool containsPoint = false;
bool containsPoint;
if (visual is ICustomSimpleHitTest custom)
{

6
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@ -256,7 +256,8 @@ namespace Avalonia.Rendering.SceneGraph
if (childCount == 0 || wasVisited)
{
if ((wasVisited || FilterAndClip(node, ref clip)) && node.HitTest(_point))
if ((wasVisited || FilterAndClip(node, ref clip)) &&
(node.Visual is ICustomSimpleHitTest custom ? custom.HitTest(_point) : node.HitTest(_point)))
{
_current = node.Visual;
@ -311,8 +312,7 @@ namespace Avalonia.Rendering.SceneGraph
if (!clipped && node.Visual is ICustomHitTest custom)
{
var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual);
clipped = !custom.HitTest(controlPoint.Value);
clipped = !custom.HitTest(_point);
}
return !clipped;

25
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -77,6 +77,31 @@ namespace Avalonia.Skia
return new ImmutableBitmap(stream);
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new WriteableBitmapImpl(stream, width, true, interpolationMode);
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new WriteableBitmapImpl(stream, height, false, interpolationMode);
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
using (var stream = File.OpenRead(fileName))
{
return LoadWriteableBitmap(stream);
}
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
return new WriteableBitmapImpl(stream);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{

65
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Threading;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Skia.Helpers;
using Avalonia.Visuals.Media.Imaging;
using SkiaSharp;
namespace Avalonia.Skia
@ -15,7 +17,70 @@ namespace Avalonia.Skia
private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc;
private readonly SKBitmap _bitmap;
private readonly object _lock = new object();
/// <summary>
/// Create a WriteableBitmap from given stream.
/// </summary>
/// <param name="stream">Stream containing encoded data.</param>
public WriteableBitmapImpl(Stream stream)
{
using (var skiaStream = new SKManagedStream(stream))
{
_bitmap = SKBitmap.Decode(skiaStream);
if (_bitmap == null)
{
throw new ArgumentException("Unable to load bitmap from provided data");
}
PixelSize = new PixelSize(_bitmap.Width, _bitmap.Height);
Dpi = SkiaPlatform.DefaultDpi;
}
}
public WriteableBitmapImpl(Stream stream, int decodeSize, bool horizontal, BitmapInterpolationMode interpolationMode)
{
using (var skStream = new SKManagedStream(stream))
using (var codec = SKCodec.Create(skStream))
{
var info = codec.Info;
// get the scale that is nearest to what we want (eg: jpg returned 512)
var supportedScale = codec.GetScaledDimensions(horizontal ? ((float)decodeSize / info.Width) : ((float)decodeSize / info.Height));
// decode the bitmap at the nearest size
var nearest = new SKImageInfo(supportedScale.Width, supportedScale.Height);
var bmp = SKBitmap.Decode(codec, nearest);
// now scale that to the size that we want
var realScale = horizontal ? ((double)info.Height / info.Width) : ((double)info.Width / info.Height);
SKImageInfo desired;
if (horizontal)
{
desired = new SKImageInfo(decodeSize, (int)(realScale * decodeSize));
}
else
{
desired = new SKImageInfo((int)(realScale * decodeSize), decodeSize);
}
if (bmp.Width != desired.Width || bmp.Height != desired.Height)
{
var scaledBmp = bmp.Resize(desired, interpolationMode.ToSKFilterQuality());
bmp.Dispose();
bmp = scaledBmp;
}
_bitmap = bmp;
PixelSize = new PixelSize(bmp.Width, bmp.Height);
Dpi = SkiaPlatform.DefaultDpi;
}
}
/// <summary>
/// Create new writeable bitmap.
/// </summary>

22
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -188,6 +188,28 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(stream);
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new WriteableWicBitmapImpl(stream, width, true, interpolationMode);
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
return new WriteableWicBitmapImpl(stream, height, false, interpolationMode);
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
return new WriteableWicBitmapImpl(fileName);
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
return new WriteableWicBitmapImpl(stream);
}
/// <inheritdoc />
public IBitmapImpl LoadBitmapToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{

17
src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs

@ -1,4 +1,5 @@
using System;
using System.IO;
using Avalonia.Platform;
using SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat;
@ -7,11 +8,27 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl
{
public WriteableWicBitmapImpl(Stream stream, int decodeSize, bool horizontal,
Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode interpolationMode)
: base(stream, decodeSize, horizontal, interpolationMode)
{
}
public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat, AlphaFormat? alphaFormat)
: base(size, dpi, pixelFormat, alphaFormat)
{
}
public WriteableWicBitmapImpl(Stream stream)
: base(stream)
{
}
public WriteableWicBitmapImpl(string fileName)
: base(fileName)
{
}
class LockedBitmap : ILockedFramebuffer
{
private readonly WriteableWicBitmapImpl _parent;

22
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -61,6 +61,28 @@ namespace Avalonia.Benchmarks
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride)
{
throw new NotImplementedException();

21
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -125,6 +125,27 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_Produce_A_Single_Fallback_Run()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
const string text = "👍 👍 👍 👍";
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
Assert.Equal(1, textLine.TextRuns.Count);
}
}
[Fact]
public void Should_Split_Run_On_Script()
{

22
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -66,6 +66,28 @@ namespace Avalonia.UnitTests
return Mock.Of<IBitmapImpl>();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
return Mock.Of<IBitmapImpl>();

17
tests/Avalonia.Visuals.UnitTests/Rendering/CustomHitTestBorder.cs

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Rendering;
namespace Avalonia.Visuals.UnitTests.Rendering
{
internal class CustomHitTestBorder : Border, ICustomHitTest
{
public bool HitTest(Point point)
{
// Move hit testing window halfway to the left
return Bounds
.WithX(Bounds.X - Bounds.Width / 2)
.Contains(point);
}
}
}

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

@ -1,6 +1,9 @@
using System.Linq;
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
@ -9,8 +12,6 @@ using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
using System;
using Avalonia.Controls.Shapes;
namespace Avalonia.Visuals.UnitTests.Rendering
{
@ -501,6 +502,42 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
}
[Fact]
public void HitTest_Should_Accommodate_ICustomHitTest()
{
using (TestApplication())
{
Border border;
var root = new TestRoot
{
Width = 300,
Height = 200,
Child = border = new CustomHitTestBorder
{
Width = 100,
Height = 100,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
root.Renderer = new DeferredRenderer(root, null);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var result = root.Renderer.HitTest(new Point(75, 100), root, null);
Assert.Equal(new[] { border }, result);
result = root.Renderer.HitTest(new Point(125, 100), root, null);
Assert.Equal(new[] { border }, result);
result = root.Renderer.HitTest(new Point(175, 100), root, null);
Assert.Empty(result);
}
}
private IDisposable TestApplication()
{
return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);

37
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs

@ -413,6 +413,43 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
}
[Fact]
public void HitTest_Should_Accommodate_ICustomHitTest()
{
using (TestApplication())
{
Border border;
var root = new TestRoot
{
Width = 300,
Height = 200,
Child = border = new CustomHitTestBorder
{
Width = 100,
Height = 100,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
root.Renderer = new ImmediateRenderer(root);
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = root.Renderer.HitTest(new Point(75, 100), root, null).First();
Assert.Equal(border, result);
result = root.Renderer.HitTest(new Point(125, 100), root, null).First();
Assert.Equal(border, result);
result = root.Renderer.HitTest(new Point(175, 100), root, null).First();
Assert.Equal(root, result);
}
}
private IDisposable TestApplication()
{
return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);

22
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

@ -42,6 +42,28 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToWidth(Stream stream, int width,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmapToHeight(Stream stream, int height,
BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(string fileName)
{
throw new NotImplementedException();
}
public IWriteableBitmapImpl LoadWriteableBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
throw new NotImplementedException();

Loading…
Cancel
Save