diff --git a/src/AlohaKit.UI.Gallery/AlohaKit.UI.Gallery.csproj b/src/AlohaKit.UI.Gallery/AlohaKit.UI.Gallery.csproj
new file mode 100644
index 0000000..d8ef35d
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/AlohaKit.UI.Gallery.csproj
@@ -0,0 +1,68 @@
+
+
+
+ net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ Exe
+ AlohaKit.UI.Gallery
+ true
+ true
+ enable
+
+
+ AlohaKit.UI.Gallery
+
+
+ com.companyname.alohakit.ui.gallery
+ d3f78708-ecf3-4a86-b8bb-967c5679690b
+
+
+ 1.0
+ 1
+
+ 11.0
+ 13.1
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+
+
diff --git a/src/AlohaKit.UI.Gallery/App.xaml b/src/AlohaKit.UI.Gallery/App.xaml
new file mode 100644
index 0000000..22ade60
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/App.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AlohaKit.UI.Gallery/App.xaml.cs b/src/AlohaKit.UI.Gallery/App.xaml.cs
new file mode 100644
index 0000000..cdd3ea1
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/App.xaml.cs
@@ -0,0 +1,12 @@
+namespace AlohaKit.UI.Gallery
+{
+ public partial class App : Application
+ {
+ public App()
+ {
+ InitializeComponent();
+
+ MainPage = new AppShell();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/AppShell.xaml b/src/AlohaKit.UI.Gallery/AppShell.xaml
new file mode 100644
index 0000000..229db37
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/AppShell.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/src/AlohaKit.UI.Gallery/AppShell.xaml.cs b/src/AlohaKit.UI.Gallery/AppShell.xaml.cs
new file mode 100644
index 0000000..a6279fc
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/AppShell.xaml.cs
@@ -0,0 +1,10 @@
+namespace AlohaKit.UI.Gallery
+{
+ public partial class AppShell : Shell
+ {
+ public AppShell()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/MauiProgram.cs b/src/AlohaKit.UI.Gallery/MauiProgram.cs
new file mode 100644
index 0000000..597e8fd
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/MauiProgram.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.Logging;
+
+namespace AlohaKit.UI.Gallery
+{
+ public static class MauiProgram
+ {
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+ builder
+ .UseMauiApp();
+
+#if DEBUG
+ builder.Logging.AddDebug();
+#endif
+
+ return builder.Build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Android/AndroidManifest.xml b/src/AlohaKit.UI.Gallery/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 0000000..e9937ad
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Android/MainActivity.cs b/src/AlohaKit.UI.Gallery/Platforms/Android/MainActivity.cs
new file mode 100644
index 0000000..f37e135
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Android/MainActivity.cs
@@ -0,0 +1,11 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace AlohaKit.UI.Gallery
+{
+ [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+ public class MainActivity : MauiAppCompatActivity
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Android/MainApplication.cs b/src/AlohaKit.UI.Gallery/Platforms/Android/MainApplication.cs
new file mode 100644
index 0000000..bf8e852
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Android/MainApplication.cs
@@ -0,0 +1,16 @@
+using Android.App;
+using Android.Runtime;
+
+namespace AlohaKit.UI.Gallery
+{
+ [Application]
+ public class MainApplication : MauiApplication
+ {
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Android/Resources/values/colors.xml b/src/AlohaKit.UI.Gallery/Platforms/Android/Resources/values/colors.xml
new file mode 100644
index 0000000..c04d749
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Android/Resources/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #512BD4
+ #2B0B98
+ #2B0B98
+
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/AppDelegate.cs b/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 0000000..770989e
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,10 @@
+using Foundation;
+
+namespace AlohaKit.UI.Gallery
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : MauiUIApplicationDelegate
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/Info.plist b/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 0000000..c96dd0a
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,30 @@
+
+
+
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/Program.cs b/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 0000000..4386efb
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace AlohaKit.UI.Gallery
+{
+ public class Program
+ {
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Tizen/Main.cs b/src/AlohaKit.UI.Gallery/Platforms/Tizen/Main.cs
new file mode 100644
index 0000000..422ab2a
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Tizen/Main.cs
@@ -0,0 +1,17 @@
+using Microsoft.Maui;
+using Microsoft.Maui.Hosting;
+using System;
+
+namespace AlohaKit.UI.Gallery
+{
+ internal class Program : MauiApplication
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+
+ static void Main(string[] args)
+ {
+ var app = new Program();
+ app.Run(args);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Tizen/tizen-manifest.xml b/src/AlohaKit.UI.Gallery/Platforms/Tizen/tizen-manifest.xml
new file mode 100644
index 0000000..a9f0eaf
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Tizen/tizen-manifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ maui-appicon-placeholder
+
+
+
+
+ http://tizen.org/privilege/internet
+
+
+
+
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Windows/App.xaml b/src/AlohaKit.UI.Gallery/Platforms/Windows/App.xaml
new file mode 100644
index 0000000..ce4cce3
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Windows/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Windows/App.xaml.cs b/src/AlohaKit.UI.Gallery/Platforms/Windows/App.xaml.cs
new file mode 100644
index 0000000..cca2dc5
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,24 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace AlohaKit.UI.Gallery.WinUI
+{
+ ///
+ /// Provides application-specific behavior to supplement the default Application class.
+ ///
+ public partial class App : MauiWinUIApplication
+ {
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Windows/Package.appxmanifest b/src/AlohaKit.UI.Gallery/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 0000000..2bcb11e
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ $placeholder$
+ User Name
+ $placeholder$.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AlohaKit.UI.Gallery/Platforms/Windows/app.manifest b/src/AlohaKit.UI.Gallery/Platforms/Windows/app.manifest
new file mode 100644
index 0000000..8cf125d
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/src/AlohaKit.UI.Gallery/Platforms/iOS/AppDelegate.cs b/src/AlohaKit.UI.Gallery/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 0000000..770989e
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,10 @@
+using Foundation;
+
+namespace AlohaKit.UI.Gallery
+{
+ [Register("AppDelegate")]
+ public class AppDelegate : MauiUIApplicationDelegate
+ {
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Platforms/iOS/Info.plist b/src/AlohaKit.UI.Gallery/Platforms/iOS/Info.plist
new file mode 100644
index 0000000..0004a4f
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/iOS/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/src/AlohaKit.UI.Gallery/Platforms/iOS/Program.cs b/src/AlohaKit.UI.Gallery/Platforms/iOS/Program.cs
new file mode 100644
index 0000000..4386efb
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Platforms/iOS/Program.cs
@@ -0,0 +1,16 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace AlohaKit.UI.Gallery
+{
+ public class Program
+ {
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Properties/launchSettings.json b/src/AlohaKit.UI.Gallery/Properties/launchSettings.json
new file mode 100644
index 0000000..edf8aad
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "MsixPackage",
+ "nativeDebugging": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Resources/AppIcon/appicon.svg b/src/AlohaKit.UI.Gallery/Resources/AppIcon/appicon.svg
new file mode 100644
index 0000000..9d63b65
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Resources/AppIcon/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Resources/AppIcon/appiconfg.svg b/src/AlohaKit.UI.Gallery/Resources/AppIcon/appiconfg.svg
new file mode 100644
index 0000000..21dfb25
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Resources/AppIcon/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Resources/Fonts/OpenSans-Regular.ttf b/src/AlohaKit.UI.Gallery/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 0000000..238424f
Binary files /dev/null and b/src/AlohaKit.UI.Gallery/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/src/AlohaKit.UI.Gallery/Resources/Fonts/OpenSans-Semibold.ttf b/src/AlohaKit.UI.Gallery/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 0000000..0fd77c6
Binary files /dev/null and b/src/AlohaKit.UI.Gallery/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/src/AlohaKit.UI.Gallery/Resources/Images/dotnet_bot.svg b/src/AlohaKit.UI.Gallery/Resources/Images/dotnet_bot.svg
new file mode 100644
index 0000000..abfaff2
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Resources/Images/dotnet_bot.svg
@@ -0,0 +1,93 @@
+
diff --git a/src/AlohaKit.UI.Gallery/Resources/Raw/AboutAssets.txt b/src/AlohaKit.UI.Gallery/Resources/Raw/AboutAssets.txt
new file mode 100644
index 0000000..15d6244
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,15 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories). Deployment of the asset to your application
+is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
+
+
+
+These files will be deployed with you package and will be accessible using Essentials:
+
+ async Task LoadMauiAsset()
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+ using var reader = new StreamReader(stream);
+
+ var contents = reader.ReadToEnd();
+ }
diff --git a/src/AlohaKit.UI.Gallery/Resources/Splash/splash.svg b/src/AlohaKit.UI.Gallery/Resources/Splash/splash.svg
new file mode 100644
index 0000000..21dfb25
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Resources/Splash/splash.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Resources/Styles/Colors.xaml b/src/AlohaKit.UI.Gallery/Resources/Styles/Colors.xaml
new file mode 100644
index 0000000..245758b
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Resources/Styles/Colors.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+ #512BD4
+ #DFD8F7
+ #2B0B98
+ White
+ Black
+ #E1E1E1
+ #C8C8C8
+ #ACACAC
+ #919191
+ #6E6E6E
+ #404040
+ #212121
+ #141414
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #F7B548
+ #FFD590
+ #FFE5B9
+ #28C2D1
+ #7BDDEF
+ #C3F2F4
+ #3E8EED
+ #72ACF1
+ #A7CBF6
+
+
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Resources/Styles/Styles.xaml b/src/AlohaKit.UI.Gallery/Resources/Styles/Styles.xaml
new file mode 100644
index 0000000..56eab87
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Resources/Styles/Styles.xaml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AlohaKit.UI.Gallery/Views/CSharp/MainPage.cs b/src/AlohaKit.UI.Gallery/Views/CSharp/MainPage.cs
new file mode 100644
index 0000000..f6cb9d8
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Views/CSharp/MainPage.cs
@@ -0,0 +1,30 @@
+using AlohaKit.UI.Extensions;
+using static AlohaKit.UI.UIStatics;
+
+namespace AlohaKit.UI.Gallery.Views.CSharp
+{
+ public class MainPage : ContentPage
+ {
+ public MainPage()
+ {
+ Title = "AlohaKit UI from C#";
+
+ Content =
+ CanvasView()
+ .Children(new Element[] {
+ Rectangle()
+ .X(10).Y(10)
+ .Height(80).Width(80)
+ .Fill(Colors.Red),
+ Ellipse()
+ .X(10).Y(100)
+ .Height(80).Width(80)
+ .Fill(Colors.Orange),
+ Label()
+ .X(10).Y(200)
+ .Height(20).Width(100)
+ .Text("Label"),
+ });
+ }
+ }
+}
diff --git a/src/AlohaKit.UI.Gallery/Views/MainPage.xaml b/src/AlohaKit.UI.Gallery/Views/MainPage.xaml
new file mode 100644
index 0000000..59e8e74
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Views/MainPage.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Views/MainPage.xaml.cs b/src/AlohaKit.UI.Gallery/Views/MainPage.xaml.cs
new file mode 100644
index 0000000..00c8230
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Views/MainPage.xaml.cs
@@ -0,0 +1,20 @@
+namespace AlohaKit.UI.Gallery.Views
+{
+ public partial class MainPage : ContentPage
+ {
+ public MainPage ()
+ {
+ InitializeComponent ();
+ }
+
+ private void OnCSharpButtonClicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new CSharp.MainPage());
+ }
+
+ private void OnXamlButtonClicked(object sender, EventArgs e)
+ {
+ Navigation.PushAsync(new XAML.MainPage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml b/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml
new file mode 100644
index 0000000..f1c74db
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml.cs b/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml.cs
new file mode 100644
index 0000000..470fea1
--- /dev/null
+++ b/src/AlohaKit.UI.Gallery/Views/XAML/MainPage.xaml.cs
@@ -0,0 +1,10 @@
+namespace AlohaKit.UI.Gallery.Views.XAML
+{
+ public partial class MainPage : ContentPage
+ {
+ public MainPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI.sln b/src/AlohaKit.UI.sln
new file mode 100644
index 0000000..c776b88
--- /dev/null
+++ b/src/AlohaKit.UI.sln
@@ -0,0 +1,33 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33015.44
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlohaKit.UI", "AlohaKit.UI\AlohaKit.UI.csproj", "{0E6FAD63-6F1A-4CA7-A16F-3F0765DC95A6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlohaKit.UI.Gallery", "AlohaKit.UI.Gallery\AlohaKit.UI.Gallery.csproj", "{09500699-E9A5-40B9-B72E-00E6B2CD4B38}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0E6FAD63-6F1A-4CA7-A16F-3F0765DC95A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E6FAD63-6F1A-4CA7-A16F-3F0765DC95A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E6FAD63-6F1A-4CA7-A16F-3F0765DC95A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E6FAD63-6F1A-4CA7-A16F-3F0765DC95A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {09500699-E9A5-40B9-B72E-00E6B2CD4B38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {09500699-E9A5-40B9-B72E-00E6B2CD4B38}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {09500699-E9A5-40B9-B72E-00E6B2CD4B38}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {09500699-E9A5-40B9-B72E-00E6B2CD4B38}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {09500699-E9A5-40B9-B72E-00E6B2CD4B38}.Release|Any CPU.Build.0 = Release|Any CPU
+ {09500699-E9A5-40B9-B72E-00E6B2CD4B38}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4704EC8E-DEA6-47CF-95CE-896B3E8949D1}
+ EndGlobalSection
+EndGlobal
diff --git a/src/AlohaKit.UI/AlohaKit.UI.csproj b/src/AlohaKit.UI/AlohaKit.UI.csproj
new file mode 100644
index 0000000..e738323
--- /dev/null
+++ b/src/AlohaKit.UI/AlohaKit.UI.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ true
+ true
+ enable
+
+ 14.2
+ 14.0
+ 21.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AlohaKit.UI/Controls/CanvasView.cs b/src/AlohaKit.UI/Controls/CanvasView.cs
new file mode 100644
index 0000000..2f9ad5a
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/CanvasView.cs
@@ -0,0 +1,47 @@
+namespace AlohaKit.UI
+{
+ class CanvasViewDrawable : IDrawable
+ {
+ readonly CanvasView _owner;
+
+ public CanvasViewDrawable(CanvasView owner)
+ {
+ _owner = owner;
+ }
+
+ public void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ _owner.DrawCore(canvas, bounds);
+
+ canvas.RestoreState();
+ }
+ }
+
+ [ContentProperty(nameof(Children))]
+ public class CanvasView : GraphicsView
+ {
+ public CanvasView()
+ {
+ Children = new ElementsCollection();
+
+ Drawable = new CanvasViewDrawable(this);
+ }
+
+ public ElementsCollection Children { get; internal set; }
+
+ internal void DrawCore(ICanvas canvas, RectF bounds)
+ {
+ Draw(canvas, bounds);
+ }
+
+ public virtual void Draw(ICanvas canvas, RectF bounds)
+ {
+ foreach (var child in Children)
+ {
+ child.Draw(canvas, bounds);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Element.cs b/src/AlohaKit.UI/Controls/Element.cs
new file mode 100644
index 0000000..cd9ea4a
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Element.cs
@@ -0,0 +1,79 @@
+namespace AlohaKit.UI
+{
+ public interface IElement : IVisualElement
+ {
+ IElement Parent { get; set; }
+ ElementsCollection Children { get; }
+
+ void AttachParent(IElement parent);
+ }
+
+ [ContentProperty(nameof(Children))]
+ public class Element : VisualElement, IElement
+ {
+ IElement _parent;
+ RectF _childrenBounds;
+
+
+ public Element()
+ {
+ Children = new ElementsCollection(this);
+ }
+
+ public IElement Parent
+ {
+ get => _parent;
+ set
+ {
+ if (_parent != value)
+ {
+ _parent?.Children.Remove(this);
+ AttachParent(value);
+ _parent?.Children.Add(this);
+ }
+ }
+ }
+
+ public ElementsCollection Children { get; private set; }
+
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ base.Draw(canvas, bounds);
+
+ DrawChildren(canvas, bounds);
+ }
+
+ protected virtual void DrawChildren(ICanvas canvas, RectF bounds)
+ {
+ foreach (var child in Children)
+ {
+ child.Draw(canvas, bounds);
+ }
+ }
+
+ public virtual void AttachParent(IElement parent)
+ {
+ _parent = parent;
+ }
+
+ public override void Invalidate()
+ {
+ base.Invalidate();
+
+ Parent?.Invalidate();
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ foreach (var child in Children)
+ {
+ if (child is BindableObject bChild)
+ {
+ SetInheritedBindingContext(bChild, this.BindingContext);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/ElementsCollection.cs b/src/AlohaKit.UI/Controls/ElementsCollection.cs
new file mode 100644
index 0000000..54a5509
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/ElementsCollection.cs
@@ -0,0 +1,53 @@
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+
+namespace AlohaKit.UI
+{
+ public class ElementsCollection : ObservableCollection
+ {
+ readonly IElement _parent;
+
+ public ElementsCollection()
+ {
+
+ }
+
+ internal ElementsCollection(IElement parent)
+ {
+ _parent = parent;
+ }
+
+ protected override void ClearItems()
+ {
+ base.ClearItems();
+ }
+
+ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+ {
+ base.OnCollectionChanged(e);
+
+ if (e.OldItems != null)
+ {
+ foreach (var oldItem in e.OldItems)
+ {
+ ((IElement)oldItem).AttachParent(null);
+ }
+ }
+
+ if (e.NewItems != null)
+ {
+ foreach (var newItem in e.NewItems)
+ {
+ ((IElement)newItem).AttachParent(_parent);
+ }
+ }
+
+ Invalidate();
+ }
+
+ void Invalidate()
+ {
+ _parent?.Invalidate();
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Controls/Ellipse.cs b/src/AlohaKit.UI/Controls/Ellipse.cs
new file mode 100644
index 0000000..9855c1e
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Ellipse.cs
@@ -0,0 +1,34 @@
+namespace AlohaKit.UI
+{
+ public class Ellipse : Shape
+ {
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ var rect = new Rect(X, Y, WidthRequest, HeightRequest);
+
+ if (Fill != null)
+ {
+ if (Fill is SolidColorBrush solidColorBrush)
+ canvas.FillColor = solidColorBrush.Color;
+ else
+ canvas.SetFillPaint(Fill, rect);
+
+ canvas.FillEllipse(rect);
+ }
+
+ if (Stroke != null)
+ {
+ if (Stroke is SolidColorBrush solidColorBrush)
+ canvas.StrokeColor = solidColorBrush.Color;
+
+ canvas.StrokeSize = (float)StrokeThickness;
+
+ canvas.DrawEllipse(rect);
+ }
+
+ canvas.RestoreState();
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Controls/Image.cs b/src/AlohaKit.UI/Controls/Image.cs
new file mode 100644
index 0000000..895613b
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Image.cs
@@ -0,0 +1,7 @@
+namespace AlohaKit.UI
+{
+ public class Image : View
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Label.cs b/src/AlohaKit.UI/Controls/Label.cs
new file mode 100644
index 0000000..4af2306
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Label.cs
@@ -0,0 +1,52 @@
+namespace AlohaKit.UI
+{
+ public class Label : View
+ {
+ public static readonly BindableProperty TextProperty =
+ BindableProperty.Create(nameof(Text), typeof(string), typeof(Label), string.Empty,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty TextColorProperty =
+ BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(Label), null,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty FontSizeProperty =
+ BindableProperty.Create(nameof(FontSize), typeof(double), typeof(Label), 12.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 override void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ base.Draw(canvas, bounds);
+
+ if (Text != null)
+ {
+ canvas.FontColor = TextColor;
+ canvas.FontSize = (float)FontSize;
+
+ canvas.DrawString(Text, new Rect(X, Y, WidthRequest, HeightRequest), HorizontalAlignment.Left, VerticalAlignment.Top);
+ }
+
+ canvas.RestoreState();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Line.cs b/src/AlohaKit.UI/Controls/Line.cs
new file mode 100644
index 0000000..e5b6890
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Line.cs
@@ -0,0 +1,78 @@
+using AlohaKit.UI.Extensions;
+
+namespace AlohaKit.UI
+{
+ public class Line : Shape
+ {
+ public static readonly BindableProperty X1Property =
+ BindableProperty.Create(nameof(X1), typeof(double), typeof(Line), 0.0d);
+
+ public static readonly BindableProperty Y1Property =
+ BindableProperty.Create(nameof(Y1), typeof(double), typeof(Line), 0.0d);
+
+ public static readonly BindableProperty X2Property =
+ BindableProperty.Create(nameof(X2), typeof(double), typeof(Line), 0.0d);
+
+ public static readonly BindableProperty Y2Property =
+ BindableProperty.Create(nameof(Y2), typeof(double), typeof(Line), 0.0d);
+
+ public double X1
+ {
+ set { SetValue(X1Property, value); }
+ get { return (double)GetValue(X1Property); }
+ }
+
+ public double Y1
+ {
+ set { SetValue(Y1Property, value); }
+ get { return (double)GetValue(Y1Property); }
+ }
+
+ public double X2
+ {
+ set { SetValue(X2Property, value); }
+ get { return (double)GetValue(X2Property); }
+ }
+
+ public double Y2
+ {
+ set { SetValue(Y2Property, value); }
+ get { return (double)GetValue(Y2Property); }
+ }
+
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ base.Draw(canvas, bounds);
+
+ if (Stroke != null)
+ {
+ if (Stroke is SolidColorBrush solidColorBrush)
+ canvas.StrokeColor = solidColorBrush.Color;
+
+ canvas.StrokeSize = (float)StrokeThickness;
+ canvas.StrokeLineCap = StrokeLineCap.ToLineJoin();
+ canvas.StrokeLineJoin = StrokeLineJoin.ToLineJoin();
+ canvas.StrokeDashPattern = StrokeDashArray.ToStrokeDashPattern();
+ canvas.StrokeDashOffset = (float)StrokeDashOffset;
+ canvas.MiterLimit = (float)StrokeMiterLimit;
+
+ var path = GetPath();
+
+ canvas.DrawPath(path);
+ }
+
+ canvas.RestoreState();
+ }
+
+ PathF GetPath()
+ {
+ var path = new PathF();
+ path.MoveTo((float)X1, (float)Y1);
+ path.LineTo((float)X2, (float)Y2);
+
+ return path;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Controls/Path.cs b/src/AlohaKit.UI/Controls/Path.cs
new file mode 100644
index 0000000..67df750
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Path.cs
@@ -0,0 +1,56 @@
+using AlohaKit.UI.Extensions;
+using Microsoft.Maui.Controls.Shapes;
+using System.ComponentModel;
+
+namespace AlohaKit.UI
+{
+ public class Path : Shape
+ {
+ public static readonly BindableProperty DataProperty =
+ BindableProperty.Create(nameof(Data), typeof(Geometry), typeof(Path), null);
+
+ [TypeConverter(typeof(PathGeometryConverter))]
+ public Geometry Data
+ {
+ set { SetValue(DataProperty, value); }
+ get { return (Geometry)GetValue(DataProperty); }
+ }
+
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ base.Draw(canvas, bounds);
+
+ if (Stroke != null)
+ {
+ canvas.Translate(X, Y);
+
+ if (Stroke is SolidColorBrush solidColorBrush)
+ canvas.StrokeColor = solidColorBrush.Color;
+
+ canvas.StrokeSize = (float)StrokeThickness;
+ canvas.StrokeLineCap = StrokeLineCap.ToLineJoin();
+ canvas.StrokeLineJoin = StrokeLineJoin.ToLineJoin();
+ canvas.StrokeDashPattern = StrokeDashArray.ToStrokeDashPattern();
+ canvas.StrokeDashOffset = (float)StrokeDashOffset;
+ canvas.MiterLimit = (float)StrokeMiterLimit;
+
+ var path = GetPath();
+
+ canvas.DrawPath(path);
+ }
+
+ canvas.RestoreState();
+ }
+
+ PathF GetPath()
+ {
+ var path = new PathF();
+
+ Data?.AppendPath(path);
+
+ return path;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Polygon.cs b/src/AlohaKit.UI/Controls/Polygon.cs
new file mode 100644
index 0000000..8d41661
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Polygon.cs
@@ -0,0 +1,61 @@
+using AlohaKit.UI.Extensions;
+
+namespace AlohaKit.UI
+{
+ public class Polygon : Shape
+ {
+ public static readonly BindableProperty PointsProperty =
+ BindableProperty.Create(nameof(Points), typeof(PointCollection), typeof(Polygon), null, defaultValueCreator: bindable => new PointCollection());
+
+ public PointCollection Points
+ {
+ set { SetValue(PointsProperty, value); }
+ get { return (PointCollection)GetValue(PointsProperty); }
+ }
+
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ base.Draw(canvas, bounds);
+
+ if (Stroke != null)
+ {
+ canvas.Translate(X, Y);
+
+ if (Stroke is SolidColorBrush solidColorBrush)
+ canvas.StrokeColor = solidColorBrush.Color;
+
+ canvas.StrokeSize = (float)StrokeThickness;
+ canvas.StrokeLineCap = StrokeLineCap.ToLineJoin();
+ canvas.StrokeLineJoin = StrokeLineJoin.ToLineJoin();
+ canvas.StrokeDashPattern = StrokeDashArray.ToStrokeDashPattern();
+ canvas.StrokeDashOffset = (float)StrokeDashOffset;
+ canvas.MiterLimit = (float)StrokeMiterLimit;
+
+ var path = GetPath();
+
+ canvas.DrawPath(path);
+ }
+
+ canvas.RestoreState();
+ }
+
+ PathF GetPath()
+ {
+ var path = new PathF();
+
+ if (Points?.Count > 0)
+ {
+ path.MoveTo((float)Points[0].X, (float)Points[0].Y);
+
+ for (int index = 1; index < Points.Count; index++)
+ path.LineTo((float)Points[index].X, (float)Points[index].Y);
+
+ path.Close();
+ }
+
+ return path;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Polyline.cs b/src/AlohaKit.UI/Controls/Polyline.cs
new file mode 100644
index 0000000..fb3757f
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Polyline.cs
@@ -0,0 +1,59 @@
+using AlohaKit.UI.Extensions;
+
+namespace AlohaKit.UI
+{
+ public class Polyline : Shape
+ {
+ public static readonly BindableProperty PointsProperty =
+ BindableProperty.Create(nameof(Points), typeof(PointCollection), typeof(Polyline), null, defaultValueCreator: bindable => new PointCollection());
+
+ public PointCollection Points
+ {
+ set { SetValue(PointsProperty, value); }
+ get { return (PointCollection)GetValue(PointsProperty); }
+ }
+
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ base.Draw(canvas, bounds);
+
+ if (Stroke != null)
+ {
+ canvas.Translate(X, Y);
+
+ if (Stroke is SolidColorBrush solidColorBrush)
+ canvas.StrokeColor = solidColorBrush.Color;
+
+ canvas.StrokeSize = (float)StrokeThickness;
+ canvas.StrokeLineCap = StrokeLineCap.ToLineJoin();
+ canvas.StrokeLineJoin = StrokeLineJoin.ToLineJoin();
+ canvas.StrokeDashPattern = StrokeDashArray.ToStrokeDashPattern();
+ canvas.StrokeDashOffset = (float)StrokeDashOffset;
+ canvas.MiterLimit = (float)StrokeMiterLimit;
+
+ var path = GetPath();
+
+ canvas.DrawPath(path);
+ }
+
+ canvas.RestoreState();
+ }
+
+ PathF GetPath()
+ {
+ var path = new PathF();
+
+ if (Points?.Count > 0)
+ {
+ path.MoveTo((float)Points[0].X, (float)Points[0].Y);
+
+ for (int index = 1; index < Points.Count; index++)
+ path.LineTo((float)Points[index].X, (float)Points[index].Y);
+ }
+
+ return path;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Controls/Rectangle.cs b/src/AlohaKit.UI/Controls/Rectangle.cs
new file mode 100644
index 0000000..f6f54e4
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Rectangle.cs
@@ -0,0 +1,36 @@
+namespace AlohaKit.UI
+{
+ public class Rectangle : Shape
+ {
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ base.Draw(canvas, bounds);
+
+ var rect = new Rect(X, Y, WidthRequest, HeightRequest);
+
+ if (Fill != null)
+ {
+ if (Fill is SolidColorBrush solidColorBrush)
+ canvas.FillColor = solidColorBrush.Color;
+ else
+ canvas.SetFillPaint(Fill, rect);
+
+ canvas.FillRectangle(rect);
+ }
+
+ if(Stroke != null)
+ {
+ if (Stroke is SolidColorBrush solidColorBrush)
+ canvas.StrokeColor = solidColorBrush.Color;
+
+ canvas.StrokeSize = (float)StrokeThickness;
+
+ canvas.DrawRectangle(rect);
+ }
+
+ canvas.RestoreState();
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Controls/RoundRectangle.cs b/src/AlohaKit.UI/Controls/RoundRectangle.cs
new file mode 100644
index 0000000..63650a4
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/RoundRectangle.cs
@@ -0,0 +1,45 @@
+namespace AlohaKit.UI
+{
+ public class RoundRectangle : Shape
+ {
+ public static readonly BindableProperty CornerRadiusProperty =
+ BindableProperty.Create(nameof(CornerRadius), typeof(CornerRadius), typeof(RoundRectangle), new CornerRadius());
+
+ public CornerRadius CornerRadius
+ {
+ get => (CornerRadius)GetValue(CornerRadiusProperty);
+ set => SetValue(CornerRadiusProperty, value);
+ }
+
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ canvas.SaveState();
+
+ base.Draw(canvas, bounds);
+
+ var rect = new Rect(X, Y, WidthRequest, HeightRequest);
+
+ if (Fill != null)
+ {
+ if (Fill is SolidColorBrush solidColorBrush)
+ canvas.FillColor = solidColorBrush.Color;
+ else
+ canvas.SetFillPaint(Fill, rect);
+
+ canvas.FillRoundedRectangle(rect, CornerRadius.TopLeft, CornerRadius.TopRight, CornerRadius.BottomLeft, CornerRadius.BottomRight);
+ }
+
+ if (Stroke != null)
+ {
+ if (Stroke is SolidColorBrush solidColorBrush)
+ canvas.StrokeColor = solidColorBrush.Color;
+
+ canvas.StrokeSize = (float)StrokeThickness;
+
+ canvas.DrawRoundedRectangle(rect, CornerRadius.TopLeft, CornerRadius.TopRight, CornerRadius.BottomLeft, CornerRadius.BottomRight);
+ }
+
+ canvas.RestoreState();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/Shape.cs b/src/AlohaKit.UI/Controls/Shape.cs
new file mode 100644
index 0000000..d4934f2
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/Shape.cs
@@ -0,0 +1,86 @@
+using Microsoft.Maui.Controls.Shapes;
+
+namespace AlohaKit.UI
+{
+ public abstract class Shape : View
+ {
+ public static readonly BindableProperty FillProperty =
+ BindableProperty.Create(nameof(Fill), typeof(Brush), typeof(Shape), null,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty StrokeProperty =
+ BindableProperty.Create(nameof(Stroke), typeof(Brush), typeof(Shape), null,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty StrokeThicknessProperty =
+ BindableProperty.Create(nameof(StrokeThickness), typeof(double), typeof(Shape), 1.0,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty StrokeDashArrayProperty =
+ BindableProperty.Create(nameof(StrokeDashArray), typeof(DoubleCollection), typeof(Shape), null,
+ defaultValueCreator: bindable => new DoubleCollection(), propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty StrokeDashOffsetProperty =
+ BindableProperty.Create(nameof(StrokeDashOffset), typeof(double), typeof(Shape), 0.0,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty StrokeLineCapProperty =
+ BindableProperty.Create(nameof(StrokeLineCap), typeof(PenLineCap), typeof(Shape), PenLineCap.Flat,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty StrokeLineJoinProperty =
+ BindableProperty.Create(nameof(StrokeLineJoin), typeof(PenLineJoin), typeof(Shape), PenLineJoin.Miter,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty StrokeMiterLimitProperty =
+ BindableProperty.Create(nameof(StrokeMiterLimit), typeof(double), typeof(Shape), 10.0,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public Brush Fill
+ {
+ set { SetValue(FillProperty, value); }
+ get { return (Brush)GetValue(FillProperty); }
+ }
+
+ public Brush Stroke
+ {
+ set { SetValue(StrokeProperty, value); }
+ get { return (Brush)GetValue(StrokeProperty); }
+ }
+
+ public double StrokeThickness
+ {
+ set { SetValue(StrokeThicknessProperty, value); }
+ get { return (double)GetValue(StrokeThicknessProperty); }
+ }
+ public DoubleCollection StrokeDashArray
+ {
+ set { SetValue(StrokeDashArrayProperty, value); }
+ get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); }
+ }
+
+ public double StrokeDashOffset
+ {
+ set { SetValue(StrokeDashOffsetProperty, value); }
+ get { return (double)GetValue(StrokeDashOffsetProperty); }
+ }
+
+ public PenLineCap StrokeLineCap
+ {
+ set { SetValue(StrokeLineCapProperty, value); }
+ get { return (PenLineCap)GetValue(StrokeLineCapProperty); }
+ }
+
+ public PenLineJoin StrokeLineJoin
+ {
+ set { SetValue(StrokeLineJoinProperty, value); }
+ get { return (PenLineJoin)GetValue(StrokeLineJoinProperty); }
+ }
+
+ public double StrokeMiterLimit
+ {
+ set { SetValue(StrokeMiterLimitProperty, value); }
+ get { return (double)GetValue(StrokeMiterLimitProperty); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/View.cs b/src/AlohaKit.UI/Controls/View.cs
new file mode 100644
index 0000000..34930a1
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/View.cs
@@ -0,0 +1,52 @@
+using AlohaKit.UI.Extensions;
+using Microsoft.Maui.Graphics.Converters;
+using System.ComponentModel;
+
+namespace AlohaKit.UI
+{
+ public interface IView : IElement
+ {
+ Brush Background { get; set; }
+ }
+
+ public class View : Element, IView
+ {
+ protected bool _drawBackground;
+
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(nameof(Background), typeof(Brush), typeof(View), null, propertyChanged: BackgroundPropertyChanged);
+
+ public static void BackgroundPropertyChanged(BindableObject bindableObject, object oldValue, object newValue)
+ {
+ ((View)bindableObject).Invalidate();
+ ((View)bindableObject)._drawBackground = true;
+ }
+
+ [TypeConverter(typeof(ColorTypeConverter))]
+ public Brush Background
+ {
+ get => (Color)GetValue(BackgroundProperty);
+ set => SetValue(BackgroundProperty, value);
+ }
+
+ public override void Draw(ICanvas canvas, RectF bounds)
+ {
+ if (IsVisible)
+ {
+ if (_drawBackground)
+ {
+ if (Background is SolidColorBrush solidColorBrush)
+ canvas.FillColor = solidColorBrush.Color;
+ else
+ canvas.SetFillPaint(Background, bounds);
+
+ canvas.FillRectangle(bounds);
+ }
+
+ canvas.Transform(TranslationX, TranslationY, ScaleX, ScaleY);
+
+ base.Draw(canvas, bounds);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Controls/VisualElement.cs b/src/AlohaKit.UI/Controls/VisualElement.cs
new file mode 100644
index 0000000..c9bddfb
--- /dev/null
+++ b/src/AlohaKit.UI/Controls/VisualElement.cs
@@ -0,0 +1,127 @@
+namespace AlohaKit.UI
+{
+ public interface IVisualElement
+ {
+ bool IsVisible { get; set; }
+
+ float X { get; set; }
+ float Y { get; set; }
+ float WidthRequest { get; set; }
+ float HeightRequest { get; set; }
+
+ float TranslationX { get; set; }
+ float TranslationY { get; set; }
+ float ScaleX { get; set; }
+ float ScaleY { get; set; }
+
+ // Draw
+ void Draw(ICanvas canvas, RectF bounds);
+ void Invalidate();
+ }
+
+ public abstract class VisualElement : BindableObject, IVisualElement
+ {
+ public static readonly BindableProperty IsVisibleProperty =
+ BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(VisualElement), true,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty XProperty =
+ BindableProperty.Create(nameof(X), typeof(float), typeof(VisualElement), 0.0f,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty YProperty =
+ BindableProperty.Create(nameof(Y), typeof(float), typeof(VisualElement), 0.0f,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty WidthRequestProperty =
+ BindableProperty.Create(nameof(WidthRequest), typeof(float), typeof(VisualElement), float.NaN,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty HeightRequestProperty =
+ BindableProperty.Create(nameof(HeightRequest), typeof(float), typeof(VisualElement), float.NaN,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty TranslationXProperty =
+ BindableProperty.Create(nameof(TranslationX), typeof(float), typeof(VisualElement), 0f,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty TranslationYProperty =
+ BindableProperty.Create(nameof(TranslationY), typeof(float), typeof(VisualElement), 0f,
+ propertyChanged: InvalidatePropertyChanged);
+
+ public static readonly BindableProperty ScaleXProperty =
+ BindableProperty.Create(nameof(ScaleX), typeof(float), typeof(VisualElement), 1f);
+
+ public static readonly BindableProperty ScaleYProperty =
+ BindableProperty.Create(nameof(ScaleY), typeof(float), typeof(VisualElement), 1f);
+
+ public static void InvalidatePropertyChanged(BindableObject bo, object oldValue, object newValue)
+ {
+ ((VisualElement)bo).Invalidate();
+ }
+
+ public bool IsVisible
+ {
+ get => (bool)GetValue(IsVisibleProperty);
+ set => SetValue(IsVisibleProperty, value);
+ }
+
+ public float X
+ {
+ get => (float)GetValue(XProperty);
+ set => SetValue(XProperty, value);
+ }
+
+ public float Y
+ {
+ get => (float)GetValue(YProperty);
+ set => SetValue(YProperty, value);
+ }
+
+ public float WidthRequest
+ {
+ get => (float)GetValue(WidthRequestProperty);
+ set => SetValue(WidthRequestProperty, value);
+ }
+
+ public float HeightRequest
+ {
+ get => (float)GetValue(HeightRequestProperty);
+ set => SetValue(HeightRequestProperty, value);
+ }
+
+ public float TranslationX
+ {
+ get => (float)GetValue(TranslationXProperty);
+ set => SetValue(TranslationXProperty, value);
+ }
+
+ public float TranslationY
+ {
+ get => (float)GetValue(TranslationYProperty);
+ set => SetValue(TranslationYProperty, value);
+ }
+
+ public float ScaleX
+ {
+ get => (float)GetValue(ScaleXProperty);
+ set => SetValue(ScaleXProperty, value);
+ }
+
+ public float ScaleY
+ {
+ get => (float)GetValue(ScaleYProperty);
+ set => SetValue(ScaleYProperty, value);
+ }
+
+ public virtual void Draw(ICanvas canvas, RectF bounds)
+ {
+
+ }
+
+ public virtual void Invalidate()
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Extensions/CanvasExtensions.cs b/src/AlohaKit.UI/Extensions/CanvasExtensions.cs
new file mode 100644
index 0000000..be77caf
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/CanvasExtensions.cs
@@ -0,0 +1,11 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class CanvasExtensions
+ {
+ public static void Transform(this ICanvas canvas, float tX, float tY, float sX, float sY)
+ {
+ canvas.Translate(tX, tY);
+ canvas.Scale(sX, sY);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Extensions/CanvasViewExtensions.cs b/src/AlohaKit.UI/Extensions/CanvasViewExtensions.cs
new file mode 100644
index 0000000..8c19c59
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/CanvasViewExtensions.cs
@@ -0,0 +1,33 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class CanvasViewExtensions
+ {
+ public static T Drawable(this T canvasView, IDrawable drawable) where T : CanvasView
+ {
+ canvasView.Drawable = drawable;
+
+ return canvasView;
+ }
+
+ public static T Children(this T canvasView, ElementsCollection children) where T : CanvasView
+ {
+ canvasView.Children = children;
+
+ return canvasView;
+ }
+
+ public static T Children(this T canvasView, Element[] children) where T : CanvasView
+ {
+ var elementsCollection = new ElementsCollection();
+
+ foreach (var element in children)
+ {
+ elementsCollection.Add(element);
+ }
+
+ canvasView.Children = elementsCollection;
+
+ return canvasView;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Extensions/ImageExtensions.cs b/src/AlohaKit.UI/Extensions/ImageExtensions.cs
new file mode 100644
index 0000000..dd7f6d6
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/ImageExtensions.cs
@@ -0,0 +1,7 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class ImageExtensions
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Extensions/LabelExtensions.cs b/src/AlohaKit.UI/Extensions/LabelExtensions.cs
new file mode 100644
index 0000000..4f1de80
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/LabelExtensions.cs
@@ -0,0 +1,26 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class LabelExtensions
+ {
+ public static T Text(this T label, string text) where T : Label
+ {
+ label.Text = text;
+
+ return label;
+ }
+
+ public static T TextColor(this T label, Color textColor) where T : Label
+ {
+ label.TextColor = textColor;
+
+ return label;
+ }
+
+ public static T FontSize(this T label, double fontSize) where T : Label
+ {
+ label.FontSize = fontSize;
+
+ return label;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Extensions/LineExtensions.cs b/src/AlohaKit.UI/Extensions/LineExtensions.cs
new file mode 100644
index 0000000..4d1c25a
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/LineExtensions.cs
@@ -0,0 +1,33 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class LineExtensions
+ {
+ public static T X1(this T line, double x1) where T : Line
+ {
+ line.X1 = x1;
+
+ return line;
+ }
+
+ public static T X2(this T line, double x2) where T : Line
+ {
+ line.X2 = x2;
+
+ return line;
+ }
+
+ public static T Y1(this T line, double y1) where T : Line
+ {
+ line.Y1 = y1;
+
+ return line;
+ }
+
+ public static T Y2(this T line, double y2) where T : Line
+ {
+ line.Y2 = y2;
+
+ return line;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Extensions/PathExtensions.cs b/src/AlohaKit.UI/Extensions/PathExtensions.cs
new file mode 100644
index 0000000..036589e
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/PathExtensions.cs
@@ -0,0 +1,14 @@
+using Microsoft.Maui.Controls.Shapes;
+
+namespace AlohaKit.UI.Extensions
+{
+ public static class PathExtensions
+ {
+ public static T Data(this T path, Geometry data) where T : Path
+ {
+ path.Data = data;
+
+ return path;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AlohaKit.UI/Extensions/PolygonExtensions.cs b/src/AlohaKit.UI/Extensions/PolygonExtensions.cs
new file mode 100644
index 0000000..ae9449b
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/PolygonExtensions.cs
@@ -0,0 +1,19 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class PolygonExtensions
+ {
+ public static T Points(this T polygon, PointCollection points) where T : Polygon
+ {
+ polygon.Points = points;
+
+ return polygon;
+ }
+
+ public static T Points(this T polygon, Point[] points) where T : Polygon
+ {
+ polygon.Points = new PointCollection(points);
+
+ return polygon;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Extensions/PolylineExtensions.cs b/src/AlohaKit.UI/Extensions/PolylineExtensions.cs
new file mode 100644
index 0000000..f4ae10e
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/PolylineExtensions.cs
@@ -0,0 +1,19 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class PolylineExtensions
+ {
+ public static T Points(this T polyline, PointCollection points) where T : Polyline
+ {
+ polyline.Points = points;
+
+ return polyline;
+ }
+
+ public static T Points(this T polyline, Point[] points) where T : Polyline
+ {
+ polyline.Points = new PointCollection(points);
+
+ return polyline;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Extensions/RoundRectangleExtensions.cs b/src/AlohaKit.UI/Extensions/RoundRectangleExtensions.cs
new file mode 100644
index 0000000..f295b52
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/RoundRectangleExtensions.cs
@@ -0,0 +1,12 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class RoundRectangleExtensions
+ {
+ public static T CornerRadius(this T roundRectangle, CornerRadius cornerRadius) where T : RoundRectangle
+ {
+ roundRectangle.CornerRadius = cornerRadius;
+
+ return roundRectangle;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Extensions/ShapeExtensions.cs b/src/AlohaKit.UI/Extensions/ShapeExtensions.cs
new file mode 100644
index 0000000..dcdce47
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/ShapeExtensions.cs
@@ -0,0 +1,70 @@
+using Microsoft.Maui.Controls.Shapes;
+
+namespace AlohaKit.UI.Extensions
+{
+ public static class ShapeExtensions
+ {
+ public static T Fill(this T shape, Brush fill) where T : Shape
+ {
+ shape.Fill = fill;
+
+ return shape;
+ }
+
+ public static T Stroke(this T shape, Brush stroke) where T : Shape
+ {
+ shape.Stroke = stroke;
+
+ return shape;
+ }
+
+ public static T StrokeThickness(this T shape, double strokeThickness) where T : Shape
+ {
+ shape.StrokeThickness = strokeThickness;
+
+ return shape;
+ }
+
+ public static LineCap ToLineJoin(this PenLineCap penLineCap)
+ {
+ switch (penLineCap)
+ {
+ case PenLineCap.Flat:
+ return LineCap.Butt;
+ case PenLineCap.Round:
+ return LineCap.Round;
+ case PenLineCap.Square:
+ return LineCap.Square;
+ default:
+ return LineCap.Butt;
+ };
+ }
+
+ public static LineJoin ToLineJoin(this PenLineJoin penLineJoin)
+ {
+ switch(penLineJoin)
+ {
+ case PenLineJoin.Round:
+ return LineJoin.Round;
+ case PenLineJoin.Bevel:
+ return LineJoin.Bevel;
+ case PenLineJoin.Miter:
+ return LineJoin.Miter;
+ default:
+ return LineJoin.Miter;
+ };
+ }
+
+ public static float[] ToStrokeDashPattern(this DoubleCollection strokeDashArray)
+ {
+ float[] result = new float[strokeDashArray.Count];
+
+ for(int i = 0; i < strokeDashArray.Count; i++)
+ {
+ result[i] = (float)strokeDashArray[i];
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/Extensions/ViewExtensions.cs b/src/AlohaKit.UI/Extensions/ViewExtensions.cs
new file mode 100644
index 0000000..ba4098c
--- /dev/null
+++ b/src/AlohaKit.UI/Extensions/ViewExtensions.cs
@@ -0,0 +1,47 @@
+namespace AlohaKit.UI.Extensions
+{
+ public static class ViewExtensions
+ {
+ public static T IsVisible(this T view, bool isVisible) where T : View
+ {
+ view.IsVisible = isVisible;
+
+ return view;
+ }
+
+ public static T X(this T view, float x) where T : View
+ {
+ view.X = x;
+
+ return view;
+ }
+
+ public static T Y(this T view, float y) where T : View
+ {
+ view.Y = y;
+
+ return view;
+ }
+
+ public static T Height(this T view, float height) where T : View
+ {
+ view.HeightRequest = height;
+
+ return view;
+ }
+
+ public static T Width(this T view, float width) where T : View
+ {
+ view.WidthRequest = width;
+
+ return view;
+ }
+
+ public static T Background(this T view, Brush background) where T : View
+ {
+ view.Background = background;
+
+ return view;
+ }
+ }
+}
diff --git a/src/AlohaKit.UI/UIStatics.cs b/src/AlohaKit.UI/UIStatics.cs
new file mode 100644
index 0000000..cc6a265
--- /dev/null
+++ b/src/AlohaKit.UI/UIStatics.cs
@@ -0,0 +1,15 @@
+namespace AlohaKit.UI
+{
+ public static class UIStatics
+ {
+ public static CanvasView CanvasView() => new CanvasView();
+ public static Ellipse Ellipse() => new Ellipse();
+ public static Image Image() => new Image();
+ public static Label Label() => new Label();
+ public static Line Line() => new Line();
+ public static Path Path() => new Path();
+ public static Polygon Polygon() => new Polygon();
+ public static Polyline Polyline() => new Polyline();
+ public static Rectangle Rectangle() => new Rectangle();
+ }
+}