From 0fdd9d5faf4b8b8ed46c27b1671ac48ccccdc40c Mon Sep 17 00:00:00 2001 From: gramps Date: Sun, 1 Nov 2026 16:12:38 -0800 Subject: [PATCH] =?UTF-8?q?fix(brand):=20one=20logo,=20not=20two=20?= =?UTF-8?q?=E2=80=94=20and=20hit-testing=20follows=20what=20you=20see?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The brand exemption for the universal blur put a BrandLogoOverlay above the blur host while the canvas ItemsControl kept drawing the same element, so two logos sat on top of each other. - The canvas presenter for a brand logo is collapsed: BrandLogoOverlay is the only render, and it now carries the element's Opacity so the canvas copy is not missed. - Hit-testing follows the eye: a click inside the logo's bounds selects the logo, because it is the topmost layer in the compositor too. Without that you saw the logo on top and grabbed the webcam underneath it. - SelectedLayerPreviewHitTestingTests updated to the new contract (collapsed presenter, logo wins its bounds, list selection still works). 3 tests pass; Release 0 warnings. --- Controls/PreviewPane.xaml | 10 ++++++++ Controls/PreviewPane.xaml.cs | 7 ++++++ .../SelectedLayerPreviewHitTestingTests.cs | 25 +++++++++++++------ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Controls/PreviewPane.xaml b/Controls/PreviewPane.xaml index 97b092c..705f97a 100644 --- a/Controls/PreviewPane.xaml +++ b/Controls/PreviewPane.xaml @@ -90,6 +90,15 @@ @@ -476,6 +485,7 @@ diff --git a/Controls/PreviewPane.xaml.cs b/Controls/PreviewPane.xaml.cs index ff93a58..23f7155 100644 --- a/Controls/PreviewPane.xaml.cs +++ b/Controls/PreviewPane.xaml.cs @@ -677,6 +677,13 @@ public partial class PreviewPane : UserControl if (vm.SelectedElement is { } selected && IsHitTestable(selected) && Contains(selected, p)) return selected; + // The brand logo is drawn ABOVE everything (the compositor blits it after the blur group, + // and the preview renders it in BrandLogoOverlay), so a click inside it must select it — + // otherwise you see the logo on top and grab the webcam underneath it. + var brand = vm.StagedBrandLogo; + if (brand is { IsVisible: true } && Contains(brand, p)) + return brand; + for (var i = scene.Elements.Count - 1; i >= 0; i--) { var element = scene.Elements[i]; diff --git a/ytLive.Tests/SelectedLayerPreviewHitTestingTests.cs b/ytLive.Tests/SelectedLayerPreviewHitTestingTests.cs index d3117fe..d24c769 100644 --- a/ytLive.Tests/SelectedLayerPreviewHitTestingTests.cs +++ b/ytLive.Tests/SelectedLayerPreviewHitTestingTests.cs @@ -71,26 +71,35 @@ public sealed class SelectedLayerPreviewHitTestingTests var pointInCanvas = new Point(logo.X + 100, logo.Y + 60); var pointInWindow = canvasGrid.TransformToVisual(window).Transform(pointInCanvas); - Assert.True(ReferenceEquals(webcam, ElementAt(window.InputHitTest(pointInWindow)))); - Assert.Same(webcam, preview.HitElementAt(vm, pointInCanvas)); - Assert.Equal(0, Panel.GetZIndex(logoContainer)); + // The brand logo is drawn in BrandLogoOverlay above the blur host (2026-11-01), so its + // canvas presenter is collapsed and the overlay is what the eye sees. Hit-testing + // follows the eye: the logo wins a click inside its bounds. + Assert.Equal(Visibility.Collapsed, logoContainer.Visibility); + Assert.Same(logo, preview.HitElementAt(vm, pointInCanvas)); sourceList.SelectedItem = logo; window.UpdateLayout(); Assert.True(ReferenceEquals(logo, vm.SelectedElement), "Selecting the layer-list row must select the brand logo."); - Assert.True(Panel.GetZIndex(logoContainer) > Panel.GetZIndex(webcamContainer)); - Assert.Same(logo, ElementAt(window.InputHitTest(pointInWindow))); Assert.Same(logo, preview.HitElementAt(vm, pointInCanvas)); + + // …and with nothing selected the logo still wins inside its own bounds, because it is + // drawn above every layer. The webcam shares that rect in this test, so it is the + // covered case: you reach it from the layer list, not by clicking through the logo. + vm.SelectedElement = null; + window.UpdateLayout(); + Assert.Same(logo, preview.HitElementAt(vm, new Point(logo.X + 5, logo.Y + 5))); Assert.Equal(originalOrder, scene.Elements); sourceList.SelectedItem = null; window.UpdateLayout(); Assert.Null(vm.SelectedElement); - Assert.Equal(0, Panel.GetZIndex(logoContainer)); - Assert.Same(webcam, ElementAt(window.InputHitTest(pointInWindow))); - Assert.Same(webcam, preview.HitElementAt(vm, pointInCanvas)); + // The logo still owns a click inside its bounds after deselection (it is the topmost + // layer in both the compositor and the preview), and the canvas presenter stays + // collapsed because BrandLogoOverlay is what draws it. + Assert.Equal(Visibility.Collapsed, logoContainer.Visibility); + Assert.Same(logo, preview.HitElementAt(vm, pointInCanvas)); Assert.Equal(originalOrder, scene.Elements); } finally