Justin 2 жил өмнө
parent
commit
f26a99d873

+ 133 - 0
fis/MacosTitleBar.axaml

@@ -0,0 +1,133 @@
+<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="fis.MacosTitleBar"
+             DockPanel.Dock="Top"
+             Height="44">
+  <Grid>
+    <DockPanel Background="{DynamicResource MacOsTitleBarBackground}"
+               IsHitTestVisible="False"
+               Name="TitleBarBackground">
+      <!--#3E3E40-->
+      <!--#DEE1E6-->
+    </DockPanel>
+    <DockPanel>
+      <StackPanel Orientation="Horizontal"
+                  DockPanel.Dock="Left"
+                  Spacing="6"
+                  Margin="6,0,0,0"
+                  Background="Transparent">
+        <StackPanel.Styles>
+          <Style Selector="StackPanel:pointerover Path">
+            <Setter Property="IsVisible" Value="true"></Setter>
+          </Style>
+          <Style Selector="StackPanel:not(:pointerover) Path">
+            <Setter Property="IsVisible" Value="false"></Setter>
+          </Style>
+        </StackPanel.Styles>
+        <Button Name="CloseButton"
+                HorizontalContentAlignment="Center"
+                VerticalContentAlignment="Center"
+                VerticalAlignment="Center"
+                Width="24"
+                Height="24">
+          <Button.Resources>
+            <CornerRadius x:Key="ControlCornerRadius">12</CornerRadius>
+          </Button.Resources>
+          <Button.Styles>
+            <Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+              <Setter Property="Background" Value="#99FF5D55"/>
+            </Style>
+            <Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+              <Setter Property="Background" Value="#FF5D55"/>
+            </Style>
+          </Button.Styles>
+
+          <Path Data="M 0,0 l 12,12 M 0,12 l 12,-12"
+                Stroke="#4C0102"
+                StrokeThickness="1"
+                Width="12"
+                Height="12"></Path>
+        </Button>
+
+        <Button Name="MinimizeButton"
+                HorizontalContentAlignment="Center"
+                VerticalContentAlignment="Center"
+                VerticalAlignment="Center"
+                Width="24"
+                Height="24">
+          <Button.Resources>
+            <CornerRadius x:Key="ControlCornerRadius">12</CornerRadius>
+          </Button.Resources>
+          <Button.Styles>
+            <Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+              <Setter Property="Background" Value="#99FFBC2E"/>
+            </Style>
+            <Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+              <Setter Property="Background" Value="#FFBC2E"/>
+            </Style>
+          </Button.Styles>
+
+          <Path Data="M 0,0 l 16,0"
+                Stroke="#985712"
+                StrokeThickness="1"
+                Width="16"
+                Height="1"></Path>
+        </Button>
+
+        <Button Name="ZoomButton"
+                HorizontalContentAlignment="Center"
+                VerticalContentAlignment="Center"
+                VerticalAlignment="Center"
+                Width="24"
+                Height="24">
+          <Button.Resources>
+            <CornerRadius x:Key="ControlCornerRadius">12</CornerRadius>
+          </Button.Resources>
+          <Button.Styles>
+            <Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+              <Setter Property="Background" Value="#9928C83E"/>
+            </Style>
+            <Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+              <Setter Property="Background" Value="#28C83E"/>
+            </Style>
+          </Button.Styles>
+
+          <Path Data="M 0,12 l 9,0 l -9,-9 l 0,9 M 12,0 l 0,9 l -9,-9 l 9,0"
+                Fill="#0A630C"
+                StrokeThickness="0"
+                Width="12"
+                Height="12"></Path>
+        </Button>
+
+      </StackPanel>
+
+      <StackPanel Orientation="Horizontal"
+                  HorizontalAlignment="Center"
+                  VerticalAlignment="Center"
+                  Spacing="6"
+                  IsHitTestVisible="False"
+                  IsVisible="True"
+                  Name="TitleAndWindowIconWrapper">
+        <StackPanel.RenderTransform>
+          <TranslateTransform X="-33"
+                              Y="0"></TranslateTransform>
+        </StackPanel.RenderTransform>
+        <Image Source="flyinsono.ico"
+               Height="25"
+               Width="25"></Image>
+        <TextBlock FontSize="20"
+				   Name="MacosTitle"
+                   VerticalAlignment="Center"
+                   Foreground="{DynamicResource MacOsWindowTitleColor}">
+          <!--#999EA1-->
+          <!--#4D4D4D-->
+        </TextBlock>
+      </StackPanel>
+    </DockPanel>
+  </Grid>
+  
+  
+</UserControl>

