refactor(ui): clean up task-row drag visuals
Collapse the grabbed row out of layout instead of overlapping neighbors with scale/opacity/shadow (the ghost window already shows the moving snapshot). Replace the thin moss drop-hint lines with a dashed placeholder gap sized like a collapsed row. Suppress the ordinary hover highlight and transitions on whatever row is currently the drop target so it doesn't read as "clickable" while dragging.
This commit is contained in:
@@ -484,12 +484,21 @@
|
|||||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
</Style>
|
</Style>
|
||||||
<!-- "Grabbed" row: lift + slight scale + lower opacity + shadow while the custom drag runs. -->
|
<!-- "Grabbed" row: collapses out of layout entirely while the custom drag runs — the
|
||||||
|
floating ghost snapshot (TaskDragController) is what the user sees moving. -->
|
||||||
<Style Selector="Border.task-row.dragging">
|
<Style Selector="Border.task-row.dragging">
|
||||||
<Setter Property="Opacity" Value="0.55" />
|
<Setter Property="IsVisible" Value="False" />
|
||||||
<Setter Property="RenderTransform" Value="scale(1.03)" />
|
<Setter Property="Transitions" Value="{x:Null}" />
|
||||||
<Setter Property="BoxShadow" Value="0 10 26 0 #66000000" />
|
</Style>
|
||||||
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
|
|
||||||
|
<!-- Row currently hovered while dragging (shows the dashed drop-hint gap): suppress the
|
||||||
|
ordinary hover highlight and transitions so they don't fight the hint. Placed after
|
||||||
|
Border.task-row:pointerover so its extra class also wins on declaration order. -->
|
||||||
|
<Style Selector="Border.task-row.drop-target">
|
||||||
|
<Setter Property="Transitions" Value="{x:Null}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Border.task-row.drop-target:pointerover">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource LineBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- Checkbox indicator (the 18px circle that replaces the native CheckBox template) -->
|
<!-- Checkbox indicator (the 18px circle that replaces the native CheckBox template) -->
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
|||||||
// Set by the custom drag while this row is being dragged — drives the "grabbed" row style.
|
// Set by the custom drag while this row is being dragged — drives the "grabbed" row style.
|
||||||
[ObservableProperty] private bool _isDragging;
|
[ObservableProperty] private bool _isDragging;
|
||||||
|
|
||||||
|
// True while a drag is hovering this row (i.e. it would show a drop-hint gap). Used to
|
||||||
|
// suppress the ordinary hover highlight/transitions so they don't fight the hint.
|
||||||
|
public bool IsDropTarget => DropHintAbove || DropHintBelow;
|
||||||
|
|
||||||
public bool CanRefine => Status == TaskStatus.Idle && PlanningPhase == PlanningPhase.None
|
public bool CanRefine => Status == TaskStatus.Idle && PlanningPhase == PlanningPhase.None
|
||||||
&& !IsRefining && !IsManual;
|
&& !IsRefining && !IsManual;
|
||||||
|
|
||||||
@@ -289,6 +293,8 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
|||||||
partial void OnDiffAdditionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffAdditionsText)); }
|
partial void OnDiffAdditionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffAdditionsText)); }
|
||||||
partial void OnDiffDeletionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffDeletionsText)); }
|
partial void OnDiffDeletionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffDeletionsText)); }
|
||||||
partial void OnRoadblockCountChanged(int value) { OnPropertyChanged(nameof(HasRoadblock)); OnPropertyChanged(nameof(RoadblockTooltip)); }
|
partial void OnRoadblockCountChanged(int value) { OnPropertyChanged(nameof(HasRoadblock)); OnPropertyChanged(nameof(RoadblockTooltip)); }
|
||||||
|
partial void OnDropHintAboveChanged(bool value) => OnPropertyChanged(nameof(IsDropTarget));
|
||||||
|
partial void OnDropHintBelowChanged(bool value) => OnPropertyChanged(nameof(IsDropTarget));
|
||||||
|
|
||||||
public void RefreshLocalized()
|
public void RefreshLocalized()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,15 +7,16 @@
|
|||||||
x:DataType="vm:TaskRowViewModel">
|
x:DataType="vm:TaskRowViewModel">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="6"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Above-row indicator: lives in the 6px gap between cards -->
|
<!-- Above-row indicator: dashed placeholder gap showing where the dragged row will land -->
|
||||||
<Border Grid.Row="0" Height="2" VerticalAlignment="Center" Margin="4,0"
|
<Grid Grid.Row="0" Height="52" IsVisible="{Binding DropHintAbove}">
|
||||||
Background="{DynamicResource MossBrush}" CornerRadius="1"
|
<Rectangle Margin="4,3" Stroke="{DynamicResource MossBrush}" StrokeThickness="1.5"
|
||||||
IsVisible="{Binding DropHintAbove}"/>
|
StrokeDashArray="4,3" Fill="Transparent" RadiusX="8" RadiusY="8"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<!-- Indent wrapper: col 0 = 24px child indent track, col 1 = content -->
|
<!-- Indent wrapper: col 0 = 24px child indent track, col 1 = content -->
|
||||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
|
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
Margin="0"
|
Margin="0"
|
||||||
Classes.selected="{Binding IsSelected}"
|
Classes.selected="{Binding IsSelected}"
|
||||||
Classes.dragging="{Binding IsDragging}"
|
Classes.dragging="{Binding IsDragging}"
|
||||||
|
Classes.drop-target="{Binding IsDropTarget}"
|
||||||
Classes.done="{Binding Done}"
|
Classes.done="{Binding Done}"
|
||||||
ContextRequested="OnRowContextRequested">
|
ContextRequested="OnRowContextRequested">
|
||||||
<Grid ColumnDefinitions="0,18,32,*,Auto,Auto,32" Margin="6,8,10,8">
|
<Grid ColumnDefinitions="0,18,32,*,Auto,Auto,32" Margin="6,8,10,8">
|
||||||
@@ -202,9 +204,9 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Below-row indicator: only expands when visible (used for the last row of a section) -->
|
<!-- Below-row indicator: only expands when visible (used for the last row of a section) -->
|
||||||
<Grid Grid.Row="2" Height="6" IsVisible="{Binding DropHintBelow}">
|
<Grid Grid.Row="2" Height="52" IsVisible="{Binding DropHintBelow}">
|
||||||
<Border Height="2" VerticalAlignment="Center" Margin="4,0"
|
<Rectangle Margin="4,3" Stroke="{DynamicResource MossBrush}" StrokeThickness="1.5"
|
||||||
Background="{DynamicResource MossBrush}" CornerRadius="1"/>
|
StrokeDashArray="4,3" Fill="Transparent" RadiusX="8" RadiusY="8"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Hidden schedule anchor (its Flyout is shown from the context menu) -->
|
<!-- Hidden schedule anchor (its Flyout is shown from the context menu) -->
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using ClaudeDo.Data.Models;
|
using ClaudeDo.Data.Models;
|
||||||
using ClaudeDo.Ui.ViewModels.Islands;
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -20,4 +21,25 @@ public class TaskRowViewModelTests
|
|||||||
vm.Status = s;
|
vm.Status = s;
|
||||||
Assert.Equal(expected, vm.StatusChipClass);
|
Assert.Equal(expected, vm.StatusChipClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsDropTarget_Follows_Either_DropHint_And_Raises_PropertyChanged()
|
||||||
|
{
|
||||||
|
var vm = new TaskRowViewModel { Id = "t" };
|
||||||
|
var raised = new List<string?>();
|
||||||
|
vm.PropertyChanged += (_, e) => raised.Add(e.PropertyName);
|
||||||
|
|
||||||
|
Assert.False(vm.IsDropTarget);
|
||||||
|
|
||||||
|
vm.DropHintAbove = true;
|
||||||
|
Assert.True(vm.IsDropTarget);
|
||||||
|
Assert.Contains(nameof(TaskRowViewModel.IsDropTarget), raised);
|
||||||
|
|
||||||
|
vm.DropHintAbove = false;
|
||||||
|
vm.DropHintBelow = true;
|
||||||
|
Assert.True(vm.IsDropTarget);
|
||||||
|
|
||||||
|
vm.DropHintBelow = false;
|
||||||
|
Assert.False(vm.IsDropTarget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user