Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

CornerRadiusEffect #1316

Merged
merged 17 commits into from
Nov 11, 2021
Merged
Show file tree
Hide file tree
Changes from 13 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
137 changes: 137 additions & 0 deletions samples/XCT.Sample/Pages/Effects/CornerRadiusEffectPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage
x:Class="Xamarin.CommunityToolkit.Sample.Pages.Effects.CornerRadiusEffectPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Xamarin.CommunityToolkit.Sample.Pages"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Name="Page">

<pages:BasePage.Resources>
<ResourceDictionary>
<Style TargetType="Slider">
<Setter Property="MaximumTrackColor" Value="White" />
<Setter Property="ThumbColor" Value="Blue" />
</Style>
<Style TargetType="Label">
<Setter Property="LineBreakMode" Value="TailTruncation" />
</Style>
</ResourceDictionary>
</pages:BasePage.Resources>

<ScrollView>
<Grid RowDefinitions="*,Auto">
<Grid Grid.Row="0">
<ContentView
Grid.Row="0"
xct:CornerRadiusEffect.CornerRadius="{Binding CornerRadius, Source={x:Reference Page}}"
BackgroundColor="Blue"
HeightRequest="{Binding Value, Source={x:Reference Name=SliderHeight}}"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="{Binding Value, Source={x:Reference Name=SliderWidth}}" />
</Grid>

<Grid
Grid.Row="1"
Padding="10"
BackgroundColor="LightGray"
RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Label
Grid.Row="0"
Grid.Column="1"
HorizontalOptions="End"
Text="Width" />
<Slider
x:Name="SliderWidth"
Grid.Row="0"
Grid.Column="2"
Maximum="1000"
Minimum="100"
Value="100" />

<Label
Grid.Row="0"
Grid.Column="3"
HorizontalOptions="End"
Text="Height" />
<Slider
x:Name="SliderHeight"
Grid.Row="0"
Grid.Column="4"
Maximum="1000"
Minimum="100"
Value="100" />

<Label
Grid.Row="1"
Grid.Column="1"
HorizontalOptions="End"
Text="TopLeft" />
<Slider
x:Name="SliderCornerRadiusTopLeft"
Grid.Row="1"
Grid.Column="2"
Maximum="200"
Minimum="0"
Value="10" />

<Label
Grid.Row="1"
Grid.Column="3"
HorizontalOptions="End"
Text="TopRight" />
<Slider
x:Name="SliderCornerRadiusTopRight"
Grid.Row="1"
Grid.Column="4"
Maximum="200"
Minimum="0"
Value="10" />

<Label
Grid.Row="2"
Grid.Column="1"
HorizontalOptions="End"
Text="BottomLeft" />
<Slider
x:Name="SliderCornerRadiusBottomLeft"
Grid.Row="2"
Grid.Column="2"
Maximum="200"
Minimum="0"
Value="10" />

<Label
Grid.Row="2"
Grid.Column="3"
HorizontalOptions="End"
Text="BottomRight" />
<Slider
x:Name="SliderCornerRadiusBottomRight"
Grid.Row="2"
Grid.Column="4"
Maximum="200"
Minimum="0"
Value="10" />
</Grid>
</Grid>
</ScrollView>
</pages:BasePage>
27 changes: 27 additions & 0 deletions samples/XCT.Sample/Pages/Effects/CornerRadiusEffectPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ protected override IEnumerable<SectionModel> 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.")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Comment on lines +21 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to point out, that we need to see if this will not cause a memory leak. I believe that doesn't, but just want to make sure to bring more eyes to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we don't unsubscribe from the event as there is no detach method. Maybe use WeakEventManager?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we can use the WeakEventManager to subscribe to other events. I'll confirm if this can be an issue, if so I'll implement a fix during this week (;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, unfortunately the current implementation of WeakEventManager does not support subscription to other events.

Maybe an enhancement that could be added, like with WPF: https://docs.microsoft.com/en-us/dotnet/desktop/wpf/advanced/weak-event-patterns?view=netframeworkdesktop-4.8#using-an-existing-weak-event-manager-class

Copy link
Contributor

@pictos pictos Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this idea. @brminnick do you think that could be added on WeakEventManager or would be better to have this as a separated internal feature?

Copy link
Contributor

@TheCodeTraveler TheCodeTraveler Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! @YZahringer is there an open Issue for it? If not, could you create one, tag me in it, and I'll take a look!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking this old MS app as a reference it's ok to use this approach. I think that we're good to go


UpdateClip(elementView);
}

static void ElementViewSizeChanged(object? sender, EventArgs e)
{
if (sender is not VisualElement elementView)
return;

UpdateClip(elementView);
}

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;
}
}
}