Selaa lähdekoodia

Merge pull request #3220 from AvaloniaUI/fixes/osx-implement-default-app-menu

Default app menu for osx - With about screen
Jumar Macato 6 vuotta sitten
vanhempi
sitoutus
1121d45ad0

+ 0 - 12
samples/ControlCatalog/App.xaml

@@ -17,16 +17,4 @@
     </Style>
     <StyleInclude Source="/SideBar.xaml"/>
   </Application.Styles>
-
-  <NativeMenu.Menu>
-      <NativeMenu>
-        <NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
-        <NativeMenuItem Header="Recent">
-          <NativeMenuItem.Menu>
-            <NativeMenu/>
-          </NativeMenuItem.Menu>
-        </NativeMenuItem>
-        <NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
-      </NativeMenu>
-  </NativeMenu.Menu>
 </Application>

+ 0 - 11
samples/ControlCatalog/App.xaml.cs

@@ -8,20 +8,9 @@ namespace ControlCatalog
 {
     public class App : Application
     {
-        private NativeMenu _recentMenu;
-
         public override void Initialize()
         {
             AvaloniaXamlLoader.Load(this);
-
-            Name = "Avalonia";
-
-            _recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu;
-        }
-
-        public void OnOpenClicked(object sender, EventArgs args)
-        {
-            _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
         }
 
         public override void OnFrameworkInitializationCompleted()

+ 16 - 8
samples/ControlCatalog/MainWindow.xaml

@@ -37,12 +37,20 @@
     </NativeMenu>
   </NativeMenu.Menu>
 
-  <Window.DataTemplates>
-        <DataTemplate DataType="vm:NotificationViewModel">
-            <v:CustomNotificationView />
-        </DataTemplate>
-    </Window.DataTemplates>
-    <Panel>
-        <local:MainView/>
-    </Panel>
+ <Window.DataTemplates>
+    <DataTemplate DataType="vm:NotificationViewModel">
+      <v:CustomNotificationView />
+    </DataTemplate>
+  </Window.DataTemplates>
+  <DockPanel LastChildFill="True">
+    <Menu Name="MainMenu" DockPanel.Dock="Top">
+      <MenuItem Header="File">
+        <MenuItem Header="Exit" Command="{Binding ExitCommand}" />
+      </MenuItem>
+      <MenuItem Header="Help">
+        <MenuItem Header="About" Command="{Binding AboutCommand}" />
+      </MenuItem>
+    </Menu>
+    <local:MainView />
+  </DockPanel>
 </Window>

+ 11 - 3
samples/ControlCatalog/MainWindow.xaml.cs

@@ -31,20 +31,28 @@ namespace ControlCatalog
 
             DataContext = new MainWindowViewModel(_notificationArea);
             _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
+            var mainMenu = this.FindControl<Menu>("MainMenu");
+            mainMenu.AttachedToVisualTree += MenuAttached;
+        }
+
+        public void MenuAttached(object sender, VisualTreeAttachmentEventArgs e)
+        {
+            if (NativeMenu.GetIsNativeMenuExported(this) && sender is Menu mainMenu)
+            {
+                mainMenu.IsVisible = false;
+            }
         }
 
         public void OnOpenClicked(object sender, EventArgs args)
         {
             _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
         }
-        
+
         public void OnCloseClicked(object sender, EventArgs args)
         {
             Close();
         }
 
-
-
         private void InitializeComponent()
         {
             // TODO: iOS does not support dynamically loading assemblies

+ 20 - 0
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@@ -1,5 +1,7 @@
 using System.Reactive;
+using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Notifications;
+using Avalonia.Dialogs;
 using ReactiveUI;
 
 namespace ControlCatalog.ViewModels
@@ -26,6 +28,20 @@ namespace ControlCatalog.ViewModels
             {
                 NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
             });
+
+            AboutCommand = ReactiveCommand.CreateFromTask(async () =>
+            {
+                var dialog = new AboutAvaloniaDialog();
+
+                var mainWindow = (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
+
+                await dialog.ShowDialog(mainWindow);
+            });
+
+            ExitCommand = ReactiveCommand.Create(() =>
+            {
+                (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown();
+            });
         }
 
         public IManagedNotificationManager NotificationManager
@@ -39,5 +55,9 @@ namespace ControlCatalog.ViewModels
         public ReactiveCommand<Unit, Unit> ShowManagedNotificationCommand { get; }
 
         public ReactiveCommand<Unit, Unit> ShowNativeNotificationCommand { get; }
+
+        public ReactiveCommand<Unit, Unit> AboutCommand { get; }
+
+        public ReactiveCommand<Unit, Unit> ExitCommand { get; }
     }
 }

+ 8 - 0
src/Avalonia.Controls/Application.cs

@@ -48,6 +48,14 @@ namespace Avalonia
         /// <inheritdoc/>
         public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
 
