using System; using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Platform; using Avalonia.VisualTree; namespace SourceGit.Views { public partial class Launcher : ChromelessWindow { public static readonly StyledProperty CaptionHeightProperty = AvaloniaProperty.Register(nameof(CaptionHeight)); public GridLength CaptionHeight { get => GetValue(CaptionHeightProperty); set => SetValue(CaptionHeightProperty, value); } public static readonly StyledProperty HasLeftCaptionButtonProperty = AvaloniaProperty.Register(nameof(HasLeftCaptionButton)); public bool HasLeftCaptionButton { get => GetValue(HasLeftCaptionButtonProperty); set => SetValue(HasLeftCaptionButtonProperty, value); } public bool HasRightCaptionButton { get { if (OperatingSystem.IsLinux()) return !Native.OS.UseSystemWindowFrame; return OperatingSystem.IsWindows(); } } public Launcher() { var layout = ViewModels.Preferences.Instance.Layout; if (layout.LauncherWindowState != WindowState.Maximized) { Width = layout.LauncherWidth; Height = layout.LauncherHeight; } if (OperatingSystem.IsMacOS()) { HasLeftCaptionButton = true; CaptionHeight = new GridLength(34); ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; } else if (UseSystemWindowFrame) { CaptionHeight = new GridLength(30); } else { CaptionHeight = new GridLength(38); } InitializeComponent(); } public void BringToTop() { if (WindowState == WindowState.Minimized) WindowState = _lastWindowState; else Activate(); } public bool HasKeyModifier(KeyModifiers modifier) { return _unhandledModifiers.HasFlag(modifier); } public void ClearKeyModifier() { _unhandledModifiers = KeyModifiers.None; } protected override void OnOpened(EventArgs e) { base.OnOpened(e); var state = ViewModels.Preferences.Instance.Layout.LauncherWindowState; if (state == WindowState.Maximized || state == WindowState.FullScreen) WindowState = WindowState.Maximized; } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == WindowStateProperty) { _lastWindowState = (WindowState)change.OldValue!; var state = (WindowState)change.NewValue!; if (!OperatingSystem.IsMacOS() && !UseSystemWindowFrame) CaptionHeight = new GridLength(state == WindowState.Maximized ? 30 : 38); if (OperatingSystem.IsMacOS()) HasLeftCaptionButton = state != WindowState.FullScreen; ViewModels.Preferences.Instance.Layout.LauncherWindowState = state; } } protected override void OnKeyDown(KeyEventArgs e) { var vm = DataContext as ViewModels.Launcher; if (vm == null) return; // We should clear all unhandled key modifiers. _unhandledModifiers = KeyModifiers.None; // Check for AltGr (which is detected as Ctrl+Alt) bool isAltGr = e.KeyModifiers.HasFlag(KeyModifiers.Control) && e.KeyModifiers.HasFlag(KeyModifiers.Alt); // Skip hotkey processing if AltGr is pressed if (isAltGr) { base.OnKeyDown(e); return; } // Ctrl+, opens preference dialog (macOS use hotkeys in system menu bar) if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: KeyModifiers.Control, Key: Key.OemComma }) { App.ShowWindow(new Preferences(), true); e.Handled = true; return; } // F1 opens preference dialog (macOS use hotkeys in system menu bar) if (!OperatingSystem.IsMacOS() && e.Key == Key.F1) { App.ShowWindow(new Hotkeys(), true); return; } // Ctrl+Q quits the application (macOS use hotkeys in system menu bar) if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: KeyModifiers.Control, Key: Key.Q }) { App.Quit(0); return; } if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control)) { if (e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.P) { vm.OpenWorkspaceSwitcher(); e.Handled = true; return; } if (e.Key == Key.P) { vm.OpenTabSwitcher(); e.Handled = true; return; } if (e.Key == Key.W) { vm.CloseTab(null); e.Handled = true; return; } if (e.Key == Key.T) { vm.AddNewTab(); e.Handled = true; return; } if (e.Key == Key.N) { if (vm.ActivePage.Data is not ViewModels.Welcome) vm.AddNewTab(); ViewModels.Welcome.Instance.Clone(); e.Handled = true; return; } if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Right) || (!OperatingSystem.IsMacOS() && !e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.Tab)) { vm.GotoNextTab(); e.Handled = true; return; } if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Left) || (!OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.Tab)) { vm.GotoPrevTab(); e.Handled = true; return; } if (vm.ActivePage.Data is ViewModels.Repository repo) { if (e.Key == Key.D1 || e.Key == Key.NumPad1) { repo.SelectedViewIndex = 0; e.Handled = true; return; } if (e.Key == Key.D2 || e.Key == Key.NumPad2) { repo.SelectedViewIndex = 1; e.Handled = true; return; } if (e.Key == Key.D3 || e.Key == Key.NumPad3) { repo.SelectedViewIndex = 2; e.Handled = true; return; } if (e.Key == Key.F) { repo.IsSearching = true; e.Handled = true; return; } if (e.Key == Key.H && e.KeyModifiers.HasFlag(KeyModifiers.Shift)) { repo.IsSearching = false; e.Handled = true; return; } } else { var welcome = this.FindDescendantOfType(); if (welcome != null) { if (e.Key == Key.F) { welcome.SearchBox.Focus(); e.Handled = true; return; } } } } else if (e.Key == Key.Escape) { if (vm.Switcher != null) vm.CancelSwitcher(); else vm.ActivePage.CancelPopup(); e.Handled = true; return; } else if (e.Key == Key.F5) { if (vm.ActivePage.Data is ViewModels.Repository repo) { repo.RefreshAll(); e.Handled = true; return; } } base.OnKeyDown(e); // Record unhandled key modifers. if (!e.Handled) { _unhandledModifiers = e.KeyModifiers; if (!_unhandledModifiers.HasFlag(KeyModifiers.Alt) && e.Key is Key.LeftAlt or Key.RightAlt) _unhandledModifiers |= KeyModifiers.Alt; if (!_unhandledModifiers.HasFlag(KeyModifiers.Control) && e.Key is Key.LeftCtrl or Key.RightCtrl) _unhandledModifiers |= KeyModifiers.Control; if (!_unhandledModifiers.HasFlag(KeyModifiers.Shift) && e.Key is Key.LeftShift or Key.RightShift) _unhandledModifiers |= KeyModifiers.Shift; } } protected override void OnKeyUp(KeyEventArgs e) { base.OnKeyUp(e); _unhandledModifiers = KeyModifiers.None; } protected override void OnClosing(WindowClosingEventArgs e) { base.OnClosing(e); if (!Design.IsDesignMode && DataContext is ViewModels.Launcher launcher) launcher.Quit(Width, Height); } private void OnOpenWorkspaceMenu(object sender, RoutedEventArgs e) { if (sender is Button btn && DataContext is ViewModels.Launcher launcher) { var menu = launcher.CreateContextForWorkspace(); menu?.Open(btn); } e.Handled = true; } private void OnCancelSwitcher(object sender, PointerPressedEventArgs e) { if (e.Source == sender) (DataContext as ViewModels.Launcher)?.CancelSwitcher(); e.Handled = true; } private KeyModifiers _unhandledModifiers = KeyModifiers.None; private WindowState _lastWindowState = WindowState.Normal; } }