Files
ClaudeDo/src/ClaudeDo.Ui/Views/MainWindow.axaml.cs
T
mika kuns 0892883a82 feat(ui): confirm main-window close when Mission Control sessions are open
Closing the main window used to silently kill every open Mission Control
ConPTY session (App's ShutdownMode.OnMainWindowClose tears the process down
without warning). Intercept Window.Closing, and when at least one pane is
open, ask via the existing ConfirmAsync pattern before proceeding; cancelling
leaves the window and sessions alive, confirming force-closes as before.
2026-08-21 18:14:48 +02:00

117 lines
3.9 KiB
C#

using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels;
using ClaudeDo.Ui.ViewModels.Islands;
using ClaudeDo.Ui.ViewModels.Modals;
using ClaudeDo.Ui.Views.Modals;
namespace ClaudeDo.Ui.Views;
public partial class MainWindow : Window
{
private bool _forceClose;
public MainWindow()
{
InitializeComponent();
KeyDown += OnWindowKeyDown;
DataContextChanged += OnDataContextChanged;
UpdateMaxIcon();
}
// Closing the main window kills every open Mission Control ConPTY session without
// warning (App.axaml.cs: ShutdownMode.OnMainWindowClose). Ask first — never silently.
protected override void OnClosing(WindowClosingEventArgs e)
{
if (!_forceClose
&& DataContext is IslandsShellViewModel { Dialogs: { } dialogs } vm
&& IslandsShellViewModel.RequiresCloseConfirmation(vm.OpenMissionControlSessionCount))
{
e.Cancel = true;
_ = ConfirmCloseWithOpenSessionsAsync(dialogs, vm.OpenMissionControlSessionCount);
return;
}
base.OnClosing(e);
}
private async Task ConfirmCloseWithOpenSessionsAsync(IDialogService dialogs, int openSessionCount)
{
var confirmed = await dialogs.ConfirmAsync(Loc.T("vm.mainWindow.closeConfirm.message", openSessionCount));
if (!confirmed) return;
_forceClose = true;
Close();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == WindowStateProperty)
{
UpdateMaxIcon();
if (DataContext is IslandsShellViewModel vm && vm.Tasks is not null)
vm.Tasks.IsWindowVisible = WindowState != WindowState.Minimized;
}
if (change.Property == OffScreenMarginProperty)
RootGrid.Margin = OffScreenMargin;
}
private void UpdateMaxIcon()
{
var key = WindowState == WindowState.Maximized ? "Icon.WinRestore" : "Icon.WinMax";
if (this.TryFindResource(key, out var geometry) && geometry is Geometry g)
MaxIcon.Data = g;
}
private void OnDataContextChanged(object? sender, EventArgs e)
{
if (DataContext is IslandsShellViewModel vm)
{
vm.Dialogs = new WindowDialogService(this);
vm.BringToFront = () =>
{
if (WindowState == WindowState.Minimized)
WindowState = WindowState.Normal;
Activate();
};
if (vm.Tasks is not null)
vm.Tasks.IsWindowVisible = WindowState != WindowState.Minimized;
}
}
private void OnWindowKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Space
&& FocusManager?.GetFocusedElement() is not TextBox
&& DataContext is IslandsShellViewModel vm)
{
e.Handled = true;
_ = vm.ToggleSelectedDoneAsync();
}
}
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
BeginMoveDrag(e);
}
private void OnMinimize(object? s, Avalonia.Interactivity.RoutedEventArgs e) =>
WindowState = WindowState.Minimized;
private void OnToggleMax(object? s, Avalonia.Interactivity.RoutedEventArgs e) =>
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
private void OnClose(object? s, Avalonia.Interactivity.RoutedEventArgs e) => Close();
protected override void OnSizeChanged(SizeChangedEventArgs e)
{
base.OnSizeChanged(e);
if (DataContext is IslandsShellViewModel vm) vm.WindowWidth = Bounds.Width;
}
}