Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8028,14 +8028,12 @@ Methods:
void Microsoft.Maui.PlatformDispatcher:.cctor ()
void Microsoft.Maui.PlatformDispatcher:.ctor (intptr,Android.Runtime.JniHandleOwnership)
void Microsoft.Maui.PlatformInterop:.cctor ()
void Microsoft.Maui.PlatformInterop:DrawMauiDrawablePath (Android.Graphics.Drawables.PaintDrawable,Android.Graphics.Canvas,int,int,Android.Graphics.Path,Android.Graphics.Paint)
void Microsoft.Maui.PlatformInterop:LoadImageFromFont (Android.Content.Context,int,string,Android.Graphics.Typeface,single,Microsoft.Maui.IImageLoaderCallback)
void Microsoft.Maui.PlatformInterop:LoadImageFromFont (Android.Widget.ImageView,int,string,Android.Graphics.Typeface,single,Microsoft.Maui.IImageLoaderCallback)
void Microsoft.Maui.PlatformInterop:RemoveFromParent (Android.Views.View)
void Microsoft.Maui.PlatformInterop:RequestLayoutIfNeeded (Android.Views.View)
void Microsoft.Maui.PlatformInterop:Set (Android.Views.View,int,int,int,int,bool,single,single,single,single,single,single,single,single,single,single)
void Microsoft.Maui.PlatformInterop:SetColorFilter (Android.Graphics.Drawables.Drawable,int,int)
void Microsoft.Maui.PlatformInterop:SetPaintValues (Android.Graphics.Paint,single,Android.Graphics.Paint/Join,Android.Graphics.Paint/Cap,single,Android.Graphics.PathEffect)
void Microsoft.Maui.PlatformInterop:SetPivotXIfNeeded (Android.Views.View,single)
void Microsoft.Maui.PlatformInterop:SetPivotYIfNeeded (Android.Views.View,single)
void Microsoft.Maui.PlatformMauiAppCompatActivity:.cctor ()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
~override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource>.OnInterceptTouchEvent(Android.Views.MotionEvent e) -> bool
~override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource>.OnTouchEvent(Android.Views.MotionEvent e) -> bool
~override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource>.OnTouchEvent(Android.Views.MotionEvent e) -> bool
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer.ViewWillTransitionToSize(CoreGraphics.CGSize toSize, UIKit.IUIViewControllerTransitionCoordinator coordinator) -> void
override Microsoft.Maui.Controls.Platform.Compatibility.ShellTableViewController.LoadView() -> void
*REMOVED*~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRootRenderer.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer.ViewWillTransitionToSize(CoreGraphics.CGSize toSize, UIKit.IUIViewControllerTransitionCoordinator coordinator) -> void
override Microsoft.Maui.Controls.Platform.Compatibility.ShellTableViewController.LoadView() -> void
*REMOVED*~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRootRenderer.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
17 changes: 16 additions & 1 deletion src/Controls/src/Core/Shapes/Shape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ namespace Microsoft.Maui.Controls.Shapes
/// <summary>
/// Base class for shape elements, such as <see cref="Ellipse"/>, <see cref="Line"/>, <see cref="Polygon"/>, <see cref="Polyline"/>, and <see cref="Rectangle"/>.
/// </summary>
public abstract partial class Shape : View, IShapeView, IShape
public abstract partial class Shape : View, IShapeView, IShape, IVersionedShape
{
WeakBrushChangedProxy? _fillProxy = null;
WeakBrushChangedProxy? _strokeProxy = null;
EventHandler? _fillChanged, _strokeChanged;
int _version;

/// <summary>
/// Initializes a new instance of the <see cref="Shape"/> class.
Expand All @@ -27,6 +28,20 @@ public Shape()
_fillProxy?.Unsubscribe();
_strokeProxy?.Unsubscribe();
}

int IVersionedShape.Version => _version;

protected override void OnPropertyChanged(string? propertyName = null)
{
unchecked
{
// Increase the version before propagating the property changed notification
// so that any code responding to the notification can get the latest version.
++_version;
}

base.OnPropertyChanged(propertyName);
}

