From 0ea7bf50ff836dd18a160dcf15f211c144c2431e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Sun, 3 Jul 2022 13:34:30 +0200 Subject: [PATCH 1/2] Added SegmentedControl --- src/AlohaKit.Gallery/AlohaKit.Gallery.csproj | 2 +- src/AlohaKit/AlohaKit.csproj | 6 +- .../SegmentedControl/SegmentedControl.cs | 272 ++++++++++++++++++ .../SegmentedControlDrawable.cs | 74 +++++ .../SelectedIndexEventArgs.cs | 12 + .../Extensions/IEnumerableExtensions.cs | 37 +++ 6 files changed, 397 insertions(+), 6 deletions(-) create mode 100644 src/AlohaKit/Controls/SegmentedControl/SegmentedControl.cs create mode 100644 src/AlohaKit/Controls/SegmentedControl/SegmentedControlDrawable.cs create mode 100644 src/AlohaKit/Controls/SegmentedControl/SelectedIndexEventArgs.cs create mode 100644 src/AlohaKit/Extensions/IEnumerableExtensions.cs diff --git a/src/AlohaKit.Gallery/AlohaKit.Gallery.csproj b/src/AlohaKit.Gallery/AlohaKit.Gallery.csproj index 874ee1b..0d94641 100644 --- a/src/AlohaKit.Gallery/AlohaKit.Gallery.csproj +++ b/src/AlohaKit.Gallery/AlohaKit.Gallery.csproj @@ -51,7 +51,7 @@ - + diff --git a/src/AlohaKit/AlohaKit.csproj b/src/AlohaKit/AlohaKit.csproj index 9844986..fa7abe0 100644 --- a/src/AlohaKit/AlohaKit.csproj +++ b/src/AlohaKit/AlohaKit.csproj @@ -1,4 +1,4 @@ - + net6.0;net6.0-android;net6.0-ios;net6.0-maccatalyst @@ -20,12 +20,8 @@ - - - - True diff --git a/src/AlohaKit/Controls/SegmentedControl/SegmentedControl.cs b/src/AlohaKit/Controls/SegmentedControl/SegmentedControl.cs new file mode 100644 index 0000000..579fe98 --- /dev/null +++ b/src/AlohaKit/Controls/SegmentedControl/SegmentedControl.cs @@ -0,0 +1,272 @@ +using AlohaKit.Extensions; +using System.Collections; + +namespace AlohaKit.Controls +{ + public class SegmentedControl : GraphicsView + { + public SegmentedControl() + { + HeightRequest = 48; + + Drawable = SegmentedControlDrawable = new SegmentedControlDrawable(); + + StartInteraction += OnSegmentedControlStartInteraction; + } + + public SegmentedControlDrawable SegmentedControlDrawable { get; set; } + + public static readonly new BindableProperty BackgroundProperty = + BindableProperty.Create(nameof(Background), typeof(Brush), typeof(SegmentedControl), null, + propertyChanged: (bindableObject, oldValue, newValue) => + { + if (newValue != null && bindableObject is SegmentedControl segmentedControl) + { + segmentedControl.UpdateBackground(); + } + }); + + public new Brush Background + { + get => (Brush)GetValue(BackgroundProperty); + set => SetValue(BackgroundProperty, value); + } + + public static readonly BindableProperty ActiveBackgroundProperty = + BindableProperty.Create(nameof(ActiveBackground), typeof(Brush), typeof(SegmentedControl), null, + propertyChanged: (bindableObject, oldValue, newValue) => + { + if (newValue != null && bindableObject is SegmentedControl segmentedControl) + { + segmentedControl.UpdateActiveBackground(); + } + }); + + public Brush ActiveBackground + { + get => (Brush)GetValue(ActiveBackgroundProperty); + set => SetValue(ActiveBackgroundProperty, value); + } + + public static readonly BindableProperty ItemsSourceProperty = + BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(SegmentedControl), null, + propertyChanged: (bindableObject, oldValue, newValue) => + { + if (newValue != null && bindableObject is SegmentedControl segmentedControl) + { + segmentedControl.UpdateItemsSource(); + } + }); + + public IEnumerable ItemsSource + { + get => (IEnumerable)GetValue(ItemsSourceProperty); + set => SetValue(ItemsSourceProperty, value); + } + + public static readonly BindableProperty SelectedIndexProperty = + BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(SegmentedControl), 0, + propertyChanged: (bindableObject, oldValue, newValue) => + { + if (newValue != null && bindableObject is SegmentedControl segmentedControl) + { + segmentedControl.UpdateSelectedIndex(); + } + }); + + public int SelectedIndex + { + get => (int)GetValue(SelectedIndexProperty); + set => SetValue(SelectedIndexProperty, value); + } + + public static readonly BindableProperty TextColorProperty = + BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(SegmentedControl), null, + propertyChanged: (bindableObject, oldValue, newValue) => + { + if (newValue != null && bindableObject is SegmentedControl segmentedControl) + { + segmentedControl.UpdateTextColor(); + } + }); + + public Color TextColor + { + get { return (Color)GetValue(TextColorProperty); } + set { SetValue(TextColorProperty, value); } + } + + public static readonly BindableProperty ActiveTextColorProperty = + BindableProperty.Create(nameof(ActiveTextColor), typeof(Color), typeof(SegmentedControl), null, + propertyChanged: (bindableObject, oldValue, newValue) => + { + if (newValue != null && bindableObject is SegmentedControl segmentedControl) + { + segmentedControl.UpdateActiveTextColor(); + } + }); + + public Color ActiveTextColor + { + get { return (Color)GetValue(ActiveTextColorProperty); } + set { SetValue(ActiveTextColorProperty, value); } + } + + public static readonly BindableProperty FontSizeProperty = + BindableProperty.Create(nameof(FontSize), typeof(double), typeof(SegmentedControl), 18.0d, + propertyChanged: (bindableObject, oldValue, newValue) => + { + if (newValue != null && bindableObject is SegmentedControl segmentedControl) + { + segmentedControl.UpdateFontSize(); + } + }); + + public double FontSize + { + get { return (double)GetValue(FontSizeProperty); } + set { SetValue(FontSizeProperty, value); } + } + + public static readonly BindableProperty ActiveFontSizeProperty = + BindableProperty.Create(nameof(ActiveFontSize), typeof(double), typeof(SegmentedControl), 20.0d, + propertyChanged: (bindableObject, oldValue, newValue) => + { + if (newValue != null && bindableObject is SegmentedControl segmentedControl) + { + segmentedControl.UpdateActiveFontSize(); + } + }); + + public double ActiveFontSize + { + get { return (double)GetValue(ActiveFontSizeProperty); } + set { SetValue(ActiveFontSizeProperty, value); } + } + + public event EventHandler SelectedIndexChanged; + + protected override void OnParentSet() + { + base.OnParentSet(); + + if (Parent != null) + { + UpdateBackground(); + UpdateActiveBackground(); + UpdateItemsSource(); + UpdateSelectedIndex(); + UpdateTextColor(); + UpdateActiveTextColor(); + UpdateFontSize(); + UpdateActiveFontSize(); + } + } + + protected override void OnSizeAllocated(double width, double height) + { + base.OnSizeAllocated(width, height); + + WidthRequest = width; + HeightRequest = height; + } + + void UpdateBackground() + { + if (SegmentedControlDrawable == null) + return; + + SegmentedControlDrawable.BackgroundPaint = Background; + + Invalidate(); + } + + void UpdateActiveBackground() + { + if (SegmentedControlDrawable == null) + return; + + SegmentedControlDrawable.ActiveBackgroundPaint = ActiveBackground; + + Invalidate(); + } + + void UpdateItemsSource() + { + if (SegmentedControlDrawable == null) + return; + + SegmentedControlDrawable.ItemsSource = ItemsSource; + + Invalidate(); + } + + void UpdateSelectedIndex() + { + if (SegmentedControlDrawable == null) + return; + + SegmentedControlDrawable.SelectedIndex = SelectedIndex; + + Invalidate(); + } + + void UpdateTextColor() + { + if (SegmentedControlDrawable == null) + return; + + SegmentedControlDrawable.TextColor = TextColor; + + Invalidate(); + } + + void UpdateActiveTextColor() + { + if (SegmentedControlDrawable == null) + return; + + SegmentedControlDrawable.ActiveTextColor = ActiveTextColor; + + Invalidate(); + } + + void UpdateFontSize() + { + if (SegmentedControlDrawable == null) + return; + + SegmentedControlDrawable.FontSize = (float)FontSize; + + Invalidate(); + } + + void UpdateActiveFontSize() + { + if (SegmentedControlDrawable == null) + return; + + SegmentedControlDrawable.ActiveFontSize = (float)ActiveFontSize; + + Invalidate(); + } + + void OnSegmentedControlStartInteraction(object sender, TouchEventArgs e) + { + float positionX = e.Touches[0].X; + var tabItemWidth = Width / ItemsSource.Count(); + + for (int i = 0; i < ItemsSource.Count(); i++) + { + float tabPositionX = (float)(i * tabItemWidth); + + if (positionX >= tabPositionX + && positionX <= (tabPositionX + tabItemWidth)) + { + SelectedIndex = i; + SelectedIndexChanged?.Invoke(this, new SelectedIndexEventArgs(SelectedIndex)); + } + } + } + } +} \ No newline at end of file diff --git a/src/AlohaKit/Controls/SegmentedControl/SegmentedControlDrawable.cs b/src/AlohaKit/Controls/SegmentedControl/SegmentedControlDrawable.cs new file mode 100644 index 0000000..26b14d7 --- /dev/null +++ b/src/AlohaKit/Controls/SegmentedControl/SegmentedControlDrawable.cs @@ -0,0 +1,74 @@ +using AlohaKit.Extensions; +using System.Collections; + +namespace AlohaKit.Controls +{ + public class SegmentedControlDrawable : IDrawable + { + public Paint BackgroundPaint { get; set; } + public Paint ActiveBackgroundPaint { get; set; } + public IEnumerable ItemsSource { get; set; } + public int SelectedIndex { get; set; } + public Color TextColor { get; set; } + public Color ActiveTextColor { get; set; } + public float FontSize { get; set; } + public float ActiveFontSize { get; set; } + + public void Draw(ICanvas canvas, RectF dirtyRect) + { + DrawBackground(canvas, dirtyRect); + DrawActiveTab(canvas, dirtyRect); + DrawTabs(canvas, dirtyRect); + } + + void DrawBackground(ICanvas canvas, RectF dirtyRect) + { + canvas.SaveState(); + + if (BackgroundPaint != null) + { + canvas.SetFillPaint(BackgroundPaint, dirtyRect); + + float tabItemRadius = dirtyRect.Height / 2; + canvas.FillRoundedRectangle(0, 0, dirtyRect.Width, dirtyRect.Height, tabItemRadius); + } + + canvas.RestoreState(); + } + + void DrawActiveTab(ICanvas canvas, RectF dirtyRect) + { + canvas.SaveState(); + + if (ActiveBackgroundPaint != null) + { + var tabItemWidth = dirtyRect.Width / ItemsSource.Count(); + + float tabItemRadius = dirtyRect.Height / 2; + + canvas.SetFillPaint(ActiveBackgroundPaint, dirtyRect); + + canvas.FillRoundedRectangle(SelectedIndex * tabItemWidth, 0, tabItemWidth, dirtyRect.Height, tabItemRadius); + } + + canvas.RestoreState(); + } + + void DrawTabs(ICanvas canvas, RectF dirtyRect) + { + var tabItemWidth = dirtyRect.Width / ItemsSource.Count(); + + for (int i = 0; i < ItemsSource.Count(); i++) + { + string title = (string)ItemsSource.ElementAt(i); + + var x = tabItemWidth * i; + + canvas.FontSize = (i == SelectedIndex) ? ActiveFontSize : FontSize; + canvas.FontColor = (i == SelectedIndex) ? ActiveTextColor : TextColor; + + canvas.DrawString(title, x, 0, tabItemWidth, dirtyRect.Height, HorizontalAlignment.Center, VerticalAlignment.Center, TextFlow.ClipBounds, 0); + } + } + } +} \ No newline at end of file diff --git a/src/AlohaKit/Controls/SegmentedControl/SelectedIndexEventArgs.cs b/src/AlohaKit/Controls/SegmentedControl/SelectedIndexEventArgs.cs new file mode 100644 index 0000000..d72cd05 --- /dev/null +++ b/src/AlohaKit/Controls/SegmentedControl/SelectedIndexEventArgs.cs @@ -0,0 +1,12 @@ +namespace AlohaKit.Controls +{ + public class SelectedIndexEventArgs : EventArgs + { + public SelectedIndexEventArgs(int selectedIndex) + { + SelectedIndex = selectedIndex; + } + + public int SelectedIndex { get; set; } + } +} \ No newline at end of file diff --git a/src/AlohaKit/Extensions/IEnumerableExtensions.cs b/src/AlohaKit/Extensions/IEnumerableExtensions.cs new file mode 100644 index 0000000..415af11 --- /dev/null +++ b/src/AlohaKit/Extensions/IEnumerableExtensions.cs @@ -0,0 +1,37 @@ +using System.Collections; + +namespace AlohaKit.Extensions +{ + public static class IEnumerableExtensions + { + public static int Count(this IEnumerable source) + { + var enumerator = source.GetEnumerator(); + + int count = 0; + + while (enumerator.MoveNext()) + count++; + + return count; + } + + public static object ElementAt(this IEnumerable source, int index) + { + int retval = -1; + var enumerator = source.GetEnumerator(); + + while (enumerator.MoveNext()) + { + retval += 1; + + if (retval.Equals(index)) + { + return enumerator.Current; + } + } + + return null; + } + } +} From d8461b6e4d1bd2f7a1507bf2bfafdf9a2d8915c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Sun, 3 Jul 2022 18:53:47 +0200 Subject: [PATCH 2/2] Added sample --- .../ViewModels/MainViewModel.cs | 7 ++- .../Views/SegmentedControlView.xaml | 54 +++++++++++++++++++ .../Views/SegmentedControlView.xaml.cs | 9 ++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/AlohaKit.Gallery/Views/SegmentedControlView.xaml create mode 100644 src/AlohaKit.Gallery/Views/SegmentedControlView.xaml.cs diff --git a/src/AlohaKit.Gallery/ViewModels/MainViewModel.cs b/src/AlohaKit.Gallery/ViewModels/MainViewModel.cs index 5947f42..c812d1c 100644 --- a/src/AlohaKit.Gallery/ViewModels/MainViewModel.cs +++ b/src/AlohaKit.Gallery/ViewModels/MainViewModel.cs @@ -33,8 +33,11 @@ namespace AlohaKit.Gallery.ViewModels new SectionModel(typeof(ProgressRadialView), "ProgressRadial", "The ProgressRadial is a control that indicates the progress of a task."), - - new SectionModel(typeof(RatingView), "Rating", + + new SectionModel(typeof(SegmentedControlView), "SegmentedControl", + "The SegmentedControl provides a simple way to choose from a linear set of two or more segments."), + + new SectionModel(typeof(RatingView), "Rating", "Rating is a control that allows users to rate by selecting number of items from a predefined number of items."), new SectionModel(typeof(SliderView), "Slider", diff --git a/src/AlohaKit.Gallery/Views/SegmentedControlView.xaml b/src/AlohaKit.Gallery/Views/SegmentedControlView.xaml new file mode 100644 index 0000000..6b652ca --- /dev/null +++ b/src/AlohaKit.Gallery/Views/SegmentedControlView.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + Tab 1 + Tab 2 + Tab 3 + + + + + diff --git a/src/AlohaKit.Gallery/Views/SegmentedControlView.xaml.cs b/src/AlohaKit.Gallery/Views/SegmentedControlView.xaml.cs new file mode 100644 index 0000000..189289b --- /dev/null +++ b/src/AlohaKit.Gallery/Views/SegmentedControlView.xaml.cs @@ -0,0 +1,9 @@ +namespace AlohaKit.Gallery; + +public partial class SegmentedControlView : ContentPage +{ + public SegmentedControlView() + { + InitializeComponent(); + } +} \ No newline at end of file