diff --git a/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml b/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml
index 2e4154a..5e913ce 100644
--- a/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml
+++ b/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml
@@ -67,5 +67,12 @@
X="100" Y="200"
Data="M 10,100 L 100,100 100,50Z"
Stroke="Black" />
+
diff --git a/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml.cs b/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml.cs
index 3223748..0f201cf 100644
--- a/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml.cs
+++ b/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml.cs
@@ -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");
}
}
}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Button.cs b/src/AlohaKit.UI/Controls/Button.cs
new file mode 100644
index 0000000..036f9fd
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Button.cs
@@ -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
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/CanvasView.cs b/src/AlohaKit.UI/Controls/CanvasView.cs
index 8065129..2fcb930 100644
--- a/src/AlohaKit.UI/Controls/CanvasView.cs
+++ b/src/AlohaKit.UI/Controls/CanvasView.cs
@@ -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();
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Cupertino/Button.cs b/src/AlohaKit.UI/Controls/Cupertino/Button.cs
new file mode 100644
index 0000000..6c515ab
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Cupertino/Button.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Element.cs b/src/AlohaKit.UI/Controls/Element.cs
index cd9ea4a..8ca45e2 100644
--- a/src/AlohaKit.UI/Controls/Element.cs
+++ b/src/AlohaKit.UI/Controls/Element.cs
@@ -12,8 +12,6 @@
public class Element : VisualElement, IElement
{
IElement _parent;
- RectF _childrenBounds;
-
public Element()
{
diff --git a/src/AlohaKit.UI/Controls/ElementsCollection.cs b/src/AlohaKit.UI/Controls/ElementsCollection.cs
index 54a5509..a68c9cd 100644
--- a/src/AlohaKit.UI/Controls/ElementsCollection.cs
+++ b/src/AlohaKit.UI/Controls/ElementsCollection.cs
@@ -6,6 +6,7 @@ namespace AlohaKit.UI
public class ElementsCollection : ObservableCollection
{
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();
}
}
diff --git a/src/AlohaKit.UI/Controls/Fluent/Button.cs b/src/AlohaKit.UI/Controls/Fluent/Button.cs
new file mode 100644
index 0000000..018b1dc
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Fluent/Button.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Material/Button.cs b/src/AlohaKit.UI/Controls/Material/Button.cs
new file mode 100644
index 0000000..eedd4b7
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Material/Button.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/View.cs b/src/AlohaKit.UI/Controls/View.cs
index a7542b1..552b91f 100644
--- a/src/AlohaKit.UI/Controls/View.cs
+++ b/src/AlohaKit.UI/Controls/View.cs
@@ -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;
diff --git a/src/AlohaKit.UI/Extensions/ColorExtensions.cs b/src/AlohaKit.UI/Extensions/ColorExtensions.cs
new file mode 100644
index 0000000..d94d829
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/ColorExtensions.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Extensions/GestureExtensions.cs b/src/AlohaKit.UI/Extensions/GestureExtensions.cs
index 32d852b..7da2894 100644
--- a/src/AlohaKit.UI/Extensions/GestureExtensions.cs
+++ b/src/AlohaKit.UI/Extensions/GestureExtensions.cs
@@ -2,9 +2,22 @@
{
public static class GestureExtensions
{
- public static bool TouchInside(this T view, PointF touchPoint) where T : View
+ public static bool IsInsideBounds(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;