diff --git a/Controls/PreviewPane.xaml b/Controls/PreviewPane.xaml index 98b20fc..9f96054 100644 --- a/Controls/PreviewPane.xaml +++ b/Controls/PreviewPane.xaml @@ -71,6 +71,15 @@ Visibility="{Binding BackgroundVisible, Converter={StaticResource BoolToVis}}"/> + + + diff --git a/Controls/PreviewPane.xaml.cs b/Controls/PreviewPane.xaml.cs index a8a480c..b501f7d 100644 --- a/Controls/PreviewPane.xaml.cs +++ b/Controls/PreviewPane.xaml.cs @@ -47,6 +47,7 @@ public partial class PreviewPane : UserControl newVm.PreviewTransition += PlayPreviewTransition; UpdateZoomBox(); UpdateSelectedLayerZOrder(); + HookBlurPreview(); } } @@ -737,4 +738,95 @@ public partial class PreviewPane : UserControl if (DataContext is MainViewModel vm) vm.ToggleSettingsDrawer(); e.Handled = true; } + + // ─── Privacy blur preview (2026-11-01) ─── + // The editor has to show the blur it is about to bake into the output: "whatever is beneath the + // blur layer is blurred". Each blur becomes a rectangle/ellipse filled with a VisualBrush of the + // whole BackdropLayer (desktop + background art) sampled at that rect, with a BlurEffect at the + // same strength the compositor uses. The host canvas sits between the backdrop and the element + // layers, which is the model's own z-rule. + private MainViewModel? _blurVm; + private Scene? _blurScene; + + private void HookBlurPreview() + { + if (_blurVm != null) _blurVm.PropertyChanged -= BlurViewModelChanged; + _blurVm = DataContext as MainViewModel; + if (_blurVm != null) _blurVm.PropertyChanged += BlurViewModelChanged; + HookBlurScene(); + } + + private void BlurViewModelChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(MainViewModel.StagedScene)) HookBlurScene(); + else if (e.PropertyName is nameof(MainViewModel.ActiveBackgroundImage) + or nameof(MainViewModel.BackgroundImage)) RebuildBlurPreview(); + } + + private void HookBlurScene() + { + if (_blurScene != null) + { + _blurScene.Elements.CollectionChanged -= BlurElementsChanged; + foreach (var element in _blurScene.Elements) element.PropertyChanged -= BlurElementChanged; + } + + _blurScene = _blurVm?.StagedScene; + + if (_blurScene != null) + { + _blurScene.Elements.CollectionChanged += BlurElementsChanged; + foreach (var element in _blurScene.Elements) element.PropertyChanged += BlurElementChanged; + } + + RebuildBlurPreview(); + } + + private void BlurElementsChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + foreach (var element in e.OldItems?.OfType() ?? System.Linq.Enumerable.Empty()) + element.PropertyChanged -= BlurElementChanged; + foreach (var element in e.NewItems?.OfType() ?? System.Linq.Enumerable.Empty()) + element.PropertyChanged += BlurElementChanged; + RebuildBlurPreview(); + } + + private void BlurElementChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (sender is Source { IsBlur: true }) RebuildBlurPreview(); + } + + /// Rebuild one overlay shape per visible blur. Cheap by design: a handful of shapes, + /// and the VisualBrush samples the live backdrop so the preview keeps moving underneath. + private void RebuildBlurPreview() + { + if (BlurPreviewHost == null) return; + BlurPreviewHost.Children.Clear(); + if (_blurScene == null) return; + + foreach (var blur in _blurScene.Elements.OfType() + .Where(s => s.IsBlur && s.IsVisible && s.Width > 0 && s.Height > 0)) + { + var shape = new Rectangle + { + Width = blur.Width, + Height = blur.Height, + Fill = new VisualBrush(BackdropLayer) + { + Viewbox = new Rect(blur.X, blur.Y, blur.Width, blur.Height), + ViewboxUnits = BrushMappingMode.Absolute, + Stretch = Stretch.Fill, + }, + // 4..24 px at master scale, the same shape of curve the compositor's radius uses. + Effect = new BlurEffect { Radius = 4 + 20 * Math.Clamp(blur.BlurAmount, 0, 1), KernelType = KernelType.Gaussian }, + }; + + if (blur.ClipShape == ClipShape.Round) + shape.Clip = new EllipseGeometry(new Rect(0, 0, blur.Width, blur.Height)); + + Canvas.SetLeft(shape, blur.X); + Canvas.SetTop(shape, blur.Y); + BlurPreviewHost.Children.Add(shape); + } + } }