+        /// <summary>
+        /// Creates an instance of the <see cref="Application"/> class.
+        /// </summary>
+        public Application()
+        {
+            Name = "Avalonia Application";
+        }
+
         /// <summary>
         /// Gets the current instance of the <see cref="Application"/> class.
         /// </summary>

+ 105 - 0
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@@ -0,0 +1,105 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        MaxWidth="400"
+        MaxHeight="475"
+        MinWidth="430"
+        MinHeight="475"
+        Title="About Avalonia"
+        Background="Purple"
+        FontFamily="/Assets/Roboto-Light.ttf#Roboto"
+        x:Class="Avalonia.Dialogs.AboutAvaloniaDialog">
+  <Window.Styles>
+    <Style>
+      <Style.Resources>
+        <DrawingGroup x:Key="AvaloniaLogo">
+          <GeometryDrawing Geometry="m 150.66581 0.66454769 c -54.77764 0 -101.0652 38.86360031 -112.62109 90.33008031 a 26.1 26.1 0 0 1 18.92187 25.070312 26.1 26.1 0 0 1 -18.91992 25.08202 c 11.56024 51.46073 57.8456 90.31837 112.61914 90.31837 63.37832 0 115.40039 -52.02207 115.40039 -115.40039 0 -63.378322 -52.02207 -115.40039231 -115.40039 -115.40039231 z m 0 60.00000031 c 30.95192 0 55.40039 24.44847 55.40039 55.400392 0 30.9519 -24.44847 55.40039 -55.40039 55.40039 -30.95191 0 -55.40039 -24.44848 -55.40039 -55.40039 0 -30.951922 24.44848 -55.400392 55.40039 -55.400392 z">
+            <GeometryDrawing.Brush>
+              <LinearGradientBrush StartPoint="272,411" EndPoint="435,248">
+                <LinearGradientBrush.GradientStops>
+                  <GradientStop Color="#B0B0B0" Offset="0" />
+                  <GradientStop Color="#FFFFFF" Offset="0.6784" />
+                </LinearGradientBrush.GradientStops>
+              </LinearGradientBrush>
+            </GeometryDrawing.Brush>
+          </GeometryDrawing>
+          <GeometryDrawing Brush="#B0B0B0">
+            <GeometryDrawing.Geometry>
+              <EllipseGeometry Rect="9.6,95.8,40.6,40.6" />
+            </GeometryDrawing.Geometry>
+          </GeometryDrawing>
+          <GeometryDrawing Brush="White">
+            <GeometryDrawing.Geometry>
+              <RectangleGeometry Rect="206.06355, 114.56503,60,116.2" />
+            </GeometryDrawing.Geometry>
+          </GeometryDrawing>
+        </DrawingGroup>
+      </Style.Resources>
+    </Style>
+    <Style Selector="Rectangle.Abstract">
+      <Setter Property="Fill" Value="White" />
+      <Setter Property="Width" Value="750" />
+      <Setter Property="Height" Value="700" />
+    </Style>
+    <Style Selector="Button.Hyperlink">
+      <Setter Property="Background" Value="Transparent" />
+      <Setter Property="BorderThickness" Value="0" />
+      <Setter Property="Margin" Value="-5"/>
+      <Setter Property="Foreground" Value="#419df2" />
+      <Setter Property="Command" Value="{Binding OpenBrowser}" />
+      <Setter Property="Content" Value="{Binding $self.CommandParameter}" />
+      <Setter Property="HorizontalAlignment" Value="Center" />
+      <Setter Property="Cursor" Value="Hand" />
+    </Style>
+  </Window.Styles>
+  <Grid Background="#4A255D">
+    <Canvas>
+      <Rectangle Classes="Abstract" Canvas.Top="90" Opacity="0.132">
+        <Rectangle.RenderTransform>
+          <RotateTransform Angle="-2" />
+        </Rectangle.RenderTransform>
+      </Rectangle>
+      <Rectangle Classes="Abstract" Canvas.Top="95" Opacity="0.3">
+        <Rectangle.RenderTransform>
+          <RotateTransform Angle="-4" />
+        </Rectangle.RenderTransform>
+      </Rectangle>
+      <Rectangle Classes="Abstract" Canvas.Top="100" Opacity="0.3">
+        <Rectangle.RenderTransform>
+          <RotateTransform Angle="-8" />
+        </Rectangle.RenderTransform>
+      </Rectangle>
+      <Rectangle Classes="Abstract" Canvas.Top="105" Opacity="0.7">
+        <Rectangle.RenderTransform>
+          <RotateTransform Angle="-12" />
+        </Rectangle.RenderTransform>
+      </Rectangle>
+    </Canvas>
+    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="18">
+      <Border Height="70" Width="70">
+        <DrawingPresenter Drawing="{DynamicResource AvaloniaLogo}" />
+      </Border>
+      <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,-10,0,0">
+        <TextBlock Text="Avalonia 0.9" FontSize="40" Foreground="White" />
+        <TextBlock Text="Development Build" Margin="0,-10,0,0" FontSize="15" Foreground="White" />
+      </StackPanel>
+    </StackPanel>
+    <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="20" Margin="10 60 10 0">
+      <TextBlock Text="This product is built with the Avalonia cross-platform UI Framework. &#x0A;&#x0A;Avalonia is made possible by the generous support of it's contributors and community." TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" />
+      <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
+        <TextBlock Text="Main source repository | " />
+        <Button Classes="Hyperlink" CommandParameter="https://github.com/AvaloniaUI/Avalonia/"  />
+      </StackPanel>
+      <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
+        <TextBlock Text="Documentation and Information | " />
+        <Button Classes="Hyperlink" CommandParameter="https://avaloniaui.net/"  />
+      </StackPanel>
+      <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
+        <TextBlock Text="Chat Room | " />
+        <Button Classes="Hyperlink" CommandParameter="https://gitter.im/AvaloniaUI/Avalonia/"  />
+      </StackPanel>
+     </StackPanel> 
+    <StackPanel VerticalAlignment="Bottom" Margin="10">
+      <TextBlock Text="© 2019 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
+    </StackPanel>
+  </Grid>
+</Window>

