Browse Source

Viewport Flyout

flabbet 2 years ago
parent
commit
88ff0fe58c

+ 2 - 0
src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs

@@ -1,6 +1,8 @@
 using System;
+using System.Runtime.InteropServices;
 
 namespace PixiEditor.DrawingApi.Core.Numerics;
+[StructLayout(LayoutKind.Sequential)]
 public struct RectI : IEquatable<RectI>
 {
     public static RectI Empty { get; } = new RectI();

+ 27 - 0
src/PixiEditor/Helpers/Converters/BoolToInverseBoolConverter.cs

@@ -0,0 +1,27 @@
+using System.Globalization;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class BoolToInverseBoolConverter : SingleInstanceConverter<BoolToInverseBoolConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is bool boolValue)
+        {
+            return !boolValue;
+        }
+
+        return Binding.DoNothing;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is bool boolValue)
+        {
+            return !boolValue;
+        }
+
+        return Binding.DoNothing;
+    }
+}

BIN
src/PixiEditor/Images/Settings.png


+ 2 - 0
src/PixiEditor/PixiEditor.csproj

@@ -329,6 +329,8 @@
 		<Resource Include="Images\Layout.png" />
 		<None Remove="Images\SymmetryVertical.png" />
 		<Resource Include="Images\SymmetryVertical.png" />
+		<None Remove="Images\Settings.png" />
+		<Resource Include="Images\Settings.png" />
 	</ItemGroup>
 	<ItemGroup>
 		<None Include="..\LICENSE">

+ 60 - 0
src/PixiEditor/Views/TogglableFlyout.xaml

@@ -0,0 +1,60 @@
+<UserControl x:Class="PixiEditor.Views.TogglableFlyout"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:local="clr-namespace:PixiEditor.Views"
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
+             mc:Ignorable="d"
+             d:DesignHeight="380" d:DesignWidth="200" Name="togglableFlyout">
+    <Border Background="Transparent">
+        <Grid>
+            <Border Background="#C8202020" CornerRadius="5" Padding="5" x:Name="btnBorder">
+                <ToggleButton Padding="0" Margin="0" ToolTip="{Binding ElementName=togglableFlyout, Path=ToolTip}"
+                              x:Name="toggleButton" BorderThickness="0" Width="24" Height="24" Background="Transparent">
+                    <ToggleButton.Template>
+                        <ControlTemplate TargetType="{x:Type ToggleButton}">
+                            <Grid>
+                                <Image Focusable="False" Width="24" Cursor="Hand" x:Name="btnBg" 
+                                       Source="{Binding ElementName=togglableFlyout, Path=IconPath}">
+                                    <Image.RenderTransform>
+                                        <RotateTransform Angle="0" CenterX="12" CenterY="12"/>
+                                    </Image.RenderTransform>
+                                </Image>
+                                <ContentPresenter/>
+                            </Grid>
+                            <ControlTemplate.Triggers>
+                                <Trigger Property="IsChecked" Value="True">
+                                    <Trigger.EnterActions>
+                                        <BeginStoryboard x:Name="Rotate90Animation">
+                                            <Storyboard>
+                                                <DoubleAnimation From="0" To="180"
+                                                                 Storyboard.TargetName="btnBg"
+                                                                 Storyboard.TargetProperty="(ToggleButton.RenderTransform).(RotateTransform.Angle)"
+                                                                 Duration="0:0:0.15"/>
+                                            </Storyboard>
+                                        </BeginStoryboard>
+                                    </Trigger.EnterActions>
+                                    <Trigger.ExitActions>
+                                        <BeginStoryboard x:Name="RotateReverse90Animation">
+                                            <Storyboard>
+                                                <DoubleAnimation From="180" To="0"
+                                                                 Storyboard.TargetName="btnBg"
+                                                                 Storyboard.TargetProperty="(ToggleButton.RenderTransform).(RotateTransform.Angle)"
+                                                                 Duration="0:0:0.15"/>
+                                            </Storyboard>
+                                        </BeginStoryboard>
+                                    </Trigger.ExitActions>
+                                </Trigger>
+                            </ControlTemplate.Triggers>
+                        </ControlTemplate>
+                    </ToggleButton.Template>
+                </ToggleButton>
+            </Border>
+            <userControls:NestedPopup AllowsTransparency="True" x:Name="popup" StaysOpen="True" IsOpen="{Binding Path=IsChecked, ElementName=toggleButton, Mode=TwoWay}" 
+                   Child="{Binding ElementName=togglableFlyout, Path=Child}" 
+                   PopupAnimation="Fade" PlacementTarget="{Binding ElementName=btnBorder}"/>
+        </Grid>
+    </Border>
+</UserControl>

