fix(border-color): keep ComboBox popup clicks inside LeftPanel (TASK 63)
This commit is contained in:
@@ -132,9 +132,107 @@ public sealed class BorderColorPickerTests
|
||||
$"the drop-down rows do not show the colour's name: '{rows}'");
|
||||
|
||||
window.Close();
|
||||
|
||||
// ── 4. clicking a row inside the open popup stays inside LeftPanel ───────
|
||||
// MainWindow_PreviewMouseLeftButtonDown deselects SelectedElement (exiting
|
||||
// the properties listing) whenever LeftPanel.IsClickInside(e.OriginalSource)
|
||||
// returns false. A ComboBox's PART_Popup lives in its own PopupRoot visual
|
||||
// tree whose VisualTreeHelper.GetParent is null — the only link back to the
|
||||
// ComboBox and ElementsScrollViewer is PopupRoot's LOGICAL parent.
|
||||
panel.DataContext = new PanelContext { SelectedElement = element };
|
||||
var hostWindow = new Window { Width = 320, Height = 720, Content = panel };
|
||||
hostWindow.Show();
|
||||
panel.UpdateLayout();
|
||||
Dispatcher.CurrentDispatcher.Invoke(() => { }, DispatcherPriority.Loaded);
|
||||
|
||||
var realCombo = FindVisualChildren<ComboBox>(panel)
|
||||
.Single(c => c.ItemsSource is IEnumerable<NamedColor> opts && opts.Any(o => o.Hex == "#e94560"));
|
||||
realCombo.IsDropDownOpen = true;
|
||||
realCombo.UpdateLayout();
|
||||
Dispatcher.CurrentDispatcher.Invoke(() => { }, DispatcherPriority.Loaded);
|
||||
|
||||
var crimsonEntry = BorderColorPalette.EntryFor("#e94560");
|
||||
var crimsonItem = realCombo.ItemContainerGenerator.ContainerFromItem(crimsonEntry) as ComboBoxItem;
|
||||
Assert.NotNull(crimsonItem);
|
||||
|
||||
// The click target inside the popup row (e.g. a TextBlock inside the item)
|
||||
// must be recognised as inside LeftPanel, or MainWindow clears SelectedElement.
|
||||
var clickTarget = FindVisualChildren<TextBlock>(crimsonItem!).FirstOrDefault()
|
||||
?? (DependencyObject)crimsonItem!;
|
||||
Assert.True(panel.IsClickInside(clickTarget),
|
||||
"clicking a colour row in the open ComboBox popup is treated as a click outside LeftPanel (exits properties)");
|
||||
|
||||
// Clicking the row in the open popup updates the webcam's BorderColor and closes the popup.
|
||||
crimsonItem!.RaiseEvent(new System.Windows.Input.MouseButtonEventArgs(
|
||||
System.Windows.Input.Mouse.PrimaryDevice, 0, System.Windows.Input.MouseButton.Left)
|
||||
{
|
||||
RoutedEvent = UIElement.MouseLeftButtonDownEvent,
|
||||
Source = clickTarget,
|
||||
});
|
||||
crimsonItem.RaiseEvent(new System.Windows.Input.MouseButtonEventArgs(
|
||||
System.Windows.Input.Mouse.PrimaryDevice, 0, System.Windows.Input.MouseButton.Left)
|
||||
{
|
||||
RoutedEvent = UIElement.MouseLeftButtonUpEvent,
|
||||
Source = clickTarget,
|
||||
});
|
||||
realCombo.UpdateLayout();
|
||||
Assert.Equal("#e94560", element.BorderColor);
|
||||
Assert.Equal(crimsonEntry, realCombo.SelectedItem);
|
||||
Assert.Contains("Crimson", Texts(realCombo));
|
||||
Assert.False(realCombo.IsDropDownOpen);
|
||||
|
||||
// And when starting from a typed custom hex (#123456), picking a palette
|
||||
// row in the open popup still lands cleanly on that palette colour.
|
||||
element.BorderColor = "#123456";
|
||||
realCombo.IsDropDownOpen = true;
|
||||
realCombo.UpdateLayout();
|
||||
Dispatcher.CurrentDispatcher.Invoke(() => { }, DispatcherPriority.Loaded);
|
||||
|
||||
var emeraldEntry = BorderColorPalette.EntryFor("#22c55e");
|
||||
var emeraldItem = realCombo.ItemContainerGenerator.ContainerFromItem(emeraldEntry) as ComboBoxItem;
|
||||
Assert.NotNull(emeraldItem);
|
||||
var emeraldTarget = FindVisualChildren<TextBlock>(emeraldItem!).FirstOrDefault()
|
||||
?? (DependencyObject)emeraldItem!;
|
||||
Assert.True(panel.IsClickInside(emeraldTarget));
|
||||
emeraldItem!.RaiseEvent(new System.Windows.Input.MouseButtonEventArgs(
|
||||
System.Windows.Input.Mouse.PrimaryDevice, 0, System.Windows.Input.MouseButton.Left)
|
||||
{
|
||||
RoutedEvent = UIElement.MouseLeftButtonDownEvent,
|
||||
Source = emeraldTarget,
|
||||
});
|
||||
emeraldItem.RaiseEvent(new System.Windows.Input.MouseButtonEventArgs(
|
||||
System.Windows.Input.Mouse.PrimaryDevice, 0, System.Windows.Input.MouseButton.Left)
|
||||
{
|
||||
RoutedEvent = UIElement.MouseLeftButtonUpEvent,
|
||||
Source = emeraldTarget,
|
||||
});
|
||||
realCombo.UpdateLayout();
|
||||
Assert.Equal("#22c55e", element.BorderColor);
|
||||
Assert.Equal(emeraldEntry, realCombo.SelectedItem);
|
||||
Assert.Contains("Emerald", Texts(realCombo));
|
||||
|
||||
hostWindow.Close();
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class PanelContext
|
||||
{
|
||||
public SceneElement? SelectedElement { get; set; }
|
||||
public bool IsWebcamSelected => true;
|
||||
public bool CanToggleClipShape => true;
|
||||
}
|
||||
|
||||
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject root) where T : DependencyObject
|
||||
{
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(root, i);
|
||||
if (child is T match) yield return match;
|
||||
foreach (var nested in FindVisualChildren<T>(child))
|
||||
yield return nested;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Every text in a visual tree, so a template's content can be read back.</summary>
|
||||
private static string Texts(DependencyObject root)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user