Merge claudedo/2a790a4416924ed6beda0880dcf16836
This commit is contained in:
@@ -365,7 +365,9 @@
|
||||
"splitView": "Nebeneinander",
|
||||
"wrapLines": "Zeilenumbruch",
|
||||
"paneBase": "BASIS",
|
||||
"paneWorktree": "WORKTREE"
|
||||
"paneWorktree": "WORKTREE",
|
||||
"maximize": "Maximieren",
|
||||
"restore": "Wiederherstellen"
|
||||
},
|
||||
"worktreesOverview": {
|
||||
"refresh": "Aktualisieren",
|
||||
|
||||
@@ -365,7 +365,9 @@
|
||||
"splitView": "Side by side",
|
||||
"wrapLines": "Wrap lines",
|
||||
"paneBase": "BASE",
|
||||
"paneWorktree": "WORKTREE"
|
||||
"paneWorktree": "WORKTREE",
|
||||
"maximize": "Maximize",
|
||||
"restore": "Restore"
|
||||
},
|
||||
"worktreesOverview": {
|
||||
"refresh": "Refresh",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ public partial class DetailsIslandView : UserControl
|
||||
// never dragged stay dynamic (Auto-sized description).
|
||||
private readonly Dictionary<string, double> _descriptionHeights = new();
|
||||
private DetailsIslandViewModel? _vm;
|
||||
private DiffViewerView? _diffViewerWindow;
|
||||
|
||||
public DetailsIslandView()
|
||||
{
|
||||
@@ -142,12 +143,26 @@ public partial class DetailsIslandView : UserControl
|
||||
vm.PropertyChanged += OnViewModelPropertyChanged;
|
||||
ApplyResizeStateForCurrentTask();
|
||||
|
||||
vm.Merge.ShowDiffViewer = async (diffVm) =>
|
||||
// Non-modal: the diff viewer must not block navigating the rest of the app.
|
||||
// Reuse the one open window (swap its DataContext + bring to front) instead of
|
||||
// stacking a second one when the user re-opens the diff for another task.
|
||||
vm.Merge.ShowDiffViewer = (diffVm) =>
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
if (owner == null) return;
|
||||
var modal = new DiffViewerView { DataContext = diffVm };
|
||||
await modal.ShowDialog(owner);
|
||||
if (owner == null) return System.Threading.Tasks.Task.CompletedTask;
|
||||
if (_diffViewerWindow is { } existing)
|
||||
{
|
||||
existing.DataContext = diffVm;
|
||||
if (existing.WindowState == WindowState.Minimized)
|
||||
existing.WindowState = WindowState.Normal;
|
||||
existing.Activate();
|
||||
return System.Threading.Tasks.Task.CompletedTask;
|
||||
}
|
||||
var window = new DiffViewerView { DataContext = diffVm };
|
||||
window.Closed += (_, _) => _diffViewerWindow = null;
|
||||
_diffViewerWindow = window;
|
||||
window.Show(owner);
|
||||
return System.Threading.Tasks.Task.CompletedTask;
|
||||
};
|
||||
|
||||
vm.Merge.ShowMergeModal = async (mergeVm) =>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||
</Window.KeyBindings>
|
||||
|
||||
<ctl:ModalShell Title="{loc:Tr modals.diff.title}" CloseCommand="{Binding CloseCommand}">
|
||||
<ctl:ModalShell Title="{loc:Tr modals.diff.title}" CloseCommand="{Binding CloseCommand}" ShowMaximizeButton="True">
|
||||
<ctl:ModalShell.Footer>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
|
||||
@@ -27,6 +27,7 @@ public sealed class WindowDialogService : IDialogService
|
||||
{
|
||||
private readonly Window _owner;
|
||||
private MissionControlWindow? _missionControl;
|
||||
private DiffViewerView? _diffViewerWindow;
|
||||
|
||||
public WindowDialogService(Window owner) => _owner = owner;
|
||||
|
||||
@@ -108,10 +109,23 @@ public sealed class WindowDialogService : IDialogService
|
||||
};
|
||||
vm.ShowDiffAction = diffVm =>
|
||||
{
|
||||
_ = diffVm.LoadAsync();
|
||||
// Non-modal: must not block the (still-open) worktrees overview or the main
|
||||
// window behind it. Reuse the one open window rather than stacking a second.
|
||||
if (_diffViewerWindow is { } existing)
|
||||
{
|
||||
existing.DataContext = diffVm;
|
||||
diffVm.CloseAction = () => existing.Close();
|
||||
if (existing.WindowState == WindowState.Minimized)
|
||||
existing.WindowState = WindowState.Normal;
|
||||
existing.Activate();
|
||||
return;
|
||||
}
|
||||
var diffDlg = new DiffViewerView { DataContext = diffVm };
|
||||
diffVm.CloseAction = () => diffDlg.Close();
|
||||
_ = diffVm.LoadAsync();
|
||||
_ = diffDlg.ShowDialog(_owner);
|
||||
diffDlg.Closed += (_, _) => _diffViewerWindow = null;
|
||||
_diffViewerWindow = diffDlg;
|
||||
diffDlg.Show(_owner);
|
||||
};
|
||||
vm.ConfirmAction = ConfirmAsync;
|
||||
if (Shell is { } shell)
|
||||
|
||||
Reference in New Issue
Block a user