public abstract PathF GetPath();

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
package com.microsoft.maui;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.Rect;
import android.graphics.drawable.PaintDrawable;
import android.graphics.drawable.shapes.Shape;
import android.graphics.drawable.shapes.RectShape;

import androidx.annotation.NonNull;

public abstract class PlatformDrawable extends PaintDrawable implements PlatformShadowDrawable {
private float strokeThickness;
private Paint.Join strokeLineJoin;
private Paint.Cap strokeLineCap;
private float strokeMiterLimit;
private PathEffect borderPathEffect;
private Paint borderPaint;

// Shape and clipping
private Path clipPath;
private Path fullClipPath;
private boolean hasShape;

// Style properties
private PlatformDrawableStyle backgroundStyle;
private PlatformDrawableStyle borderStyle;

// Dimensions
private int width;
private int height;

// State flags
private boolean invalidatePath = true;

// Constructor (note: we're keeping Context here in case we need it for future style computations)
public PlatformDrawable(Context context) {
super();
this.clipPath = new Path();
this.fullClipPath = new Path();
this.backgroundStyle = new PlatformDrawableStyle();
this.borderStyle = new PlatformDrawableStyle();
setShape(new RectShape());
}

public void setStrokeThickness(float thickness) {
this.strokeThickness = thickness;
if (thickness == 0) {
this.borderPaint = null;
} else if (this.borderPaint == null) {
this.borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
this.borderPaint.setStyle(Paint.Style.STROKE);
}

this.invalidateShapePath();
}

public float getStrokeThickness() {
return strokeThickness;
}

public void setStrokeLineJoin(Paint.Join join) {
this.strokeLineJoin = join;
this.invalidateSelf();
}

public Paint.Join getStrokeLineJoin() {
return strokeLineJoin;
}

public void setStrokeLineCap(Paint.Cap cap) {
this.strokeLineCap = cap;
this.invalidateSelf();
}

public Paint.Cap getStrokeLineCap() {
return strokeLineCap;
}

public void setStrokeMiterLimit(float limit) {
this.strokeMiterLimit = limit;
this.invalidateSelf();
}

public float getStrokeMiterLimit() {
return strokeMiterLimit;
}

public void setBorderPathEffect(PathEffect effect) {
this.borderPathEffect = effect;
this.invalidateSelf();
}

public PathEffect getBorderPathEffect() {
return borderPathEffect;
}

// Shape and clipping
public void setClipPath(Path path) {
if (this.clipPath != null) {
this.clipPath.reset();
this.clipPath.set(path);
}
}

public Path getClipPath() {
return clipPath;
}

public void setFullClipPath(Path path) {
if (this.fullClipPath != null) {
this.fullClipPath.reset();
this.fullClipPath.set(path);
}
}

public Path getFullClipPath() {
return fullClipPath;
}

public void shapeChanged(boolean hasShape) {
this.hasShape = hasShape;
this.invalidateShapePath();
}

// Simplified style management
public void setLinearGradientBackground(float x1, float y1, float x2, float y2, int[] colors, float[] positions) {
float[] bounds = { x1, y1, x2, y2 };
this.backgroundStyle.setStyle(PlatformPaintType.LINEAR, 0, colors, positions, bounds);
invalidateSelf();
}

public void setRadialGradientBackground(float x, float y, float radius, int[] colors, float[] positions) {
float[] bounds = { x, y, radius };
this.backgroundStyle.setStyle(PlatformPaintType.RADIAL, 0, colors, positions, bounds);
invalidateSelf();
}

public void setSolidBackground(int solidColor) {
this.backgroundStyle.setStyle(PlatformPaintType.SOLID, solidColor, null, null, null);
invalidateSelf();
}

public void setNoBackground() {
this.backgroundStyle.setStyle(PlatformPaintType.NONE, 0, null, null, null);
invalidateSelf();
}

public void setLinearGradientBorder(float x1, float y1, float x2, float y2, int[] colors, float[] positions) {
float[] bounds = { x1, y1, x2, y2 };
this.borderStyle.setStyle(PlatformPaintType.LINEAR, 0, colors, positions, bounds);
invalidateSelf();
}

public void setRadialGradientBorder(float x, float y, float radius, int[] colors, float[] positions) {
float[] bounds = { x, y, radius };
this.borderStyle.setStyle(PlatformPaintType.RADIAL, 0, colors, positions, bounds);
invalidateSelf();
}

public void setSolidBorder(int solidColor) {
this.borderStyle.setStyle(PlatformPaintType.SOLID, solidColor, null, null, null);
invalidateSelf();
}

public void setNoBorder() {
this.borderStyle.setStyle(PlatformPaintType.NONE, 0, null, null, null);
invalidateSelf();
}

// State management
public void invalidateShapePath() {
this.invalidatePath = true;
invalidateSelf();
}

@Override
protected void onBoundsChange(Rect bounds) {
int newWidth = bounds.width();
int newHeight = bounds.height();

if (this.width != newWidth || this.height != newHeight) {
this.width = newWidth;
this.height = newHeight;
this.invalidatePath = true;
}

super.onBoundsChange(bounds);
}

@Override
protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
// Set background paint
this.backgroundStyle.applyStyle(paint, this.width, this.height, null);

if (this.hasShape) {

// Configure border paint (when set it means there is a border - aka border thickness > 0)
if (this.borderPaint != null) {
Paint borderPaint = this.borderPaint;
borderPaint.setStrokeWidth(strokeThickness);
borderPaint.setStrokeJoin(strokeLineJoin);
borderPaint.setStrokeCap(strokeLineCap);
borderPaint.setStrokeMiter(this.strokeMiterLimit * 2);
borderPaint.setPathEffect(this.borderPathEffect);
this.borderStyle.applyStyle(borderPaint, this.width, this.height, this.backgroundStyle);
}

tryUpdateClipPath();

if (this.clipPath != null) {
boolean hasBackgroundPaint = this.backgroundStyle.getPaintType() != PlatformPaintType.NONE;
if (hasBackgroundPaint) {
canvas.drawPath(this.clipPath, paint);
}

boolean hasBorderPaint = this.borderStyle.getPaintType() != PlatformPaintType.NONE;
if (this.borderPaint != null && (hasBorderPaint || hasBackgroundPaint)) {
canvas.drawPath(this.clipPath, this.borderPaint);
}

return;
}
// else fallback to simple background drawing
}

// Simple background drawing (border is supported **only** with a shape)
super.onDraw(shape, canvas, paint);
}

