protected override void OnFillChanged(Brush newValue) { base.OnFillChanged(newValue); if (_gradientLayer != null) { _gradientLayer.RemoveFromSuperLayer(); _gradientLayer = null; } _rectangleLayer.FillColor = _Color.Clear.CGColor; var scbFill = newValue as SolidColorBrush; var lgbFill = newValue as LinearGradientBrush; if (scbFill != null) { _fillSubscription.Disposable = Brush.AssignAndObserveBrush(scbFill, c => _rectangleLayer.FillColor = c); } else if (lgbFill != null) { _gradientLayer = lgbFill.GetLayer(Frame.Size); Layer.InsertSublayer(_gradientLayer, 0); // We want the _gradientLayer to be below the _rectangleLayer (which contains the stroke) } InvalidateMeasure(); }
protected override void OnBackgroundChanged(DependencyPropertyChangedEventArgs e) { // Don't call base, just update the filling color. _brushChanged.Disposable = Brush.AssignAndObserveBrush(e.NewValue as Brush, c => UpdateBorder(), UpdateBorder); UpdateBorder(); }
protected override void OnStrokeUpdated(Brush newValue) { base.OnStrokeUpdated(newValue); _strokeSubscription.Disposable = Brush.AssignAndObserveBrush(Stroke, c => _rectangleLayer.StrokeColor = c); this.SetNeedsDisplay(); }
partial void OnBorderBrushChangedPartial(Brush oldValue, Brush newValue) { _borderBrushChanged.Disposable = null; if (newValue?.SupportsAssignAndObserveBrush ?? false) { _borderBrushChanged.Disposable = Brush.AssignAndObserveBrush(newValue, _ => UpdateBorder()); } UpdateBorder(); }
private void UpdateStroke() { var brush = Stroke as SolidColorBrush ?? SolidColorBrushHelper.Transparent; if (_pathSpriteShape != null) { _strokeSubscription.Disposable = Brush.AssignAndObserveBrush(brush, c => _pathSpriteShape.StrokeBrush = Visual.Compositor.CreateColorBrush(brush.Color)); } }
protected override void OnStrokeUpdated(Brush newValue) { base.OnStrokeUpdated(newValue); var brush = Stroke as SolidColorBrush ?? SolidColorBrushHelper.Transparent; _strokeSubscription.Disposable = Brush.AssignAndObserveBrush(brush, c => _rectangleLayer.StrokeColor = c); this.SetNeedsDisplay(); }
protected virtual void OnFillChanged(Brush newValue) { _brushChanged.Disposable = Brush.AssignAndObserveBrush(newValue, _ => #if __WASM__ OnFillUpdatedPartial() #else RefreshShape(true) #endif ); OnFillUpdated(newValue); }
private void UpdateStroke() { if (_pathSpriteShape != null) { _strokeSubscription.Disposable = null; _pathSpriteShape.StrokeBrush = null; _strokeSubscription.Disposable = Brush.AssignAndObserveBrush(Stroke, Visual.Compositor, compositionBrush => _pathSpriteShape.StrokeBrush = compositionBrush); } }
private void UpdateFill() { if (_pathSpriteShape != null) { _fillSubscription.Disposable = null; _pathSpriteShape.FillBrush = null; _fillSubscription.Disposable = Brush.AssignAndObserveBrush(Fill, Visual.Compositor, compositionBrush => _pathSpriteShape.FillBrush = compositionBrush); } }
private void OnForegroundChanged(Brush oldValue, Brush newValue) { _foregroundChanged.Disposable = null; var scb = newValue as SolidColorBrush; if (scb != null) { _foregroundChanged.Disposable = Brush.AssignAndObserveBrush(scb, _ => ApplyColor()); ApplyColor(); void ApplyColor() { SetTextColor(scb.Color); SetCursorColor(scb.Color); } } }
private void UpdateFill() { if (_pathSpriteShape != null) { _fillSubscription.Disposable = null; _pathSpriteShape.FillBrush = null; var scbFill = Fill as SolidColorBrush; var lgbFill = Fill as LinearGradientBrush; if (scbFill != null) { _fillSubscription.Disposable = Brush.AssignAndObserveBrush(scbFill, c => _pathSpriteShape.FillBrush = Visual.Compositor.CreateColorBrush(scbFill.Color)); } else if (lgbFill != null) { } } }
public void OnForegroundChanged(Brush oldValue, Brush newValue) { _foregroundChanged.Disposable = null; var textBox = _textBox.GetTarget(); if (textBox != null) { var scb = newValue as SolidColorBrush; if (scb != null) { _foregroundChanged.Disposable = Brush.AssignAndObserveBrush(scb, _ => ApplyColor()); ApplyColor(); void ApplyColor() { TextColor = scb.Color; TintColor = scb.Color; } } } }
private static IDisposable InnerCreateLayer(UIElement owner, CALayer parent, LayoutState state) { var area = state.Area; var background = state.Background; var borderThickness = state.BorderThickness; var borderBrush = state.BorderBrush; var cornerRadius = state.CornerRadius; var disposables = new CompositeDisposable(); var sublayers = new List <CALayer>(); var adjustedLineWidth = borderThickness.Top; var adjustedLineWidthOffset = adjustedLineWidth / 2; var adjustedArea = area; adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); if (cornerRadius != CornerRadius.None) { var maxRadius = Math.Max(0, Math.Min((float)area.Width / 2 - adjustedLineWidthOffset, (float)area.Height / 2 - adjustedLineWidthOffset)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxRadius), Math.Min(cornerRadius.TopRight, maxRadius), Math.Min(cornerRadius.BottomRight, maxRadius), Math.Min(cornerRadius.BottomLeft, maxRadius)); CAShapeLayer layer = new CAShapeLayer(); layer.LineWidth = (nfloat)adjustedLineWidth; layer.FillColor = null; Brush.AssignAndObserveBrush(borderBrush, color => layer.StrokeColor = color) .DisposeWith(disposables); var path = GetRoundedPath(cornerRadius, adjustedArea); var outerPath = GetRoundedPath(cornerRadius, area); var insertionIndex = 0; if (background is GradientBrush gradientBackground) { var fillMask = new CAShapeLayer() { Path = path, Frame = area, // We only use the fill color to create the mask area FillColor = _Color.White.CGColor, }; // We reduce the adjustedArea again so that the gradient is inside the border (like in Windows) adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); CreateGradientBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, gradientBackground, fillMask); } else if (background is SolidColorBrush scbBackground) { Brush.AssignAndObserveBrush(scbBackground, color => layer.FillColor = color) .DisposeWith(disposables); } else if (background is ImageBrush imgBackground) { var uiImage = imgBackground.ImageSource?.ImageData; if (uiImage != null && uiImage.Size != CGSize.Empty) { var fillMask = new CAShapeLayer() { Path = path, Frame = area, // We only use the fill color to create the mask area FillColor = _Color.White.CGColor, }; // We reduce the adjustedArea again so that the image is inside the border (like in Windows) adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); CreateImageBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask); } } else { layer.FillColor = Colors.Transparent; } layer.Path = path; sublayers.Add(layer); parent.InsertSublayer(layer, insertionIndex); parent.Mask = new CAShapeLayer() { Path = outerPath, Frame = area, // We only use the fill color to create the mask area FillColor = _Color.White.CGColor, }; if (owner != null) { owner.ClippingIsSetByCornerRadius = true; } state.BoundsPath = path; } else { if (background is GradientBrush gradientBackground) { var fullArea = new CGRect( area.X + borderThickness.Left, area.Y + borderThickness.Top, area.Width - borderThickness.Left - borderThickness.Right, area.Height - borderThickness.Top - borderThickness.Bottom); var insideArea = new CGRect(CGPoint.Empty, fullArea.Size); var insertionIndex = 0; CreateGradientBrushLayers(fullArea, insideArea, parent, sublayers, ref insertionIndex, gradientBackground, fillMask: null); } else if (background is SolidColorBrush scbBackground) { Brush.AssignAndObserveBrush(scbBackground, c => parent.BackgroundColor = c) .DisposeWith(disposables); // This is required because changing the CornerRadius changes the background drawing // implementation and we don't want a rectangular background behind a rounded background. Disposable.Create(() => parent.BackgroundColor = null) .DisposeWith(disposables); } else if (background is ImageBrush imgBackground) { var uiImage = imgBackground.ImageSource?.ImageData; if (uiImage != null && uiImage.Size != CGSize.Empty) { var fullArea = new CGRect( area.X + borderThickness.Left, area.Y + borderThickness.Top, area.Width - borderThickness.Left - borderThickness.Right, area.Height - borderThickness.Top - borderThickness.Bottom); var insideArea = new CGRect(CGPoint.Empty, fullArea.Size); var insertionIndex = 0; CreateImageBrushLayers(fullArea, insideArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask: null); } } else { parent.BackgroundColor = Colors.Transparent; } if (borderThickness != Thickness.Empty) { Action <Action <CAShapeLayer, CGPath> > createLayer = builder => { CAShapeLayer layer = new CAShapeLayer(); var path = new CGPath(); Brush.AssignAndObserveBrush(borderBrush, c => layer.StrokeColor = c) .DisposeWith(disposables); builder(layer, path); layer.Path = path; // Must be inserted below the other subviews, which may happen when // the current view has subviews. sublayers.Add(layer); parent.InsertSublayer(layer, 0); }; if (borderThickness.Top != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Top; var lineWidthAdjust = (nfloat)(borderThickness.Top / 2); path.MoveToPoint(area.X + (nfloat)borderThickness.Left, area.Y + lineWidthAdjust); path.AddLineToPoint(area.X + area.Width - (nfloat)borderThickness.Right, area.Y + lineWidthAdjust); path.CloseSubpath(); }); } if (borderThickness.Bottom != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Bottom; var lineWidthAdjust = borderThickness.Bottom / 2; path.MoveToPoint(area.X + (nfloat)borderThickness.Left, (nfloat)(area.Y + area.Height - lineWidthAdjust)); path.AddLineToPoint(area.X + area.Width - (nfloat)borderThickness.Right, (nfloat)(area.Y + area.Height - lineWidthAdjust)); path.CloseSubpath(); }); } if (borderThickness.Left != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Left; var lineWidthAdjust = borderThickness.Left / 2; path.MoveToPoint((nfloat)(area.X + lineWidthAdjust), area.Y); path.AddLineToPoint((nfloat)(area.X + lineWidthAdjust), area.Y + area.Height); path.CloseSubpath(); }); } if (borderThickness.Right != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Right; var lineWidthAdjust = borderThickness.Right / 2; path.MoveToPoint((nfloat)(area.X + area.Width - lineWidthAdjust), area.Y); path.AddLineToPoint((nfloat)(area.X + area.Width - lineWidthAdjust), area.Y + area.Height); path.CloseSubpath(); }); } } state.BoundsPath = CGPath.FromRect(parent.Bounds); } disposables.Add(() => { foreach (var sl in sublayers) { sl.RemoveFromSuperLayer(); sl.Dispose(); } if (owner != null) { owner.ClippingIsSetByCornerRadius = false; } } ); return(disposables); }
private static IDisposable InnerCreateLayer(UIElement owner, LayoutState state) { var parent = owner.Visual; var compositor = parent.Compositor; var area = owner.LayoutRound(state.Area); var background = state.Background; var borderThickness = owner.LayoutRound(state.BorderThickness); var borderBrush = state.BorderBrush; var cornerRadius = state.CornerRadius; var disposables = new CompositeDisposable(); var sublayers = new List <Visual>(); var heightOffset = ((float)borderThickness.Top / 2) + ((float)borderThickness.Bottom / 2); var widthOffset = ((float)borderThickness.Left / 2) + ((float)borderThickness.Right / 2); var halfWidth = (float)area.Width / 2; var halfHeight = (float)area.Height / 2; var adjustedArea = area; adjustedArea = adjustedArea.DeflateBy(borderThickness); if (cornerRadius != CornerRadius.None) { var maxOuterRadius = Math.Max(0, Math.Min(halfWidth - widthOffset, halfHeight - heightOffset)); var maxInnerRadius = Math.Max(0, Math.Min(halfWidth, halfHeight)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxOuterRadius), Math.Min(cornerRadius.TopRight, maxOuterRadius), Math.Min(cornerRadius.BottomRight, maxOuterRadius), Math.Min(cornerRadius.BottomLeft, maxOuterRadius)); var innerCornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxInnerRadius), Math.Min(cornerRadius.TopRight, maxInnerRadius), Math.Min(cornerRadius.BottomRight, maxInnerRadius), Math.Min(cornerRadius.BottomLeft, maxInnerRadius)); var borderShape = compositor.CreateSpriteShape(); var backgroundShape = compositor.CreateSpriteShape(); var outerShape = compositor.CreateSpriteShape(); // Border brush Brush.AssignAndObserveBrush(borderBrush, compositor, brush => borderShape.FillBrush = brush) .DisposeWith(disposables); // Background brush if (background is ImageBrush imgBackground) { adjustedArea = CreateImageLayer(compositor, disposables, borderThickness, adjustedArea, backgroundShape, adjustedArea, imgBackground); } else { Brush.AssignAndObserveBrush(background, compositor, brush => backgroundShape.FillBrush = brush) .DisposeWith(disposables); } var borderPath = GetRoundedRect(cornerRadius, innerCornerRadius, area, adjustedArea); var backgroundPath = GetRoundedPath(cornerRadius, adjustedArea); var outerPath = GetRoundedPath(cornerRadius, area); backgroundShape.Geometry = compositor.CreatePathGeometry(backgroundPath); borderShape.Geometry = compositor.CreatePathGeometry(borderPath); outerShape.Geometry = compositor.CreatePathGeometry(outerPath); var borderVisual = compositor.CreateShapeVisual(); var backgroundVisual = compositor.CreateShapeVisual(); backgroundVisual.Shapes.Add(backgroundShape); borderVisual.Shapes.Add(borderShape); sublayers.Add(backgroundVisual); sublayers.Add(borderVisual); parent.Children.InsertAtBottom(backgroundVisual); parent.Children.InsertAtTop(borderVisual); owner.ClippingIsSetByCornerRadius = cornerRadius != CornerRadius.None; if (owner.ClippingIsSetByCornerRadius) { parent.Clip = compositor.CreateGeometricClip(outerShape.Geometry); } } else { var shapeVisual = compositor.CreateShapeVisual(); var backgroundShape = compositor.CreateSpriteShape(); var backgroundArea = area; // Background brush if (background is ImageBrush imgBackground) { backgroundArea = CreateImageLayer(compositor, disposables, borderThickness, adjustedArea, backgroundShape, backgroundArea, imgBackground); } else { Brush.AssignAndObserveBrush(background, compositor, brush => backgroundShape.FillBrush = brush) .DisposeWith(disposables); // This is required because changing the CornerRadius changes the background drawing // implementation and we don't want a rectangular background behind a rounded background. Disposable.Create(() => backgroundShape.FillBrush = null) .DisposeWith(disposables); } var geometrySource = new SkiaGeometrySource2D(); var geometry = geometrySource.Geometry; geometry.AddRect(backgroundArea.ToSKRect()); backgroundShape.Geometry = compositor.CreatePathGeometry(new CompositionPath(geometrySource)); shapeVisual.Shapes.Add(backgroundShape); if (borderThickness != Thickness.Empty) { Action <Action <CompositionSpriteShape, SKPath> > createLayer = builder => { var spriteShape = compositor.CreateSpriteShape(); var geometry = new SkiaGeometrySource2D(); // Border brush Brush.AssignAndObserveBrush(borderBrush, compositor, brush => spriteShape.StrokeBrush = brush) .DisposeWith(disposables); builder(spriteShape, geometry.Geometry); spriteShape.Geometry = compositor.CreatePathGeometry(new CompositionPath(geometry)); shapeVisual.Shapes.Add(spriteShape); }; if (borderThickness.Top != 0) { createLayer((l, path) => { l.StrokeThickness = (float)borderThickness.Top; var StrokeThicknessAdjust = (float)(borderThickness.Top / 2); path.MoveTo((float)(area.X + borderThickness.Left), (float)(area.Y + StrokeThicknessAdjust)); path.LineTo((float)(area.X + area.Width - borderThickness.Right), (float)(area.Y + StrokeThicknessAdjust)); path.Close(); }); } if (borderThickness.Bottom != 0) { createLayer((l, path) => { l.StrokeThickness = (float)borderThickness.Bottom; var StrokeThicknessAdjust = borderThickness.Bottom / 2; path.MoveTo((float)(area.X + (float)borderThickness.Left), (float)(area.Y + area.Height - StrokeThicknessAdjust)); path.LineTo((float)(area.X + area.Width - (float)borderThickness.Right), (float)(area.Y + area.Height - StrokeThicknessAdjust)); path.Close(); }); } if (borderThickness.Left != 0) { createLayer((l, path) => { l.StrokeThickness = (float)borderThickness.Left; var StrokeThicknessAdjust = borderThickness.Left / 2; path.MoveTo((float)(area.X + StrokeThicknessAdjust), (float)area.Y); path.LineTo((float)(area.X + StrokeThicknessAdjust), (float)(area.Y + area.Height)); path.Close(); }); } if (borderThickness.Right != 0) { createLayer((l, path) => { l.StrokeThickness = (float)borderThickness.Right; var StrokeThicknessAdjust = borderThickness.Right / 2; path.MoveTo((float)(area.X + area.Width - StrokeThicknessAdjust), (float)area.Y); path.LineTo((float)(area.X + area.Width - StrokeThicknessAdjust), (float)(area.Y + area.Height)); path.Close(); }); } } sublayers.Add(shapeVisual); // Must be inserted below the other subviews, which may happen when // the current view has subviews. parent.Children.InsertAtBottom(shapeVisual); } disposables.Add(() => { owner.ClippingIsSetByCornerRadius = false; foreach (var sv in sublayers) { parent.Children.Remove(sv); sv.Dispose(); } } ); compositor.InvalidateRender(); return(disposables); }
protected virtual void OnFillChanged(Brush newValue) { _brushChanged.Disposable = Brush.AssignAndObserveBrush(newValue, _ => RefreshShape(true)); OnFillUpdated(newValue); }
private static IDisposable InnerCreateLayer(CALayer parent, CGRect area, Brush background, Thickness borderThickness, Brush borderBrush, CornerRadius cornerRadius) { var disposables = new CompositeDisposable(); var sublayers = new List <CALayer>(); var adjustedLineWidth = borderThickness.Top; var adjustedLineWidthOffset = adjustedLineWidth / 2; var adjustedArea = area; adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); if (cornerRadius != CornerRadius.None) { var maxRadius = Math.Max(0, Math.Min((float)area.Width / 2 - adjustedLineWidthOffset, (float)area.Height / 2 - adjustedLineWidthOffset)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxRadius), Math.Min(cornerRadius.TopRight, maxRadius), Math.Min(cornerRadius.BottomRight, maxRadius), Math.Min(cornerRadius.BottomLeft, maxRadius)); CAShapeLayer layer = new CAShapeLayer(); layer.LineWidth = (nfloat)adjustedLineWidth; layer.FillColor = null; var path = new CGPath(); Brush.AssignAndObserveBrush(borderBrush, color => layer.StrokeColor = color) .DisposeWith(disposables); // How AddArcToPoint works: // http://www.twistedape.me.uk/blog/2013/09/23/what-arctopointdoes/ path.MoveToPoint(adjustedArea.GetMidX(), adjustedArea.Y); path.AddArcToPoint(adjustedArea.Right, adjustedArea.Top, adjustedArea.Right, adjustedArea.GetMidY(), (float)cornerRadius.TopRight); path.AddArcToPoint(adjustedArea.Right, adjustedArea.Bottom, adjustedArea.GetMidX(), adjustedArea.Bottom, (float)cornerRadius.BottomRight); path.AddArcToPoint(adjustedArea.Left, adjustedArea.Bottom, adjustedArea.Left, adjustedArea.GetMidY(), (float)cornerRadius.BottomLeft); path.AddArcToPoint(adjustedArea.Left, adjustedArea.Top, adjustedArea.GetMidX(), adjustedArea.Top, (float)cornerRadius.TopLeft); path.CloseSubpath(); var lgbBackground = background as LinearGradientBrush; var scbBackground = background as SolidColorBrush; var imgBackground = background as ImageBrush; var insertionIndex = 0; if (lgbBackground != null) { var fillMask = new CAShapeLayer() { Path = path, Frame = area, // We only use the fill color to create the mask area FillColor = UIColor.White.CGColor, }; // We reduce the adjustedArea again so that the gradient is inside the border (like in Windows) adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); CreateLinearGradientBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, lgbBackground, fillMask); } else if (scbBackground != null) { Brush.AssignAndObserveBrush(scbBackground, color => layer.FillColor = color) .DisposeWith(disposables); } else if (imgBackground != null) { var uiImage = imgBackground.ImageSource?.ImageData; if (uiImage != null && uiImage.Size != CGSize.Empty) { var fillMask = new CAShapeLayer() { Path = path, Frame = area, // We only use the fill color to create the mask area FillColor = UIColor.White.CGColor, }; // We reduce the adjustedArea again so that the image is inside the border (like in Windows) adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); CreateImageBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask); } } else { layer.FillColor = Colors.Transparent; } layer.Path = path; sublayers.Add(layer); parent.InsertSublayer(layer, insertionIndex); } else { var lgbBackground = background as LinearGradientBrush; var scbBackground = background as SolidColorBrush; var imgBackground = background as ImageBrush; if (lgbBackground != null) { var fullArea = new CGRect( area.X + borderThickness.Left, area.Y + borderThickness.Top, area.Width - borderThickness.Left - borderThickness.Right, area.Height - borderThickness.Top - borderThickness.Bottom); var insideArea = new CGRect(CGPoint.Empty, fullArea.Size); var insertionIndex = 0; CreateLinearGradientBrushLayers(fullArea, insideArea, parent, sublayers, ref insertionIndex, lgbBackground, fillMask: null); } else if (scbBackground != null) { Brush.AssignAndObserveBrush(scbBackground, c => parent.BackgroundColor = c) .DisposeWith(disposables); // This is required because changing the CornerRadius changes the background drawing // implementation and we don't want a rectangular background behind a rounded background. Disposable.Create(() => parent.BackgroundColor = null) .DisposeWith(disposables); } else if (imgBackground != null) { var uiImage = imgBackground.ImageSource?.ImageData; if (uiImage != null && uiImage.Size != CGSize.Empty) { var fullArea = new CGRect( area.X + borderThickness.Left, area.Y + borderThickness.Top, area.Width - borderThickness.Left - borderThickness.Right, area.Height - borderThickness.Top - borderThickness.Bottom); var insideArea = new CGRect(CGPoint.Empty, fullArea.Size); var insertionIndex = 0; CreateImageBrushLayers(fullArea, insideArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask: null); } } else { parent.BackgroundColor = Colors.Transparent; } if (borderThickness != Thickness.Empty) { var strokeColor = borderBrush ?? SolidColorBrushHelper.Transparent; Action <Action <CAShapeLayer, CGPath> > createLayer = builder => { CAShapeLayer layer = new CAShapeLayer(); var path = new CGPath(); Brush.AssignAndObserveBrush(borderBrush, c => layer.StrokeColor = c) .DisposeWith(disposables); builder(layer, path); layer.Path = path; // Must be inserted below the other subviews, which may happen when // the current view has subviews. sublayers.Add(layer); parent.InsertSublayer(layer, 0); }; if (borderThickness.Top != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Top; var lineWidthAdjust = (nfloat)(borderThickness.Top / 2); path.MoveToPoint(area.X, area.Y + lineWidthAdjust); path.AddLineToPoint(area.X + area.Width, area.Y + lineWidthAdjust); path.CloseSubpath(); }); } if (borderThickness.Bottom != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Bottom; var lineWidthAdjust = borderThickness.Bottom / 2; path.MoveToPoint(area.X, (nfloat)(area.Y + area.Height - lineWidthAdjust)); path.AddLineToPoint(area.X + area.Width, (nfloat)(area.Y + area.Height - lineWidthAdjust)); path.CloseSubpath(); }); } if (borderThickness.Left != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Left; var lineWidthAdjust = borderThickness.Left / 2; path.MoveToPoint((nfloat)(area.X + lineWidthAdjust), area.Y); path.AddLineToPoint((nfloat)(area.X + lineWidthAdjust), area.Y + area.Height); path.CloseSubpath(); }); } if (borderThickness.Right != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Right; var lineWidthAdjust = borderThickness.Right / 2; path.MoveToPoint((nfloat)(area.X + area.Width - lineWidthAdjust), area.Y); path.AddLineToPoint((nfloat)(area.X + area.Width - lineWidthAdjust), area.Y + area.Height); path.CloseSubpath(); }); } } } disposables.Add(() => { foreach (var sl in sublayers) { sl.RemoveFromSuperLayer(); sl.Dispose(); } } ); return(disposables); }
private static IDisposable InnerCreateLayer(UIElement owner, CALayer parent, LayoutState state) { var area = state.Area; var background = state.Background; var borderThickness = state.BorderThickness; var borderBrush = state.BorderBrush; var cornerRadius = state.CornerRadius; var backgroundSizing = state.BackgroundSizing; var disposables = new CompositeDisposable(); var sublayers = new List <CALayer>(); var heightOffset = ((float)borderThickness.Top / 2) + ((float)borderThickness.Bottom / 2); var widthOffset = ((float)borderThickness.Left / 2) + ((float)borderThickness.Right / 2); var halfWidth = (float)area.Width / 2; var halfHeight = (float)area.Height / 2; var adjustedArea = area; adjustedArea = adjustedArea.Shrink( (nfloat)borderThickness.Left, (nfloat)borderThickness.Top, (nfloat)borderThickness.Right, (nfloat)borderThickness.Bottom ); if (cornerRadius != CornerRadius.None) { var maxOuterRadius = Math.Max(0, Math.Min(halfWidth - widthOffset, halfHeight - heightOffset)); var maxInnerRadius = Math.Max(0, Math.Min(halfWidth, halfHeight)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxOuterRadius), Math.Min(cornerRadius.TopRight, maxOuterRadius), Math.Min(cornerRadius.BottomRight, maxOuterRadius), Math.Min(cornerRadius.BottomLeft, maxOuterRadius)); var innerCornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxInnerRadius), Math.Min(cornerRadius.TopRight, maxInnerRadius), Math.Min(cornerRadius.BottomRight, maxInnerRadius), Math.Min(cornerRadius.BottomLeft, maxInnerRadius)); var outerLayer = new CAShapeLayer(); var backgroundLayer = new CAShapeLayer(); backgroundLayer.FillColor = null; outerLayer.FillRule = CAShapeLayer.FillRuleEvenOdd; outerLayer.LineWidth = 0; var path = GetRoundedRect(cornerRadius, innerCornerRadius, area, adjustedArea); var innerPath = GetRoundedPath(cornerRadius, adjustedArea); var outerPath = GetRoundedPath(cornerRadius, area); var isInnerBorderEdge = backgroundSizing == BackgroundSizing.InnerBorderEdge; var backgroundPath = isInnerBorderEdge ? innerPath : outerPath; var backgroundArea = isInnerBorderEdge ? adjustedArea : area; var insertionIndex = 0; if (background is GradientBrush gradientBackground) { var fillMask = new CAShapeLayer() { Path = backgroundPath, Frame = area, // We only use the fill color to create the mask area FillColor = _Color.White.CGColor, }; CreateGradientBrushLayers(area, backgroundArea, parent, sublayers, ref insertionIndex, gradientBackground, fillMask); } else if (background is SolidColorBrush scbBackground) { Brush.AssignAndObserveBrush(scbBackground, color => backgroundLayer.FillColor = color) .DisposeWith(disposables); } else if (background is ImageBrush imgBackground) { var imgSrc = imgBackground.ImageSource; if (imgSrc != null && imgSrc.TryOpenSync(out var uiImage) && uiImage.Size != CGSize.Empty) { var fillMask = new CAShapeLayer() { Path = backgroundPath, Frame = area, // We only use the fill color to create the mask area FillColor = _Color.White.CGColor, }; CreateImageBrushLayers(area, backgroundArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask); } } else if (background is AcrylicBrush acrylicBrush) { var fillMask = new CAShapeLayer() { Path = backgroundPath, Frame = area, // We only use the fill color to create the mask area FillColor = _Color.White.CGColor, }; acrylicBrush.Subscribe(owner, area, backgroundArea, parent, sublayers, ref insertionIndex, fillMask) .DisposeWith(disposables); } else if (background is XamlCompositionBrushBase unsupportedCompositionBrush) { Brush.AssignAndObserveBrush(unsupportedCompositionBrush, color => backgroundLayer.FillColor = color) .DisposeWith(disposables); } else { backgroundLayer.FillColor = Colors.Transparent; } if (borderBrush is SolidColorBrush scbBorder || borderBrush == null) { Brush.AssignAndObserveBrush(borderBrush, color => { outerLayer.StrokeColor = color; outerLayer.FillColor = color; }) .DisposeWith(disposables); } else if (borderBrush is GradientBrush gradientBorder) { var fillMask = new CAShapeLayer() { Path = path, Frame = area, // We only use the fill color to create the mask area FillColor = _Color.White.CGColor, }; var borderLayerIndex = parent.Sublayers.Length; CreateGradientBrushLayers(area, area, parent, sublayers, ref borderLayerIndex, gradientBorder, fillMask); } outerLayer.Path = path; backgroundLayer.Path = backgroundPath; sublayers.Add(outerLayer); sublayers.Add(backgroundLayer); parent.AddSublayer(outerLayer); parent.InsertSublayer(backgroundLayer, insertionIndex); parent.Mask = new CAShapeLayer() { Path = outerPath, Frame = area, // We only use the fill color to create the mask area FillColor = _Color.White.CGColor, }; if (owner != null) { owner.ClippingIsSetByCornerRadius = true; } state.BoundsPath = outerPath; }
partial void OnBorderBrushChangedPartial(Brush oldValue, Brush newValue) { _borderBrushChanged.Disposable = Brush.AssignAndObserveBrush(newValue, _ => UpdateBorder(), UpdateBorder); UpdateBorder(); }
private static IDisposable InnerCreateLayer(UIElement owner, LayoutState state) { var parent = owner.Visual; var compositor = parent.Compositor; var area = owner.LayoutRound(state.Area); var background = state.Background; var borderThickness = owner.LayoutRound(state.BorderThickness); var borderBrush = state.BorderBrush; var cornerRadius = state.CornerRadius; var disposables = new CompositeDisposable(); var sublayers = new List <Visual>(); var heightOffset = ((float)borderThickness.Top / 2) + ((float)borderThickness.Bottom / 2); var widthOffset = ((float)borderThickness.Left / 2) + ((float)borderThickness.Right / 2); var adjustedArea = area; adjustedArea = adjustedArea.DeflateBy(borderThickness); if (cornerRadius != CornerRadius.None) { var maxRadius = Math.Max(0, Math.Min((float)area.Width / 2 - heightOffset, (float)area.Height / 2 - widthOffset)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxRadius), Math.Min(cornerRadius.TopRight, maxRadius), Math.Min(cornerRadius.BottomRight, maxRadius), Math.Min(cornerRadius.BottomLeft, maxRadius)); var borderShape = compositor.CreateSpriteShape(); var backgroundShape = compositor.CreateSpriteShape(); var outerShape = compositor.CreateSpriteShape(); Brush.AssignAndObserveBrush(borderBrush, color => borderShape.FillBrush = compositor.CreateColorBrush(color)) .DisposeWith(disposables); if (background is GradientBrush gradientBackground) { //var fillMask = new CAShapeLayer() //{ // Path = path, // Frame = area, // // We only use the fill color to create the mask area // FillColor = _Color.White.CGColor, //}; //// We reduce the adjustedArea again so that the gradient is inside the border (like in Windows) //adjustedArea = adjustedArea.Shrink((float)adjustedStrokeThicknessOffset); //CreateGradientBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, gradientBackground, fillMask); } else if (background is SolidColorBrush scbBackground) { Brush.AssignAndObserveBrush(scbBackground, color => backgroundShape.FillBrush = compositor.CreateColorBrush(color)) .DisposeWith(disposables); } else if (background is ImageBrush imgBackground) { adjustedArea = CreateImageLayer(compositor, disposables, borderThickness, adjustedArea, backgroundShape, adjustedArea, imgBackground); } else if (background is AcrylicBrush acrylicBrush) { Brush.AssignAndObserveBrush(acrylicBrush, color => backgroundShape.FillBrush = compositor.CreateColorBrush(color)) .DisposeWith(disposables); } else { backgroundShape.FillBrush = null; } var borderPath = GetRoundedRect(cornerRadius, area, adjustedArea); var backgroundPath = GetRoundedPath(cornerRadius, adjustedArea); var outerPath = GetRoundedPath(cornerRadius, area); backgroundShape.Geometry = compositor.CreatePathGeometry(backgroundPath); borderShape.Geometry = compositor.CreatePathGeometry(borderPath); outerShape.Geometry = compositor.CreatePathGeometry(outerPath); var borderVisual = compositor.CreateShapeVisual(); var backgroundVisual = compositor.CreateShapeVisual(); backgroundVisual.Shapes.Add(backgroundShape); borderVisual.Shapes.Add(borderShape); sublayers.Add(backgroundVisual); sublayers.Add(borderVisual); parent.Children.InsertAtBottom(backgroundVisual); parent.Children.InsertAtTop(borderVisual); owner.ClippingIsSetByCornerRadius = cornerRadius != CornerRadius.None; if (owner.ClippingIsSetByCornerRadius) { parent.Clip = compositor.CreateGeometricClip(outerShape.Geometry); } } else { var shapeVisual = compositor.CreateShapeVisual(); var backgroundShape = compositor.CreateSpriteShape(); var backgroundArea = area; if (background is GradientBrush gradientBackground) { var fullArea = new Rect( area.X + borderThickness.Left, area.Y + borderThickness.Top, area.Width - borderThickness.Left - borderThickness.Right, area.Height - borderThickness.Top - borderThickness.Bottom); var insideArea = new Rect(default, fullArea.Size);
private static IDisposable InnerCreateLayer( ContainerVisual parent, Rect area, Brush background, Thickness borderThickness, Brush borderBrush, CornerRadius cornerRadius) { var disposables = new CompositeDisposable(); var subVisuals = new List <Visual>(); var adjustedLineWidth = borderThickness.Top; var adjustedLineWidthOffset = adjustedLineWidth / 2; var adjustedArea = area; adjustedArea.Inflate(-adjustedLineWidthOffset, -adjustedLineWidthOffset); var compositor = parent.Compositor; var shapeVisual = compositor.CreateShapeVisual(); parent.Children.InsertAtBottom(shapeVisual); var spriteShape = compositor.CreateSpriteShape(); SkiaGeometrySource2D BuildGeometry() { var maxRadius = Math.Max(0, Math.Min((float)area.Width / 2 - adjustedLineWidthOffset, (float)area.Height / 2 - adjustedLineWidthOffset)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxRadius), Math.Min(cornerRadius.TopRight, maxRadius), Math.Min(cornerRadius.BottomRight, maxRadius), Math.Min(cornerRadius.BottomLeft, maxRadius)); var geometry = new SkiaGeometrySource2D(); Brush.AssignAndObserveBrush(borderBrush, color => spriteShape.StrokeBrush = compositor.CreateColorBrush(color)) .DisposeWith(disposables); geometry.Geometry.MoveTo((float)adjustedArea.GetMidX(), (float)adjustedArea.Y); geometry.Geometry.ArcTo((float)adjustedArea.Right, (float)adjustedArea.Top, (float)adjustedArea.Right, (float)adjustedArea.GetMidY(), (float)cornerRadius.TopRight); geometry.Geometry.ArcTo((float)adjustedArea.Right, (float)adjustedArea.Bottom, (float)adjustedArea.GetMidX(), (float)adjustedArea.Bottom, (float)cornerRadius.BottomRight); geometry.Geometry.ArcTo((float)adjustedArea.Left, (float)adjustedArea.Bottom, (float)adjustedArea.Left, (float)adjustedArea.GetMidY(), (float)cornerRadius.BottomLeft); geometry.Geometry.ArcTo((float)adjustedArea.Left, (float)adjustedArea.Top, (float)adjustedArea.GetMidX(), (float)adjustedArea.Top, (float)cornerRadius.TopLeft); geometry.Geometry.Close(); if (background is LinearGradientBrush lgbBackground) { //var fillMask = new CAShapeLayer() //{ // Path = path, // Frame = area, // // We only use the fill color to create the mask area // FillColor = _Color.White.CGColor, //}; //// We reduce the adjustedArea again so that the gradient is inside the border (like in Windows) //adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); //CreateLinearGradientBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, lgbBackground, fillMask); } else if (background is SolidColorBrush scbBackground) { Brush.AssignAndObserveBrush(scbBackground, color => spriteShape.FillBrush = compositor.CreateColorBrush(color)) .DisposeWith(disposables); } else if (background is ImageBrush imgBackground) { //var uiImage = imgBackground.ImageSource?.ImageData; //if (uiImage != null && uiImage.Size != CGSize.Empty) //{ // var fillMask = new CAShapeLayer() // { // Path = path, // Frame = area, // // We only use the fill color to create the mask area // FillColor = _Color.White.CGColor, // }; // // We reduce the adjustedArea again so that the image is inside the border (like in Windows) // adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); // CreateImageBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask); //} } else { spriteShape.FillBrush = null; } return(geometry); } spriteShape.Geometry = compositor.CreatePathGeometry(new CompositionPath(BuildGeometry())); shapeVisual.Size = new Vector2((float)area.Width, (float)area.Height); shapeVisual.Offset = new Vector3(0, 0, 0); shapeVisual.Shapes.Add(spriteShape); subVisuals.Add(shapeVisual); disposables.Add(() => { foreach (var sv in subVisuals) { parent.Children.Remove(sv); sv.Dispose(); } }); Window.Current.QueueInvalidateRender(); return(disposables); }