fix(ui): manual modal dragging, maximize/restore icon, day-toggle style
- Drive modal title-bar dragging manually via pointer capture + Window.Position; Avalonia 12's BeginMoveDrag and VisualRoot-as-Window cast no longer work (VisualRoot is a TopLevelHost). Applies to ModalShell and WorktreeModalView. - Toggle the MainWindow maximize button between maximize/restore glyphs on WindowState changes (adds Icon.WinRestore geometry). - Add the ToggleButton.day-toggle style used by the Prime weekday picker row. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
<!-- Window control icons — filled geometries (PathIcon fills, not strokes) -->
|
<!-- Window control icons — filled geometries (PathIcon fills, not strokes) -->
|
||||||
<StreamGeometry x:Key="Icon.WinMin">M4 9 H16 V11 H4 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.WinMin">M4 9 H16 V11 H4 Z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icon.WinMax">M4 4 H16 V6 H4 Z M4 14 H16 V16 H4 Z M4 4 H6 V16 H4 Z M14 4 H16 V16 H14 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.WinMax">M4 4 H16 V6 H4 Z M4 14 H16 V16 H4 Z M4 4 H6 V16 H4 Z M14 4 H16 V16 H14 Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="Icon.WinRestore">M4 7 H13 V9 H4 Z M4 14 H13 V16 H4 Z M4 7 H6 V16 H4 Z M11 7 H13 V16 H11 Z M7 4 H16 V6 H7 Z M14 4 H16 V13 H14 Z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icon.WinClose">M4 5 L5 4 L16 15 L15 16 Z M15 4 L16 5 L5 16 L4 15 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.WinClose">M4 5 L5 4 L16 15 L15 16 Z M15 4 L16 5 L5 16 L4 15 Z</StreamGeometry>
|
||||||
<!-- Brand check glyph — filled rounded square with inset tick -->
|
<!-- Brand check glyph — filled rounded square with inset tick -->
|
||||||
<StreamGeometry x:Key="Icon.BrandCheck">M3 3 H21 V21 H3 Z M6 12 L7 11 L10 14 L17 7 L18 8 L10 16 Z</StreamGeometry>
|
<StreamGeometry x:Key="Icon.BrandCheck">M3 3 H21 V21 H3 Z M6 12 L7 11 L10 14 L17 7 L18 8 L10 16 Z</StreamGeometry>
|
||||||
@@ -1034,4 +1035,23 @@
|
|||||||
<Setter Property="FontWeight" Value="SemiBold" />
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- DAY TOGGLE -->
|
||||||
|
<!-- Small ToggleButton for weekday pickers (Prime schedule row) -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<Style Selector="ToggleButton.day-toggle">
|
||||||
|
<Setter Property="MinWidth" Value="34"/>
|
||||||
|
<Setter Property="Padding" Value="6,4"/>
|
||||||
|
<Setter Property="Margin" Value="0"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Background" Value="{DynamicResource DeepBrush}"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource LineBrush}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="CornerRadius" Value="4"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ToggleButton.day-toggle:checked /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentBrush}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
@@ -23,16 +22,49 @@ public class ModalShell : ContentControl
|
|||||||
public object? Footer { get => GetValue(FooterProperty); set => SetValue(FooterProperty, value); }
|
public object? Footer { get => GetValue(FooterProperty); set => SetValue(FooterProperty, value); }
|
||||||
public ICommand? CloseCommand { get => GetValue(CloseCommandProperty); set => SetValue(CloseCommandProperty, value); }
|
public ICommand? CloseCommand { get => GetValue(CloseCommandProperty); set => SetValue(CloseCommandProperty, value); }
|
||||||
|
|
||||||
|
private Window? _window;
|
||||||
|
private PixelPoint _dragStartScreen;
|
||||||
|
private PixelPoint _dragStartPos;
|
||||||
|
private bool _dragging;
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
if (e.NameScope.Find<Border>("PART_TitleBar") is { } bar)
|
if (e.NameScope.Find<Border>("PART_TitleBar") is { } bar)
|
||||||
|
{
|
||||||
bar.PointerPressed += OnTitleBarPressed;
|
bar.PointerPressed += OnTitleBarPressed;
|
||||||
|
bar.PointerMoved += OnTitleBarMoved;
|
||||||
|
bar.PointerReleased += OnTitleBarReleased;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VisualRoot is a TopLevelHost (not the Window) in Avalonia 12, so resolve the
|
||||||
|
// owning Window via TopLevel.GetTopLevel and drive the move manually — BeginMoveDrag
|
||||||
|
// and a VisualRoot-as-Window cast both fail here.
|
||||||
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && VisualRoot is Window w)
|
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||||
w.BeginMoveDrag(e);
|
_window = TopLevel.GetTopLevel(this) as Window;
|
||||||
|
if (_window is null) return;
|
||||||
|
_dragStartScreen = _window.PointToScreen(e.GetPosition(_window));
|
||||||
|
_dragStartPos = _window.Position;
|
||||||
|
_dragging = true;
|
||||||
|
e.Pointer.Capture(sender as IInputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTitleBarMoved(object? sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_dragging || _window is null
|
||||||
|
|| !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||||
|
var cur = _window.PointToScreen(e.GetPosition(_window));
|
||||||
|
_window.Position = new PixelPoint(
|
||||||
|
_dragStartPos.X + (cur.X - _dragStartScreen.X),
|
||||||
|
_dragStartPos.Y + (cur.Y - _dragStartScreen.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTitleBarReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
_dragging = false;
|
||||||
|
e.Pointer.Capture(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
<PathIcon Data="{StaticResource Icon.WinMin}" Width="10" Height="10"/>
|
<PathIcon Data="{StaticResource Icon.WinMin}" Width="10" Height="10"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Classes="title-ctrl" Click="OnToggleMax">
|
<Button Classes="title-ctrl" Click="OnToggleMax">
|
||||||
<PathIcon Data="{StaticResource Icon.WinMax}" Width="10" Height="10"/>
|
<PathIcon x:Name="MaxIcon" Data="{StaticResource Icon.WinMax}" Width="10" Height="10"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Classes="title-ctrl close" Click="OnClose">
|
<Button Classes="title-ctrl close" Click="OnClose">
|
||||||
<PathIcon Data="{StaticResource Icon.WinClose}" Width="10" Height="10"/>
|
<PathIcon Data="{StaticResource Icon.WinClose}" Width="10" Height="10"/>
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ public partial class MainWindow : Window
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
KeyDown += OnWindowKeyDown;
|
KeyDown += OnWindowKeyDown;
|
||||||
DataContextChanged += OnDataContextChanged;
|
DataContextChanged += OnDataContextChanged;
|
||||||
|
UpdateMaxIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
|
{
|
||||||
|
base.OnPropertyChanged(change);
|
||||||
|
if (change.Property == WindowStateProperty)
|
||||||
|
UpdateMaxIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
private void OnDataContextChanged(object? sender, EventArgs e)
|
||||||
|
|||||||
@@ -28,7 +28,10 @@
|
|||||||
|
|
||||||
<!-- Title strip -->
|
<!-- Title strip -->
|
||||||
<Border DockPanel.Dock="Top" Height="36"
|
<Border DockPanel.Dock="Top" Height="36"
|
||||||
PointerPressed="OnTitleBarPressed">
|
Background="Transparent"
|
||||||
|
PointerPressed="OnTitleBarPressed"
|
||||||
|
PointerMoved="OnTitleBarMoved"
|
||||||
|
PointerReleased="OnTitleBarReleased">
|
||||||
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
|
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
|
||||||
<TextBlock Grid.Column="0" Text="Worktree" VerticalAlignment="Center"
|
<TextBlock Grid.Column="0" Text="Worktree" VerticalAlignment="Center"
|
||||||
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeBody}"
|
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeBody}"
|
||||||
|
|||||||
@@ -66,9 +66,31 @@ public partial class WorktreeModalView : Window
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PixelPoint _dragStartScreen;
|
||||||
|
private PixelPoint _dragStartPos;
|
||||||
|
private bool _dragging;
|
||||||
|
|
||||||
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||||
BeginMoveDrag(e);
|
_dragStartScreen = this.PointToScreen(e.GetPosition(this));
|
||||||
|
_dragStartPos = Position;
|
||||||
|
_dragging = true;
|
||||||
|
e.Pointer.Capture(sender as IInputElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTitleBarMoved(object? sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_dragging || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||||
|
var cur = this.PointToScreen(e.GetPosition(this));
|
||||||
|
Position = new PixelPoint(
|
||||||
|
_dragStartPos.X + (cur.X - _dragStartScreen.X),
|
||||||
|
_dragStartPos.Y + (cur.Y - _dragStartScreen.Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTitleBarReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
_dragging = false;
|
||||||
|
e.Pointer.Capture(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user