private void tryUpdateClipPath() {
if (this.invalidatePath) {
this.invalidatePath = false;

if (this.hasShape) {
updateClipPath(this.width, this.height);
} else {
this.fullClipPath = null;
this.clipPath = null;
}
}
}

protected abstract void updateClipPath(int width, int height);

// PlatformShadowDrawable implementation
@Override
public boolean canDrawShadow() {
return this.backgroundStyle.getIsSolid() && (this.strokeThickness == 0 || this.borderStyle.getPaintType() == PlatformPaintType.NONE || this.borderStyle.getIsSolid());
}

@Override
public void drawShadow(Canvas canvas, Paint shadowPaint, Path outerClipPath) {
if (canvas == null || shadowPaint == null) {
return;
}

Path contentPath;

if (this.hasShape) {
tryUpdateClipPath();
if (this.fullClipPath == null) {
return;
}
contentPath = this.fullClipPath;
} else {
contentPath = new Path();
contentPath.addRect(0, 0, this.width, this.height, Path.Direction.CW);
}

if (outerClipPath != null) {
Path clippedPath = new Path();
clippedPath.op(contentPath, outerClipPath, Path.Op.INTERSECT);
canvas.drawPath(clippedPath, shadowPaint);
} else {
canvas.drawPath(contentPath, shadowPaint);
}
}
}
Loading
Loading