Implement Button (Cupertino, Material and Fluent)

This commit is contained in:
Javier Suárez 2022-11-07 17:56:27 +01:00
Родитель 5aa91fb768
Коммит f5ba86b91a
12 изменённых файлов: 432 добавлений и 16 удалений

Просмотреть файл

@ -67,5 +67,12 @@
X="100" Y="200"
Data="M 10,100 L 100,100 100,50Z"
Stroke="Black" />
<alohakit:Button
X="250" Y="200"
Background="Blue"
WidthRequest="150"
Text="Button"
TextColor="White"
Clicked="OnButtonClicked"/>
</alohakit:CanvasView>
</ContentPage>

Просмотреть файл

@ -9,7 +9,12 @@
void OnEllipseTapped(object sender, EventArgs e)
{
DisplayAlert("Gestures", "Ellipse tapped.", "Ok");
DisplayAlert("Gestures", "Ellipse Tapped.", "Ok");
}
void OnButtonClicked(object sender, EventArgs e)
{
DisplayAlert("Button", "Button Clicked.", "Ok");
}
}
}

Просмотреть файл

@ -0,0 +1,71 @@
using Microsoft.Maui;
using Microsoft.Maui.Graphics.Text;
namespace AlohaKit.UI
{
public abstract class ButtonBase : View
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create(nameof(Text), typeof(string), typeof(ButtonBase), string.Empty,
propertyChanged: InvalidatePropertyChanged);
public static readonly BindableProperty TextColorProperty =
BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(ButtonBase), null,
propertyChanged: InvalidatePropertyChanged);
public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create(nameof(FontSize), typeof(double), typeof(ButtonBase), 14.0d,
propertyChanged: InvalidatePropertyChanged);
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public Color TextColor
{
get => (Color)GetValue(TextColorProperty);
set => SetValue(TextColorProperty, value);
}
public double FontSize
{
get => (double)GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
public event EventHandler Clicked;
public event EventHandler Pressed;
public event EventHandler Released;
public override void StartInteraction(PointF[] points)
{
base.StartInteraction(points);
Pressed?.Invoke(this, EventArgs.Empty);
Clicked?.Invoke(this, EventArgs.Empty);
}
public override void EndInteraction(PointF[] points, bool isInsideBounds)
{
base.EndInteraction(points, isInsideBounds);
Released?.Invoke(this, EventArgs.Empty);
}
}
public class Button :
#if ANDROID
Material.Button
#elif IOS || MACCATALYST
Cupertino.Button
#elif WINDOWS
Fluent.Button
#else
Material.Button
#endif
{
}
}

Просмотреть файл

