committed by
GitHub
204 changed files with 6951 additions and 1335 deletions
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,160 @@ |
|||
#include "common.h" |
|||
|
|||
|
|||
IAvnNativeControlHostTopLevelAttachment* CreateAttachment(); |
|||
|
|||
class AvnNativeControlHost : |
|||
public ComSingleObject<IAvnNativeControlHost, &IID_IAvnNativeControlHost> |
|||
{ |
|||
public: |
|||
FORWARD_IUNKNOWN(); |
|||
NSView* View; |
|||
AvnNativeControlHost(NSView* view) |
|||
{ |
|||
View = view; |
|||
} |
|||
|
|||
virtual HRESULT CreateDefaultChild(void* parent, void** retOut) override |
|||
{ |
|||
NSView* view = [NSView new]; |
|||
[view setWantsLayer: true]; |
|||
|
|||
*retOut = (__bridge_retained void*)view; |
|||
return S_OK; |
|||
}; |
|||
|
|||
virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() override |
|||
{ |
|||
return ::CreateAttachment(); |
|||
}; |
|||
|
|||
virtual void DestroyDefaultChild(void* child) override |
|||
{ |
|||
// ARC will release the object for us |
|||
(__bridge_transfer NSView*) child; |
|||
} |
|||
}; |
|||
|
|||
class AvnNativeControlHostTopLevelAttachment : |
|||
public ComSingleObject<IAvnNativeControlHostTopLevelAttachment, &IID_IAvnNativeControlHostTopLevelAttachment> |
|||
{ |
|||
NSView* _holder; |
|||
NSView* _child; |
|||
public: |
|||
FORWARD_IUNKNOWN(); |
|||
|
|||
AvnNativeControlHostTopLevelAttachment() |
|||
{ |
|||
_holder = [NSView new]; |
|||
[_holder setWantsLayer:true]; |
|||
} |
|||
|
|||
virtual ~AvnNativeControlHostTopLevelAttachment() |
|||
{ |
|||
if(_child != nil && [_child superview] == _holder) |
|||
{ |
|||
[_child removeFromSuperview]; |
|||
} |
|||
|
|||
if([_holder superview] != nil) |
|||
{ |
|||
[_holder removeFromSuperview]; |
|||
} |
|||
} |
|||
|
|||
virtual void* GetParentHandle() override |
|||
{ |
|||
return (__bridge void*)_holder; |
|||
}; |
|||
|
|||
virtual HRESULT InitializeWithChildHandle(void* child) override |
|||
{ |
|||
if(_child != nil) |
|||
return E_FAIL; |
|||
_child = (__bridge NSView*)child; |
|||
if(_child == nil) |
|||
return E_FAIL; |
|||
[_holder addSubview:_child]; |
|||
[_child setHidden: false]; |
|||
return S_OK; |
|||
}; |
|||
|
|||
virtual HRESULT AttachTo(IAvnNativeControlHost* host) override |
|||
{ |
|||
if(host == nil) |
|||
{ |
|||
[_holder removeFromSuperview]; |
|||
[_holder setHidden: true]; |
|||
} |
|||
else |
|||
{ |
|||
AvnNativeControlHost* chost = dynamic_cast<AvnNativeControlHost*>(host); |
|||
if(chost == nil || chost->View == nil) |
|||
return E_FAIL; |
|||
[_holder setHidden:true]; |
|||
[chost->View addSubview:_holder]; |
|||
} |
|||
return S_OK; |
|||
}; |
|||
|
|||
virtual void ShowInBounds(float x, float y, float width, float height) override |
|||
{ |
|||
if(_child == nil) |
|||
return; |
|||
if(AvnInsidePotentialDeadlock::IsInside()) |
|||
{ |
|||
IAvnNativeControlHostTopLevelAttachment* slf = this; |
|||
slf->AddRef(); |
|||
dispatch_async(dispatch_get_main_queue(), ^{ |
|||
slf->ShowInBounds(x, y, width, height); |
|||
slf->Release(); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
NSRect childFrame = {0, 0, width, height}; |
|||
NSRect holderFrame = {x, y, width, height}; |
|||
|
|||
[_child setFrame: childFrame]; |
|||
[_holder setFrame: holderFrame]; |
|||
[_holder setHidden: false]; |
|||
if([_holder superview] != nil) |
|||
[[_holder superview] setNeedsDisplay:true]; |
|||
} |
|||
|
|||
virtual void HideWithSize(float width, float height) override |
|||
{ |
|||
if(_child == nil) |
|||
return; |
|||
if(AvnInsidePotentialDeadlock::IsInside()) |
|||
{ |
|||
IAvnNativeControlHostTopLevelAttachment* slf = this; |
|||
slf->AddRef(); |
|||
dispatch_async(dispatch_get_main_queue(), ^{ |
|||
slf->HideWithSize(width, height); |
|||
slf->Release(); |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
NSRect frame = {0, 0, width, height}; |
|||
[_holder setHidden: true]; |
|||
[_child setFrame: frame]; |
|||
} |
|||
|
|||
virtual void ReleaseChild() override |
|||
{ |
|||
[_child removeFromSuperview]; |
|||
_child = nil; |
|||
} |
|||
}; |
|||
|
|||
IAvnNativeControlHostTopLevelAttachment* CreateAttachment() |
|||
{ |
|||
return new AvnNativeControlHostTopLevelAttachment(); |
|||
} |
|||
|
|||
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent) |
|||
{ |
|||
return new AvnNativeControlHost(parent); |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
#include "common.h" |
|||
|
|||
static int Counter = 0; |
|||
AvnInsidePotentialDeadlock::AvnInsidePotentialDeadlock() |
|||
{ |
|||
Counter++; |
|||
} |
|||
|
|||
AvnInsidePotentialDeadlock::~AvnInsidePotentialDeadlock() |
|||
{ |
|||
Counter--; |
|||
} |
|||
|
|||
bool AvnInsidePotentialDeadlock::IsInside() |
|||
{ |
|||
return Counter!=0; |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8 |
|||
Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5 |
|||
@ -1,3 +1,11 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
|
|||
<ItemGroup> |
|||
<AvailableItemName Include="AvaloniaXaml" /> |
|||
<AvailableItemName Include="AvaloniaResource" /> |
|||
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)AvaloniaItemSchema.xaml" /> |
|||
</ItemGroup> |
|||
<ItemGroup Condition="'$(EnableDefaultItems)'=='True'"> |
|||
<AvaloniaXaml Include="**\*.axaml" SubType="Designer" /> |
|||
<AvaloniaXaml Include="**\*.paml" SubType="Designer" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -0,0 +1,18 @@ |
|||
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties"> |
|||
<ContentType |
|||
Name="AvaloniaXaml" |
|||
DisplayName="Avalonia XAML" |
|||
ItemType="AvaloniaXaml"> |
|||
<NameValuePair Name="DependentFileExtensions" Value=".cs" /> |
|||
<NameValuePair Name="DefaultMetadata_SubType" Value="Designer" /> |
|||
</ContentType> |
|||
|
|||
<ItemType Name="AvaloniaXaml" DisplayName="Avalonia XAML" /> |
|||
<FileExtension Name=".axaml" ContentType="AvaloniaXaml" /> |
|||
<FileExtension Name=".paml" ContentType="AvaloniaXaml" /> |
|||
<ContentType |
|||
Name="AvaloniaResource" |
|||
DisplayName="Avalonia Resource" |
|||
ItemType="AvaloniaResource" |
|||
/> |
|||
</ProjectSchemaDefinitions> |
|||
@ -0,0 +1,35 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.ScrollViewerPage"> |
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Classes="h1">ScrollViewer</TextBlock> |
|||
<TextBlock Classes="h2">Allows for horizontal and vertical content scrolling.</TextBlock> |
|||
|
|||
<Grid ColumnDefinitions="Auto, *"> |
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<ToggleSwitch IsChecked="{Binding AllowAutoHide}" Content="Allow auto hide" /> |
|||
|
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Text="Horizontal Scroll" /> |
|||
<ComboBox Items="{Binding AvailableVisibility}" SelectedItem="{Binding HorizontalScrollVisibility}" /> |
|||
</StackPanel> |
|||
|
|||
<StackPanel Orientation="Vertical" Spacing="4"> |
|||
<TextBlock Text="Vertical Scroll" /> |
|||
<ComboBox Items="{Binding AvailableVisibility}" SelectedItem="{Binding VerticalScrollVisibility}" /> |
|||
</StackPanel> |
|||
</StackPanel> |
|||
|
|||
<ScrollViewer x:Name="ScrollViewer" |
|||
Grid.Column="1" |
|||
Width="400" Height="400" |
|||
AllowAutoHide="{Binding AllowAutoHide}" |
|||
HorizontalScrollBarVisibility="{Binding HorizontalScrollVisibility}" |
|||
VerticalScrollBarVisibility="{Binding VerticalScrollVisibility}"> |
|||
<Image Width="800" Height="800" Stretch="UniformToFill" |
|||
Source="/Assets/delicate-arch-896885_640.jpg" /> |
|||
</ScrollViewer> |
|||
</Grid> |
|||
|
|||
</StackPanel> |
|||
</UserControl> |
|||
@ -0,0 +1,65 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Markup.Xaml; |
|||
using ReactiveUI; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class ScrollViewerPageViewModel : ReactiveObject |
|||
{ |
|||
private bool _allowAutoHide; |
|||
private ScrollBarVisibility _horizontalScrollVisibility; |
|||
private ScrollBarVisibility _verticalScrollVisibility; |
|||
|
|||
public ScrollViewerPageViewModel() |
|||
{ |
|||
AvailableVisibility = new List<ScrollBarVisibility> |
|||
{ |
|||
ScrollBarVisibility.Auto, |
|||
ScrollBarVisibility.Visible, |
|||
ScrollBarVisibility.Hidden, |
|||
ScrollBarVisibility.Disabled, |
|||
}; |
|||
|
|||
HorizontalScrollVisibility = ScrollBarVisibility.Auto; |
|||
VerticalScrollVisibility = ScrollBarVisibility.Auto; |
|||
AllowAutoHide = true; |
|||
} |
|||
|
|||
public bool AllowAutoHide |
|||
{ |
|||
get => _allowAutoHide; |
|||
set => this.RaiseAndSetIfChanged(ref _allowAutoHide, value); |
|||
} |
|||
|
|||
public ScrollBarVisibility HorizontalScrollVisibility |
|||
{ |
|||
get => _horizontalScrollVisibility; |
|||
set => this.RaiseAndSetIfChanged(ref _horizontalScrollVisibility, value); |
|||
} |
|||
|
|||
public ScrollBarVisibility VerticalScrollVisibility |
|||
{ |
|||
get => _verticalScrollVisibility; |
|||
set => this.RaiseAndSetIfChanged(ref _verticalScrollVisibility, value); |
|||
} |
|||
|
|||
public List<ScrollBarVisibility> AvailableVisibility { get; } |
|||
} |
|||
|
|||
public class ScrollViewerPage : UserControl |
|||
{ |
|||
public ScrollViewerPage() |
|||
{ |
|||
InitializeComponent(); |
|||
|
|||
DataContext = new ScrollViewerPageViewModel(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
<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.SplitViewPage"> |
|||
|
|||
<Border> |
|||
|
|||
<Grid ColumnDefinitions="*,225"> |
|||
|
|||
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="4" Margin="5"> |
|||
<ToggleButton Name="PaneOpenButton" |
|||
Content="IsPaneOpen" |
|||
IsChecked="{Binding IsPaneOpen, ElementName=SplitView}" /> |
|||
|
|||
<ToggleButton Name="UseLightDismissOverlayModeButton" |
|||
Content="UseLightDismissOverlayMode" |
|||
IsChecked="{Binding UseLightDismissOverlayMode, ElementName=SplitView}" /> |
|||
|
|||
<ToggleSwitch OffContent="Left" OnContent="Right" Content="Placement" IsChecked="{Binding !IsLeft}" /> |
|||
|
|||
<TextBlock Text="DisplayMode" /> |
|||
<ComboBox Name="DisplayModeSelector" Width="170" Margin="10" SelectedIndex="{Binding DisplayMode}"> |
|||
<ComboBoxItem>Inline</ComboBoxItem> |
|||
<ComboBoxItem>CompactInline</ComboBoxItem> |
|||
<ComboBoxItem>Overlay</ComboBoxItem> |
|||
<ComboBoxItem>CompactOverlay</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<TextBlock Text="PaneBackground" /> |
|||
<ComboBox Name="PaneBackgroundSelector" SelectedIndex="0" Width="170" Margin="10"> |
|||
<ComboBoxItem Tag="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}">SystemControlBackgroundChromeMediumLowBrush</ComboBoxItem> |
|||
<ComboBoxItem Tag="Red">Red</ComboBoxItem> |
|||
<ComboBoxItem Tag="Blue">Blue</ComboBoxItem> |
|||
<ComboBoxItem Tag="Green">Green</ComboBoxItem> |
|||
</ComboBox> |
|||
|
|||
<TextBlock Text="{Binding Value, ElementName=OpenPaneLengthSlider, StringFormat='{}OpenPaneLength: {0}'}" /> |
|||
<Slider Name="OpenPaneLengthSlider" Value="256" Minimum="128" Maximum="500" |
|||
Width="150" /> |
|||
|
|||
<TextBlock Text="{Binding Value, ElementName=CompactPaneLengthSlider, StringFormat='{}CompactPaneLength: {0}'}" /> |
|||
<Slider Name="CompactPaneLengthSlider" Value="48" Minimum="24" Maximum="128" |
|||
Width="150" /> |
|||
|
|||
</StackPanel> |
|||
|
|||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}" |
|||
BorderThickness="1"> |
|||
<!--{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}--> |
|||
<SplitView Name="SplitView" |
|||
PanePlacement="{Binding PanePlacement}" |
|||
PaneBackground="{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}" |
|||
OpenPaneLength="{Binding Value, ElementName=OpenPaneLengthSlider}" |
|||
CompactPaneLength="{Binding Value, ElementName=CompactPaneLengthSlider}" |
|||
DisplayMode="{Binding CurrentDisplayMode}"> |
|||
<SplitView.Pane> |
|||
<Grid> |
|||
<Grid.RowDefinitions> |
|||
<RowDefinition Height="Auto" /> |
|||
<RowDefinition Height="*" /> |
|||
<RowDefinition Height="Auto" /> |
|||
</Grid.RowDefinitions> |
|||
<TextBlock Text="PANE CONTENT" FontWeight="Bold" Name="PaneHeader" Margin="5,12,0,0" /> |
|||
<ListBoxItem Grid.Row="1" VerticalAlignment="Top" Margin="0 10"> |
|||
<StackPanel Orientation="Horizontal"> |
|||
<!--Path glyph from materialdesignicons.com--> |
|||
<Border Width="48"> |
|||
<Viewbox Width="24" Height="24" HorizontalAlignment="Left"> |
|||
<Canvas Width="24" Height="24"> |
|||
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" /> |
|||
</Canvas> |
|||
</Viewbox> |
|||
</Border> |
|||
<TextBlock Text="People" VerticalAlignment="Center" /> |
|||
</StackPanel> |
|||
</ListBoxItem> |
|||
<TextBlock Grid.Row="2" Text="Item at bottom" Margin="60,12" /> |
|||
</Grid> |
|||
</SplitView.Pane> |
|||
|
|||
<Grid> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|||
</Grid> |
|||
|
|||
</SplitView> |
|||
</Border> |
|||
|
|||
</Grid> |
|||
</Border> |
|||
|
|||
</UserControl> |
|||
@ -0,0 +1,21 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
using ControlCatalog.ViewModels; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class SplitViewPage : UserControl |
|||
{ |
|||
public SplitViewPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
DataContext = new SplitViewPageViewModel(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using ReactiveUI; |
|||
|
|||
namespace ControlCatalog.ViewModels |
|||
{ |
|||
public class SplitViewPageViewModel : ReactiveObject |
|||
{ |
|||
private bool _isLeft = true; |
|||
private int _displayMode = 3; //CompactOverlay
|
|||
|
|||
public bool IsLeft |
|||
{ |
|||
get => _isLeft; |
|||
set |
|||
{ |
|||
this.RaiseAndSetIfChanged(ref _isLeft, value); |
|||
this.RaisePropertyChanged(nameof(PanePlacement)); |
|||
} |
|||
} |
|||
|
|||
public int DisplayMode |
|||
{ |
|||
get => _displayMode; |
|||
set |
|||
{ |
|||
this.RaiseAndSetIfChanged(ref _displayMode, value); |
|||
this.RaisePropertyChanged(nameof(CurrentDisplayMode)); |
|||
} |
|||
} |
|||
|
|||
public SplitViewPanePlacement PanePlacement => _isLeft ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right; |
|||
|
|||
public SplitViewDisplayMode CurrentDisplayMode |
|||
{ |
|||
get |
|||
{ |
|||
if (Enum.IsDefined(typeof(SplitViewDisplayMode), _displayMode)) |
|||
{ |
|||
return (SplitViewDisplayMode)_displayMode; |
|||
} |
|||
return SplitViewDisplayMode.CompactOverlay; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
<Application xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="NativeEmbedSample.App"> |
|||
<Application.Styles> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/> |
|||
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/> |
|||
</Application.Styles> |
|||
</Application> |
|||
@ -0,0 +1,22 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace NativeEmbedSample |
|||
{ |
|||
public class App : Application |
|||
{ |
|||
public override void Initialize() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
public override void OnFrameworkInitializationCompleted() |
|||
{ |
|||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) |
|||
desktopLifetime.MainWindow = new MainWindow(); |
|||
|
|||
base.OnFrameworkInitializationCompleted(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.IO; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
using MonoMac.AppKit; |
|||
using MonoMac.Foundation; |
|||
using MonoMac.WebKit; |
|||
using Encoding = SharpDX.Text.Encoding; |
|||
|
|||
namespace NativeEmbedSample |
|||
{ |
|||
public class EmbedSample : NativeControlHost |
|||
{ |
|||
public bool IsSecond { get; set; } |
|||
private Process _mplayer; |
|||
|
|||
IPlatformHandle CreateLinux(IPlatformHandle parent) |
|||
{ |
|||
if (IsSecond) |
|||
{ |
|||
var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle); |
|||
if (chooser != null) |
|||
return chooser; |
|||
} |
|||
|
|||
var control = base.CreateNativeControlCore(parent); |
|||
var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName, |
|||
"..", |
|||
"nodes.mp4")); |
|||
_mplayer = Process.Start(new ProcessStartInfo("mplayer", |
|||
$"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"") |
|||
{ |
|||
UseShellExecute = false, |
|||
|
|||
}); |
|||
return control; |
|||
} |
|||
|
|||
void DestroyLinux(IPlatformHandle handle) |
|||
{ |
|||
_mplayer?.Kill(); |
|||
_mplayer = null; |
|||
base.DestroyNativeControlCore(handle); |
|||
} |
|||
|
|||
private const string RichText = |
|||
@"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
|
|||
{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;} |
|||
{\*\generator Riched20 6.3.9600}\viewkind4\uc1 |
|||
\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0 a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par |
|||
}";
|
|||
|
|||
IPlatformHandle CreateWin32(IPlatformHandle parent) |
|||
{ |
|||
WinApi.LoadLibrary("Msftedit.dll"); |
|||
var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W", |
|||
@"Rich Edit", |
|||
0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle, |
|||
IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero); |
|||
var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 }; |
|||
var text = RichText.Replace("<PREFIX>", IsSecond ? "\\qr " : ""); |
|||
var bytes = Encoding.UTF8.GetBytes(text); |
|||
WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes); |
|||
return new PlatformHandle(handle, "HWND"); |
|||
|
|||
} |
|||
|
|||
void DestroyWin32(IPlatformHandle handle) |
|||
{ |
|||
WinApi.DestroyWindow(handle.Handle); |
|||
} |
|||
|
|||
IPlatformHandle CreateOSX(IPlatformHandle parent) |
|||
{ |
|||
// Note: We are using MonoMac for example purposes
|
|||
// It shouldn't be used in production apps
|
|||
MacHelper.EnsureInitialized(); |
|||
|
|||
var webView = new WebView(); |
|||
Dispatcher.UIThread.Post(() => |
|||
{ |
|||
webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl( |
|||
IsSecond ? "https://bing.com": "https://google.com/"))); |
|||
}); |
|||
return new MacOSViewHandle(webView); |
|||
|
|||
} |
|||
|
|||
void DestroyOSX(IPlatformHandle handle) |
|||
{ |
|||
((MacOSViewHandle)handle).Dispose(); |
|||
} |
|||
|
|||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
|||
return CreateLinux(parent); |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
return CreateWin32(parent); |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|||
return CreateOSX(parent); |
|||
return base.CreateNativeControlCore(parent); |
|||
} |
|||
|
|||
protected override void DestroyNativeControlCore(IPlatformHandle control) |
|||
{ |
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) |
|||
DestroyLinux(control); |
|||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
DestroyWin32(control); |
|||
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|||
DestroyOSX(control); |
|||
else |
|||
base.DestroyNativeControlCore(control); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Platform.Interop; |
|||
using Avalonia.X11.NativeDialogs; |
|||
using static Avalonia.X11.NativeDialogs.Gtk; |
|||
using static Avalonia.X11.NativeDialogs.Glib; |
|||
namespace NativeEmbedSample |
|||
{ |
|||
public class GtkHelper |
|||
{ |
|||
private static Task<bool> s_gtkTask; |
|||
class FileChooser : INativeControlHostDestroyableControlHandle |
|||
{ |
|||
private readonly IntPtr _widget; |
|||
|
|||
public FileChooser(IntPtr widget, IntPtr xid) |
|||
{ |
|||
_widget = widget; |
|||
Handle = xid; |
|||
} |
|||
|
|||
public IntPtr Handle { get; } |
|||
public string HandleDescriptor => "XID"; |
|||
public void Destroy() |
|||
{ |
|||
RunOnGlibThread(() => |
|||
{ |
|||
gtk_widget_destroy(_widget); |
|||
return 0; |
|||
}).Wait(); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
public static IPlatformHandle CreateGtkFileChooser(IntPtr parentXid) |
|||
{ |
|||
if (s_gtkTask == null) |
|||
s_gtkTask = StartGtk(); |
|||
if (!s_gtkTask.Result) |
|||
return null; |
|||
return RunOnGlibThread(() => |
|||
{ |
|||
using (var title = new Utf8Buffer("Embedded")) |
|||
{ |
|||
var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder, |
|||
IntPtr.Zero); |
|||
gtk_widget_realize(widget); |
|||
var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget)); |
|||
gtk_window_present(widget); |
|||
return new FileChooser(widget, xid); |
|||
} |
|||
}).Result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using MonoMac.AppKit; |
|||
|
|||
namespace NativeEmbedSample |
|||
{ |
|||
public class MacHelper |
|||
{ |
|||
private static bool _isInitialized; |
|||
|
|||
public static void EnsureInitialized() |
|||
{ |
|||
if (_isInitialized) |
|||
return; |
|||
_isInitialized = true; |
|||
NSApplication.Init(); |
|||
} |
|||
} |
|||
|
|||
class MacOSViewHandle : IPlatformHandle, IDisposable |
|||
{ |
|||
private NSView _view; |
|||
|
|||
public MacOSViewHandle(NSView view) |
|||
{ |
|||
_view = view; |
|||
} |
|||
|
|||
public IntPtr Handle => _view?.Handle ?? IntPtr.Zero; |
|||
public string HandleDescriptor => "NSView"; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_view.Dispose(); |
|||
_view = null; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300" |
|||
Width="1024" Height="800" |
|||
Title="Native embedding sample" |
|||
xmlns:local="clr-namespace:NativeEmbedSample" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="NativeEmbedSample.MainWindow"> |
|||
<DockPanel> |
|||
<Menu DockPanel.Dock="Top"> |
|||
<MenuItem Header="Test"> |
|||
<MenuItem Header="SubMenu"> |
|||
<MenuItem Header="Item 1"/> |
|||
<MenuItem Header="Item 2"/> |
|||
<MenuItem Header="Item 3"/> |
|||
</MenuItem> |
|||
<MenuItem Header="Item 1"/> |
|||
<MenuItem Header="Item 2"/> |
|||
<MenuItem Header="Item 3"/> |
|||
</MenuItem> |
|||
</Menu> |
|||
<DockPanel DockPanel.Dock="Top"> |
|||
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button> |
|||
<Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button> |
|||
<Border DockPanel.Dock="Right" Background="#c0c0c0"> |
|||
<ToolTip.Tip> |
|||
<ToolTip> |
|||
<TextBlock>Text</TextBlock> |
|||
</ToolTip> |
|||
</ToolTip.Tip> |
|||
<TextBlock>Tooltip</TextBlock> |
|||
</Border> |
|||
<TextBox Text="Lorem ipsum dolor sit amet"/> |
|||
|
|||
</DockPanel> |
|||
<Grid ColumnDefinitions="*,5,*"> |
|||
<DockPanel> |
|||
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top"> |
|||
<CheckBox x:Name="firstVisible" IsChecked="True"/> |
|||
<TextBlock>Visible</TextBlock> |
|||
</StackPanel> |
|||
<local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/> |
|||
</DockPanel> |
|||
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" /> |
|||
<DockPanel Grid.Column="2"> |
|||
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top"> |
|||
<CheckBox x:Name="secondVisible" IsChecked="True"/> |
|||
<TextBlock>Visible</TextBlock> |
|||
</StackPanel> |
|||
<local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/> |
|||
</DockPanel> |
|||
</Grid> |
|||
</DockPanel> |
|||
</Window> |
|||
@ -0,0 +1,36 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace NativeEmbedSample |
|||
{ |
|||
public class MainWindow : Window |
|||
{ |
|||
public MainWindow() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
this.AttachDevTools(); |
|||
} |
|||
|
|||
public async void ShowPopupDelay(object sender, RoutedEventArgs args) |
|||
{ |
|||
await Task.Delay(3000); |
|||
ShowPopup(sender, args); |
|||
} |
|||
|
|||
public void ShowPopup(object sender, RoutedEventArgs args) |
|||
{ |
|||
|
|||
new ContextMenu() |
|||
{ |
|||
Items = new List<MenuItem> |
|||
{ |
|||
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" } |
|||
} |
|||
}.Open((Control)sender); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<OutputType>Exe</OutputType> |
|||
<TargetFramework>netcoreapp2.0</TargetFramework> |
|||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" /> |
|||
<ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" /> |
|||
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> |
|||
<ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> |
|||
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" /> |
|||
|
|||
<AvaloniaResource Include="**\*.xaml"> |
|||
<SubType>Designer</SubType> |
|||
</AvaloniaResource> |
|||
<None Remove="nodes.mp4" /> |
|||
<Content Include="nodes.mp4"> |
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
|||
</Content> |
|||
<Compile Include="..\..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\..\build\SampleApp.props" /> |
|||
<Import Project="..\..\..\build\BuildTargets.targets" /> |
|||
<Import Project="..\..\..\build\ReferenceCoreLibraries.props" /> |
|||
</Project> |
|||
@ -0,0 +1,17 @@ |
|||
using Avalonia; |
|||
|
|||
namespace NativeEmbedSample |
|||
{ |
|||
class Program |
|||
{ |
|||
static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); |
|||
|
|||
public static AppBuilder BuildAvaloniaApp() |
|||
=> AppBuilder.Configure<App>() |
|||
.With(new AvaloniaNativePlatformOptions() |
|||
{ |
|||
}) |
|||
.UsePlatformDetect(); |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace NativeEmbedSample |
|||
{ |
|||
public unsafe class WinApi |
|||
{ |
|||
public enum CommonControls : uint |
|||
{ |
|||
ICC_LISTVIEW_CLASSES = 0x00000001, // listview, header
|
|||
ICC_TREEVIEW_CLASSES = 0x00000002, // treeview, tooltips
|
|||
ICC_BAR_CLASSES = 0x00000004, // toolbar, statusbar, trackbar, tooltips
|
|||
ICC_TAB_CLASSES = 0x00000008, // tab, tooltips
|
|||
ICC_UPDOWN_CLASS = 0x00000010, // updown
|
|||
ICC_PROGRESS_CLASS = 0x00000020, // progress
|
|||
ICC_HOTKEY_CLASS = 0x00000040, // hotkey
|
|||
ICC_ANIMATE_CLASS = 0x00000080, // animate
|
|||
ICC_WIN95_CLASSES = 0x000000FF, |
|||
ICC_DATE_CLASSES = 0x00000100, // month picker, date picker, time picker, updown
|
|||
ICC_USEREX_CLASSES = 0x00000200, // comboex
|
|||
ICC_COOL_CLASSES = 0x00000400, // rebar (coolbar) control
|
|||
ICC_INTERNET_CLASSES = 0x00000800, |
|||
ICC_PAGESCROLLER_CLASS = 0x00001000, // page scroller
|
|||
ICC_NATIVEFNTCTL_CLASS = 0x00002000, // native font control
|
|||
ICC_STANDARD_CLASSES = 0x00004000, |
|||
ICC_LINK_CLASS = 0x00008000 |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct INITCOMMONCONTROLSEX |
|||
{ |
|||
public int dwSize; |
|||
public uint dwICC; |
|||
} |
|||
|
|||
[DllImport("Comctl32.dll")] |
|||
public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init); |
|||
|
|||
[DllImport("user32.dll", SetLastError = true)] |
|||
public static extern bool DestroyWindow(IntPtr hwnd); |
|||
|
|||
[DllImport("kernel32.dll")] |
|||
public static extern IntPtr LoadLibrary(string lib); |
|||
|
|||
|
|||
[DllImport("kernel32.dll")] |
|||
public static extern IntPtr GetModuleHandle(string lpModuleName); |
|||
|
|||
[DllImport("user32.dll", SetLastError = true)] |
|||
public static extern IntPtr CreateWindowEx( |
|||
int dwExStyle, |
|||
string lpClassName, |
|||
string lpWindowName, |
|||
uint dwStyle, |
|||
int x, |
|||
int y, |
|||
int nWidth, |
|||
int nHeight, |
|||
IntPtr hWndParent, |
|||
IntPtr hMenu, |
|||
IntPtr hInstance, |
|||
IntPtr lpParam); |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct SETTEXTEX |
|||
{ |
|||
public uint Flags; |
|||
public uint Codepage; |
|||
} |
|||
|
|||
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")] |
|||
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam); |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
nodes.mp4 by beeple is licensed under the creative commons license, downloaded from https://vimeo.com/9936271 |
|||
Binary file not shown.
@ -0,0 +1,85 @@ |
|||
namespace Avalonia.Animation.Easings |
|||
{ |
|||
/// <summary>
|
|||
/// Eases a <see cref="double"/> value
|
|||
/// using a user-defined cubic bezier curve.
|
|||
/// Good for custom easing functions that doesn't quite
|
|||
/// fit with the built-in ones.
|
|||
/// </summary>
|
|||
public class SplineEasing : Easing |
|||
{ |
|||
/// <summary>
|
|||
/// X coordinate of the first control point
|
|||
/// </summary>
|
|||
public double X1 |
|||
{ |
|||
get => _internalKeySpline.ControlPointX1; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointX1 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Y coordinate of the first control point
|
|||
/// </summary>
|
|||
public double Y1 |
|||
{ |
|||
get => _internalKeySpline.ControlPointY1; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointY1 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// X coordinate of the second control point
|
|||
/// </summary>
|
|||
public double X2 |
|||
{ |
|||
get => _internalKeySpline.ControlPointX2; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointX2 = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Y coordinate of the second control point
|
|||
/// </summary>
|
|||
public double Y2 |
|||
{ |
|||
get => _internalKeySpline.ControlPointY2; |
|||
set |
|||
{ |
|||
_internalKeySpline.ControlPointY2 = value; |
|||
} |
|||
} |
|||
|
|||
private readonly KeySpline _internalKeySpline; |
|||
|
|||
public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d) |
|||
{ |
|||
_internalKeySpline = new KeySpline(); |
|||
|
|||
this.X1 = x1; |
|||
this.Y1 = y1; |
|||
this.X2 = x2; |
|||
this.Y1 = y2; |
|||
} |
|||
|
|||
public SplineEasing(KeySpline keySpline) |
|||
{ |
|||
_internalKeySpline = keySpline; |
|||
} |
|||
|
|||
public SplineEasing() |
|||
{ |
|||
_internalKeySpline = new KeySpline(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override double Ease(double progress) => |
|||
_internalKeySpline.GetSplineProgress(progress); |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
|
|||
// Ported from WPF open-source code.
|
|||
// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
|
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Converts string values to <see cref="KeySpline"/> values
|
|||
/// </summary>
|
|||
public class KeySplineTypeConverter : TypeConverter |
|||
{ |
|||
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) |
|||
{ |
|||
return sourceType == typeof(string); |
|||
} |
|||
|
|||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) |
|||
{ |
|||
return KeySpline.Parse((string)value, culture); |
|||
} |
|||
} |
|||
} |
|||
@ -1,9 +1,29 @@ |
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Specifies a contract for a scrolling control that supports scroll anchoring.
|
|||
/// </summary>
|
|||
public interface IScrollAnchorProvider |
|||
{ |
|||
/// <summary>
|
|||
/// The currently chosen anchor element to use for scroll anchoring.
|
|||
/// </summary>
|
|||
IControl CurrentAnchor { get; } |
|||
|
|||
/// <summary>
|
|||
/// Registers a control as a potential scroll anchor candidate.
|
|||
/// </summary>
|
|||
/// <param name="element">
|
|||
/// A control within the subtree of the <see cref="IScrollAnchorProvider"/>.
|
|||
/// </param>
|
|||
void RegisterAnchorCandidate(IControl element); |
|||
|
|||
/// <summary>
|
|||
/// Unregisters a control as a potential scroll anchor candidate.
|
|||
/// </summary>
|
|||
/// <param name="element">
|
|||
/// A control within the subtree of the <see cref="IScrollAnchorProvider"/>.
|
|||
/// </param>
|
|||
void UnregisterAnchorCandidate(IControl element); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,198 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class NativeControlHost : Control |
|||
{ |
|||
private TopLevel _currentRoot; |
|||
private INativeControlHostImpl _currentHost; |
|||
private INativeControlHostControlTopLevelAttachment _attachment; |
|||
private IPlatformHandle _nativeControlHandle; |
|||
private bool _queuedForDestruction; |
|||
private bool _queuedForMoveResize; |
|||
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>(); |
|||
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler; |
|||
static NativeControlHost() |
|||
{ |
|||
IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged); |
|||
} |
|||
|
|||
public NativeControlHost() |
|||
{ |
|||
_propertyChangedHandler = PropertyChangedHandler; |
|||
} |
|||
|
|||
private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) |
|||
=> host.UpdateHost(); |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
_currentRoot = e.Root as TopLevel; |
|||
var visual = (IVisual)this; |
|||
while (visual != _currentRoot) |
|||
{ |
|||
|
|||
if (visual is Visual v) |
|||
{ |
|||
v.PropertyChanged += _propertyChangedHandler; |
|||
_propertyChangedSubscriptions.Add(v); |
|||
} |
|||
|
|||
visual = visual.GetVisualParent(); |
|||
} |
|||
|
|||
UpdateHost(); |
|||
} |
|||
|
|||
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.IsEffectiveValueChange && e.Property == BoundsProperty) |
|||
EnqueueForMoveResize(); |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
_currentRoot = null; |
|||
if (_propertyChangedSubscriptions != null) |
|||
{ |
|||
foreach (var v in _propertyChangedSubscriptions) |
|||
v.PropertyChanged -= _propertyChangedHandler; |
|||
_propertyChangedSubscriptions.Clear(); |
|||
} |
|||
UpdateHost(); |
|||
} |
|||
|
|||
|
|||
private void UpdateHost() |
|||
{ |
|||
_queuedForMoveResize = false; |
|||
_currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost; |
|||
var needsAttachment = _currentHost != null; |
|||
|
|||
if (needsAttachment) |
|||
{ |
|||
// If there is an existing attachment, ensure that we are attached to the proper host or destroy the attachment
|
|||
if (_attachment != null && _attachment.AttachedTo != _currentHost) |
|||
{ |
|||
if (_attachment != null) |
|||
{ |
|||
if (_attachment.IsCompatibleWith(_currentHost)) |
|||
{ |
|||
_attachment.AttachedTo = _currentHost; |
|||
} |
|||
else |
|||
{ |
|||
_attachment.Dispose(); |
|||
_attachment = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// If there is no attachment, but the control exists,
|
|||
// attempt to attach to to the current toplevel or destroy the control if it's incompatible
|
|||
if (_attachment == null && _nativeControlHandle != null) |
|||
{ |
|||
if (_currentHost.IsCompatibleWith(_nativeControlHandle)) |
|||
_attachment = _currentHost.CreateNewAttachment(_nativeControlHandle); |
|||
else |
|||
DestroyNativeControl(); |
|||
} |
|||
|
|||
// There is no control handle an no attachment, create both
|
|||
if (_nativeControlHandle == null) |
|||
{ |
|||
_attachment = _currentHost.CreateNewAttachment(parent => |
|||
_nativeControlHandle = CreateNativeControlCore(parent)); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Immediately detach the control from the current toplevel if there is an existing attachment
|
|||
if (_attachment != null) |
|||
_attachment.AttachedTo = null; |
|||
|
|||
// Don't destroy the control immediately, it might be just being reparented to another TopLevel
|
|||
if (_nativeControlHandle != null && !_queuedForDestruction) |
|||
{ |
|||
_queuedForDestruction = true; |
|||
Dispatcher.UIThread.Post(CheckDestruction, DispatcherPriority.Background); |
|||
} |
|||
} |
|||
|
|||
if (_attachment?.AttachedTo != _currentHost) |
|||
return; |
|||
|
|||
TryUpdateNativeControlPosition(); |
|||
} |
|||
|
|||
|
|||
private Rect? GetAbsoluteBounds() |
|||
{ |
|||
var bounds = Bounds; |
|||
var position = this.TranslatePoint(bounds.Position, _currentRoot); |
|||
if (position == null) |
|||
return null; |
|||
return new Rect(position.Value, bounds.Size); |
|||
} |
|||
|
|||
void EnqueueForMoveResize() |
|||
{ |
|||
if(_queuedForMoveResize) |
|||
return; |
|||
_queuedForMoveResize = true; |
|||
Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render); |
|||
} |
|||
|
|||
public bool TryUpdateNativeControlPosition() |
|||
{ |
|||
if (_currentHost == null) |
|||
return false; |
|||
|
|||
var bounds = GetAbsoluteBounds(); |
|||
var needsShow = IsEffectivelyVisible && bounds.HasValue; |
|||
|
|||
if (needsShow) |
|||
_attachment?.ShowInBounds(bounds.Value); |
|||
else |
|||
_attachment?.HideWithSize(Bounds.Size); |
|||
return false; |
|||
} |
|||
|
|||
private void CheckDestruction() |
|||
{ |
|||
_queuedForDestruction = false; |
|||
if (_currentRoot == null) |
|||
DestroyNativeControl(); |
|||
} |
|||
|
|||
protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) |
|||
{ |
|||
if (_currentHost == null) |
|||
throw new InvalidOperationException(); |
|||
return _currentHost.CreateDefaultChild(parent); |
|||
} |
|||
|
|||
private void DestroyNativeControl() |
|||
{ |
|||
if (_nativeControlHandle != null) |
|||
{ |
|||
_attachment?.Dispose(); |
|||
_attachment = null; |
|||
|
|||
DestroyNativeControlCore(_nativeControlHandle); |
|||
_nativeControlHandle = null; |
|||
} |
|||
} |
|||
|
|||
protected virtual void DestroyNativeControlCore(IPlatformHandle control) |
|||
{ |
|||
((INativeControlHostDestroyableControlHandle)control).Destroy(); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a platform-specific embeddable window implementation.
|
|||
/// </summary>
|
|||
public interface IEmbeddableWindowImpl : ITopLevelImpl |
|||
{ |
|||
event Action LostFocus; |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.Platform |
|||
{ |
|||
public interface INativeControlHostImpl |
|||
{ |
|||
INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent); |
|||
INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create); |
|||
INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle); |
|||
bool IsCompatibleWith(IPlatformHandle handle); |
|||
} |
|||
|
|||
public interface INativeControlHostDestroyableControlHandle : IPlatformHandle |
|||
{ |
|||
void Destroy(); |
|||
} |
|||
|
|||
public interface INativeControlHostControlTopLevelAttachment : IDisposable |
|||
{ |
|||
INativeControlHostImpl AttachedTo { get; set; } |
|||
bool IsCompatibleWith(INativeControlHostImpl host); |
|||
void HideWithSize(Size size); |
|||
void ShowInBounds(Rect rect); |
|||
} |
|||
|
|||
public interface ITopLevelImplWithNativeControlHost |
|||
{ |
|||
INativeControlHostImpl NativeControlHost { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,487 @@ |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Media; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Defines constants for how the SplitView Pane should display
|
|||
/// </summary>
|
|||
public enum SplitViewDisplayMode |
|||
{ |
|||
/// <summary>
|
|||
/// Pane is displayed next to content, and does not auto collapse
|
|||
/// when tapped outside
|
|||
/// </summary>
|
|||
Inline, |
|||
/// <summary>
|
|||
/// Pane is displayed next to content. When collapsed, pane is still
|
|||
/// visible according to CompactPaneLength. Pane does not auto collapse
|
|||
/// when tapped outside
|
|||
/// </summary>
|
|||
CompactInline, |
|||
/// <summary>
|
|||
/// Pane is displayed above content. Pane collapses when tapped outside
|
|||
/// </summary>
|
|||
Overlay, |
|||
/// <summary>
|
|||
/// Pane is displayed above content. When collapsed, pane is still
|
|||
/// visible according to CompactPaneLength. Pane collapses when tapped outside
|
|||
/// </summary>
|
|||
CompactOverlay |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines constants for where the Pane should appear
|
|||
/// </summary>
|
|||
public enum SplitViewPanePlacement |
|||
{ |
|||
Left, |
|||
Right |
|||
} |
|||
|
|||
public class SplitViewTemplateSettings : AvaloniaObject |
|||
{ |
|||
internal SplitViewTemplateSettings() { } |
|||
|
|||
public static readonly StyledProperty<double> ClosedPaneWidthProperty = |
|||
AvaloniaProperty.Register<SplitViewTemplateSettings, double>(nameof(ClosedPaneWidth), 0d); |
|||
|
|||
public static readonly StyledProperty<GridLength> PaneColumnGridLengthProperty = |
|||
AvaloniaProperty.Register<SplitViewTemplateSettings, GridLength>(nameof(PaneColumnGridLength)); |
|||
|
|||
public double ClosedPaneWidth |
|||
{ |
|||
get => GetValue(ClosedPaneWidthProperty); |
|||
internal set => SetValue(ClosedPaneWidthProperty, value); |
|||
} |
|||
|
|||
public GridLength PaneColumnGridLength |
|||
{ |
|||
get => GetValue(PaneColumnGridLengthProperty); |
|||
internal set => SetValue(PaneColumnGridLengthProperty, value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A control with two views: A collapsible pane and an area for content
|
|||
/// </summary>
|
|||
public class SplitView : TemplatedControl |
|||
{ |
|||
/* |
|||
Pseudo classes & combos |
|||
:open / :closed |
|||
:compactoverlay :compactinline :overlay :inline |
|||
:left :right |
|||
*/ |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Content"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IControl> ContentProperty = |
|||
AvaloniaProperty.Register<SplitView, IControl>(nameof(Content)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CompactPaneLength"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> CompactPaneLengthProperty = |
|||
AvaloniaProperty.Register<SplitView, double>(nameof(CompactPaneLength), defaultValue: 48); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="DisplayMode"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewDisplayMode> DisplayModeProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewDisplayMode>(nameof(DisplayMode), defaultValue: SplitViewDisplayMode.Overlay); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="IsPaneOpen"/> property
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<SplitView, bool> IsPaneOpenProperty = |
|||
AvaloniaProperty.RegisterDirect<SplitView, bool>(nameof(IsPaneOpen), |
|||
x => x.IsPaneOpen, (x, v) => x.IsPaneOpen = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="OpenPaneLength"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<double> OpenPaneLengthProperty = |
|||
AvaloniaProperty.Register<SplitView, double>(nameof(OpenPaneLength), defaultValue: 320); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PaneBackground"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IBrush> PaneBackgroundProperty = |
|||
AvaloniaProperty.Register<SplitView, IBrush>(nameof(PaneBackground)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PanePlacement"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewPanePlacement> PanePlacementProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewPanePlacement>(nameof(PanePlacement)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="Pane"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IControl> PaneProperty = |
|||
AvaloniaProperty.Register<SplitView, IControl>(nameof(Pane)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="UseLightDismissOverlayMode"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<bool> UseLightDismissOverlayModeProperty = |
|||
AvaloniaProperty.Register<SplitView, bool>(nameof(UseLightDismissOverlayMode)); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="TemplateSettings"/> property
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<SplitViewTemplateSettings> TemplateSettingsProperty = |
|||
AvaloniaProperty.Register<SplitView, SplitViewTemplateSettings>(nameof(TemplateSettings)); |
|||
|
|||
private bool _isPaneOpen; |
|||
private Panel _pane; |
|||
private CompositeDisposable _pointerDisposables; |
|||
|
|||
public SplitView() |
|||
{ |
|||
PseudoClasses.Add(":overlay"); |
|||
PseudoClasses.Add(":left"); |
|||
|
|||
TemplateSettings = new SplitViewTemplateSettings(); |
|||
} |
|||
|
|||
static SplitView() |
|||
{ |
|||
UseLightDismissOverlayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnUseLightDismissChanged(v)); |
|||
CompactPaneLengthProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnCompactPaneLengthChanged(v)); |
|||
PanePlacementProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnPanePlacementChanged(v)); |
|||
DisplayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnDisplayModeChanged(v)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the content of the SplitView
|
|||
/// </summary>
|
|||
[Content] |
|||
public IControl Content |
|||
{ |
|||
get => GetValue(ContentProperty); |
|||
set => SetValue(ContentProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the length of the pane when in <see cref="SplitViewDisplayMode.CompactOverlay"/>
|
|||
/// or <see cref="SplitViewDisplayMode.CompactInline"/> mode
|
|||
/// </summary>
|
|||
public double CompactPaneLength |
|||
{ |
|||
get => GetValue(CompactPaneLengthProperty); |
|||
set => SetValue(CompactPaneLengthProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="SplitViewDisplayMode"/> for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewDisplayMode DisplayMode |
|||
{ |
|||
get => GetValue(DisplayModeProperty); |
|||
set => SetValue(DisplayModeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether the pane is open or closed
|
|||
/// </summary>
|
|||
public bool IsPaneOpen |
|||
{ |
|||
get => _isPaneOpen; |
|||
set |
|||
{ |
|||
if (value == _isPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (value) |
|||
{ |
|||
OnPaneOpening(this, null); |
|||
SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value); |
|||
|
|||
PseudoClasses.Add(":open"); |
|||
PseudoClasses.Remove(":closed"); |
|||
OnPaneOpened(this, null); |
|||
} |
|||
else |
|||
{ |
|||
SplitViewPaneClosingEventArgs args = new SplitViewPaneClosingEventArgs(false); |
|||
OnPaneClosing(this, args); |
|||
if (!args.Cancel) |
|||
{ |
|||
SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value); |
|||
|
|||
PseudoClasses.Add(":closed"); |
|||
PseudoClasses.Remove(":open"); |
|||
OnPaneClosed(this, null); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the length of the pane when open
|
|||
/// </summary>
|
|||
public double OpenPaneLength |
|||
{ |
|||
get => GetValue(OpenPaneLengthProperty); |
|||
set => SetValue(OpenPaneLengthProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the background of the pane
|
|||
/// </summary>
|
|||
public IBrush PaneBackground |
|||
{ |
|||
get => GetValue(PaneBackgroundProperty); |
|||
set => SetValue(PaneBackgroundProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="SplitViewPanePlacement"/> for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewPanePlacement PanePlacement |
|||
{ |
|||
get => GetValue(PanePlacementProperty); |
|||
set => SetValue(PanePlacementProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the Pane for the SplitView
|
|||
/// </summary>
|
|||
public IControl Pane |
|||
{ |
|||
get => GetValue(PaneProperty); |
|||
set => SetValue(PaneProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets whether WinUI equivalent LightDismissOverlayMode is enabled
|
|||
/// <para>When enabled, and the pane is open in Overlay or CompactOverlay mode,
|
|||
/// the contents of the splitview are darkened to visually separate the open pane
|
|||
/// and the rest of the SplitView</para>
|
|||
/// </summary>
|
|||
public bool UseLightDismissOverlayMode |
|||
{ |
|||
get => GetValue(UseLightDismissOverlayModeProperty); |
|||
set => SetValue(UseLightDismissOverlayModeProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the TemplateSettings for the SplitView
|
|||
/// </summary>
|
|||
public SplitViewTemplateSettings TemplateSettings |
|||
{ |
|||
get => GetValue(TemplateSettingsProperty); |
|||
set => SetValue(TemplateSettingsProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is closed
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneClosed; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is closing
|
|||
/// </summary>
|
|||
public event EventHandler<SplitViewPaneClosingEventArgs> PaneClosing; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is opened
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneOpened; |
|||
|
|||
/// <summary>
|
|||
/// Fired when the pane is opening
|
|||
/// </summary>
|
|||
public event EventHandler<EventArgs> PaneOpening; |
|||
|
|||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|||
{ |
|||
base.OnApplyTemplate(e); |
|||
_pane = e.NameScope.Find<Panel>("PART_PaneRoot"); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
var topLevel = this.VisualRoot; |
|||
if (topLevel is Window window) |
|||
{ |
|||
//Logic adapted from Popup
|
|||
//Basically if we're using an overlay DisplayMode, close the pane if we don't click on the pane
|
|||
IDisposable subscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler, |
|||
Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe) |
|||
{ |
|||
subscribe(target, handler); |
|||
return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler)); |
|||
} |
|||
|
|||
_pointerDisposables = new CompositeDisposable( |
|||
window.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel), |
|||
InputManager.Instance?.Process.Subscribe(OnNonClientClick), |
|||
subscribeToEventHandler<Window, EventHandler>(window, Window_Deactivated, |
|||
(x, handler) => x.Deactivated += handler, (x, handler) => x.Deactivated -= handler), |
|||
subscribeToEventHandler<IWindowImpl, Action>(window.PlatformImpl, OnWindowLostFocus, |
|||
(x, handler) => x.LostFocus += handler, (x, handler) => x.LostFocus -= handler)); |
|||
} |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnDetachedFromVisualTree(e); |
|||
_pointerDisposables?.Dispose(); |
|||
} |
|||
|
|||
private void OnWindowLostFocus() |
|||
{ |
|||
if (IsPaneOpen && ShouldClosePane()) |
|||
{ |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private void PointerPressedOutside(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
if (!IsPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
//If we click within the Pane, don't do anything
|
|||
//Otherwise, ClosePane if open & using an overlay display mode
|
|||
bool closePane = ShouldClosePane(); |
|||
if (!closePane) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var src = e.Source as IVisual; |
|||
while (src != null) |
|||
{ |
|||
if (src == _pane) |
|||
{ |
|||
closePane = false; |
|||
break; |
|||
} |
|||
|
|||
src = src.VisualParent; |
|||
} |
|||
if (closePane) |
|||
{ |
|||
IsPaneOpen = false; |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
|
|||
private void OnNonClientClick(RawInputEventArgs obj) |
|||
{ |
|||
if (!IsPaneOpen) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var mouse = obj as RawPointerEventArgs; |
|||
if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown) |
|||
|
|||
{ |
|||
if (ShouldClosePane()) |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private void Window_Deactivated(object sender, EventArgs e) |
|||
{ |
|||
if (IsPaneOpen && ShouldClosePane()) |
|||
{ |
|||
IsPaneOpen = false; |
|||
} |
|||
} |
|||
|
|||
private bool ShouldClosePane() |
|||
{ |
|||
return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay); |
|||
} |
|||
|
|||
protected virtual void OnPaneOpening(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneOpening?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneOpened(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneOpened?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneClosing(SplitView sender, SplitViewPaneClosingEventArgs args) |
|||
{ |
|||
PaneClosing?.Invoke(sender, args); |
|||
} |
|||
|
|||
protected virtual void OnPaneClosed(SplitView sender, EventArgs args) |
|||
{ |
|||
PaneClosed?.Invoke(sender, args); |
|||
} |
|||
|
|||
private void OnCompactPaneLengthChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var newLen = (double)e.NewValue; |
|||
var displayMode = DisplayMode; |
|||
if (displayMode == SplitViewDisplayMode.CompactInline) |
|||
{ |
|||
TemplateSettings.ClosedPaneWidth = newLen; |
|||
} |
|||
else if (displayMode == SplitViewDisplayMode.CompactOverlay) |
|||
{ |
|||
TemplateSettings.ClosedPaneWidth = newLen; |
|||
TemplateSettings.PaneColumnGridLength = new GridLength(newLen, GridUnitType.Pixel); |
|||
} |
|||
} |
|||
|
|||
private void OnPanePlacementChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var oldState = e.OldValue.ToString().ToLower(); |
|||
var newState = e.NewValue.ToString().ToLower(); |
|||
PseudoClasses.Remove($":{oldState}"); |
|||
PseudoClasses.Add($":{newState}"); |
|||
} |
|||
|
|||
private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var oldState = e.OldValue.ToString().ToLower(); |
|||
var newState = e.NewValue.ToString().ToLower(); |
|||
|
|||
PseudoClasses.Remove($":{oldState}"); |
|||
PseudoClasses.Add($":{newState}"); |
|||
|
|||
var (closedPaneWidth, paneColumnGridLength) = (SplitViewDisplayMode)e.NewValue switch |
|||
{ |
|||
SplitViewDisplayMode.Overlay => (0, new GridLength(0, GridUnitType.Pixel)), |
|||
SplitViewDisplayMode.CompactOverlay => (CompactPaneLength, new GridLength(CompactPaneLength, GridUnitType.Pixel)), |
|||
SplitViewDisplayMode.Inline => (0, new GridLength(0, GridUnitType.Auto)), |
|||
SplitViewDisplayMode.CompactInline => (CompactPaneLength, new GridLength(0, GridUnitType.Auto)), |
|||
_ => throw new NotImplementedException(), |
|||
}; |
|||
TemplateSettings.ClosedPaneWidth = closedPaneWidth; |
|||
TemplateSettings.PaneColumnGridLength = paneColumnGridLength; |
|||
} |
|||
|
|||
private void OnUseLightDismissChanged(AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
var mode = (bool)e.NewValue; |
|||
PseudoClasses.Set(":lightdismiss", mode); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class SplitViewPaneClosingEventArgs : EventArgs |
|||
{ |
|||
public bool Cancel { get; set; } |
|||
|
|||
public SplitViewPaneClosingEventArgs(bool cancel) |
|||
{ |
|||
Cancel = cancel; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using System.ComponentModel; |
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable |
|||
{ |
|||
private AvaloniaList<TreeNode> _inner; |
|||
|
|||
public TreeNodeCollection(TreeNode owner) => Owner = owner; |
|||
|
|||
public TreeNode this[int index] |
|||
{ |
|||
get |
|||
{ |
|||
EnsureInitialized(); |
|||
return _inner[index]; |
|||
} |
|||
} |
|||
|
|||
public int Count |
|||
{ |
|||
get |
|||
{ |
|||
EnsureInitialized(); |
|||
return _inner.Count; |
|||
} |
|||
} |
|||
|
|||
protected TreeNode Owner { get; } |
|||
|
|||
public event NotifyCollectionChangedEventHandler CollectionChanged |
|||
{ |
|||
add => _inner.CollectionChanged += value; |
|||
remove => _inner.CollectionChanged -= value; |
|||
} |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged |
|||
{ |
|||
add => _inner.PropertyChanged += value; |
|||
remove => _inner.PropertyChanged -= value; |
|||
} |
|||
|
|||
public virtual void Dispose() |
|||
{ |
|||
if (_inner is object) |
|||
{ |
|||
foreach (var node in _inner) |
|||
{ |
|||
node.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IEnumerator<TreeNode> GetEnumerator() |
|||
{ |
|||
EnsureInitialized(); |
|||
return _inner.GetEnumerator(); |
|||
} |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|||
|
|||
protected abstract void Initialize(AvaloniaList<TreeNode> nodes); |
|||
|
|||
private void EnsureInitialized() |
|||
{ |
|||
if (_inner is null) |
|||
{ |
|||
_inner = new AvaloniaList<TreeNode>(); |
|||
Initialize(_inner); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Layout |
|||
{ |
|||
/// <summary>
|
|||
/// Provides data for the <see cref="Layoutable.EffectiveViewportChanged"/> event.
|
|||
/// </summary>
|
|||
public class EffectiveViewportChangedEventArgs : EventArgs |
|||
{ |
|||
public EffectiveViewportChangedEventArgs(Rect effectiveViewport) |
|||
{ |
|||
EffectiveViewport = effectiveViewport; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="Rect"/> representing the effective viewport.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The viewport is expressed in coordinates relative to the control that the event is
|
|||
/// raised on.
|
|||
/// </remarks>
|
|||
public Rect EffectiveViewport { get; } |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue