68 changed files with 2732 additions and 663 deletions
@ -0,0 +1,19 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
|||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
|||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
|||
x:Class="ControlCatalog.Pages.WindowCustomizationsPage"> |
|||
<StackPanel Spacing="10" Margin="25"> |
|||
<CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" /> |
|||
<CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" /> |
|||
<CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" /> |
|||
<Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" /> |
|||
<ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}"> |
|||
<ComboBoxItem>None</ComboBoxItem> |
|||
<ComboBoxItem>Transparent</ComboBoxItem> |
|||
<ComboBoxItem>Blur</ComboBoxItem> |
|||
<ComboBoxItem>AcrylicBlur</ComboBoxItem> |
|||
</ComboBox> |
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,19 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class WindowCustomizationsPage : UserControl |
|||
{ |
|||
public WindowCustomizationsPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
#pragma warning disable MA0048 // File name must match type name
|
|||
#define INTERNAL_NULLABLE_ATTRIBUTES
|
|||
#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
|
|||
|
|||
// https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
|
|||
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
namespace System.Diagnostics.CodeAnalysis |
|||
{ |
|||
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class AllowNullAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class DisallowNullAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class MaybeNullAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class NotNullAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class MaybeNullWhenAttribute : Attribute |
|||
{ |
|||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
|||
/// <param name="returnValue">
|
|||
/// The return value condition. If the method returns this value, the associated parameter may be null.
|
|||
/// </param>
|
|||
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; |
|||
|
|||
/// <summary>Gets the return value condition.</summary>
|
|||
public bool ReturnValue { get; } |
|||
} |
|||
|
|||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class NotNullWhenAttribute : Attribute |
|||
{ |
|||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
|||
/// <param name="returnValue">
|
|||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
|||
/// </param>
|
|||
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; |
|||
|
|||
/// <summary>Gets the return value condition.</summary>
|
|||
public bool ReturnValue { get; } |
|||
} |
|||
|
|||
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class NotNullIfNotNullAttribute : Attribute |
|||
{ |
|||
/// <summary>Initializes the attribute with the associated parameter name.</summary>
|
|||
/// <param name="parameterName">
|
|||
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
|
|||
/// </param>
|
|||
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; |
|||
|
|||
/// <summary>Gets the associated parameter name.</summary>
|
|||
public string ParameterName { get; } |
|||
} |
|||
|
|||
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class DoesNotReturnAttribute : Attribute |
|||
{ } |
|||
|
|||
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
|
|||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] |
|||
#if INTERNAL_NULLABLE_ATTRIBUTES
|
|||
internal |
|||
#else
|
|||
public |
|||
#endif
|
|||
sealed class DoesNotReturnIfAttribute : Attribute |
|||
{ |
|||
/// <summary>Initializes the attribute with the specified parameter value.</summary>
|
|||
/// <param name="parameterValue">
|
|||
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
|
|||
/// the associated parameter matches this value.
|
|||
/// </param>
|
|||
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; |
|||
|
|||
/// <summary>Gets the condition parameter value.</summary>
|
|||
public bool ParameterValue { get; } |
|||
} |
|||
} |
|||
#endif
|
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
#if !BUILDTASK
|
|||
public |
|||
#endif
|
|||
static class StyleClassParser |
|||
{ |
|||
public static ReadOnlySpan<char> ParseStyleClass(this ref CharacterReader r) |
|||
{ |
|||
if (IsValidIdentifierStart(r.Peek)) |
|||
{ |
|||
return r.TakeWhile(c => IsValidIdentifierChar(c)); |
|||
} |
|||
else |
|||
{ |
|||
return ReadOnlySpan<char>.Empty; |
|||
} |
|||
} |
|||
|
|||
private static bool IsValidIdentifierStart(char c) |
|||
{ |
|||
return char.IsLetter(c) || c == '_'; |
|||
} |
|||
|
|||
private static bool IsValidIdentifierChar(char c) |
|||
{ |
|||
if (IsValidIdentifierStart(c) || c == '-') |
|||
{ |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
var cat = CharUnicodeInfo.GetUnicodeCategory(c); |
|||
return cat == UnicodeCategory.NonSpacingMark || |
|||
cat == UnicodeCategory.SpacingCombiningMark || |
|||
cat == UnicodeCategory.ConnectorPunctuation || |
|||
cat == UnicodeCategory.Format || |
|||
cat == UnicodeCategory.DecimalDigitNumber; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Chrome |
|||
{ |
|||
/// <summary>
|
|||
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
|
|||
/// </summary>
|
|||
public class CaptionButtons : TemplatedControl |
|||
{ |
|||
private CompositeDisposable? _disposables; |
|||
private Window? _hostWindow; |
|||
|
|||
public void Attach(Window hostWindow) |
|||
{ |
|||
if (_disposables == null) |
|||
{ |
|||
_hostWindow = hostWindow; |
|||
|
|||
_disposables = new CompositeDisposable |
|||
{ |
|||
_hostWindow.GetObservable(Window.WindowStateProperty) |
|||
.Subscribe(x => |
|||
{ |
|||
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
|||
PseudoClasses.Set(":normal", x == WindowState.Normal); |
|||
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
|||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
|||
}) |
|||
}; |
|||
} |
|||
} |
|||
|
|||
public void Detach() |
|||
{ |
|||
if (_disposables != null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Remove(this); |
|||
|
|||
_disposables.Dispose(); |
|||
_disposables = null; |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
|
|||
var closeButton = e.NameScope.Get<Panel>("PART_CloseButton"); |
|||
var restoreButton = e.NameScope.Get<Panel>("PART_RestoreButton"); |
|||
var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton"); |
|||
var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton"); |
|||
|
|||
closeButton.PointerReleased += (sender, e) => _hostWindow?.Close(); |
|||
|
|||
restoreButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; |
|||
} |
|||
}; |
|||
|
|||
minimiseButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = WindowState.Minimized; |
|||
} |
|||
}; |
|||
|
|||
fullScreenButton.PointerReleased += (sender, e) => |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Controls.Primitives; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Chrome |
|||
{ |
|||
/// <summary>
|
|||
/// Draws a titlebar when managed client decorations are enabled.
|
|||
/// </summary>
|
|||
public class TitleBar : TemplatedControl |
|||
{ |
|||
private CompositeDisposable? _disposables; |
|||
private readonly Window? _hostWindow; |
|||
private CaptionButtons? _captionButtons; |
|||
|
|||
public TitleBar(Window hostWindow) |
|||
{ |
|||
_hostWindow = hostWindow; |
|||
} |
|||
|
|||
public TitleBar() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public void Attach() |
|||
{ |
|||
if (_disposables == null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Add(this); |
|||
|
|||
if (_hostWindow != null) |
|||
{ |
|||
_disposables = new CompositeDisposable |
|||
{ |
|||
_hostWindow.GetObservable(Window.WindowDecorationMarginProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.OffScreenMarginProperty) |
|||
.Subscribe(x => UpdateSize()), |
|||
|
|||
_hostWindow.GetObservable(Window.WindowStateProperty) |
|||
.Subscribe(x => |
|||
{ |
|||
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
|||
PseudoClasses.Set(":normal", x == WindowState.Normal); |
|||
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
|||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
|||
}) |
|||
}; |
|||
|
|||
_captionButtons?.Attach(_hostWindow); |
|||
} |
|||
|
|||
UpdateSize(); |
|||
} |
|||
} |
|||
|
|||
private void UpdateSize() |
|||
{ |
|||
if (_hostWindow != null) |
|||
{ |
|||
Margin = new Thickness( |
|||
_hostWindow.OffScreenMargin.Left, |
|||
_hostWindow.OffScreenMargin.Top, |
|||
_hostWindow.OffScreenMargin.Right, |
|||
_hostWindow.OffScreenMargin.Bottom); |
|||
|
|||
if (_hostWindow.WindowState != WindowState.FullScreen) |
|||
{ |
|||
Height = _hostWindow.WindowDecorationMargin.Top; |
|||
|
|||
if (_captionButtons != null) |
|||
{ |
|||
_captionButtons.Height = Height; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Detach() |
|||
{ |
|||
if (_disposables != null) |
|||
{ |
|||
var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); |
|||
|
|||
layer?.Children.Remove(this); |
|||
|
|||
_disposables.Dispose(); |
|||
_disposables = null; |
|||
|
|||
_captionButtons?.Detach(); |
|||
} |
|||
} |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
|
|||
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons"); |
|||
|
|||
if (_hostWindow != null) |
|||
{ |
|||
_captionButtons.Attach(_hostWindow); |
|||
} |
|||
|
|||
UpdateSize(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Hint for Window Chrome when ClientArea is Extended.
|
|||
/// </summary>
|
|||
[Flags] |
|||
public enum ExtendClientAreaChromeHints |
|||
{ |
|||
/// <summary>
|
|||
/// The will be no chrome at all.
|
|||
/// </summary>
|
|||
NoChrome, |
|||
|
|||
/// <summary>
|
|||
/// The default for the platform.
|
|||
/// </summary>
|
|||
Default = SystemChrome, |
|||
|
|||
/// <summary>
|
|||
/// Use SystemChrome
|
|||
/// </summary>
|
|||
SystemChrome = 0x01, |
|||
|
|||
/// <summary>
|
|||
/// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
|
|||
/// This is because Windows Chrome can not be shown ontop of user content.
|
|||
/// </summary>
|
|||
PreferSystemChrome = 0x02, |
|||
|
|||
/// <summary>
|
|||
/// On OSX the titlebar is the thicker toolbar kind. Causes traffic lights to be positioned
|
|||
/// slightly lower than normal.
|
|||
/// </summary>
|
|||
OSXThickTitleBar = 0x08, |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using System.Linq; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.VisualTree; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest |
|||
{ |
|||
public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual) |
|||
{ |
|||
foreach (var v in visual.GetVisualAncestors()) |
|||
if (v is VisualLayerManager vlm) |
|||
if (vlm.OverlayLayer != null) |
|||
return vlm.ChromeOverlayLayer; |
|||
|
|||
if (visual is TopLevel tl) |
|||
{ |
|||
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault(); |
|||
return layers?.ChromeOverlayLayer; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public bool HitTest(Point point) => Children.HitTestCustom(point); |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Style Selector="CaptionButtons"> |
|||
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/> |
|||
<Setter Property="MaxHeight" Value="30" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Panel"> |
|||
<Setter Property="Width" Value="45" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
<Style Selector="Panel:pointerover"> |
|||
<Setter Property="Background" Value="#7F7f7f7f" /> |
|||
</Style> |
|||
<Style Selector="Panel#PART_CloseButton:pointerover"> |
|||
<Setter Property="Background" Value="#7FFF0000" /> |
|||
</Style> |
|||
<Style Selector="Viewbox"> |
|||
<Setter Property="Width" Value="11" /> |
|||
<Setter Property="Margin" Value="2" /> |
|||
</Style> |
|||
</StackPanel.Styles> |
|||
<Panel x:Name="PART_FullScreenButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_MinimiseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_RestoreButton"> |
|||
<Viewbox> |
|||
<Viewbox.RenderTransform> |
|||
<RotateTransform Angle="-90" /> |
|||
</Viewbox.RenderTransform> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_CloseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
</StackPanel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,53 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Design.PreviewWith> |
|||
<Border> |
|||
<TitleBar Background="SkyBlue" Height="30" Width="300" Foreground="Black" /> |
|||
</Border> |
|||
</Design.PreviewWith> |
|||
<Style Selector="TitleBar"> |
|||
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
|||
<Setter Property="VerticalAlignment" Value="Top" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="Stretch"> |
|||
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" /> |
|||
<Panel x:Name="PART_Container"> |
|||
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" /> |
|||
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" /> |
|||
</Panel> |
|||
</Panel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen"> |
|||
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar /template/ Border#PART_Background"> |
|||
<Setter Property="IsHitTestVisible" Value="False" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Border#PART_Background"> |
|||
<Setter Property="IsHitTestVisible" Value="True" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_MouseTracker"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_Container"> |
|||
<Setter Property="RenderTransform" Value="translateY(-30px)" /> |
|||
<Setter Property="Transitions"> |
|||
<Transitions> |
|||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" /> |
|||
</Transitions> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen:pointerover /template/ Panel#PART_Container"> |
|||
<Setter Property="RenderTransform" Value="none" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,68 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Style Selector="CaptionButtons"> |
|||
<Setter Property="MaxHeight" Value="30" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal"> |
|||
<StackPanel.Styles> |
|||
<Style Selector="Panel"> |
|||
<Setter Property="Width" Value="45" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
<Style Selector="Panel:pointerover"> |
|||
<Setter Property="Background" Value="#7F7f7f7f" /> |
|||
</Style> |
|||
<Style Selector="Panel#PART_CloseButton:pointerover"> |
|||
<Setter Property="Background" Value="#7FFF0000" /> |
|||
</Style> |
|||
<Style Selector="Viewbox"> |
|||
<Setter Property="Width" Value="11" /> |
|||
<Setter Property="Margin" Value="2" /> |
|||
</Style> |
|||
</StackPanel.Styles> |
|||
<Panel x:Name="PART_FullScreenButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_MinimiseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_RestoreButton"> |
|||
<Viewbox> |
|||
<Viewbox.RenderTransform> |
|||
<RotateTransform Angle="-90" /> |
|||
</Viewbox.RenderTransform> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/> |
|||
</Viewbox> |
|||
</Panel> |
|||
|
|||
<Panel x:Name="PART_CloseButton"> |
|||
<Viewbox> |
|||
<Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
|||
</Viewbox> |
|||
</Panel> |
|||
</StackPanel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path"> |
|||
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path"> |
|||
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
|||
</Style> |
|||
<Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton"> |
|||
<Setter Property="IsVisible" Value="False" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,53 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Design.PreviewWith> |
|||
<Border> |
|||
<TitleBar Background="SkyBlue" Height="30" Width="300" Foreground="Black" /> |
|||
</Border> |
|||
</Design.PreviewWith> |
|||
<Style Selector="TitleBar"> |
|||
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> |
|||
<Setter Property="VerticalAlignment" Value="Top" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="Stretch"> |
|||
<Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" /> |
|||
<Panel x:Name="PART_Container"> |
|||
<Border x:Name="PART_Background" Background="{TemplateBinding Background}" /> |
|||
<CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" /> |
|||
</Panel> |
|||
</Panel> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen"> |
|||
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar /template/ Border#PART_Background"> |
|||
<Setter Property="IsHitTestVisible" Value="False" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Border#PART_Background"> |
|||
<Setter Property="IsHitTestVisible" Value="True" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_MouseTracker"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen /template/ Panel#PART_Container"> |
|||
<Setter Property="RenderTransform" Value="translateY(-30px)" /> |
|||
<Setter Property="Transitions"> |
|||
<Transitions> |
|||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" /> |
|||
</Transitions> |
|||
</Setter> |
|||
</Style> |
|||
|
|||
<Style Selector="TitleBar:fullscreen:pointerover /template/ Panel#PART_Container"> |
|||
<Setter Property="RenderTransform" Value="none" /> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,539 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Win32.Input; |
|||
using static Avalonia.Win32.Interop.UnmanagedMethods; |
|||
|
|||
namespace Avalonia.Win32 |
|||
{ |
|||
public partial class WindowImpl |
|||
{ |
|||
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", |
|||
Justification = "Using Win32 naming for consistency.")] |
|||
protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) |
|||
{ |
|||
const double wheelDelta = 120.0; |
|||
uint timestamp = unchecked((uint)GetMessageTime()); |
|||
|
|||
RawInputEventArgs e = null; |
|||
var shouldTakeFocus = false; |
|||
|
|||
switch ((WindowsMessage)msg) |
|||
{ |
|||
case WindowsMessage.WM_ACTIVATE: |
|||
{ |
|||
var wa = (WindowActivate)(ToInt32(wParam) & 0xffff); |
|||
|
|||
switch (wa) |
|||
{ |
|||
case WindowActivate.WA_ACTIVE: |
|||
case WindowActivate.WA_CLICKACTIVE: |
|||
{ |
|||
Activated?.Invoke(); |
|||
break; |
|||
} |
|||
|
|||
case WindowActivate.WA_INACTIVE: |
|||
{ |
|||
Deactivated?.Invoke(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
case WindowsMessage.WM_NCCALCSIZE: |
|||
{ |
|||
if (ToInt32(wParam) == 1 && !HasFullDecorations || _isClientAreaExtended) |
|||
{ |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_CLOSE: |
|||
{ |
|||
bool? preventClosing = Closing?.Invoke(); |
|||
if (preventClosing == true) |
|||
{ |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_DESTROY: |
|||
{ |
|||
//Window doesn't exist anymore
|
|||
_hwnd = IntPtr.Zero; |
|||
//Remove root reference to this class, so unmanaged delegate can be collected
|
|||
s_instances.Remove(this); |
|||
Closed?.Invoke(); |
|||
|
|||
_mouseDevice.Dispose(); |
|||
_touchDevice?.Dispose(); |
|||
//Free other resources
|
|||
Dispose(); |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
case WindowsMessage.WM_DPICHANGED: |
|||
{ |
|||
var dpi = ToInt32(wParam) & 0xffff; |
|||
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam); |
|||
_scaling = dpi / 96.0; |
|||
ScalingChanged?.Invoke(_scaling); |
|||
SetWindowPos(hWnd, |
|||
IntPtr.Zero, |
|||
newDisplayRect.left, |
|||
newDisplayRect.top, |
|||
newDisplayRect.right - newDisplayRect.left, |
|||
newDisplayRect.bottom - newDisplayRect.top, |
|||
SetWindowPosFlags.SWP_NOZORDER | |
|||
SetWindowPosFlags.SWP_NOACTIVATE); |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
case WindowsMessage.WM_KEYDOWN: |
|||
case WindowsMessage.WM_SYSKEYDOWN: |
|||
{ |
|||
e = new RawKeyEventArgs( |
|||
WindowsKeyboardDevice.Instance, |
|||
timestamp, |
|||
_owner, |
|||
RawKeyEventType.KeyDown, |
|||
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), |
|||
WindowsKeyboardDevice.Instance.Modifiers); |
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_MENUCHAR: |
|||
{ |
|||
// mute the system beep
|
|||
return (IntPtr)((int)MenuCharParam.MNC_CLOSE << 16); |
|||
} |
|||
|
|||
case WindowsMessage.WM_KEYUP: |
|||
case WindowsMessage.WM_SYSKEYUP: |
|||
{ |
|||
e = new RawKeyEventArgs( |
|||
WindowsKeyboardDevice.Instance, |
|||
timestamp, |
|||
_owner, |
|||
RawKeyEventType.KeyUp, |
|||
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), |
|||
WindowsKeyboardDevice.Instance.Modifiers); |
|||
break; |
|||
} |
|||
case WindowsMessage.WM_CHAR: |
|||
{ |
|||
// Ignore control chars
|
|||
if (ToInt32(wParam) >= 32) |
|||
{ |
|||
e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner, |
|||
new string((char)ToInt32(wParam), 1)); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_LBUTTONDOWN: |
|||
case WindowsMessage.WM_RBUTTONDOWN: |
|||
case WindowsMessage.WM_MBUTTONDOWN: |
|||
case WindowsMessage.WM_XBUTTONDOWN: |
|||
{ |
|||
shouldTakeFocus = ShouldTakeFocusOnClick; |
|||
if (ShouldIgnoreTouchEmulatedMessage()) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
e = new RawPointerEventArgs( |
|||
_mouseDevice, |
|||
timestamp, |
|||
_owner, |
|||
(WindowsMessage)msg switch |
|||
{ |
|||
WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown, |
|||
WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown, |
|||
WindowsMessage.WM_MBUTTONDOWN => RawPointerEventType.MiddleButtonDown, |
|||
WindowsMessage.WM_XBUTTONDOWN => |
|||
HighWord(ToInt32(wParam)) == 1 ? |
|||
RawPointerEventType.XButton1Down : |
|||
RawPointerEventType.XButton2Down |
|||
}, |
|||
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_LBUTTONUP: |
|||
case WindowsMessage.WM_RBUTTONUP: |
|||
case WindowsMessage.WM_MBUTTONUP: |
|||
case WindowsMessage.WM_XBUTTONUP: |
|||
{ |
|||
shouldTakeFocus = ShouldTakeFocusOnClick; |
|||
if (ShouldIgnoreTouchEmulatedMessage()) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
e = new RawPointerEventArgs( |
|||
_mouseDevice, |
|||
timestamp, |
|||
_owner, |
|||
(WindowsMessage)msg switch |
|||
{ |
|||
WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp, |
|||
WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp, |
|||
WindowsMessage.WM_MBUTTONUP => RawPointerEventType.MiddleButtonUp, |
|||
WindowsMessage.WM_XBUTTONUP => |
|||
HighWord(ToInt32(wParam)) == 1 ? |
|||
RawPointerEventType.XButton1Up : |
|||
RawPointerEventType.XButton2Up, |
|||
}, |
|||
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_MOUSEMOVE: |
|||
{ |
|||
if (ShouldIgnoreTouchEmulatedMessage()) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
if (!_trackingMouse) |
|||
{ |
|||
var tm = new TRACKMOUSEEVENT |
|||
{ |
|||
cbSize = Marshal.SizeOf<TRACKMOUSEEVENT>(), |
|||
dwFlags = 2, |
|||
hwndTrack = _hwnd, |
|||
dwHoverTime = 0, |
|||
}; |
|||
|
|||
TrackMouseEvent(ref tm); |
|||
} |
|||
|
|||
e = new RawPointerEventArgs( |
|||
_mouseDevice, |
|||
timestamp, |
|||
_owner, |
|||
RawPointerEventType.Move, |
|||
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
|||
|
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_MOUSEWHEEL: |
|||
{ |
|||
e = new RawMouseWheelEventArgs( |
|||
_mouseDevice, |
|||
timestamp, |
|||
_owner, |
|||
PointToClient(PointFromLParam(lParam)), |
|||
new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam)); |
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_MOUSEHWHEEL: |
|||
{ |
|||
e = new RawMouseWheelEventArgs( |
|||
_mouseDevice, |
|||
timestamp, |
|||
_owner, |
|||
PointToClient(PointFromLParam(lParam)), |
|||
new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam)); |
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_MOUSELEAVE: |
|||
{ |
|||
_trackingMouse = false; |
|||
e = new RawPointerEventArgs( |
|||
_mouseDevice, |
|||
timestamp, |
|||
_owner, |
|||
RawPointerEventType.LeaveWindow, |
|||
new Point(-1, -1), WindowsKeyboardDevice.Instance.Modifiers); |
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_NCLBUTTONDOWN: |
|||
case WindowsMessage.WM_NCRBUTTONDOWN: |
|||
case WindowsMessage.WM_NCMBUTTONDOWN: |
|||
case WindowsMessage.WM_NCXBUTTONDOWN: |
|||
{ |
|||
e = new RawPointerEventArgs( |
|||
_mouseDevice, |
|||
timestamp, |
|||
_owner, |
|||
(WindowsMessage)msg switch |
|||
{ |
|||
WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType |
|||
.NonClientLeftButtonDown, |
|||
WindowsMessage.WM_NCRBUTTONDOWN => RawPointerEventType.RightButtonDown, |
|||
WindowsMessage.WM_NCMBUTTONDOWN => RawPointerEventType.MiddleButtonDown, |
|||
WindowsMessage.WM_NCXBUTTONDOWN => |
|||
HighWord(ToInt32(wParam)) == 1 ? |
|||
RawPointerEventType.XButton1Down : |
|||
RawPointerEventType.XButton2Down, |
|||
}, |
|||
PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam)); |
|||
break; |
|||
} |
|||
case WindowsMessage.WM_TOUCH: |
|||
{ |
|||
var touchInputCount = wParam.ToInt32(); |
|||
|
|||
var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount]; |
|||
var touchInputs = new Span<TOUCHINPUT>(pTouchInputs, touchInputCount); |
|||
|
|||
if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf<TOUCHINPUT>())) |
|||
{ |
|||
foreach (var touchInput in touchInputs) |
|||
{ |
|||
Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, |
|||
_owner, |
|||
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ? |
|||
RawPointerEventType.TouchEnd : |
|||
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ? |
|||
RawPointerEventType.TouchBegin : |
|||
RawPointerEventType.TouchUpdate, |
|||
PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)), |
|||
WindowsKeyboardDevice.Instance.Modifiers, |
|||
touchInput.Id)); |
|||
} |
|||
|
|||
CloseTouchInputHandle(lParam); |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
case WindowsMessage.WM_NCPAINT: |
|||
{ |
|||
if (!HasFullDecorations) |
|||
{ |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_NCACTIVATE: |
|||
{ |
|||
if (!HasFullDecorations) |
|||
{ |
|||
return new IntPtr(1); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case WindowsMessage.WM_PAINT: |
|||
{ |
|||
using (_rendererLock.Lock()) |
|||
{ |
|||
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) |
|||
{ |
|||
var f = Scaling; |
|||
var r = ps.rcPaint; |
|||
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, |
|||
(r.bottom - r.top) / f)); |
|||
EndPaint(_hwnd, ref ps); |
|||
} |
|||
} |
|||
|
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
case WindowsMessage.WM_SIZE: |
|||
{ |
|||
using (_rendererLock.Lock()) |
|||
{ |
|||
// Do nothing here, just block until the pending frame render is completed on the render thread
|
|||
} |
|||
|
|||
var size = (SizeCommand)wParam; |
|||
|
|||
if (Resized != null && |
|||
(size == SizeCommand.Restored || |
|||
size == SizeCommand.Maximized)) |
|||
{ |
|||
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); |
|||
Resized(clientSize / Scaling); |
|||
} |
|||
|
|||
var windowState = size == SizeCommand.Maximized ? |
|||
WindowState.Maximized : |
|||
(size == SizeCommand.Minimized ? WindowState.Minimized : WindowState.Normal); |
|||
|
|||
if (windowState != _lastWindowState) |
|||
{ |
|||
_lastWindowState = windowState; |
|||
|
|||
WindowStateChanged?.Invoke(windowState); |
|||
|
|||
if (_isClientAreaExtended) |
|||
{ |
|||
UpdateExtendMargins(); |
|||
|
|||
ExtendClientAreaToDecorationsChanged?.Invoke(true); |
|||
} |
|||
} |
|||
|
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
case WindowsMessage.WM_MOVE: |
|||
{ |
|||
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), |
|||
(short)(ToInt32(lParam) >> 16))); |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
case WindowsMessage.WM_GETMINMAXINFO: |
|||
{ |
|||
MINMAXINFO mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam); |
|||
|
|||
if (_minSize.Width > 0) |
|||
{ |
|||
mmi.ptMinTrackSize.X = |
|||
(int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); |
|||
} |
|||
|
|||
if (_minSize.Height > 0) |
|||
{ |
|||
mmi.ptMinTrackSize.Y = |
|||
(int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); |
|||
} |
|||
|
|||
if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) |
|||
{ |
|||
mmi.ptMaxTrackSize.X = |
|||
(int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); |
|||
} |
|||
|
|||
if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) |
|||
{ |
|||
mmi.ptMaxTrackSize.Y = |
|||
(int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); |
|||
} |
|||
|
|||
Marshal.StructureToPtr(mmi, lParam, true); |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
case WindowsMessage.WM_DISPLAYCHANGE: |
|||
{ |
|||
(Screen as ScreenImpl)?.InvalidateScreensCache(); |
|||
return IntPtr.Zero; |
|||
} |
|||
|
|||
case WindowsMessage.WM_KILLFOCUS: |
|||
LostFocus?.Invoke(); |
|||
break; |
|||
} |
|||
|
|||
#if USE_MANAGED_DRAG
|
|||
if (_managedDrag.PreprocessInputEvent(ref e)) |
|||
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); |
|||
#endif
|
|||
|
|||
if(shouldTakeFocus) |
|||
{ |
|||
SetFocus(_hwnd); |
|||
} |
|||
|
|||
if (e != null && Input != null) |
|||
{ |
|||
Input(e); |
|||
|
|||
if (e.Handled) |
|||
{ |
|||
return IntPtr.Zero; |
|||
} |
|||
} |
|||
|
|||
using (_rendererLock.Lock()) |
|||
{ |
|||
return DefWindowProc(hWnd, msg, wParam, lParam); |
|||
} |
|||
} |
|||
|
|||
private static int ToInt32(IntPtr ptr) |
|||
{ |
|||
if (IntPtr.Size == 4) |
|||
return ptr.ToInt32(); |
|||
|
|||
return (int)(ptr.ToInt64() & 0xffffffff); |
|||
} |
|||
|
|||
private static int HighWord(int param) => param >> 16; |
|||
|
|||
private Point DipFromLParam(IntPtr lParam) |
|||
{ |
|||
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; |
|||
} |
|||
|
|||
private PixelPoint PointFromLParam(IntPtr lParam) |
|||
{ |
|||
return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); |
|||
} |
|||
|
|||
private bool ShouldIgnoreTouchEmulatedMessage() |
|||
{ |
|||
if (!_multitouch) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// MI_WP_SIGNATURE
|
|||
// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages
|
|||
const long marker = 0xFF515700L; |
|||
|
|||
var info = GetMessageExtraInfo().ToInt64(); |
|||
return (info & marker) == marker; |
|||
} |
|||
|
|||
private static RawInputModifiers GetMouseModifiers(IntPtr wParam) |
|||
{ |
|||
var keys = (ModifierKeys)ToInt32(wParam); |
|||
var modifiers = WindowsKeyboardDevice.Instance.Modifiers; |
|||
|
|||
if (keys.HasFlagCustom(ModifierKeys.MK_LBUTTON)) |
|||
{ |
|||
modifiers |= RawInputModifiers.LeftMouseButton; |
|||
} |
|||
|
|||
if (keys.HasFlagCustom(ModifierKeys.MK_RBUTTON)) |
|||
{ |
|||
modifiers |= RawInputModifiers.RightMouseButton; |
|||
} |
|||
|
|||
if (keys.HasFlagCustom(ModifierKeys.MK_MBUTTON)) |
|||
{ |
|||
modifiers |= RawInputModifiers.MiddleMouseButton; |
|||
} |
|||
|
|||
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON1)) |
|||
{ |
|||
modifiers |= RawInputModifiers.XButton1MouseButton; |
|||
} |
|||
|
|||
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON2)) |
|||
{ |
|||
modifiers |= RawInputModifiers.XButton2MouseButton; |
|||
} |
|||
|
|||
return modifiers; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using static Avalonia.Win32.Interop.UnmanagedMethods; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Win32 |
|||
{ |
|||
public partial class WindowImpl |
|||
{ |
|||
// Hit test the frame for resizing and moving.
|
|||
HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam) |
|||
{ |
|||
// Get the point coordinates for the hit test.
|
|||
var ptMouse = PointFromLParam(lParam); |
|||
|
|||
// Get the window rectangle.
|
|||
GetWindowRect(hWnd, out var rcWindow); |
|||
|
|||
// Get the frame rectangle, adjusted for the style without a caption.
|
|||
RECT rcFrame = new RECT(); |
|||
AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0); |
|||
|
|||
RECT border_thickness = new RECT(); |
|||
if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME)) |
|||
{ |
|||
AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0); |
|||
border_thickness.left *= -1; |
|||
border_thickness.top *= -1; |
|||
} |
|||
else if (GetStyle().HasFlag(WindowStyles.WS_BORDER)) |
|||
{ |
|||
border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 }; |
|||
} |
|||
|
|||
if (_extendTitleBarHint >= 0) |
|||
{ |
|||
border_thickness.top = (int)(_extendedMargins.Top * Scaling); |
|||
} |
|||
|
|||
// Determine if the hit test is for resizing. Default middle (1,1).
|
|||
ushort uRow = 1; |
|||
ushort uCol = 1; |
|||
bool fOnResizeBorder = false; |
|||
|
|||
// Determine if the point is at the top or bottom of the window.
|
|||
if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top) |
|||
{ |
|||
fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top)); |
|||
uRow = 0; |
|||
} |
|||
else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom) |
|||
{ |
|||
uRow = 2; |
|||
} |
|||
|
|||
// Determine if the point is at the left or right of the window.
|
|||
if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left) |
|||
{ |
|||
uCol = 0; // left side
|
|||
} |
|||
else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right) |
|||
{ |
|||
uCol = 2; // right side
|
|||
} |
|||
|
|||
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
|
|||
HitTestValues[][] hitTests = new[] |
|||
{ |
|||
new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT }, |
|||
new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT }, |
|||
new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT }, |
|||
}; |
|||
|
|||
return hitTests[uRow][uCol]; |
|||
} |
|||
|
|||
protected virtual IntPtr CustomCaptionProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool callDwp) |
|||
{ |
|||
IntPtr lRet = IntPtr.Zero; |
|||
|
|||
callDwp = !DwmDefWindowProc(hWnd, msg, wParam, lParam, ref lRet); |
|||
|
|||
switch ((WindowsMessage)msg) |
|||
{ |
|||
case WindowsMessage.WM_DWMCOMPOSITIONCHANGED: |
|||
// TODO handle composition changed.
|
|||
break; |
|||
|
|||
case WindowsMessage.WM_NCHITTEST: |
|||
if (lRet == IntPtr.Zero) |
|||
{ |
|||
if(WindowState == WindowState.FullScreen) |
|||
{ |
|||
return (IntPtr)HitTestValues.HTCLIENT; |
|||
} |
|||
var hittestResult = HitTestNCA(hWnd, wParam, lParam); |
|||
|
|||
lRet = (IntPtr)hittestResult; |
|||
|
|||
uint timestamp = unchecked((uint)GetMessageTime()); |
|||
|
|||
if (hittestResult == HitTestValues.HTCAPTION) |
|||
{ |
|||
var position = PointToClient(PointFromLParam(lParam)); |
|||
|
|||
if (_owner is Window window) |
|||
{ |
|||
var visual = window.Renderer.HitTestFirst(position, _owner as Window, x => |
|||
{ |
|||
if (x is IInputElement ie && !ie.IsHitTestVisible) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
}); |
|||
|
|||
if (visual != null) |
|||
{ |
|||
hittestResult = HitTestValues.HTCLIENT; |
|||
lRet = (IntPtr)hittestResult; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (hittestResult != HitTestValues.HTNOWHERE) |
|||
{ |
|||
callDwp = false; |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
|
|||
return lRet; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue