diff --git a/samples/XCT.Sample/Pages/Effects/CornerRadiusEffectPage.xaml b/samples/XCT.Sample/Pages/Effects/CornerRadiusEffectPage.xaml new file mode 100644 index 000000000..b7a2ebe72 --- /dev/null +++ b/samples/XCT.Sample/Pages/Effects/CornerRadiusEffectPage.xaml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/XCT.Sample/Pages/Effects/CornerRadiusEffectPage.xaml.cs b/samples/XCT.Sample/Pages/Effects/CornerRadiusEffectPage.xaml.cs new file mode 100644 index 000000000..37e17ff04 --- /dev/null +++ b/samples/XCT.Sample/Pages/Effects/CornerRadiusEffectPage.xaml.cs @@ -0,0 +1,27 @@ +using Xamarin.Forms; + +namespace Xamarin.CommunityToolkit.Sample.Pages.Effects +{ + public partial class CornerRadiusEffectPage + { + public CornerRadiusEffectPage() + { + InitializeComponent(); + + SliderCornerRadiusTopLeft.ValueChanged += OnCornerRadiusValueChanged; + SliderCornerRadiusTopRight.ValueChanged += OnCornerRadiusValueChanged; + SliderCornerRadiusBottomLeft.ValueChanged += OnCornerRadiusValueChanged; + SliderCornerRadiusBottomRight.ValueChanged += OnCornerRadiusValueChanged; + } + + void OnCornerRadiusValueChanged(object sender, ValueChangedEventArgs e) + { + CornerRadius = new CornerRadius( + SliderCornerRadiusTopLeft.Value, SliderCornerRadiusTopRight.Value, + SliderCornerRadiusBottomLeft.Value, SliderCornerRadiusBottomRight.Value); + OnPropertyChanged(nameof(CornerRadius)); + } + + public CornerRadius CornerRadius { get; private set; } = new (10); + } +} \ No newline at end of file diff --git a/samples/XCT.Sample/ViewModels/Effects/EffectsGalleryViewModel.cs b/samples/XCT.Sample/ViewModels/Effects/EffectsGalleryViewModel.cs index fe74f2616..309f609ce 100644 --- a/samples/XCT.Sample/ViewModels/Effects/EffectsGalleryViewModel.cs +++ b/samples/XCT.Sample/ViewModels/Effects/EffectsGalleryViewModel.cs @@ -50,6 +50,11 @@ protected override IEnumerable CreateItems() => new[] "The SemanticEffect allows you to set semantic properties for accessibility."), new SectionModel( + typeof(CornerRadiusEffectPage), + nameof(CornerRadiusEffect), + "The CornerRadius allows rounded corners everywhere."), + + new SectionModel( typeof(StatusBarEffectPage), nameof(StatusBarEffect), "The StatusBar allows to change status bar color and style. This page also demonstrates Android only NavigationBar changes.") diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/CornerRadius/CornerRadiusEffect.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/CornerRadius/CornerRadiusEffect.shared.cs new file mode 100644 index 000000000..1ea6c9a29 --- /dev/null +++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/CornerRadius/CornerRadiusEffect.shared.cs @@ -0,0 +1,83 @@ +using System; +using Xamarin.Forms; +using Xamarin.Forms.Shapes; + +namespace Xamarin.CommunityToolkit.Effects +{ + public class CornerRadiusEffect + { + public static readonly BindableProperty CornerRadiusProperty = BindableProperty.CreateAttached( + nameof(CornerRadius), + typeof(CornerRadius), + typeof(CornerRadiusEffect), + default(CornerRadius), + propertyChanged: OnCornerRadiusPropertyChanged); + + static void OnCornerRadiusPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is not VisualElement elementView) + return; + + elementView.SizeChanged -= ElementViewSizeChanged; + elementView.SizeChanged += ElementViewSizeChanged; + + UpdateClip(elementView); + } + + static void ElementViewSizeChanged(object? sender, EventArgs e) + { + if (sender == null) + return; + + UpdateClip((VisualElement)sender); + } + + public static CornerRadius GetCornerRadius(BindableObject? bindable) + => (CornerRadius)(bindable?.GetValue(CornerRadiusProperty) ?? throw new ArgumentNullException(nameof(bindable))); + + public static void SetCornerRadius(BindableObject? bindable, CornerRadius value) + => bindable?.SetValue(CornerRadiusProperty, value); + + static void UpdateClip(VisualElement elementView) + { + var rect = new Rect(0, 0, elementView.Width, elementView.Height); + var cornerRadius = GetCornerRadius(rect, elementView); + if (cornerRadius == default) + { + elementView.Clip = null; + return; + } + + if (elementView.Clip is not RoundRectangleGeometry roundRectangleGeometry) + { + elementView.Clip = new RoundRectangleGeometry(cornerRadius, rect); + return; + } + + roundRectangleGeometry.CornerRadius = cornerRadius; + roundRectangleGeometry.Rect = rect; + } + + static CornerRadius GetCornerRadius(Rect rect, VisualElement elementView) + { + var maxCornerRadius = Math.Min(rect.Width, rect.Height) / 2; + if (maxCornerRadius <= 0) + return default; + + var cornerRadius = GetCornerRadius(elementView); + if (cornerRadius.TopLeft > maxCornerRadius || + cornerRadius.TopRight > maxCornerRadius || + cornerRadius.BottomLeft > maxCornerRadius || + cornerRadius.BottomRight > maxCornerRadius) + { + return new CornerRadius( + Math.Min(cornerRadius.TopLeft, maxCornerRadius), + Math.Min(cornerRadius.TopRight, maxCornerRadius), + Math.Min(cornerRadius.BottomLeft, maxCornerRadius), + Math.Min(cornerRadius.BottomRight, maxCornerRadius)); + } + + return cornerRadius; + } + } +}