+ 127 - 0
fis/MacosTitleBar.axaml.cs

@@ -0,0 +1,127 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace fis
+{
+    public class MacosTitleBar : UserControl
+    {
+        private Button closeButton;
+        private Button minimizeButton;
+        private Button zoomButton;
+
+        private DockPanel titleBarBackground;
+        private StackPanel titleAndWindowIconWrapper;
+
+        public static readonly StyledProperty<bool> IsSeamlessProperty =
+        AvaloniaProperty.Register<MacosTitleBar, bool>(nameof(IsSeamless));
+
+        public bool IsSeamless
+        {
+            get { return GetValue(IsSeamlessProperty); }
+            set {
+                SetValue(IsSeamlessProperty, value);
+                if (titleBarBackground != null && titleAndWindowIconWrapper != null)
+                {
+                    titleBarBackground.IsVisible = IsSeamless ? false : true;
+                    titleAndWindowIconWrapper.IsVisible = IsSeamless ? false : true;
+                }
+            }
+        }
+
+        public MacosTitleBar()
+        {
+            this.InitializeComponent();
+
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) == false)
+            {
+                this.IsVisible = false;
+            }
+            else
+            {
+                minimizeButton = this.FindControl<Button>("MinimizeButton");
+                zoomButton = this.FindControl<Button>("ZoomButton");
+                closeButton = this.FindControl<Button>("CloseButton");
+
+                minimizeButton.Click += MinimizeWindow;
+                zoomButton.Click += MaximizeWindow;
+                closeButton.Click += CloseWindow;
+
+                titleBarBackground = this.FindControl<DockPanel>("TitleBarBackground");
+                titleAndWindowIconWrapper = this.FindControl<StackPanel>("TitleAndWindowIconWrapper");
+
+                SubscribeToWindowState();
+            }
+        }
+
+        private void CloseWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window hostWindow = (Window)this.VisualRoot;
+            hostWindow.Close();
+        }
+
+        private void MaximizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window hostWindow = (Window)this.VisualRoot;
+
+            if (hostWindow.WindowState == WindowState.Normal)
+            {
+                hostWindow.WindowState = WindowState.Maximized;
+            }
+            else
+            {
+                hostWindow.WindowState = WindowState.Normal;
+            }
+        }
+
+        private void MinimizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window hostWindow = (Window)this.VisualRoot;
+            hostWindow.WindowState = WindowState.Minimized;
+        }
+
+        private async void SubscribeToWindowState()
+        {
+            Window hostWindow = (Window)this.VisualRoot;
+
+            while (hostWindow == null)
+            {
+                hostWindow = (Window)this.VisualRoot;
+                await Task.Delay(50);
+            }
+
+            hostWindow.ExtendClientAreaTitleBarHeightHint = 44;
+            hostWindow.GetObservable(Window.WindowStateProperty).Subscribe(s =>
+            {
+                if (s != WindowState.Maximized)
+                {
+                    hostWindow.Padding = new Thickness(0, 0, 0, 0);
+                }
+                if (s == WindowState.Maximized)
+                {
+                    hostWindow.Padding = new Thickness(7, 7, 7, 7);
+
+                    // This should be a more universal approach in both cases, but I found it to be less reliable, when for example double-clicking the title bar.
+                    /*hostWindow.Padding = new Thickness(
+                            hostWindow.OffScreenMargin.Left,
+                            hostWindow.OffScreenMargin.Top,
+                            hostWindow.OffScreenMargin.Right,
+                            hostWindow.OffScreenMargin.Bottom);*/
+                }
+            });
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 16 - 3
fis/MainWindow.axaml

@@ -2,10 +2,23 @@
         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"
+		xmlns:titlebars="clr-namespace:fis;assembly=fis"
         mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
         x:Class="fis.MainWindow"
         Title="FLYINSONO">
-   <Grid>
-      <Border BorderBrush="White" BorderThickness="1,0,1,1" Grid.Row="1" x:Name="BrowserBorder"/>
-  </Grid>
+	<Grid>
+		<Border Name="WindowBorder" BorderBrush="#BFBFBF" BorderThickness="1">
+			<Grid>
+				<Grid.RowDefinitions>
+					<RowDefinition Height="32"/>
+					<RowDefinition Height="*"/>
+				</Grid.RowDefinitions>
+				<Grid Grid.Row="0">
+					<titlebars:WindowsTitleBar Name="WinTitleBar" IsSeamless="True"/>
+					<titlebars:MacosTitleBar  Name="MacTitleBar" IsSeamless="False"/>
+				</Grid>
+				<Border Grid.Row="1" BorderThickness="0" x:Name="BrowserBorder"/>
+			</Grid>
+		</Border>
+	</Grid>
 </Window>

+ 36 - 21
fis/MainWindow.axaml.cs

@@ -2,7 +2,6 @@ using System;
 using System.IO;
 using Avalonia;
 using Avalonia.Controls;
-using Avalonia.Input;
 using Avalonia.Markup.Xaml;
 using fis.Mac;
 using fis.Win;
@@ -13,17 +12,22 @@ using Xilium.CefGlue.Common.Handlers;
 using System.Globalization;
 using System.Reflection;
 using System.Threading;
+using System.Runtime.InteropServices;
 
 namespace fis
 {
     public partial class MainWindow : Window
     {
         public static SynchronizationContext? MainThreadSyncContext;
+        private TextBlock? _title;
         private AvaloniaCefBrowser? _browser;
 
         public MainWindow()
         {
             InitializeComponent();
+            ExtendClientAreaToDecorationsHint = true;
+            ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.NoChrome;
+            ExtendClientAreaTitleBarHeightHint = -1;
             MainThreadSyncContext = SynchronizationContext.Current;
 #if DEBUG
             this.AttachDevTools();
@@ -37,12 +41,16 @@ namespace fis
             var appHandler = new FileSystemHostHandler(ShellConfig.Instance.AppHost, ShellConfig.Instance.AppResourcePath);
             var resourceHandler = new FileSystemHostHandler(ShellConfig.Instance.LocalResourceHost, ShellConfig.Instance.LocalResourcePath);
             IPlatformService platformService;
-            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
+                this.FindControl<MacosTitleBar>("MacTitleBar").IsVisible = false;
+                _title = this.FindControl<WindowsTitleBar>("WinTitleBar").FindControl<TextBlock>("WindowsTitle");
                 platformService = new WinService();
             }
-            else if (Environment.OSVersion.Platform == PlatformID.MacOSX)
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
             {
+                this.FindControl<WindowsTitleBar>("WinTitleBar").IsVisible = false;
+                _title = this.FindControl<MacosTitleBar>("MacTitleBar").FindControl<TextBlock>("MacosTitle");
                 platformService = new MacService();
             }
             else
@@ -54,41 +62,45 @@ namespace fis
             handler.RegisterHostHandler(appHandler);
             handler.RegisterHostHandler(resourceHandler);
             handler.RegisterHostHandler(platformHandler);
-            _browser = new AvaloniaCefBrowser();
-            _browser.RequestHandler = handler;
+            _browser = new AvaloniaCefBrowser
+            {
+                RequestHandler = handler
+            };
             //Make cross domain work.
             _browser.Settings.WebSecurity = CefState.Disabled;
             _browser.Address = "http://" + ShellConfig.Instance.AppHost + "/index.html";
             _browser.ContextMenuHandler = new TextContextMenuHandler(_browser);
             _browser.BrowserInitialized += () =>
             {
-                var scriptObj = new FisBrowserScriptObject(this, platformService);
+                var scriptObj = new FisBrowserScriptObject(_title, platformService);
                 _browser.RegisterJavascriptObject(scriptObj, "FisShellApi");
             };
             browserContainer.Child = _browser;
 
             WindowStartupLocation = WindowStartupLocation.CenterScreen;
-            WindowState = WindowState.Maximized;
+            //WindowState = WindowState.Maximized;
             MinWidth = 1366;
             MinHeight = 768;
             var language = CultureInfo.CurrentCulture.Name;
-            if (language == "zh-CN")
-            {
-                Title = "杏聆荟";
-            }
-            else
+            if (_title != null)
             {
-                Title = "FLYINSONO";
+                if (language == "zh-CN")
+                {
+                    _title.Text = "杏聆荟";
+                }
+                else
+                {
+                    _title.Text = "FLYINSONO";
+                }
             }
-
             var assembly = Assembly.GetExecutingAssembly();
 
             Stream? resourceStream = null;
-            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
                 resourceStream = assembly.GetManifestResourceStream("fis.Win.flyinsono.ico");
             }
-            else if (Environment.OSVersion.Platform == PlatformID.Unix)
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
             {
                 resourceStream = assembly.GetManifestResourceStream("fis.Mac.flyinsono.ico");
             }
@@ -101,7 +113,7 @@ namespace fis
 #if DEBUG
             System.Threading.Tasks.Task.Run(() =>
             {
-                System.Threading.Thread.Sleep(1000);
+                Thread.Sleep(1000);
                 _browser.ShowDeveloperTools();
             });
 #endif
@@ -111,10 +123,10 @@ namespace fis
     internal class FisBrowserScriptObject
     {
         IPlatformService _platformService;
-        Window _window;
-        internal FisBrowserScriptObject(Window window, IPlatformService platformService)
+        TextBlock _title;
+        internal FisBrowserScriptObject(TextBlock title, IPlatformService platformService)
         {
-            _window = window;
+            _title = title;
             _platformService = platformService;
         }
 
@@ -139,7 +151,10 @@ namespace fis
         {
             MainWindow.MainThreadSyncContext?.Send(new SendOrPostCallback((args) =>
             {
-                _window.Title = title;
+                if(_title != null)
+                {
+                    _title.Text = title;
+                }
             }), this);
         }
 

+ 108 - 0
fis/WindowsTitleBar.axaml

@@ -0,0 +1,108 @@
+<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="fis.WindowsTitleBar">
+	<Grid>
+		<!--The proper way would be not to use white as default, but somehow retrieve the users' window chrome color.-->
+		<DockPanel Background="#EBEBEB"
+				   IsHitTestVisible="False"
+				   Name="TitleBarBackground"></DockPanel>
+		<DockPanel Name="TitleBar">
+			<StackPanel Orientation="Horizontal"
+						DockPanel.Dock="Left"
+						Spacing="0">
+				<Image Source="flyinsono.ico"
+					   Height="20"
+					   Width="20"
+					   VerticalAlignment="Center"
+					   Margin="5,0,3,0"
+					   Name="WindowIcon"></Image>
+				<TextBlock FontSize="12"
+						   Foreground="Black"
+						   VerticalAlignment="Center"
+						   IsHitTestVisible="False"
+						   Name="WindowsTitle"></TextBlock>
+			</StackPanel>
+			<StackPanel HorizontalAlignment="Right"
+						Orientation="Horizontal"
+						Spacing="0">
+				<Button Width="46"
+						Height="30"
+						HorizontalContentAlignment="Center"
+						BorderThickness="0"
+						Name="MinimizeButton">
+					<Button.Resources>
+						<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
+					</Button.Resources>
+					<Button.Styles>
+						<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+							<Setter Property="Background" Value="#C6C6C6"/>
+						</Style>
+						<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+							<Setter Property="Background" Value="Transparent"/>
+						</Style>
+					</Button.Styles>
+					<Path VerticalAlignment="Center"
+						  Margin="10,0,10,0"
+						  Stretch="Uniform"
+						  Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
+						  Data="M2048 1229v-205h-2048v205h2048z"></Path>
+				</Button>
+
+				<Button Width="46"
+						VerticalAlignment="Stretch"
+						BorderThickness="0"
+						Name="MaximizeButton">
+					<Button.Resources>
+						<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
+					</Button.Resources>
+					<Button.Styles>
+						<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+							<Setter Property="Background" Value="#C6C6C6"/>
+						</Style>
+						<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+							<Setter Property="Background" Value="Transparent"/>
+						</Style>
+					</Button.Styles>
+					<Path VerticalAlignment="Center"
+						  Margin="10,0,10,0"
+						  Stretch="Uniform"
+						  Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
+						  Name="MaximizeIcon"
+						  Data="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z"></Path>
+				</Button>
+
+				<Button Width="46"
+						VerticalAlignment="Stretch"
+						BorderThickness="0"
+						Name="CloseButton">
+					<Button.Resources>
+						<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
+					</Button.Resources>
+					<Button.Styles>
+						<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+							<Setter Property="Background" Value="#C6C6C6"/>
+						</Style>
+						<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
+							<Setter Property="Background" Value="Transparent"/>
+						</Style>
+						<Style Selector="Button:pointerover > Path">
+							<Setter Property="Fill" Value="White"/>
+						</Style>
+						<Style Selector="Button:not(:pointerover) > Path">
+							<Setter Property="Fill" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
+						</Style>
+					</Button.Styles>
+					<Path VerticalAlignment="Center"
+						  Margin="10,0,10,0"
+						  Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
+						  Stretch="Uniform"
+						  Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z"></Path>
+				</Button>
+
+			</StackPanel>
+		</DockPanel>
+	</Grid>
+</UserControl>

+ 146 - 0
fis/WindowsTitleBar.axaml.cs

@@ -0,0 +1,146 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace fis
+{
+    public class WindowsTitleBar : UserControl
+    {
+        private Button? _minimizeButton;
+        private Button? _maximizeButton;
+        private Path? _maximizeIcon;
+        private Button? _closeButton;
+        private DockPanel? _titleBar;
+        private DockPanel? _titleBarBackground;
+        private TextBlock? _systemChromeTitle;
+        private NativeMenuBar? _seamlessMenuBar;
+        private NativeMenuBar? _defaultMenuBar;
+
+        public static readonly StyledProperty<bool> IsSeamlessProperty = AvaloniaProperty.Register<MacosTitleBar, bool>(nameof(IsSeamless));
+
+        public bool IsSeamless
+        {
+            get { return GetValue(IsSeamlessProperty); }
+            set
+            {
+                SetValue(IsSeamlessProperty, value);
+                if (_titleBarBackground != null &&
+                    _systemChromeTitle != null &&
+                    _seamlessMenuBar != null &&
+                    _defaultMenuBar != null)
+                {
+                    _titleBarBackground.IsVisible = IsSeamless ? false : true;
+                    _systemChromeTitle.IsVisible = IsSeamless ? false : true;
+                    _seamlessMenuBar.IsVisible = IsSeamless ? true : false;
+                    _defaultMenuBar.IsVisible = IsSeamless ? false : true;
+
+                    if (IsSeamless == false && _titleBar != null)
+                    {
+                        _titleBar.Resources["SystemControlForegroundBaseHighBrush"] = new SolidColorBrush { Color = new Color(255, 0, 0, 0) };
+                    }
+                }
+            }
+        }
+
+        public WindowsTitleBar()
+        {
+            InitializeComponent();
+
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) == false)
+            {
+                IsVisible = false;
+            }
+            else
+            {
+                _minimizeButton = this.FindControl<Button>("MinimizeButton");
+                _maximizeButton = this.FindControl<Button>("MaximizeButton");
+                _maximizeIcon = this.FindControl<Path>("MaximizeIcon");
+                _closeButton = this.FindControl<Button>("CloseButton");
+                if (_minimizeButton != null)
+                {
+                    _minimizeButton.Click += MinimizeWindow;
+                }
+                if (_maximizeButton != null)
+                {
+                    _maximizeButton.Click += MaximizeWindow;
+                }
+                if (_closeButton != null)
+                {
+                    _closeButton.Click += CloseWindow;
+                }
+                _titleBar = this.FindControl<DockPanel>("TitleBar");
+                _titleBarBackground = this.FindControl<DockPanel>("TitleBarBackground");
+                _systemChromeTitle = this.FindControl<TextBlock>("WindowsTitle");
+                _seamlessMenuBar = this.FindControl<NativeMenuBar>("SeamlessMenuBar");
+                _defaultMenuBar = this.FindControl<NativeMenuBar>("DefaultMenuBar");
+
+                SubscribeToWindowState();
+            }
+        }
+
+        private void CloseWindow(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window? hostWindow = VisualRoot as Window;
+            hostWindow?.Close();
+        }
+
+        private void MaximizeWindow(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window? hostWindow = VisualRoot as Window;
+            if (hostWindow != null)
+            {
+                if (hostWindow.WindowState == WindowState.Normal)
+                {
+                    hostWindow.WindowState = WindowState.Maximized;
+                }
+                else
+                {
+                    hostWindow.WindowState = WindowState.Normal;
+                }
+            }
+        }
+
+        private void MinimizeWindow(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Window? hostWindow = VisualRoot as Window;
+            if (hostWindow != null)
+            {
+                hostWindow.WindowState = WindowState.Minimized;
+            }
+        }
+
+        private async void SubscribeToWindowState()
+        {
+            Window? hostWindow = VisualRoot as Window;
+            while (hostWindow == null)
+            {
+                hostWindow = VisualRoot as Window;
+                await Task.Delay(50);
+            }
+
+            hostWindow.GetObservable(Window.WindowStateProperty).Subscribe(s =>
+            {
+                if (s != WindowState.Maximized && _maximizeIcon != null)
+                {
+                    _maximizeIcon.Data = Geometry.Parse("M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z");
+                    hostWindow.Padding = new Thickness(0, 0, 0, 0);
+                }
+                if (s == WindowState.Maximized && _maximizeIcon != null)
+                {
+                    _maximizeIcon.Data = Geometry.Parse("M2048 1638h-410v410h-1638v-1638h410v-410h1638v1638zm-614-1024h-1229v1229h1229v-1229zm409-409h-1229v205h1024v1024h205v-1229z");
+                    hostWindow.Padding = new Thickness(7, 7, 7, 7);
+                }
+            });
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 5 - 0
fis/fis.Mac.csproj

@@ -5,6 +5,11 @@
     <Nullable>enable</Nullable>
 	  <AssemblyName>fis</AssemblyName>
   </PropertyGroup>
+  <ItemGroup>
+    <AvaloniaResource Include="flyinsono.ico">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </AvaloniaResource>
+  </ItemGroup>
   <ItemGroup>
     <PackageReference Include="Avalonia" Version="0.10.10" />
     <PackageReference Include="Avalonia.Desktop" Version="0.10.10" />

+ 6 - 1
fis/fis.Win.Dev.csproj

@@ -1,10 +1,15 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
     <TargetFramework>net6.0</TargetFramework>
     <Nullable>enable</Nullable>
     <AssemblyName>fis</AssemblyName>
   </PropertyGroup>
+  <ItemGroup>
+    <AvaloniaResource Include="flyinsono.ico">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </AvaloniaResource>
+  </ItemGroup>
   <ItemGroup>
 	<PackageReference Include="Avalonia" Version="0.10.10" />
     <PackageReference Include="Avalonia.Desktop" Version="0.10.10" />

+ 3 - 1
fis/fis.Win.csproj

@@ -14,7 +14,9 @@
 	<Copyright>VINNO Technology (Suzhou) Co., Ltd .</Copyright>
   </PropertyGroup>
   <ItemGroup>
-    <None Remove="flyinsono.ico" />
+    <AvaloniaResource Include="flyinsono.ico">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </AvaloniaResource>
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="flyinsono.ico">