fix(ui): diff viewer opens non-modal and maximizable
The diff viewer used ShowDialog at both call sites (task details, worktrees overview), blocking navigation of the rest of the app while open. Switch both to Window.Show(owner) and reuse the one open window (swap DataContext, Activate()) instead of stacking a second one on re-open. Add an opt-in maximize/restore button + double-click-titlebar toggle to ModalShell (ShowMaximizeButton, default false so other modals are untouched), wired on for DiffViewerView only. Reuses the existing Icon.WinMax/WinRestore geometry and OffScreenMargin inset from MainWindow's own maximize handling. The review gate (Approve & Merge locked until the diff is opened) already fires DiffViewed before the dialog/window is shown, so it is unaffected by the modal-to-non-modal change.
This commit is contained in:
@@ -14,14 +14,19 @@
|
||||
Background="{DynamicResource DeepBrush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" Margin="14,0">
|
||||
<TextBlock Text="{TemplateBinding Title}"
|
||||
FontFamily="{DynamicResource MonoFont}"
|
||||
FontSize="{DynamicResource FontSizeMono}"
|
||||
LetterSpacing="1.4"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="1" Classes="icon-btn" Content="✕"
|
||||
<Button Name="PART_MaxButton" Grid.Column="1" Classes="icon-btn"
|
||||
IsVisible="{TemplateBinding ShowMaximizeButton}"
|
||||
VerticalAlignment="Center">
|
||||
<PathIcon Name="PART_MaxIcon" Data="{StaticResource Icon.WinMax}" Width="10" Height="10"/>
|
||||
</Button>
|
||||
<Button Grid.Column="2" Classes="icon-btn" Content="✕"
|
||||
FontSize="{DynamicResource FontSizeBody}"
|
||||
Command="{TemplateBinding CloseCommand}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
@@ -3,6 +3,8 @@ using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.Controls;
|
||||
|
||||
@@ -18,17 +20,29 @@ public class ModalShell : ContentControl
|
||||
public static readonly StyledProperty<ICommand?> CloseCommandProperty =
|
||||
AvaloniaProperty.Register<ModalShell, ICommand?>(nameof(CloseCommand));
|
||||
|
||||
// Opt-in: most modals are fixed-purpose dialogs that don't need maximise. Set true only
|
||||
// on windows (e.g. the diff viewer) that benefit from a maximised full-screen layout.
|
||||
public static readonly StyledProperty<bool> ShowMaximizeButtonProperty =
|
||||
AvaloniaProperty.Register<ModalShell, bool>(nameof(ShowMaximizeButton));
|
||||
|
||||
public string? Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||
public object? Footer { get => GetValue(FooterProperty); set => SetValue(FooterProperty, value); }
|
||||
public ICommand? CloseCommand { get => GetValue(CloseCommandProperty); set => SetValue(CloseCommandProperty, value); }
|
||||
public bool ShowMaximizeButton { get => GetValue(ShowMaximizeButtonProperty); set => SetValue(ShowMaximizeButtonProperty, value); }
|
||||
|
||||
private Window? _window;
|
||||
private PathIcon? _maxIcon;
|
||||
private Button? _maxButton;
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
if (e.NameScope.Find<Border>("PART_TitleBar") is { } bar)
|
||||
bar.PointerPressed += OnTitleBarPressed;
|
||||
_maxButton = e.NameScope.Find<Button>("PART_MaxButton");
|
||||
if (_maxButton is not null) _maxButton.Click += OnMaxButtonClick;
|
||||
_maxIcon = e.NameScope.Find<PathIcon>("PART_MaxIcon");
|
||||
UpdateMaxIcon();
|
||||
}
|
||||
|
||||
/// Mirrors what MainWindow does for itself: with an extended client area a maximised window
|
||||
@@ -42,6 +56,7 @@ public class ModalShell : ContentControl
|
||||
if (_window is null) return;
|
||||
Margin = _window.OffScreenMargin;
|
||||
_window.PropertyChanged += OnWindowPropertyChanged;
|
||||
UpdateMaxIcon();
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
@@ -55,6 +70,32 @@ public class ModalShell : ContentControl
|
||||
{
|
||||
if (e.Property == Window.OffScreenMarginProperty && _window is not null)
|
||||
Margin = _window.OffScreenMargin;
|
||||
if (e.Property == Window.WindowStateProperty)
|
||||
UpdateMaxIcon();
|
||||
}
|
||||
|
||||
private void UpdateMaxIcon()
|
||||
{
|
||||
if (_maxIcon is null || _window is null) return;
|
||||
var maximized = _window.WindowState == WindowState.Maximized;
|
||||
var key = maximized ? "Icon.WinRestore" : "Icon.WinMax";
|
||||
if (this.TryFindResource(key, out var geometry) && geometry is Geometry g)
|
||||
_maxIcon.Data = g;
|
||||
if (_maxButton is not null)
|
||||
{
|
||||
var tipKey = maximized ? "modals.diff.restore" : "modals.diff.maximize";
|
||||
ToolTip.SetTip(_maxButton, ClaudeDo.Ui.Localization.Loc.T(tipKey));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMaxButtonClick(object? sender, RoutedEventArgs e) => ToggleMaximize();
|
||||
|
||||
private void ToggleMaximize()
|
||||
{
|
||||
if (_window is null) return;
|
||||
_window.WindowState = _window.WindowState == WindowState.Maximized
|
||||
? WindowState.Normal
|
||||
: WindowState.Maximized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,6 +115,11 @@ public class ModalShell : ContentControl
|
||||
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||
if (ShowMaximizeButton && e.ClickCount == 2)
|
||||
{
|
||||
ToggleMaximize();
|
||||
return;
|
||||
}
|
||||
(TopLevel.GetTopLevel(this) as Window)?.BeginMoveDrag(e);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user