diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml index bbfbd4b4cd..486cc55d44 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml @@ -6,7 +6,7 @@ A drop-down list. - + Inline Items Inline Item 2 Inline Item 3 diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 9e9b84537b..067d9f462f 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -93,17 +93,17 @@ namespace Avalonia.Animation var oldTransitions = change.OldValue.GetValueOrDefault(); var newTransitions = change.NewValue.GetValueOrDefault(); - if (oldTransitions is object) - { - oldTransitions.CollectionChanged -= TransitionsCollectionChanged; - RemoveTransitions(oldTransitions); - } - if (newTransitions is object) { newTransitions.CollectionChanged += TransitionsCollectionChanged; AddTransitions(newTransitions); } + + if (oldTransitions is object) + { + oldTransitions.CollectionChanged -= TransitionsCollectionChanged; + RemoveTransitions(oldTransitions); + } } else if (_transitionsEnabled && Transitions is object && @@ -111,8 +111,10 @@ namespace Avalonia.Animation !change.Property.IsDirect && change.Priority > BindingPriority.Animation) { - foreach (var transition in Transitions) + for (var i = Transitions.Count -1; i >= 0; --i) { + var transition = Transitions[i]; + if (transition.Property == change.Property) { var state = _transitionState[transition]; diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index e71bcc24a7..6557346b1f 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -51,6 +51,18 @@ namespace Avalonia.Controls public static readonly StyledProperty VirtualizationModeProperty = ItemsPresenter.VirtualizationModeProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty PlaceholderTextProperty = + AvaloniaProperty.Register(nameof(PlaceholderText)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty PlaceholderForegroundProperty = + AvaloniaProperty.Register(nameof(PlaceholderForeground)); + private bool _isDropDownOpen; private Popup _popup; private object _selectionBoxItem; @@ -94,6 +106,24 @@ namespace Avalonia.Controls set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } } + /// + /// Gets or sets the PlaceHolder text. + /// + public string PlaceholderText + { + get { return GetValue(PlaceholderTextProperty); } + set { SetValue(PlaceholderTextProperty, value); } + } + + /// + /// Gets or sets the Brush that renders the placeholder text. + /// + public IBrush PlaceholderForeground + { + get { return GetValue(PlaceholderForegroundProperty); } + set { SetValue(PlaceholderForegroundProperty, value); } + } + /// /// Gets or sets the virtualization mode for the items. /// diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs index 199c9e0ff8..e04c79987f 100644 --- a/src/Avalonia.Controls/ListBoxItem.cs +++ b/src/Avalonia.Controls/ListBoxItem.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Mixins; +using Avalonia.Input; namespace Avalonia.Controls { @@ -19,6 +20,7 @@ namespace Avalonia.Controls static ListBoxItem() { SelectableMixin.Attach(IsSelectedProperty); + PressedMixin.Attach(); FocusableProperty.OverrideDefaultValue(true); } diff --git a/src/Avalonia.Controls/Mixins/PressedMixin.cs b/src/Avalonia.Controls/Mixins/PressedMixin.cs new file mode 100644 index 0000000000..4c5792b4a0 --- /dev/null +++ b/src/Avalonia.Controls/Mixins/PressedMixin.cs @@ -0,0 +1,37 @@ +using Avalonia.Interactivity; +using Avalonia.Input; + +namespace Avalonia.Controls.Mixins +{ + /// + /// Adds pressed functionality to control classes. + /// + /// Adds the ':pressed' class when the item is pressed. + /// + public static class PressedMixin + { + /// + /// Initializes a new instance of the class. + /// + /// The control type. + public static void Attach() where TControl : Control + { + InputElement.PointerPressedEvent.AddClassHandler((x, e) => HandlePointerPressed(x, e), RoutingStrategies.Tunnel); + InputElement.PointerReleasedEvent.AddClassHandler((x, e) => HandlePointerReleased(x), RoutingStrategies.Tunnel); + InputElement.PointerCaptureLostEvent.AddClassHandler((x, e) => HandlePointerReleased(x), RoutingStrategies.Tunnel); + } + + private static void HandlePointerPressed(TControl sender, PointerPressedEventArgs e) where TControl : Control + { + if (e.GetCurrentPoint(sender).Properties.IsLeftButtonPressed) + { + ((IPseudoClasses)sender.Classes).Set(":pressed", true); + } + } + + private static void HandlePointerReleased(TControl sender) where TControl : Control + { + ((IPseudoClasses)sender.Classes).Set(":pressed", false); + } + } +} diff --git a/src/Avalonia.Controls/Platform/IPopupImpl.cs b/src/Avalonia.Controls/Platform/IPopupImpl.cs index 3944695f04..477d5fab43 100644 --- a/src/Avalonia.Controls/Platform/IPopupImpl.cs +++ b/src/Avalonia.Controls/Platform/IPopupImpl.cs @@ -8,5 +8,7 @@ namespace Avalonia.Platform public interface IPopupImpl : IWindowBaseImpl { IPopupPositioner PopupPositioner { get; } + + void SetWindowManagerAddShadowHint(bool enabled); } } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 66f2153b6c..49315e1b25 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -19,6 +19,9 @@ namespace Avalonia.Controls.Primitives /// public class Popup : Control, IVisualTreeHost { + public static readonly StyledProperty WindowManagerAddShadowHintProperty = + AvaloniaProperty.Register(nameof(WindowManagerAddShadowHint), true); + /// /// Defines the property. /// @@ -89,7 +92,7 @@ namespace Avalonia.Controls.Primitives { IsHitTestVisibleProperty.OverrideDefaultValue(false); ChildProperty.Changed.AddClassHandler((x, e) => x.ChildChanged(e)); - IsOpenProperty.Changed.AddClassHandler((x, e) => x.IsOpenChanged((AvaloniaPropertyChangedEventArgs)e)); + IsOpenProperty.Changed.AddClassHandler((x, e) => x.IsOpenChanged((AvaloniaPropertyChangedEventArgs)e)); } /// @@ -104,6 +107,12 @@ namespace Avalonia.Controls.Primitives public IPopupHost? Host => _openState?.PopupHost; + public bool WindowManagerAddShadowHint + { + get { return GetValue(WindowManagerAddShadowHintProperty); } + set { SetValue(WindowManagerAddShadowHintProperty, value); } + } + /// /// Gets or sets the control to display in the popup. /// @@ -293,6 +302,8 @@ namespace Avalonia.Controls.Primitives _openState = new PopupOpenState(topLevel, popupHost, cleanupPopup); + WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint); + popupHost.Show(); using (BeginIgnoringIsOpen()) @@ -332,6 +343,14 @@ namespace Avalonia.Controls.Primitives return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler)); } + private void WindowManagerAddShadowHintChanged(IPopupHost host, bool hint) + { + if(host is PopupRoot pr) + { + pr.PlatformImpl.SetWindowManagerAddShadowHint(hint); + } + } + /// /// Called when the property changes. /// diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 788fe03162..aab7a68795 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -17,14 +17,14 @@ namespace Avalonia.Controls.Primitives public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost { private readonly TopLevel _parent; - private PopupPositionerParameters _positionerParameters; + private PopupPositionerParameters _positionerParameters; /// /// Initializes static members of the class. /// static PopupRoot() { - BackgroundProperty.OverrideDefaultValue(typeof(PopupRoot), Brushes.White); + BackgroundProperty.OverrideDefaultValue(typeof(PopupRoot), Brushes.White); } /// @@ -53,7 +53,7 @@ namespace Avalonia.Controls.Primitives /// Gets the platform-specific window implementation. /// [CanBeNull] - public new IPopupImpl PlatformImpl => (IPopupImpl)base.PlatformImpl; + public new IPopupImpl PlatformImpl => (IPopupImpl)base.PlatformImpl; /// /// Gets the parent control in the event route. diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index e9867e4503..13bc4ed124 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -70,6 +70,14 @@ namespace Avalonia.Controls Brushes.Black, inherits: true); + /// + /// Defines the property. + /// + public static readonly StyledProperty MaxLinesProperty = + AvaloniaProperty.Register( + nameof(MaxLines), + validate: IsValidMaxLines); + /// /// Defines the property. /// @@ -222,6 +230,15 @@ namespace Avalonia.Controls set { SetValue(ForegroundProperty, value); } } + /// + /// Gets or sets the maximum number of text lines. + /// + public int MaxLines + { + get => GetValue(MaxLinesProperty); + set => SetValue(MaxLinesProperty, value); + } + /// /// Gets or sets the control's text wrapping mode. /// @@ -404,7 +421,8 @@ namespace Avalonia.Controls TextTrimming, TextDecorations, constraint.Width, - constraint.Height); + constraint.Height, + MaxLines); } /// @@ -451,5 +469,7 @@ namespace Avalonia.Controls InvalidateMeasure(); } + + private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; } } diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index 586fe4d341..51eb6edbea 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -118,12 +118,11 @@ namespace Avalonia.Controls.Utils pen = new Pen(borderBrush, borderThickness); } - var rrect = new RoundedRect(new Rect(_size), _cornerRadius.TopLeft, _cornerRadius.TopRight, - _cornerRadius.BottomRight, _cornerRadius.BottomLeft); + var rect = new Rect(_size); if (Math.Abs(borderThickness) > double.Epsilon) - { - rrect = rrect.Deflate(borderThickness * 0.5, borderThickness * 0.5); - } + rect = rect.Deflate(borderThickness * 0.5); + var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight, + _cornerRadius.BottomRight, _cornerRadius.BottomLeft); context.PlatformImpl.DrawRectangle(background, pen, rrect, boxShadows); } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index a9665b1519..c2565cc59c 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -146,6 +146,10 @@ namespace Avalonia.DesignerSupport.Remote public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } + public void SetWindowManagerAddShadowHint(bool enabled) + { + } + public WindowTransparencyLevel TransparencyLevel { get; private set; } } diff --git a/src/Avalonia.Input/NavigationDirection.cs b/src/Avalonia.Input/NavigationDirection.cs index 34bedac141..9b9af0b0a6 100644 --- a/src/Avalonia.Input/NavigationDirection.cs +++ b/src/Avalonia.Input/NavigationDirection.cs @@ -102,7 +102,7 @@ namespace Avalonia.Input switch (key) { case Key.Tab: - return (modifiers & KeyModifiers.Shift) != 0 ? + return (modifiers & KeyModifiers.Shift) == 0 ? NavigationDirection.Next : NavigationDirection.Previous; case Key.Up: return NavigationDirection.Up; diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index e4ee293757..c41be1723b 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -59,6 +59,11 @@ namespace Avalonia.Native } public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); + + public void SetWindowManagerAddShadowHint(bool enabled) + { + } + public IPopupPositioner PopupPositioner { get; } } } diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml index 7ce6c5a19f..44318ffa8f 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml @@ -138,7 +138,9 @@ + + False @@ -159,8 +161,8 @@ 24 40 3 - 374 - 0 + 374 + 0 2 0 21 @@ -258,10 +260,10 @@ 0,0,0,0 0,0,0,0 3,0,3,0 - 1 - 0,2,0,2 - -1,0,-1,0 - 12,11,0,13 + 1 + 0,2,0,2 + -1,0,-1,0 + 12,11,0,13 2 2 2 diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml index 026edca4f9..e43a7ab4e7 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml @@ -138,7 +138,9 @@ + + False @@ -158,8 +160,8 @@ 24 40 3 - 374 - 0 + 374 + 0 2 0 21 @@ -257,10 +259,10 @@ 0,0,0,0 0,0,0,0 3,0,3,0 - 1 - 0,2,0,2 - -1,0,-1,0 - 12,11,0,13 + 1 + 0,2,0,2 + -1,0,-1,0 + 12,11,0,13 2 2 2 diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml index ed6f56b211..7a715bbde7 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml @@ -1,6 +1,4 @@ - diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml index ac7b792a9a..5c6286a0bc 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml @@ -1,6 +1,4 @@ - diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index a0f3ef0d62..7364c339f1 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -39,6 +39,136 @@ + + 21 + 64 + 80 + 240 + 1 + 1 + 0,0,0,4 + 2 + 11,5,11,7 + 11,11,11,13 + 11,11,11,13 + Normal + SemiLight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0,4,0,4 + + + + + + + + 0 + + + + + + + + + + + + + + + + @@ -64,6 +194,12 @@ + + + + + 12 + 1 0 @@ -153,5 +289,59 @@ + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index ec4f35664c..15e157f573 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -39,6 +39,136 @@ + + 21 + 64 + 80 + 240 + 1 + 1 + 0,0,0,4 + 2 + 11,5,11,7 + 11,11,11,13 + 11,11,11,13 + Normal + SemiLight + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0,4,0,4 + + + + + + + + 0 + + + + + + + + + + + + + + + + @@ -64,6 +194,12 @@ + + + + + 12 + 1 0 @@ -153,5 +289,59 @@ + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml b/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml index 788b60892b..44e7962e17 100644 --- a/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Fluent/AutoCompleteBox.xaml @@ -1,37 +1,70 @@ - + + + + + + Alabama + Alaska + Arizona + Arkansas + California + Colorado + Connecticut + Delaware + + + + + diff --git a/src/Avalonia.Themes.Fluent/ComboBox.xaml b/src/Avalonia.Themes.Fluent/ComboBox.xaml index 95bd9550a5..1fc657c1a3 100644 --- a/src/Avalonia.Themes.Fluent/ComboBox.xaml +++ b/src/Avalonia.Themes.Fluent/ComboBox.xaml @@ -1,64 +1,192 @@ - + + + + + + Item 1 + Item 2 + + + + Item 1 + Item 2 + + + + + + 0,0,0,4 + 15 + 7 + + 12,5,0,7 + 11,5,32,6 + 32 + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml b/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml index 23456ec744..c26dec3d34 100644 --- a/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml +++ b/src/Avalonia.Themes.Fluent/ComboBoxItem.xaml @@ -1,9 +1,27 @@ + + + + + Item 1 + Item 2 + + + + Item 1 + Item 2 + + + + + - + - + - + - + diff --git a/src/Avalonia.Themes.Fluent/ListBox.xaml b/src/Avalonia.Themes.Fluent/ListBox.xaml index e91d8a6772..7a87387de8 100644 --- a/src/Avalonia.Themes.Fluent/ListBox.xaml +++ b/src/Avalonia.Themes.Fluent/ListBox.xaml @@ -1,31 +1,43 @@ - - + + + + Test + Test + Test + Test + + + + + diff --git a/src/Avalonia.Themes.Fluent/ListBoxItem.xaml b/src/Avalonia.Themes.Fluent/ListBoxItem.xaml index 19a6e3d4ec..430d3c5468 100644 --- a/src/Avalonia.Themes.Fluent/ListBoxItem.xaml +++ b/src/Avalonia.Themes.Fluent/ListBoxItem.xaml @@ -1,12 +1,27 @@ - + + + + + Disabled + Test + Test + + + + + 12,9,12,12 + - + + + + + + + + + + + + + + + - - - + + + + - - + diff --git a/src/Avalonia.Themes.Fluent/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/PopupRoot.xaml index 9af4f5a910..25c71e3493 100644 --- a/src/Avalonia.Themes.Fluent/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/PopupRoot.xaml @@ -1,17 +1,21 @@ - + + + diff --git a/src/Avalonia.Themes.Fluent/RadioButton.xaml b/src/Avalonia.Themes.Fluent/RadioButton.xaml index 4cdb116cdd..23bcfc616a 100644 --- a/src/Avalonia.Themes.Fluent/RadioButton.xaml +++ b/src/Avalonia.Themes.Fluent/RadioButton.xaml @@ -1,60 +1,173 @@ + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index 8d4475d1c3..720185a3ad 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -32,6 +32,7 @@ namespace Avalonia.Media.TextFormatting /// The text decorations. /// The maximum width. /// The maximum height. + /// The maximum number of text lines. /// The text style overrides. public TextLayout( string text, @@ -44,6 +45,7 @@ namespace Avalonia.Media.TextFormatting TextDecorationCollection textDecorations = null, double maxWidth = double.PositiveInfinity, double maxHeight = double.PositiveInfinity, + int maxLines = 0, IReadOnlyList textStyleOverrides = null) { _text = string.IsNullOrEmpty(text) ? @@ -59,6 +61,8 @@ namespace Avalonia.Media.TextFormatting MaxHeight = maxHeight; + MaxLines = maxLines; + UpdateLayout(); } @@ -73,6 +77,12 @@ namespace Avalonia.Media.TextFormatting /// public double MaxHeight { get; } + + /// + /// Gets the maximum number of text lines. + /// + public double MaxLines { get; } + /// /// Gets the text lines. /// @@ -192,7 +202,7 @@ namespace Avalonia.Media.TextFormatting var currentPosition = 0; - while (currentPosition < _text.Length) + while (currentPosition < _text.Length && (MaxLines == 0 || textLines.Count < MaxLines)) { int length; @@ -222,7 +232,7 @@ namespace Avalonia.Media.TextFormatting var remainingLength = length; - while (remainingLength > 0) + while (remainingLength > 0 && (MaxLines == 0 || textLines.Count < MaxLines)) { var textSlice = _text.AsSlice(currentPosition, remainingLength); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 4899f0efc0..1d41fe4bdd 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1098,6 +1098,10 @@ namespace Avalonia.X11 public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) => _transparencyHelper.SetTransparencyRequest(transparencyLevel); + public void SetWindowManagerAddShadowHint(bool enabled) + { + } + public WindowTransparencyLevel TransparencyLevel => _transparencyHelper.CurrentLevel; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs index e266c5ee54..787a2e4cb8 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs @@ -176,7 +176,13 @@ namespace Avalonia.LinuxFramebuffer.Output [DllImport(libdrm, SetLastError = true)] public static extern int drmModeAddFB(int fd, uint width, uint height, byte depth, byte bpp, uint pitch, uint bo_handle, - out uint buf_id); + out uint buf_id); + + [DllImport(libdrm, SetLastError = true)] + public static extern int drmModeAddFB2(int fd, uint width, uint height, + uint pixel_format, uint[] bo_handles, uint[] pitches, + uint[] offsets, out uint buf_id, uint flags); + [DllImport(libdrm, SetLastError = true)] public static extern int drmModeSetCrtc(int fd, uint crtcId, uint bufferId, uint x, uint y, uint *connectors, int count, @@ -261,6 +267,8 @@ namespace Avalonia.LinuxFramebuffer.Output [DllImport(libgbm, SetLastError = true)] public static extern uint gbm_bo_get_stride(IntPtr bo); + [DllImport(libgbm, SetLastError = true)] + public static extern uint gbm_bo_get_format(IntPtr bo); [StructLayout(LayoutKind.Explicit)] public struct GbmBoHandle @@ -278,7 +286,7 @@ namespace Avalonia.LinuxFramebuffer.Output } [DllImport(libgbm, SetLastError = true)] - public static extern ulong gbm_bo_get_handle(IntPtr bo); + public static extern GbmBoHandle gbm_bo_get_handle(IntPtr bo); public static class GbmColorFormats { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 0f5b66befb..7a5d20fc83 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -7,6 +7,8 @@ using Avalonia.OpenGL; using Avalonia.Platform.Interop; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; using static Avalonia.LinuxFramebuffer.Output.LibDrm; +using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats; + namespace Avalonia.LinuxFramebuffer.Output { public unsafe class DrmOutput : IOutputBackend, IGlPlatformSurface, IWindowingPlatformGlFeature @@ -71,11 +73,26 @@ namespace Avalonia.LinuxFramebuffer.Output var w = gbm_bo_get_width(bo); var h = gbm_bo_get_height(bo); var stride = gbm_bo_get_stride(bo); - var handle = gbm_bo_get_handle(bo); + var handle = gbm_bo_get_handle(bo).u32; + var format = gbm_bo_get_format(bo); + + // prepare for the new ioctl call + var handles = new uint[] {handle, 0, 0, 0}; + var pitches = new uint[] {stride, 0, 0, 0}; + var offsets = new uint[] {}; + + var ret = drmModeAddFB2(_card.Fd, w, h, format, handles, pitches, + offsets, out var fbHandle, 0); - var ret = drmModeAddFB(_card.Fd, w, h, 24, 32, stride, (uint)handle, out var fbHandle); if (ret != 0) - throw new Win32Exception(ret, "drmModeAddFb failed"); + { + // legacy fallback + ret = drmModeAddFB(_card.Fd, w, h, 24, 32, stride, (uint)handle, + out fbHandle); + + if (ret != 0) + throw new Win32Exception(ret, $"drmModeAddFb failed {ret}"); + } gbm_bo_set_user_data(bo, new IntPtr((int)fbHandle), FbDestroyDelegate); diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 67db56dd1b..49700710e9 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -6,6 +6,7 @@ + - + diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs index 504cefb9b3..efcf1ea674 100644 --- a/src/Windows/Avalonia.Win32/PopupImpl.cs +++ b/src/Windows/Avalonia.Win32/PopupImpl.cs @@ -7,6 +7,8 @@ namespace Avalonia.Win32 { class PopupImpl : WindowImpl, IPopupImpl { + private bool _dropShadowHint = true; + public override void Show() { UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate); @@ -36,11 +38,7 @@ namespace Avalonia.Win32 IntPtr.Zero, IntPtr.Zero); - var classes = (int)UnmanagedMethods.GetClassLongPtr(result, (int)UnmanagedMethods.ClassLongIndex.GCL_STYLE); - - classes |= (int)UnmanagedMethods.ClassStyles.CS_DROPSHADOW; - - UnmanagedMethods.SetClassLong(result, UnmanagedMethods.ClassLongIndex.GCL_STYLE, new IntPtr(classes)); + EnableBoxShadow(result, _dropShadowHint); return result; } @@ -68,6 +66,32 @@ namespace Avalonia.Win32 //TODO: We ignore the scaling override for now } + private void EnableBoxShadow (IntPtr hwnd, bool enabled) + { + var classes = (int)UnmanagedMethods.GetClassLongPtr(hwnd, (int)UnmanagedMethods.ClassLongIndex.GCL_STYLE); + + if (enabled) + { + classes |= (int)UnmanagedMethods.ClassStyles.CS_DROPSHADOW; + } + else + { + classes &= ~(int)UnmanagedMethods.ClassStyles.CS_DROPSHADOW; + } + + UnmanagedMethods.SetClassLong(hwnd, UnmanagedMethods.ClassLongIndex.GCL_STYLE, new IntPtr(classes)); + } + + public void SetWindowManagerAddShadowHint(bool enabled) + { + _dropShadowHint = enabled; + + if (Handle != null) + { + EnableBoxShadow(Handle.Handle, enabled); + } + } + public IPopupPositioner PopupPositioner { get; } } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index e25c5e4733..e324febfd5 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -36,7 +36,7 @@ namespace Avalonia public class Win32PlatformOptions { public bool UseDeferredRendering { get; set; } = true; - public bool AllowEglInitialization { get; set; } + public bool AllowEglInitialization { get; set; } = true; public bool? EnableMultitouch { get; set; } public bool OverlayPopups { get; set; } } diff --git a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs index b5c61883e7..784f40fe1f 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Layout; +using Avalonia.Media; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -265,6 +266,70 @@ namespace Avalonia.Animation.UnitTests } } + [Fact] + public void Replacing_Transitions_During_Animation_Does_Not_Throw_KeyNotFound() + { + // Issue #4059 + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + Border target; + var clock = new TestClock(); + var root = new TestRoot + { + Clock = clock, + Styles = + { + new Style(x => x.OfType()) + { + Setters = + { + new Setter(Border.TransitionsProperty, + new Transitions + { + new DoubleTransition + { + Property = Border.OpacityProperty, + Duration = TimeSpan.FromSeconds(1), + }, + }), + }, + }, + new Style(x => x.OfType().Class("foo")) + { + Setters = + { + new Setter(Border.TransitionsProperty, + new Transitions + { + new DoubleTransition + { + Property = Border.OpacityProperty, + Duration = TimeSpan.FromSeconds(1), + }, + }), + new Setter(Border.OpacityProperty, 0.0), + }, + }, + }, + Child = target = new Border + { + Background = Brushes.Red, + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + target.Classes.Add("foo"); + clock.Step(TimeSpan.FromSeconds(0)); + clock.Step(TimeSpan.FromSeconds(0.5)); + + Assert.Equal(0.5, target.Opacity); + + target.Classes.Remove("foo"); + } + } + private static Mock CreateTarget() { return CreateTransition(Visual.OpacityProperty); diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs new file mode 100644 index 0000000000..0ff1f40121 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs @@ -0,0 +1,37 @@ +using Avalonia.Controls.Mixins; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Mixins +{ + public class PressedMixinTests + { + private MouseTestHelper _mouse = new MouseTestHelper(); + + [Fact] + public void Selected_Class_Should_Not_Initially_Be_Added() + { + var target = new TestControl(); + + Assert.Empty(target.Classes); + } + + [Fact] + public void Setting_IsSelected_Should_Add_Selected_Class() + { + var target = new TestControl(); + + _mouse.Down(target); + + Assert.Equal(new[] { ":pressed" }, target.Classes); + } + + private class TestControl : Control + { + static TestControl() + { + PressedMixin.Attach(); + } + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 04d0ac0a7a..fe9c7b1261 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -994,7 +994,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.Presenter.ApplyTemplate(); _helper.Down((Interactive)target.Presenter.Panel.Children[3]); - Assert.Equal(new[] { ":selected" }, target.Presenter.Panel.Children[3].Classes); + Assert.Equal(new[] { ":pressed", ":selected" }, target.Presenter.Panel.Children[3].Classes); } [Fact] diff --git a/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs index 0d9fd31e52..a2c9f8b8cd 100644 --- a/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs @@ -506,6 +506,26 @@ namespace Avalonia.Skia.UnitTests } } + [InlineData("0123456789\r\n0123456789\r\n0123456789", 0, 3)] + [InlineData("0123456789\r\n0123456789\r\n0123456789", 1, 1)] + [InlineData("0123456789\r\n0123456789\r\n0123456789", 4, 3)] + [Theory] + public void Should_Not_Exceed_MaxLines(string text, int maxLines, int expectedLines) + { + using (Start()) + { + var layout = new TextLayout( + text, + Typeface.Default, + 12, + Brushes.Black, + maxWidth: 50, + maxLines: maxLines); + + Assert.Equal(expectedLines, layout.TextLines.Count); + } + } + private const string Text = "日本でTest一番読まれている英字新聞・ジャパンタイムズが発信する国内外ニュースと、様々なジャンルの特集記事。"; [Fact(Skip= "Only used for profiling.")]