@ -21,19 +21,27 @@ namespace AlohaKit.UI
}
}
public interface ICanvasView
{
void Draw(ICanvas canvas, RectF bounds);
void Invalidate();
}
[ContentProperty(nameof(Children))]
public class CanvasView : GraphicsView
public class CanvasView : GraphicsView, ICanvasView, IDisposable
{
public CanvasView()
{
Children = new ElementsCollection();
Children = new ElementsCollection(this);
Drawable = new CanvasViewDrawable(this);
StartInteraction += OnCanvasViewStartInteraction;
}
EndInteraction += OnCanvasViewEndInteraction;
CancelInteraction += OnCanvasViewCancelInteraction;
}
public ElementsCollection Children { get; internal set; }
public ElementsCollection Children { get; internal set; }
internal void DrawCore(ICanvas canvas, RectF bounds)
{
@ -51,14 +59,23 @@ namespace AlohaKit.UI
}
}
void IDisposable.Dispose()
{
StartInteraction -= OnCanvasViewStartInteraction;
EndInteraction -= OnCanvasViewEndInteraction;
CancelInteraction -= OnCanvasViewCancelInteraction;
}
void OnCanvasViewStartInteraction(object sender, TouchEventArgs e)
{
var touchPoint = e.Touches[0];
foreach (var child in Children)
{
if (child.IsVisible && child is View view && view.TouchInside(touchPoint))
if (child.IsVisible && child is View view && view.IsInsideBounds(touchPoint))
{
view.StartInteraction(e.Touches);
foreach (var gesture in view.GestureRecognizers)
{
if (gesture is TapGestureRecognizer tapGestureRecognizer)
@ -67,5 +84,29 @@ namespace AlohaKit.UI
}
}
}
}
void OnCanvasViewEndInteraction(object sender, TouchEventArgs e)
{
var touchPoint = e.Touches[0];
foreach (var child in Children)
{
if (child.IsVisible && child is View view && view.IsInsideBounds(touchPoint))
{
view.EndInteraction(e.Touches, e.IsInsideBounds);
}
}
}
void OnCanvasViewCancelInteraction(object sender, EventArgs e)
{
foreach (var child in Children)
{
if (child.IsVisible && child is View view)
{
view.CancelInteraction();
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
namespace AlohaKit.UI.Cupertino
{
public class Button : ButtonBase
{
const string BackgroundColor = "#007AFF";
const float CornerRadius = 2.0f;
const float MinimumHeight = 44f;
public override void Draw(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
base.Draw(canvas, bounds);
DrawBackground(canvas, bounds);
DrawText(canvas, bounds);
canvas.RestoreState();
}
public virtual void DrawBackground(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
if (Background is SolidColorBrush solidColorBrush)
{
if (solidColorBrush.Color != null)
canvas.FillColor = solidColorBrush.Color;
else
canvas.FillColor = Color.FromArgb(BackgroundColor);
}
else
canvas.SetFillPaint(Background, bounds);
float height = MinimumHeight;
if (!float.IsNaN(HeightRequest))
height = HeightRequest;
canvas.FillRoundedRectangle(X, Y, WidthRequest, height, CornerRadius);
canvas.RestoreState();
}
public virtual void DrawText(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
canvas.FontColor = TextColor;
canvas.FontSize = (float)FontSize;
float height = MinimumHeight;
if (!float.IsNaN(HeightRequest))
height = HeightRequest;
canvas.DrawString(Text, X, Y, WidthRequest, height, HorizontalAlignment.Center, VerticalAlignment.Center);
canvas.RestoreState();
}
}
}

Просмотреть файл

@ -12,8 +12,6 @@
public class Element : VisualElement, IElement
{
IElement _parent;
RectF _childrenBounds;
public Element()
{

Просмотреть файл

@ -6,6 +6,7 @@ namespace AlohaKit.UI
public class ElementsCollection : ObservableCollection<IElement>
{
readonly IElement _parent;
readonly ICanvasView _canvasView;
public ElementsCollection()
{
@ -15,9 +16,14 @@ namespace AlohaKit.UI
internal ElementsCollection(IElement parent)
{
_parent = parent;
}
}
protected override void ClearItems()
internal ElementsCollection(ICanvasView canvasView)
{
_canvasView = canvasView;
}
protected override void ClearItems()
{
base.ClearItems();
}
@ -47,6 +53,7 @@ namespace AlohaKit.UI
void Invalidate()
{
_canvasView?.Invalidate();
_parent?.Invalidate();
}
}

Просмотреть файл

@ -0,0 +1,99 @@
using AlohaKit.UI.Extensions;
namespace AlohaKit.UI.Fluent
{
public class Button : ButtonBase
{
const string BackgroundColor = "#2A2A2A";
const float CornerRadius = 4.0f;
const float MinimumHeight = 32f;
public override void Draw(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
base.Draw(canvas, bounds);
DrawBackground(canvas, bounds);
DrawText(canvas, bounds);
canvas.RestoreState();
}
public virtual void DrawBackground(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
var x = X;
var y = Y;
var width = WidthRequest;
var height = MinimumHeight;
if (!float.IsNaN(HeightRequest))
height = HeightRequest;
var backgroundColor = Color.FromArgb(BackgroundColor);
if (Background is SolidColorBrush backgroundBrush)
{
if (backgroundBrush.Color != null)
backgroundColor = backgroundBrush.Color;
}
var border = new LinearGradientPaint
{
GradientStops = new PaintGradientStop[]
{
new PaintGradientStop(0.0f, backgroundColor.Lighter()),
new PaintGradientStop(0.9f, backgroundColor.Darker())
},
StartPoint = new Point(0, 0),
EndPoint = new Point(0, 1)
};
canvas.SetFillPaint(border, bounds);
canvas.FillRoundedRectangle(x, y, width, height, CornerRadius);
canvas.RestoreState();
canvas.SaveState();
canvas.StrokeColor = Colors.Black;
if (Background is SolidColorBrush borderBrush)
{
if (borderBrush.Color != null)
canvas.FillColor = borderBrush.Color;
else
canvas.FillColor = backgroundColor;
}
else
canvas.SetFillPaint(Background, bounds);
var strokeWidth = 1;
float margin = strokeWidth * 2;
canvas.FillRoundedRectangle(x + strokeWidth, y + strokeWidth, width - margin, height - margin, CornerRadius);
canvas.RestoreState();
}
public virtual void DrawText(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
canvas.FontColor = TextColor;
canvas.FontSize = (float)FontSize;
float height = MinimumHeight;
if (!float.IsNaN(HeightRequest))
height = HeightRequest;
canvas.DrawString(Text, X, Y, WidthRequest, height, HorizontalAlignment.Center, VerticalAlignment.Center);
canvas.RestoreState();
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
namespace AlohaKit.UI.Material
{
public class Button : ButtonBase
{
const string BackgroundColor = "#2196f3";
const float MinimumHeight = 36f;
const float CornerRadius = 2.0f;
public override void Draw(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
base.Draw(canvas, bounds);
DrawBackground(canvas, bounds);
DrawText(canvas, bounds);
canvas.RestoreState();
}
public void DrawBackground(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
if (Background is SolidColorBrush solidColorBrush)
{
if (solidColorBrush.Color != null)
canvas.FillColor = solidColorBrush.Color;
else
canvas.FillColor = Color.FromArgb(BackgroundColor);
}
else
canvas.SetFillPaint(Background, bounds);
float height = MinimumHeight;
if (!float.IsNaN(HeightRequest))
height = HeightRequest;
canvas.FillRoundedRectangle(X, Y, WidthRequest, height, CornerRadius);
canvas.RestoreState();
}
public void DrawText(ICanvas canvas, RectF bounds)
{
canvas.SaveState();
canvas.FontColor = TextColor;
canvas.FontSize = (float)FontSize;
float height = MinimumHeight;
if (!float.IsNaN(HeightRequest))
height = HeightRequest;
canvas.DrawString(Text.ToUpper(), X, Y, WidthRequest, height, HorizontalAlignment.Center, VerticalAlignment.Center);
canvas.RestoreState();
}
}
}

Просмотреть файл

@ -9,7 +9,15 @@ namespace AlohaKit.UI
public interface IView : IElement
{
Brush Background { get; set; }
}
void StartHoverInteraction(PointF[] points);
void MoveHoverInteraction(PointF[] points);
void EndHoverInteraction();
void StartInteraction(PointF[] points);
void DragInteraction(PointF[] points);
void EndInteraction(PointF[] points, bool isInsideBounds);
void CancelInteraction();
}
public class View : Element, IView
{
@ -58,7 +66,8 @@ namespace AlohaKit.UI
}
public static readonly BindableProperty BackgroundProperty =
BindableProperty.Create(nameof(Background), typeof(Brush), typeof(View), null, propertyChanged: BackgroundPropertyChanged);
BindableProperty.Create(nameof(Background), typeof(Brush), typeof(View), null,
propertyChanged: BackgroundPropertyChanged);
public static void BackgroundPropertyChanged(BindableObject bindableObject, object oldValue, object newValue)
{
@ -93,7 +102,21 @@ namespace AlohaKit.UI
}
}
void DrawBackground(ICanvas canvas, RectF bounds)
public virtual void CancelInteraction() { }
public virtual void DragInteraction(PointF[] points) { }
public virtual void EndHoverInteraction() { }
public virtual void EndInteraction(PointF[] points, bool isInsideBounds) { }
public virtual void StartHoverInteraction(PointF[] points) { }
public virtual void MoveHoverInteraction(PointF[] points) { }
public virtual void StartInteraction(PointF[] points) { }
void DrawBackground(ICanvas canvas, RectF bounds)
{
if (Background is SolidColorBrush solidColorBrush)
canvas.FillColor = solidColorBrush.Color;

Просмотреть файл

@ -0,0 +1,28 @@
using System;
namespace AlohaKit.UI.Extensions
{
public static class ColorExtensions
{
const float LighterFactor = 1.1f;
const float DarkerFactor = 0.9f;
public static Color Lighter(this Color color)
{
return new Color(
color.Red * LighterFactor,
color.Green * LighterFactor,
color.Blue * LighterFactor,
color.Alpha);
}
public static Color Darker(this Color color)
{
return new Color(
color.Red * DarkerFactor,
color.Green * DarkerFactor,
color.Blue * DarkerFactor,
color.Alpha);
}
}
}

Просмотреть файл

@ -2,9 +2,22 @@
{
public static class GestureExtensions
{
public static bool TouchInside<T>(this T view, PointF touchPoint) where T : View
public static bool IsInsideBounds<T>(this T view, PointF touchPoint) where T : View
{
var bounds = new RectF(view.X, view.Y, view.WidthRequest, view.HeightRequest);
if (view == null)
return false;
var minimumTouchSize = 24f;
var width = view.WidthRequest;
if (float.IsNaN(width))
width = minimumTouchSize;
var height = view.HeightRequest;
if (float.IsNaN(height))
height = minimumTouchSize;
var bounds = new RectF(view.X, view.Y, width, height);
if (bounds.Contains(touchPoint))
return true;