From e123a737cc72d11c649c9989229a8851cfc769d3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 May 2019 14:55:20 +0200 Subject: [PATCH 01/44] Added failing tests for #2501 --- .../ButtonTests.cs | 15 ++++++++++++ .../MenuItemTests.cs | 24 ++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 9a751d4953..d1872c5b9e 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -31,6 +31,21 @@ namespace Avalonia.Controls.UnitTests Assert.False(target.IsEnabled); } + [Fact] + public void Button_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False() + { + var command = new TestCommand(true); + var target = new Button + { + IsEnabled = false, + Command = command, + }; + + var root = new TestRoot { Child = target }; + + Assert.False(((IInputElement)target).IsEnabledCore); + } + [Fact] public void Button_Is_Disabled_When_Bound_Command_Doesnt_Exist() { diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index 32d154249c..704c79155a 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Windows.Input; +using Avalonia.Input; using Avalonia.UnitTests; using Xunit; @@ -58,10 +59,31 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(0, command.SubscriptionCount); } + [Fact] + public void MenuItem_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False() + { + var command = new TestCommand(true); + var target = new MenuItem + { + IsEnabled = false, + Command = command, + }; + + var root = new TestRoot { Child = target }; + + Assert.False(((IInputElement)target).IsEnabledCore); + } + private class TestCommand : ICommand { + private bool _enabled; private EventHandler _canExecuteChanged; + public TestCommand(bool enabled = true) + { + _enabled = enabled; + } + public int SubscriptionCount { get; private set; } public event EventHandler CanExecuteChanged @@ -70,7 +92,7 @@ namespace Avalonia.Controls.UnitTests remove { _canExecuteChanged -= value; --SubscriptionCount; } } - public bool CanExecute(object parameter) => true; + public bool CanExecute(object parameter) => _enabled; public void Execute(object parameter) { From e6be9b7c5acf1c26d9f519707ac631bb98fe0984 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 May 2019 15:09:13 +0200 Subject: [PATCH 02/44] Renamed IsEnabledCore -> IsEffectivelyEnabled. I now understand how WPF's `IsEnabledCore` works, and it's not like this. Rename `IsEnabledCore` to `IsEffectivelyEnabled` so that we can add a new `IsEnabledCore` property which works like WPF's. This also aligns with the existing `IsEffectivelyVisible` property. --- src/Avalonia.Controls/ComboBox.cs | 2 +- src/Avalonia.Input/FocusManager.cs | 2 +- src/Avalonia.Input/IInputElement.cs | 6 +-- src/Avalonia.Input/InputElement.cs | 42 +++++++++---------- src/Avalonia.Input/InputExtensions.cs | 2 +- .../Navigation/FocusExtensions.cs | 4 +- .../ButtonTests.cs | 2 +- .../MenuItemTests.cs | 2 +- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index bf79e192c5..5d427df5a6 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -302,7 +302,7 @@ namespace Avalonia.Controls } } - private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible; + private bool CanFocus(IControl control) => control.Focusable && control.IsEffectivelyEnabled && control.IsVisible; private void UpdateSelectionBoxItem(object item) { diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs index 102da6efc4..1603b250b8 100644 --- a/src/Avalonia.Input/FocusManager.cs +++ b/src/Avalonia.Input/FocusManager.cs @@ -146,7 +146,7 @@ namespace Avalonia.Input /// /// The element. /// True if the element can be focused. - private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; + private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible; /// /// Gets the focus scope ancestors of the specified control, traversing popups. diff --git a/src/Avalonia.Input/IInputElement.cs b/src/Avalonia.Input/IInputElement.cs index c9924dbffb..9247fb48a9 100644 --- a/src/Avalonia.Input/IInputElement.cs +++ b/src/Avalonia.Input/IInputElement.cs @@ -83,14 +83,14 @@ namespace Avalonia.Input Cursor Cursor { get; } /// - /// Gets a value indicating whether the control is effectively enabled for user interaction. + /// Gets a value indicating whether this control and all its parents are enabled. /// /// /// The property is used to toggle the enabled state for individual - /// controls. The property takes into account the + /// controls. The property takes into account the /// value of this control and its parent controls. /// - bool IsEnabledCore { get; } + bool IsEffectivelyEnabled { get; } /// /// Gets a value indicating whether the control is focused. diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 07e04486ec..a1b00f47fb 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -27,10 +27,10 @@ namespace Avalonia.Input AvaloniaProperty.Register(nameof(IsEnabled), true); /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty IsEnabledCoreProperty = - AvaloniaProperty.Register(nameof(IsEnabledCore), true); + public static readonly StyledProperty IsEffectivelyEnabledProperty = + AvaloniaProperty.Register(nameof(IsEffectivelyEnabled), true); /// /// Gets or sets associated mouse cursor. @@ -168,7 +168,7 @@ namespace Avalonia.Input PointerReleasedEvent.AddClassHandler(x => x.OnPointerReleased); PointerWheelChangedEvent.AddClassHandler(x => x.OnPointerWheelChanged); - PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled"); + PseudoClass(IsEffectivelyEnabledProperty, x => !x, ":disabled"); PseudoClass(IsFocusedProperty, ":focus"); PseudoClass(IsPointerOverProperty, ":pointerover"); } @@ -349,23 +349,23 @@ namespace Avalonia.Input /// /// /// The property is used to toggle the enabled state for individual - /// controls. The property takes into account the + /// controls. The property takes into account the /// value of this control and its parent controls. /// - bool IInputElement.IsEnabledCore => IsEnabledCore; + bool IInputElement.IsEffectivelyEnabled => IsEffectivelyEnabled; /// /// Gets a value indicating whether the control is effectively enabled for user interaction. /// /// /// The property is used to toggle the enabled state for individual - /// controls. The property takes into account the + /// controls. The property takes into account the /// value of this control and its parent controls. /// - protected bool IsEnabledCore + protected bool IsEffectivelyEnabled { - get { return GetValue(IsEnabledCoreProperty); } - set { SetValue(IsEnabledCoreProperty, value); } + get { return GetValue(IsEffectivelyEnabledProperty); } + set { SetValue(IsEffectivelyEnabledProperty, value); } } public List KeyBindings { get; } = new List(); @@ -393,7 +393,7 @@ namespace Avalonia.Input protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTreeCore(e); - UpdateIsEnabledCore(); + UpdateIsEffectivelyEnabled(); } /// @@ -488,7 +488,7 @@ namespace Avalonia.Input private static void IsEnabledChanged(AvaloniaPropertyChangedEventArgs e) { - ((InputElement)e.Sender).UpdateIsEnabledCore(); + ((InputElement)e.Sender).UpdateIsEffectivelyEnabled(); } /// @@ -512,32 +512,32 @@ namespace Avalonia.Input } /// - /// Updates the property value. + /// Updates the property value. /// - private void UpdateIsEnabledCore() + private void UpdateIsEffectivelyEnabled() { - UpdateIsEnabledCore(this.GetVisualParent()); + UpdateIsEffectivelyEnabled(this.GetVisualParent()); } /// - /// Updates the property based on the parent's - /// . + /// Updates the property based on the parent's + /// . /// /// The parent control. - private void UpdateIsEnabledCore(InputElement parent) + private void UpdateIsEffectivelyEnabled(InputElement parent) { if (parent != null) { - IsEnabledCore = IsEnabled && parent.IsEnabledCore; + IsEffectivelyEnabled = IsEnabled && parent.IsEffectivelyEnabled; } else { - IsEnabledCore = IsEnabled; + IsEffectivelyEnabled = IsEnabled; } foreach (var child in this.GetVisualChildren().OfType()) { - child.UpdateIsEnabledCore(this); + child.UpdateIsEffectivelyEnabled(this); } } } diff --git a/src/Avalonia.Input/InputExtensions.cs b/src/Avalonia.Input/InputExtensions.cs index f184e41998..c1d0729560 100644 --- a/src/Avalonia.Input/InputExtensions.cs +++ b/src/Avalonia.Input/InputExtensions.cs @@ -45,7 +45,7 @@ namespace Avalonia.Input return element != null && element.IsVisible && element.IsHitTestVisible && - element.IsEnabledCore && + element.IsEffectivelyEnabled && element.IsAttachedToVisualTree; } } diff --git a/src/Avalonia.Input/Navigation/FocusExtensions.cs b/src/Avalonia.Input/Navigation/FocusExtensions.cs index 41e7c4cd7b..794dc63f84 100644 --- a/src/Avalonia.Input/Navigation/FocusExtensions.cs +++ b/src/Avalonia.Input/Navigation/FocusExtensions.cs @@ -13,13 +13,13 @@ namespace Avalonia.Input.Navigation /// /// The element. /// True if the element can be focused. - public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; + public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible; /// /// Checks if descendants of the specified element can be focused. /// /// The element. /// True if descendants of the element can be focused. - public static bool CanFocusDescendants(this IInputElement e) => e.IsEnabledCore && e.IsVisible; + public static bool CanFocusDescendants(this IInputElement e) => e.IsEffectivelyEnabled && e.IsVisible; } } diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index d1872c5b9e..9255b00e50 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -43,7 +43,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot { Child = target }; - Assert.False(((IInputElement)target).IsEnabledCore); + Assert.False(((IInputElement)target).IsEffectivelyEnabled); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index 704c79155a..ebf2c72ab4 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -71,7 +71,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot { Child = target }; - Assert.False(((IInputElement)target).IsEnabledCore); + Assert.False(((IInputElement)target).IsEffectivelyEnabled); } private class TestCommand : ICommand From 38d68865fde632c113833d8148f312300c4cc1c8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 May 2019 17:14:44 +0200 Subject: [PATCH 03/44] Correctly handle command.CanExecute state. Added a new `IsEnabledCore` property to `InputElement` which is overridden in `Button` and `MenuItem` to override the `IsEffectivelyEnabled` state with the enabled state of the command. Also add data validation of the `Command` property to `MenuItem` to make it behave the same as `Button` when `Command` is bound to a non-existent property. Fixes #2501 --- src/Avalonia.Controls/Button.cs | 23 +++- src/Avalonia.Controls/MenuItem.cs | 36 ++++-- src/Avalonia.Input/InputElement.cs | 67 +++++------ .../ButtonTests.cs | 29 +++-- .../MenuItemTests.cs | 113 +++++++++++++++--- .../InputElement_Enabled.cs | 101 ++++++++++++++++ 6 files changed, 294 insertions(+), 75 deletions(-) create mode 100644 tests/Avalonia.Input.UnitTests/InputElement_Enabled.cs diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index cc9e6b7444..c47413c14b 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -33,8 +33,6 @@ namespace Avalonia.Controls /// public class Button : ContentControl { - private ICommand _command; - /// /// Defines the property. /// @@ -75,6 +73,9 @@ namespace Avalonia.Controls public static readonly StyledProperty IsPressedProperty = AvaloniaProperty.Register(nameof(IsPressed)); + private ICommand _command; + private bool _commandCanExecute = true; + /// /// Initializes static members of the class. /// @@ -147,6 +148,8 @@ namespace Avalonia.Controls private set { SetValue(IsPressedProperty, value); } } + protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { @@ -289,7 +292,11 @@ namespace Avalonia.Controls { if (status?.ErrorType == BindingErrorType.Error) { - IsEnabled = false; + if (_commandCanExecute) + { + _commandCanExecute = false; + UpdateIsEffectivelyEnabled(); + } } } } @@ -348,9 +355,13 @@ namespace Avalonia.Controls /// The event args. private void CanExecuteChanged(object sender, EventArgs e) { - // HACK: Just set the IsEnabled property for the moment. This needs to be changed to - // use IsEnabledCore etc. but it will do for now. - IsEnabled = Command == null || Command.CanExecute(CommandParameter); + var canExecute = Command == null || Command.CanExecute(CommandParameter); + + if (canExecute != _commandCanExecute) + { + _commandCanExecute = canExecute; + UpdateIsEffectivelyEnabled(); + } } /// diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index d8473dc613..4cd215c238 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -9,6 +9,7 @@ using Avalonia.Controls.Generators; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; @@ -20,8 +21,6 @@ namespace Avalonia.Controls /// public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable { - private ICommand _command; - /// /// Defines the property. /// @@ -91,9 +90,8 @@ namespace Avalonia.Controls private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel()); - /// - /// The submenu popup. - /// + private ICommand _command; + private bool _commandCanExecute = true; private Popup _popup; /// @@ -231,6 +229,8 @@ namespace Avalonia.Controls /// IMenuElement IMenuItem.Parent => Parent as IMenuElement; + protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; + /// bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap); @@ -400,6 +400,22 @@ namespace Avalonia.Controls } } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status) + { + base.UpdateDataValidation(property, status); + if (property == CommandProperty) + { + if (status?.ErrorType == BindingErrorType.Error) + { + if (_commandCanExecute) + { + _commandCanExecute = false; + UpdateIsEffectivelyEnabled(); + } + } + } + } + /// /// Closes all submenus of the menu item. /// @@ -443,9 +459,13 @@ namespace Avalonia.Controls /// The event args. private void CanExecuteChanged(object sender, EventArgs e) { - // HACK: Just set the IsEnabled property for the moment. This needs to be changed to - // use IsEnabledCore etc. but it will do for now. - IsEnabled = Command == null || Command.CanExecute(CommandParameter); + var canExecute = Command == null || Command.CanExecute(CommandParameter); + + if (canExecute != _commandCanExecute) + { + _commandCanExecute = canExecute; + UpdateIsEffectivelyEnabled(); + } } /// diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index a1b00f47fb..e1183e7154 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -29,8 +29,10 @@ namespace Avalonia.Input /// /// Defines the property. /// - public static readonly StyledProperty IsEffectivelyEnabledProperty = - AvaloniaProperty.Register(nameof(IsEffectivelyEnabled), true); + public static readonly DirectProperty IsEffectivelyEnabledProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsEffectivelyEnabled), + o => o.IsEffectivelyEnabled); /// /// Gets or sets associated mouse cursor. @@ -146,6 +148,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; + private bool _isEffectivelyEnabled = true; private bool _isFocused; private bool _isPointerOver; @@ -344,31 +347,25 @@ namespace Avalonia.Input internal set { SetAndRaise(IsPointerOverProperty, ref _isPointerOver, value); } } - /// - /// Gets a value indicating whether the control is effectively enabled for user interaction. - /// - /// - /// The property is used to toggle the enabled state for individual - /// controls. The property takes into account the - /// value of this control and its parent controls. - /// - bool IInputElement.IsEffectivelyEnabled => IsEffectivelyEnabled; + /// + public bool IsEffectivelyEnabled + { + get => _isEffectivelyEnabled; + private set => SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value); + } + + public List KeyBindings { get; } = new List(); /// - /// Gets a value indicating whether the control is effectively enabled for user interaction. + /// Allows a derived class to override the enabled state of the control. /// /// - /// The property is used to toggle the enabled state for individual - /// controls. The property takes into account the - /// value of this control and its parent controls. + /// Derived controls may wish to disable the enabled state of the control without overwriting the + /// user-supplied setting. This can be done by overriding this property + /// to return the overridden enabled state. If the value returned from + /// should change, then the derived control should call . /// - protected bool IsEffectivelyEnabled - { - get { return GetValue(IsEffectivelyEnabledProperty); } - set { SetValue(IsEffectivelyEnabledProperty, value); } - } - - public List KeyBindings { get; } = new List(); + protected virtual bool IsEnabledCore => IsEnabled; /// /// Focuses the control. @@ -486,6 +483,15 @@ namespace Avalonia.Input { } + /// + /// Updates the property value according to the parent + /// control's enabled state and . + /// + protected void UpdateIsEffectivelyEnabled() + { + UpdateIsEffectivelyEnabled(this.GetVisualParent()); + } + private static void IsEnabledChanged(AvaloniaPropertyChangedEventArgs e) { ((InputElement)e.Sender).UpdateIsEffectivelyEnabled(); @@ -511,14 +517,6 @@ namespace Avalonia.Input OnPointerLeave(e); } - /// - /// Updates the property value. - /// - private void UpdateIsEffectivelyEnabled() - { - UpdateIsEffectivelyEnabled(this.GetVisualParent()); - } - /// /// Updates the property based on the parent's /// . @@ -526,14 +524,7 @@ namespace Avalonia.Input /// The parent control. private void UpdateIsEffectivelyEnabled(InputElement parent) { - if (parent != null) - { - IsEffectivelyEnabled = IsEnabled && parent.IsEffectivelyEnabled; - } - else - { - IsEffectivelyEnabled = IsEnabled; - } + IsEffectivelyEnabled = IsEnabledCore && (parent?.IsEffectivelyEnabled ?? true); foreach (var child in this.GetVisualChildren().OfType()) { diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 9255b00e50..fe0ac47a7d 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -24,11 +24,11 @@ namespace Avalonia.Controls.UnitTests }; var root = new TestRoot { Child = target }; - Assert.False(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); command.IsEnabled = true; - Assert.True(target.IsEnabled); + Assert.True(target.IsEffectivelyEnabled); command.IsEnabled = false; - Assert.False(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); } [Fact] @@ -54,7 +54,8 @@ namespace Avalonia.Controls.UnitTests [!Button.CommandProperty] = new Binding("Command"), }; - Assert.False(target.IsEnabled); + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); } [Fact] @@ -72,8 +73,12 @@ namespace Avalonia.Controls.UnitTests }; Assert.True(target.IsEnabled); + Assert.True(target.IsEffectivelyEnabled); + target.DataContext = null; - Assert.False(target.IsEnabled); + + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); } [Fact] @@ -90,9 +95,13 @@ namespace Avalonia.Controls.UnitTests [!Button.CommandProperty] = new Binding("Command"), }; - Assert.False(target.IsEnabled); + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + target.DataContext = viewModel; + Assert.True(target.IsEnabled); + Assert.True(target.IsEffectivelyEnabled); } [Fact] @@ -109,9 +118,13 @@ namespace Avalonia.Controls.UnitTests [!Button.CommandProperty] = new Binding("Command"), }; - Assert.False(target.IsEnabled); + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + target.DataContext = viewModel; - Assert.False(target.IsEnabled); + + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs index ebf2c72ab4..34371916df 100644 --- a/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MenuItemTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Windows.Input; +using Avalonia.Data; using Avalonia.Input; using Avalonia.UnitTests; using Xunit; @@ -26,6 +27,103 @@ namespace Avalonia.Controls.UnitTests Assert.False(target.Focusable); } + + [Fact] + public void MenuItem_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False() + { + var command = new TestCommand(true); + var target = new MenuItem + { + IsEnabled = false, + Command = command, + }; + + var root = new TestRoot { Child = target }; + + Assert.False(((IInputElement)target).IsEffectivelyEnabled); + } + + [Fact] + public void MenuItem_Is_Disabled_When_Bound_Command_Doesnt_Exist() + { + var target = new MenuItem + { + [!MenuItem.CommandProperty] = new Binding("Command"), + }; + + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + } + + [Fact] + public void MenuItem_Is_Disabled_When_Bound_Command_Is_Removed() + { + var viewModel = new + { + Command = new TestCommand(true), + }; + + var target = new MenuItem + { + DataContext = viewModel, + [!MenuItem.CommandProperty] = new Binding("Command"), + }; + + Assert.True(target.IsEnabled); + Assert.True(target.IsEffectivelyEnabled); + + target.DataContext = null; + + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + } + + [Fact] + public void MenuItem_Is_Enabled_When_Bound_Command_Is_Added() + { + var viewModel = new + { + Command = new TestCommand(true), + }; + + var target = new MenuItem + { + DataContext = new object(), + [!MenuItem.CommandProperty] = new Binding("Command"), + }; + + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + + target.DataContext = viewModel; + + Assert.True(target.IsEnabled); + Assert.True(target.IsEffectivelyEnabled); + } + + [Fact] + public void MenuItem_Is_Disabled_When_Disabled_Bound_Command_Is_Added() + { + var viewModel = new + { + Command = new TestCommand(false), + }; + + var target = new MenuItem + { + DataContext = new object(), + [!MenuItem.CommandProperty] = new Binding("Command"), + }; + + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + + target.DataContext = viewModel; + + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + } + [Fact] public void MenuItem_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree() { @@ -59,21 +157,6 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(0, command.SubscriptionCount); } - [Fact] - public void MenuItem_Is_Disabled_When_Command_Is_Enabled_But_IsEnabled_Is_False() - { - var command = new TestCommand(true); - var target = new MenuItem - { - IsEnabled = false, - Command = command, - }; - - var root = new TestRoot { Child = target }; - - Assert.False(((IInputElement)target).IsEffectivelyEnabled); - } - private class TestCommand : ICommand { private bool _enabled; diff --git a/tests/Avalonia.Input.UnitTests/InputElement_Enabled.cs b/tests/Avalonia.Input.UnitTests/InputElement_Enabled.cs new file mode 100644 index 0000000000..5dd66e3190 --- /dev/null +++ b/tests/Avalonia.Input.UnitTests/InputElement_Enabled.cs @@ -0,0 +1,101 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls; +using Xunit; + +namespace Avalonia.Input.UnitTests +{ + public class InputElement_Enabled + { + [Fact] + public void IsEffectivelyEnabled_Follows_IsEnabled() + { + var target = new Decorator(); + + Assert.True(target.IsEnabled); + Assert.True(target.IsEffectivelyEnabled); + + target.IsEnabled = false; + + Assert.False(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + } + + [Fact] + public void IsEffectivelyEnabled_Follows_Ancestor_IsEnabled() + { + Decorator child; + Decorator grandchild; + var target = new Decorator + { + Child = child = new Decorator + { + Child = grandchild = new Decorator(), + } + }; + + Assert.True(target.IsEnabled); + Assert.True(target.IsEffectivelyEnabled); + Assert.True(child.IsEnabled); + Assert.True(child.IsEffectivelyEnabled); + Assert.True(grandchild.IsEnabled); + Assert.True(grandchild.IsEffectivelyEnabled); + + target.IsEnabled = false; + + Assert.False(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + Assert.True(child.IsEnabled); + Assert.False(child.IsEffectivelyEnabled); + Assert.True(grandchild.IsEnabled); + Assert.False(grandchild.IsEffectivelyEnabled); + } + + [Fact] + public void Disabled_Pseudoclass_Follows_IsEffectivelyEnabled() + { + Decorator child; + var target = new Decorator + { + Child = child = new Decorator() + }; + + Assert.DoesNotContain(":disabled", child.Classes); + + target.IsEnabled = false; + + Assert.Contains(":disabled", child.Classes); + } + + [Fact] + public void IsEffectivelyEnabled_Respects_IsEnabledCore() + { + Decorator child; + var target = new TestControl + { + Child = child = new Decorator() + }; + + target.ShouldEnable = false; + + Assert.True(target.IsEnabled); + Assert.False(target.IsEffectivelyEnabled); + Assert.True(child.IsEnabled); + Assert.False(child.IsEffectivelyEnabled); + } + + private class TestControl : Decorator + { + private bool _shouldEnable; + + public bool ShouldEnable + { + get => _shouldEnable; + set { _shouldEnable = value; UpdateIsEffectivelyEnabled(); } + } + + protected override bool IsEnabledCore => IsEnabled && _shouldEnable; + } + } +} From 5e5b090602bc748d0a84d0108d76268ccb34a1b0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 May 2019 17:24:31 +0200 Subject: [PATCH 04/44] Display a disabed menu item in ControlCatalog. --- samples/ControlCatalog/ViewModels/MenuPageViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index 038f3574cc..88b1bf0b6b 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Reactive; +using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; using ReactiveUI; @@ -11,7 +12,7 @@ namespace ControlCatalog.ViewModels public MenuPageViewModel() { OpenCommand = ReactiveCommand.CreateFromTask(Open); - SaveCommand = ReactiveCommand.Create(Save); + SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false)); OpenRecentCommand = ReactiveCommand.Create(OpenRecent); MenuItems = new[] From 22c5a5e6a1b0443488e5bddb037f1dece754e277 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 May 2019 17:43:43 +0200 Subject: [PATCH 05/44] Fix usage of AddClassHandler in non-static ctor. Property `AddClassHandler`s should only be added in static constructors. In this case it's not so important because `FocusManager` is a singleton, but best to fix the usage anyway. Fixes #2315. --- src/Avalonia.Input/FocusManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs index 102da6efc4..28fb26dca4 100644 --- a/src/Avalonia.Input/FocusManager.cs +++ b/src/Avalonia.Input/FocusManager.cs @@ -23,7 +23,7 @@ namespace Avalonia.Input /// /// Initializes a new instance of the class. /// - public FocusManager() + static FocusManager() { InputElement.PointerPressedEvent.AddClassHandler( typeof(IInputElement), @@ -174,7 +174,7 @@ namespace Avalonia.Input /// /// The event sender. /// The event args. - private void OnPreviewPointerPressed(object sender, RoutedEventArgs e) + private static void OnPreviewPointerPressed(object sender, RoutedEventArgs e) { var ev = (PointerPressedEventArgs)e; @@ -191,7 +191,7 @@ namespace Avalonia.Input if (element != null) { - Focus(element, NavigationMethod.Pointer, ev.InputModifiers); + Instance?.Focus(element, NavigationMethod.Pointer, ev.InputModifiers); } } } From fb08f031094c8904855c1b474bf64a2dd9c3ac34 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 May 2019 17:44:26 +0200 Subject: [PATCH 06/44] Add a note to fix usage of AddClassHandler. I plan on replacing DevTools with something newer soon, so just add a FIXME comment for now. --- src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs index 59c0f82414..7ece790310 100644 --- a/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/ViewModels/EventTreeNode.cs @@ -56,6 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels { if (IsEnabled.GetValueOrDefault() && !_isRegistered) { + // FIXME: This leaks event handlers. _event.AddClassHandler(typeof(object), HandleEvent, (RoutingStrategies)7, handledEventsToo: true); _isRegistered = true; } From 7cdaeac4411bb95d56b7602c3c99a38de17b6437 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 May 2019 18:41:58 +0200 Subject: [PATCH 07/44] Make Rect.Deflate consistent with Rect.Inflate. `Rect.Deflate` was halving the thickness passed into it whereas `Rect.Inflate` wasn't. Make both methods consistent and _not_ halve the thickness. --- src/Avalonia.Controls/Shapes/Ellipse.cs | 2 +- src/Avalonia.Controls/Shapes/Rectangle.cs | 2 +- src/Avalonia.Visuals/Rect.cs | 12 +++++------- .../Controls/CustomRenderTests.cs | 6 +++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/Shapes/Ellipse.cs b/src/Avalonia.Controls/Shapes/Ellipse.cs index 8cb73bf5c6..f80bf5d967 100644 --- a/src/Avalonia.Controls/Shapes/Ellipse.cs +++ b/src/Avalonia.Controls/Shapes/Ellipse.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes protected override Geometry CreateDefiningGeometry() { - var rect = new Rect(Bounds.Size).Deflate(StrokeThickness); + var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2); return new EllipseGeometry(rect); } diff --git a/src/Avalonia.Controls/Shapes/Rectangle.cs b/src/Avalonia.Controls/Shapes/Rectangle.cs index 375b0b719a..b803bde588 100644 --- a/src/Avalonia.Controls/Shapes/Rectangle.cs +++ b/src/Avalonia.Controls/Shapes/Rectangle.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.Shapes protected override Geometry CreateDefiningGeometry() { - var rect = new Rect(Bounds.Size).Deflate(StrokeThickness); + var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2); return new RectangleGeometry(rect); } diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 530a47729f..49d4724b9a 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -256,7 +256,7 @@ namespace Avalonia /// /// Inflates the rectangle. /// - /// The thickness. + /// The thickness to be subtracted for each side of the rectangle. /// The inflated rectangle. public Rect Inflate(double thickness) { @@ -266,7 +266,7 @@ namespace Avalonia /// /// Inflates the rectangle. /// - /// The thickness. + /// The thickness to be subtracted for each side of the rectangle. /// The inflated rectangle. public Rect Inflate(Thickness thickness) { @@ -278,20 +278,18 @@ namespace Avalonia /// /// Deflates the rectangle. /// - /// The thickness. + /// The thickness to be subtracted for each side of the rectangle. /// The deflated rectangle. - /// The deflated rectangle size cannot be less than 0. public Rect Deflate(double thickness) { - return Deflate(new Thickness(thickness / 2)); + return Deflate(new Thickness(thickness)); } /// /// Deflates the rectangle by a . /// - /// The thickness. + /// The thickness to be subtracted for each side of the rectangle. /// The deflated rectangle. - /// The deflated rectangle size cannot be less than 0. public Rect Deflate(Thickness thickness) { return new Rect( diff --git a/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs b/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs index 6a01536b12..d3a935c281 100644 --- a/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs +++ b/tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls new Rect(control.Bounds.Size), 4); - using (context.PushClip(new Rect(control.Bounds.Size).Deflate(20))) + using (context.PushClip(new Rect(control.Bounds.Size).Deflate(10))) { context.FillRectangle( Brushes.Blue, @@ -100,7 +100,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls { context.FillRectangle( Brushes.Blue, - new Rect(control.Bounds.Size).Deflate(20), + new Rect(control.Bounds.Size).Deflate(10), 4); } }), @@ -140,7 +140,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls { context.FillRectangle( Brushes.Blue, - new Rect(control.Bounds.Size).Deflate(20), + new Rect(control.Bounds.Size).Deflate(10), 4); } }), From 88f2bd791d17ff67b6d369a4b19d9d836fb6856e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 16 May 2019 18:58:07 +0200 Subject: [PATCH 08/44] Fix hit testing in RectangleNode. There were two problems here: - As #2370 describes we were tested against the rotated AABB of the rectangle - We were hit-testing non-filled rectangles as filled rectangles Fixes #2370 --- .../Rendering/SceneGraph/RectangleNode.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index 33cc39cbe3..c622dc8a43 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -105,8 +105,28 @@ namespace Avalonia.Rendering.SceneGraph } } - // TODO: This doesn't respect CornerRadius yet. /// - public override bool HitTest(Point p) => Bounds.Contains(p); + public override bool HitTest(Point p) + { + // TODO: This doesn't respect CornerRadius yet. + if (Transform.HasInverse) + { + p *= Transform.Invert(); + + if (Brush != null) + { + var rect = Rect.Inflate((Pen?.Thickness / 2) ?? 0); + return rect.Contains(p); + } + else + { + var borderRect = Rect.Inflate((Pen?.Thickness / 2) ?? 0); + var emptyRect = Rect.Deflate((Pen?.Thickness / 2) ?? 0); + return borderRect.Contains(p) && !emptyRect.Contains(p); + } + } + + return false; + } } } From 2fcad75a6bf1385aaa5cf0b47459d769dff58b49 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 May 2019 08:59:18 +0200 Subject: [PATCH 09/44] Added failing test for #2535. --- .../VisualExtensionsTests.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs diff --git a/tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs new file mode 100644 index 0000000000..a8d8c07d8b --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs @@ -0,0 +1,38 @@ +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Visuals.UnitTests +{ + public class VisualExtensionsTests + { + [Fact] + public void TranslatePoint_Should_Respect_RenderTransforms() + { + Border target; + var root = new TestRoot + { + Width = 100, + Height = 100, + Child = new Decorator + { + Width = 50, + Height = 50, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + RenderTransform = new TranslateTransform(25, 25), + Child = target = new Border(), + } + }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(root.DesiredSize)); + + var result = target.TranslatePoint(new Point(0, 0), root); + + Assert.Equal(new Point(50, 50), result); + } + } +} From 9eac3b6203e5115a6b69ff02f7ee6a98e4fbf7bb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 May 2019 09:05:37 +0200 Subject: [PATCH 10/44] Fix TranslatePoint. - Make `VisualExtensions.TranslatePoint` respect render transforms. - Move `IVisual.TransformToVisual` out of the interface and into `VisualExtensions` as an extension method - Make `TranslatePoint` return a nullable value as `TransformToVisual` does, so it can return null when controls don't have a common ancestor --- src/Avalonia.Input/DragDropDevice.cs | 10 +- src/Avalonia.Input/DragEventArgs.cs | 5 +- .../Rendering/SceneGraph/Scene.cs | 2 +- src/Avalonia.Visuals/Visual.cs | 62 ------------- src/Avalonia.Visuals/VisualExtensions.cs | 92 ++++++++++++++----- src/Avalonia.Visuals/VisualTree/IVisual.cs | 11 --- .../VisualTree/VisualExtensions.cs | 10 +- 7 files changed, 87 insertions(+), 105 deletions(-) diff --git a/src/Avalonia.Input/DragDropDevice.cs b/src/Avalonia.Input/DragDropDevice.cs index 0692b21c66..0b9f09b224 100644 --- a/src/Avalonia.Input/DragDropDevice.cs +++ b/src/Avalonia.Input/DragDropDevice.cs @@ -23,7 +23,13 @@ namespace Avalonia.Input { if (target == null) return DragDropEffects.None; - var args = new DragEventArgs(routedEvent, data, target, inputRoot.TranslatePoint(point, target), modifiers) + + var p = inputRoot.TranslatePoint(point, target); + + if (!p.HasValue) + return DragDropEffects.None; + + var args = new DragEventArgs(routedEvent, data, target, p.Value, modifiers) { RoutedEvent = routedEvent, DragEffects = operation @@ -108,4 +114,4 @@ namespace Avalonia.Input } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Input/DragEventArgs.cs b/src/Avalonia.Input/DragEventArgs.cs index 915ee4ee5c..dc0b76b225 100644 --- a/src/Avalonia.Input/DragEventArgs.cs +++ b/src/Avalonia.Input/DragEventArgs.cs @@ -26,8 +26,9 @@ namespace Avalonia.Input if (_target != null) { - point = _target.TranslatePoint(_targetLocation, relativeTo); + point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point; } + return point; } @@ -41,4 +42,4 @@ namespace Avalonia.Input } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index ad4c475d89..1afc096c98 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -173,7 +173,7 @@ namespace Avalonia.Rendering.SceneGraph if (node.GeometryClip != null) { var controlPoint = Root.Visual.TranslatePoint(p, node.Visual); - clipped = !node.GeometryClip.FillContains(controlPoint); + clipped = !node.GeometryClip.FillContains(controlPoint.Value); } if (!clipped) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index d294f111ad..9e088cb136 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -291,29 +291,6 @@ namespace Avalonia Contract.Requires(context != null); } - /// - /// Returns a transform that transforms the visual's coordinates into the coordinates - /// of the specified . - /// - /// The visual to translate the coordinates to. - /// - /// A containing the transform or null if the visuals don't share a - /// common ancestor. - /// - public Matrix? TransformToVisual(IVisual visual) - { - var common = this.FindCommonVisualAncestor(visual); - - if (common != null) - { - var thisOffset = GetOffsetFrom(common, this); - var thatOffset = GetOffsetFrom(common, visual); - return -thatOffset * thisOffset; - } - - return null; - } - /// /// Indicates that a property change should cause to be /// called. @@ -480,45 +457,6 @@ namespace Avalonia } } - /// - /// Gets the visual offset from the specified ancestor. - /// - /// The ancestor visual. - /// The visual. - /// The visual offset. - private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual) - { - var result = Matrix.Identity; - - while (visual != ancestor) - { - if (visual.RenderTransform?.Value != null) - { - var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size); - var offset = Matrix.CreateTranslation(origin); - var renderTransform = (-offset) * visual.RenderTransform.Value * (offset); - - result *= renderTransform; - } - - var topLeft = visual.Bounds.TopLeft; - - if (topLeft != default) - { - result *= Matrix.CreateTranslation(topLeft); - } - - visual = visual.VisualParent; - - if (visual == null) - { - throw new ArgumentException("'visual' is not a descendant of 'ancestor'."); - } - } - - return result; - } - /// /// Called when a visual's changes. /// diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs index 650921a985..b191d044db 100644 --- a/src/Avalonia.Visuals/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Avalonia.Rendering; using Avalonia.VisualTree; namespace Avalonia @@ -20,10 +19,8 @@ namespace Avalonia /// The point in client coordinates. public static Point PointToClient(this IVisual visual, PixelPoint point) { - var (root, offset) = GetRootAndPosition(visual); - var screenOffset = PixelPoint.FromPoint((Point)offset, root.RenderScaling); - var screenPoint = new PixelPoint(point.X - screenOffset.X, point.Y - screenOffset.Y); - return root.PointToClient(screenPoint); + var rootPoint = visual.VisualRoot.PointToClient(point); + return visual.VisualRoot.TranslatePoint(rootPoint, visual).Value; } /// @@ -34,48 +31,93 @@ namespace Avalonia /// The point in screen coordinates. public static PixelPoint PointToScreen(this IVisual visual, Point point) { - var p = GetRootAndPosition(visual); - return p.Item1.PointToScreen(point + p.Item2); + var p = visual.TranslatePoint(point, visual.VisualRoot); + return visual.VisualRoot.PointToScreen(p.Value); + } + + /// + /// Returns a transform that transforms the visual's coordinates into the coordinates + /// of the specified . + /// + /// The visual whose coordinates are to be transformed. + /// The visual to translate the coordinates to. + /// + /// A containing the transform or null if the visuals don't share a + /// common ancestor. + /// + public static Matrix? TransformToVisual(this IVisual from, IVisual to) + { + var common = from.FindCommonVisualAncestor(to); + + if (common != null) + { + var thisOffset = GetOffsetFrom(common, from); + var thatOffset = GetOffsetFrom(common, to); + return -thatOffset * thisOffset; + } + + return null; } /// /// Translates a point relative to this visual to coordinates that are relative to the specified visual. - /// The visual and relativeTo should be descendants of the same root window /// /// The visual. /// The point value, as relative to this visual. /// The visual to translate the given point into. - /// A point value, now relative to the target visual rather than this source element. - public static Point TranslatePoint(this IVisual visual, Point point, IVisual relativeTo) + /// + /// A point value, now relative to the target visual rather than this source element, or null if the + /// two elements have no common ancestor. + /// + public static Point? TranslatePoint(this IVisual visual, Point point, IVisual relativeTo) { - var pos = GetRootAndPosition(visual); - var relToPos = GetRootAndPosition(relativeTo); + var transform = visual.TransformToVisual(relativeTo); - return point - (relToPos.Item2 - pos.Item2); + if (transform.HasValue) + { + return point.Transform(transform.Value); + } + + return null; } /// - /// Gets the root of the control's visual tree and the position of the control - /// in the root's coordinate space. + /// Gets a transform from an ancestor to a descendent. /// - /// The visual. - /// A tuple containing the root and the position of the control. - private static Tuple GetRootAndPosition(IVisual v) + /// The ancestor visual. + /// The visual. + /// The transform. + private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual) { - var result = new Vector(); + var result = Matrix.Identity; - while (!(v is IRenderRoot)) + while (visual != ancestor) { - result = new Vector(result.X + v.Bounds.X, result.Y + v.Bounds.Y); - v = v.VisualParent; + if (visual.RenderTransform?.Value != null) + { + var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size); + var offset = Matrix.CreateTranslation(origin); + var renderTransform = (-offset) * visual.RenderTransform.Value * (offset); + + result *= renderTransform; + } + + var topLeft = visual.Bounds.TopLeft; + + if (topLeft != default) + { + result *= Matrix.CreateTranslation(topLeft); + } + + visual = visual.VisualParent; - if (v == null) + if (visual == null) { - throw new InvalidOperationException("Control is not attached to visual tree."); + throw new ArgumentException("'visual' is not a descendant of 'ancestor'."); } } - return Tuple.Create((IRenderRoot)v, result); + return result; } } } diff --git a/src/Avalonia.Visuals/VisualTree/IVisual.cs b/src/Avalonia.Visuals/VisualTree/IVisual.cs index 278a802597..dd3686ce3d 100644 --- a/src/Avalonia.Visuals/VisualTree/IVisual.cs +++ b/src/Avalonia.Visuals/VisualTree/IVisual.cs @@ -116,16 +116,5 @@ namespace Avalonia.VisualTree /// /// The context. void Render(DrawingContext context); - - /// - /// Returns a transform that transforms the visual's coordinates into the coordinates - /// of the specified . - /// - /// The visual to translate the coordinates to. - /// - /// A containing the transform or null if the visuals don't share a - /// common ancestor. - /// - Matrix? TransformToVisual(IVisual visual); } } diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index 2d417852fe..567b676b1e 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -132,8 +132,14 @@ namespace Avalonia.VisualTree Contract.Requires(visual != null); var root = visual.GetVisualRoot(); - p = visual.TranslatePoint(p, root); - return root.Renderer.HitTest(p, visual, filter); + var rootPoint = visual.TranslatePoint(p, root); + + if (rootPoint.HasValue) + { + return root.Renderer.HitTest(rootPoint.Value, visual, filter); + } + + return Enumerable.Empty(); } /// From 564d8f1510782c5aaf38ea5d0eb4edd2df483237 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 May 2019 13:56:14 +0200 Subject: [PATCH 11/44] Allow random item heights in VirtualizationDemo. Add a "randomize" and "reset" button in order to test #2144. --- samples/VirtualizationDemo/MainWindow.xaml | 4 +++- .../ViewModels/ItemViewModel.cs | 7 +++++++ .../ViewModels/MainWindowViewModel.cs | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/samples/VirtualizationDemo/MainWindow.xaml b/samples/VirtualizationDemo/MainWindow.xaml index 58970eff01..12137cd03d 100644 --- a/samples/VirtualizationDemo/MainWindow.xaml +++ b/samples/VirtualizationDemo/MainWindow.xaml @@ -39,6 +39,8 @@ + + - + diff --git a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs index e883cdfeb9..4401a2dfeb 100644 --- a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs @@ -10,6 +10,7 @@ namespace VirtualizationDemo.ViewModels { private string _prefix; private int _index; + private double _height = double.NaN; public ItemViewModel(int index, string prefix = "Item") { @@ -18,5 +19,11 @@ namespace VirtualizationDemo.ViewModels } public string Header => $"{_prefix} {_index}"; + + public double Height + { + get => _height; + set => this.RaiseAndSetIfChanged(ref _height, value); + } } } diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs index eba17f92e4..80e0fb2586 100644 --- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs @@ -98,6 +98,24 @@ namespace VirtualizationDemo.ViewModels public ReactiveCommand SelectFirstCommand { get; private set; } public ReactiveCommand SelectLastCommand { get; private set; } + public void RandomizeSize() + { + var random = new Random(); + + foreach (var i in Items) + { + i.Height = random.Next(240) + 10; + } + } + + public void ResetSize() + { + foreach (var i in Items) + { + i.Height = double.NaN; + } + } + private void ResizeItems(int count) { if (Items == null) From d398d418215a96b3353912c07776095a22bcb14f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 May 2019 16:39:09 +0200 Subject: [PATCH 12/44] Use 1 and 10 as Small/LargeChange default values. And make them be treated as absolute values in `ScrollBar`. This is consistent with WPF and also fixes scrolling with scrollbar buttons in virtualized lists, where a unit of 1 is one item. --- src/Avalonia.Controls/Primitives/RangeBase.cs | 4 ++-- src/Avalonia.Controls/Primitives/ScrollBar.cs | 8 ++++---- src/Avalonia.Controls/Slider.cs | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index 76df94cdb8..ae175734b9 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.cs @@ -44,13 +44,13 @@ namespace Avalonia.Controls.Primitives /// Defines the property. /// public static readonly StyledProperty SmallChangeProperty = - AvaloniaProperty.Register(nameof(SmallChange), 0.1); + AvaloniaProperty.Register(nameof(SmallChange), 1); /// /// Defines the property. /// public static readonly StyledProperty LargeChangeProperty = - AvaloniaProperty.Register(nameof(LargeChange), 1); + AvaloniaProperty.Register(nameof(LargeChange), 10); private double _minimum; private double _maximum = 100.0; diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index f0d8c81808..e1b3061b54 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -216,25 +216,25 @@ namespace Avalonia.Controls.Primitives private void SmallDecrement() { - Value = Math.Max(Value - SmallChange * ViewportSize, Minimum); + Value = Math.Max(Value - SmallChange, Minimum); OnScroll(ScrollEventType.SmallDecrement); } private void SmallIncrement() { - Value = Math.Min(Value + SmallChange * ViewportSize, Maximum); + Value = Math.Min(Value + SmallChange, Maximum); OnScroll(ScrollEventType.SmallIncrement); } private void LargeDecrement() { - Value = Math.Max(Value - LargeChange * ViewportSize, Minimum); + Value = Math.Max(Value - LargeChange, Minimum); OnScroll(ScrollEventType.LargeDecrement); } private void LargeIncrement() { - Value = Math.Min(Value + LargeChange * ViewportSize, Maximum); + Value = Math.Min(Value + LargeChange, Maximum); OnScroll(ScrollEventType.LargeIncrement); } diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 260c708515..bc4733296b 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -47,8 +47,6 @@ namespace Avalonia.Controls Thumb.DragStartedEvent.AddClassHandler(x => x.OnThumbDragStarted, RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler(x => x.OnThumbDragDelta, RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble); - SmallChangeProperty.OverrideDefaultValue(1); - LargeChangeProperty.OverrideDefaultValue(10); } /// From 5853723e5beeda759d542e4661ef10316112c956 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 8 May 2019 16:15:18 +0200 Subject: [PATCH 13/44] Remove the WithContainers concept... ...from the `ItemsPresenter` virtualization tests. All tests were setting it to `true` anyway. --- ...emsPresenterTests_Virtualization_Simple.cs | 168 ++++++++---------- 1 file changed, 79 insertions(+), 89 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 9921a8de6c..b842d448cd 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -756,6 +756,80 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Same(target.Panel.Children[9].DataContext, last); } + [Fact] + public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items() + { + var target = CreateTarget(); + var items = (IList)target.Items; + + target.ApplyTemplate(); + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var containers = target.Panel.Children.ToList(); + var scroller = (ScrollContentPresenter)target.Parent; + + scroller.Offset = new Vector(0, 5); + + var scrolledContainers = containers + .Skip(5) + .Take(5) + .Concat(containers.Take(5)).ToList(); + + Assert.Equal(new Vector(0, 5), ((ILogicalScrollable)target).Offset); + Assert.Equal(scrolledContainers, target.Panel.Children); + + for (var i = 0; i < target.Panel.Children.Count; ++i) + { + Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext); + } + + scroller.Offset = new Vector(0, 0); + Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset); + Assert.Equal(containers, target.Panel.Children); + + var dcs = target.Panel.Children.Select(x => x.DataContext).ToList(); + + for (var i = 0; i < target.Panel.Children.Count; ++i) + { + Assert.Equal(items[i], target.Panel.Children[i].DataContext); + } + } + + [Fact] + public void Scrolling_More_Than_A_Page_Should_Recycle_Items() + { + var target = CreateTarget(itemCount: 50); + var items = (IList)target.Items; + + target.ApplyTemplate(); + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + var containers = target.Panel.Children.ToList(); + var scroller = (ScrollContentPresenter)target.Parent; + + scroller.Offset = new Vector(0, 20); + + Assert.Equal(new Vector(0, 20), ((ILogicalScrollable)target).Offset); + Assert.Equal(containers, target.Panel.Children); + + for (var i = 0; i < target.Panel.Children.Count; ++i) + { + Assert.Equal(items[i + 20], target.Panel.Children[i].DataContext); + } + + scroller.Offset = new Vector(0, 0); + + Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset); + Assert.Equal(containers, target.Panel.Children); + + for (var i = 0; i < target.Panel.Children.Count; ++i) + { + Assert.Equal(items[i], target.Panel.Children[i].DataContext); + } + } + public class Vertical { [Fact] @@ -941,86 +1015,8 @@ namespace Avalonia.Controls.UnitTests.Presenters } } - public class WithContainers - { - [Fact] - public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items() - { - var target = CreateTarget(); - var items = (IList)target.Items; - - target.ApplyTemplate(); - target.Measure(new Size(100, 100)); - target.Arrange(new Rect(0, 0, 100, 100)); - - var containers = target.Panel.Children.ToList(); - var scroller = (ScrollContentPresenter)target.Parent; - - scroller.Offset = new Vector(0, 5); - - var scrolledContainers = containers - .Skip(5) - .Take(5) - .Concat(containers.Take(5)).ToList(); - - Assert.Equal(new Vector(0, 5), ((ILogicalScrollable)target).Offset); - Assert.Equal(scrolledContainers, target.Panel.Children); - - for (var i = 0; i < target.Panel.Children.Count; ++i) - { - Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext); - } - - scroller.Offset = new Vector(0, 0); - Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset); - Assert.Equal(containers, target.Panel.Children); - - var dcs = target.Panel.Children.Select(x => x.DataContext).ToList(); - - for (var i = 0; i < target.Panel.Children.Count; ++i) - { - Assert.Equal(items[i], target.Panel.Children[i].DataContext); - } - } - - [Fact] - public void Scrolling_More_Than_A_Page_Should_Recycle_Items() - { - var target = CreateTarget(itemCount: 50); - var items = (IList)target.Items; - - target.ApplyTemplate(); - target.Measure(new Size(100, 100)); - target.Arrange(new Rect(0, 0, 100, 100)); - - var containers = target.Panel.Children.ToList(); - var scroller = (ScrollContentPresenter)target.Parent; - - scroller.Offset = new Vector(0, 20); - - Assert.Equal(new Vector(0, 20), ((ILogicalScrollable)target).Offset); - Assert.Equal(containers, target.Panel.Children); - - for (var i = 0; i < target.Panel.Children.Count; ++i) - { - Assert.Equal(items[i + 20], target.Panel.Children[i].DataContext); - } - - scroller.Offset = new Vector(0, 0); - - Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset); - Assert.Equal(containers, target.Panel.Children); - - for (var i = 0; i < target.Panel.Children.Count; ++i) - { - Assert.Equal(items[i], target.Panel.Children[i].DataContext); - } - } - } - private static ItemsPresenter CreateTarget( Orientation orientation = Orientation.Vertical, - bool useContainers = true, int itemCount = 20, bool useAvaloniaList = false) { @@ -1034,7 +1030,7 @@ namespace Avalonia.Controls.UnitTests.Presenters { CanHorizontallyScroll = true, CanVerticallyScroll = true, - Content = result = new TestItemsPresenter(useContainers) + Content = result = new TestItemsPresenter { Items = items, ItemsPanel = VirtualizingPanelTemplate(orientation), @@ -1085,18 +1081,12 @@ namespace Avalonia.Controls.UnitTests.Presenters private class TestItemsPresenter : ItemsPresenter { - private bool _useContainers; - - public TestItemsPresenter(bool useContainers) - { - _useContainers = useContainers; - } - protected override IItemContainerGenerator CreateItemContainerGenerator() { - return _useContainers ? - new ItemContainerGenerator(this, TestContainer.ContentProperty, null) : - new ItemContainerGenerator(this); + return new ItemContainerGenerator( + this, + TestContainer.ContentProperty, + null); } } From 347a4eb0a17a94742b0a92032e9475548d3a0ddb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 8 May 2019 17:52:53 +0200 Subject: [PATCH 14/44] Don't fix size of TestContainer. Instead let its size come from the data displayed by it (as would happen in the real world). --- .../ItemsPresenterTests_Virtualization_Simple.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index b842d448cd..97d57e9eb6 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -15,6 +15,7 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -1034,7 +1035,7 @@ namespace Avalonia.Controls.UnitTests.Presenters { Items = items, ItemsPanel = VirtualizingPanelTemplate(orientation), - ItemTemplate = ItemTemplate(), + DataTemplates = { StringDataTemplate() }, VirtualizationMode = ItemVirtualizationMode.Simple, } }; @@ -1043,7 +1044,7 @@ namespace Avalonia.Controls.UnitTests.Presenters return result; } - private static IDataTemplate ItemTemplate() + private static IDataTemplate StringDataTemplate() { return new FuncDataTemplate(x => new Canvas { @@ -1061,7 +1062,7 @@ namespace Avalonia.Controls.UnitTests.Presenters }); } - private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot + private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, IStyleRoot { public IRenderer Renderer { get; } public Size ClientSize { get; } @@ -1094,8 +1095,12 @@ namespace Avalonia.Controls.UnitTests.Presenters { public TestContainer() { - Width = 10; - Height = 10; + Template = new FuncControlTemplate(parent => new ContentPresenter + { + Name = "PART_ContentPresenter", + [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], + [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], + }); } } } From dd9839461aa8a2ff20b9a155767d8e24df1253fe Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 29 May 2019 16:54:27 +0200 Subject: [PATCH 15/44] Quick fix for scroll to end with virtualized items. As described in #2144, scrolling to the end of a list of virtualized items with differing heights doesn't always work correctly. This is a quick hack to try to fix that. --- .../Presenters/ItemVirtualizerSimple.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index f72a65ead2..d11ce9a7ea 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -20,6 +20,8 @@ namespace Avalonia.Controls.Presenters /// internal class ItemVirtualizerSimple : ItemVirtualizer { + private int _anchor; + /// /// Initializes a new instance of the class. /// @@ -362,7 +364,10 @@ namespace Avalonia.Controls.Presenters if (panel.OverflowCount > 0) { - RemoveContainers(panel.OverflowCount); + if (_anchor <= FirstIndex) + { + RemoveContainers(panel.OverflowCount); + } } } @@ -540,7 +545,9 @@ namespace Avalonia.Controls.Presenters // it means we're running a unit test. if (container != null && layoutManager != null) { + _anchor = index; layoutManager.ExecuteLayoutPass(); + _anchor = -1; if (newOffset != -1 && newOffset != OffsetValue) { From 687037ab50ad9d0cc62e0272a3b4468f002c631e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 19:35:09 +0200 Subject: [PATCH 16/44] Initial port of WPF DockPanel --- src/Avalonia.Controls/DockPanel.cs | 164 +++++++++++++++++------------ 1 file changed, 97 insertions(+), 67 deletions(-) diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs index e147fe1a52..e580283d85 100644 --- a/src/Avalonia.Controls/DockPanel.cs +++ b/src/Avalonia.Controls/DockPanel.cs @@ -70,107 +70,137 @@ namespace Avalonia.Controls set { SetValue(LastChildFillProperty, value); } } - /// + /// + /// Updates DesiredSize of the DockPanel. Called by parent Control. This is the first pass of layout. + /// + /// + /// Children are measured based on their sizing properties and . + /// Each child is allowed to consume all of the space on the side on which it is docked; Left/Right docked + /// children are granted all vertical space for their entire width, and Top/Bottom docked children are + /// granted all horizontal space for their entire height. + /// + /// Constraint size is an "upper limit" that the return value should not exceed. + /// The Panel's desired size. protected override Size MeasureOverride(Size constraint) { - double usedWidth = 0.0; - double usedHeight = 0.0; - double maximumWidth = 0.0; - double maximumHeight = 0.0; + var children = Children; - // Measure each of the Children - foreach (Control element in Children) + double parentWidth = 0; // Our current required width due to children thus far. + double parentHeight = 0; // Our current required height due to children thus far. + double accumulatedWidth = 0; // Total width consumed by children. + double accumulatedHeight = 0; // Total height consumed by children. + + for (int i = 0, count = children.Count; i < count; ++i) { - // Get the child's desired size - Size remainingSize = new Size( - Math.Max(0.0, constraint.Width - usedWidth), - Math.Max(0.0, constraint.Height - usedHeight)); - element.Measure(remainingSize); - Size desiredSize = element.DesiredSize; - - // Decrease the remaining space for the rest of the children - switch (GetDock(element)) + var child = children[i]; + Size childConstraint; // Contains the suggested input constraint for this child. + Size childDesiredSize; // Contains the return size from child measure. + + if (child == null) + { continue; } + + // Child constraint is the remaining size; this is total size minus size consumed by previous children. + childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth), + Math.Max(0.0, constraint.Height - accumulatedHeight)); + + // Measure child. + child.Measure(childConstraint); + childDesiredSize = child.DesiredSize; + + // Now, we adjust: + // 1. Size consumed by children (accumulatedSize). This will be used when computing subsequent + // children to determine how much space is remaining for them. + // 2. Parent size implied by this child (parentSize) when added to the current children (accumulatedSize). + // This is different from the size above in one respect: A Dock.Left child implies a height, but does + // not actually consume any height for subsequent children. + // If we accumulate size in a given dimension, the next child (or the end conditions after the child loop) + // will deal with computing our minimum size (parentSize) due to that accumulation. + // Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does + // not accumulate: Width for Top/Bottom, Height for Left/Right. + switch (DockPanel.GetDock((Control)child)) { case Dock.Left: case Dock.Right: - maximumHeight = Math.Max(maximumHeight, usedHeight + desiredSize.Height); - usedWidth += desiredSize.Width; + parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height); + accumulatedWidth += childDesiredSize.Width; break; + case Dock.Top: case Dock.Bottom: - maximumWidth = Math.Max(maximumWidth, usedWidth + desiredSize.Width); - usedHeight += desiredSize.Height; + parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width); + accumulatedHeight += childDesiredSize.Height; break; } } - maximumWidth = Math.Max(maximumWidth, usedWidth); - maximumHeight = Math.Max(maximumHeight, usedHeight); - return new Size(maximumWidth, maximumHeight); + // Make sure the final accumulated size is reflected in parentSize. + parentWidth = Math.Max(parentWidth, accumulatedWidth); + parentHeight = Math.Max(parentHeight, accumulatedHeight); + + return (new Size(parentWidth, parentHeight)); } - /// + /// + /// DockPanel computes a position and final size for each of its children based upon their + /// enum and sizing properties. + /// + /// Size that DockPanel will assume to position children. protected override Size ArrangeOverride(Size arrangeSize) { - double left = 0.0; - double top = 0.0; - double right = 0.0; - double bottom = 0.0; - - // Arrange each of the Children var children = Children; - int dockedCount = children.Count - (LastChildFill ? 1 : 0); - int index = 0; + int totalChildrenCount = children.Count; + int nonFillChildrenCount = totalChildrenCount - (LastChildFill ? 1 : 0); - foreach (Control element in children) + double accumulatedLeft = 0; + double accumulatedTop = 0; + double accumulatedRight = 0; + double accumulatedBottom = 0; + + for (int i = 0; i < totalChildrenCount; ++i) { - // Determine the remaining space left to arrange the element - Rect remainingRect = new Rect( - left, - top, - Math.Max(0.0, arrangeSize.Width - left - right), - Math.Max(0.0, arrangeSize.Height - top - bottom)); - - // Trim the remaining Rect to the docked size of the element - // (unless the element should fill the remaining space because - // of LastChildFill) - if (index < dockedCount) + var child = children[i]; + if (child == null) + { continue; } + + Size childDesiredSize = child.DesiredSize; + Rect rcChild = new Rect( + accumulatedLeft, + accumulatedTop, + Math.Max(0.0, arrangeSize.Width - (accumulatedLeft + accumulatedRight)), + Math.Max(0.0, arrangeSize.Height - (accumulatedTop + accumulatedBottom))); + + if (i < nonFillChildrenCount) { - Size desiredSize = element.DesiredSize; - switch (GetDock(element)) + switch (DockPanel.GetDock((Control)child)) { case Dock.Left: - left += desiredSize.Width; - remainingRect = remainingRect.WithWidth(desiredSize.Width); - break; - case Dock.Top: - top += desiredSize.Height; - remainingRect = remainingRect.WithHeight(desiredSize.Height); + accumulatedLeft += childDesiredSize.Width; + rcChild = rcChild.WithWidth(childDesiredSize.Width); break; + case Dock.Right: - right += desiredSize.Width; - remainingRect = new Rect( - Math.Max(0.0, arrangeSize.Width - right), - remainingRect.Y, - desiredSize.Width, - remainingRect.Height); + accumulatedRight += childDesiredSize.Width; + rcChild = rcChild.WithX(Math.Max(0.0, arrangeSize.Width - accumulatedRight)); + rcChild = rcChild.WithWidth(childDesiredSize.Width); break; + + case Dock.Top: + accumulatedTop += childDesiredSize.Height; + rcChild = rcChild.WithHeight(childDesiredSize.Height); + break; + case Dock.Bottom: - bottom += desiredSize.Height; - remainingRect = new Rect( - remainingRect.X, - Math.Max(0.0, arrangeSize.Height - bottom), - remainingRect.Width, - desiredSize.Height); + accumulatedBottom += childDesiredSize.Height; + rcChild = rcChild.WithY(Math.Max(0.0, arrangeSize.Height - accumulatedBottom)); + rcChild = rcChild.WithHeight(childDesiredSize.Height); break; } } - element.Arrange(remainingRect); - index++; + child.Arrange(rcChild); } - return arrangeSize; + return (arrangeSize); } } } From 1c2e27015523cbe13ce0aa40a02340d32d27d9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 21:25:27 +0200 Subject: [PATCH 17/44] Initial port of WPF StackPanel --- src/Avalonia.Controls/StackPanel.cs | 136 +++++++++++++++------------- 1 file changed, 73 insertions(+), 63 deletions(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index c29faa1b4d..d456e462f5 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; -using Avalonia.Input; -using Avalonia.Layout; +using Avalonia.Input namespace Avalonia.Controls { @@ -155,106 +153,118 @@ namespace Avalonia.Controls } /// - /// Measures the control. + /// General StackPanel layout behavior is to grow unbounded in the "stacking" direction (Size To Content). + /// Children in this dimension are encouraged to be as large as they like. In the other dimension, + /// StackPanel will assume the maximum size of its children. /// - /// The available size. - /// The desired size of the control. - protected override Size MeasureOverride(Size availableSize) + /// Constraint + /// Desired size + protected override Size MeasureOverride(Size constraint) { - double childAvailableWidth = double.PositiveInfinity; - double childAvailableHeight = double.PositiveInfinity; + Size stackDesiredSize = new Size(); + var children = Children; + Size layoutSlotSize = constraint; + bool fHorizontal = (Orientation == Orientation.Horizontal); + double spacing = Spacing; + bool hasVisibleChild = false; - if (Orientation == Orientation.Vertical) + // + // Initialize child sizing and iterator data + // Allow children as much size as they want along the stack. + // + if (fHorizontal) { - childAvailableWidth = availableSize.Width; - - if (!double.IsNaN(Width)) - { - childAvailableWidth = Width; - } - - childAvailableWidth = Math.Min(childAvailableWidth, MaxWidth); - childAvailableWidth = Math.Max(childAvailableWidth, MinWidth); + layoutSlotSize = layoutSlotSize.WithWidth(Double.PositiveInfinity); } else { - childAvailableHeight = availableSize.Height; + layoutSlotSize = layoutSlotSize.WithHeight(Double.PositiveInfinity); + } - if (!double.IsNaN(Height)) - { - childAvailableHeight = Height; - } + // + // Iterate through children. + // While we still supported virtualization, this was hidden in a child iterator (see source history). + // + for (int i = 0, count = children.Count; i < count; ++i) + { + // Get next child. + var child = children[i]; - childAvailableHeight = Math.Min(childAvailableHeight, MaxHeight); - childAvailableHeight = Math.Max(childAvailableHeight, MinHeight); - } + if (child == null) + { continue; } - double measuredWidth = 0; - double measuredHeight = 0; - double spacing = Spacing; - bool hasVisibleChild = Children.Any(c => c.IsVisible); + if (child.IsVisible) + { + hasVisibleChild = true; + } - foreach (Control child in Children) - { - child.Measure(new Size(childAvailableWidth, childAvailableHeight)); - Size size = child.DesiredSize; + // Measure the child. + child.Measure(layoutSlotSize); + Size childDesiredSize = child.DesiredSize; - if (Orientation == Orientation.Vertical) + // Accumulate child size. + if (fHorizontal) { - measuredHeight += size.Height + (child.IsVisible ? spacing : 0); - measuredWidth = Math.Max(measuredWidth, size.Width); + stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width + childDesiredSize.Width + (child.IsVisible ? spacing : 0)); + stackDesiredSize = stackDesiredSize.WithHeight(Math.Max(stackDesiredSize.Height, childDesiredSize.Height)); } else { - measuredWidth += size.Width + (child.IsVisible ? spacing : 0); - measuredHeight = Math.Max(measuredHeight, size.Height); + stackDesiredSize = stackDesiredSize.WithWidth(Math.Max(stackDesiredSize.Width, childDesiredSize.Width)); + stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height + childDesiredSize.Height + (child.IsVisible ? spacing : 0)); } } if (Orientation == Orientation.Vertical) { - measuredHeight -= (hasVisibleChild ? spacing : 0); + stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0)); } else { - measuredWidth -= (hasVisibleChild ? spacing : 0); + stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width - (hasVisibleChild ? spacing : 0)); } - return new Size(measuredWidth, measuredHeight).Constrain(availableSize); + return stackDesiredSize; } - /// + /// + /// Content arrangement. + /// + /// Arrange size protected override Size ArrangeOverride(Size finalSize) { - var orientation = Orientation; + var children = Children; + bool fHorizontal = (Orientation == Orientation.Horizontal); + Rect rcChild = new Rect(finalSize); + double previousChildSize = 0.0; var spacing = Spacing; - var finalRect = new Rect(finalSize); - var pos = 0.0; - foreach (Control child in Children) + // + // Arrange and Position Children. + // + for (int i = 0, count = children.Count; i < count; ++i) { - if (!child.IsVisible) - { - continue; - } + var child = children[i]; - double childWidth = child.DesiredSize.Width; - double childHeight = child.DesiredSize.Height; + if (child == null) + { continue; } - if (orientation == Orientation.Vertical) + if (fHorizontal) { - var rect = new Rect(0, pos, childWidth, childHeight) - .Align(finalRect, child.HorizontalAlignment, VerticalAlignment.Top); - ArrangeChild(child, rect, finalSize, orientation); - pos += childHeight + spacing; + rcChild = rcChild.WithX(rcChild.X + previousChildSize); + previousChildSize = child.DesiredSize.Width; + rcChild = rcChild.WithWidth(previousChildSize); + rcChild = rcChild.WithHeight(Math.Max(finalSize.Height, child.DesiredSize.Height) + (child.IsVisible ? spacing : 0)); } else { - var rect = new Rect(pos, 0, childWidth, childHeight) - .Align(finalRect, HorizontalAlignment.Left, child.VerticalAlignment); - ArrangeChild(child, rect, finalSize, orientation); - pos += childWidth + spacing; + rcChild = rcChild.WithY(rcChild.Y + previousChildSize); + previousChildSize = child.DesiredSize.Height; + rcChild = rcChild.WithHeight(previousChildSize); + rcChild = rcChild.WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width) + (child.IsVisible ? spacing : 0)); } + + child.Arrange(rcChild); } return finalSize; From 8304eaab8502ac0e288b7c31bd40e721df7f8a34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 21:26:14 +0200 Subject: [PATCH 18/44] Fix --- src/Avalonia.Controls/StackPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index d456e462f5..6caffb8686 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Avalonia.Input +using Avalonia.Input; namespace Avalonia.Controls { From 39a696b9851e46dacd5c6b6293ec0da14f0169fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 22:12:36 +0200 Subject: [PATCH 19/44] Fix tests --- src/Avalonia.Controls/StackPanel.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 6caffb8686..0a64096ee9 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -215,13 +215,13 @@ namespace Avalonia.Controls } } - if (Orientation == Orientation.Vertical) + if (fHorizontal) { - stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0)); + stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width - (hasVisibleChild ? spacing : 0)); } else - { - stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width - (hasVisibleChild ? spacing : 0)); + { + stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0)); } return stackDesiredSize; @@ -252,16 +252,16 @@ namespace Avalonia.Controls if (fHorizontal) { rcChild = rcChild.WithX(rcChild.X + previousChildSize); - previousChildSize = child.DesiredSize.Width; + previousChildSize = child.DesiredSize.Width + (child.IsVisible ? spacing : 0); rcChild = rcChild.WithWidth(previousChildSize); - rcChild = rcChild.WithHeight(Math.Max(finalSize.Height, child.DesiredSize.Height) + (child.IsVisible ? spacing : 0)); + rcChild = rcChild.WithHeight(Math.Max(finalSize.Height, child.DesiredSize.Height)); } else { rcChild = rcChild.WithY(rcChild.Y + previousChildSize); - previousChildSize = child.DesiredSize.Height; + previousChildSize = child.DesiredSize.Height + (child.IsVisible ? spacing : 0); rcChild = rcChild.WithHeight(previousChildSize); - rcChild = rcChild.WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width) + (child.IsVisible ? spacing : 0)); + rcChild = rcChild.WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width)); } child.Arrange(rcChild); From 895f458fa9b5fb23c6c86389cee8abad3e8fe677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 22:20:21 +0200 Subject: [PATCH 20/44] Update StackPanel.cs --- src/Avalonia.Controls/StackPanel.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 0a64096ee9..c2f36b94d7 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -157,13 +157,13 @@ namespace Avalonia.Controls /// Children in this dimension are encouraged to be as large as they like. In the other dimension, /// StackPanel will assume the maximum size of its children. /// - /// Constraint + /// Constraint /// Desired size - protected override Size MeasureOverride(Size constraint) + protected override Size MeasureOverride(Size availableSize) { Size stackDesiredSize = new Size(); var children = Children; - Size layoutSlotSize = constraint; + Size layoutSlotSize = availableSize; bool fHorizontal = (Orientation == Orientation.Horizontal); double spacing = Spacing; bool hasVisibleChild = false; @@ -224,7 +224,7 @@ namespace Avalonia.Controls stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0)); } - return stackDesiredSize; + return stackDesiredSize.Constrain(availableSize); // TODO: In WPF `.Constrain(availableSize)` is not used. } /// From 705ebc26752a1b9e956cda8ac5dd908eb1883e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 22:30:37 +0200 Subject: [PATCH 21/44] Update StackPanel.cs --- src/Avalonia.Controls/StackPanel.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index c2f36b94d7..fd75353364 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -205,13 +205,13 @@ namespace Avalonia.Controls // Accumulate child size. if (fHorizontal) { - stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width + childDesiredSize.Width + (child.IsVisible ? spacing : 0)); + stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width + (child.IsVisible ? spacing : 0) + childDesiredSize.Width); stackDesiredSize = stackDesiredSize.WithHeight(Math.Max(stackDesiredSize.Height, childDesiredSize.Height)); } else { stackDesiredSize = stackDesiredSize.WithWidth(Math.Max(stackDesiredSize.Width, childDesiredSize.Width)); - stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height + childDesiredSize.Height + (child.IsVisible ? spacing : 0)); + stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height + (child.IsVisible ? spacing : 0) + childDesiredSize.Height); } } @@ -224,7 +224,9 @@ namespace Avalonia.Controls stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0)); } - return stackDesiredSize.Constrain(availableSize); // TODO: In WPF `.Constrain(availableSize)` is not used. + return stackDesiredSize; + // TODO: In WPF `.Constrain(availableSize)` is not used. + //return stackDesiredSize.Constrain(availableSize); } /// @@ -251,15 +253,15 @@ namespace Avalonia.Controls if (fHorizontal) { - rcChild = rcChild.WithX(rcChild.X + previousChildSize); - previousChildSize = child.DesiredSize.Width + (child.IsVisible ? spacing : 0); + rcChild = rcChild.WithX(rcChild.X + previousChildSize + spacing); + previousChildSize = child.DesiredSize.Width;//+ (child.IsVisible ? spacing : 0); rcChild = rcChild.WithWidth(previousChildSize); rcChild = rcChild.WithHeight(Math.Max(finalSize.Height, child.DesiredSize.Height)); } else { - rcChild = rcChild.WithY(rcChild.Y + previousChildSize); - previousChildSize = child.DesiredSize.Height + (child.IsVisible ? spacing : 0); + rcChild = rcChild.WithY(rcChild.Y + previousChildSize + spacing); + previousChildSize = child.DesiredSize.Height;//+ (child.IsVisible ? spacing : 0); rcChild = rcChild.WithHeight(previousChildSize); rcChild = rcChild.WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width)); } From af69f801204435e46ea7312f334f46ea79e7f652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 23:08:21 +0200 Subject: [PATCH 22/44] Update StackPanel.cs --- src/Avalonia.Controls/StackPanel.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index fd75353364..a4e711bbd3 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -193,7 +193,9 @@ namespace Avalonia.Controls if (child == null) { continue; } - if (child.IsVisible) + bool isVisible = child.IsVisible; + + if (isVisible && !hasVisibleChild) { hasVisibleChild = true; } @@ -205,13 +207,13 @@ namespace Avalonia.Controls // Accumulate child size. if (fHorizontal) { - stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width + (child.IsVisible ? spacing : 0) + childDesiredSize.Width); + stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width + (isVisible ? spacing : 0) + childDesiredSize.Width); stackDesiredSize = stackDesiredSize.WithHeight(Math.Max(stackDesiredSize.Height, childDesiredSize.Height)); } else { stackDesiredSize = stackDesiredSize.WithWidth(Math.Max(stackDesiredSize.Width, childDesiredSize.Width)); - stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height + (child.IsVisible ? spacing : 0) + childDesiredSize.Height); + stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height + (isVisible ? spacing : 0) + childDesiredSize.Height); } } From 4cf502e61b5ccf409d84f72cc5f0acd50cee5bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 23:15:54 +0200 Subject: [PATCH 23/44] Update StackPanel.cs --- src/Avalonia.Controls/StackPanel.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index a4e711bbd3..8abc82f747 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -255,17 +255,19 @@ namespace Avalonia.Controls if (fHorizontal) { - rcChild = rcChild.WithX(rcChild.X + previousChildSize + spacing); - previousChildSize = child.DesiredSize.Width;//+ (child.IsVisible ? spacing : 0); + rcChild = rcChild.WithX(rcChild.X + previousChildSize); + previousChildSize = child.DesiredSize.Width; rcChild = rcChild.WithWidth(previousChildSize); rcChild = rcChild.WithHeight(Math.Max(finalSize.Height, child.DesiredSize.Height)); + previousChildSize += spacing; } else { - rcChild = rcChild.WithY(rcChild.Y + previousChildSize + spacing); - previousChildSize = child.DesiredSize.Height;//+ (child.IsVisible ? spacing : 0); + rcChild = rcChild.WithY(rcChild.Y + previousChildSize); + previousChildSize = child.DesiredSize.Height; rcChild = rcChild.WithHeight(previousChildSize); rcChild = rcChild.WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width)); + previousChildSize += spacing; } child.Arrange(rcChild); From ebb8d43727f6688db9c69851b033600e46f75715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 10 Jun 2019 23:15:58 +0200 Subject: [PATCH 24/44] Update StackPanel.cs --- src/Avalonia.Controls/StackPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 8abc82f747..8b002dc64d 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -226,9 +226,9 @@ namespace Avalonia.Controls stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0)); } - return stackDesiredSize; // TODO: In WPF `.Constrain(availableSize)` is not used. - //return stackDesiredSize.Constrain(availableSize); + //return stackDesiredSize; + return stackDesiredSize.Constrain(availableSize); } /// From 4ae22f579e47df6c7edf2d7ec93fc3b099f64d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 12 Jun 2019 17:28:57 +0200 Subject: [PATCH 25/44] Fix license headers --- src/Avalonia.Controls/DockPanel.cs | 9 +++++++-- src/Avalonia.Controls/StackPanel.cs | 6 ++++-- src/Avalonia.Controls/WrapPanel.cs | 10 ++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs index e580283d85..8e23555c2d 100644 --- a/src/Avalonia.Controls/DockPanel.cs +++ b/src/Avalonia.Controls/DockPanel.cs @@ -1,7 +1,12 @@ +// This source file is adapted from the Windows Presentation Foundation project. +// (https://github.com/dotnet/wpf/) +// +// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. + +using System; + namespace Avalonia.Controls { - using System; - /// /// Defines the available docking modes for a control in a . /// diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 8b002dc64d..59c3c33942 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -1,5 +1,7 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. +// This source file is adapted from the Windows Presentation Foundation project. +// (https://github.com/dotnet/wpf/) +// +// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; using Avalonia.Input; diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 4df1b39400..6f53d853c7 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -1,9 +1,7 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; +// This source file is adapted from the Windows Presentation Foundation project. +// (https://github.com/dotnet/wpf/) +// +// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using Avalonia.Input; using Avalonia.Utilities; From 98764795bd535aa761d9c2e647a47006d67a752d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Wed, 19 Jun 2019 17:51:23 +0800 Subject: [PATCH 26/44] Update readme --- readme.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/readme.md b/readme.md index cf995f10fb..7a03fda384 100644 --- a/readme.md +++ b/readme.md @@ -8,9 +8,9 @@ ## About -Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Linux (libX11), MacOS, Android (experimental) and iOS (exprerimental). +**Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS. -**Avalonia is currently in beta** which means that the framework is generally usable for writing applications, but there may be some bugs and breaking changes as we continue development, for more details about the status see https://github.com/AvaloniaUI/Avalonia/issues/2239 +**Avalonia** is ready for **General-Purpose Desktop App Development**. However there may be some bugs and breaking changes as we continue along into this project's development. To see the status for some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239). | Control catalog | Desktop platforms | Mobile platforms | |---|---|---| @@ -40,11 +40,11 @@ https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed ## Documentation -As mentioned above, Avalonia is still in beta and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia). +You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia). There's also a high-level [architecture document](http://avaloniaui.net/architecture/project-structure) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/. -Contributions are always welcome! +Contributions for our docs are always welcome! ## Building and Using @@ -80,6 +80,4 @@ Support this project by becoming a sponsor. Your logo will show up here with a l - - - + From b3dce20c4e5da44f39df6b6a083e62d833ddf57c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 20 Jun 2019 15:08:31 +0200 Subject: [PATCH 27/44] Remove unused usings. The reference to `Avalonia.Markup.Xaml.PortableXaml` was causing a compile error in ncrunch but not in a normal build for some reason. --- src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index e8f2439f46..db0b617bcd 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -2,18 +2,10 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; -using System.ComponentModel; using System.IO; -using System.Linq; using System.Reflection; -using System.Runtime.Serialization; using System.Text; -using System.Xml.Linq; using Avalonia.Markup.Xaml.XamlIl; -using Avalonia.Controls; -using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.PortableXaml; using Avalonia.Platform; namespace Avalonia.Markup.Xaml From d2f7905262fb43ebd3c7fe87cd145949533ada27 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 21 Jun 2019 10:26:20 +0300 Subject: [PATCH 28/44] Updated XamlIl --- src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 610cda30c6..894b2c0282 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 610cda30c69e32e83c8235060606480904c937bc +Subproject commit 894b2c02827fd5eb16a338de5d5b6c9fbc60fef5 From ebdb66048de9a0eba8ed959bd6ff1a54c3286d24 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 22 Jun 2019 00:00:55 +0300 Subject: [PATCH 29/44] Fixed fbdev --- src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs | 5 ++++- src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index 43beb923e5..83d1c4aae3 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs @@ -45,7 +45,10 @@ namespace Avalonia.Controls.Embedding { if (EnforceClientSize) availableSize = PlatformImpl?.ClientSize ?? default(Size); - return base.MeasureOverride(availableSize); + var rv = base.MeasureOverride(availableSize); + if (EnforceClientSize) + return availableSize; + return rv; } private readonly NameScope _nameScope = new NameScope(); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs index 87b55be488..1e25bd4a8a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs @@ -43,12 +43,10 @@ namespace Avalonia.LinuxFramebuffer SetBpp(); - _varInfo.yoffset = 100; if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) _varInfo.transp = new fb_bitfield(); - if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) - throw new Exception("FBIOPUT_VSCREENINFO error: " + Marshal.GetLastWin32Error()); + NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo); if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); From 9efdffe8753ff012d7e488e4ef294140f92c135b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 22 Jun 2019 00:02:47 +0300 Subject: [PATCH 30/44] Don't use global variables in ControlCatalog --- .../ControlCatalog/Pages/ContextMenuPage.xaml.cs | 13 +++++++++++++ samples/ControlCatalog/Pages/MenuPage.xaml.cs | 14 ++++++++++++++ .../ViewModels/ContextMenuPageViewModel.cs | 7 ++++++- .../ControlCatalog/ViewModels/MenuPageViewModel.cs | 7 ++++++- 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs index 96e8b49f89..f861bfab33 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Controls; using Avalonia.Markup.Xaml; using ControlCatalog.ViewModels; @@ -12,6 +13,18 @@ namespace ControlCatalog.Pages DataContext = new ContextMenuPageViewModel(); } + private ContextMenuPageViewModel _model; + protected override void OnDataContextChanged(EventArgs e) + { + if (_model != null) + _model.View = null; + _model = DataContext as ContextMenuPageViewModel; + if (_model != null) + _model.View = this; + + base.OnDataContextChanged(e); + } + private void InitializeComponent() { AvaloniaXamlLoader.Load(this); diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 0a77607719..46dbe3dcad 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Reactive; using System.Threading.Tasks; @@ -21,5 +22,18 @@ namespace ControlCatalog.Pages { AvaloniaXamlLoader.Load(this); } + + private MenuPageViewModel _model; + protected override void OnDataContextChanged(EventArgs e) + { + if (_model != null) + _model.View = null; + _model = DataContext as MenuPageViewModel; + if (_model != null) + _model.View = this; + + base.OnDataContextChanged(e); + } + } } diff --git a/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs index d34e9af017..5c2f74d2d5 100644 --- a/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs @@ -2,12 +2,14 @@ using System.Reactive; using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.VisualTree; using ReactiveUI; namespace ControlCatalog.ViewModels { public class ContextMenuPageViewModel { + public Control View { get; set; } public ContextMenuPageViewModel() { OpenCommand = ReactiveCommand.CreateFromTask(Open); @@ -48,8 +50,11 @@ namespace ControlCatalog.ViewModels public async Task Open() { + var window = View?.GetVisualRoot() as Window; + if (window == null) + return; var dialog = new OpenFileDialog(); - var result = await dialog.ShowAsync(App.Current.MainWindow); + var result = await dialog.ShowAsync(window); if (result != null) { diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index 88b1bf0b6b..dc9c4a8f49 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -3,12 +3,14 @@ using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.VisualTree; using ReactiveUI; namespace ControlCatalog.ViewModels { public class MenuPageViewModel { + public Control View { get; set; } public MenuPageViewModel() { OpenCommand = ReactiveCommand.CreateFromTask(Open); @@ -65,8 +67,11 @@ namespace ControlCatalog.ViewModels public async Task Open() { + var window = View?.GetVisualRoot() as Window; + if (window == null) + return; var dialog = new OpenFileDialog(); - var result = await dialog.ShowAsync(App.Current.MainWindow); + var result = await dialog.ShowAsync(window); if (result != null) { From 425edab298421d873ba5f2a04f8ff76a8ed45e54 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 22 Jun 2019 00:21:32 +0300 Subject: [PATCH 31/44] Move Screens property to WindowBase since they are in IWindowBaseImpl anyway --- src/Avalonia.Controls/Primitives/PopupRoot.cs | 2 +- src/Avalonia.Controls/Screens.cs | 5 +++-- src/Avalonia.Controls/Window.cs | 3 --- src/Avalonia.Controls/WindowBase.cs | 3 +++ 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 90020839d6..d2e8f1ab92 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -80,7 +80,7 @@ namespace Avalonia.Controls.Primitives /// public void SnapInsideScreenEdges() { - var screen = Application.Current.MainWindow?.Screens.ScreenFromPoint(Position); + var screen = (VisualRoot as WindowBase)?.Screens?.ScreenFromPoint(Position); if (screen != null) { diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index 0f0adec790..8a0a0fa728 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Avalonia.Platform; using Avalonia.Utilities; @@ -11,7 +12,7 @@ namespace Avalonia.Controls private readonly IScreenImpl _iScreenImpl; public int ScreenCount => _iScreenImpl.ScreenCount; - public IReadOnlyList All => _iScreenImpl?.AllScreens; + public IReadOnlyList All => _iScreenImpl?.AllScreens ?? Array.Empty(); public Screen Primary => All.FirstOrDefault(x => x.Primary); public Screens(IScreenImpl iScreenImpl) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 01614ba87b..2265e89af3 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -140,7 +140,6 @@ namespace Avalonia.Controls impl.Closing = HandleClosing; impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size); - Screens = new Screens(PlatformImpl?.Screen); } /// @@ -157,8 +156,6 @@ namespace Avalonia.Controls remove { _nameScope.Unregistered -= value; } } - public Screens Screens { get; private set; } - /// /// Gets the platform-specific window implementation. /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 363af05a0b..40c9fc94d2 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -63,6 +63,7 @@ namespace Avalonia.Controls public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver) { + Screens = new Screens(PlatformImpl?.Screen); impl.Activated = HandleActivated; impl.Deactivated = HandleDeactivated; impl.PositionChanged = HandlePositionChanged; @@ -108,6 +109,8 @@ namespace Avalonia.Controls impl.Position = value; } } + + public Screens Screens { get; private set; } /// /// Whether an auto-size operation is in progress. From b33601ee9b2c2d669bf58da42f081df7294081af Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 22 Jun 2019 00:30:46 +0300 Subject: [PATCH 32/44] Refactored lifetime control into separate lifetime classes --- samples/ControlCatalog.NetCore/Program.cs | 22 +- samples/ControlCatalog/App.xaml.cs | 11 + src/Avalonia.Controls/AppBuilderBase.cs | 15 +- src/Avalonia.Controls/Application.cs | 200 ++---------------- .../ClassicDesktopStyleApplicationLifetime.cs | 99 +++++++++ ...rolledApplicationLifetimeExitEventArgs.cs} | 11 +- .../IApplicationLifetime.cs | 7 + ...IClassicDesktopStyleApplicationLifetime.cs | 28 +++ .../IControlledApplicationLifetime.cs} | 13 +- .../ISingleViewLifetime.cs | 7 + .../ApplicationLifetimes/StartupEventArgs.cs | 22 ++ .../DesktopApplicationExtensions.cs | 67 ++++++ src/Avalonia.Controls/StartupEventArgs.cs | 36 ---- src/Avalonia.Controls/TopLevel.cs | 20 -- src/Avalonia.Controls/Window.cs | 19 -- src/Avalonia.Controls/WindowCollection.cs | 33 +-- .../Remote/RemoteDesignerEntryPoint.cs | 12 +- .../FramebufferToplevelImpl.cs | 2 +- .../LinuxFramebufferPlatform.cs | 74 +++++-- src/Linux/Avalonia.LinuxFramebuffer/Mice.cs | 12 +- .../ApplicationTests.cs | 120 ----------- .../ContextMenuTests.cs | 4 +- .../DesktopStyleApplicationLifetimeTests.cs | 139 ++++++++++++ .../TopLevelTests.cs | 19 -- .../WindowBaseTests.cs | 6 - .../WindowTests.cs | 2 +- .../Avalonia.UnitTests/UnitTestApplication.cs | 3 +- 27 files changed, 493 insertions(+), 510 deletions(-) create mode 100644 src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs rename src/Avalonia.Controls/{ExitEventArgs.cs => ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs} (54%) create mode 100644 src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs create mode 100644 src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs rename src/Avalonia.Controls/{IApplicationLifecycle.cs => ApplicationLifetimes/IControlledApplicationLifetime.cs} (65%) create mode 100644 src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs create mode 100644 src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs create mode 100644 src/Avalonia.Controls/DesktopApplicationExtensions.cs delete mode 100644 src/Avalonia.Controls/StartupEventArgs.cs create mode 100644 tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 4027c5cd63..40321496c0 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using Avalonia; +using Avalonia.Controls; using Avalonia.Skia; using Avalonia.ReactiveUI; @@ -11,7 +12,7 @@ namespace ControlCatalog.NetCore static class Program { - static void Main(string[] args) + static int Main(string[] args) { Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); if (args.Contains("--wait-for-attach")) @@ -25,21 +26,16 @@ namespace ControlCatalog.NetCore } } + var builder = BuildAvaloniaApp(); if (args.Contains("--fbdev")) - AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => - { - tl.Content = new MainView(); - System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); - }); + { + System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + return builder.StartLinuxFramebuffer(args); + } else - BuildAvaloniaApp().Start(AppMain, args); + return builder.StartWithClassicDesktopLifetime(args); } - - static void AppMain(Application app, string[] args) - { - app.Run(new MainWindow()); - } - + /// /// This method is needed for IDE previewer infrastructure /// diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index d862749132..b9bce96b2a 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; namespace ControlCatalog @@ -9,5 +10,15 @@ namespace ControlCatalog { AvaloniaXamlLoader.Load(this); } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) + desktopLifetime.MainWindow = new MainWindow(); + else if (ApplicationLifetime is ISingleViewLifetime singleViewLifetime) + singleViewLifetime.MainView = new MainView(); + + base.OnFrameworkInitializationCompleted(); + } } } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 419064b051..967ebac4ef 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -117,6 +117,7 @@ namespace Avalonia.Controls /// /// The window type. /// A delegate that will be called to create a data context for the window (optional). + [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] public void Start(Func dataContextProvider = null) where TMainWindow : Window, new() { @@ -135,6 +136,7 @@ namespace Avalonia.Controls /// The window type. /// Instance of type TMainWindow to use when starting the app /// A delegate that will be called to create a data context for the window (optional). + [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] public void Start(TMainWindow mainWindow, Func dataContextProvider = null) where TMainWindow : Window { @@ -148,6 +150,7 @@ namespace Avalonia.Controls public delegate void AppMainDelegate(Application app, string[] args); + [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] public void Start() { Setup(); @@ -224,17 +227,6 @@ namespace Avalonia.Controls public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); - /// - /// Sets the shutdown mode of the application. - /// - /// The shutdown mode. - /// - public TAppBuilder SetShutdownMode(ShutdownMode shutdownMode) - { - Instance.ShutdownMode = shutdownMode; - return Self; - } - protected virtual bool CheckSetup => true; private void SetupAvaloniaModules() @@ -313,6 +305,7 @@ namespace Avalonia.Controls Instance.RegisterServices(); Instance.Initialize(); AfterSetupCallback(Self); + Instance.OnFrameworkInitializationCompleted(); } } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 9f9fe5eae1..329b757ec4 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -6,6 +6,7 @@ using System.Reactive.Concurrency; using System.Threading; using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; @@ -31,7 +32,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode + public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode { /// /// The application-global data templates. @@ -43,8 +44,6 @@ namespace Avalonia private readonly Styler _styler = new Styler(); private Styles _styles; private IResourceDictionary _resources; - private CancellationTokenSource _mainLoopCancellationTokenSource; - private int _exitCode; /// /// Initializes a new instance of the class. @@ -54,12 +53,6 @@ namespace Avalonia Windows = new WindowCollection(this); } - /// - public event EventHandler Startup; - - /// - public event EventHandler Exit; - /// public event EventHandler ResourcesChanged; @@ -167,24 +160,6 @@ namespace Avalonia /// IResourceNode IResourceNode.ResourceParent => null; - /// - /// Gets or sets the . This property indicates whether the application is shutdown explicitly or implicitly. - /// If is set to OnExplicitShutdown the application is only closes if Shutdown is called. - /// The default is OnLastWindowClose - /// - /// - /// The shutdown mode. - /// - public ShutdownMode ShutdownMode { get; set; } - - /// - /// Gets or sets the main window of the application. - /// - /// - /// The main window. - /// - public Window MainWindow { get; set; } - /// /// Gets the open windows of the application. /// @@ -192,172 +167,21 @@ namespace Avalonia /// The windows. /// public WindowCollection Windows { get; } - + /// - /// Gets or sets a value indicating whether this instance is shutting down. + /// Application lifetime, use it for things like setting the main window and exiting the app from code + /// Currently supported lifetimes are: + /// - + /// - + /// - /// - /// - /// true if this instance is shutting down; otherwise, false. - /// - internal bool IsShuttingDown { get; private set; } + public IApplicationLifetime ApplicationLifetime { get; set; } /// /// Initializes the application by loading XAML etc. /// public virtual void Initialize() { } - /// - /// Runs the application's main loop. - /// - /// - /// This will return when the condition is met - /// or was called. - /// - /// The application's exit code that is returned to the operating system on termination. - public int Run() - { - return Run(new CancellationTokenSource()); - } - - /// - /// Runs the application's main loop. - /// - /// - /// This will return when the condition is met - /// or was called. - /// This also returns when is closed. - /// - /// The closable to track. - /// The application's exit code that is returned to the operating system on termination. - public int Run(ICloseable closable) - { - closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); - - return Run(new CancellationTokenSource()); - } - - /// - /// Runs the application's main loop. - /// - /// - /// This will return when the condition is met - /// or was called. - /// - /// The window that is used as - /// when the isn't already set. - /// The application's exit code that is returned to the operating system on termination. - public int Run(Window mainWindow) - { - if (mainWindow == null) - { - throw new ArgumentNullException(nameof(mainWindow)); - } - - if (MainWindow == null) - { - if (!mainWindow.IsVisible) - { - mainWindow.Show(); - } - - MainWindow = mainWindow; - } - - return Run(new CancellationTokenSource()); - } - /// - /// Runs the application's main loop. - /// - /// - /// This will return when the condition is met - /// or was called. - /// This also returns when the is canceled. - /// - /// The application's exit code that is returned to the operating system on termination. - /// The token to track. - public int Run(CancellationToken token) - { - return Run(CancellationTokenSource.CreateLinkedTokenSource(token)); - } - - private int Run(CancellationTokenSource tokenSource) - { - if (IsShuttingDown) - { - throw new InvalidOperationException("Application is shutting down."); - } - - if (_mainLoopCancellationTokenSource != null) - { - throw new InvalidOperationException("Application is already running."); - } - - _mainLoopCancellationTokenSource = tokenSource; - - Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send); - - Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); - - if (!IsShuttingDown) - { - Shutdown(_exitCode); - } - - return _exitCode; - } - - /// - /// Raises the event. - /// - /// A that contains the event data. - protected virtual void OnStartup(StartupEventArgs e) - { - Startup?.Invoke(this, e); - } - - /// - /// Raises the event. - /// - /// A that contains the event data. - protected virtual void OnExit(ExitEventArgs e) - { - Exit?.Invoke(this, e); - } - - /// - public void Shutdown(int exitCode = 0) - { - if (IsShuttingDown) - { - throw new InvalidOperationException("Application is already shutting down."); - } - - _exitCode = exitCode; - - IsShuttingDown = true; - - Windows.Clear(); - - try - { - var e = new ExitEventArgs { ApplicationExitCode = _exitCode }; - - OnExit(e); - - _exitCode = e.ApplicationExitCode; - } - finally - { - _mainLoopCancellationTokenSource?.Cancel(); - - _mainLoopCancellationTokenSource = null; - - IsShuttingDown = false; - - Environment.ExitCode = _exitCode; - } - } - /// bool IResourceProvider.TryGetResource(object key, out object value) { @@ -383,7 +207,6 @@ namespace Avalonia .Bind().ToConstant(InputManager) .Bind().ToTransient() .Bind().ToConstant(_styler) - .Bind().ToConstant(this) .Bind().ToConstant(AvaloniaScheduler.Instance) .Bind().ToConstant(DragDropDevice.Instance) .Bind().ToTransient(); @@ -394,6 +217,11 @@ namespace Avalonia .GetService()?.Add(clock); } + public virtual void OnFrameworkInitializationCompleted() + { + + } + private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) { ResourcesChanged?.Invoke(this, e); diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs new file mode 100644 index 0000000000..d6d5c56537 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -0,0 +1,99 @@ +using System; +using System.Threading; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime + { + private readonly Application _app; + private int _exitCode; + private CancellationTokenSource _cts; + private bool _isShuttingDown; + + public ClassicDesktopStyleApplicationLifetime(Application app) + { + _app = app; + app.Windows.OnWindowClosed += HandleWindowClosed; + } + + /// + public event EventHandler Startup; + /// + public event EventHandler Exit; + + /// + public ShutdownMode ShutdownMode { get; set; } + + /// + public Window MainWindow { get; set; } + + private void HandleWindowClosed(Window window) + { + if (window == null) + return; + + if (_isShuttingDown) + return; + + if (ShutdownMode == ShutdownMode.OnLastWindowClose && _app.Windows.Count == 0) + Shutdown(); + else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow) + Shutdown(); + } + + + + + public void Shutdown(int exitCode = 0) + { + if (_isShuttingDown) + throw new InvalidOperationException("Application is already shutting down."); + + _exitCode = exitCode; + _isShuttingDown = true; + + try + { + _app.Windows.CloseAll(); + var e = new ControlledApplicationLifetimeExitEventArgs(exitCode); + Exit?.Invoke(this, e); + _exitCode = e.ApplicationExitCode; + } + finally + { + _cts?.Cancel(); + _cts = null; + _isShuttingDown = false; + } + } + + + public int Start(string[] args) + { + Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args)); + _cts = new CancellationTokenSource(); + MainWindow?.Show(); + _app.Run(_cts.Token); + Environment.ExitCode = _exitCode; + return _exitCode; + } + } +} + +namespace Avalonia +{ + public static class ClassicDesktopStyleApplicationLifetimeExtensions + { + public static int StartWithClassicDesktopLifetime( + this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) + where T : AppBuilderBase, new() + { + var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode}; + builder.Instance.ApplicationLifetime = lifetime; + builder.SetupWithoutStarting(); + return lifetime.Start(args); + } + } +} diff --git a/src/Avalonia.Controls/ExitEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs similarity index 54% rename from src/Avalonia.Controls/ExitEventArgs.cs rename to src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs index c99f7fe047..d4c3b27f7a 100644 --- a/src/Avalonia.Controls/ExitEventArgs.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs @@ -3,13 +3,18 @@ using System; -namespace Avalonia.Controls +namespace Avalonia.Controls.ApplicationLifetimes { /// - /// Contains the arguments for the event. + /// Contains the arguments for the event. /// - public class ExitEventArgs : EventArgs + public class ControlledApplicationLifetimeExitEventArgs : EventArgs { + public ControlledApplicationLifetimeExitEventArgs(int applicationExitCode) + { + ApplicationExitCode = applicationExitCode; + } + /// /// Gets or sets the exit code that an application returns to the operating system when the application exits. /// diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs new file mode 100644 index 0000000000..9860d0cb38 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Controls.ApplicationLifetimes +{ + public interface IApplicationLifetime + { + + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs new file mode 100644 index 0000000000..6d809c6714 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -0,0 +1,28 @@ +using System; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + /// + /// Controls application lifetime in classic desktop style + /// + public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime + { + /// + /// Gets or sets the . This property indicates whether the application is shutdown explicitly or implicitly. + /// If is set to OnExplicitShutdown the application is only closes if Shutdown is called. + /// The default is OnLastWindowClose + /// + /// + /// The shutdown mode. + /// + ShutdownMode ShutdownMode { get; set; } + + /// + /// Gets or sets the main window of the application. + /// + /// + /// The main window. + /// + Window MainWindow { get; set; } + } +} diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs similarity index 65% rename from src/Avalonia.Controls/IApplicationLifecycle.cs rename to src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs index a3c6599b20..3f61aeb536 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs @@ -1,22 +1,19 @@ using System; -namespace Avalonia.Controls +namespace Avalonia.Controls.ApplicationLifetimes { - /// - /// Sends events about the application lifecycle. - /// - public interface IApplicationLifecycle + public interface IControlledApplicationLifetime : IApplicationLifetime { /// /// Sent when the application is starting up. /// - event EventHandler Startup; + event EventHandler Startup; /// /// Sent when the application is exiting. /// - event EventHandler Exit; - + event EventHandler Exit; + /// /// Shuts down the application and sets the exit code that is returned to the operating system when the application exits. /// diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs new file mode 100644 index 0000000000..efa42b6d75 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Controls.ApplicationLifetimes +{ + public interface ISingleViewLifetime : IApplicationLifetime + { + Control MainView { get; set; } + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs new file mode 100644 index 0000000000..4c08712707 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs @@ -0,0 +1,22 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + /// + /// Contains the arguments for the event. + /// + public class ControlledApplicationLifetimeStartupEventArgs : EventArgs + { + public ControlledApplicationLifetimeStartupEventArgs(IEnumerable args) + { + Args = args?.ToArray() ?? Array.Empty(); + } + + public string[] Args { get; } + } +} diff --git a/src/Avalonia.Controls/DesktopApplicationExtensions.cs b/src/Avalonia.Controls/DesktopApplicationExtensions.cs new file mode 100644 index 0000000000..9b81bc94d1 --- /dev/null +++ b/src/Avalonia.Controls/DesktopApplicationExtensions.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Threading; + +namespace Avalonia.Controls +{ + public static class DesktopApplicationExtensions + { + [Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] + public static void Run(this Application app) => throw new NotSupportedException(); + + /// + /// On desktop-style platforms runs the application's main loop until closable is closed + /// + /// + /// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details + /// + public static void Run(this Application app, ICloseable closable) + { + var cts = new CancellationTokenSource(); + closable.Closed += (s, e) => cts.Cancel(); + + app.Run(cts.Token); + } + + /// + /// On desktop-style platforms runs the application's main loop until main window is closed + /// + /// + /// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details + /// + public static void Run(this Application app, Window mainWindow) + { + if (mainWindow == null) + { + throw new ArgumentNullException(nameof(mainWindow)); + } + var cts = new CancellationTokenSource(); + mainWindow.Closed += (_, __) => cts.Cancel(); + if (!mainWindow.IsVisible) + { + mainWindow.Show(); + } + app.Run(cts.Token); + } + + /// + /// On desktop-style platforms runs the application's main loop with custom CancellationToken + /// without setting a lifetime. + /// + /// The token to track. + public static void Run(this Application app, CancellationToken token) + { + Dispatcher.UIThread.MainLoop(token); + } + + public static void RunWithMainWindow(this Application app) + where TWindow : Avalonia.Controls.Window, new() + { + var window = new TWindow(); + window.Show(); + app.Run(window); + } + } +} diff --git a/src/Avalonia.Controls/StartupEventArgs.cs b/src/Avalonia.Controls/StartupEventArgs.cs deleted file mode 100644 index 0e12f5c01a..0000000000 --- a/src/Avalonia.Controls/StartupEventArgs.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Avalonia.Controls -{ - /// - /// Contains the arguments for the event. - /// - public class StartupEventArgs : EventArgs - { - private string[] _args; - - /// - /// Gets the command line arguments that were passed to the application. - /// - public IReadOnlyList Args => _args ?? (_args = GetArgs()); - - private static string[] GetArgs() - { - try - { - var args = Environment.GetCommandLineArgs(); - - return args.Length > 1 ? args.Skip(1).ToArray() : new string[0]; - } - catch (NotSupportedException) - { - return new string[0]; - } - } - } -} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 21bd0e4e57..87100ceeb0 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -51,7 +51,6 @@ namespace Avalonia.Controls private readonly IInputManager _inputManager; private readonly IAccessKeyHandler _accessKeyHandler; private readonly IKeyboardNavigationHandler _keyboardNavigationHandler; - private readonly IApplicationLifecycle _applicationLifecycle; private readonly IPlatformRenderInterface _renderInterface; private Size _clientSize; private ILayoutManager _layoutManager; @@ -96,7 +95,6 @@ namespace Avalonia.Controls _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); - _applicationLifecycle = TryGetService(dependencyResolver); _renderInterface = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); @@ -125,11 +123,6 @@ namespace Avalonia.Controls x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor)); - if (_applicationLifecycle != null) - { - _applicationLifecycle.Exit += OnApplicationExiting; - } - if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources) { WeakSubscriptionManager.Subscribe( @@ -281,7 +274,6 @@ namespace Avalonia.Controls Closed?.Invoke(this, EventArgs.Empty); Renderer?.Dispose(); Renderer = null; - _applicationLifecycle.Exit -= OnApplicationExiting; } /// @@ -348,18 +340,6 @@ namespace Avalonia.Controls return result; } - private void OnApplicationExiting(object sender, EventArgs args) - { - HandleApplicationExiting(); - } - - /// - /// Handles the application exiting, either from the last window closing, or a call to . - /// - protected virtual void HandleApplicationExiting() - { - } - /// /// Handles input from . /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 2265e89af3..7ae0380ba0 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -277,12 +277,6 @@ namespace Avalonia.Controls Close(false); } - protected override void HandleApplicationExiting() - { - base.HandleApplicationExiting(); - Close(true); - } - /// /// Closes a dialog window with the specified result. /// @@ -585,16 +579,3 @@ namespace Avalonia.Controls protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); } } - -namespace Avalonia -{ - public static class WindowApplicationExtensions - { - public static void RunWithMainWindow(this Application app) where TWindow : Avalonia.Controls.Window, new() - { - var window = new TWindow(); - window.Show(); - app.Run(window); - } - } -} diff --git a/src/Avalonia.Controls/WindowCollection.cs b/src/Avalonia.Controls/WindowCollection.cs index 328bb9f147..aa076a1808 100644 --- a/src/Avalonia.Controls/WindowCollection.cs +++ b/src/Avalonia.Controls/WindowCollection.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Collections; using System.Collections.Generic; @@ -12,6 +13,7 @@ namespace Avalonia { private readonly Application _application; private readonly List _windows = new List(); + public event Action OnWindowClosed; public WindowCollection(Application application) { @@ -92,7 +94,7 @@ namespace Avalonia /// /// Closes all windows and removes them from the underlying collection. /// - internal void Clear() + public void CloseAll() { while (_windows.Count > 0) { @@ -102,33 +104,8 @@ namespace Avalonia private void OnRemoveWindow(Window window) { - if (window == null) - { - return; - } - - if (_application.IsShuttingDown) - { - return; - } - - switch (_application.ShutdownMode) - { - case ShutdownMode.OnLastWindowClose: - if (Count == 0) - { - _application.Shutdown(); - } - - break; - case ShutdownMode.OnMainWindowClose: - if (window == _application.MainWindow) - { - _application.Shutdown(); - } - - break; - } + if (window != null) + OnWindowClosed?.Invoke(window); } } } diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index a0e86a53b0..617fbc8005 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net; using System.Reflection; +using System.Threading; using System.Xml; using Avalonia.Controls; using Avalonia.Input; @@ -122,15 +123,6 @@ namespace Avalonia.DesignerSupport.Remote } private const string BuilderMethodName = "BuildAvaloniaApp"; - - class NeverClose : ICloseable - { - public event EventHandler Closed - { - add {} - remove {} - } - } public static void Main(string[] cmdline) { @@ -155,7 +147,7 @@ namespace Avalonia.DesignerSupport.Remote transport.OnException += (t, e) => Die(e.ToString()); Log("Sending StartDesignerSessionMessage"); transport.Send(new StartDesignerSessionMessage {SessionId = args.SessionId}); - app.Run(new NeverClose()); + Dispatcher.UIThread.MainLoop(CancellationToken.None); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 6a63014e1a..78369a3648 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -18,7 +18,7 @@ namespace Avalonia.LinuxFramebuffer { _fb = fb; Invalidate(default(Rect)); - var mice = new Mice(ClientSize.Width, ClientSize.Height); + var mice = new Mice(this, ClientSize.Width, ClientSize.Height); mice.Start(); mice.Event += e => Input?.Invoke(e); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 9ed06c978d..44fd14b3b7 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Threading; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Embedding; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -21,7 +22,6 @@ namespace Avalonia.LinuxFramebuffer private static readonly Stopwatch St = Stopwatch.StartNew(); internal static uint Timestamp => (uint)St.ElapsedTicks; public static InternalPlatformThreadingInterface Threading; - public static FramebufferToplevelImpl TopLevel; LinuxFramebufferPlatform(string fbdev = null) { _fb = new LinuxFramebuffer(fbdev); @@ -41,37 +41,73 @@ namespace Avalonia.LinuxFramebuffer .Bind().ToConstant(Threading); } - internal static TopLevel Initialize(T builder, string fbdev = null) where T : AppBuilderBase, new() + internal static LinuxFramebufferLifetime Initialize(T builder, string fbdev = null) where T : AppBuilderBase, new() { var platform = new LinuxFramebufferPlatform(fbdev); - builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev") - .SetupWithoutStarting(); - var tl = new EmbeddableControlRoot(TopLevel = new FramebufferToplevelImpl(platform._fb)); - tl.Prepare(); - return tl; + builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev"); + return new LinuxFramebufferLifetime(platform._fb); } } -} -public static class LinuxFramebufferPlatformExtensions -{ - class TokenClosable : ICloseable + class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewLifetime { - public event EventHandler Closed; + private readonly LinuxFramebuffer _fb; + private TopLevel _topLevel; + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + public CancellationToken Token => _cts.Token; - public TokenClosable(CancellationToken token) + public LinuxFramebufferLifetime(LinuxFramebuffer fb) + { + _fb = fb; + } + + public Control MainView { - token.Register(() => Dispatcher.UIThread.Post(() => Closed?.Invoke(this, new EventArgs()))); + get => (Control)_topLevel?.Content; + set + { + if (_topLevel == null) + { + + var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb)); + tl.Prepare(); + _topLevel = tl; + } + _topLevel.Content = value; + } + } + + public int ExitCode { get; private set; } + public event EventHandler Startup; + public event EventHandler Exit; + + public void Start(string[] args) + { + Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args)); + } + + public void Shutdown(int exitCode) + { + ExitCode = exitCode; + var e = new ControlledApplicationLifetimeExitEventArgs(exitCode); + Exit?.Invoke(this, e); + ExitCode = e.ApplicationExitCode; + _cts.Cancel(); } } +} - public static void InitializeWithLinuxFramebuffer(this T builder, Action setup, - CancellationToken stop = default(CancellationToken), string fbdev = null) +public static class LinuxFramebufferPlatformExtensions +{ + public static int StartLinuxFramebuffer(this T builder, string[] args, string fbdev = null) where T : AppBuilderBase, new() { - setup(LinuxFramebufferPlatform.Initialize(builder, fbdev)); - builder.BeforeStartCallback(builder); - builder.Instance.Run(new TokenClosable(stop)); + var lifetime = LinuxFramebufferPlatform.Initialize(builder, fbdev); + builder.Instance.ApplicationLifetime = lifetime; + builder.SetupWithoutStarting(); + lifetime.Start(args); + builder.Instance.Run(lifetime.Token); + return lifetime.ExitCode; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs index b982b98d38..2b82b4f4aa 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs @@ -7,8 +7,9 @@ using Avalonia.Platform; namespace Avalonia.LinuxFramebuffer { - public unsafe class Mice + unsafe class Mice { + private readonly FramebufferToplevelImpl _topLevel; private readonly double _width; private readonly double _height; private double _x; @@ -16,8 +17,9 @@ namespace Avalonia.LinuxFramebuffer public event Action Event; - public Mice(double width, double height) + public Mice(FramebufferToplevelImpl topLevel, double width, double height) { + _topLevel = topLevel; _width = width; _height = height; } @@ -78,7 +80,7 @@ namespace Avalonia.LinuxFramebuffer return; Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice, LinuxFramebufferPlatform.Timestamp, - LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y), + _topLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y), InputModifiers.None)); } if (ev.type ==(int) EvType.EV_ABS) @@ -91,7 +93,7 @@ namespace Avalonia.LinuxFramebuffer return; Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice, LinuxFramebufferPlatform.Timestamp, - LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y), + _topLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y), InputModifiers.None)); } if (ev.type == (short) EvType.EV_KEY) @@ -108,7 +110,7 @@ namespace Avalonia.LinuxFramebuffer Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice, LinuxFramebufferPlatform.Timestamp, - LinuxFramebufferPlatform.TopLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers))); + _topLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers))); } } } diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index b7d752dfe3..9e31cab421 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -11,113 +11,6 @@ namespace Avalonia.Controls.UnitTests { public class ApplicationTests { - [Fact] - public void Should_Exit_After_MainWindow_Closed() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose; - - var hasExit = false; - - Application.Current.Exit += (s, e) => hasExit = true; - - var mainWindow = new Window(); - - mainWindow.Show(); - - Application.Current.MainWindow = mainWindow; - - var window = new Window(); - - window.Show(); - - mainWindow.Close(); - - Assert.True(hasExit); - } - } - - [Fact] - public void Should_Exit_After_Last_Window_Closed() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose; - - var hasExit = false; - - Application.Current.Exit += (s, e) => hasExit = true; - - var windowA = new Window(); - - windowA.Show(); - - var windowB = new Window(); - - windowB.Show(); - - windowA.Close(); - - Assert.False(hasExit); - - windowB.Close(); - - Assert.True(hasExit); - } - } - - [Fact] - public void Should_Only_Exit_On_Explicit_Exit() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; - - var hasExit = false; - - Application.Current.Exit += (s, e) => hasExit = true; - - var windowA = new Window(); - - windowA.Show(); - - var windowB = new Window(); - - windowB.Show(); - - windowA.Close(); - - Assert.False(hasExit); - - windowB.Close(); - - Assert.False(hasExit); - - Application.Current.Shutdown(); - - Assert.True(hasExit); - } - } - - [Fact] - public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var windows = new List { new Window(), new Window(), new Window(), new Window() }; - - foreach (var window in windows) - { - window.Show(); - } - - Application.Current.Shutdown(); - - Assert.Empty(Application.Current.Windows); - } - } - [Fact] public void Throws_ArgumentNullException_On_Run_If_MainWindow_Is_Null() { @@ -142,18 +35,5 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } - - [Fact] - public void Should_Set_ExitCode_After_Shutdown() - { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) - { - Application.Current.Shutdown(1337); - - var exitCode = Application.Current.Run(); - - Assert.Equal(1337, exitCode); - } - } } } diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 067c66969f..6482fcb4da 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -59,9 +59,7 @@ namespace Avalonia.Controls.UnitTests }; var window = new Window { Content = target }; - - Avalonia.Application.Current.MainWindow = window; - + _mouse.Click(target, MouseButton.Right); Assert.True(sut.IsOpen); diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs new file mode 100644 index 0000000000..ee27d6493b --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Threading; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + + public class DesktopStyleApplicationLifetimeTests + { + [Fact] + public void Should_Set_ExitCode_After_Shutdown() + { + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); + Dispatcher.UIThread.InvokeAsync(() => lifetime.Shutdown(1337)); + lifetime.Shutdown(1337); + + var exitCode = lifetime.Start(Array.Empty()); + + Assert.Equal(1337, exitCode); + } + } + + + [Fact] + public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var windows = new List { new Window(), new Window(), new Window(), new Window() }; + + foreach (var window in windows) + { + window.Show(); + } + new ClassicDesktopStyleApplicationLifetime(Application.Current).Shutdown(); + + Assert.Empty(Application.Current.Windows); + } + } + + [Fact] + public void Should_Only_Exit_On_Explicit_Exit() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); + lifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown; + + var hasExit = false; + + lifetime.Exit += (s, e) => hasExit = true; + + var windowA = new Window(); + + windowA.Show(); + + var windowB = new Window(); + + windowB.Show(); + + windowA.Close(); + + Assert.False(hasExit); + + windowB.Close(); + + Assert.False(hasExit); + + lifetime.Shutdown(); + + Assert.True(hasExit); + } + } + + [Fact] + public void Should_Exit_After_MainWindow_Closed() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); + lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose; + + var hasExit = false; + + lifetime.Exit += (s, e) => hasExit = true; + + var mainWindow = new Window(); + + mainWindow.Show(); + + lifetime.MainWindow = mainWindow; + + var window = new Window(); + + window.Show(); + + mainWindow.Close(); + + Assert.True(hasExit); + } + } + + [Fact] + public void Should_Exit_After_Last_Window_Closed() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); + lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose; + + var hasExit = false; + + lifetime.Exit += (s, e) => hasExit = true; + + var windowA = new Window(); + + windowA.Show(); + + var windowB = new Window(); + + windowB.Show(); + + windowA.Close(); + + Assert.False(hasExit); + + windowB.Close(); + + Assert.True(hasExit); + } + } + } + +} diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index aa99d31cff..f112289460 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -207,19 +207,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Exiting_Application_Notifies_Top_Level() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var impl = new Mock(); - impl.SetupAllProperties(); - var target = new TestTopLevel(impl.Object); - UnitTestApplication.Current.Shutdown(); - Assert.True(target.IsClosed); - } - } - [Fact] public void Adding_Resource_To_Application_Should_Raise_ResourcesChanged() { @@ -259,12 +246,6 @@ namespace Avalonia.Controls.UnitTests } protected override ILayoutManager CreateLayoutManager() => _layoutManager; - - protected override void HandleApplicationExiting() - { - base.HandleApplicationExiting(); - IsClosed = true; - } } } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 6d00409ae0..12d29f2e5b 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -276,12 +276,6 @@ namespace Avalonia.Controls.UnitTests : base(impl) { } - - protected override void HandleApplicationExiting() - { - base.HandleApplicationExiting(); - IsClosed = true; - } } } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index ee416c4cb0..35f60e92cd 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -425,7 +425,7 @@ namespace Avalonia.Controls.UnitTests { // HACK: We really need a decent way to have "statics" that can be scoped to // AvaloniaLocator scopes. - Application.Current.Windows.Clear(); + Application.Current.Windows.CloseAll(); } } } diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 3578471397..a516facb92 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -66,8 +66,7 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.Styler) .Bind().ToConstant(Services.WindowingPlatform) - .Bind().ToSingleton() - .Bind().ToConstant(this); + .Bind().ToSingleton(); var styles = Services.Theme?.Invoke(); if (styles != null) From c161d4e07c6e758f186383f409b846429d620c5a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 22 Jun 2019 01:30:37 +0300 Subject: [PATCH 33/44] AppBuilderBase cleanup --- src/Avalonia.Controls/AppBuilderBase.cs | 38 ------------------------- 1 file changed, 38 deletions(-) diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 967ebac4ef..0f0b55e9e8 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -57,10 +57,6 @@ namespace Avalonia.Controls /// public Action AfterSetupCallback { get; private set; } = builder => { }; - /// - /// Gets or sets a method to call before Start is called on the . - /// - public Action BeforeStartCallback { get; private set; } = builder => { }; protected AppBuilderBase(IRuntimePlatform platform, Action platformServices) { @@ -95,17 +91,6 @@ namespace Avalonia.Controls protected TAppBuilder Self => (TAppBuilder)this; - /// - /// Registers a callback to call before Start is called on the . - /// - /// The callback. - /// An instance. - public TAppBuilder BeforeStarting(Action callback) - { - BeforeStartCallback = (Action)Delegate.Combine(BeforeStartCallback, callback); - return Self; - } - public TAppBuilder AfterSetup(Action callback) { AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback); @@ -121,47 +106,24 @@ namespace Avalonia.Controls public void Start(Func dataContextProvider = null) where TMainWindow : Window, new() { - Setup(); - BeforeStartCallback(Self); - var window = new TMainWindow(); if (dataContextProvider != null) window.DataContext = dataContextProvider(); Instance.Run(window); } - /// - /// Starts the application with the provided instance of . - /// - /// The window type. - /// Instance of type TMainWindow to use when starting the app - /// A delegate that will be called to create a data context for the window (optional). - [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] - public void Start(TMainWindow mainWindow, Func dataContextProvider = null) - where TMainWindow : Window - { - Setup(); - BeforeStartCallback(Self); - - if (dataContextProvider != null) - mainWindow.DataContext = dataContextProvider(); - Instance.Run(mainWindow); - } - public delegate void AppMainDelegate(Application app, string[] args); [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] public void Start() { Setup(); - BeforeStartCallback(Self); Instance.Run(); } public void Start(AppMainDelegate main, string[] args) { Setup(); - BeforeStartCallback(Self); main(Instance, args); } From 3655b6eea17293dbcf3a4f574c19d21da7b74a85 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 22 Jun 2019 11:34:01 +0300 Subject: [PATCH 34/44] Move Application.Windows to IClassicDesktopStyleApplicationLifetime --- src/Avalonia.Controls/Application.cs | 16 --- .../ClassicDesktopStyleApplicationLifetime.cs | 42 ++++++- ...IClassicDesktopStyleApplicationLifetime.cs | 3 + src/Avalonia.Controls/Window.cs | 41 +++---- src/Avalonia.Controls/WindowCollection.cs | 111 ------------------ .../DesktopStyleApplicationLifetimeTests.cs | 88 ++++++++++++-- .../WindowTests.cs | 76 ------------ 7 files changed, 140 insertions(+), 237 deletions(-) delete mode 100644 src/Avalonia.Controls/WindowCollection.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 329b757ec4..acd9534d14 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -45,14 +45,6 @@ namespace Avalonia private Styles _styles; private IResourceDictionary _resources; - /// - /// Initializes a new instance of the class. - /// - public Application() - { - Windows = new WindowCollection(this); - } - /// public event EventHandler ResourcesChanged; @@ -159,14 +151,6 @@ namespace Avalonia /// IResourceNode IResourceNode.ResourceParent => null; - - /// - /// Gets the open windows of the application. - /// - /// - /// The windows. - /// - public WindowCollection Windows { get; } /// /// Application lifetime, use it for things like setting the main window and exiting the app from code diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index d6d5c56537..abca7a64ee 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -1,21 +1,46 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Interactivity; namespace Avalonia.Controls.ApplicationLifetimes { - public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime + public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable { private readonly Application _app; private int _exitCode; private CancellationTokenSource _cts; private bool _isShuttingDown; + private HashSet _windows = new HashSet(); + + private static ClassicDesktopStyleApplicationLifetime _activeLifetime; + static ClassicDesktopStyleApplicationLifetime() + { + Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); + Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); + } + + private static void WindowClosedEvent(object sender, RoutedEventArgs e) + { + _activeLifetime?._windows.Remove((Window)sender); + _activeLifetime?.HandleWindowClosed((Window)sender); + } + + private static void OnWindowOpened(object sender, RoutedEventArgs e) + { + _activeLifetime?._windows.Add((Window)sender); + } public ClassicDesktopStyleApplicationLifetime(Application app) { + if (_activeLifetime != null) + throw new InvalidOperationException( + "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); _app = app; - app.Windows.OnWindowClosed += HandleWindowClosed; + _activeLifetime = this; } /// @@ -29,6 +54,8 @@ namespace Avalonia.Controls.ApplicationLifetimes /// public Window MainWindow { get; set; } + public IReadOnlyList Windows => _windows.ToList(); + private void HandleWindowClosed(Window window) { if (window == null) @@ -37,7 +64,7 @@ namespace Avalonia.Controls.ApplicationLifetimes if (_isShuttingDown) return; - if (ShutdownMode == ShutdownMode.OnLastWindowClose && _app.Windows.Count == 0) + if (ShutdownMode == ShutdownMode.OnLastWindowClose && _windows.Count == 0) Shutdown(); else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow) Shutdown(); @@ -56,7 +83,8 @@ namespace Avalonia.Controls.ApplicationLifetimes try { - _app.Windows.CloseAll(); + foreach (var w in Windows) + w.Close(); var e = new ControlledApplicationLifetimeExitEventArgs(exitCode); Exit?.Invoke(this, e); _exitCode = e.ApplicationExitCode; @@ -79,6 +107,12 @@ namespace Avalonia.Controls.ApplicationLifetimes Environment.ExitCode = _exitCode; return _exitCode; } + + public void Dispose() + { + if (_activeLifetime == this) + _activeLifetime = null; + } } } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 6d809c6714..a1006d907b 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Avalonia.Controls.ApplicationLifetimes { @@ -24,5 +25,7 @@ namespace Avalonia.Controls.ApplicationLifetimes /// The main window. /// Window MainWindow { get; set; } + + IReadOnlyList Windows { get; } } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 7ae0380ba0..5c117f508b 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using System.ComponentModel; +using Avalonia.Interactivity; namespace Avalonia.Controls { @@ -97,6 +98,20 @@ namespace Avalonia.Controls public static readonly StyledProperty CanResizeProperty = AvaloniaProperty.Register(nameof(CanResize), true); + /// + /// Routed event that can be used for global tracking of window destruction + /// + public static readonly RoutedEvent WindowClosedEvent = + RoutedEvent.Register("WindowClosed", RoutingStrategies.Direct); + + /// + /// Routed event that can be used for global tracking of opening windows + /// + public static readonly RoutedEvent WindowOpenedEvent = + RoutedEvent.Register("WindowOpened", RoutingStrategies.Direct); + + + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; @@ -249,26 +264,6 @@ namespace Avalonia.Controls /// public event EventHandler Closing; - private static void AddWindow(Window window) - { - if (Application.Current == null) - { - return; - } - - Application.Current.Windows.Add(window); - } - - private static void RemoveWindow(Window window) - { - if (Application.Current == null) - { - return; - } - - Application.Current.Windows.Remove(window); - } - /// /// Closes the window. /// @@ -376,7 +371,7 @@ namespace Avalonia.Controls return; } - AddWindow(this); + this.RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); IsVisible = true; @@ -438,7 +433,7 @@ namespace Avalonia.Controls throw new InvalidOperationException("The window is already being shown."); } - AddWindow(this); + RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); IsVisible = true; @@ -551,7 +546,7 @@ namespace Avalonia.Controls protected override void HandleClosed() { - RemoveWindow(this); + RaiseEvent(new RoutedEventArgs(WindowClosedEvent)); base.HandleClosed(); } diff --git a/src/Avalonia.Controls/WindowCollection.cs b/src/Avalonia.Controls/WindowCollection.cs deleted file mode 100644 index aa076a1808..0000000000 --- a/src/Avalonia.Controls/WindowCollection.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; -using System.Collections; -using System.Collections.Generic; - -using Avalonia.Controls; - -namespace Avalonia -{ - public class WindowCollection : IReadOnlyList - { - private readonly Application _application; - private readonly List _windows = new List(); - public event Action OnWindowClosed; - - public WindowCollection(Application application) - { - _application = application; - } - - /// - /// - /// Gets the number of elements in the collection. - /// - public int Count => _windows.Count; - - /// - /// - /// Gets the at the specified index. - /// - /// - /// The . - /// - /// The index. - /// - public Window this[int index] => _windows[index]; - - /// - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return _windows.GetEnumerator(); - } - - /// - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Adds the specified window. - /// - /// The window. - internal void Add(Window window) - { - if (window == null) - { - return; - } - - _windows.Add(window); - } - - /// - /// Removes the specified window. - /// - /// The window. - internal void Remove(Window window) - { - if (window == null) - { - return; - } - - _windows.Remove(window); - - OnRemoveWindow(window); - } - - /// - /// Closes all windows and removes them from the underlying collection. - /// - public void CloseAll() - { - while (_windows.Count > 0) - { - _windows[0].Close(true); - } - } - - private void OnRemoveWindow(Window window) - { - if (window != null) - OnWindowClosed?.Invoke(window); - } - } -} diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index ee27d6493b..74523d4193 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform; using Avalonia.Threading; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -14,9 +16,8 @@ namespace Avalonia.Controls.UnitTests public void Should_Set_ExitCode_After_Shutdown() { using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) { - var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); - Dispatcher.UIThread.InvokeAsync(() => lifetime.Shutdown(1337)); lifetime.Shutdown(1337); var exitCode = lifetime.Start(Array.Empty()); @@ -30,6 +31,7 @@ namespace Avalonia.Controls.UnitTests public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call() { using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) { var windows = new List { new Window(), new Window(), new Window(), new Window() }; @@ -37,9 +39,10 @@ namespace Avalonia.Controls.UnitTests { window.Show(); } - new ClassicDesktopStyleApplicationLifetime(Application.Current).Shutdown(); + Assert.Equal(4, lifetime.Windows.Count); + lifetime.Shutdown(); - Assert.Empty(Application.Current.Windows); + Assert.Empty(lifetime.Windows); } } @@ -47,8 +50,8 @@ namespace Avalonia.Controls.UnitTests public void Should_Only_Exit_On_Explicit_Exit() { using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) { - var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); lifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown; var hasExit = false; @@ -81,8 +84,8 @@ namespace Avalonia.Controls.UnitTests public void Should_Exit_After_MainWindow_Closed() { using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) { - var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose; var hasExit = false; @@ -109,8 +112,8 @@ namespace Avalonia.Controls.UnitTests public void Should_Exit_After_Last_Window_Closed() { using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) { - var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose; var hasExit = false; @@ -134,6 +137,77 @@ namespace Avalonia.Controls.UnitTests Assert.True(hasExit); } } + + [Fact] + public void Show_Should_Add_Window_To_OpenWindows() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var window = new Window(); + + window.Show(); + + Assert.Equal(new[] { window }, lifetime.Windows); + } + } + + [Fact] + public void Window_Should_Be_Added_To_OpenWindows_Only_Once() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var window = new Window(); + + window.Show(); + window.Show(); + window.IsVisible = true; + + Assert.Equal(new[] { window }, lifetime.Windows); + + window.Close(); + } + } + + [Fact] + public void Close_Should_Remove_Window_From_OpenWindows() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var window = new Window(); + + window.Show(); + Assert.Equal(1, lifetime.Windows.Count); + window.Close(); + + Assert.Empty(lifetime.Windows); + } + } + + [Fact] + public void Impl_Closing_Should_Remove_Window_From_OpenWindows() + { + var windowImpl = new Mock(); + windowImpl.SetupProperty(x => x.Closed); + windowImpl.Setup(x => x.Scaling).Returns(1); + + var services = TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); + + using (UnitTestApplication.Start(services)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var window = new Window(); + + window.Show(); + Assert.Equal(1, lifetime.Windows.Count); + windowImpl.Object.Closed(); + + Assert.Empty(lifetime.Windows); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 35f60e92cd..f4d9a91d0c 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -121,75 +121,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Show_Should_Add_Window_To_OpenWindows() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - ClearOpenWindows(); - var window = new Window(); - - window.Show(); - - Assert.Equal(new[] { window }, Application.Current.Windows); - } - } - - [Fact] - public void Window_Should_Be_Added_To_OpenWindows_Only_Once() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - ClearOpenWindows(); - var window = new Window(); - - window.Show(); - window.Show(); - window.IsVisible = true; - - Assert.Equal(new[] { window }, Application.Current.Windows); - - window.Close(); - } - } - - [Fact] - public void Close_Should_Remove_Window_From_OpenWindows() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - ClearOpenWindows(); - var window = new Window(); - - window.Show(); - window.Close(); - - Assert.Empty(Application.Current.Windows); - } - } - - [Fact] - public void Impl_Closing_Should_Remove_Window_From_OpenWindows() - { - var windowImpl = new Mock(); - windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); - - var services = TestServices.StyledWindow.With( - windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); - - using (UnitTestApplication.Start(services)) - { - ClearOpenWindows(); - var window = new Window(); - - window.Show(); - windowImpl.Object.Closed(); - - Assert.Empty(Application.Current.Windows); - } - } - [Fact] public void Closing_Should_Only_Be_Invoked_Once() { @@ -420,12 +351,5 @@ namespace Avalonia.Controls.UnitTests x.Scaling == 1 && x.CreateRenderer(It.IsAny()) == renderer.Object); } - - private void ClearOpenWindows() - { - // HACK: We really need a decent way to have "statics" that can be scoped to - // AvaloniaLocator scopes. - Application.Current.Windows.CloseAll(); - } } } From 3e071fc3662170857645f624b8c6b5c84f4a9515 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 22 Jun 2019 11:34:40 +0300 Subject: [PATCH 35/44] Naming --- samples/ControlCatalog/App.xaml.cs | 2 +- src/Avalonia.Controls/Application.cs | 2 +- ...ISingleViewLifetime.cs => ISingleViewApplicationLifetime.cs} | 2 +- src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/Avalonia.Controls/ApplicationLifetimes/{ISingleViewLifetime.cs => ISingleViewApplicationLifetime.cs} (58%) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index b9bce96b2a..07c42c60c4 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -15,7 +15,7 @@ namespace ControlCatalog { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) desktopLifetime.MainWindow = new MainWindow(); - else if (ApplicationLifetime is ISingleViewLifetime singleViewLifetime) + else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime) singleViewLifetime.MainView = new MainView(); base.OnFrameworkInitializationCompleted(); diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index acd9534d14..382106de65 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -156,7 +156,7 @@ namespace Avalonia /// Application lifetime, use it for things like setting the main window and exiting the app from code /// Currently supported lifetimes are: /// - - /// - + /// - /// - /// public IApplicationLifetime ApplicationLifetime { get; set; } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs similarity index 58% rename from src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs rename to src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs index efa42b6d75..eb451f51af 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs @@ -1,6 +1,6 @@ namespace Avalonia.Controls.ApplicationLifetimes { - public interface ISingleViewLifetime : IApplicationLifetime + public interface ISingleViewApplicationLifetime : IApplicationLifetime { Control MainView { get; set; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 44fd14b3b7..396942c8dd 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -49,7 +49,7 @@ namespace Avalonia.LinuxFramebuffer } } - class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewLifetime + class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime { private readonly LinuxFramebuffer _fb; private TopLevel _topLevel; From 998ef86a68855c09b10a7a299dcda4ef2a772414 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 22 Jun 2019 22:42:03 +0300 Subject: [PATCH 36/44] =?UTF-8?q?[GTK3]=20F=C3=9CER?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Avalonia.sln | 29 - src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 11 - src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs | 51 - src/Gtk/Avalonia.Gtk3/CursorFactory.cs | 84 -- src/Gtk/Avalonia.Gtk3/FramebufferManager.cs | 62 - src/Gtk/Avalonia.Gtk3/GdkCursor.cs | 86 -- src/Gtk/Avalonia.Gtk3/GdkKey.cs | 1341 ----------------- .../Gtk3ForeignX11SystemDialog.cs | 115 -- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 168 --- src/Gtk/Avalonia.Gtk3/GtkScreen.cs | 24 - .../Avalonia.Gtk3/IDeferredRenderOperation.cs | 9 - .../Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 144 -- src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs | 20 - src/Gtk/Avalonia.Gtk3/Interop/GException.cs | 30 - src/Gtk/Avalonia.Gtk3/Interop/GObject.cs | 87 -- src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs | 46 - src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 58 - .../ICustomGtk3NativeLibraryResolver.cs | 12 - .../Interop/ManagedCairoSurface.cs | 37 - src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 790 ---------- .../Avalonia.Gtk3/Interop/NativeException.cs | 20 - src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs | 65 - src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs | 156 -- src/Gtk/Avalonia.Gtk3/Interop/Signal.cs | 47 - src/Gtk/Avalonia.Gtk3/KeyTransform.cs | 230 --- src/Gtk/Avalonia.Gtk3/PlatformIconLoader.cs | 20 - src/Gtk/Avalonia.Gtk3/PopupImpl.cs | 19 - .../Avalonia.Gtk3/Properties/AssemblyInfo.cs | 10 - src/Gtk/Avalonia.Gtk3/README.md | 8 - src/Gtk/Avalonia.Gtk3/ScreenImpl.cs | 56 - src/Gtk/Avalonia.Gtk3/SystemDialogs.cs | 110 -- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 527 ------- src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 102 -- src/Gtk/Avalonia.Gtk3/X11.cs | 78 - src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs | 55 - 35 files changed, 4707 deletions(-) delete mode 100644 src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj delete mode 100644 src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/CursorFactory.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/FramebufferManager.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/GdkCursor.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/GdkKey.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/GtkScreen.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/GException.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/GObject.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Native.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Signal.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/KeyTransform.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/PlatformIconLoader.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/PopupImpl.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/README.md delete mode 100644 src/Gtk/Avalonia.Gtk3/ScreenImpl.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/SystemDialogs.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/WindowImpl.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/X11.cs delete mode 100644 src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs diff --git a/Avalonia.sln b/Avalonia.sln index f86c18ba1e..ac678ba9ba 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -63,8 +63,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE src\Shared\SharedAssemblyInfo.cs = src\Shared\SharedAssemblyInfo.cs EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gtk", "Gtk", "{B9894058-278A-46B5-B6ED-AD613FCC03B3}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PlatformSupport", "src\Shared\PlatformSupport\PlatformSupport.shproj", "{E4D9629C-F168-4224-3F51-A5E482FFBC42}" @@ -123,8 +121,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia", "src\Skia\Avalonia.Skia\Avalonia.Skia.csproj", "{7D2D3083-71DD-4CC9-8907-39A0D86FB322}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Gtk3", "src\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj", "{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "samples\ControlCatalog.NetCore\ControlCatalog.NetCore.csproj", "{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}" @@ -1318,30 +1314,6 @@ Global {7D2D3083-71DD-4CC9-8907-39A0D86FB322}.Release|iPhone.Build.0 = Release|Any CPU {7D2D3083-71DD-4CC9-8907-39A0D86FB322}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {7D2D3083-71DD-4CC9-8907-39A0D86FB322}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.Build.0 = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU @@ -1911,7 +1883,6 @@ Global {F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098} {29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098} {7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098} {854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj deleted file mode 100644 index 95443a364e..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - netstandard2.0 - true - $(DefineConstants);GTK3_PINVOKE - Avalonia.Gtk3 - - - - - diff --git a/src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs b/src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs deleted file mode 100644 index a860673732..0000000000 --- a/src/Gtk/Avalonia.Gtk3/ClipboardImpl.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Avalonia.Gtk3.Interop; -using Avalonia.Input.Platform; -using Avalonia.Platform.Interop; - -namespace Avalonia.Gtk3 -{ - class ClipboardImpl : IClipboard - { - - IntPtr GetClipboard() => Native.GtkClipboardGetForDisplay(Native.GdkGetDefaultDisplay(), IntPtr.Zero); - - static void OnText(IntPtr clipboard, IntPtr utf8string, IntPtr userdata) - { - var handle = GCHandle.FromIntPtr(userdata); - - ((TaskCompletionSource) handle.Target) - .TrySetResult(Utf8Buffer.StringFromPtr(utf8string)); - handle.Free(); - } - - private static readonly Native.D.GtkClipboardTextReceivedFunc OnTextDelegate = OnText; - - static ClipboardImpl() - { - GCHandle.Alloc(OnTextDelegate); - } - - public Task GetTextAsync() - { - var tcs = new TaskCompletionSource(); - Native.GtkClipboardRequestText(GetClipboard(), OnTextDelegate, GCHandle.ToIntPtr(GCHandle.Alloc(tcs))); - return tcs.Task; - } - - public Task SetTextAsync(string text) - { - using (var buf = new Utf8Buffer(text)) - Native.GtkClipboardSetText(GetClipboard(), buf, buf.ByteLen); - return Task.FromResult(0); - } - - public Task ClearAsync() - { - Native.GtkClipboardRequestClear(GetClipboard()); - return Task.FromResult(0); - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs b/src/Gtk/Avalonia.Gtk3/CursorFactory.cs deleted file mode 100644 index 95fa3ba9e3..0000000000 --- a/src/Gtk/Avalonia.Gtk3/CursorFactory.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Gtk3.Interop; -using Avalonia.Input; -using Avalonia.Platform; -using Avalonia.Platform.Interop; -using CursorType = Avalonia.Gtk3.GdkCursorType; -namespace Avalonia.Gtk3 -{ - class CursorFactory : IStandardCursorFactory - { - private static readonly Dictionary CursorTypeMapping = new Dictionary - - { - {StandardCursorType.None, CursorType.Blank}, - {StandardCursorType.AppStarting, CursorType.Watch}, - {StandardCursorType.Arrow, CursorType.LeftPtr}, - {StandardCursorType.Cross, CursorType.Cross}, - {StandardCursorType.Hand, CursorType.Hand1}, - {StandardCursorType.Ibeam, CursorType.Xterm}, - {StandardCursorType.No, "gtk-cancel"}, - {StandardCursorType.SizeAll, CursorType.Sizing}, - //{ StandardCursorType.SizeNorthEastSouthWest, 32643 }, - {StandardCursorType.SizeNorthSouth, CursorType.SbVDoubleArrow}, - //{ StandardCursorType.SizeNorthWestSouthEast, 32642 }, - {StandardCursorType.SizeWestEast, CursorType.SbHDoubleArrow}, - {StandardCursorType.UpArrow, CursorType.BasedArrowUp}, - {StandardCursorType.Wait, CursorType.Watch}, - {StandardCursorType.Help, "gtk-help"}, - {StandardCursorType.TopSide, CursorType.TopSide}, - {StandardCursorType.BottomSize, CursorType.BottomSide}, - {StandardCursorType.LeftSide, CursorType.LeftSide}, - {StandardCursorType.RightSide, CursorType.RightSide}, - {StandardCursorType.TopLeftCorner, CursorType.TopLeftCorner}, - {StandardCursorType.TopRightCorner, CursorType.TopRightCorner}, - {StandardCursorType.BottomLeftCorner, CursorType.BottomLeftCorner}, - {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner}, - {StandardCursorType.DragCopy, CursorType.CenterPtr}, - {StandardCursorType.DragMove, CursorType.Fleur}, - {StandardCursorType.DragLink, CursorType.Cross}, - }; - - private static readonly Dictionary Cache = - new Dictionary(); - - private IntPtr GetCursor(object desc) - { - IntPtr rv; - var name = desc as string; - if (name != null) - { - var theme = Native.GtkIconThemeGetDefault(); - IntPtr icon, error; - using (var u = new Utf8Buffer(name)) - icon = Native.GtkIconThemeLoadIcon(theme, u, 32, 0, out error); - rv = icon == IntPtr.Zero - ? Native.GdkCursorNew(GdkCursorType.XCursor) - : Native.GdkCursorNewFromPixbuf(Native.GdkGetDefaultDisplay(), icon, 0, 0); - } - else - { - rv = Native.GdkCursorNew((CursorType)desc); - } - - - return rv; - } - - public IPlatformHandle GetCursor(StandardCursorType cursorType) - { - IPlatformHandle rv; - if (!Cache.TryGetValue(cursorType, out rv)) - { - Cache[cursorType] = - rv = - new PlatformHandle( - GetCursor(CursorTypeMapping[cursorType]), - "GTKCURSOR"); - } - - return rv; - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs deleted file mode 100644 index e3bbe19978..0000000000 --- a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using Avalonia.Controls.Platform.Surfaces; -using Avalonia.Platform; -using Avalonia.Threading; - -namespace Avalonia.Gtk3 -{ - class FramebufferManager : IFramebufferPlatformSurface, IDisposable - { - private readonly WindowBaseImpl _window; - public FramebufferManager(WindowBaseImpl window) - { - _window = window; - } - - public void Dispose() - { - // - } - - public ILockedFramebuffer Lock() - { - // This method may be called from non-UI thread, don't touch anything that calls back to GTK/GDK - var s = _window.ClientSize; - var width = Math.Max(1, (int) s.Width); - var height = Math.Max(1, (int) s.Height); - - - if (!Dispatcher.UIThread.CheckAccess() && Gtk3Platform.DisplayClassName.ToLower().Contains("x11")) - { - var x11 = LockX11Framebuffer(width, height); - if (x11 != null) - return x11; - } - - - return new ImageSurfaceFramebuffer(_window, width, height, _window.LastKnownScaleFactor); - } - - private static int X11ErrorHandler(IntPtr d, ref X11.XErrorEvent e) - { - return 0; - } - - private static X11.XErrorHandler X11ErrorHandlerDelegate = X11ErrorHandler; - - private static IntPtr X11Display; - private ILockedFramebuffer LockX11Framebuffer(int width, int height) - { - if (!_window.GdkWindowHandle.HasValue) - return null; - if (X11Display == IntPtr.Zero) - { - X11Display = X11.XOpenDisplay(IntPtr.Zero); - if (X11Display == IntPtr.Zero) - return null; - X11.XSetErrorHandler(X11ErrorHandlerDelegate); - } - return new X11Framebuffer(X11Display, _window.GdkWindowHandle.Value, width, height, _window.LastKnownScaleFactor); - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/GdkCursor.cs b/src/Gtk/Avalonia.Gtk3/GdkCursor.cs deleted file mode 100644 index aa0f8cde0d..0000000000 --- a/src/Gtk/Avalonia.Gtk3/GdkCursor.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace Avalonia.Gtk3 -{ - enum GdkCursorType - { - Blank = -2, - CursorIsPixmap = -1, - XCursor = 0, - Arrow = 2, - BasedArrowDown = 4, - BasedArrowUp = 6, - Boat = 8, - Bogosity = 10, - BottomLeftCorner = 12, - BottomRightCorner = 14, - BottomSide = 16, - BottomTee = 18, - BoxSpiral = 20, - CenterPtr = 22, - Circle = 24, - Clock = 26, - CoffeeMug = 28, - Cross = 30, - CrossReverse = 32, - Crosshair = 34, - DiamondCross = 36, - Dot = 38, - Dotbox = 40, - DoubleArrow = 42, - DraftLarge = 44, - DraftSmall = 46, - DrapedBox = 48, - Exchange = 50, - Fleur = 52, - Gobbler = 54, - Gumby = 56, - Hand1 = 58, - Hand2 = 60, - Heart = 62, - Icon = 64, - IronCross = 66, - LeftPtr = 68, - LeftSide = 70, - LeftTee = 72, - Leftbutton = 74, - LlAngle = 76, - LrAngle = 78, - Man = 80, - Middlebutton = 82, - Mouse = 84, - Pencil = 86, - Pirate = 88, - Plus = 90, - QuestionArrow = 92, - RightPtr = 94, - RightSide = 96, - RightTee = 98, - Rightbutton = 100, - RtlLogo = 102, - Sailboat = 104, - SbDownArrow = 106, - SbHDoubleArrow = 108, - SbLeftArrow = 110, - SbRightArrow = 112, - SbUpArrow = 114, - SbVDoubleArrow = 116, - Shuttle = 118, - Sizing = 120, - Spider = 122, - Spraycan = 124, - Star = 126, - Target = 128, - Tcross = 130, - TopLeftArrow = 132, - TopLeftCorner = 134, - TopRightCorner = 136, - TopSide = 138, - TopTee = 140, - Trek = 142, - UlAngle = 144, - Umbrella = 146, - UrAngle = 148, - Watch = 150, - Xterm = 152, - LastCursor = 153, - } -} diff --git a/src/Gtk/Avalonia.Gtk3/GdkKey.cs b/src/Gtk/Avalonia.Gtk3/GdkKey.cs deleted file mode 100644 index 7aa165a1da..0000000000 --- a/src/Gtk/Avalonia.Gtk3/GdkKey.cs +++ /dev/null @@ -1,1341 +0,0 @@ -namespace Avalonia.Gtk3 -{ - enum GdkKey - { - space = 32, - exclam = 33, - quotedbl = 34, - numbersign = 35, - dollar = 36, - percent = 37, - ampersand = 38, - apostrophe = 39, - quoteright = 39, - parenleft = 40, - parenright = 41, - asterisk = 42, - plus = 43, - comma = 44, - minus = 45, - period = 46, - slash = 47, - Key_0 = 48, - Key_1 = 49, - Key_2 = 50, - Key_3 = 51, - Key_4 = 52, - Key_5 = 53, - Key_6 = 54, - Key_7 = 55, - Key_8 = 56, - Key_9 = 57, - colon = 58, - semicolon = 59, - less = 60, - equal = 61, - greater = 62, - question = 63, - at = 64, - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - bracketleft = 91, - backslash = 92, - bracketright = 93, - asciicircum = 94, - underscore = 95, - grave = 96, - quoteleft = 96, - a = 97, - b = 98, - c = 99, - d = 100, - e = 101, - f = 102, - g = 103, - h = 104, - i = 105, - j = 106, - k = 107, - l = 108, - m = 109, - n = 110, - o = 111, - p = 112, - q = 113, - r = 114, - s = 115, - t = 116, - u = 117, - v = 118, - w = 119, - x = 120, - y = 121, - z = 122, - braceleft = 123, - bar = 124, - braceright = 125, - asciitilde = 126, - nobreakspace = 160, - exclamdown = 161, - cent = 162, - sterling = 163, - currency = 164, - yen = 165, - brokenbar = 166, - section = 167, - diaeresis = 168, - copyright = 169, - ordfeminine = 170, - guillemotleft = 171, - notsign = 172, - hyphen = 173, - registered = 174, - macron = 175, - degree = 176, - plusminus = 177, - twosuperior = 178, - threesuperior = 179, - acute = 180, - mu = 181, - paragraph = 182, - periodcentered = 183, - cedilla = 184, - onesuperior = 185, - masculine = 186, - guillemotright = 187, - onequarter = 188, - onehalf = 189, - threequarters = 190, - questiondown = 191, - Agrave = 192, - Aacute = 193, - Acircumflex = 194, - Atilde = 195, - Adiaeresis = 196, - Aring = 197, - AE = 198, - Ccedilla = 199, - Egrave = 200, - Eacute = 201, - Ecircumflex = 202, - Ediaeresis = 203, - Igrave = 204, - Iacute = 205, - Icircumflex = 206, - Idiaeresis = 207, - ETH = 208, - Eth = 208, - Ntilde = 209, - Ograve = 210, - Oacute = 211, - Ocircumflex = 212, - Otilde = 213, - Odiaeresis = 214, - multiply = 215, - Ooblique = 216, - Ugrave = 217, - Uacute = 218, - Ucircumflex = 219, - Udiaeresis = 220, - Yacute = 221, - THORN = 222, - Thorn = 222, - ssharp = 223, - agrave = 224, - aacute = 225, - acircumflex = 226, - atilde = 227, - adiaeresis = 228, - aring = 229, - ae = 230, - ccedilla = 231, - egrave = 232, - eacute = 233, - ecircumflex = 234, - ediaeresis = 235, - igrave = 236, - iacute = 237, - icircumflex = 238, - idiaeresis = 239, - eth = 240, - ntilde = 241, - ograve = 242, - oacute = 243, - ocircumflex = 244, - otilde = 245, - odiaeresis = 246, - division = 247, - oslash = 248, - ugrave = 249, - uacute = 250, - ucircumflex = 251, - udiaeresis = 252, - yacute = 253, - thorn = 254, - ydiaeresis = 255, - Aogonek = 417, - breve = 418, - Lstroke = 419, - Lcaron = 421, - Sacute = 422, - Scaron = 425, - Scedilla = 426, - Tcaron = 427, - Zacute = 428, - Zcaron = 430, - Zabovedot = 431, - aogonek = 433, - ogonek = 434, - lstroke = 435, - lcaron = 437, - sacute = 438, - caron = 439, - scaron = 441, - scedilla = 442, - tcaron = 443, - zacute = 444, - doubleacute = 445, - zcaron = 446, - zabovedot = 447, - Racute = 448, - Abreve = 451, - Lacute = 453, - Cacute = 454, - Ccaron = 456, - Eogonek = 458, - Ecaron = 460, - Dcaron = 463, - Dstroke = 464, - Nacute = 465, - Ncaron = 466, - Odoubleacute = 469, - Rcaron = 472, - Uring = 473, - Udoubleacute = 475, - Tcedilla = 478, - racute = 480, - abreve = 483, - lacute = 485, - cacute = 486, - ccaron = 488, - eogonek = 490, - ecaron = 492, - dcaron = 495, - dstroke = 496, - nacute = 497, - ncaron = 498, - odoubleacute = 501, - rcaron = 504, - uring = 505, - udoubleacute = 507, - tcedilla = 510, - abovedot = 511, - Hstroke = 673, - Hcircumflex = 678, - Iabovedot = 681, - Gbreve = 683, - Jcircumflex = 684, - hstroke = 689, - hcircumflex = 694, - idotless = 697, - gbreve = 699, - jcircumflex = 700, - Cabovedot = 709, - Ccircumflex = 710, - Gabovedot = 725, - Gcircumflex = 728, - Ubreve = 733, - Scircumflex = 734, - cabovedot = 741, - ccircumflex = 742, - gabovedot = 757, - gcircumflex = 760, - ubreve = 765, - scircumflex = 766, - kappa = 930, - kra = 930, - Rcedilla = 931, - Itilde = 933, - Lcedilla = 934, - Emacron = 938, - Gcedilla = 939, - Tslash = 940, - rcedilla = 947, - itilde = 949, - lcedilla = 950, - emacron = 954, - gcedilla = 955, - tslash = 956, - ENG = 957, - eng = 959, - Amacron = 960, - Iogonek = 967, - Eabovedot = 972, - Imacron = 975, - Ncedilla = 977, - Omacron = 978, - Kcedilla = 979, - Uogonek = 985, - Utilde = 989, - Umacron = 990, - amacron = 992, - iogonek = 999, - eabovedot = 1004, - imacron = 1007, - ncedilla = 1009, - omacron = 1010, - kcedilla = 1011, - uogonek = 1017, - utilde = 1021, - umacron = 1022, - overline = 1150, - kana_fullstop = 1185, - kana_openingbracket = 1186, - kana_closingbracket = 1187, - kana_comma = 1188, - kana_conjunctive = 1189, - kana_middledot = 1189, - kana_WO = 1190, - kana_a = 1191, - kana_i = 1192, - kana_u = 1193, - kana_e = 1194, - kana_o = 1195, - kana_ya = 1196, - kana_yu = 1197, - kana_yo = 1198, - kana_tsu = 1199, - kana_tu = 1199, - prolongedsound = 1200, - kana_A = 1201, - kana_I = 1202, - kana_U = 1203, - kana_E = 1204, - kana_O = 1205, - kana_KA = 1206, - kana_KI = 1207, - kana_KU = 1208, - kana_KE = 1209, - kana_KO = 1210, - kana_SA = 1211, - kana_SHI = 1212, - kana_SU = 1213, - kana_SE = 1214, - kana_SO = 1215, - kana_TA = 1216, - kana_CHI = 1217, - kana_TI = 1217, - kana_TSU = 1218, - kana_TU = 1218, - kana_TE = 1219, - kana_TO = 1220, - kana_NA = 1221, - kana_NI = 1222, - kana_NU = 1223, - kana_NE = 1224, - kana_NO = 1225, - kana_HA = 1226, - kana_HI = 1227, - kana_FU = 1228, - kana_HU = 1228, - kana_HE = 1229, - kana_HO = 1230, - kana_MA = 1231, - kana_MI = 1232, - kana_MU = 1233, - kana_ME = 1234, - kana_MO = 1235, - kana_YA = 1236, - kana_YU = 1237, - kana_YO = 1238, - kana_RA = 1239, - kana_RI = 1240, - kana_RU = 1241, - kana_RE = 1242, - kana_RO = 1243, - kana_WA = 1244, - kana_N = 1245, - voicedsound = 1246, - semivoicedsound = 1247, - Arabic_comma = 1452, - Arabic_semicolon = 1467, - Arabic_question_mark = 1471, - Arabic_hamza = 1473, - Arabic_maddaonalef = 1474, - Arabic_hamzaonalef = 1475, - Arabic_hamzaonwaw = 1476, - Arabic_hamzaunderalef = 1477, - Arabic_hamzaonyeh = 1478, - Arabic_alef = 1479, - Arabic_beh = 1480, - Arabic_tehmarbuta = 1481, - Arabic_teh = 1482, - Arabic_theh = 1483, - Arabic_jeem = 1484, - Arabic_hah = 1485, - Arabic_khah = 1486, - Arabic_dal = 1487, - Arabic_thal = 1488, - Arabic_ra = 1489, - Arabic_zain = 1490, - Arabic_seen = 1491, - Arabic_sheen = 1492, - Arabic_sad = 1493, - Arabic_dad = 1494, - Arabic_tah = 1495, - Arabic_zah = 1496, - Arabic_ain = 1497, - Arabic_ghain = 1498, - Arabic_tatweel = 1504, - Arabic_feh = 1505, - Arabic_qaf = 1506, - Arabic_kaf = 1507, - Arabic_lam = 1508, - Arabic_meem = 1509, - Arabic_noon = 1510, - Arabic_ha = 1511, - Arabic_heh = 1511, - Arabic_waw = 1512, - Arabic_alefmaksura = 1513, - Arabic_yeh = 1514, - Arabic_fathatan = 1515, - Arabic_dammatan = 1516, - Arabic_kasratan = 1517, - Arabic_fatha = 1518, - Arabic_damma = 1519, - Arabic_kasra = 1520, - Arabic_shadda = 1521, - Arabic_sukun = 1522, - Serbian_dje = 1697, - Macedonia_gje = 1698, - Cyrillic_io = 1699, - Ukrainian_ie = 1700, - Ukranian_je = 1700, - Macedonia_dse = 1701, - Ukrainian_i = 1702, - Ukranian_i = 1702, - Ukrainian_yi = 1703, - Ukranian_yi = 1703, - Cyrillic_je = 1704, - Serbian_je = 1704, - Cyrillic_lje = 1705, - Serbian_lje = 1705, - Cyrillic_nje = 1706, - Serbian_nje = 1706, - Serbian_tshe = 1707, - Macedonia_kje = 1708, - Byelorussian_shortu = 1710, - Cyrillic_dzhe = 1711, - Serbian_dze = 1711, - numerosign = 1712, - Serbian_DJE = 1713, - Macedonia_GJE = 1714, - Cyrillic_IO = 1715, - Ukrainian_IE = 1716, - Ukranian_JE = 1716, - Macedonia_DSE = 1717, - Ukrainian_I = 1718, - Ukranian_I = 1718, - Ukrainian_YI = 1719, - Ukranian_YI = 1719, - Cyrillic_JE = 1720, - Serbian_JE = 1720, - Cyrillic_LJE = 1721, - Serbian_LJE = 1721, - Cyrillic_NJE = 1722, - Serbian_NJE = 1722, - Serbian_TSHE = 1723, - Macedonia_KJE = 1724, - Byelorussian_SHORTU = 1726, - Cyrillic_DZHE = 1727, - Serbian_DZE = 1727, - Cyrillic_yu = 1728, - Cyrillic_a = 1729, - Cyrillic_be = 1730, - Cyrillic_tse = 1731, - Cyrillic_de = 1732, - Cyrillic_ie = 1733, - Cyrillic_ef = 1734, - Cyrillic_ghe = 1735, - Cyrillic_ha = 1736, - Cyrillic_i = 1737, - Cyrillic_shorti = 1738, - Cyrillic_ka = 1739, - Cyrillic_el = 1740, - Cyrillic_em = 1741, - Cyrillic_en = 1742, - Cyrillic_o = 1743, - Cyrillic_pe = 1744, - Cyrillic_ya = 1745, - Cyrillic_er = 1746, - Cyrillic_es = 1747, - Cyrillic_te = 1748, - Cyrillic_u = 1749, - Cyrillic_zhe = 1750, - Cyrillic_ve = 1751, - Cyrillic_softsign = 1752, - Cyrillic_yeru = 1753, - Cyrillic_ze = 1754, - Cyrillic_sha = 1755, - Cyrillic_e = 1756, - Cyrillic_shcha = 1757, - Cyrillic_che = 1758, - Cyrillic_hardsign = 1759, - Cyrillic_YU = 1760, - Cyrillic_A = 1761, - Cyrillic_BE = 1762, - Cyrillic_TSE = 1763, - Cyrillic_DE = 1764, - Cyrillic_IE = 1765, - Cyrillic_EF = 1766, - Cyrillic_GHE = 1767, - Cyrillic_HA = 1768, - Cyrillic_I = 1769, - Cyrillic_SHORTI = 1770, - Cyrillic_KA = 1771, - Cyrillic_EL = 1772, - Cyrillic_EM = 1773, - Cyrillic_EN = 1774, - Cyrillic_O = 1775, - Cyrillic_PE = 1776, - Cyrillic_YA = 1777, - Cyrillic_ER = 1778, - Cyrillic_ES = 1779, - Cyrillic_TE = 1780, - Cyrillic_U = 1781, - Cyrillic_ZHE = 1782, - Cyrillic_VE = 1783, - Cyrillic_SOFTSIGN = 1784, - Cyrillic_YERU = 1785, - Cyrillic_ZE = 1786, - Cyrillic_SHA = 1787, - Cyrillic_E = 1788, - Cyrillic_SHCHA = 1789, - Cyrillic_CHE = 1790, - Cyrillic_HARDSIGN = 1791, - Greek_ALPHAaccent = 1953, - Greek_EPSILONaccent = 1954, - Greek_ETAaccent = 1955, - Greek_IOTAaccent = 1956, - Greek_IOTAdiaeresis = 1957, - Greek_OMICRONaccent = 1959, - Greek_UPSILONaccent = 1960, - Greek_UPSILONdieresis = 1961, - Greek_OMEGAaccent = 1963, - Greek_accentdieresis = 1966, - Greek_horizbar = 1967, - Greek_alphaaccent = 1969, - Greek_epsilonaccent = 1970, - Greek_etaaccent = 1971, - Greek_iotaaccent = 1972, - Greek_iotadieresis = 1973, - Greek_iotaaccentdieresis = 1974, - Greek_omicronaccent = 1975, - Greek_upsilonaccent = 1976, - Greek_upsilondieresis = 1977, - Greek_upsilonaccentdieresis = 1978, - Greek_omegaaccent = 1979, - Greek_ALPHA = 1985, - Greek_BETA = 1986, - Greek_GAMMA = 1987, - Greek_DELTA = 1988, - Greek_EPSILON = 1989, - Greek_ZETA = 1990, - Greek_ETA = 1991, - Greek_THETA = 1992, - Greek_IOTA = 1993, - Greek_KAPPA = 1994, - Greek_LAMBDA = 1995, - Greek_LAMDA = 1995, - Greek_MU = 1996, - Greek_NU = 1997, - Greek_XI = 1998, - Greek_OMICRON = 1999, - Greek_PI = 2000, - Greek_RHO = 2001, - Greek_SIGMA = 2002, - Greek_TAU = 2004, - Greek_UPSILON = 2005, - Greek_PHI = 2006, - Greek_CHI = 2007, - Greek_PSI = 2008, - Greek_OMEGA = 2009, - Greek_alpha = 2017, - Greek_beta = 2018, - Greek_gamma = 2019, - Greek_delta = 2020, - Greek_epsilon = 2021, - Greek_zeta = 2022, - Greek_eta = 2023, - Greek_theta = 2024, - Greek_iota = 2025, - Greek_kappa = 2026, - Greek_lambda = 2027, - Greek_lamda = 2027, - Greek_mu = 2028, - Greek_nu = 2029, - Greek_xi = 2030, - Greek_omicron = 2031, - Greek_pi = 2032, - Greek_rho = 2033, - Greek_sigma = 2034, - Greek_finalsmallsigma = 2035, - Greek_tau = 2036, - Greek_upsilon = 2037, - Greek_phi = 2038, - Greek_chi = 2039, - Greek_psi = 2040, - Greek_omega = 2041, - leftradical = 2209, - topleftradical = 2210, - horizconnector = 2211, - topintegral = 2212, - botintegral = 2213, - vertconnector = 2214, - topleftsqbracket = 2215, - botleftsqbracket = 2216, - toprightsqbracket = 2217, - botrightsqbracket = 2218, - topleftparens = 2219, - botleftparens = 2220, - toprightparens = 2221, - botrightparens = 2222, - leftmiddlecurlybrace = 2223, - rightmiddlecurlybrace = 2224, - topleftsummation = 2225, - botleftsummation = 2226, - topvertsummationconnector = 2227, - botvertsummationconnector = 2228, - toprightsummation = 2229, - botrightsummation = 2230, - rightmiddlesummation = 2231, - lessthanequal = 2236, - notequal = 2237, - greaterthanequal = 2238, - integral = 2239, - therefore = 2240, - variation = 2241, - infinity = 2242, - nabla = 2245, - approximate = 2248, - similarequal = 2249, - ifonlyif = 2253, - implies = 2254, - identical = 2255, - radical = 2262, - includedin = 2266, - includes = 2267, - intersection = 2268, - union = 2269, - logicaland = 2270, - logicalor = 2271, - partialderivative = 2287, - function = 2294, - leftarrow = 2299, - uparrow = 2300, - rightarrow = 2301, - downarrow = 2302, - blank = 2527, - soliddiamond = 2528, - checkerboard = 2529, - ht = 2530, - ff = 2531, - cr = 2532, - lf = 2533, - nl = 2536, - vt = 2537, - lowrightcorner = 2538, - uprightcorner = 2539, - upleftcorner = 2540, - lowleftcorner = 2541, - crossinglines = 2542, - horizlinescan1 = 2543, - horizlinescan3 = 2544, - horizlinescan5 = 2545, - horizlinescan7 = 2546, - horizlinescan9 = 2547, - leftt = 2548, - rightt = 2549, - bott = 2550, - topt = 2551, - vertbar = 2552, - emspace = 2721, - enspace = 2722, - em3space = 2723, - em4space = 2724, - digitspace = 2725, - punctspace = 2726, - thinspace = 2727, - hairspace = 2728, - emdash = 2729, - endash = 2730, - signifblank = 2732, - ellipsis = 2734, - doubbaselinedot = 2735, - onethird = 2736, - twothirds = 2737, - onefifth = 2738, - twofifths = 2739, - threefifths = 2740, - fourfifths = 2741, - onesixth = 2742, - fivesixths = 2743, - careof = 2744, - figdash = 2747, - leftanglebracket = 2748, - decimalpoint = 2749, - rightanglebracket = 2750, - marker = 2751, - oneeighth = 2755, - threeeighths = 2756, - fiveeighths = 2757, - seveneighths = 2758, - trademark = 2761, - signaturemark = 2762, - trademarkincircle = 2763, - leftopentriangle = 2764, - rightopentriangle = 2765, - emopencircle = 2766, - emopenrectangle = 2767, - leftsinglequotemark = 2768, - rightsinglequotemark = 2769, - leftdoublequotemark = 2770, - rightdoublequotemark = 2771, - prescription = 2772, - minutes = 2774, - seconds = 2775, - latincross = 2777, - hexagram = 2778, - filledrectbullet = 2779, - filledlefttribullet = 2780, - filledrighttribullet = 2781, - emfilledcircle = 2782, - emfilledrect = 2783, - enopencircbullet = 2784, - enopensquarebullet = 2785, - openrectbullet = 2786, - opentribulletup = 2787, - opentribulletdown = 2788, - openstar = 2789, - enfilledcircbullet = 2790, - enfilledsqbullet = 2791, - filledtribulletup = 2792, - filledtribulletdown = 2793, - leftpointer = 2794, - rightpointer = 2795, - club = 2796, - diamond = 2797, - heart = 2798, - maltesecross = 2800, - dagger = 2801, - doubledagger = 2802, - checkmark = 2803, - ballotcross = 2804, - musicalsharp = 2805, - musicalflat = 2806, - malesymbol = 2807, - femalesymbol = 2808, - telephone = 2809, - telephonerecorder = 2810, - phonographcopyright = 2811, - caret = 2812, - singlelowquotemark = 2813, - doublelowquotemark = 2814, - cursor = 2815, - leftcaret = 2979, - rightcaret = 2982, - downcaret = 2984, - upcaret = 2985, - overbar = 3008, - downtack = 3010, - upshoe = 3011, - downstile = 3012, - underbar = 3014, - jot = 3018, - quad = 3020, - uptack = 3022, - circle = 3023, - upstile = 3027, - downshoe = 3030, - rightshoe = 3032, - leftshoe = 3034, - lefttack = 3036, - righttack = 3068, - hebrew_doublelowline = 3295, - hebrew_aleph = 3296, - hebrew_bet = 3297, - hebrew_beth = 3297, - hebrew_gimel = 3298, - hebrew_gimmel = 3298, - hebrew_dalet = 3299, - hebrew_daleth = 3299, - hebrew_he = 3300, - hebrew_waw = 3301, - hebrew_zain = 3302, - hebrew_zayin = 3302, - hebrew_chet = 3303, - hebrew_het = 3303, - hebrew_tet = 3304, - hebrew_teth = 3304, - hebrew_yod = 3305, - hebrew_finalkaph = 3306, - hebrew_kaph = 3307, - hebrew_lamed = 3308, - hebrew_finalmem = 3309, - hebrew_mem = 3310, - hebrew_finalnun = 3311, - hebrew_nun = 3312, - hebrew_samech = 3313, - hebrew_samekh = 3313, - hebrew_ayin = 3314, - hebrew_finalpe = 3315, - hebrew_pe = 3316, - hebrew_finalzade = 3317, - hebrew_finalzadi = 3317, - hebrew_zade = 3318, - hebrew_zadi = 3318, - hebrew_kuf = 3319, - hebrew_qoph = 3319, - hebrew_resh = 3320, - hebrew_shin = 3321, - hebrew_taf = 3322, - hebrew_taw = 3322, - Thai_kokai = 3489, - Thai_khokhai = 3490, - Thai_khokhuat = 3491, - Thai_khokhwai = 3492, - Thai_khokhon = 3493, - Thai_khorakhang = 3494, - Thai_ngongu = 3495, - Thai_chochan = 3496, - Thai_choching = 3497, - Thai_chochang = 3498, - Thai_soso = 3499, - Thai_chochoe = 3500, - Thai_yoying = 3501, - Thai_dochada = 3502, - Thai_topatak = 3503, - Thai_thothan = 3504, - Thai_thonangmontho = 3505, - Thai_thophuthao = 3506, - Thai_nonen = 3507, - Thai_dodek = 3508, - Thai_totao = 3509, - Thai_thothung = 3510, - Thai_thothahan = 3511, - Thai_thothong = 3512, - Thai_nonu = 3513, - Thai_bobaimai = 3514, - Thai_popla = 3515, - Thai_phophung = 3516, - Thai_fofa = 3517, - Thai_phophan = 3518, - Thai_fofan = 3519, - Thai_phosamphao = 3520, - Thai_moma = 3521, - Thai_yoyak = 3522, - Thai_rorua = 3523, - Thai_ru = 3524, - Thai_loling = 3525, - Thai_lu = 3526, - Thai_wowaen = 3527, - Thai_sosala = 3528, - Thai_sorusi = 3529, - Thai_sosua = 3530, - Thai_hohip = 3531, - Thai_lochula = 3532, - Thai_oang = 3533, - Thai_honokhuk = 3534, - Thai_paiyannoi = 3535, - Thai_saraa = 3536, - Thai_maihanakat = 3537, - Thai_saraaa = 3538, - Thai_saraam = 3539, - Thai_sarai = 3540, - Thai_saraii = 3541, - Thai_saraue = 3542, - Thai_sarauee = 3543, - Thai_sarau = 3544, - Thai_sarauu = 3545, - Thai_phinthu = 3546, - Thai_maihanakat_maitho = 3550, - Thai_baht = 3551, - Thai_sarae = 3552, - Thai_saraae = 3553, - Thai_sarao = 3554, - Thai_saraaimaimuan = 3555, - Thai_saraaimaimalai = 3556, - Thai_lakkhangyao = 3557, - Thai_maiyamok = 3558, - Thai_maitaikhu = 3559, - Thai_maiek = 3560, - Thai_maitho = 3561, - Thai_maitri = 3562, - Thai_maichattawa = 3563, - Thai_thanthakhat = 3564, - Thai_nikhahit = 3565, - Thai_leksun = 3568, - Thai_leknung = 3569, - Thai_leksong = 3570, - Thai_leksam = 3571, - Thai_leksi = 3572, - Thai_lekha = 3573, - Thai_lekhok = 3574, - Thai_lekchet = 3575, - Thai_lekpaet = 3576, - Thai_lekkao = 3577, - Hangul_Kiyeog = 3745, - Hangul_SsangKiyeog = 3746, - Hangul_KiyeogSios = 3747, - Hangul_Nieun = 3748, - Hangul_NieunJieuj = 3749, - Hangul_NieunHieuh = 3750, - Hangul_Dikeud = 3751, - Hangul_SsangDikeud = 3752, - Hangul_Rieul = 3753, - Hangul_RieulKiyeog = 3754, - Hangul_RieulMieum = 3755, - Hangul_RieulPieub = 3756, - Hangul_RieulSios = 3757, - Hangul_RieulTieut = 3758, - Hangul_RieulPhieuf = 3759, - Hangul_RieulHieuh = 3760, - Hangul_Mieum = 3761, - Hangul_Pieub = 3762, - Hangul_SsangPieub = 3763, - Hangul_PieubSios = 3764, - Hangul_Sios = 3765, - Hangul_SsangSios = 3766, - Hangul_Ieung = 3767, - Hangul_Jieuj = 3768, - Hangul_SsangJieuj = 3769, - Hangul_Cieuc = 3770, - Hangul_Khieuq = 3771, - Hangul_Tieut = 3772, - Hangul_Phieuf = 3773, - Hangul_Hieuh = 3774, - Hangul_A = 3775, - Hangul_AE = 3776, - Hangul_YA = 3777, - Hangul_YAE = 3778, - Hangul_EO = 3779, - Hangul_E = 3780, - Hangul_YEO = 3781, - Hangul_YE = 3782, - Hangul_O = 3783, - Hangul_WA = 3784, - Hangul_WAE = 3785, - Hangul_OE = 3786, - Hangul_YO = 3787, - Hangul_U = 3788, - Hangul_WEO = 3789, - Hangul_WE = 3790, - Hangul_WI = 3791, - Hangul_YU = 3792, - Hangul_EU = 3793, - Hangul_YI = 3794, - Hangul_I = 3795, - Hangul_J_Kiyeog = 3796, - Hangul_J_SsangKiyeog = 3797, - Hangul_J_KiyeogSios = 3798, - Hangul_J_Nieun = 3799, - Hangul_J_NieunJieuj = 3800, - Hangul_J_NieunHieuh = 3801, - Hangul_J_Dikeud = 3802, - Hangul_J_Rieul = 3803, - Hangul_J_RieulKiyeog = 3804, - Hangul_J_RieulMieum = 3805, - Hangul_J_RieulPieub = 3806, - Hangul_J_RieulSios = 3807, - Hangul_J_RieulTieut = 3808, - Hangul_J_RieulPhieuf = 3809, - Hangul_J_RieulHieuh = 3810, - Hangul_J_Mieum = 3811, - Hangul_J_Pieub = 3812, - Hangul_J_PieubSios = 3813, - Hangul_J_Sios = 3814, - Hangul_J_SsangSios = 3815, - Hangul_J_Ieung = 3816, - Hangul_J_Jieuj = 3817, - Hangul_J_Cieuc = 3818, - Hangul_J_Khieuq = 3819, - Hangul_J_Tieut = 3820, - Hangul_J_Phieuf = 3821, - Hangul_J_Hieuh = 3822, - Hangul_RieulYeorinHieuh = 3823, - Hangul_SunkyeongeumMieum = 3824, - Hangul_SunkyeongeumPieub = 3825, - Hangul_PanSios = 3826, - Hangul_KkogjiDalrinIeung = 3827, - Hangul_SunkyeongeumPhieuf = 3828, - Hangul_YeorinHieuh = 3829, - Hangul_AraeA = 3830, - Hangul_AraeAE = 3831, - Hangul_J_PanSios = 3832, - Hangul_J_KkogjiDalrinIeung = 3833, - Hangul_J_YeorinHieuh = 3834, - Korean_Won = 3839, - OE = 5052, - oe = 5053, - Ydiaeresis = 5054, - EcuSign = 8352, - ColonSign = 8353, - CruzeiroSign = 8354, - FFrancSign = 8355, - LiraSign = 8356, - MillSign = 8357, - NairaSign = 8358, - PesetaSign = 8359, - RupeeSign = 8360, - WonSign = 8361, - NewSheqelSign = 8362, - DongSign = 8363, - EuroSign = 8364, - Key_3270_Duplicate = 64769, - Key_3270_FieldMark = 64770, - Key_3270_Right2 = 64771, - Key_3270_Left2 = 64772, - Key_3270_BackTab = 64773, - Key_3270_EraseEOF = 64774, - Key_3270_EraseInput = 64775, - Key_3270_Reset = 64776, - Key_3270_Quit = 64777, - Key_3270_PA1 = 64778, - Key_3270_PA2 = 64779, - Key_3270_PA3 = 64780, - Key_3270_Test = 64781, - Key_3270_Attn = 64782, - Key_3270_CursorBlink = 64783, - Key_3270_AltCursor = 64784, - Key_3270_KeyClick = 64785, - Key_3270_Jump = 64786, - Key_3270_Ident = 64787, - Key_3270_Rule = 64788, - Key_3270_Copy = 64789, - Key_3270_Play = 64790, - Key_3270_Setup = 64791, - Key_3270_Record = 64792, - Key_3270_ChangeScreen = 64793, - Key_3270_DeleteWord = 64794, - Key_3270_ExSelect = 64795, - Key_3270_CursorSelect = 64796, - Key_3270_PrintScreen = 64797, - Key_3270_Enter = 64798, - ISO_Lock = 65025, - ISO_Level2_Latch = 65026, - ISO_Level3_Shift = 65027, - ISO_Level3_Latch = 65028, - ISO_Level3_Lock = 65029, - ISO_Group_Latch = 65030, - ISO_Group_Lock = 65031, - ISO_Next_Group = 65032, - ISO_Next_Group_Lock = 65033, - ISO_Prev_Group = 65034, - ISO_Prev_Group_Lock = 65035, - ISO_First_Group = 65036, - ISO_First_Group_Lock = 65037, - ISO_Last_Group = 65038, - ISO_Last_Group_Lock = 65039, - ISO_Left_Tab = 65056, - ISO_Move_Line_Up = 65057, - ISO_Move_Line_Down = 65058, - ISO_Partial_Line_Up = 65059, - ISO_Partial_Line_Down = 65060, - ISO_Partial_Space_Left = 65061, - ISO_Partial_Space_Right = 65062, - ISO_Set_Margin_Left = 65063, - ISO_Set_Margin_Right = 65064, - ISO_Release_Margin_Left = 65065, - ISO_Release_Margin_Right = 65066, - ISO_Release_Both_Margins = 65067, - ISO_Fast_Cursor_Left = 65068, - ISO_Fast_Cursor_Right = 65069, - ISO_Fast_Cursor_Up = 65070, - ISO_Fast_Cursor_Down = 65071, - ISO_Continuous_Underline = 65072, - ISO_Discontinuous_Underline = 65073, - ISO_Emphasize = 65074, - ISO_Center_Object = 65075, - ISO_Enter = 65076, - dead_grave = 65104, - dead_acute = 65105, - dead_circumflex = 65106, - dead_tilde = 65107, - dead_macron = 65108, - dead_breve = 65109, - dead_abovedot = 65110, - dead_diaeresis = 65111, - dead_abovering = 65112, - dead_doubleacute = 65113, - dead_caron = 65114, - dead_cedilla = 65115, - dead_ogonek = 65116, - dead_iota = 65117, - dead_voiced_sound = 65118, - dead_semivoiced_sound = 65119, - dead_belowdot = 65120, - AccessX_Enable = 65136, - AccessX_Feedback_Enable = 65137, - RepeatKeys_Enable = 65138, - SlowKeys_Enable = 65139, - BounceKeys_Enable = 65140, - StickyKeys_Enable = 65141, - MouseKeys_Enable = 65142, - MouseKeys_Accel_Enable = 65143, - Overlay1_Enable = 65144, - Overlay2_Enable = 65145, - AudibleBell_Enable = 65146, - First_Virtual_Screen = 65232, - Prev_Virtual_Screen = 65233, - Next_Virtual_Screen = 65234, - Last_Virtual_Screen = 65236, - Terminate_Server = 65237, - Pointer_Left = 65248, - Pointer_Right = 65249, - Pointer_Up = 65250, - Pointer_Down = 65251, - Pointer_UpLeft = 65252, - Pointer_UpRight = 65253, - Pointer_DownLeft = 65254, - Pointer_DownRight = 65255, - Pointer_Button_Dflt = 65256, - Pointer_Button1 = 65257, - Pointer_Button2 = 65258, - Pointer_Button3 = 65259, - Pointer_Button4 = 65260, - Pointer_Button5 = 65261, - Pointer_DblClick_Dflt = 65262, - Pointer_DblClick1 = 65263, - Pointer_DblClick2 = 65264, - Pointer_DblClick3 = 65265, - Pointer_DblClick4 = 65266, - Pointer_DblClick5 = 65267, - Pointer_Drag_Dflt = 65268, - Pointer_Drag1 = 65269, - Pointer_Drag2 = 65270, - Pointer_Drag3 = 65271, - Pointer_Drag4 = 65272, - Pointer_EnableKeys = 65273, - Pointer_Accelerate = 65274, - Pointer_DfltBtnNext = 65275, - Pointer_DfltBtnPrev = 65276, - Pointer_Drag5 = 65277, - BackSpace = 65288, - Tab = 65289, - Linefeed = 65290, - Clear = 65291, - Return = 65293, - Pause = 65299, - Scroll_Lock = 65300, - Sys_Req = 65301, - Escape = 65307, - Multi_key = 65312, - Kanji = 65313, - Muhenkan = 65314, - Henkan = 65315, - Henkan_Mode = 65315, - Romaji = 65316, - Hiragana = 65317, - Katakana = 65318, - Hiragana_Katakana = 65319, - Zenkaku = 65320, - Hankaku = 65321, - Zenkaku_Hankaku = 65322, - Touroku = 65323, - Massyo = 65324, - Kana_Lock = 65325, - Kana_Shift = 65326, - Eisu_Shift = 65327, - Eisu_toggle = 65328, - Hangul = 65329, - Hangul_Start = 65330, - Hangul_End = 65331, - Hangul_Hanja = 65332, - Hangul_Jamo = 65333, - Hangul_Romaja = 65334, - Codeinput = 65335, - Hangul_Codeinput = 65335, - Kanji_Bangou = 65335, - Hangul_Jeonja = 65336, - Hangul_Banja = 65337, - Hangul_PreHanja = 65338, - Hangul_PostHanja = 65339, - Hangul_SingleCandidate = 65340, - SingleCandidate = 65340, - Hangul_MultipleCandidate = 65341, - MultipleCandidate = 65341, - Zen_Koho = 65341, - Hangul_PreviousCandidate = 65342, - Mae_Koho = 65342, - PreviousCandidate = 65342, - Hangul_Special = 65343, - Home = 65360, - Left = 65361, - Up = 65362, - Right = 65363, - Down = 65364, - Page_Up = 65365, - Prior = 65365, - Next = 65366, - Page_Down = 65366, - End = 65367, - Begin = 65368, - Select = 65376, - Print = 65377, - Execute = 65378, - Insert = 65379, - Undo = 65381, - Redo = 65382, - Menu = 65383, - Find = 65384, - Cancel = 65385, - Help = 65386, - Break = 65387, - Arabic_switch = 65406, - Greek_switch = 65406, - Hangul_switch = 65406, - Hebrew_switch = 65406, - ISO_Group_Shift = 65406, - Mode_switch = 65406, - kana_switch = 65406, - script_switch = 65406, - Num_Lock = 65407, - KP_Space = 65408, - KP_Tab = 65417, - KP_Enter = 65421, - KP_F1 = 65425, - KP_F2 = 65426, - KP_F3 = 65427, - KP_F4 = 65428, - KP_Home = 65429, - KP_Left = 65430, - KP_Up = 65431, - KP_Right = 65432, - KP_Down = 65433, - KP_Page_Up = 65434, - KP_Prior = 65434, - KP_Next = 65435, - KP_Page_Down = 65435, - KP_End = 65436, - KP_Begin = 65437, - KP_Insert = 65438, - KP_Delete = 65439, - KP_Multiply = 65450, - KP_Add = 65451, - KP_Separator = 65452, - KP_Subtract = 65453, - KP_Decimal = 65454, - KP_Divide = 65455, - KP_0 = 65456, - KP_1 = 65457, - KP_2 = 65458, - KP_3 = 65459, - KP_4 = 65460, - KP_5 = 65461, - KP_6 = 65462, - KP_7 = 65463, - KP_8 = 65464, - KP_9 = 65465, - KP_Equal = 65469, - F1 = 65470, - F2 = 65471, - F3 = 65472, - F4 = 65473, - F5 = 65474, - F6 = 65475, - F7 = 65476, - F8 = 65477, - F9 = 65478, - F10 = 65479, - F11 = 65480, - L1 = 65480, - F12 = 65481, - L2 = 65481, - F13 = 65482, - L3 = 65482, - F14 = 65483, - L4 = 65483, - F15 = 65484, - L5 = 65484, - F16 = 65485, - L6 = 65485, - F17 = 65486, - L7 = 65486, - F18 = 65487, - L8 = 65487, - F19 = 65488, - L9 = 65488, - F20 = 65489, - L10 = 65489, - F21 = 65490, - R1 = 65490, - F22 = 65491, - R2 = 65491, - F23 = 65492, - R3 = 65492, - F24 = 65493, - R4 = 65493, - F25 = 65494, - R5 = 65494, - F26 = 65495, - R6 = 65495, - F27 = 65496, - R7 = 65496, - F28 = 65497, - R8 = 65497, - F29 = 65498, - R9 = 65498, - F30 = 65499, - R10 = 65499, - F31 = 65500, - R11 = 65500, - F32 = 65501, - R12 = 65501, - F33 = 65502, - R13 = 65502, - F34 = 65503, - R14 = 65503, - F35 = 65504, - R15 = 65504, - Shift_L = 65505, - Shift_R = 65506, - Control_L = 65507, - Control_R = 65508, - Caps_Lock = 65509, - Shift_Lock = 65510, - Meta_L = 65511, - Meta_R = 65512, - Alt_L = 65513, - Alt_R = 65514, - Super_L = 65515, - Super_R = 65516, - Hyper_L = 65517, - Hyper_R = 65518, - Delete = 65535, - VoidSymbol = 16777215, - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs b/src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs deleted file mode 100644 index b80914572b..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Gtk3.Interop; -using Avalonia.Platform; -using Avalonia.Platform.Interop; - -namespace Avalonia.Gtk3 -{ - public class Gtk3ForeignX11SystemDialog : ISystemDialogImpl - { - private Task _initialized; - private SystemDialogBase _inner = new SystemDialogBase(); - - - public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) - { - await EnsureInitialized(); - var xid = parent.Handle.Handle; - return await await RunOnGtkThread( - () => _inner.ShowFileDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid))); - } - - public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) - { - await EnsureInitialized(); - var xid = parent.Handle.Handle; - return await await RunOnGtkThread( - () => _inner.ShowFolderDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid))); - } - - void UpdateParent(GtkFileChooser chooser, IntPtr xid) - { - Native.GtkWidgetRealize(chooser); - var window = Native.GtkWidgetGetWindow(chooser); - var parent = Native.GdkWindowForeignNewForDisplay(GdkDisplay, xid); - if (window != IntPtr.Zero && parent != IntPtr.Zero) - Native.GdkWindowSetTransientFor(window, parent); - } - - async Task EnsureInitialized() - { - if (_initialized == null) - { - var tcs = new TaskCompletionSource(); - _initialized = tcs.Task; - new Thread(() => GtkThread(tcs)) - { - IsBackground = true - }.Start(); - } - - if (!(await _initialized)) - throw new Exception("Unable to initialize GTK on separate thread"); - - } - - Task RunOnGtkThread(Func action) - { - var tcs = new TaskCompletionSource(); - GlibTimeout.Add(0, 0, () => - { - - try - { - tcs.SetResult(action()); - } - catch (Exception e) - { - tcs.TrySetException(e); - } - - return false; - }); - return tcs.Task; - } - - - void GtkThread(TaskCompletionSource tcs) - { - try - { - X11.XInitThreads(); - }catch{} - Resolver.Resolve(); - if (Native.GdkWindowForeignNewForDisplay == null) - throw new Exception("gdk_x11_window_foreign_new_for_display is not found in your libgdk-3.so"); - using (var backends = new Utf8Buffer("x11")) - Native.GdkSetAllowedBackends?.Invoke(backends); - if (!Native.GtkInitCheck(0, IntPtr.Zero)) - { - tcs.SetResult(false); - return; - } - - using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid().ToString("N")}")) - App = Native.GtkApplicationNew(utf, 0); - if (App == IntPtr.Zero) - { - tcs.SetResult(false); - return; - } - GdkDisplay = Native.GdkGetDefaultDisplay(); - tcs.SetResult(true); - while (true) - Native.GtkMainIteration(); - } - - private IntPtr GdkDisplay { get; set; } - private IntPtr App { get; set; } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs deleted file mode 100644 index 965973d3cb..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Threading; -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Gtk3; -using Avalonia.Gtk3.Interop; -using Avalonia.Input; -using Avalonia.Input.Platform; -using Avalonia.OpenGL; -using Avalonia.Platform; -using Avalonia.Platform.Interop; -using Avalonia.Rendering; -using Avalonia.Threading; - -namespace Avalonia.Gtk3 -{ - public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface - { - internal static readonly Gtk3Platform Instance = new Gtk3Platform(); - internal static readonly MouseDevice Mouse = new MouseDevice(); - internal static readonly KeyboardDevice Keyboard = new KeyboardDevice(); - internal static IntPtr App { get; set; } - internal static string DisplayClassName; - public static bool UseDeferredRendering = true; - private static bool s_gtkInitialized; - - static bool EnvOption(string option, bool def, bool? specified) - { - bool? Parse(string env) - { - var v = Environment.GetEnvironmentVariable("AVALONIA_GTK3_" + env); - if (v == null) - return null; - if (v.ToLowerInvariant() == "false" || v == "0") - return false; - return true; - } - - var overridden = Parse(option + "_OVERRIDE"); - if (overridden.HasValue) - return overridden.Value; - if (specified.HasValue) - return specified.Value; - var envValue = Parse(option); - return envValue ?? def; - } - - public static void Initialize(Gtk3PlatformOptions options) - { - Resolver.Custom = options.CustomResolver; - UseDeferredRendering = EnvOption("USE_DEFERRED_RENDERING", true, options.UseDeferredRendering); - var useGpu = EnvOption("USE_GPU", true, options.UseGpuAcceleration); - if (!s_gtkInitialized) - { - try - { - X11.XInitThreads(); - }catch{} - Resolver.Resolve(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - using (var backends = new Utf8Buffer("x11")) - Native.GdkSetAllowedBackends?.Invoke(backends); - Native.GtkInit(0, IntPtr.Zero); - var disp = Native.GdkGetDefaultDisplay(); - DisplayClassName = - Utf8Buffer.StringFromPtr(Native.GTypeName(Marshal.ReadIntPtr(Marshal.ReadIntPtr(disp)))); - - using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid().ToString("N")}")) - App = Native.GtkApplicationNew(utf, 0); - //Mark current thread as UI thread - s_tlsMarker = true; - s_gtkInitialized = true; - } - AvaloniaLocator.CurrentMutable.Bind().ToConstant(Instance) - .Bind().ToSingleton() - .Bind().ToConstant(new CursorFactory()) - .Bind().ToConstant(Keyboard) - .Bind().ToConstant(Instance) - .Bind().ToConstant(Instance) - .Bind().ToSingleton() - .Bind().ToConstant(new RenderLoop()) - .Bind().ToConstant(new DefaultRenderTimer(60)) - .Bind().ToSingleton() - .Bind().ToConstant(new PlatformIconLoader()); - if (useGpu) - EglGlPlatformFeature.TryInitialize(); - } - - public IWindowImpl CreateWindow() => new WindowImpl(); - - public IEmbeddableWindowImpl CreateEmbeddableWindow() - { - throw new NotImplementedException(); - } - - public IPopupImpl CreatePopup() => new PopupImpl(); - - public Size DoubleClickSize => new Size(4, 4); - - public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(100); //STUB - public double RenderScalingFactor { get; } = 1; - public double LayoutScalingFactor { get; } = 1; - - public void RunLoop(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - Native.GtkMainIteration(); - } - - public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) - { - var msec = interval.TotalMilliseconds; - var imsec = (uint) msec; - if (imsec == 0) - imsec = 1; - return GlibTimeout.StartTimer(GlibPriority.FromDispatcherPriority(priority), imsec, tick); - } - - private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1]; - object _lock = new object(); - public void Signal(DispatcherPriority prio) - { - var idx = (int) prio; - lock(_lock) - if (!_signaled[idx]) - { - _signaled[idx] = true; - GlibTimeout.Add(GlibPriority.FromDispatcherPriority(prio), 0, () => - { - lock (_lock) - { - _signaled[idx] = false; - } - Signaled?.Invoke(prio); - return false; - }); - } - } - public event Action Signaled; - - - [ThreadStatic] - private static bool s_tlsMarker; - - public bool CurrentThreadIsLoopThread => s_tlsMarker; - } - - public class Gtk3PlatformOptions - { - public bool? UseDeferredRendering { get; set; } - public bool? UseGpuAcceleration { get; set; } - public ICustomGtk3NativeLibraryResolver CustomResolver { get; set; } - } -} - -namespace Avalonia -{ - public static class Gtk3AppBuilderExtensions - { - public static T UseGtk3(this AppBuilderBase builder, Gtk3PlatformOptions options = null) - where T : AppBuilderBase, new() - { - return builder.UseWindowingSubsystem(() => Gtk3Platform.Initialize(options ?? new Gtk3PlatformOptions()), - "GTK3"); - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs deleted file mode 100644 index a2b4604130..0000000000 --- a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Avalonia.Platform; - -namespace Avalonia.Gtk3 -{ - public class GtkScreen : Screen - { - private readonly int _screenId; - - public GtkScreen(PixelRect bounds, PixelRect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary) - { - this._screenId = screenId; - } - - public override int GetHashCode() - { - return _screenId; - } - - public override bool Equals(object obj) - { - return (obj is GtkScreen screen) ? this._screenId == screen._screenId : base.Equals(obj); - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs deleted file mode 100644 index e16463a2ef..0000000000 --- a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Avalonia.Gtk3 -{ - public interface IDeferredRenderOperation : IDisposable - { - void RenderNow(IntPtr? ctx); - } -} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs deleted file mode 100644 index 878689442d..0000000000 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using Avalonia.Gtk3.Interop; -using Avalonia.Platform; -using Avalonia.Threading; - - -namespace Avalonia.Gtk3 -{ - class ImageSurfaceFramebuffer : ILockedFramebuffer - { - private readonly WindowBaseImpl _impl; - private readonly GtkWidget _widget; - private ManagedCairoSurface _surface; - private int _factor; - private object _lock = new object(); - public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height, int factor) - { - _impl = impl; - _widget = impl.GtkWidget; - _factor = factor; - width *= _factor; - height *= _factor; - _surface = new ManagedCairoSurface(width, height); - - Size = new PixelSize(width, height); - Address = _surface.Buffer; - RowBytes = _surface.Stride; - Native.CairoSurfaceFlush(_surface.Surface); - } - - static void Draw(IntPtr context, CairoSurface surface, double factor) - { - - Native.CairoSurfaceMarkDirty(surface); - Native.CairoScale(context, 1d / factor, 1d / factor); - Native.CairoSetSourceSurface(context, surface, 0, 0); - Native.CairoPaint(context); - - } - /* - static Stopwatch St =Stopwatch.StartNew(); - private static int _frames; - private static int _fps;*/ - static void DrawToWidget(GtkWidget widget, CairoSurface surface, int width, int height, double factor) - { - if(surface == null || widget.IsClosed) - return; - var window = Native.GtkWidgetGetWindow(widget); - if(window == IntPtr.Zero) - return; - var rc = new GdkRectangle {Width = width, Height = height}; - Native.GdkWindowBeginPaintRect(window, ref rc); - var context = Native.GdkCairoCreate(window); - Draw(context, surface, factor); - /* - _frames++; - var el = St.Elapsed; - if (el.TotalSeconds > 1) - { - _fps = (int) (_frames / el.TotalSeconds); - _frames = 0; - St = Stopwatch.StartNew(); - } - - Native.CairoSetSourceRgba(context, 1, 0, 0, 1); - Native.CairoMoveTo(context, 20, 20); - Native.CairoSetFontSize(context, 30); - using (var txt = new Utf8Buffer("FPS: " + _fps)) - Native.CairoShowText(context, txt); - */ - - Native.CairoDestroy(context); - Native.GdkWindowEndPaint(window); - } - - class RenderOp : IDeferredRenderOperation - { - private readonly GtkWidget _widget; - private ManagedCairoSurface _surface; - private readonly double _factor; - private readonly int _width; - private readonly int _height; - - public RenderOp(GtkWidget widget, ManagedCairoSurface surface, double factor, int width, int height) - { - _widget = widget; - _surface = surface ?? throw new ArgumentNullException(nameof(surface)); - _factor = factor; - _width = width; - _height = height; - } - - public void Dispose() - { - _surface?.Dispose(); - _surface = null; - } - - public void RenderNow(IntPtr? ctx) - { - if(ctx.HasValue) - Draw(ctx.Value, _surface.Surface, _factor); - else - DrawToWidget(_widget, _surface.Surface, _width, _height, _factor); - } - } - - public void Dispose() - { - lock (_lock) - { - if (Dispatcher.UIThread.CheckAccess()) - { - if (_impl.CurrentCairoContext != IntPtr.Zero) - Draw(_impl.CurrentCairoContext, _surface.Surface, _factor); - else - DrawToWidget(_widget, _surface.Surface, Size.Width, Size.Height, _factor); - _surface.Dispose(); - } - else - _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Size.Width, Size.Height)); - _surface = null; - } - } - - public IntPtr Address { get; } - public PixelSize Size { get; } - public int RowBytes { get; } - - - public Vector Dpi - { - get - { - return new Vector(96, 96) * _factor; - } - } - - public PixelFormat Format => PixelFormat.Bgra8888; - } -} - - - diff --git a/src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs b/src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs deleted file mode 100644 index 7838be9305..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Avalonia.Gtk3.Interop -{ - class CairoSurface : SafeHandle - { - public CairoSurface() : base(IntPtr.Zero, true) - { - } - - protected override bool ReleaseHandle() - { - Native.CairoSurfaceDestroy(handle); - return true; - } - - public override bool IsInvalid => handle == IntPtr.Zero; - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GException.cs b/src/Gtk/Avalonia.Gtk3/Interop/GException.cs deleted file mode 100644 index 5ff6cbe8ed..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/GException.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Avalonia.Platform.Interop; - -namespace Avalonia.Gtk3.Interop -{ - public class GException : Exception - { - [StructLayout(LayoutKind.Sequential)] - struct GError - { - UInt32 domain; - int code; - public IntPtr message; - }; - - static unsafe string GetError(IntPtr error) - { - if (error == IntPtr.Zero) - return "Unknown error"; - return Utf8Buffer.StringFromPtr(((GError*) error)->message); - } - - public GException(IntPtr error) : base(GetError(error)) - { - - } - - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs b/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs deleted file mode 100644 index bb272b06d5..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace Avalonia.Gtk3.Interop -{ - class GObject : SafeHandle - { - public GObject() : base(IntPtr.Zero, true) - { - } - - public GObject(IntPtr handle, bool owned = true) : base(IntPtr.Zero, owned) - { - this.handle = handle; - } - - protected override bool ReleaseHandle() - { - if (handle != IntPtr.Zero) - { - Debug.Assert(Native.GTypeCheckInstanceIsFundamentallyA(handle, new IntPtr(Native.G_TYPE_OBJECT)), - "Handle is not a GObject"); - Native.GObjectUnref(handle); - } - - handle = IntPtr.Zero; - return true; - } - - public override bool IsInvalid => handle == IntPtr.Zero; - } - - class GInputStream : GObject - { - - } - - class GtkWidget : GObject - { - - } - - class GtkWindow : GtkWidget - { - public static GtkWindow Null { get; } = new GtkWindow(); - } - - class GtkImContext : GObject - { - } - - class GdkScreen : GObject - { - public GdkScreen() : base(IntPtr.Zero, false) - { - } - - public GdkScreen(IntPtr handle, bool owned = true) : base(handle, owned) - { - this.handle = handle; - } - } - - class UnownedGdkScreen : GdkScreen - { - public UnownedGdkScreen() : base(IntPtr.Zero, false) - { - } - - public UnownedGdkScreen(IntPtr handle, bool owned = true) : base(IntPtr.Zero, false) - { - this.handle = handle; - } - } - - class GtkDialog : GtkWindow - { - - } - - class GtkFileChooser : GtkDialog - { - - } -} - diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs deleted file mode 100644 index 08448e30d5..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibPriority.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Avalonia.Threading; - -namespace Avalonia.Gtk3.Interop -{ - static class GlibPriority - { - public static int High = -100; - public static int Default = 0; - public static int HighIdle = 100; - public static int GtkResize = HighIdle + 10; - public static int GtkPaint = HighIdle + 20; - public static int DefaultIdle = 200; - public static int Low = 300; - public static int GdkEvents = Default; - public static int GdkRedraw = HighIdle + 20; - - public static int FromDispatcherPriority(DispatcherPriority prio) - { - if (prio == DispatcherPriority.Send) - return High; - if (prio == DispatcherPriority.Normal) - return Default; - if (prio == DispatcherPriority.DataBind) - return Default + 1; - if (prio == DispatcherPriority.Layout) - return Default + 2; - if (prio == DispatcherPriority.Render) - return Default + 3; - if (prio == DispatcherPriority.Loaded) - return GtkPaint + 20; - if (prio == DispatcherPriority.Input) - return GtkPaint + 21; - if (prio == DispatcherPriority.Background) - return DefaultIdle + 1; - if (prio == DispatcherPriority.ContextIdle) - return DefaultIdle + 2; - if (prio == DispatcherPriority.ApplicationIdle) - return DefaultIdle + 3; - if (prio == DispatcherPriority.SystemIdle) - return DefaultIdle + 4; - throw new ArgumentException("Unknown priority"); - - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs deleted file mode 100644 index 6e3840914c..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Avalonia.Gtk3.Interop -{ - static class GlibTimeout - { - static bool Handler(IntPtr data) - { - var handle = GCHandle.FromIntPtr(data); - var cb = (Func) handle.Target; - if (!cb()) - { - handle.Free(); - return false; - } - return true; - } - - private static readonly Native.D.timeout_callback PinnedHandler; - static GlibTimeout() - { - PinnedHandler = Handler; - } - - - public static void Add(int priority, uint interval, Func callback) - { - var handle = GCHandle.Alloc(callback); - Native.GTimeoutAddFull(priority, interval, PinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero); - } - - class Timer : IDisposable - { - public bool Stopped; - public void Dispose() - { - - Stopped = true; - } - } - - public static IDisposable StartTimer(int priority, uint interval, Action tick) - { - var timer = new Timer (); - GlibTimeout.Add(priority, interval, - () => - { - if (timer.Stopped) - return false; - tick(); - return !timer.Stopped; - }); - - return timer; - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs deleted file mode 100644 index 3b78953d1b..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Avalonia.Gtk3.Interop; - -namespace Avalonia.Gtk3 -{ - public interface ICustomGtk3NativeLibraryResolver - { - string GetName(GtkDll dll); - string BasePath { get; } - bool TrySystemFirst { get; } - string Lookup(GtkDll dll); - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs b/src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs deleted file mode 100644 index 2b0a7eae12..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Avalonia.Platform; - -namespace Avalonia.Gtk3.Interop -{ - class ManagedCairoSurface : IDisposable - { - public IntPtr Buffer { get; private set; } - public CairoSurface Surface { get; private set; } - public int Stride { get; private set; } - private int _size; - private IRuntimePlatform _plat; - private IUnmanagedBlob _blob; - - public ManagedCairoSurface(int width, int height) - { - _plat = AvaloniaLocator.Current.GetService(); - Stride = width * 4; - _size = height * Stride; - _blob = _plat.AllocBlob(_size * 2); - Buffer = _blob.Address; - Surface = Native.CairoImageSurfaceCreateForData(Buffer, 1, width, height, Stride); - } - - public void Dispose() - { - - if (Buffer != IntPtr.Zero) - { - Surface.Dispose(); - _blob.Dispose(); - Buffer = IntPtr.Zero; - } - } - - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs deleted file mode 100644 index 765c19a796..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ /dev/null @@ -1,790 +0,0 @@ -#pragma warning disable 649 -using System; -using System.Runtime.InteropServices; -using Avalonia.Controls; -using Avalonia.Platform.Interop; -using gdouble = System.Double; -using gint = System.Int32; -using gint16 = System.Int16; -using gint8 = System.Byte; -using guint = System.UInt32; -using guint16 = System.UInt16; -using guint32 = System.UInt32; - -namespace Avalonia.Gtk3.Interop -{ - static class Native - { - public static class D - { - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate gint16 gdk_display_get_n_screens(IntPtr display); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate UnownedGdkScreen gdk_display_get_screen(IntPtr display, gint16 num); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate UnownedGdkScreen gdk_display_get_default_screen (IntPtr display); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate gint16 gdk_screen_get_n_monitors(GdkScreen screen); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate gint16 gdk_screen_get_primary_monitor(GdkScreen screen); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_screen_get_monitor_geometry(GdkScreen screen, gint16 num, ref GdkRectangle rect); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_screen_get_monitor_workarea(GdkScreen screen, gint16 num, ref GdkRectangle rect); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_application_new(Utf8Buffer appId, int flags); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_main_iteration(); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate GtkWindow gtk_window_new(GtkWindowType windowType); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_init(int argc, IntPtr argv); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate bool gtk_init_check(int argc, IntPtr argv); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)] - public delegate IntPtr gdk_set_allowed_backends (Utf8Buffer backends); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_present(GtkWindow gtkWindow); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_widget_hide(GtkWidget gtkWidget); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_widget_show(GtkWidget gtkWidget); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_icon(GtkWindow window, Pixbuf pixbuf); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_modal(GtkWindow window, bool modal); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_transient_for(GtkWindow window, IntPtr parent); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] //No manual import - public delegate IntPtr gdk_get_native_handle(IntPtr gdkWindow); - - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_widget_get_window(GtkWidget gtkWidget); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk, optional: true)] - public delegate uint gtk_widget_get_scale_factor(GtkWidget gtkWidget); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_widget_get_screen(GtkWidget gtkWidget); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_widget_set_double_buffered(GtkWidget gtkWidget, bool value); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_widget_set_events(GtkWidget gtkWidget, uint flags); - - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate int gdk_screen_get_height(IntPtr screen); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate int gdk_screen_get_width(IntPtr screen); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate IntPtr gdk_display_get_default(); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate int gdk_window_get_origin(IntPtr gdkWindow, out int x, out int y); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_resize(IntPtr gtkWindow, int width, int height); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_set_override_redirect(IntPtr gdkWindow, bool value); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_widget_realize(GtkWidget gtkWidget); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_title(GtkWindow gtkWindow, Utf8Buffer title); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_resizable(GtkWindow gtkWindow, bool resizable); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_decorated(GtkWindow gtkWindow, bool decorated); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_skip_taskbar_hint(GtkWindow gtkWindow, bool setting); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate bool gtk_window_get_skip_taskbar_hint(GtkWindow gtkWindow); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_skip_pager_hint(GtkWindow gtkWindow, bool setting); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate bool gtk_window_get_skip_pager_hint(GtkWindow gtkWindow); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_get_size(GtkWindow gtkWindow, out int width, out int height); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_resize(GtkWindow gtkWindow, int width, int height); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_widget_set_size_request(GtkWidget widget, int width, int height); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_default_size(GtkWindow gtkWindow, int width, int height); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_get_position(GtkWindow gtkWindow, out int x, out int y); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_move(GtkWindow gtkWindow, int x, int y); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate GtkFileChooser gtk_file_chooser_dialog_new(Utf8Buffer title, GtkWindow parent, GtkFileChooserAction action, IntPtr ignore); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public unsafe delegate GSList* gtk_file_chooser_get_filenames(GtkFileChooser chooser); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_file_chooser_set_select_multiple(GtkFileChooser chooser, bool allow); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_file_chooser_set_filename(GtkFileChooser chooser, Utf8Buffer file); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_dialog_add_button(GtkDialog raw, Utf8Buffer button_text, GtkResponseType response_id); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate CairoSurface cairo_image_surface_create(int format, int width, int height); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate CairoSurface cairo_image_surface_create_for_data(IntPtr data, int format, int width, int height, int stride); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate IntPtr cairo_image_surface_get_data(CairoSurface surface); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate int cairo_image_surface_get_stride(CairoSurface surface); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_surface_mark_dirty(CairoSurface surface); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_surface_write_to_png(CairoSurface surface, Utf8Buffer path); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_surface_flush(CairoSurface surface); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_surface_destroy(IntPtr surface); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_set_source_rgba(IntPtr cr, double r, double g, double b, double a); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_scale(IntPtr context, double sx, double sy); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_paint(IntPtr context); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_show_text(IntPtr context, Utf8Buffer text); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_set_font_size(IntPtr context, double size); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_select_font_face(IntPtr context, Utf8Buffer face, int slant, int weight); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_move_to(IntPtr context, double x, double y); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] - public delegate void cairo_destroy(IntPtr context); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_widget_queue_draw_area(GtkWidget widget, int x, int y, int width, int height); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate uint gtk_widget_add_tick_callback(GtkWidget widget, TickCallback callback, IntPtr userData, IntPtr destroy); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate uint gtk_widget_remove_tick_callback(GtkWidget widget, uint id); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate GtkImContext gtk_im_multicontext_new(); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_im_context_set_client_window(GtkImContext context, IntPtr window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate bool gtk_im_context_filter_keypress(GtkImContext context, IntPtr ev); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_widget_activate(GtkWidget widget); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate IntPtr gdk_screen_get_root_window(IntPtr screen); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate IntPtr gdk_cursor_new(GdkCursorType type); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate IntPtr gdk_window_get_pointer(IntPtr raw, out int x, out int y, out int mask); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate GdkWindowState gdk_window_get_state(IntPtr window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_iconify(GtkWindow window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_deiconify(GtkWindow window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_maximize(GtkWindow window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_unmaximize(GtkWindow window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_close(GtkWindow window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_keep_above(GtkWindow gtkWindow, bool setting); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_invalidate_rect(IntPtr window, ref GdkRectangle rect, bool invalidate_children); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_begin_move_drag(IntPtr window, gint button, gint root_x, gint root_y, guint32 timestamp); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_begin_resize_drag(IntPtr window, WindowEdge edge, gint button, gint root_x, gint root_y, guint32 timestamp); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_process_updates(IntPtr window, bool updateChildren); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_begin_paint_rect(IntPtr window, ref GdkRectangle rect); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_end_paint(IntPtr window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)] - public delegate IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_window_set_transient_for(IntPtr window, IntPtr parent); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate void gdk_event_request_motions(IntPtr ev); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_clipboard_get_for_display(IntPtr display, IntPtr atom); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_clipboard_request_text(IntPtr clipboard, GtkClipboardTextReceivedFunc callback, IntPtr user_data); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_clipboard_set_text(IntPtr clipboard, Utf8Buffer text, int len); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate void gtk_clipboard_clear(IntPtr clipboard); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.GdkPixBuf)] - public delegate IntPtr gdk_pixbuf_new_from_file(Utf8Buffer filename, out IntPtr error); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_icon_theme_get_default(); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_icon_theme_load_icon(IntPtr icon_theme, Utf8Buffer icon_name, gint size, int flags,out IntPtr error); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate IntPtr gdk_cursor_new_from_pixbuf(IntPtr disp, IntPtr pixbuf, int x, int y); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate IntPtr gdk_window_set_cursor(IntPtr window, IntPtr cursor); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.GdkPixBuf)] - public delegate IntPtr gdk_pixbuf_new_from_stream(GInputStream stream, IntPtr cancel, out IntPtr error); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.GdkPixBuf)] - public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer, out IntPtr buffer_size, - Utf8Buffer type, IntPtr option_keys, IntPtr option_values, out IntPtr error); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] - public delegate IntPtr gdk_cairo_create(IntPtr window); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] - public delegate void g_object_unref(IntPtr instance); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] - public delegate void g_object_ref(GObject instance); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] - public delegate IntPtr g_type_name(IntPtr instance); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] - public delegate ulong g_signal_connect_object(GObject instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] - public delegate ulong g_signal_handler_disconnect(GObject instance, ulong connectionId); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] - public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] - public delegate ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data, IntPtr destroy); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] - public delegate ulong g_free(IntPtr data); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] - public delegate bool g_type_check_instance_is_fundamentally_a(IntPtr instance, IntPtr type); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] - public unsafe delegate void g_slist_free(GSList* data); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gio)] - public delegate GInputStream g_memory_input_stream_new_from_data(IntPtr ptr, IntPtr len, IntPtr destroyCallback); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool signal_widget_draw(IntPtr gtkWidget, IntPtr cairoContext, IntPtr userData); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool signal_generic(IntPtr gtkWidget, IntPtr userData); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool signal_dialog_response(IntPtr gtkWidget, GtkResponseType response, IntPtr userData); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool signal_onevent(IntPtr gtkWidget, IntPtr ev, IntPtr userData); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void monitors_changed(IntPtr screen, IntPtr userData); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool signal_commit(IntPtr gtkWidget, IntPtr utf8string, IntPtr userData); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool timeout_callback(IntPtr data); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void GtkClipboardTextReceivedFunc(IntPtr clipboard, IntPtr utf8string, IntPtr userdata); - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate bool TickCallback(IntPtr widget, IntPtr clock, IntPtr userdata); - - - } - - public static D.gdk_display_get_n_screens GdkDisplayGetNScreens; - public static D.gdk_display_get_screen GdkDisplayGetScreen; - public static D.gdk_display_get_default_screen GdkDisplayGetDefaultScreen; - public static D.gdk_screen_get_n_monitors GdkScreenGetNMonitors; - public static D.gdk_screen_get_primary_monitor GdkScreenGetPrimaryMonitor; - public static D.gdk_screen_get_monitor_geometry GdkScreenGetMonitorGeometry; - public static D.gdk_screen_get_monitor_workarea GdkScreenGetMonitorWorkarea; - public static D.gtk_window_set_decorated GtkWindowSetDecorated; - public static D.gtk_window_set_resizable GtkWindowSetResizable; - public static D.gtk_window_set_skip_taskbar_hint GtkWindowSetSkipTaskbarHint; - public static D.gtk_window_get_skip_taskbar_hint GtkWindowGetSkipTaskbarHint; - public static D.gtk_window_set_skip_pager_hint GtkWindowSetSkipPagerHint; - public static D.gtk_window_get_skip_pager_hint GtkWindowGetSkipPagerHint; - public static D.gtk_window_set_title GtkWindowSetTitle; - public static D.gtk_application_new GtkApplicationNew; - public static D.gtk_main_iteration GtkMainIteration; - public static D.gtk_window_new GtkWindowNew; - public static D.gtk_window_set_icon GtkWindowSetIcon; - public static D.gtk_window_set_modal GtkWindowSetModal; - public static D.gtk_window_set_transient_for GtkWindowSetTransientFor; - public static D.gdk_set_allowed_backends GdkSetAllowedBackends; - public static D.gtk_init GtkInit; - public static D.gtk_init_check GtkInitCheck; - public static D.gtk_window_present GtkWindowPresent; - public static D.gtk_widget_hide GtkWidgetHide; - public static D.gtk_widget_show GtkWidgetShow; - public static D.gdk_get_native_handle GetNativeGdkWindowHandle; - public static D.gtk_widget_get_window GtkWidgetGetWindow; - public static D.gtk_widget_get_scale_factor GtkWidgetGetScaleFactor; - public static D.gtk_widget_get_screen GtkWidgetGetScreen; - public static D.gtk_widget_realize GtkWidgetRealize; - public static D.gtk_window_get_size GtkWindowGetSize; - public static D.gtk_window_resize GtkWindowResize; - public static D.gdk_window_resize GdkWindowResize; - public static D.gdk_window_set_override_redirect GdkWindowSetOverrideRedirect; - public static D.gtk_widget_set_size_request GtkWindowSetSizeRequest; - public static D.gtk_window_set_default_size GtkWindowSetDefaultSize; - public static D.gtk_window_set_geometry_hints GtkWindowSetGeometryHints; - public static D.gtk_window_get_position GtkWindowGetPosition; - public static D.gtk_window_move GtkWindowMove; - public static D.gtk_file_chooser_dialog_new GtkFileChooserDialogNew; - public static D.gtk_file_chooser_set_select_multiple GtkFileChooserSetSelectMultiple; - public static D.gtk_file_chooser_set_filename GtkFileChooserSetFilename; - public static D.gtk_file_chooser_get_filenames GtkFileChooserGetFilenames; - public static D.gtk_dialog_add_button GtkDialogAddButton; - public static D.g_object_unref GObjectUnref; - public static D.g_object_ref GObjectRef; - public static D.g_type_name GTypeName; - public static D.g_signal_connect_object GSignalConnectObject; - public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; - public static D.g_timeout_add GTimeoutAdd; - public static D.g_timeout_add_full GTimeoutAddFull; - public static D.g_free GFree; - public static D.g_type_check_instance_is_fundamentally_a GTypeCheckInstanceIsFundamentallyA; - public static D.g_slist_free GSlistFree; - public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData; - public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered; - public static D.gtk_widget_set_events GtkWidgetSetEvents; - public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect; - public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea; - public static D.gtk_widget_add_tick_callback GtkWidgetAddTickCallback; - public static D.gtk_widget_remove_tick_callback GtkWidgetRemoveTickCallback; - public static D.gtk_widget_activate GtkWidgetActivate; - public static D.gtk_clipboard_get_for_display GtkClipboardGetForDisplay; - public static D.gtk_clipboard_request_text GtkClipboardRequestText; - public static D.gtk_clipboard_set_text GtkClipboardSetText; - public static D.gtk_clipboard_clear GtkClipboardRequestClear; - - public static D.gtk_im_multicontext_new GtkImMulticontextNew; - public static D.gtk_im_context_filter_keypress GtkImContextFilterKeypress; - public static D.gtk_im_context_set_client_window GtkImContextSetClientWindow; - - public static D.gdk_screen_get_height GdkScreenGetHeight; - public static D.gdk_display_get_default GdkGetDefaultDisplay; - public static D.gdk_screen_get_width GdkScreenGetWidth; - public static D.gdk_screen_get_root_window GdkScreenGetRootWindow; - public static D.gdk_cursor_new GdkCursorNew; - public static D.gdk_window_get_origin GdkWindowGetOrigin; - public static D.gdk_window_get_pointer GdkWindowGetPointer; - public static D.gdk_window_get_state GdkWindowGetState; - public static D.gtk_window_iconify GtkWindowIconify; - public static D.gtk_window_deiconify GtkWindowDeiconify; - public static D.gtk_window_maximize GtkWindowMaximize; - public static D.gtk_window_unmaximize GtkWindowUnmaximize; - public static D.gtk_window_close GtkWindowClose; - public static D.gtk_window_set_keep_above GtkWindowSetKeepAbove; - public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag; - public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag; - public static D.gdk_event_request_motions GdkEventRequestMotions; - public static D.gdk_window_process_updates GdkWindowProcessUpdates; - public static D.gdk_window_begin_paint_rect GdkWindowBeginPaintRect; - public static D.gdk_window_end_paint GdkWindowEndPaint; - public static D.gdk_x11_window_foreign_new_for_display GdkWindowForeignNewForDisplay; - public static D.gdk_window_set_transient_for GdkWindowSetTransientFor; - - public static D.gdk_pixbuf_new_from_file GdkPixbufNewFromFile; - public static D.gtk_icon_theme_get_default GtkIconThemeGetDefault; - public static D.gtk_icon_theme_load_icon GtkIconThemeLoadIcon; - public static D.gdk_cursor_new_from_pixbuf GdkCursorNewFromPixbuf; - public static D.gdk_window_set_cursor GdkWindowSetCursor; - public static D.gdk_pixbuf_new_from_stream GdkPixbufNewFromStream; - public static D.gdk_pixbuf_save_to_bufferv GdkPixbufSaveToBufferv; - public static D.gdk_cairo_create GdkCairoCreate; - - public static D.cairo_image_surface_create CairoImageSurfaceCreate; - public static D.cairo_image_surface_create_for_data CairoImageSurfaceCreateForData; - public static D.cairo_image_surface_get_data CairoImageSurfaceGetData; - public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride; - public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty; - public static D.cairo_surface_write_to_png CairoSurfaceWriteToPng; - public static D.cairo_surface_flush CairoSurfaceFlush; - public static D.cairo_surface_destroy CairoSurfaceDestroy; - public static D.cairo_set_source_surface CairoSetSourceSurface; - public static D.cairo_set_source_rgba CairoSetSourceRgba; - public static D.cairo_scale CairoScale; - public static D.cairo_paint CairoPaint; - public static D.cairo_show_text CairoShowText; - public static D.cairo_select_font_face CairoSelectFontFace; - public static D.cairo_set_font_size CairoSetFontSize; - public static D.cairo_move_to CairoMoveTo; - public static D.cairo_destroy CairoDestroy; - - public const int G_TYPE_OBJECT = 80; - } - - public enum GtkWindowType - { - TopLevel, - Popup - } - - [StructLayout(LayoutKind.Sequential)] - public struct GdkRectangle - { - public int X, Y, Width, Height; - - public static GdkRectangle FromRect(Rect rect) - { - return new GdkRectangle - { - X = (int) rect.X, - Y = (int) rect.Y, - Width = (int) rect.Width, - Height = (int) rect.Height - }; - } - } - - enum GdkEventType - { - Nothing = -1, - Delete = 0, - Destroy = 1, - Expose = 2, - MotionNotify = 3, - ButtonPress = 4, - TwoButtonPress = 5, - ThreeButtonPress = 6, - ButtonRelease = 7, - KeyPress = 8, - KeyRelease = 9, - EnterNotify = 10, - LeaveNotify = 11, - FocusChange = 12, - Configure = 13, - Map = 14, - Unmap = 15, - PropertyNotify = 16, - SelectionClear = 17, - SelectionRequest = 18, - SelectionNotify = 19, - ProximityIn = 20, - ProximityOut = 21, - DragEnter = 22, - DragLeave = 23, - DragMotion = 24, - DragStatus = 25, - DropStart = 26, - DropFinished = 27, - ClientEvent = 28, - VisibilityNotify = 29, - NoExpose = 30, - Scroll = 31, - WindowState = 32, - Setting = 33, - OwnerChange = 34, - GrabBroken = 35, - } - - enum GdkModifierType - { - ShiftMask = 1, - LockMask = 2, - ControlMask = 4, - Mod1Mask = 8, - Mod2Mask = 16, - Mod3Mask = 32, - Mod4Mask = 64, - Mod5Mask = 128, - Button1Mask = 256, - Button2Mask = 512, - Button3Mask = 1024, - Button4Mask = 2048, - Button5Mask = 4096, - SuperMask = 67108864, - HyperMask = 134217728, - MetaMask = 268435456, - ReleaseMask = 1073741824, - ModifierMask = ReleaseMask | Button5Mask | Button4Mask | Button3Mask | Button2Mask | Button1Mask | Mod5Mask | Mod4Mask | Mod3Mask | Mod2Mask | Mod1Mask | ControlMask | LockMask | ShiftMask, - None = 0, - } - - enum GdkScrollDirection - { - Up, - Down, - Left, - Right, - Smooth - } - - [StructLayout(LayoutKind.Sequential)] - unsafe struct GdkEventButton - { - public GdkEventType type; - public IntPtr window; - public gint8 send_event; - public guint32 time; - public gdouble x; - public gdouble y; - public gdouble* axes; - public GdkModifierType state; - public guint button; - public IntPtr device; - public gdouble x_root, y_root; - } - - [StructLayout(LayoutKind.Sequential)] - unsafe struct GdkEventMotion - { - public GdkEventType type; - public IntPtr window; - public gint8 send_event; - public guint32 time; - public gdouble x; - public gdouble y; - public gdouble* axes; - public GdkModifierType state; - public gint16 is_hint; - public IntPtr device; - public gdouble x_root, y_root; - } - - [StructLayout(LayoutKind.Sequential)] - unsafe struct GdkEventScroll - { - public GdkEventType type; - public IntPtr window; - public gint8 send_event; - public guint32 time; - public gdouble x; - public gdouble y; - public GdkModifierType state; - public GdkScrollDirection direction; - public IntPtr device; - public gdouble x_root, y_root; - public gdouble delta_x; - public gdouble delta_y; - } - - [StructLayout(LayoutKind.Sequential)] - unsafe struct GdkEventCrossing - { - public GdkEventType type; - public IntPtr window; - public gint8 send_event; - public IntPtr subwindow; - public guint32 time; - public gdouble x; - public gdouble y; - public gdouble x_root; - public gdouble y_root; - public int mode; - public int detail; - public bool focus; - public GdkModifierType state; - }; - - [StructLayout(LayoutKind.Sequential)] - unsafe struct GdkEventWindowState - { - public GdkEventType type; - public IntPtr window; - gint8 send_event; - public GdkWindowState changed_mask; - public GdkWindowState new_window_state; - } - - [StructLayout(LayoutKind.Sequential)] - unsafe struct GdkEventKey - { - public GdkEventType type; - public IntPtr window; - public gint8 send_event; - public guint32 time; - public guint state; - public guint keyval; - public gint length; - public IntPtr pstring; - public guint16 hardware_keycode; - public byte group; - public guint is_modifier; - } - - [StructLayout(LayoutKind.Sequential)] - unsafe struct GSList - { - public IntPtr Data; - public GSList* Next; - } - - [Flags] - public enum GdkWindowState - { - Withdrawn = 1, - Iconified = 2, - Maximized = 4, - Sticky = 8, - Fullscreen = 16, - Above = 32, - Below = 64, - Focused = 128, - Ttiled = 256 - } - - public enum GtkResponseType - { - Help = -11, - Apply = -10, - No = -9, - Yes = -8, - Close = -7, - Cancel = -6, - Ok = -5, - DeleteEvent = -4, - Accept = -3, - Reject = -2, - None = -1, - } - - public enum GtkFileChooserAction - { - Open, - Save, - SelectFolder, - CreateFolder, - } - - [StructLayout(LayoutKind.Sequential)] - public struct GdkGeometry - { - public gint min_width; - public gint min_height; - public gint max_width; - public gint max_height; - public gint base_width; - public gint base_height; - public gint width_inc; - public gint height_inc; - public gdouble min_aspect; - public gdouble max_aspect; - public gint win_gravity; - } - - enum GdkWindowHints - { - GDK_HINT_POS = 1 << 0, - GDK_HINT_MIN_SIZE = 1 << 1, - GDK_HINT_MAX_SIZE = 1 << 2, - GDK_HINT_BASE_SIZE = 1 << 3, - GDK_HINT_ASPECT = 1 << 4, - GDK_HINT_RESIZE_INC = 1 << 5, - GDK_HINT_WIN_GRAVITY = 1 << 6, - GDK_HINT_USER_POS = 1 << 7, - GDK_HINT_USER_SIZE = 1 << 8 - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs b/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs deleted file mode 100644 index 64cbccbd26..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Avalonia.Gtk3.Interop -{ - public class NativeException : Exception - { - public NativeException() - { - } - - public NativeException(string message) : base(message) - { - } - - public NativeException(string message, Exception inner) : base(message, inner) - { - } - - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs b/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs deleted file mode 100644 index 322b9bdfae..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using Avalonia.Platform; -using Avalonia.Platform.Interop; - -namespace Avalonia.Gtk3.Interop -{ - internal class Pixbuf : GObject, IWindowIconImpl - { - Pixbuf(IntPtr handle) : base(handle) - { - - } - - public static Pixbuf NewFromFile(string filename) - { - using (var ub = new Utf8Buffer(filename)) - { - IntPtr err; - var rv = Native.GdkPixbufNewFromFile(ub, out err); - if(rv != IntPtr.Zero) - return new Pixbuf(rv); - throw new GException(err); - } - } - - public static unsafe Pixbuf NewFromBytes(byte[] data) - { - fixed (void* bytes = data) - { - using (var stream = Native.GMemoryInputStreamNewFromData(new IntPtr(bytes), new IntPtr(data.Length), IntPtr.Zero)) - { - IntPtr err; - var rv = Native.GdkPixbufNewFromStream(stream, IntPtr.Zero, out err); - if (rv != IntPtr.Zero) - return new Pixbuf(rv); - throw new GException(err); - } - } - } - - public static Pixbuf NewFromStream(Stream s) - { - if (s is MemoryStream) - return NewFromBytes(((MemoryStream) s).ToArray()); - var ms = new MemoryStream(); - s.CopyTo(ms); - return NewFromBytes(ms.ToArray()); - } - - public void Save(Stream outputStream) - { - IntPtr buffer, bufferLen, error; - using (var png = new Utf8Buffer("png")) - if (!Native.GdkPixbufSaveToBufferv(this, out buffer, out bufferLen, png, - IntPtr.Zero, IntPtr.Zero, out error)) - throw new GException(error); - var data = new byte[bufferLen.ToInt32()]; - Marshal.Copy(buffer, data, 0, bufferLen.ToInt32()); - Native.GFree(buffer); - outputStream.Write(data, 0, data.Length); - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs deleted file mode 100644 index c39cb9e394..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using Avalonia.Platform; -using Avalonia.Platform.Interop; - -namespace Avalonia.Gtk3.Interop -{ - internal class GtkImportAttribute : Attribute - { - public GtkDll Dll { get; set; } - public string Name { get; set; } - public bool Optional { get; set; } - - public GtkImportAttribute(GtkDll dll, string name = null, bool optional = false) - { - Dll = dll; - Name = name; - Optional = optional; - } - } - - public enum GtkDll - { - Gdk, - Gtk, - Glib, - Gio, - Gobject, - Cairo, - GdkPixBuf - } - - static class Resolver - { - private static Lazy Platform = - new Lazy( - () => AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem); - - public static ICustomGtk3NativeLibraryResolver Custom { get; set; } - - - static string FormatName(string name, int version = 0) - { - if (Platform.Value == OperatingSystemType.WinNT) - return "lib" + name + "-" + version + ".dll"; - if (Platform.Value == OperatingSystemType.Linux) - return "lib" + name + ".so" + "." + version; - if (Platform.Value == OperatingSystemType.OSX) - return "lib" + name + "." + version + ".dylib"; - throw new Exception("Unknown platform, use custom name resolver"); - } - - - - static string GetDllName(GtkDll dll) - { - var name = Custom?.GetName(dll); - if (name != null) - return name; - - switch (dll) - { - case GtkDll.Cairo: - return FormatName("cairo", 2); - case GtkDll.Gdk: - return FormatName("gdk-3"); - case GtkDll.Glib: - return FormatName("glib-2.0"); - case GtkDll.Gio: - return FormatName("gio-2.0"); - case GtkDll.Gtk: - return FormatName("gtk-3"); - case GtkDll.Gobject: - return FormatName("gobject-2.0"); - case GtkDll.GdkPixBuf: - return FormatName("gdk_pixbuf-2.0"); - default: - throw new ArgumentException("Unknown lib: " + dll); - } - } - - static IntPtr LoadDll(IDynamicLibraryLoader loader, GtkDll dll) - { - - var exceptions = new List(); - - var name = GetDllName(dll); - if (Custom?.TrySystemFirst != false) - { - try - { - return loader.LoadLibrary(name); - } - catch (Exception e) - { - exceptions.Add(e); - } - } - var path = Custom?.Lookup(dll); - if (path == null && Custom?.BasePath != null) - path = Path.Combine(Custom.BasePath, name); - if (path != null) - { - try - { - return loader.LoadLibrary(path); - } - catch (Exception e) - { - exceptions.Add(e); - } - } - throw new AggregateException("Unable to load " + dll, exceptions); - } - - public static void Resolve(string basePath = null) - { - var loader = AvaloniaLocator.Current.GetService(); - - var dlls = Enum.GetValues(typeof(GtkDll)).Cast().ToDictionary(x => x, x => LoadDll(loader, x)); - - foreach (var fieldInfo in typeof(Native).GetTypeInfo().DeclaredFields) - { - var import = fieldInfo.FieldType.GetTypeInfo().GetCustomAttributes(typeof(GtkImportAttribute), true).Cast().FirstOrDefault(); - if(import == null) - continue; - IntPtr lib = dlls[import.Dll]; - - var funcPtr = loader.GetProcAddress(lib, import.Name ?? fieldInfo.FieldType.Name, import.Optional); - - if (funcPtr != IntPtr.Zero) - fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType)); - } - - var nativeHandleNames = new[] { "gdk_win32_window_get_handle", "gdk_x11_window_get_xid", "gdk_quartz_window_get_nswindow" }; - foreach (var name in nativeHandleNames) - { - var ptr = loader.GetProcAddress(dlls[GtkDll.Gdk], name, true); - if (ptr == IntPtr.Zero) - continue; - Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle) Marshal - .GetDelegateForFunctionPointer(ptr, typeof(Native.D.gdk_get_native_handle)); - } - if (Native.GetNativeGdkWindowHandle == null) - throw new Exception($"Unable to locate any of [{string.Join(", ", nativeHandleNames)}] in libgdk"); - - } - - - } -} - diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs deleted file mode 100644 index 8eaca93152..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Avalonia.Platform.Interop; - -namespace Avalonia.Gtk3.Interop -{ - class Signal - { - class ConnectedSignal : IDisposable - { - private readonly GObject _instance; - private GCHandle _handle; - private readonly ulong _id; - - public ConnectedSignal(GObject instance, GCHandle handle, ulong id) - { - _instance = instance; - Native.GObjectRef(instance); - _handle = handle; - _id = id; - } - - public void Dispose() - { - if (_handle.IsAllocated) - { - Native.GObjectUnref(_instance.DangerousGetHandle()); - Native.GSignalHandlerDisconnect(_instance, _id); - _handle.Free(); - } - } - } - - public static IDisposable Connect(GObject obj, string name, T handler) - { - var handle = GCHandle.Alloc(handler); - var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler); - using (var utf = new Utf8Buffer(name)) - { - var id = Native.GSignalConnectObject(obj, utf, ptr, IntPtr.Zero, 0); - if (id == 0) - throw new ArgumentException("Unable to connect to signal " + name); - return new ConnectedSignal(obj, handle, id); - } - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/KeyTransform.cs b/src/Gtk/Avalonia.Gtk3/KeyTransform.cs deleted file mode 100644 index 4299e07094..0000000000 --- a/src/Gtk/Avalonia.Gtk3/KeyTransform.cs +++ /dev/null @@ -1,230 +0,0 @@ -using System.Collections.Generic; -using Avalonia.Gtk3; -using Avalonia.Input; - -namespace Avalonia.Gtk.Common -{ - static class KeyTransform - { - private static readonly Dictionary KeyDic = new Dictionary - { - { GdkKey.Cancel, Key.Cancel }, - { GdkKey.BackSpace, Key.Back }, - { GdkKey.Tab, Key.Tab }, - { GdkKey.Linefeed, Key.LineFeed }, - { GdkKey.Clear, Key.Clear }, - { GdkKey.Return, Key.Return }, - { GdkKey.KP_Enter, Key.Return }, - { GdkKey.Pause, Key.Pause }, - { GdkKey.Caps_Lock, Key.CapsLock }, - //{ GdkKey.?, Key.HangulMode } - //{ GdkKey.?, Key.JunjaMode } - //{ GdkKey.?, Key.FinalMode } - //{ GdkKey.?, Key.KanjiMode } - { GdkKey.Escape, Key.Escape }, - //{ GdkKey.?, Key.ImeConvert } - //{ GdkKey.?, Key.ImeNonConvert } - //{ GdkKey.?, Key.ImeAccept } - //{ GdkKey.?, Key.ImeModeChange } - { GdkKey.space, Key.Space }, - { GdkKey.Prior, Key.Prior }, - { GdkKey.KP_Prior, Key.Prior }, - { GdkKey.Page_Down, Key.PageDown }, - { GdkKey.KP_Page_Down, Key.PageDown }, - { GdkKey.End, Key.End }, - { GdkKey.KP_End, Key.End }, - { GdkKey.Home, Key.Home }, - { GdkKey.KP_Home, Key.Home }, - { GdkKey.Left, Key.Left }, - { GdkKey.KP_Left, Key.Left }, - { GdkKey.Up, Key.Up }, - { GdkKey.KP_Up, Key.Up }, - { GdkKey.Right, Key.Right }, - { GdkKey.KP_Right, Key.Right }, - { GdkKey.Down, Key.Down }, - { GdkKey.KP_Down, Key.Down }, - { GdkKey.Select, Key.Select }, - { GdkKey.Print, Key.Print }, - { GdkKey.Execute, Key.Execute }, - //{ GdkKey.?, Key.Snapshot } - { GdkKey.Insert, Key.Insert }, - { GdkKey.KP_Insert, Key.Insert }, - { GdkKey.Delete, Key.Delete }, - { GdkKey.KP_Delete, Key.Delete }, - { GdkKey.Help, Key.Help }, - { GdkKey.Key_0, Key.D0 }, - { GdkKey.Key_1, Key.D1 }, - { GdkKey.Key_2, Key.D2 }, - { GdkKey.Key_3, Key.D3 }, - { GdkKey.Key_4, Key.D4 }, - { GdkKey.Key_5, Key.D5 }, - { GdkKey.Key_6, Key.D6 }, - { GdkKey.Key_7, Key.D7 }, - { GdkKey.Key_8, Key.D8 }, - { GdkKey.Key_9, Key.D9 }, - { GdkKey.A, Key.A }, - { GdkKey.B, Key.B }, - { GdkKey.C, Key.C }, - { GdkKey.D, Key.D }, - { GdkKey.E, Key.E }, - { GdkKey.F, Key.F }, - { GdkKey.G, Key.G }, - { GdkKey.H, Key.H }, - { GdkKey.I, Key.I }, - { GdkKey.J, Key.J }, - { GdkKey.K, Key.K }, - { GdkKey.L, Key.L }, - { GdkKey.M, Key.M }, - { GdkKey.N, Key.N }, - { GdkKey.O, Key.O }, - { GdkKey.P, Key.P }, - { GdkKey.Q, Key.Q }, - { GdkKey.R, Key.R }, - { GdkKey.S, Key.S }, - { GdkKey.T, Key.T }, - { GdkKey.U, Key.U }, - { GdkKey.V, Key.V }, - { GdkKey.W, Key.W }, - { GdkKey.X, Key.X }, - { GdkKey.Y, Key.Y }, - { GdkKey.Z, Key.Z }, - { GdkKey.a, Key.A }, - { GdkKey.b, Key.B }, - { GdkKey.c, Key.C }, - { GdkKey.d, Key.D }, - { GdkKey.e, Key.E }, - { GdkKey.f, Key.F }, - { GdkKey.g, Key.G }, - { GdkKey.h, Key.H }, - { GdkKey.i, Key.I }, - { GdkKey.j, Key.J }, - { GdkKey.k, Key.K }, - { GdkKey.l, Key.L }, - { GdkKey.m, Key.M }, - { GdkKey.n, Key.N }, - { GdkKey.o, Key.O }, - { GdkKey.p, Key.P }, - { GdkKey.q, Key.Q }, - { GdkKey.r, Key.R }, - { GdkKey.s, Key.S }, - { GdkKey.t, Key.T }, - { GdkKey.u, Key.U }, - { GdkKey.v, Key.V }, - { GdkKey.w, Key.W }, - { GdkKey.x, Key.X }, - { GdkKey.y, Key.Y }, - { GdkKey.z, Key.Z }, - //{ GdkKey.?, Key.LWin } - //{ GdkKey.?, Key.RWin } - { GdkKey.Menu, Key.Apps }, - //{ GdkKey.?, Key.Sleep } - { GdkKey.KP_0, Key.NumPad0 }, - { GdkKey.KP_1, Key.NumPad1 }, - { GdkKey.KP_2, Key.NumPad2 }, - { GdkKey.KP_3, Key.NumPad3 }, - { GdkKey.KP_4, Key.NumPad4 }, - { GdkKey.KP_5, Key.NumPad5 }, - { GdkKey.KP_6, Key.NumPad6 }, - { GdkKey.KP_7, Key.NumPad7 }, - { GdkKey.KP_8, Key.NumPad8 }, - { GdkKey.KP_9, Key.NumPad9 }, - { GdkKey.multiply, Key.Multiply }, - { GdkKey.KP_Multiply, Key.Multiply }, - { GdkKey.KP_Add, Key.Add }, - //{ GdkKey.?, Key.Separator } - { GdkKey.KP_Subtract, Key.Subtract }, - { GdkKey.KP_Decimal, Key.Decimal }, - { GdkKey.KP_Divide, Key.Divide }, - { GdkKey.F1, Key.F1 }, - { GdkKey.F2, Key.F2 }, - { GdkKey.F3, Key.F3 }, - { GdkKey.F4, Key.F4 }, - { GdkKey.F5, Key.F5 }, - { GdkKey.F6, Key.F6 }, - { GdkKey.F7, Key.F7 }, - { GdkKey.F8, Key.F8 }, - { GdkKey.F9, Key.F9 }, - { GdkKey.F10, Key.F10 }, - { GdkKey.F11, Key.F11 }, - { GdkKey.F12, Key.F12 }, - { GdkKey.L3, Key.F13 }, - { GdkKey.F14, Key.F14 }, - { GdkKey.L5, Key.F15 }, - { GdkKey.F16, Key.F16 }, - { GdkKey.F17, Key.F17 }, - { GdkKey.L8, Key.F18 }, - { GdkKey.L9, Key.F19 }, - { GdkKey.L10, Key.F20 }, - { GdkKey.R1, Key.F21 }, - { GdkKey.R2, Key.F22 }, - { GdkKey.F23, Key.F23 }, - { GdkKey.R4, Key.F24 }, - { GdkKey.Num_Lock, Key.NumLock }, - { GdkKey.Scroll_Lock, Key.Scroll }, - { GdkKey.Shift_L, Key.LeftShift }, - { GdkKey.Shift_R, Key.RightShift }, - { GdkKey.Control_L, Key.LeftCtrl }, - { GdkKey.Control_R, Key.RightCtrl }, - { GdkKey.Alt_L, Key.LeftAlt }, - { GdkKey.Alt_R, Key.RightAlt }, - //{ GdkKey.?, Key.BrowserBack } - //{ GdkKey.?, Key.BrowserForward } - //{ GdkKey.?, Key.BrowserRefresh } - //{ GdkKey.?, Key.BrowserStop } - //{ GdkKey.?, Key.BrowserSearch } - //{ GdkKey.?, Key.BrowserFavorites } - //{ GdkKey.?, Key.BrowserHome } - //{ GdkKey.?, Key.VolumeMute } - //{ GdkKey.?, Key.VolumeDown } - //{ GdkKey.?, Key.VolumeUp } - //{ GdkKey.?, Key.MediaNextTrack } - //{ GdkKey.?, Key.MediaPreviousTrack } - //{ GdkKey.?, Key.MediaStop } - //{ GdkKey.?, Key.MediaPlayPause } - //{ GdkKey.?, Key.LaunchMail } - //{ GdkKey.?, Key.SelectMedia } - //{ GdkKey.?, Key.LaunchApplication1 } - //{ GdkKey.?, Key.LaunchApplication2 } - { GdkKey.semicolon, Key.OemSemicolon }, - { GdkKey.plus, Key.OemPlus }, - { GdkKey.equal, Key.OemPlus }, - { GdkKey.comma, Key.OemComma }, - { GdkKey.minus, Key.OemMinus }, - { GdkKey.period, Key.OemPeriod }, - { GdkKey.slash, Key.Oem2 }, - { GdkKey.grave, Key.OemTilde }, - //{ GdkKey.?, Key.AbntC1 } - //{ GdkKey.?, Key.AbntC2 } - { GdkKey.bracketleft, Key.OemOpenBrackets }, - { GdkKey.backslash, Key.OemPipe }, - { GdkKey.bracketright, Key.OemCloseBrackets }, - { GdkKey.apostrophe, Key.OemQuotes }, - //{ GdkKey.?, Key.Oem8 } - //{ GdkKey.?, Key.Oem102 } - //{ GdkKey.?, Key.ImeProcessed } - //{ GdkKey.?, Key.System } - //{ GdkKey.?, Key.OemAttn } - //{ GdkKey.?, Key.OemFinish } - //{ GdkKey.?, Key.DbeHiragana } - //{ GdkKey.?, Key.OemAuto } - //{ GdkKey.?, Key.DbeDbcsChar } - //{ GdkKey.?, Key.OemBackTab } - //{ GdkKey.?, Key.Attn } - //{ GdkKey.?, Key.DbeEnterWordRegisterMode } - //{ GdkKey.?, Key.DbeEnterImeConfigureMode } - //{ GdkKey.?, Key.EraseEof } - //{ GdkKey.?, Key.Play } - //{ GdkKey.?, Key.Zoom } - //{ GdkKey.?, Key.NoName } - //{ GdkKey.?, Key.DbeEnterDialogConversionMode } - //{ GdkKey.?, Key.OemClear } - //{ GdkKey.?, Key.DeadCharProcessed } - }; - - public static Key ConvertKey(GdkKey key) - { - Key result; - return KeyDic.TryGetValue(key, out result) ? result : Key.None; - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/PlatformIconLoader.cs b/src/Gtk/Avalonia.Gtk3/PlatformIconLoader.cs deleted file mode 100644 index 6965e7c812..0000000000 --- a/src/Gtk/Avalonia.Gtk3/PlatformIconLoader.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using Avalonia.Gtk3.Interop; -using Avalonia.Platform; - -namespace Avalonia.Gtk3 -{ - class PlatformIconLoader : IPlatformIconLoader - { - public IWindowIconImpl LoadIcon(string fileName) => Pixbuf.NewFromFile(fileName); - - public IWindowIconImpl LoadIcon(Stream stream) => Pixbuf.NewFromStream(stream); - - public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) - { - var ms = new MemoryStream(); - bitmap.Save(ms); - return Pixbuf.NewFromBytes(ms.ToArray()); - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/PopupImpl.cs b/src/Gtk/Avalonia.Gtk3/PopupImpl.cs deleted file mode 100644 index ef17407dd6..0000000000 --- a/src/Gtk/Avalonia.Gtk3/PopupImpl.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia.Gtk3.Interop; -using Avalonia.Platform; - -namespace Avalonia.Gtk3 -{ - class PopupImpl : WindowBaseImpl, IPopupImpl - { - static GtkWindow CreateWindow() - { - var window = Native.GtkWindowNew(GtkWindowType.Popup); - return window; - } - - public PopupImpl() : base(CreateWindow()) - { - OverrideRedirect = true; - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs deleted file mode 100644 index a27d631ee0..0000000000 --- a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Reflection; -using Avalonia.Gtk3; -using Avalonia.Platform; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 2, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] -[assembly: ExportWindowingSubsystem(OperatingSystemType.Linux, 1, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] -[assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 2, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/README.md b/src/Gtk/Avalonia.Gtk3/README.md deleted file mode 100644 index ea853bde75..0000000000 --- a/src/Gtk/Avalonia.Gtk3/README.md +++ /dev/null @@ -1,8 +0,0 @@ -P/Invoke based GTK3 backend -=========================== - -Code is EXPERIMENTAL at this point. It also needs Direct2D/Skia for rendering. - -Windows GTK3 binaries aren't included in the repo, you need to download them from https://sourceforge.net/projects/gtk3win/ - -On Linux it should work out of the box with system-provided GTK3. On OSX you should be able to wire GTK3 using DYLD_LIBRARY_PATH environment variable. \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs deleted file mode 100644 index 2cb8a6b127..0000000000 --- a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia.Gtk3.Interop; -using Avalonia.Platform; - -namespace Avalonia.Gtk3 -{ - internal class ScreenImpl : IScreenImpl - { - public int ScreenCount - { - get => _allScreens.Length; - } - - private Screen[] _allScreens; - public IReadOnlyList AllScreens - { - get - { - if (_allScreens == null) - { - IntPtr display = Native.GdkGetDefaultDisplay(); - GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display); - short primary = Native.GdkScreenGetPrimaryMonitor(screen); - Screen[] screens = new Screen[Native.GdkScreenGetNMonitors(screen)]; - for (short i = 0; i < screens.Length; i++) - { - GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle(); - Native.GdkScreenGetMonitorGeometry(screen, i, ref geometry); - Native.GdkScreenGetMonitorWorkarea(screen, i, ref workArea); - PixelRect workAreaRect = new PixelRect(workArea.X, workArea.Y, workArea.Width, workArea.Height); - PixelRect geometryRect = new PixelRect(geometry.X, geometry.Y, geometry.Width, geometry.Height); - GtkScreen s = new GtkScreen(geometryRect, workAreaRect, i == primary, i); - screens[i] = s; - } - - _allScreens = screens; - } - - return _allScreens; - } - } - - public ScreenImpl() - { - IntPtr display = Native.GdkGetDefaultDisplay(); - GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display); - Signal.Connect(screen, "monitors-changed", MonitorsChanged); - } - - private unsafe void MonitorsChanged(IntPtr screen, IntPtr userData) - { - _allScreens = null; - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs deleted file mode 100644 index 1e85eaa156..0000000000 --- a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Gtk3.Interop; -using Avalonia.Platform; -using Avalonia.Platform.Interop; - -namespace Avalonia.Gtk3 -{ - class SystemDialogBase - { - - public unsafe static Task ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action, - bool multiselect, string initialFileName, Action modify) - { - GtkFileChooser dlg; - parent = parent ?? GtkWindow.Null; - using (var name = new Utf8Buffer(title)) - dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero); - modify?.Invoke(dlg); - if (multiselect) - Native.GtkFileChooserSetSelectMultiple(dlg, true); - - Native.GtkWindowSetModal(dlg, true); - var tcs = new TaskCompletionSource(); - List disposables = null; - Action dispose = () => - { - // ReSharper disable once PossibleNullReferenceException - foreach (var d in disposables) - d.Dispose(); - disposables.Clear(); - }; - disposables = new List - { - Signal.Connect(dlg, "close", delegate - { - tcs.TrySetResult(null); - dispose(); - return false; - }), - Signal.Connect(dlg, "response", (_, resp, __) => - { - string[] result = null; - if (resp == GtkResponseType.Accept) - { - var rlst = new List(); - var gs = Native.GtkFileChooserGetFilenames(dlg); - var cgs = gs; - while (cgs != null) - { - if (cgs->Data != IntPtr.Zero) - rlst.Add(Utf8Buffer.StringFromPtr(cgs->Data)); - cgs = cgs->Next; - } - - Native.GSlistFree(gs); - result = rlst.ToArray(); - } - - Native.GtkWidgetHide(dlg); - dispose(); - tcs.TrySetResult(result); - return false; - }), - dlg - }; - using (var open = new Utf8Buffer("Open")) - Native.GtkDialogAddButton(dlg, open, GtkResponseType.Accept); - using (var open = new Utf8Buffer("Cancel")) - Native.GtkDialogAddButton(dlg, open, GtkResponseType.Cancel); - if (initialFileName != null) - using (var fn = new Utf8Buffer(initialFileName)) - Native.GtkFileChooserSetFilename(dlg, fn); - Native.GtkWindowPresent(dlg); - return tcs.Task; - } - - public Task ShowFileDialogAsync(FileDialog dialog, GtkWindow parent, - Action modify = null) - { - return ShowDialog(dialog.Title, parent, - dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save, - (dialog as OpenFileDialog)?.AllowMultiple ?? false, - Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory, - string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), modify); - } - - public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, GtkWindow parent, - Action modify = null) - { - var res = await ShowDialog(dialog.Title, parent, - GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory, modify); - return res?.FirstOrDefault(); - } - } - - class SystemDialog : SystemDialogBase, ISystemDialogImpl - { - public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) - => ShowFolderDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget); - - public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) - => ShowFileDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget); - } -} diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs deleted file mode 100644 index bff50a979d..0000000000 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ /dev/null @@ -1,527 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Threading; -using Avalonia.Controls; -using Avalonia.Gtk3.Interop; -using Avalonia.Input; -using Avalonia.Input.Raw; -using Avalonia.OpenGL; -using Avalonia.Platform; -using Avalonia.Platform.Interop; -using Avalonia.Rendering; -using Avalonia.Threading; - -namespace Avalonia.Gtk3 -{ - abstract class WindowBaseImpl : IWindowBaseImpl, IPlatformHandle, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo - { - public readonly GtkWindow GtkWidget; - private IInputRoot _inputRoot; - private readonly GtkImContext _imContext; - private readonly FramebufferManager _framebuffer; - private readonly EglGlPlatformSurface _egl; - protected readonly List Disposables = new List(); - private Size _lastSize; - private PixelPoint _lastPosition; - private double _lastScaling; - private uint _lastKbdEvent; - private uint _lastSmoothScrollEvent; - private GCHandle _gcHandle; - private object _lock = new object(); - private IDeferredRenderOperation _nextRenderOperation; - private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true); - internal IntPtr? GdkWindowHandle; - private bool _overrideRedirect; - private uint? _tickCallback; - public WindowBaseImpl(GtkWindow gtkWidget) - { - - GtkWidget = gtkWidget; - - var glf = AvaloniaLocator.Current.GetService() as EglGlPlatformFeature; - if (glf != null) - _egl = new EglGlPlatformSurface((EglDisplay)glf.Display, glf.DeferredContext, this); - else - _framebuffer = new FramebufferManager(this); - - _imContext = Native.GtkImMulticontextNew(); - Disposables.Add(_imContext); - Native.GtkWidgetSetEvents(gtkWidget, 0xFFFFFE); - Disposables.Add(Signal.Connect(_imContext, "commit", OnCommit)); - Connect("draw", OnDraw); - Connect("realize", OnRealized); - ConnectEvent("configure-event", OnConfigured); - ConnectEvent("button-press-event", OnButton); - ConnectEvent("button-release-event", OnButton); - ConnectEvent("motion-notify-event", OnMotion); - ConnectEvent("scroll-event", OnScroll); - ConnectEvent("window-state-event", OnStateChanged); - ConnectEvent("key-press-event", OnKeyEvent); - ConnectEvent("key-release-event", OnKeyEvent); - ConnectEvent("leave-notify-event", OnLeaveNotifyEvent); - ConnectEvent("delete-event", OnClosingEvent); - Connect("destroy", OnDestroy); - Native.GtkWidgetRealize(gtkWidget); - GdkWindowHandle = this.Handle.Handle; - _lastSize = ClientSize; - - if (_egl != null) - Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); - else if (Gtk3Platform.UseDeferredRendering) - { - Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); - _gcHandle = GCHandle.Alloc(this); - _tickCallback = Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, - GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero); - } - } - - private bool OnConfigured(IntPtr gtkwidget, IntPtr ev, IntPtr userdata) - { - int w, h; - if (!OverrideRedirect) - { - Native.GtkWindowGetSize(GtkWidget, out w, out h); - var size = ClientSize = new Size(w, h); - if (_lastSize != size) - { - Resized?.Invoke(size); - _lastSize = size; - } - } - var pos = Position; - if (_lastPosition != pos) - { - PositionChanged?.Invoke(pos); - _lastPosition = pos; - } - var scaling = Scaling; - if (_lastScaling != scaling) - { - ScalingChanged?.Invoke(scaling); - _lastScaling = scaling; - } - return false; - } - - private bool OnRealized(IntPtr gtkwidget, IntPtr userdata) - { - Native.GtkImContextSetClientWindow(_imContext, Native.GtkWidgetGetWindow(GtkWidget)); - return false; - } - - private bool OnDestroy(IntPtr gtkwidget, IntPtr userdata) - { - DoDispose(true); - return false; - } - - private static InputModifiers GetModifierKeys(GdkModifierType state) - { - var rv = InputModifiers.None; - if (state.HasFlag(GdkModifierType.ControlMask)) - rv |= InputModifiers.Control; - if (state.HasFlag(GdkModifierType.ShiftMask)) - rv |= InputModifiers.Shift; - if (state.HasFlag(GdkModifierType.Mod1Mask)) - rv |= InputModifiers.Alt; - if (state.HasFlag(GdkModifierType.Button1Mask)) - rv |= InputModifiers.LeftMouseButton; - if (state.HasFlag(GdkModifierType.Button2Mask)) - rv |= InputModifiers.RightMouseButton; - if (state.HasFlag(GdkModifierType.Button3Mask)) - rv |= InputModifiers.MiddleMouseButton; - return rv; - } - - private unsafe bool OnClosingEvent(IntPtr w, IntPtr ev, IntPtr userdata) - { - bool? preventClosing = Closing?.Invoke(); - return preventClosing ?? false; - } - - private unsafe bool OnButton(IntPtr w, IntPtr ev, IntPtr userdata) - { - var evnt = (GdkEventButton*)ev; - var e = new RawPointerEventArgs( - Gtk3Platform.Mouse, - evnt->time, - _inputRoot, - evnt->type == GdkEventType.ButtonRelease - ? evnt->button == 1 - ? RawPointerEventType.LeftButtonUp - : evnt->button == 3 ? RawPointerEventType.RightButtonUp : RawPointerEventType.MiddleButtonUp - : evnt->button == 1 - ? RawPointerEventType.LeftButtonDown - : evnt->button == 3 ? RawPointerEventType.RightButtonDown : RawPointerEventType.MiddleButtonDown, - new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state)); - OnInput(e); - return true; - } - - protected virtual unsafe bool OnStateChanged(IntPtr w, IntPtr pev, IntPtr userData) - { - var ev = (GdkEventWindowState*) pev; - if (ev->changed_mask.HasFlag(GdkWindowState.Focused)) - { - if(ev->new_window_state.HasFlag(GdkWindowState.Focused)) - Activated?.Invoke(); - else - Deactivated?.Invoke(); - } - return true; - } - - private unsafe bool OnMotion(IntPtr w, IntPtr ev, IntPtr userdata) - { - var evnt = (GdkEventMotion*)ev; - var position = new Point(evnt->x, evnt->y); - Native.GdkEventRequestMotions(ev); - var e = new RawPointerEventArgs( - Gtk3Platform.Mouse, - evnt->time, - _inputRoot, - RawPointerEventType.Move, - position, GetModifierKeys(evnt->state)); - OnInput(e); - - return true; - } - private unsafe bool OnScroll(IntPtr w, IntPtr ev, IntPtr userdata) - { - var evnt = (GdkEventScroll*)ev; - - //Ignore duplicates - if (evnt->time - _lastSmoothScrollEvent < 10 && evnt->direction != GdkScrollDirection.Smooth) - return true; - - var delta = new Vector(); - const double step = (double) 1; - if (evnt->direction == GdkScrollDirection.Down) - delta = new Vector(0, -step); - else if (evnt->direction == GdkScrollDirection.Up) - delta = new Vector(0, step); - else if (evnt->direction == GdkScrollDirection.Right) - delta = new Vector(-step, 0); - else if (evnt->direction == GdkScrollDirection.Left) - delta = new Vector(step, 0); - else if (evnt->direction == GdkScrollDirection.Smooth) - { - delta = new Vector(-evnt->delta_x, -evnt->delta_y); - _lastSmoothScrollEvent = evnt->time; - } - var e = new RawMouseWheelEventArgs(Gtk3Platform.Mouse, evnt->time, _inputRoot, - new Point(evnt->x, evnt->y), delta, GetModifierKeys(evnt->state)); - OnInput(e); - return true; - } - - private unsafe bool OnKeyEvent(IntPtr w, IntPtr pev, IntPtr userData) - { - var evnt = (GdkEventKey*) pev; - _lastKbdEvent = evnt->time; - var e = new RawKeyEventArgs( - Gtk3Platform.Keyboard, - evnt->time, - evnt->type == GdkEventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, - Avalonia.Gtk.Common.KeyTransform.ConvertKey((GdkKey)evnt->keyval), GetModifierKeys((GdkModifierType)evnt->state)); - OnInput(e); - if (Native.GtkImContextFilterKeypress(_imContext, pev)) - return true; - return true; - } - - private unsafe bool OnLeaveNotifyEvent(IntPtr w, IntPtr pev, IntPtr userData) - { - var evnt = (GdkEventCrossing*) pev; - var position = new Point(evnt->x, evnt->y); - OnInput(new RawPointerEventArgs(Gtk3Platform.Mouse, - evnt->time, - _inputRoot, - RawPointerEventType.Move, - position, GetModifierKeys(evnt->state))); - return true; - } - - private unsafe bool OnCommit(IntPtr gtkwidget, IntPtr utf8string, IntPtr userdata) - { - OnInput(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string))); - return true; - } - - protected void ConnectEvent(string name, Native.D.signal_onevent handler) - => Disposables.Add(Signal.Connect(GtkWidget, name, handler)); - void Connect(string name, T handler) => Disposables.Add(Signal.Connect(GtkWidget, name, handler)); - - internal IntPtr CurrentCairoContext { get; private set; } - - private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) - { - if (!Gtk3Platform.UseDeferredRendering) - { - CurrentCairoContext = cairocontext; - Paint?.Invoke(new Rect(ClientSize)); - CurrentCairoContext = IntPtr.Zero; - } - else - Paint?.Invoke(new Rect(ClientSize)); - return true; - } - - private static Native.D.TickCallback PinnedStaticCallback = StaticTickCallback; - - static bool StaticTickCallback(IntPtr widget, IntPtr clock, IntPtr userData) - { - var impl = (WindowBaseImpl) GCHandle.FromIntPtr(userData).Target; - impl.OnRenderTick(); - return true; - } - - public void SetNextRenderOperation(IDeferredRenderOperation op) - { - while (true) - { - lock (_lock) - { - if (_nextRenderOperation == null) - { - _nextRenderOperation = op; - return; - } - } - _canSetNextOperation.WaitOne(); - } - - } - - private void OnRenderTick() - { - IDeferredRenderOperation op = null; - lock (_lock) - { - if (_nextRenderOperation != null) - { - op = _nextRenderOperation; - _nextRenderOperation = null; - } - _canSetNextOperation.Set(); - } - if (op != null) - { - op?.RenderNow(null); - op?.Dispose(); - } - } - - - public void Dispose() => DoDispose(false); - - void DoDispose(bool fromDestroy) - { - if (_tickCallback.HasValue) - { - if (!GtkWidget.IsClosed) - Native.GtkWidgetRemoveTickCallback(GtkWidget, _tickCallback.Value); - _tickCallback = null; - } - - //We are calling it here, since signal handler will be detached - if (!GtkWidget.IsClosed) - Closed?.Invoke(); - foreach(var d in Disposables.AsEnumerable().Reverse()) - d.Dispose(); - Disposables.Clear(); - - if (!fromDestroy && !GtkWidget.IsClosed) - Native.GtkWindowClose(GtkWidget); - GtkWidget.Dispose(); - - if (_gcHandle.IsAllocated) - { - _gcHandle.Free(); - } - } - - public Size MaxClientSize - { - get - { - var s = Native.GtkWidgetGetScreen(GtkWidget); - return new Size(Native.GdkScreenGetWidth(s), Native.GdkScreenGetHeight(s)); - } - } - - public void SetMinMaxSize(Size minSize, Size maxSize) - { - if (GtkWidget.IsClosed) - return; - - GdkGeometry geometry = new GdkGeometry(); - geometry.min_width = minSize.Width > 0 ? (int)minSize.Width : -1; - geometry.min_height = minSize.Height > 0 ? (int)minSize.Height : -1; - geometry.max_width = !Double.IsInfinity(maxSize.Width) && maxSize.Width > 0 ? (int)maxSize.Width : 999999; - geometry.max_height = !Double.IsInfinity(maxSize.Height) && maxSize.Height > 0 ? (int)maxSize.Height : 999999; - - Native.GtkWindowSetGeometryHints(GtkWidget, IntPtr.Zero, ref geometry, GdkWindowHints.GDK_HINT_MIN_SIZE | GdkWindowHints.GDK_HINT_MAX_SIZE); - } - - public IMouseDevice MouseDevice => Gtk3Platform.Mouse; - - public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1); - - public IPlatformHandle Handle => this; - - string IPlatformHandle.HandleDescriptor => "HWND"; - - public Action Activated { get; set; } - public Func Closing { get; set; } - public Action Closed { get; set; } - public Action Deactivated { get; set; } - public Action Input { get; set; } - public Action Paint { get; set; } - public Action Resized { get; set; } - public Action ScalingChanged { get; set; } //TODO - public Action PositionChanged { get; set; } - - public void Activate() => Native.GtkWidgetActivate(GtkWidget); - - public void Invalidate(Rect rect) - { - if(GtkWidget.IsClosed) - return; - var s = ClientSize; - Native.GtkWidgetQueueDrawArea(GtkWidget, 0, 0, (int) s.Width, (int) s.Height); - } - - public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; - - void OnInput(RawInputEventArgs args) - { - Dispatcher.UIThread.Post(() => Input?.Invoke(args), DispatcherPriority.Input); - } - - public Point PointToClient(PixelPoint point) - { - int x, y; - Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); - - return new Point(point.X - x, point.Y - y); - } - - public PixelPoint PointToScreen(Point point) - { - int x, y; - Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); - return new PixelPoint((int)(point.X + x), (int)(point.Y + y)); - } - - public void SetCursor(IPlatformHandle cursor) - { - if (GtkWidget.IsClosed) - return; - Native.GdkWindowSetCursor(Native.GtkWidgetGetWindow(GtkWidget), cursor?.Handle ?? IntPtr.Zero); - } - - public virtual void Show() => Native.GtkWindowPresent(GtkWidget); - - public void Hide() => Native.GtkWidgetHide(GtkWidget); - - public void SetTopmost(bool value) => Native.GtkWindowSetKeepAbove(GtkWidget, value); - - void GetGlobalPointer(out int x, out int y) - { - int mask; - Native.GdkWindowGetPointer(Native.GdkScreenGetRootWindow(Native.GtkWidgetGetScreen(GtkWidget)), - out x, out y, out mask); - } - - public void BeginMoveDrag() - { - int x, y; - GetGlobalPointer(out x, out y); - Native.GdkWindowBeginMoveDrag(Native.GtkWidgetGetWindow(GtkWidget), 1, x, y, 0); - } - - public void BeginResizeDrag(WindowEdge edge) - { - int x, y; - GetGlobalPointer(out x, out y); - Native.GdkWindowBeginResizeDrag(Native.GtkWidgetGetWindow(GtkWidget), edge, 1, x, y, 0); - } - - - public Size ClientSize { get; private set; } - public int LastKnownScaleFactor { get; private set; } - - public void Resize(Size value) - { - if (GtkWidget.IsClosed) - return; - - Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); - if (OverrideRedirect) - { - var size = ClientSize = value; - if (_lastSize != size) - { - Resized?.Invoke(size); - _lastSize = size; - } - } - } - - public bool OverrideRedirect - { - get => _overrideRedirect; - set - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - Native.GdkWindowSetOverrideRedirect(Native.GtkWidgetGetWindow(GtkWidget), value); - _overrideRedirect = value; - } - } - } - - public IScreenImpl Screen - { - get; - } = new ScreenImpl(); - - public PixelPoint Position - { - get - { - int x, y; - Native.GtkWindowGetPosition(GtkWidget, out x, out y); - return new PixelPoint(x, y); - } - set { Native.GtkWindowMove(GtkWidget, (int)value.X, (int)value.Y); } - } - - IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); - public IEnumerable Surfaces => new object[] {Handle, _egl, _framebuffer}; - - public IRenderer CreateRenderer(IRenderRoot root) - { - var loop = AvaloniaLocator.Current.GetService(); - return Gtk3Platform.UseDeferredRendering - ? (IRenderer) new DeferredRenderer(root, loop) - : new ImmediateRenderer(root); - } - - PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size - { - get - { - var cs = ClientSize; - return new PixelSize((int)Math.Max(1, LastKnownScaleFactor * cs.Width), - (int)Math.Max(1, LastKnownScaleFactor * ClientSize.Height)); - } - } - double EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Scaling => LastKnownScaleFactor; - IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle; - } -} diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs deleted file mode 100644 index 1c8d800f2f..0000000000 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using Avalonia.Controls; -using Avalonia.Gtk3.Interop; -using Avalonia.Platform; -using Avalonia.Platform.Interop; - -namespace Avalonia.Gtk3 -{ - class WindowImpl : WindowBaseImpl, IWindowImpl - { - private WindowState _lastWindowState; - - public WindowImpl() : base(Native.GtkWindowNew(GtkWindowType.TopLevel)) - { - } - - protected unsafe override bool OnStateChanged(IntPtr w, IntPtr pev, IntPtr userData) - { - var windowStateEvent = (GdkEventWindowState*)pev; - var newWindowState = windowStateEvent->new_window_state; - var windowState = newWindowState.HasFlag(GdkWindowState.Iconified) ? WindowState.Minimized - : (newWindowState.HasFlag(GdkWindowState.Maximized) ? WindowState.Maximized : WindowState.Normal); - - if (windowState != _lastWindowState) - { - _lastWindowState = windowState; - WindowStateChanged?.Invoke(windowState); - } - - return base.OnStateChanged(w, pev, userData); - } - - public void SetTitle(string title) - { - using (var t = new Utf8Buffer(title)) - Native.GtkWindowSetTitle(GtkWidget, t); - } - - public WindowState WindowState - { - get - { - var state = Native.GdkWindowGetState(Native.GtkWidgetGetWindow(GtkWidget)); - if (state.HasFlag(GdkWindowState.Iconified)) - return WindowState.Minimized; - if (state.HasFlag(GdkWindowState.Maximized)) - return WindowState.Maximized; - return WindowState.Normal; - } - set - { - if (value == WindowState.Minimized) - Native.GtkWindowIconify(GtkWidget); - else if (value == WindowState.Maximized) - Native.GtkWindowMaximize(GtkWidget); - else - { - Native.GtkWindowUnmaximize(GtkWidget); - Native.GtkWindowDeiconify(GtkWidget); - } - } - } - - public Action WindowStateChanged { get; set; } - - public void ShowDialog(IWindowImpl parent) - { - Native.GtkWindowSetModal(GtkWidget, true); - Native.GtkWindowSetTransientFor(GtkWidget, ((WindowImpl)parent).GtkWidget.DangerousGetHandle()); - Native.GtkWindowPresent(GtkWidget); - } - - public override void Show() - { - Native.GtkWindowSetModal(GtkWidget, false); - Native.GtkWindowSetTransientFor(GtkWidget, IntPtr.Zero); - Native.GtkWindowPresent(GtkWidget); - } - - public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(GtkWidget, enabled); - - public void SetIcon(IWindowIconImpl icon) => Native.GtkWindowSetIcon(GtkWidget, (Pixbuf) icon); - - public void SetCoverTaskbarWhenMaximized(bool enable) - { - //Why do we even have that? - } - - public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value); - - public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value); - - - class EmptyDisposable : IDisposable - { - public void Dispose() - { - - } - } - } -} diff --git a/src/Gtk/Avalonia.Gtk3/X11.cs b/src/Gtk/Avalonia.Gtk3/X11.cs deleted file mode 100644 index 4677114bdb..0000000000 --- a/src/Gtk/Avalonia.Gtk3/X11.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Avalonia.Gtk3 -{ - class X11 - { - [DllImport("libX11.so.6")] - public static extern IntPtr XInitThreads(); - - [DllImport("libX11.so.6")] - public static extern IntPtr XOpenDisplay(IntPtr name); - - [DllImport("libX11.so.6")] - public static extern IntPtr XLockDisplay(IntPtr display); - - [DllImport("libX11.so.6")] - public static extern IntPtr XUnlockDisplay(IntPtr display); - - [DllImport("libX11.so.6")] - public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); - - [DllImport("libX11.so.6")] - public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); - - [DllImport("libX11.so.6")] - public static extern int XInitImage(ref XImage image); - - [DllImport("libX11.so.6")] - public static extern int XDestroyImage(ref XImage image); - - [DllImport("libX11.so.6")] - public static extern IntPtr XSetErrorHandler(XErrorHandler handler); - - [DllImport("libX11.so.6")] - public static extern int XSync(IntPtr display, bool discard); - - public delegate int XErrorHandler(IntPtr display, ref XErrorEvent error); - - [DllImport("libX11.so.6")] - public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, - int srcx, int srcy, int destx, int desty, uint width, uint height); - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct XErrorEvent - { - public int type; - public IntPtr* display; /* Display the event was read from */ - public ulong serial; /* serial number of failed request */ - public byte error_code; /* error code of failed request */ - public byte request_code; /* Major op-code of failed request */ - public byte minor_code; /* Minor op-code of failed request */ - public IntPtr resourceid; /* resource id */ - } - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct XImage - { - public int width, height; /* size of image */ - public int xoffset; /* number of pixels offset in X direction */ - public int format; /* XYBitmap, XYPixmap, ZPixmap */ - public IntPtr data; /* pointer to image data */ - public int byte_order; /* data byte order, LSBFirst, MSBFirst */ - public int bitmap_unit; /* quant. of scanline 8, 16, 32 */ - public int bitmap_bit_order; /* LSBFirst, MSBFirst */ - public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ - public int depth; /* depth of image */ - public int bytes_per_line; /* accelerator to next scanline */ - public int bits_per_pixel; /* bits per pixel (ZPixmap) */ - public ulong red_mask; /* bits in z arrangement */ - public ulong green_mask; - public ulong blue_mask; - private fixed byte funcs[128]; - } - - - } -} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs b/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs deleted file mode 100644 index c84d7c7541..0000000000 --- a/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using Avalonia.Platform; - -namespace Avalonia.Gtk3 -{ - class X11Framebuffer : ILockedFramebuffer - { - private readonly IntPtr _display; - private readonly IntPtr _xid; - private IUnmanagedBlob _blob; - - public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, int factor) - { - _display = display; - _xid = xid; - Size = new PixelSize(width * factor, height * factor); - RowBytes = Size.Width * 4; - Dpi = new Vector(96, 96) * factor; - Format = PixelFormat.Bgra8888; - _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * Size.Height); - Address = _blob.Address; - } - - public void Dispose() - { - var image = new X11.XImage(); - int bitsPerPixel = 32; - image.width = Size.Width; - image.height = Size.Height; - image.format = 2; //ZPixmap; - image.data = Address; - image.byte_order = 0;// LSBFirst; - image.bitmap_unit = bitsPerPixel; - image.bitmap_bit_order = 0;// LSBFirst; - image.bitmap_pad = bitsPerPixel; - image.depth = 24; - image.bytes_per_line = RowBytes - Size.Width * 4; - image.bits_per_pixel = bitsPerPixel; - X11.XLockDisplay(_display); - X11.XInitImage(ref image); - var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero); - X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint)Size.Width, (uint)Size.Height); - X11.XFreeGC(_display, gc); - X11.XSync(_display, true); - X11.XUnlockDisplay(_display); - _blob.Dispose(); - } - - public IntPtr Address { get; } - public PixelSize Size { get; } - public int RowBytes { get; } - public Vector Dpi { get; } - public PixelFormat Format { get; } - } -} From e4c0ecc0e35f3f070d8e2539d74a27623d8cfc82 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 24 Jun 2019 00:51:27 +1000 Subject: [PATCH 37/44] Make Avalonia compile on windows machines with VS2017 and VS2019 --- nukebuild/Build.cs | 28 +++++++++++++++++++++++----- nukebuild/_build.csproj | 3 ++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 84092d52eb..a99ac4d026 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -19,7 +19,7 @@ using static Nuke.Common.IO.PathConstruction; using static Nuke.Common.Tools.MSBuild.MSBuildTasks; using static Nuke.Common.Tools.DotNet.DotNetTasks; using static Nuke.Common.Tools.Xunit.XunitTasks; - +using static Nuke.Common.Tools.VSWhere.VSWhereTasks; /* Before editing this file, install support plugin for your IDE, @@ -30,7 +30,26 @@ using static Nuke.Common.Tools.Xunit.XunitTasks; */ partial class Build : NukeBuild -{ +{ + static Lazy MsBuildExe = new Lazy(() => + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return null; + + var msBuildDirectory = VSWhere("-latest -nologo -property installationPath -format value -prerelease").FirstOrDefault().Text; + + if (!string.IsNullOrWhiteSpace(msBuildDirectory)) + { + string msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\Current\Bin\MSBuild.exe"); + if (!System.IO.File.Exists(msBuildExe)) + msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\15.0\Bin\MSBuild.exe"); + + return msBuildExe; + } + + return null; + }, false); + BuildParameters Parameters { get; set; } protected override void OnBuildInitialized() { @@ -85,7 +104,6 @@ partial class Build : NukeBuild .DependsOn(Clean) .Executes(() => { - if (Parameters.IsRunningOnWindows) MSBuild(Parameters.MSBuildSolution, c => c .SetArgumentConfigurator(a => a.Add("/r")) @@ -93,7 +111,7 @@ partial class Build : NukeBuild .SetVerbosity(MSBuildVerbosity.Minimal) .AddProperty("PackageVersion", Parameters.Version) .AddProperty("iOSRoslynPathHackRequired", "true") - .SetToolsVersion(MSBuildToolsVersion._15_0) + .SetToolPath(MsBuildExe.Value) .AddTargets("Build") ); @@ -224,7 +242,7 @@ partial class Build : NukeBuild .SetVerbosity(MSBuildVerbosity.Minimal) .AddProperty("PackageVersion", Parameters.Version) .AddProperty("iOSRoslynPathHackRequired", "true") - .SetToolsVersion(MSBuildToolsVersion._15_0) + .SetToolPath(MsBuildExe.Value) .AddTargets("Pack")); else DotNetPack(Parameters.MSBuildSolution, c => diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index e02acff007..2a736e4653 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -1,4 +1,4 @@ - + Exe @@ -13,6 +13,7 @@ + From 561ae7f185728bbceab3624e36bfaf73c164a681 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 24 Jun 2019 19:17:23 +0800 Subject: [PATCH 38/44] Invalidate parent grid measure when Row/Col definition's height/width props change. Fixes #2664 --- src/Avalonia.Controls/ColumnDefinition.cs | 33 ++++++++++++++---- src/Avalonia.Controls/RowDefinition.cs | 41 +++++++++++++++++------ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index e3d2489241..9c520c434e 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -62,8 +62,15 @@ namespace Avalonia.Controls /// public double MaxWidth { - get { return GetValue(MaxWidthProperty); } - set { SetValue(MaxWidthProperty, value); } + get + { + return GetValue(MaxWidthProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(MaxWidthProperty, value); + } } /// @@ -71,8 +78,15 @@ namespace Avalonia.Controls /// public double MinWidth { - get { return GetValue(MinWidthProperty); } - set { SetValue(MinWidthProperty, value); } + get + { + return GetValue(MinWidthProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(MinWidthProperty, value); + } } /// @@ -80,8 +94,15 @@ namespace Avalonia.Controls /// public GridLength Width { - get { return GetValue(WidthProperty); } - set { SetValue(WidthProperty, value); } + get + { + return GetValue(WidthProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(WidthProperty, value); + } } internal override GridLength UserSizeValueCache => this.Width; diff --git a/src/Avalonia.Controls/RowDefinition.cs b/src/Avalonia.Controls/RowDefinition.cs index ad7312d515..1f2f738670 100644 --- a/src/Avalonia.Controls/RowDefinition.cs +++ b/src/Avalonia.Controls/RowDefinition.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls /// /// Initializes a new instance of the class. /// - public RowDefinition() + public RowDefinition() { } @@ -38,7 +38,7 @@ namespace Avalonia.Controls /// /// The height of the row. /// The height unit of the column. - public RowDefinition(double value, GridUnitType type) + public RowDefinition(double value, GridUnitType type) { Height = new GridLength(value, type); } @@ -47,7 +47,7 @@ namespace Avalonia.Controls /// Initializes a new instance of the class. /// /// The height of the column. - public RowDefinition(GridLength height) + public RowDefinition(GridLength height) { Height = height; } @@ -62,8 +62,15 @@ namespace Avalonia.Controls /// public double MaxHeight { - get { return GetValue(MaxHeightProperty); } - set { SetValue(MaxHeightProperty, value); } + get + { + return GetValue(MaxHeightProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(MaxHeightProperty, value); + } } /// @@ -71,8 +78,15 @@ namespace Avalonia.Controls /// public double MinHeight { - get { return GetValue(MinHeightProperty); } - set { SetValue(MinHeightProperty, value); } + get + { + return GetValue(MinHeightProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(MinHeightProperty, value); + } } /// @@ -80,12 +94,19 @@ namespace Avalonia.Controls /// public GridLength Height { - get { return GetValue(HeightProperty); } - set { SetValue(HeightProperty, value); } + get + { + return GetValue(HeightProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(HeightProperty, value); + } } internal override GridLength UserSizeValueCache => this.Height; internal override double UserMinSizeValueCache => this.MinHeight; internal override double UserMaxSizeValueCache => this.MaxHeight; } -} \ No newline at end of file +} From 710215ddb2558fe927880e95bae68485fd944cc1 Mon Sep 17 00:00:00 2001 From: wieslawsoltes Date: Tue, 25 Jun 2019 13:26:35 +0200 Subject: [PATCH 39/44] Revert StackPanel --- src/Avalonia.Controls/StackPanel.cs | 148 ++++++++++++---------------- 1 file changed, 65 insertions(+), 83 deletions(-) diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 59c3c33942..c29faa1b4d 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -1,10 +1,10 @@ -// This source file is adapted from the Windows Presentation Foundation project. -// (https://github.com/dotnet/wpf/) -// -// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using Avalonia.Input; +using Avalonia.Layout; namespace Avalonia.Controls { @@ -155,124 +155,106 @@ namespace Avalonia.Controls } /// - /// General StackPanel layout behavior is to grow unbounded in the "stacking" direction (Size To Content). - /// Children in this dimension are encouraged to be as large as they like. In the other dimension, - /// StackPanel will assume the maximum size of its children. + /// Measures the control. /// - /// Constraint - /// Desired size + /// The available size. + /// The desired size of the control. protected override Size MeasureOverride(Size availableSize) { - Size stackDesiredSize = new Size(); - var children = Children; - Size layoutSlotSize = availableSize; - bool fHorizontal = (Orientation == Orientation.Horizontal); - double spacing = Spacing; - bool hasVisibleChild = false; + double childAvailableWidth = double.PositiveInfinity; + double childAvailableHeight = double.PositiveInfinity; - // - // Initialize child sizing and iterator data - // Allow children as much size as they want along the stack. - // - if (fHorizontal) + if (Orientation == Orientation.Vertical) { - layoutSlotSize = layoutSlotSize.WithWidth(Double.PositiveInfinity); + childAvailableWidth = availableSize.Width; + + if (!double.IsNaN(Width)) + { + childAvailableWidth = Width; + } + + childAvailableWidth = Math.Min(childAvailableWidth, MaxWidth); + childAvailableWidth = Math.Max(childAvailableWidth, MinWidth); } else { - layoutSlotSize = layoutSlotSize.WithHeight(Double.PositiveInfinity); - } + childAvailableHeight = availableSize.Height; - // - // Iterate through children. - // While we still supported virtualization, this was hidden in a child iterator (see source history). - // - for (int i = 0, count = children.Count; i < count; ++i) - { - // Get next child. - var child = children[i]; - - if (child == null) - { continue; } - - bool isVisible = child.IsVisible; - - if (isVisible && !hasVisibleChild) + if (!double.IsNaN(Height)) { - hasVisibleChild = true; + childAvailableHeight = Height; } - // Measure the child. - child.Measure(layoutSlotSize); - Size childDesiredSize = child.DesiredSize; + childAvailableHeight = Math.Min(childAvailableHeight, MaxHeight); + childAvailableHeight = Math.Max(childAvailableHeight, MinHeight); + } + + double measuredWidth = 0; + double measuredHeight = 0; + double spacing = Spacing; + bool hasVisibleChild = Children.Any(c => c.IsVisible); + + foreach (Control child in Children) + { + child.Measure(new Size(childAvailableWidth, childAvailableHeight)); + Size size = child.DesiredSize; - // Accumulate child size. - if (fHorizontal) + if (Orientation == Orientation.Vertical) { - stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width + (isVisible ? spacing : 0) + childDesiredSize.Width); - stackDesiredSize = stackDesiredSize.WithHeight(Math.Max(stackDesiredSize.Height, childDesiredSize.Height)); + measuredHeight += size.Height + (child.IsVisible ? spacing : 0); + measuredWidth = Math.Max(measuredWidth, size.Width); } else { - stackDesiredSize = stackDesiredSize.WithWidth(Math.Max(stackDesiredSize.Width, childDesiredSize.Width)); - stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height + (isVisible ? spacing : 0) + childDesiredSize.Height); + measuredWidth += size.Width + (child.IsVisible ? spacing : 0); + measuredHeight = Math.Max(measuredHeight, size.Height); } } - if (fHorizontal) + if (Orientation == Orientation.Vertical) { - stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width - (hasVisibleChild ? spacing : 0)); + measuredHeight -= (hasVisibleChild ? spacing : 0); } else - { - stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0)); + { + measuredWidth -= (hasVisibleChild ? spacing : 0); } - // TODO: In WPF `.Constrain(availableSize)` is not used. - //return stackDesiredSize; - return stackDesiredSize.Constrain(availableSize); + return new Size(measuredWidth, measuredHeight).Constrain(availableSize); } - /// - /// Content arrangement. - /// - /// Arrange size + /// protected override Size ArrangeOverride(Size finalSize) { - var children = Children; - bool fHorizontal = (Orientation == Orientation.Horizontal); - Rect rcChild = new Rect(finalSize); - double previousChildSize = 0.0; + var orientation = Orientation; var spacing = Spacing; + var finalRect = new Rect(finalSize); + var pos = 0.0; - // - // Arrange and Position Children. - // - for (int i = 0, count = children.Count; i < count; ++i) + foreach (Control child in Children) { - var child = children[i]; + if (!child.IsVisible) + { + continue; + } - if (child == null) - { continue; } + double childWidth = child.DesiredSize.Width; + double childHeight = child.DesiredSize.Height; - if (fHorizontal) + if (orientation == Orientation.Vertical) { - rcChild = rcChild.WithX(rcChild.X + previousChildSize); - previousChildSize = child.DesiredSize.Width; - rcChild = rcChild.WithWidth(previousChildSize); - rcChild = rcChild.WithHeight(Math.Max(finalSize.Height, child.DesiredSize.Height)); - previousChildSize += spacing; + var rect = new Rect(0, pos, childWidth, childHeight) + .Align(finalRect, child.HorizontalAlignment, VerticalAlignment.Top); + ArrangeChild(child, rect, finalSize, orientation); + pos += childHeight + spacing; } else { - rcChild = rcChild.WithY(rcChild.Y + previousChildSize); - previousChildSize = child.DesiredSize.Height; - rcChild = rcChild.WithHeight(previousChildSize); - rcChild = rcChild.WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width)); - previousChildSize += spacing; + var rect = new Rect(pos, 0, childWidth, childHeight) + .Align(finalRect, HorizontalAlignment.Left, child.VerticalAlignment); + ArrangeChild(child, rect, finalSize, orientation); + pos += childWidth + spacing; } - - child.Arrange(rcChild); } return finalSize; From 0225023d53d54f9ea794e02f38c6ae483ef15f2e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 25 Jun 2019 14:54:22 +0200 Subject: [PATCH 40/44] Added failing tests for #2372. --- .../Primitives/RangeBaseTests.cs | 35 ++++++++++++++++++- .../Xaml/BasicTests.cs | 19 ++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs index 42578c61ac..0d88a1a9ff 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Markup.Data; using Avalonia.Styling; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -160,6 +161,38 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Equal(expected, track.Value); } + [Fact] + public void Coercion_Should_Not_Be_Done_During_Initialization() + { + var target = new TestRange(); + + target.BeginInit(); + + var root = new TestRoot(target); + target.Minimum = 1; + Assert.Equal(0, target.Value); + + target.Value = 50; + target.EndInit(); + + Assert.Equal(50, target.Value); + } + + [Fact] + public void Coercion_Should_Be_Done_After_Initialization() + { + var target = new TestRange(); + + target.BeginInit(); + + var root = new TestRoot(target); + target.Minimum = 1; + + target.EndInit(); + + Assert.Equal(1, target.Value); + } + private class TestRange : RangeBase { } @@ -199,4 +232,4 @@ namespace Avalonia.Controls.UnitTests.Primitives } } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 7fe0fc4a08..720ff3b0de 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -907,6 +907,25 @@ do we need it?")] } } + [Fact] + public void Slider_Properties_Can_Be_Set_In_Any_Order() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + + var window = AvaloniaXamlLoader.Parse(xaml); + var slider = (Slider)window.Content; + + Assert.Equal(0, slider.Minimum); + Assert.Equal(1000, slider.Maximum); + Assert.Equal(500, slider.Value); + } + } + private class SelectedItemsViewModel : INotifyPropertyChanged { public string[] Items { get; set; } From 4242c882b4daaca0a3580d27e6e04145d6c1eb21 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 25 Jun 2019 14:59:28 +0200 Subject: [PATCH 41/44] Added StyledElement.OnInitialized. So that a control can easily react to its own initialization. --- src/Avalonia.Styling/StyledElement.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index f7e063dfb5..146a4c75e7 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -392,6 +392,7 @@ namespace Avalonia if (_initCount == 0 && !IsInitialized) { IsInitialized = true; + OnInitialized(); Initialized?.Invoke(this, EventArgs.Empty); } } @@ -608,7 +609,14 @@ namespace Avalonia protected virtual void OnDataContextEndUpdate() { } - + + /// + /// Called when the control finishes initialization. + /// + protected virtual void OnInitialized() + { + } + private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) { if (o is StyledElement element) From 440af4623550861e917c9ff62f668bc2af7997b9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 25 Jun 2019 15:01:22 +0200 Subject: [PATCH 42/44] Don't coerce values until initialized. Fixes #2372. --- src/Avalonia.Controls/Primitives/RangeBase.cs | 65 ++++++++++++------- .../Primitives/RangeBaseTests.cs | 4 ++ 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index ae175734b9..f1ee7c0e1a 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.cs @@ -75,10 +75,18 @@ namespace Avalonia.Controls.Primitives set { - value = ValidateMinimum(value); - SetAndRaise(MinimumProperty, ref _minimum, value); - Maximum = ValidateMaximum(Maximum); - Value = ValidateValue(Value); + ValidateDouble(value, "Minimum"); + + if (IsInitialized) + { + SetAndRaise(MinimumProperty, ref _minimum, value); + Maximum = ValidateMaximum(Maximum); + Value = ValidateValue(Value); + } + else + { + SetAndRaise(MinimumProperty, ref _minimum, value); + } } } @@ -94,9 +102,18 @@ namespace Avalonia.Controls.Primitives set { - value = ValidateMaximum(value); - SetAndRaise(MaximumProperty, ref _maximum, value); - Value = ValidateValue(Value); + ValidateDouble(value, "Maximum"); + + if (IsInitialized) + { + value = ValidateMaximum(value); + SetAndRaise(MaximumProperty, ref _maximum, value); + Value = ValidateValue(Value); + } + else + { + SetAndRaise(MaximumProperty, ref _maximum, value); + } } } @@ -112,8 +129,17 @@ namespace Avalonia.Controls.Primitives set { - value = ValidateValue(value); - SetAndRaise(ValueProperty, ref _value, value); + ValidateDouble(value, "Value"); + + if (IsInitialized) + { + value = ValidateValue(value); + SetAndRaise(ValueProperty, ref _value, value); + } + else + { + SetAndRaise(ValueProperty, ref _value, value); + } } } @@ -129,6 +155,14 @@ namespace Avalonia.Controls.Primitives set => SetValue(LargeChangeProperty, value); } + protected override void OnInitialized() + { + base.OnInitialized(); + + Maximum = ValidateMaximum(Maximum); + Value = ValidateValue(Value); + } + /// /// Throws an exception if the double value is NaN or Inf. /// @@ -142,17 +176,6 @@ namespace Avalonia.Controls.Primitives } } - /// - /// Validates the property. - /// - /// The value. - /// The coerced value. - private double ValidateMinimum(double value) - { - ValidateDouble(value, "Minimum"); - return value; - } - /// /// Validates/coerces the property. /// @@ -160,7 +183,6 @@ namespace Avalonia.Controls.Primitives /// The coerced value. private double ValidateMaximum(double value) { - ValidateDouble(value, "Maximum"); return Math.Max(value, Minimum); } @@ -171,7 +193,6 @@ namespace Avalonia.Controls.Primitives /// The coerced value. private double ValidateValue(double value) { - ValidateDouble(value, "Value"); return MathUtilities.Clamp(value, Minimum, Maximum); } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs index 0d88a1a9ff..d913e3e54f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs @@ -23,6 +23,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Minimum = 100, Maximum = 50, }; + var root = new TestRoot(target); Assert.Equal(100, target.Minimum); Assert.Equal(100, target.Maximum); @@ -37,6 +38,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Maximum = 50, Value = 100, }; + var root = new TestRoot(target); Assert.Equal(0, target.Minimum); Assert.Equal(50, target.Maximum); @@ -52,6 +54,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Maximum = 100, Value = 50, }; + var root = new TestRoot(target); target.Minimum = 200; @@ -69,6 +72,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Maximum = 100, Value = 100, }; + var root = new TestRoot(target); target.Maximum = 50; From 336086a588222d63e2e7a08266d113c10f77c883 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 25 Jun 2019 17:51:06 +0200 Subject: [PATCH 43/44] Make ItemCount a direct property. Fixes #2658 --- src/Avalonia.Controls/ItemsControl.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 3dfeae52a4..a292ff7d0a 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -36,6 +36,12 @@ namespace Avalonia.Controls public static readonly DirectProperty ItemsProperty = AvaloniaProperty.RegisterDirect(nameof(Items), o => o.Items, (o, v) => o.Items = v); + /// + /// Defines the property. + /// + public static readonly DirectProperty ItemCountProperty = + AvaloniaProperty.RegisterDirect(nameof(ItemCount), o => o.ItemCount); + /// /// Defines the property. /// @@ -55,6 +61,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(MemberSelector)); private IEnumerable _items = new AvaloniaList(); + private int _itemCount; private IItemContainerGenerator _itemContainerGenerator; private IDisposable _itemsCollectionChangedSubscription; @@ -110,10 +117,13 @@ namespace Avalonia.Controls set { SetAndRaise(ItemsProperty, ref _items, value); } } + /// + /// Gets the number of items in . + /// public int ItemCount { - get; - private set; + get => _itemCount; + private set => SetAndRaise(ItemCountProperty, ref _itemCount, value); } /// From 1e115b89c8e980c9288bfcd8d0375a5fbce00c98 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 25 Jun 2019 19:55:09 +0300 Subject: [PATCH 44/44] Fixed Start() + obsoleted Start() --- src/Avalonia.Controls/AppBuilderBase.cs | 24 +++++++++++++------ .../DesktopApplicationExtensions.cs | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 0f0b55e9e8..59ff35be76 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -4,6 +4,7 @@ using System; using System.Reflection; using System.Linq; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform; namespace Avalonia.Controls @@ -106,19 +107,28 @@ namespace Avalonia.Controls public void Start(Func dataContextProvider = null) where TMainWindow : Window, new() { - var window = new TMainWindow(); - if (dataContextProvider != null) - window.DataContext = dataContextProvider(); - Instance.Run(window); + AfterSetup(builder => + { + var window = new TMainWindow(); + if (dataContextProvider != null) + window.DataContext = dataContextProvider(); + ((IClassicDesktopStyleApplicationLifetime)builder.Instance.ApplicationLifetime) + .MainWindow = window; + }); + + // Copy-pasted because we can't call extension methods due to generic constraints + var lifetime = new ClassicDesktopStyleApplicationLifetime(Instance) {ShutdownMode = ShutdownMode.OnMainWindowClose}; + Instance.ApplicationLifetime = lifetime; + SetupWithoutStarting(); + lifetime.Start(Array.Empty()); } public delegate void AppMainDelegate(Application app, string[] args); - [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] + [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details", true)] public void Start() { - Setup(); - Instance.Run(); + throw new NotSupportedException(); } public void Start(AppMainDelegate main, string[] args) diff --git a/src/Avalonia.Controls/DesktopApplicationExtensions.cs b/src/Avalonia.Controls/DesktopApplicationExtensions.cs index 9b81bc94d1..ff6705cdc0 100644 --- a/src/Avalonia.Controls/DesktopApplicationExtensions.cs +++ b/src/Avalonia.Controls/DesktopApplicationExtensions.cs @@ -8,7 +8,7 @@ namespace Avalonia.Controls { public static class DesktopApplicationExtensions { - [Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] + [Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details", true)] public static void Run(this Application app) => throw new NotSupportedException(); ///