diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index cb50cba540..22f4e9be1f 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -81,7 +81,7 @@ namespace ControlCatalog
public override void Initialize()
{
- Styles.Insert(0, FluentDark);
+ Styles.Insert(0, FluentLight);
AvaloniaXamlLoader.Load(this);
}
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 790813fda0..bd5beafe29 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -77,8 +77,8 @@
Full Decorations
- Fluent - Dark
Fluent - Light
+ Fluent - Dark
Simple - Light
Simple - Dark
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index b0c205246e..c84f2f06b6 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -38,10 +38,10 @@ namespace ControlCatalog
switch (themes.SelectedIndex)
{
case 0:
- Application.Current.Styles[0] = App.FluentDark;
+ Application.Current.Styles[0] = App.FluentLight;
break;
case 1:
- Application.Current.Styles[0] = App.FluentLight;
+ Application.Current.Styles[0] = App.FluentDark;
break;
case 2:
Application.Current.Styles[0] = App.DefaultLight;
diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml
index 4a1c196917..d4f72f161a 100644
--- a/samples/ControlCatalog/Pages/TextBlockPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml
@@ -18,8 +18,8 @@
-
-
+
+
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 65233f9230..6645d25b5d 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -113,16 +113,8 @@ namespace Avalonia
/// The binding information.
public IBinding this[IndexerDescriptor binding]
{
- get
- {
- return new IndexerBinding(this, binding.Property, binding.Mode);
- }
-
- set
- {
- var sourceBinding = value as IBinding;
- this.Bind(binding.Property, sourceBinding);
- }
+ get { return new IndexerBinding(this, binding.Property, binding.Mode); }
+ set { this.Bind(binding.Property, value); }
}
public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index e9ea942142..e830717b95 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -36,11 +36,7 @@ namespace Avalonia.Controls.Primitives
private Button _headerButton;
private Button _nextButton;
private Button _previousButton;
- private Grid _monthView;
- private Grid _yearView;
private ITemplate _dayTitleTemplate;
- private CalendarButton _lastCalendarButton;
- private CalendarDayButton _lastCalendarDayButton;
private DateTime _currentMonth;
private bool _isMouseLeftButtonDown = false;
@@ -160,38 +156,12 @@ namespace Avalonia.Controls.Primitives
///
/// Gets the Grid that hosts the content when in month mode.
///
- internal Grid MonthView
- {
- get { return _monthView; }
- private set
- {
- if (_monthView != null)
- _monthView.PointerLeave -= MonthView_MouseLeave;
-
- _monthView = value;
-
- if (_monthView != null)
- _monthView.PointerLeave += MonthView_MouseLeave;
- }
- }
+ internal Grid MonthView { get; set; }
///
/// Gets the Grid that hosts the content when in year or decade mode.
///
- internal Grid YearView
- {
- get { return _yearView; }
- private set
- {
- if (_yearView != null)
- _yearView.PointerLeave -= YearView_MouseLeave;
-
- _yearView = value;
-
- if (_yearView != null)
- _yearView.PointerLeave += YearView_MouseLeave;
- }
- }
-
+ internal Grid YearView { get; set; }
+
private void PopulateGrids()
{
if (MonthView != null)
@@ -226,7 +196,6 @@ namespace Avalonia.Controls.Primitives
cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown;
cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp;
cell.PointerEnter += Cell_MouseEnter;
- cell.PointerLeave += Cell_MouseLeave;
cell.Click += Cell_Click;
children.Add(cell);
}
@@ -256,7 +225,6 @@ namespace Avalonia.Controls.Primitives
month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown;
month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp;
month.PointerEnter += Month_MouseEnter;
- month.PointerLeave += Month_MouseLeave;
children.Add(month);
}
}
@@ -937,17 +905,7 @@ namespace Avalonia.Controls.Primitives
}
}
}
- internal void Cell_MouseLeave(object sender, PointerEventArgs e)
- {
- if (_isMouseLeftButtonDown)
- {
- CalendarDayButton b = (CalendarDayButton)sender;
- // The button is in Pressed state. Change the state to normal.
- if (e.Pointer.Captured == b)
- e.Pointer.Capture(null);
- _lastCalendarDayButton = b;
- }
- }
+
internal void Cell_MouseLeftButtonDown(object sender, PointerPressedEventArgs e)
{
if (Owner != null)
@@ -1207,35 +1165,6 @@ namespace Avalonia.Controls.Primitives
}
}
- private void Month_MouseLeave(object sender, PointerEventArgs e)
- {
- if (_isMouseLeftButtonDownYearView)
- {
- CalendarButton b = (CalendarButton)sender;
- // The button is in Pressed state. Change the state to normal.
- if (e.Pointer.Captured == b)
- e.Pointer.Capture(null);
- //b.ReleaseMouseCapture();
-
- _lastCalendarButton = b;
- }
- }
- private void MonthView_MouseLeave(object sender, PointerEventArgs e)
- {
- if (_lastCalendarDayButton != null)
- {
- e.Pointer.Capture(_lastCalendarDayButton);
- }
- }
-
- private void YearView_MouseLeave(object sender, PointerEventArgs e)
- {
- if (_lastCalendarButton != null)
- {
- e.Pointer.Capture(_lastCalendarButton);
- }
- }
-
internal void UpdateDisabled(bool isEnabled)
{
PseudoClasses.Set(":calendardisabled", !isEnabled);
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index 3d8ab3ae48..7d4fef009d 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -101,7 +101,7 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
- private Popup _popup;
+ private Popup? _popup;
///
/// Initializes static members of the class.
@@ -145,7 +145,7 @@ namespace Avalonia.Controls
{
var parent = x as Control;
return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ??
- Observable.Return(null);
+ Observable.Return(null);
});
this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope);
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index 6d6398bcda..a54d1ce308 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
@@ -148,6 +148,7 @@ namespace Avalonia.Controls.Platform
{
case Key.Up:
case Key.Down:
+ {
if (item?.IsTopLevel == true)
{
if (item.HasSubMenu && !item.IsSubMenuOpen)
@@ -161,8 +162,10 @@ namespace Avalonia.Controls.Platform
goto default;
}
break;
+ }
case Key.Left:
+ {
if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen)
{
parent.Close();
@@ -174,8 +177,10 @@ namespace Avalonia.Controls.Platform
goto default;
}
break;
+ }
case Key.Right:
+ {
if (item != null && !item.IsTopLevel && item.HasSubMenu)
{
Open(item, true);
@@ -186,8 +191,10 @@ namespace Avalonia.Controls.Platform
goto default;
}
break;
+ }
case Key.Enter:
+ {
if (item != null)
{
if (!item.HasSubMenu)
@@ -202,12 +209,14 @@ namespace Avalonia.Controls.Platform
e.Handled = true;
}
break;
+ }
case Key.Escape:
- if (item?.Parent != null)
+ {
+ if (item?.Parent is IMenuElement parent)
{
- item.Parent.Close();
- item.Parent.Focus();
+ parent.Close();
+ parent.Focus();
}
else
{
@@ -216,8 +225,10 @@ namespace Avalonia.Controls.Platform
e.Handled = true;
break;
+ }
default:
+ {
var direction = e.Key.ToNavigationDirection();
if (direction.HasValue)
@@ -246,6 +257,7 @@ namespace Avalonia.Controls.Platform
}
break;
+ }
}
if (!e.Handled && item?.Parent is IMenuItem parentItem)
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index f5115a2f7c..6a6d37605d 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -77,7 +77,8 @@ namespace Avalonia.Controls.Presenters
static TextPresenter()
{
- AffectsRender(SelectionBrushProperty);
+ AffectsRender(SelectionBrushProperty, TextBlock.ForegroundProperty,
+ SelectionForegroundBrushProperty, CaretBrushProperty);
AffectsMeasure(TextProperty, PasswordCharProperty, RevealPasswordProperty,
TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty,
TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty);
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index e34b3b145f..4317d795f1 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -848,21 +848,6 @@ namespace Avalonia.Controls.Primitives
}
}
- ///
- /// Sets an item container's 'selected' class or .
- ///
- /// The index of the item.
- /// Whether the item should be selected or deselected.
- private void MarkItemSelected(int index, bool selected)
- {
- var container = ItemContainerGenerator?.ContainerFromIndex(index);
-
- if (container != null)
- {
- MarkContainerSelected(container, selected);
- }
- }
-
private void UpdateContainerSelection()
{
if (Presenter?.Panel is IPanel panel)
diff --git a/src/Avalonia.Themes.Fluent/Accents/Base.xaml b/src/Avalonia.Themes.Fluent/Accents/Base.xaml
index 134e804c53..0e34eb3f3b 100644
--- a/src/Avalonia.Themes.Fluent/Accents/Base.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/Base.xaml
@@ -12,7 +12,7 @@
#FF0066CC
#FFFFFFFF
#FF000000
- avares://Avalonia.Themes.Fluent/Assets#Roboto
+ avares://Avalonia.Themes.Fluent/Assets#Inter
14
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
index 4d21068492..abc02a6371 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
@@ -439,7 +439,7 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf
new file mode 100644
index 0000000000..847ffd191b
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf
new file mode 100644
index 0000000000..33267cd799
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf
new file mode 100644
index 0000000000..c22eafe9fb
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf
new file mode 100644
index 0000000000..f782894eea
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf
new file mode 100644
index 0000000000..3b7e686e54
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf
new file mode 100644
index 0000000000..556e972f48
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf
new file mode 100644
index 0000000000..e49058e33d
Binary files /dev/null and b/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Black.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Black.ttf
deleted file mode 100644
index 38f809991d..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Roboto-Black.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Bold.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Bold.ttf
deleted file mode 100644
index 5fd9dd9eed..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Roboto-Bold.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Light.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Light.ttf
deleted file mode 100644
index 113d545eea..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Roboto-Light.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Medium.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Medium.ttf
deleted file mode 100644
index 98c4538462..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Roboto-Medium.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Regular.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Regular.ttf
deleted file mode 100644
index c1c3700fe5..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Roboto-Regular.ttf and /dev/null differ
diff --git a/src/Avalonia.Themes.Fluent/Assets/Roboto-Thin.ttf b/src/Avalonia.Themes.Fluent/Assets/Roboto-Thin.ttf
deleted file mode 100644
index 04b15402d9..0000000000
Binary files a/src/Avalonia.Themes.Fluent/Assets/Roboto-Thin.ttf and /dev/null differ
diff --git a/src/Avalonia.Visuals/Animation/CompositePageTransition.cs b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs
new file mode 100644
index 0000000000..9489914c97
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs
@@ -0,0 +1,61 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
+
+namespace Avalonia.Animation
+{
+ ///
+ /// Defines a composite page transition that can be used to combine multiple transitions.
+ ///
+ ///
+ ///
+ /// Instantiate the in XAML and initialize the
+ /// property in order to have many animations triggered at once.
+ /// For example, you can combine and .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ ///
+ ///
+ public class CompositePageTransition : IPageTransition
+ {
+ ///
+ /// Gets or sets the transitions to be executed. Can be defined from XAML.
+ ///
+ [Content]
+ public List PageTransitions { get; set; } = new List();
+
+ ///
+ /// Starts the animation.
+ ///
+ ///
+ /// The control that is being transitioned away from. May be null.
+ ///
+ ///
+ /// The control that is being transitioned to. May be null.
+ ///
+ ///
+ /// Defines the direction of the transition.
+ ///
+ ///
+ /// A that tracks the progress of the animation.
+ ///
+ public Task Start(Visual from, Visual to, bool forward)
+ {
+ var transitionTasks = PageTransitions
+ .Select(transition => transition.Start(from, to, forward))
+ .ToList();
+ return Task.WhenAll(transitionTasks);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs
index 640f401418..0615b854da 100644
--- a/src/Avalonia.Visuals/Animation/CrossFade.cs
+++ b/src/Avalonia.Visuals/Animation/CrossFade.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Avalonia.Animation.Easings;
using Avalonia.Styling;
using Avalonia.VisualTree;
@@ -74,14 +75,26 @@ namespace Avalonia.Animation
///
public TimeSpan Duration
{
- get
- {
- return _fadeOutAnimation.Duration;
- }
- set
- {
- _fadeOutAnimation.Duration = _fadeInAnimation.Duration = value;
- }
+ get => _fadeOutAnimation.Duration;
+ set => _fadeOutAnimation.Duration = _fadeInAnimation.Duration = value;
+ }
+
+ ///
+ /// Gets or sets element entrance easing.
+ ///
+ public Easing FadeInEasing
+ {
+ get => _fadeInAnimation.Easing;
+ set => _fadeInAnimation.Easing = value;
+ }
+
+ ///
+ /// Gets or sets element exit easing.
+ ///
+ public Easing FadeOutEasing
+ {
+ get => _fadeOutAnimation.Easing;
+ set => _fadeOutAnimation.Easing = value;
}
///
diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs
index 501c8c0ba4..dd5d598e12 100644
--- a/src/Avalonia.Visuals/Animation/PageSlide.cs
+++ b/src/Avalonia.Visuals/Animation/PageSlide.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.VisualTree;
@@ -48,6 +49,16 @@ namespace Avalonia.Animation
/// Gets the duration of the animation.
///
public SlideAxis Orientation { get; set; }
+
+ ///
+ /// Gets or sets element entrance easing.
+ ///
+ public Easing SlideInEasing { get; set; } = new LinearEasing();
+
+ ///
+ /// Gets or sets element exit easing.
+ ///
+ public Easing SlideOutEasing { get; set; } = new LinearEasing();
///
/// Starts the animation.
@@ -75,18 +86,12 @@ namespace Avalonia.Animation
{
var animation = new Animation
{
- Children =
+ Easing = SlideOutEasing,
+ Children =
{
new KeyFrame
{
- Setters =
- {
- new Setter
- {
- Property = translateProperty,
- Value = 0d
- }
- },
+ Setters = { new Setter { Property = translateProperty, Value = 0d } },
Cue = new Cue(0d)
},
new KeyFrame
@@ -100,10 +105,10 @@ namespace Avalonia.Animation
}
},
Cue = new Cue(1d)
- }
- }
+ }
+ },
+ Duration = Duration
};
- animation.Duration = Duration;
tasks.Add(animation.RunAsync(from));
}
@@ -112,9 +117,9 @@ namespace Avalonia.Animation
to.IsVisible = true;
var animation = new Animation
{
+ Easing = SlideInEasing,
Children =
{
-
new KeyFrame
{
Setters =
@@ -129,19 +134,12 @@ namespace Avalonia.Animation
},
new KeyFrame
{
- Setters =
- {
- new Setter
- {
- Property = translateProperty,
- Value = 0d
- }
- },
+ Setters = { new Setter { Property = translateProperty, Value = 0d } },
Cue = new Cue(1d)
}
- }
+ },
+ Duration = Duration
};
- animation.Duration = Duration;
tasks.Add(animation.RunAsync(to));
}
diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs
index 14ab083b4f..155339b985 100644
--- a/src/Avalonia.Visuals/Media/GlyphRun.cs
+++ b/src/Avalonia.Visuals/Media/GlyphRun.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Media
private double _fontRenderingEmSize;
private Size? _size;
private int _biDiLevel;
- private Point? _baselineOrigin;
+ private Point _baselineOrigin;
private ReadOnlySlice _glyphIndices;
private ReadOnlySlice _glyphAdvances;
@@ -97,9 +97,7 @@ namespace Avalonia.Media
{
get
{
- _baselineOrigin ??= CalculateBaselineOrigin();
-
- return _baselineOrigin.Value;
+ return _baselineOrigin;
}
set => Set(ref _baselineOrigin, value);
}
@@ -540,15 +538,6 @@ namespace Avalonia.Media
return GlyphAdvances[index];
}
- ///
- /// Calculates the default baseline origin of the .
- ///
- /// The baseline origin.
- private Point CalculateBaselineOrigin()
- {
- return new Point(0, -GlyphTypeface.Ascent * Scale);
- }
-
///
/// Calculates the size of the .
///
@@ -611,8 +600,6 @@ namespace Avalonia.Media
throw new InvalidOperationException();
}
- _baselineOrigin = new Point(0, -GlyphTypeface.Ascent * Scale);
-
var platformRenderInterface = AvaloniaLocator.Current.GetService();
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
index 09ecc0a026..9f6f2b2f43 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
@@ -52,7 +52,7 @@ namespace Avalonia.Media.TextFormatting
return;
}
- if (Properties.Typeface == null)
+ if (Properties.Typeface == default)
{
return;
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
index 3e85f0f6f0..4a7282af27 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
@@ -43,18 +43,23 @@ namespace Avalonia.Media.TextFormatting
}
///
- /// Measures the number of characters that fits into available width.
+ /// Measures the number of characters that fit into available width.
///
/// The text run.
/// The available width.
- ///
- internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth)
+ /// The count of fitting characters.
+ ///
+ /// true if characters fit into the available width; otherwise, false.
+ ///
+ internal static bool TryMeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth, out int count)
{
var glyphRun = textCharacters.GlyphRun;
if (glyphRun.Size.Width < availableWidth)
{
- return glyphRun.Characters.Length;
+ count = glyphRun.Characters.Length;
+
+ return true;
}
var glyphCount = 0;
@@ -96,21 +101,34 @@ namespace Avalonia.Media.TextFormatting
}
}
+ if (glyphCount == 0)
+ {
+ count = 0;
+
+ return false;
+ }
+
if (glyphCount == glyphRun.GlyphIndices.Length)
{
- return glyphRun.Characters.Length;
+ count = glyphRun.Characters.Length;
+
+ return true;
}
if (glyphRun.GlyphClusters.IsEmpty)
{
- return glyphCount;
+ count = glyphCount;
+
+ return true;
}
var firstCluster = glyphRun.GlyphClusters[0];
var lastCluster = glyphRun.GlyphClusters[glyphCount];
- return lastCluster - firstCluster;
+ count = lastCluster - firstCluster;
+
+ return count > 0;
}
///
@@ -350,29 +368,38 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth + currentRun.Size.Width > availableWidth)
{
- var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);
-
var breakFound = false;
var currentBreakPosition = 0;
- if (measuredLength < currentRun.Text.Length)
+ if (TryMeasureCharacters(currentRun, paragraphWidth - currentWidth, out var measuredLength))
{
- var lineBreaker = new LineBreakEnumerator(currentRun.Text);
-
- while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
+ if (measuredLength < currentRun.Text.Length)
{
- var nextBreakPosition = lineBreaker.Current.PositionWrap;
+ var lineBreaker = new LineBreakEnumerator(currentRun.Text);
- if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
+ while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
- break;
- }
+ var nextBreakPosition = lineBreaker.Current.PositionWrap;
+
+ if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
+ {
+ break;
+ }
- breakFound = lineBreaker.Current.Required ||
- lineBreaker.Current.PositionWrap != currentRun.Text.Length;
+ breakFound = lineBreaker.Current.Required ||
+ lineBreaker.Current.PositionWrap != currentRun.Text.Length;
- currentBreakPosition = nextBreakPosition;
+ currentBreakPosition = nextBreakPosition;
+ }
+ }
+ }
+ else
+ {
+ // Make sure we wrap at least one character.
+ if (currentLength == 0)
+ {
+ measuredLength = 1;
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
index f5e87d097b..d13b4836ea 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
@@ -39,7 +39,9 @@ namespace Avalonia.Media.TextFormatting
foreach (var textRun in _textRuns)
{
- using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, 0)))
+ var offsetY = LineMetrics.TextBaseline;
+
+ using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, offsetY)))
{
textRun.Draw(drawingContext);
}
@@ -75,37 +77,35 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth > availableWidth)
{
- var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth);
-
- var currentBreakPosition = 0;
-
- if (measuredLength < textRange.End)
+ if (TextFormatterImpl.TryMeasureCharacters(currentRun, availableWidth, out var measuredLength))
{
- var lineBreaker = new LineBreakEnumerator(currentRun.Text);
-
- while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
+ if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord && measuredLength < textRange.End)
{
- var nextBreakPosition = lineBreaker.Current.PositionWrap;
+ var currentBreakPosition = 0;
- if (nextBreakPosition == 0)
- {
- break;
- }
+ var lineBreaker = new LineBreakEnumerator(currentRun.Text);
- if (nextBreakPosition > measuredLength)
+ while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
- break;
+ var nextBreakPosition = lineBreaker.Current.PositionWrap;
+
+ if (nextBreakPosition == 0)
+ {
+ break;
+ }
+
+ if (nextBreakPosition > measuredLength)
+ {
+ break;
+ }
+
+ currentBreakPosition = nextBreakPosition;
}
- currentBreakPosition = nextBreakPosition;
+ measuredLength = currentBreakPosition;
}
}
- if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord)
- {
- measuredLength = currentBreakPosition;
- }
-
collapsedLength += measuredLength;
var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength);
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 98528a128a..05a8daaec8 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -583,14 +583,45 @@ namespace Avalonia.Skia
var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
var radius = (float)(radialGradient.Radius * targetSize.Width);
- // TODO: There is no SetAlpha in SkiaSharp
- //paint.setAlpha(128);
+ var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
- // would be nice to cache these shaders possibly?
- using (var shader =
- SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
+ if (origin.Equals(center))
{
- paintWrapper.Paint.Shader = shader;
+ // when the origin is the same as the center the Skia RadialGradient acts the same as D2D
+ using (var shader =
+ SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
+ {
+ paintWrapper.Paint.Shader = shader;
+ }
+ }
+ else
+ {
+ // when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D
+
+ // reverse the order of the stops to match D2D
+ var reversedColors = new SKColor[stopColors.Length];
+ Array.Copy(stopColors, reversedColors, stopColors.Length);
+ Array.Reverse(reversedColors);
+
+ // and then reverse the reference point of the stops
+ var reversedStops = new float[stopOffsets.Length];
+ for (var i = 0; i < stopOffsets.Length; i++)
+ {
+ reversedStops[i] = stopOffsets[i];
+ if (reversedStops[i] > 0 && reversedStops[i] < 1)
+ {
+ reversedStops[i] = Math.Abs(1 - stopOffsets[i]);
+ }
+ }
+
+ // compose with a background colour of the final stop to match D2D's behaviour of filling with the final color
+ using (var shader = SKShader.CreateCompose(
+ SKShader.CreateColor(reversedColors[0]),
+ SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
+ ))
+ {
+ paintWrapper.Paint.Shader = shader;
+ }
}
break;
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 25a34561fc..a03e1ffc22 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -200,6 +200,10 @@ namespace Avalonia.Win32
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
}
+ // Mouse capture is lost
+ case WindowsMessage.WM_CANCELMODE:
+ _mouseDevice.Capture(null);
+ break;
case WindowsMessage.WM_MOUSEMOVE:
{
diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
index 9beb860bee..2941b8dc34 100644
--- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
public RadialGradientBrushTests() : base(@"Media\RadialGradientBrush")
{
}
-
+
[Fact]
public async Task RadialGradientBrush_RedBlue()
{
@@ -43,5 +43,127 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
+
+ ///
+ /// Tests using a GradientOrigin that falls inside of the circle described by Center/Radius.
+ ///
+ [Fact]
+ public async Task RadialGradientBrush_RedBlue_Offset_Inside()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ Background = new RadialGradientBrush
+ {
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Blue, Offset = 1 }
+ },
+ GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative)
+ }
+ }
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
+
+ ///
+ /// Tests using a GradientOrigin that falls outside of the circle described by Center/Radius.
+ ///
+ [Fact]
+ public async Task RadialGradientBrush_RedBlue_Offset_Outside()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ Background = new RadialGradientBrush
+ {
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Blue, Offset = 1 }
+ },
+ GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative)
+ }
+ }
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
+
+ ///
+ /// Tests using a GradientOrigin that falls inside of the circle described by Center/Radius.
+ ///
+ [Fact]
+ public async Task RadialGradientBrush_RedGreenBlue_Offset_Inside()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ Background = new RadialGradientBrush
+ {
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Green, Offset = 0.5 },
+ new GradientStop { Color = Colors.Blue, Offset = 1 }
+ },
+ GradientOrigin = new RelativePoint(0.25, 0.25, RelativeUnit.Relative),
+ Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative),
+ Radius = 0.5
+ }
+ }
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
+
+ ///
+ /// Tests using a GradientOrigin that falls outside of the circle described by Center/Radius.
+ ///
+ [Fact]
+ public async Task RadialGradientBrush_RedGreenBlue_Offset_Outside()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Border
+ {
+ Background = new RadialGradientBrush
+ {
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Green, Offset = 0.25 },
+ new GradientStop { Color = Colors.Blue, Offset = 1 }
+ },
+ GradientOrigin = new RelativePoint(0.1, 0.1, RelativeUnit.Relative),
+ Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative),
+ Radius = 0.5
+ }
+ }
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index adcc79e029..7f9713930a 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -293,6 +293,34 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
+ [Fact]
+ public void Wrap_Should_Not_Produce_Empty_Lines()
+ {
+ using (Start())
+ {
+ const string text = "012345";
+
+ var defaultProperties = new GenericTextRunProperties(Typeface.Default);
+ var paragraphProperties = new GenericTextParagraphProperties(defaultProperties, textWrapping: TextWrapping.Wrap);
+ var textSource = new SingleBufferTextSource(text, defaultProperties);
+ var formatter = new TextFormatterImpl();
+
+ var textSourceIndex = 0;
+
+ while (textSourceIndex < text.Length)
+ {
+ var textLine =
+ formatter.FormatLine(textSource, textSourceIndex, 3, paragraphProperties);
+
+ Assert.NotEqual(0, textLine.TextRange.Length);
+
+ textSourceIndex += textLine.TextRange.Length;
+ }
+
+ Assert.Equal(text.Length, textSourceIndex);
+ }
+ }
+
public static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
index f3e1c37705..26e8ce4797 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
@@ -575,6 +575,24 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
+ [Fact]
+ public void Should_Wrap_Min_OneCharacter_EveryLine()
+ {
+ using (Start())
+ {
+ var layout = new TextLayout(
+ s_singleLineText,
+ Typeface.Default,
+ 12,
+ Brushes.Black,
+ textWrapping: TextWrapping.Wrap,
+ maxWidth: 3);
+
+ //every character should be new line as there not enough space for even one character
+ Assert.Equal(s_singleLineText.Length, layout.TextLines.Count);
+ }
+ }
+
private static IDisposable Start()
{
var disposable = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png
new file mode 100644
index 0000000000..4d690abee5
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png
new file mode 100644
index 0000000000..667aa9151e
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png
new file mode 100644
index 0000000000..1d660f79d1
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png
new file mode 100644
index 0000000000..ea1009b54a
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png
new file mode 100644
index 0000000000..4d690abee5
Binary files /dev/null and b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Inside.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png
new file mode 100644
index 0000000000..667aa9151e
Binary files /dev/null and b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedBlue_Offset_Outside.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png
new file mode 100644
index 0000000000..1d660f79d1
Binary files /dev/null and b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Inside.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png
new file mode 100644
index 0000000000..ea1009b54a
Binary files /dev/null and b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_RedGreenBlue_Offset_Outside.expected.png differ