+ 34 - 0
src/PixiEditor/Views/TogglableFlyout.xaml.cs

@@ -0,0 +1,34 @@
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace PixiEditor.Views;
+
+public partial class TogglableFlyout : UserControl
+{
+    public static readonly DependencyProperty ChildProperty = DependencyProperty.Register(
+        nameof(Child), typeof(UIElement), typeof(TogglableFlyout), new PropertyMetadata(default(UIElement)));
+
+    [Bindable(true)]
+    [Category("Content")]
+    public UIElement Child
+    {
+        get { return (UIElement)GetValue(ChildProperty); }
+        set { SetValue(ChildProperty, value); }
+    }
+
+    public static readonly DependencyProperty IconPathProperty = DependencyProperty.Register(
+        nameof(IconPath), typeof(string), typeof(TogglableFlyout), new PropertyMetadata(default(string)));
+
+    public string IconPath
+    {
+        get { return (string)GetValue(IconPathProperty); }
+        set { SetValue(IconPathProperty, value); }
+    }
+    
+    public TogglableFlyout()
+    {
+        InitializeComponent();
+    }
+}
+

+ 52 - 0
src/PixiEditor/Views/UserControls/NestedPopup.cs

@@ -0,0 +1,52 @@
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Controls.Primitives;
+using System.Windows.Interop;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.Views.UserControls;
+
+public class NestedPopup : Popup
+{
+    public static DependencyProperty TopmostProperty = Window.TopmostProperty.AddOwner(
+        typeof(NestedPopup),
+        new FrameworkPropertyMetadata( false, OnTopmostChanged ) );
+
+    public bool Topmost
+    {
+        get { return (bool)GetValue( TopmostProperty ); }
+        set { SetValue( TopmostProperty, value ); }
+    }
+
+    private static void OnTopmostChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e )
+    {
+        (obj as NestedPopup)?.UpdateWindow();
+    }
+
+    protected override void OnOpened( EventArgs e )
+    {
+        UpdateWindow();
+    }
+
+    private void UpdateWindow()
+    {
+        var hwnd = ((HwndSource)PresentationSource.FromVisual(this.Child)).Handle;
+        RectI rect;
+
+        if (GetWindowRect(hwnd, out rect))
+        {
+            SetWindowPos(hwnd, Topmost ? -1 : -2, rect.Left, rect.Top, (int)this.Width, (int)this.Height, 0);
+        }
+    }
+
+    #region P/Invoke imports & definitions
+
+    [DllImport( "user32.dll" )]
+    [return: MarshalAs( UnmanagedType.Bool )]
+    private static extern bool GetWindowRect(IntPtr hWnd, out RectI lpRect);
+
+    [DllImport( "user32", EntryPoint = "SetWindowPos" )]
+    private static extern int SetWindowPos( IntPtr hWnd, int hwndInsertAfter, int x, int y, int cx, int cy, int wFlags );
+
+    #endregion
+}

+ 7 - 1
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -17,6 +17,7 @@
     xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
     xmlns:cmds="clr-namespace:PixiEditor.Models.Commands.XAML"
     xmlns:tools ="clr-namespace:PixiEditor.ViewModels.SubViewModels.Tools.Tools"
+    xmlns:views="clr-namespace:PixiEditor.Views"
     mc:Ignorable="d"
     x:Name="vpUc"
     d:DesignHeight="450"
@@ -45,7 +46,10 @@
                                         PassEventArgsToCommand="True"/>
             </i:EventTrigger>
         </i:Interaction.Triggers>
-        <Border Padding="5" BorderThickness="1" CornerRadius="5" Margin="5" Background="#C8202020" HorizontalAlignment="Right" VerticalAlignment="Top" Panel.ZIndex="2">
+        <views:TogglableFlyout Margin="5" IconPath="/Images/Settings.png" ToolTip="Viewport settings"
+                               Panel.ZIndex="2" HorizontalAlignment="Right" VerticalAlignment="Top">
+            <views:TogglableFlyout.Child>
+                <Border BorderThickness="1" CornerRadius="5" Padding="5" Background="#C8202020" Panel.ZIndex="2">
         <StackPanel Orientation="Vertical">
             <StackPanel Orientation="Horizontal">
             <TextBlock Margin="5 0" TextAlignment="Center"
@@ -86,6 +90,8 @@
             </StackPanel>
         </StackPanel>
         </Border>
+            </views:TogglableFlyout.Child>
+        </views:TogglableFlyout>
         <zoombox:Zoombox
             Tag="{Binding ElementName=vpUc}"
             x:Name="zoombox"