+ 62 - 0
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs

@@ -0,0 +1,62 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Avalonia.Dialogs
+{
+    public class AboutAvaloniaDialog : Window
+    {
+        public AboutAvaloniaDialog()
+        {
+            AvaloniaXamlLoader.Load(this);
+            DataContext = this;
+        }
+ 
+        public static void OpenBrowser(string url)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+            {
+                // If no associated application/json MimeType is found xdg-open opens retrun error
+                // but it tries to open it anyway using the console editor (nano, vim, other..)
+                ShellExec($"xdg-open {url}", waitForExit: false);
+            }
+            else
+            {
+                using (Process process = Process.Start(new ProcessStartInfo
+                {
+                    FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open",
+                    Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"-e {url}" : "",
+                    CreateNoWindow = true,
+                    UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
+                }));
+            }
+        }
+
+        private static void ShellExec(string cmd, bool waitForExit = true)
+        {
+            var escapedArgs = cmd.Replace("\"", "\\\"");
+
+            using (var process = Process.Start(
+                new ProcessStartInfo
+                {
+                    FileName = "/bin/sh",
+                    Arguments = $"-c \"{escapedArgs}\"",
+                    RedirectStandardOutput = true,
+                    UseShellExecute = false,
+                    CreateNoWindow = true,
+                    WindowStyle = ProcessWindowStyle.Hidden
+                }
+            ))
+            {
+                if (waitForExit)
+                {
+                    process.WaitForExit();
+                }
+            }
+        }
+    }
+}

BIN
src/Avalonia.Dialogs/Assets/Roboto-Light.ttf


+ 1 - 0
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@@ -7,6 +7,7 @@
     <AvaloniaResource Include="**\*.xaml">
       <SubType>Designer</SubType>
     </AvaloniaResource>
+    <AvaloniaResource Include="Assets\*" />
   </ItemGroup>
 
   <Import Project="..\..\build\BuildTargets.targets" />

+ 1 - 0
src/Avalonia.Native/Avalonia.Native.csproj

@@ -22,5 +22,6 @@
     <PackageReference Include="SharpGenTools.Sdk" Version="1.1.2" PrivateAssets="all" />
     <PackageReference Include="SharpGen.Runtime.COM" Version="1.1.0" />
     <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
+    <ProjectReference Include="..\..\packages\Avalonia\Avalonia.Dialogs.csproj" />
   </ItemGroup>
 </Project>

+ 30 - 0
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@@ -3,12 +3,15 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Linq;
 using System.Text;
+using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform;
 using Avalonia.Input;
 using Avalonia.Native.Interop;
 using Avalonia.Platform.Interop;
 using Avalonia.Threading;
+using Avalonia.Dialogs;
+using Avalonia.Controls.ApplicationLifetimes;
 
 namespace Avalonia.Native
 {
@@ -211,6 +214,29 @@ namespace Avalonia.Native
             DoLayoutReset();
         }
 
+        private static NativeMenu CreateDefaultAppMenu()
+        {
+            var result = new NativeMenu();
+
+            var aboutItem = new NativeMenuItem
+            {
+                Header = "About Avalonia",
+            };
+
+            aboutItem.Clicked += async (sender, e) =>
+            {
+                var dialog = new AboutAvaloniaDialog();
+
+                var mainWindow = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
+
+                await dialog.ShowDialog(mainWindow);
+            };
+
+            result.Add(aboutItem);
+
+            return result;
+        }
+
         private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
         {
             QueueReset();
@@ -241,6 +267,10 @@ namespace Avalonia.Native
                 {
                     SetMenu(_menu);
                 }
+                else
+                {
+                    SetMenu(CreateDefaultAppMenu());
+                }
             }
             else
             {