diff --git a/.vscode/launch.json b/.vscode/launch.json index 2cfae82..bd500c8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,9 +10,9 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceRoot}/Samples/bin/Debug/netcoreapp2.0/Samples.dll", + "program": "${workspaceRoot}/PlatformSamples/ConsoleNet6/bin/Debug/net6.0/ConsoleNet6.dll", "args": [], - "cwd": "${workspaceRoot}/Samples", + "cwd": "${workspaceRoot}/PlatformSamples/ConsoleNet6", // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window "console": "internalConsole", "stopAtEntry": false, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 787f062..a5f9594 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,24 +1,27 @@ { - "version": "0.1.0", + "version": "2.0.0", "command": "dotnet", - "isShellCommand": true, "args": [], "tasks": [ { - "taskName": "build", + "label": "build", + "type": "shell", + "command": "dotnet", "args": [ - "${workspaceRoot}/Ooui.sln" + "build", + "${workspaceRoot}/PlatformSamples/ConsoleNet6/ConsoleNet6.csproj" ], - "isBuildCommand": true, - "problemMatcher": "$msCompile" + "problemMatcher": "$msCompile", + "group": "build" }, { - "taskName": "test", + "label": "test", + "type": "shell", + "command": "dotnet", "args": [ + "test", "${workspaceRoot}/Tests/Tests.csproj" ], - "isTestCommand": true, - "showOutput": "silent", "problemMatcher": { "owner": "external", "fileLocation": "absolute", @@ -40,7 +43,8 @@ "location": 3 } ] - } + }, + "group": "test" } ] } \ No newline at end of file diff --git a/Ooui.Forms/Renderers/ProgressBarRenderer.cs b/Ooui.Forms/Renderers/ProgressBarRenderer.cs index ddeff72..a924642 100644 --- a/Ooui.Forms/Renderers/ProgressBarRenderer.cs +++ b/Ooui.Forms/Renderers/ProgressBarRenderer.cs @@ -1,7 +1,12 @@ using System; using System.ComponentModel; using System.Linq; + +#if MAUI +using Microsoft.Maui.Controls; +#else using Xamarin.Forms; +#endif namespace Ooui.Forms.Renderers { diff --git a/Ooui.Maui/ActivationState.cs b/Ooui.Maui/ActivationState.cs new file mode 100644 index 0000000..78711fb --- /dev/null +++ b/Ooui.Maui/ActivationState.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Maui; + +namespace Ooui.Maui +{ + class ActivationState : IActivationState + { + public ActivationState(IMauiContext context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + } + + public IMauiContext Context { get; } + } +} diff --git a/Ooui.Maui/AppHostBuilderExtensions.cs b/Ooui.Maui/AppHostBuilderExtensions.cs new file mode 100644 index 0000000..164e35c --- /dev/null +++ b/Ooui.Maui/AppHostBuilderExtensions.cs @@ -0,0 +1,373 @@ +#nullable enable +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Maui.Controls.Compatibility; +using Microsoft.Maui.Controls.Shapes; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Hosting; +using Microsoft.Maui.LifecycleEvents; +using System.Collections.Generic; + +#if __ANDROID__ +using Microsoft.Maui.Controls.Compatibility.Platform.Android; +using Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat; +using Microsoft.Maui.Graphics.Native; +using FrameRenderer = Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers.FrameRenderer; +using LabelRenderer = Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers.LabelRenderer; +using ImageRenderer = Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers.ImageRenderer; +using ButtonRenderer = Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers.ButtonRenderer; +using DefaultRenderer = Microsoft.Maui.Controls.Compatibility.Platform.Android.Platform.DefaultRenderer; +#elif WINDOWS +using Microsoft.Maui.Controls.Compatibility.Platform.UWP; +using Microsoft.Maui.Graphics.Win2D; +using BoxRenderer = Microsoft.Maui.Controls.Compatibility.Platform.UWP.BoxViewBorderRenderer; +using CellRenderer = Microsoft.Maui.Controls.Compatibility.Platform.UWP.TextCellRenderer; +using Deserializer = Microsoft.Maui.Controls.Compatibility.Platform.UWP.WindowsSerializer; +using ResourcesProvider = Microsoft.Maui.Controls.Compatibility.Platform.UWP.WindowsResourcesProvider; +using StreamImagesourceHandler = Microsoft.Maui.Controls.Compatibility.Platform.UWP.StreamImageSourceHandler; +using ImageLoaderSourceHandler = Microsoft.Maui.Controls.Compatibility.Platform.UWP.UriImageSourceHandler; +using DefaultRenderer = Microsoft.Maui.Controls.Compatibility.Platform.UWP.DefaultRenderer; +#elif __IOS__ +using Microsoft.Maui.Controls.Compatibility.Platform.iOS; +using Microsoft.Maui.Graphics.Native; +using WebViewRenderer = Microsoft.Maui.Controls.Compatibility.Platform.iOS.WkWebViewRenderer; +using NavigationPageRenderer = Microsoft.Maui.Controls.Compatibility.Platform.iOS.NavigationRenderer; +using TabbedPageRenderer = Microsoft.Maui.Controls.Compatibility.Platform.iOS.TabbedRenderer; +using FlyoutPageRenderer = Microsoft.Maui.Controls.Compatibility.Platform.iOS.PhoneFlyoutPageRenderer; +using RadioButtonRenderer = Microsoft.Maui.Controls.Compatibility.Platform.iOS.Platform.DefaultRenderer; +using DefaultRenderer = Microsoft.Maui.Controls.Compatibility.Platform.iOS.Platform.DefaultRenderer; +#elif __OOUI__ +using Microsoft.Maui.Controls.Compatibility.Platform.Ooui; +#endif + +namespace Microsoft.Maui.Controls.Hosting +{ + public static class AppHostBuilderExtensions + { + // static readonly Dictionary DefaultMauiControlHandlers = new Dictionary + // { +// #if WINDOWS +// { typeof(Shell), typeof(ShellHandler) }, +// #endif +// { typeof(ActivityIndicator), typeof(ActivityIndicatorHandler) }, + // { typeof(Button), typeof(ButtonHandler) }, +// { typeof(CheckBox), typeof(CheckBoxHandler) }, +// { typeof(DatePicker), typeof(DatePickerHandler) }, +// { typeof(Editor), typeof(EditorHandler) }, +// { typeof(Entry), typeof(EntryHandler) }, +// { typeof(GraphicsView), typeof(GraphicsViewHandler) }, +// { typeof(Image), typeof(ImageHandler) }, + // { typeof(Label), typeof(LabelHandler) }, +// { typeof(Layout2.Layout), typeof(LayoutHandler) }, +// { typeof(Picker), typeof(PickerHandler) }, +// { typeof(ProgressBar), typeof(ProgressBarHandler) }, +// { typeof(SearchBar), typeof(SearchBarHandler) }, +// { typeof(Slider), typeof(SliderHandler) }, +// { typeof(Stepper), typeof(StepperHandler) }, +// { typeof(Switch), typeof(SwitchHandler) }, +// { typeof(TimePicker), typeof(TimePickerHandler) }, +// { typeof(Page), typeof(PageHandler) }, +// { typeof(Shapes.Ellipse), typeof(ShapeViewHandler) }, +// { typeof(Shapes.Line), typeof(ShapeViewHandler) }, +// { typeof(Shapes.Path), typeof(ShapeViewHandler) }, +// { typeof(Shapes.Polygon), typeof(ShapeViewHandler) }, +// { typeof(Shapes.Polyline), typeof(ShapeViewHandler) }, +// { typeof(Shapes.Rectangle), typeof(ShapeViewHandler) }, +// { typeof(Layout), typeof(LayoutHandler) }, +// { typeof(Window), typeof(WindowHandler) }, + // }; + + // public static IMauiHandlersCollection AddMauiControlsHandlers(this IMauiHandlersCollection handlersCollection) + // => handlersCollection.AddHandlers(DefaultMauiControlHandlers); + + public static IAppHostBuilder UseMauiApp(this IAppHostBuilder builder) + where TApp : class, IApplication + { + builder.ConfigureServices((context, collection) => + { + collection.AddSingleton(); + }); + + builder.SetupDefaults(); + + return builder; + } + + public static IAppHostBuilder UseMauiApp(this IAppHostBuilder builder, Func implementationFactory) + where TApp : class, IApplication + { + builder.ConfigureServices((context, collection) => + { + collection.AddSingleton(implementationFactory); + }); + + builder.SetupDefaults(); + + return builder; + } + + static IAppHostBuilder SetupDefaults(this IAppHostBuilder builder) + { + builder.ConfigureLifecycleEvents(events => + { +#if __ANDROID__ + events.AddAndroid(android => android + .OnApplicationCreating((app) => + { + // This is the initial Init to set up any system services registered by + // Forms.Init(). This happens in the Application's OnCreate - before + // any UI has appeared. + // This creates a dummy MauiContext that wraps the Application. + + var services = MauiApplication.Current.Services; + var mauiContext = new MauiContext(services, app); + var state = new ActivationState(mauiContext); + Forms.Init(state, new InitializationOptions { Flags = InitializationFlags.SkipRenderers }); + }) + .OnMauiContextCreated((mauiContext) => + { + // This is the final Init that sets up the real context from the activity. + + var state = new ActivationState(mauiContext); + Forms.Init(state); + })); +#elif __IOS__ + events.AddiOS(iOS => iOS + .WillFinishLaunching((app, options) => + { + // This is the initial Init to set up any system services registered by + // Forms.Init(). This happens before any UI has appeared. + // This creates a dummy MauiContext. + + var services = MauiUIApplicationDelegate.Current.Services; + var mauiContext = new MauiContext(services); + var state = new ActivationState(mauiContext); + Forms.Init(state, new InitializationOptions { Flags = InitializationFlags.SkipRenderers }); + return true; + }) + .OnMauiContextCreated((mauiContext) => + { + // This is the final Init that sets up the real context from the application. + + var state = new ActivationState(mauiContext); + Forms.Init(state); + })); +#elif WINDOWS + events.AddWindows(windows => windows + .OnLaunching((app, args) => + { + // This is the initial Init to set up any system services registered by + // Forms.Init(). This happens before any UI has appeared. + // This creates a dummy MauiContext. + // We need to call this so the Window and Root Page can new up successfully + // The dispatcher that's inside of Forms.Init needs to be setup before the initial + // window and root page start creating. + // Inside OnLaunched we grab the MauiContext that's on the window so we can have the correct + // MauiContext inside Forms + + var services = MauiWinUIApplication.Current.Services; + var mauiContext = new MauiContext(services); + var state = new ActivationState(mauiContext, args); + Forms.Init(state, new InitializationOptions { Flags = InitializationFlags.SkipRenderers }); + }) + .OnMauiContextCreated((mauiContext) => + { + // This is the final Init that sets up the real context from the application. + + var state = new ActivationState(mauiContext); + Forms.Init(state); + })); +#endif + }); + + static void HackSetToInitialized() { + // DependencyService.SetToInitialized(); + var typ = typeof(DependencyService); + var field = typ.GetField("s_initialized", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + field.SetValue(null, true); + } + + builder + .ConfigureMauiHandlers(handlers => + { + handlers.AddMauiControlsHandlers(); + HackSetToInitialized(); + +#if __ANDROID__ || __IOS__ || WINDOWS || MACCATALYST + + handlers.TryAddCompatibilityRenderer(typeof(BoxView), typeof(BoxRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Entry), typeof(EntryRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Editor), typeof(EditorRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Label), typeof(LabelRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Image), typeof(ImageRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Button), typeof(ButtonRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(ImageButton), typeof(ImageButtonRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(TableView), typeof(TableViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(ListView), typeof(ListViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(CollectionView), typeof(CollectionViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(CarouselView), typeof(CarouselViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(IndicatorView), typeof(IndicatorViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Path), typeof(PathRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Ellipse), typeof(EllipseRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Line), typeof(LineRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Polyline), typeof(PolylineRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Polygon), typeof(PolygonRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Shapes.Rectangle), typeof(RectangleRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(RadioButton), typeof(RadioButtonRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Slider), typeof(SliderRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(WebView), typeof(WebViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(SearchBar), typeof(SearchBarRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Switch), typeof(SwitchRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(SwipeView), typeof(SwipeViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(DatePicker), typeof(DatePickerRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(TimePicker), typeof(TimePickerRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Picker), typeof(PickerRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Stepper), typeof(StepperRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(ProgressBar), typeof(ProgressBarRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(ScrollView), typeof(ScrollViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(ActivityIndicator), typeof(ActivityIndicatorRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Frame), typeof(FrameRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(CheckBox), typeof(CheckBoxRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer)); +#if !WINDOWS + handlers.TryAddCompatibilityRenderer(typeof(Shell), typeof(ShellRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(OpenGLView), typeof(OpenGLViewRenderer)); +#else + handlers.TryAddCompatibilityRenderer(typeof(Layout), typeof(LayoutRenderer)); +#endif + handlers.TryAddCompatibilityRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(CarouselPage), typeof(CarouselPageRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Page), typeof(PageRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(FlyoutPage), typeof(FlyoutPageRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(RefreshView), typeof(RefreshViewRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(NativeViewWrapper), typeof(NativeViewWrapperRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(Cell), typeof(CellRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(ImageCell), typeof(ImageCellRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(EntryCell), typeof(EntryCellRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(TextCell), typeof(TextCellRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(ViewCell), typeof(ViewCellRenderer)); + handlers.TryAddCompatibilityRenderer(typeof(SwitchCell), typeof(SwitchCellRenderer)); + + // This is for Layouts that currently don't work when assigned to LayoutHandler + handlers.TryAddCompatibilityRenderer(typeof(ContentView), typeof(DefaultRenderer)); +#if __IOS__ + handlers.TryAddCompatibilityRenderer(typeof(AbsoluteLayout), typeof(DefaultRenderer)); +#endif + + + DependencyService.Register(); + DependencyService.Register(); + DependencyService.Register(); + DependencyService.Register(); + DependencyService.Register(); + DependencyService.Register(); + + // Shimmed renderers go directly to the registrar to load Image Handlers + Internals.Registrar.Registered.Register(typeof(FileImageSource), typeof(FileImageSourceHandler)); + Internals.Registrar.Registered.Register(typeof(StreamImageSource), typeof(StreamImagesourceHandler)); + Internals.Registrar.Registered.Register(typeof(UriImageSource), typeof(ImageLoaderSourceHandler)); + Internals.Registrar.Registered.Register(typeof(FontImageSource), typeof(FontImageSourceHandler)); + + + Internals.Registrar.Registered.Register(typeof(Microsoft.Maui.EmbeddedFont), typeof(Microsoft.Maui.EmbeddedFontLoader)); + +#endif + +#if __IOS__ || MACCATALYST + Internals.Registrar.RegisterEffect("Xamarin", "ShadowEffect", typeof(ShadowEffect)); +#endif + }) + .ConfigureServices(); + + return builder; + } + + class MauiCompatBuilder : IMauiServiceBuilder + { + public void Configure(HostBuilderContext context, IServiceProvider services) + { +#if __ANDROID__ || __IOS__ || WINDOWS || MACCATALYST + CompatServiceProvider.SetServiceProvider(services); +#endif + + if (services.GetService() is IGraphicsService graphicsService) + GraphicsPlatform.RegisterGlobalService(graphicsService); + +#if WINDOWS + var dictionaries = UI.Xaml.Application.Current?.Resources?.MergedDictionaries; + if (dictionaries != null) + { + // WinUI + AddLibraryResources(); + + // Microsoft.Maui + AddLibraryResources("MicrosoftMauiCoreIncluded", "ms-appx:///Microsoft.Maui/Platform/Windows/Styles/Resources.xbf"); + + // Microsoft.Maui.Controls + AddLibraryResources("MicrosoftMauiControlsIncluded", "ms-appx:///Microsoft.Maui.Controls/Platform/Windows/Styles/Resources.xbf"); + + // Microsoft.Maui.Controls.Compatibility + AddLibraryResources("MicrosoftMauiControlsCompatibilityIncluded", "ms-appx:///Microsoft.Maui.Controls.Compatibility/Windows/Resources.xbf"); + } +#endif + } + + public void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { +#if __IOS__ || MACCATALYST + services.AddSingleton(NativeGraphicsService.Instance); +#elif __ANDROID__ + services.AddSingleton(NativeGraphicsService.Instance); +#elif WINDOWS + services.AddSingleton(W2DGraphicsService.Instance); +#endif + } + +#if WINDOWS + static void AddLibraryResources(string key, string uri) + { + var resources = UI.Xaml.Application.Current?.Resources; + if (resources == null) + return; + + var dictionaries = resources.MergedDictionaries; + if (dictionaries == null) + return; + + if (!resources.ContainsKey(key)) + { + dictionaries.Add(new UI.Xaml.ResourceDictionary + { + Source = new Uri(uri) + }); + } + } + + static void AddLibraryResources() + where T : UI.Xaml.ResourceDictionary, new() + { + var dictionaries = UI.Xaml.Application.Current?.Resources?.MergedDictionaries; + if (dictionaries == null) + return; + + var found = false; + foreach (var dic in dictionaries) + { + if (dic is T) + { + found = true; + break; + } + } + + if (!found) + { + var dic = new T(); + dictionaries.Add(dic); + } + } +#endif + } + } +} diff --git a/Ooui.Maui/Controls/Compatibility.cs b/Ooui.Maui/Controls/Compatibility.cs new file mode 100644 index 0000000..2dceb87 --- /dev/null +++ b/Ooui.Maui/Controls/Compatibility.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Microsoft.Maui.Hosting; +using Ooui.Maui.Controls.Compatibility; + +namespace Microsoft.Maui.Controls.Compatibility +{ + public struct InitializationOptions + { + public InitializationFlags Flags; + } + + public static class AppHostBuilderExtensions + { + // This won't really be a thing once we have all the handlers built + static readonly List ControlsWithHandlers = new List + { + typeof(Button), + typeof(ContentPage), + typeof(Page), + typeof(Label), + typeof(CheckBox), + typeof(Entry), + typeof(Switch), + typeof(Editor), + typeof(ActivityIndicator), + typeof(DatePicker), + typeof(Picker), + typeof(ProgressBar), + typeof(SearchBar), + typeof(Slider), + typeof(Stepper), + typeof(TimePicker), + typeof(Shell), + }; + + public static IAppHostBuilder UseFormsCompatibility(this IAppHostBuilder builder, bool registerRenderers = true) + { + // TODO: This should not be immediately run, but rather a registered delegate with values + // of the Context and LaunchActivatedEventArgs passed in. + + var options = new InitializationOptions(); + + options.Flags |= InitializationFlags.SkipRenderers; + + Forms.Init(options); + + if (registerRenderers) + builder.UseCompatibilityRenderers(); + + return builder; + } + + public static IAppHostBuilder UseCompatibilityRenderers(this IAppHostBuilder builder) + { + Forms.RegisterCompatRenderers( + new[] { typeof(RendererToHandlerShim).Assembly }, + typeof(RendererToHandlerShim).Assembly, + (controlType) => + { + foreach (var type in ControlsWithHandlers) + { + if (type.IsAssignableFrom(controlType)) + return; + } + + builder.ConfigureMauiHandlers((_, handlersCollection) => handlersCollection.AddHandler(controlType, typeof(RendererToHandlerShim))); + }); + + return builder; + } + } +} diff --git a/Ooui.Maui/Controls/Forms.cs b/Ooui.Maui/Controls/Forms.cs new file mode 100644 index 0000000..9e8ddb4 --- /dev/null +++ b/Ooui.Maui/Controls/Forms.cs @@ -0,0 +1,107 @@ + +using System; +using System.Diagnostics; +using System.Reflection; +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Internals; + +namespace Ooui.Maui.Controls.Compatibility +{ + static class Forms + { + internal static IMauiContext? MauiContext { get; private set; } + + public static bool IsInitialized { get; private set; } + + public static bool IsInitializedRenderers { get; private set; } + + public static void Init(IMauiContext context) => SetupInit(context, 0); + + public static void Init(Microsoft.Maui.Controls.Compatibility.InitializationOptions options) => + SetupInit(new OouiMauiContext(), options.Flags); + + static void SetupInit(IMauiContext context, InitializationFlags initFlags) + { + MauiContext = context; + + // TODO: RendererToHandlerShim + // Microsoft.Maui.Controls.Internals.Registrar.RegisterRendererToHandlerShim(RendererToHandlerShim.CreateShim); + + Application.AccentColor = Microsoft.Maui.Graphics.Color.FromRgba(50, 79, 133, 255); + + if (!IsInitialized) + { + // Only need to do this once + Log.Listeners.Add(new DelegateLogListener((c, m) => Trace.WriteLine(m, c))); + } + + Device.SetIdiom(TargetIdiom.Desktop); + // TODO: fak: Find the correct text flow direction + // Device.SetFlowDirection(UIApplication.SharedApplication.UserInterfaceLayoutDirection.ToFlowDirection()); + // Device.SetFlags(s_flags); + + var platformServices = new Ooui.Maui.OouiPlatformServices(); + Device.PlatformServices = platformServices; + Device.PlatformInvalidator = platformServices; + + // use field and not property to avoid exception in getter + if (Device.info is IDisposable infoDisposable) + { + infoDisposable.Dispose(); + Device.info = null; + } + Device.Info = new OouiDeviceInfo(); + + if (initFlags.HasFlag(InitializationFlags.SkipRenderers) != true) { + RegisterCompatRenderers(); + } + + // TODO: fak: Expressions? + // ExpressionSearch.Default = new iOSExpressionSearch(); + + IsInitialized = true; + } + + internal static void RegisterCompatRenderers() + { + if (!IsInitializedRenderers) + { + IsInitializedRenderers = true; + + // Only need to do this once + Microsoft.Maui.Controls.Internals.Registrar.RegisterAll(new[] + { + // TODO: fak: do we need these? + // typeof(ExportRendererAttribute), + // typeof(ExportCellAttribute), + // typeof(ExportImageSourceHandlerAttribute), + typeof(ExportFontAttribute) + }); + } + } + + internal static void RegisterCompatRenderers( + Assembly[] assemblies, + Assembly defaultRendererAssembly, + Action viewRegistered) + { + if (IsInitializedRenderers) + return; + + IsInitializedRenderers = true; + + // Only need to do this once + Microsoft.Maui.Controls.Internals.Registrar.RegisterAll( + assemblies, + defaultRendererAssembly, + new[] { + // typeof(ExportRendererAttribute), + // typeof(ExportCellAttribute), + // typeof(ExportImageSourceHandlerAttribute), + typeof(ExportFontAttribute) + }, default(InitializationFlags), + viewRegistered); + } + } +} diff --git a/Ooui.Maui/Controls/IVisualElementRendeer.cs b/Ooui.Maui/Controls/IVisualElementRendeer.cs new file mode 100644 index 0000000..145abf9 --- /dev/null +++ b/Ooui.Maui/Controls/IVisualElementRendeer.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Platform; +using Microsoft.Maui.Graphics; +using Ooui; +using NativeView = Ooui.Element; +using NativeViewController = Ooui.Element; + +namespace Ooui.Maui.Controls.Compatibility.Platform.iOS +{ + public interface IVisualElementRenderer : IDisposable, IRegisterable + { + VisualElement Element { get; } + + NativeView NativeView { get; } + + NativeViewController ViewController { get; } + + event EventHandler ElementChanged; + + SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint); + + void SetElement(VisualElement element); + + void SetElementSize(Size size); + } +} diff --git a/Ooui.Maui/Controls/OouiDeviceInfo.cs b/Ooui.Maui/Controls/OouiDeviceInfo.cs new file mode 100644 index 0000000..a1078e9 --- /dev/null +++ b/Ooui.Maui/Controls/OouiDeviceInfo.cs @@ -0,0 +1,15 @@ +using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Graphics; + +namespace Ooui.Maui.Controls +{ + public class OouiDeviceInfo : DeviceInfo + { + // TODO: fak: return correct screen size + public override Size PixelScreenSize => new Size(1024, 768); + + public override Size ScaledScreenSize => new Size(1024, 768); + + public override double ScalingFactor => 1.0; + } +} diff --git a/Ooui.Maui/Controls/RendererToHandlerShim.cs b/Ooui.Maui/Controls/RendererToHandlerShim.cs new file mode 100644 index 0000000..356bcd6 --- /dev/null +++ b/Ooui.Maui/Controls/RendererToHandlerShim.cs @@ -0,0 +1,126 @@ +using System; +using Microsoft.Maui; +using Microsoft.Maui.Controls.Compatibility.Platform.Ooui; +using Microsoft.Maui.Controls.Platform; +using Ooui; +using Ooui.Maui.Controls.Compatibility.Platform.iOS; +using ViewHandler = Ooui.Maui.Handlers.ViewHandler; + +namespace Ooui.Maui.Controls.Compatibility +{ + public class RendererToHandlerShim : ViewHandler + { + internal IVisualElementRenderer? VisualElementRenderer { get; private set; } + + public static IViewHandler CreateShim(object renderer) + { + if (renderer is IViewHandler handler) + return handler; + + if (renderer is IVisualElementRenderer ivr) + return new RendererToHandlerShim(ivr); + + return new RendererToHandlerShim(); + } + + public RendererToHandlerShim() : base(ViewHandler.ViewMapper) + { + } + + public RendererToHandlerShim(IVisualElementRenderer visualElementRenderer) : this() + { + if (visualElementRenderer != null) + SetupRenderer(visualElementRenderer); + } + + public void SetupRenderer(IVisualElementRenderer visualElementRenderer) + { + VisualElementRenderer = visualElementRenderer; + + // TODO: fak: Handle Forms element changed + // VisualElementRenderer.ElementChanged += OnElementChanged; + + if (VisualElementRenderer.Element is IView view) + { + view.Handler = this; + SetVirtualView(view); + } + else if (VisualElementRenderer.Element != null) + throw new Exception($"{VisualElementRenderer.Element} must implement: {nameof(IView)}"); + } + + void OnElementChanged(object sender, VisualElementChangedEventArgs e) + { + if (e.OldElement is IView view) + view.Handler = null; + + if (e.NewElement is IView newView) + { + newView.Handler = this; + this.SetVirtualView(newView); + } + else if (e.NewElement != null) + throw new Exception($"{e.NewElement} must implement: {nameof(IView)}"); + } + + protected override Ooui.Element CreateNativeView() + { + return VisualElementRenderer.NativeView; + } + + protected override void ConnectHandler(Ooui.Element nativeView) + { + base.ConnectHandler(nativeView); + VirtualView.Handler = this; + } + + protected override void DisconnectHandler(Ooui.Element nativeView) + { + // TODO: fak: Need to set renderer for native view on DisconnectHandler + // Ooui.Maui.Controls.Compatibility.Platform.Platform.SetRenderer( + // VisualElementRenderer.Element, + // VisualElementRenderer); + + VisualElementRenderer.SetElement(null); + + base.DisconnectHandler(nativeView); + VirtualView.Handler = null; + } + + public override void SetVirtualView(IView view) + { + // TODO: fak: Set Compat virtual view + throw new NotImplementedException("Cannote SetVirtualView"); + + // if (VisualElementRenderer == null) + // { + // var renderer = Microsoft.Maui.Controls.Internals.Registrar.Registered.GetHandlerForObject(view) + // ?? new Microsoft.Maui.Controls.Compatibility.Platform.Ooui.Platform.DefaultRenderer(); + + // SetupRenderer(renderer); + // } + + // if (VisualElementRenderer.Element != view) + // { + // VisualElementRenderer.SetElement((VisualElement)view); + // } + // else + // { + // base.SetVirtualView(view); + // } + + // Microsoft.Maui.Controls.Compatibility.Platform.Platform.SetRenderer( + // VisualElementRenderer.Element, + // VisualElementRenderer); + } + + public override void UpdateValue(string property) + { + base.UpdateValue(property); + if (property == "Frame") + { + NativeArrange(VisualElementRenderer.Element.Bounds); + } + } + } +} diff --git a/Ooui.Maui/Controls/ResourcesProvider.cs b/Ooui.Maui/Controls/ResourcesProvider.cs new file mode 100644 index 0000000..ac0877b --- /dev/null +++ b/Ooui.Maui/Controls/ResourcesProvider.cs @@ -0,0 +1,47 @@ +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Internals; + +namespace Microsoft.Maui.Controls.Compatibility.Platform.Ooui +{ + [Preserve(AllMembers = true)] + class ResourcesProvider : ISystemResourcesProvider + { + ResourceDictionary? _dictionary; + + public ResourcesProvider() + { + } + + public IResourceDictionary GetSystemResources() + { + _dictionary = new ResourceDictionary(); + UpdateStyles(); + + return _dictionary; + } + + Style GenerateStyle(string elementType) + { + var result = new Style(typeof(Label)); + + // result.Setters.Add(new Setter { Property = Label.FontSizeProperty, Value = (double)font.PointSize }); + + // result.Setters.Add(new Setter { Property = Label.FontFamilyProperty, Value = font.Name }); + + return result; + } + + void UpdateStyles() + { + if (_dictionary == null) + return; + _dictionary[Device.Styles.TitleStyleKey] = GenerateStyle("h1"); + _dictionary[Device.Styles.SubtitleStyleKey] = GenerateStyle("h2"); + _dictionary[Device.Styles.BodyStyleKey] = GenerateStyle("body"); + _dictionary[Device.Styles.CaptionStyleKey] = GenerateStyle("small"); + + _dictionary[Device.Styles.ListItemTextStyleKey] = GenerateStyle("body"); + _dictionary[Device.Styles.ListItemDetailTextStyleKey] = GenerateStyle("body"); + } + } +} diff --git a/Ooui.Maui/HandlerExtensions.cs b/Ooui.Maui/HandlerExtensions.cs new file mode 100644 index 0000000..6db1846 --- /dev/null +++ b/Ooui.Maui/HandlerExtensions.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.Maui; +using Microsoft.Maui.Hosting; +using Ooui; + +namespace Ooui.Maui +{ + public static class HandlerExtensions + { + public static Element ToNative(this IView view, IMauiContext context) + { + return ToOouiElement(view, context); + } + + public static Element ToOouiElement(this IApplication application, IMauiContext context) + { + var window = application.CreateWindow(new ActivationState (context)); + var element = window.View.ToOouiElement(context); + return element; + } + + public static Element ToOouiElement(this IView view, IMauiContext context) + { + _ = view ?? throw new ArgumentNullException(nameof(view)); + _ = context ?? throw new ArgumentNullException(nameof(context)); + + // Maui: This is how MVU works. It collapses views down + if (view is IReplaceableView ir) + view = ir.ReplacedView; + + var handler = view.Handler; + + if (handler == null) + { + Console.WriteLine($"Context == {context.GetType()}"); + + var viewType = view.GetType(); + Console.WriteLine($"Creating handler for type {viewType.FullName}"); + + handler = context.Handlers.GetHandler(viewType); + Console.WriteLine($"Found Handler {handler.GetType().FullName} in {handler.GetType().Assembly} "); + + if (handler == null) + throw new Exception($"Handler not found for view {view}"); + + handler.SetMauiContext(context); + + view.Handler = handler; + } + + handler.SetVirtualView(view); + + if (((INativeViewHandler)handler).NativeView is not Element result) + { + throw new InvalidOperationException($"Unable to convert {view} to {typeof(Element)}"); + } + + return result; + } + } +} diff --git a/Ooui.Maui/Handlers/Layout/ILayoutHandler.cs b/Ooui.Maui/Handlers/Layout/ILayoutHandler.cs new file mode 100644 index 0000000..eed5824 --- /dev/null +++ b/Ooui.Maui/Handlers/Layout/ILayoutHandler.cs @@ -0,0 +1,10 @@ +using Microsoft.Maui; + +namespace Ooui.Maui +{ + public interface ILayoutHandler : IViewHandler + { + void Add(IView view); + void Remove(IView view); + } +} diff --git a/Ooui.Maui/Handlers/Layout/LayoutHandler.Ooui.cs b/Ooui.Maui/Handlers/Layout/LayoutHandler.Ooui.cs new file mode 100644 index 0000000..cdf8553 --- /dev/null +++ b/Ooui.Maui/Handlers/Layout/LayoutHandler.Ooui.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; + +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers +{ + public partial class LayoutHandler : ViewHandler + { + protected override Ooui.Div CreateNativeView() { + if (VirtualView == null) + { + throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a LayoutViewGroup"); + } + + var view = new Ooui.Div(); + + return view; + } + + public override void SetVirtualView(IView view) + { + base.SetVirtualView(view); + + _ = NativeView ?? throw new InvalidOperationException($"{nameof(NativeView)} should have been set by base class."); + _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class."); + _ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class."); + + // NativeView.View = view; + + //Cleanup the old view when reused + var oldChildren = NativeView.Children.ToList(); + oldChildren.ForEach(x => NativeView.RemoveChild(x)); + + foreach (var child in VirtualView.Children) + { + NativeView.AppendChild(child.ToNative(MauiContext)); + } + } + + public void Add(IView child) + { + _ = NativeView ?? throw new InvalidOperationException($"{nameof(NativeView)} should have been set by base class."); + _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class."); + _ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class."); + + NativeView.AppendChild(child.ToNative(MauiContext)); + } + + public void Remove(IView child) + { + _ = NativeView ?? throw new InvalidOperationException($"{nameof(NativeView)} should have been set by base class."); + _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class."); + + if (child?.Handler?.NativeView is Ooui.Div nativeView) + { + NativeView.RemoveChild(nativeView); + } + } + + protected override void DisconnectHandler(Ooui.Div nativeView) + { + base.DisconnectHandler(nativeView); + var subViews = nativeView.Children.ToList(); + + foreach (var subView in subViews) + { + nativeView.RemoveChild(subView); + } + } + } +} diff --git a/Ooui.Maui/Handlers/Layout/LayoutHandler.cs b/Ooui.Maui/Handlers/Layout/LayoutHandler.cs new file mode 100644 index 0000000..bd81b70 --- /dev/null +++ b/Ooui.Maui/Handlers/Layout/LayoutHandler.cs @@ -0,0 +1,26 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Text; + +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers +{ + public partial class LayoutHandler : ILayoutHandler + { + public static PropertyMapper LayoutMapper = new OouiPropertyMapper(ViewHandler.ViewMapper) + { + }; + + public LayoutHandler() : base(LayoutMapper) + { + + } + + public LayoutHandler(PropertyMapper? mapper = null) : base(mapper ?? LayoutMapper) + { + + } + } +} diff --git a/Ooui.Maui/Handlers/Page/PageHandler.Ooui.cs b/Ooui.Maui/Handlers/Page/PageHandler.Ooui.cs new file mode 100644 index 0000000..ed92396 --- /dev/null +++ b/Ooui.Maui/Handlers/Page/PageHandler.Ooui.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers +{ + public partial class PageHandler : ViewHandler + { + protected override Ooui.Element CreateNativeView() => new Ooui.Div(); + + void UpdateContent() + { + _ = NativeView ?? throw new InvalidOperationException($"{nameof(NativeView)} should have been set by base class."); + _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class."); + _ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class."); + + //Cleanup the old view when reused + var oldChildren = NativeView.Children.ToList(); + oldChildren.ForEach(x => NativeView.RemoveChild(x)); + + if (VirtualView.Content != null) { + NativeView.AppendChild(VirtualView.Content.ToNative(MauiContext)); + } + } + + public static void MapTitle(PageHandler handler, IPage page) + { + // TODO: fak: Map Page.Title + } + + public static void MapContent(PageHandler handler, IPage page) + { + handler.UpdateContent(); + } + } +} diff --git a/Ooui.Maui/Handlers/Page/PageHandler.cs b/Ooui.Maui/Handlers/Page/PageHandler.cs new file mode 100644 index 0000000..5fce9fb --- /dev/null +++ b/Ooui.Maui/Handlers/Page/PageHandler.cs @@ -0,0 +1,26 @@ +#nullable enable +using System; +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers +{ + public partial class PageHandler : IViewHandler + { + public static PropertyMapper PageMapper = new OouiPropertyMapper(ViewHandler.ViewMapper) + { + [nameof(IPage.Title)] = MapTitle, + [nameof(IPage.Content)] = MapContent, + }; + + public PageHandler() : base(PageMapper) + { + + Console.WriteLine("PageHandler created"); + } + + // public PageHandler(PropertyMapper? mapper = null) : base(mapper ?? PageMapper) + // { + + // } + } +} diff --git a/Ooui.Maui/Handlers/View/ButtonExtensions.cs b/Ooui.Maui/Handlers/View/ButtonExtensions.cs new file mode 100644 index 0000000..6bed6e2 --- /dev/null +++ b/Ooui.Maui/Handlers/View/ButtonExtensions.cs @@ -0,0 +1,31 @@ +using Microsoft.Maui; + +namespace Ooui.Maui +{ + public static class ButtonExtensions + { + public static void UpdateText(this Ooui.Button nativeButton, IButton button) => + nativeButton.Text = button.Text; + + public static void UpdateTextColor(this Ooui.Button nativeButton, IButton button) + { + // var color = button.TextColor.ToNative(); + // nativeButton.Style.Color = color; + } + + public static void UpdateCharacterSpacing(this Ooui.Button nativeButton, ITextStyle textStyle) + { + // nativeButton.Style.UpdateCharacterSpacing(textStyle); + } + + public static void UpdateFont(this Ooui.Button nativeButton, ITextStyle textStyle, IFontManager fontManager) + { + // nativeButton.Style.UpdateFont(textStyle, fontManager, UIFont.ButtonFontSize); + } + + public static void UpdatePadding(this Ooui.Button nativeButton, IButton button) + { + // nativeButton.Style.UpdatePadding(button.Padding); + } + } +} \ No newline at end of file diff --git a/Ooui.Maui/Handlers/View/ButtonHandler.Ooui.cs b/Ooui.Maui/Handlers/View/ButtonHandler.Ooui.cs new file mode 100644 index 0000000..ba44f85 --- /dev/null +++ b/Ooui.Maui/Handlers/View/ButtonHandler.Ooui.cs @@ -0,0 +1,81 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers +{ + public partial class ButtonHandler : ViewHandler + { + protected override Ooui.Button CreateNativeView() + { + SetControlPropertiesFromProxy(); + return new Ooui.Button(); + } + + protected override void ConnectHandler(Ooui.Button nativeView) + { + nativeView.Click += OnClick; + + base.ConnectHandler(nativeView); + } + + protected override void DisconnectHandler(Ooui.Button nativeView) + { + nativeView.Click -= OnClick; + + base.DisconnectHandler(nativeView); + } + + protected override void SetupDefaults(Ooui.Button nativeView) + { + base.SetupDefaults(nativeView); + } + + public static void MapText(Ooui.Maui.Handlers.ButtonHandler handler, IButton button) + { + handler.NativeView?.UpdateText(button); + + // Any text update requires that we update any attributed string formatting + MapFormatting(handler, button); + } + + public static void MapTextColor(Ooui.Maui.Handlers.ButtonHandler handler, IButton button) + { + handler.NativeView?.UpdateTextColor(button); + } + + public static void MapCharacterSpacing(Ooui.Maui.Handlers.ButtonHandler handler, IButton button) + { + handler.NativeView?.UpdateCharacterSpacing(button); + } + + public static void MapPadding(Ooui.Maui.Handlers.ButtonHandler handler, IButton button) + { + handler.NativeView?.UpdatePadding(button); + } + + public static void MapFont(Ooui.Maui.Handlers.ButtonHandler handler, IButton button) + { + // var fontManager = handler.GetRequiredService(); + // handler.NativeView?.UpdateFont(button, fontManager); + } + + public static void MapFormatting(Ooui.Maui.Handlers.ButtonHandler handler, IButton button) + { + // Update all of the attributed text formatting properties + handler.NativeView?.UpdateCharacterSpacing(button); + } + + void SetControlPropertiesFromProxy() + { + if (NativeView == null) + return; + } + + void OnClick(object? sender, EventArgs e) + { + VirtualView?.Released(); + VirtualView?.Clicked(); + } + } +} \ No newline at end of file diff --git a/Ooui.Maui/Handlers/View/ButtonHandler.cs b/Ooui.Maui/Handlers/View/ButtonHandler.cs new file mode 100644 index 0000000..50f098a --- /dev/null +++ b/Ooui.Maui/Handlers/View/ButtonHandler.cs @@ -0,0 +1,30 @@ +#nullable enable + +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers +{ + public partial class ButtonHandler + { + public static PropertyMapper ButtonMapper = new OouiPropertyMapper(ViewHandler.ViewMapper) + { +#if WINDOWS || __ANDROID__ + [nameof(IButton.Background)] = MapBackground, +#endif + [nameof(IButton.CharacterSpacing)] = MapCharacterSpacing, + [nameof(IButton.Font)] = MapFont, + [nameof(IButton.Padding)] = MapPadding, + [nameof(IButton.Text)] = MapText, + [nameof(IButton.TextColor)] = MapTextColor, + }; + + public ButtonHandler() : base(ButtonMapper) + { + + } + + public ButtonHandler(PropertyMapper? mapper = null) : base(mapper ?? ButtonMapper) + { + } + } +} diff --git a/Ooui.Maui/Handlers/View/LabelHandler.Ooui.cs b/Ooui.Maui/Handlers/View/LabelHandler.Ooui.cs new file mode 100644 index 0000000..9365b5f --- /dev/null +++ b/Ooui.Maui/Handlers/View/LabelHandler.Ooui.cs @@ -0,0 +1,86 @@ +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers +{ + public partial class LabelHandler : ViewHandler + { + protected override Ooui.Span CreateNativeView() => new Ooui.Span(); + + public override bool NeedsContainer => + VirtualView?.Background != null || + base.NeedsContainer; + + public static void MapBackground(LabelHandler handler, ILabel label) + { + handler.UpdateValue(nameof(IViewHandler.ContainerView)); + + handler.WrappedNativeView?.UpdateBackground(label); + } + + public static void MapText(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdateText(label); + + // Any text update requires that we update any attributed string formatting + MapFormatting(handler, label); + } + + public static void MapTextColor(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdateTextColor(label); + } + + public static void MapCharacterSpacing(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdateCharacterSpacing(label); + } + + public static void MapHorizontalTextAlignment(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdateHorizontalTextAlignment(label); + } + + public static void MapLineBreakMode(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdateLineBreakMode(label); + } + + public static void MapMaxLines(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdateMaxLines(label); + } + + public static void MapPadding(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdatePadding(label); + } + + public static void MapTextDecorations(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdateTextDecorations(label); + } + + public static void MapFont(LabelHandler handler, ILabel label) + { + // var fontManager = handler.GetRequiredService(); + // handler.NativeView?.UpdateFont(label, fontManager); + } + + public static void MapLineHeight(LabelHandler handler, ILabel label) + { + handler.NativeView?.UpdateLineHeight(label); + } + + public static void MapFormatting(LabelHandler handler, ILabel label) + { + // Update all of the attributed text formatting properties + handler.NativeView?.UpdateLineHeight(label); + handler.NativeView?.UpdateTextDecorations(label); + handler.NativeView?.UpdateCharacterSpacing(label); + + // Setting any of those may have removed text alignment settings, + // so we need to make sure those are applied, too + handler.NativeView?.UpdateHorizontalTextAlignment(label); + } + } +} \ No newline at end of file diff --git a/Ooui.Maui/Handlers/View/LabelHandler.cs b/Ooui.Maui/Handlers/View/LabelHandler.cs new file mode 100644 index 0000000..36beb0a --- /dev/null +++ b/Ooui.Maui/Handlers/View/LabelHandler.cs @@ -0,0 +1,36 @@ +#nullable enable + +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers +{ + public partial class LabelHandler + { + public static PropertyMapper LabelMapper = new OouiPropertyMapper(ViewHandler.ViewMapper) + { +#if WINDOWS || __IOS__ + [nameof(ILabel.Background)] = MapBackground, +#endif + [nameof(ILabel.CharacterSpacing)] = MapCharacterSpacing, + [nameof(ILabel.Font)] = MapFont, + [nameof(ILabel.HorizontalTextAlignment)] = MapHorizontalTextAlignment, + [nameof(ILabel.LineBreakMode)] = MapLineBreakMode, + [nameof(ILabel.LineHeight)] = MapLineHeight, + [nameof(ILabel.MaxLines)] = MapMaxLines, + [nameof(ILabel.Padding)] = MapPadding, + [nameof(ILabel.Text)] = MapText, + [nameof(ILabel.TextColor)] = MapTextColor, + [nameof(ILabel.TextDecorations)] = MapTextDecorations, + }; + + public LabelHandler() : base(LabelMapper) + { + + } + + public LabelHandler(PropertyMapper? mapper = null) : base(mapper ?? LabelMapper) + { + + } + } +} \ No newline at end of file diff --git a/Ooui.Maui/Handlers/View/OouiPropertyMapper.cs b/Ooui.Maui/Handlers/View/OouiPropertyMapper.cs new file mode 100644 index 0000000..bc17e1a --- /dev/null +++ b/Ooui.Maui/Handlers/View/OouiPropertyMapper.cs @@ -0,0 +1,72 @@ + +using Microsoft.Maui; + +namespace Ooui.Maui.Handlers { + public class OouiPropertyMapper : PropertyMapper, IOouiPropertyMapper + where TVirtualView : IFrameworkElement + where TViewHandler : IViewHandler + { + public OouiPropertyMapper() + { + } + + public OouiPropertyMapper(PropertyMapper chained) : base(chained) + { + } + + public void UpdateProperty(IViewHandler viewHandler, IFrameworkElement? virtualView, string property) + { + if (virtualView == null) + return; + + UpdatePropertyCore(property, viewHandler, virtualView); + } + + public void UpdateProperties(IViewHandler viewHandler, IFrameworkElement? virtualView) + { + if (virtualView == null) + return; + + foreach (var key in UpdateKeys) + { + UpdatePropertyCore(key, viewHandler, virtualView); + } + } + } + + public class OouiPropertyMapper : Microsoft.Maui.PropertyMapper, IOouiPropertyMapper + where TVirtualView : IFrameworkElement + { + public OouiPropertyMapper() + { + } + + public OouiPropertyMapper(PropertyMapper chained) : base(chained) + { + } + public void UpdateProperty(IViewHandler viewHandler, IFrameworkElement? virtualView, string property) + { + if (virtualView == null) + return; + + UpdatePropertyCore(property, viewHandler, virtualView); + } + + public void UpdateProperties(IViewHandler viewHandler, IFrameworkElement? virtualView) + { + if (virtualView == null) + return; + + foreach (var key in UpdateKeys) + { + UpdatePropertyCore(key, viewHandler, virtualView); + } + } + } + + public interface IOouiPropertyMapper + { + void UpdateProperty(IViewHandler viewHandler, IFrameworkElement? virtualView, string property); + void UpdateProperties(IViewHandler viewHandler, IFrameworkElement? virtualView); + } +} diff --git a/Ooui.Maui/Handlers/View/ViewExtensions.cs b/Ooui.Maui/Handlers/View/ViewExtensions.cs new file mode 100644 index 0000000..dfe383d --- /dev/null +++ b/Ooui.Maui/Handlers/View/ViewExtensions.cs @@ -0,0 +1,63 @@ +using NativeView = Ooui.Element; + +using Microsoft.Maui; + +namespace Ooui.Maui +{ + public static class ViewExtensions + { + public static void UpdateIsEnabled(this NativeView nativeView, IView view) { } + + public static void UpdateVisibility(this NativeView nativeView, IView view) { } + + public static void UpdateBackground(this NativeView nativeView, IView view) { } + + public static void UpdateAutomationId(this NativeView nativeView, IView view) { } + + public static void UpdateOpacity(this NativeView nativeView, IView view) { } + + public static void UpdateSemantics(this NativeView nativeView, IView view) { } + + public static void UpdateTranslationX(this NativeView nativeView, IView view) { } + + public static void UpdateTranslationY(this NativeView nativeView, IView view) { } + + public static void UpdateScale(this NativeView nativeView, IView view) { } + + public static void UpdateRotation(this NativeView nativeView, IView view) { } + + public static void UpdateRotationX(this NativeView nativeView, IView view) { } + + public static void UpdateRotationY(this NativeView nativeView, IView view) { } + + public static void UpdateAnchorX(this NativeView nativeView, IView view) { } + + public static void UpdateAnchorY(this NativeView nativeView, IView view) { } + + public static void InvalidateMeasure(this NativeView nativeView, IView view) { } + + public static void UpdateWidth(this NativeView nativeView, IView view) { } + + public static void UpdateHeight(this NativeView nativeView, IView view) { } + + public static void UpdateText(this NativeView nativeView, ILabel view) { + nativeView.Text = view.Text; + } + + public static void UpdateTextColor(this NativeView nativeView, IView view) { } + + public static void UpdateCharacterSpacing(this NativeView nativeView, IView view) { } + + public static void UpdateHorizontalTextAlignment(this NativeView nativeView, IView view) { } + + public static void UpdatePadding(this NativeView nativeView, IView view) { } + + public static void UpdateTextDecorations(this NativeView nativeView, IView view) { } + + public static void UpdateLineHeight(this NativeView nativeView, IView view) { } + + public static void UpdateMaxLines(this NativeView nativeView, IView view) { } + + public static void UpdateLineBreakMode(this NativeView nativeView, IView view) { } + } +} diff --git a/Ooui.Maui/Handlers/View/ViewHandler.cs b/Ooui.Maui/Handlers/View/ViewHandler.cs new file mode 100644 index 0000000..53d1e6d --- /dev/null +++ b/Ooui.Maui/Handlers/View/ViewHandler.cs @@ -0,0 +1,172 @@ +#nullable enable +using Microsoft.Maui; +using Microsoft.Maui.Graphics; +using System; +using NativeView = Ooui.Element; + +namespace Ooui.Maui.Handlers +{ + public abstract partial class ViewHandler : IViewHandler + { + public static PropertyMapper ViewMapper = new OouiPropertyMapper + { + [nameof(IView.AutomationId)] = MapAutomationId, + // [nameof(IView.Visibility)] = MapVisibility, + // [nameof(IView.Background)] = MapBackground, + [nameof(IView.Width)] = MapWidth, + [nameof(IView.Height)] = MapHeight, + [nameof(IView.IsEnabled)] = MapIsEnabled, + // [nameof(IView.Opacity)] = MapOpacity, + [nameof(IView.Semantics)] = MapSemantics, + // [nameof(IView.TranslationX)] = MapTranslationX, + // [nameof(IView.TranslationY)] = MapTranslationY, + // [nameof(IView.Scale)] = MapScale, + // [nameof(IView.ScaleX)] = MapScaleX, + // [nameof(IView.ScaleY)] = MapScaleY, + // [nameof(IView.Rotation)] = MapRotation, + // [nameof(IView.RotationX)] = MapRotationX, + // [nameof(IView.RotationY)] = MapRotationY, + // [nameof(IView.AnchorX)] = MapAnchorX, + // [nameof(IView.AnchorY)] = MapAnchorY, + Actions = + { + // [nameof(IViewHandler.ContainerView)] = MapContainerView, + [nameof(IFrameworkElement.InvalidateMeasure)] = MapInvalidateMeasure, + [nameof(IFrameworkElement.Frame)] = MapFrame, + } + }; + + internal ViewHandler() + { + } + + bool _hasContainer; + + public bool HasContainer + { + get => _hasContainer; + set + { + if (_hasContainer == value) + return; + + _hasContainer = value; + + if (value) + SetupContainer(); + else + RemoveContainer(); + } + } + + protected abstract void SetupContainer(); + + protected abstract void RemoveContainer(); + + public IMauiContext? MauiContext { get; private set; } + + public IServiceProvider? Services => MauiContext?.Services; + + public virtual bool NeedsContainer { get; } + + public object? ContainerView { get; private protected set; } + + public object? NativeView { get; private protected set; } + + protected object? WrappedNativeView => ContainerView ?? NativeView; + + public IView? VirtualView { get; private protected set; } + + public void SetMauiContext(IMauiContext mauiContext) => MauiContext = mauiContext; + + public abstract void SetVirtualView(IView view); + + public abstract void UpdateValue(string property); + + void IViewHandler.DisconnectHandler() => DisconnectHandler(((NativeView?)NativeView)); + + public abstract Size GetDesiredSize(double widthConstraint, double heightConstraint); + + public abstract void NativeArrange(Rectangle frame); + + partial void ConnectingHandler(NativeView? nativeView); + + private protected void ConnectHandler(NativeView? nativeView) + { + ConnectingHandler(nativeView); + } + + partial void DisconnectingHandler(NativeView? nativeView); + + private protected void DisconnectHandler(NativeView? nativeView) + { + DisconnectingHandler(nativeView); + + if (VirtualView != null) + VirtualView.Handler = null; + + VirtualView = null; + } + + public static void MapWidth(ViewHandler handler, IView view) + { + ((NativeView?)handler.NativeView)?.UpdateWidth(view); + } + + public static void MapHeight(ViewHandler handler, IView view) + { + ((NativeView?)handler.NativeView)?.UpdateHeight(view); + } + + public static void MapIsEnabled(ViewHandler handler, IView view) + { + ((NativeView?)handler.NativeView)?.UpdateIsEnabled(view); + } + + public static void MapVisibility(ViewHandler handler, IView view) + { + ((NativeView?)handler.NativeView)?.UpdateVisibility(view); + } + + public static void MapBackground(ViewHandler handler, IView view) + { + ((NativeView?)handler.NativeView)?.UpdateBackground(view); + } + + public static void MapOpacity(IViewHandler handler, IView view) + { + ((NativeView?)handler.NativeView)?.UpdateOpacity(view); + } + + public static void MapAutomationId(ViewHandler handler, IView view) + { + ((NativeView?)handler.NativeView)?.UpdateAutomationId(view); + } + + static partial void MappingSemantics(ViewHandler handler, IView view); + + public static void MapSemantics(ViewHandler handler, IView view) + { + MappingSemantics(handler, view); + ((NativeView?)handler.NativeView)?.UpdateSemantics(view); + } + + public static void MapInvalidateMeasure(ViewHandler handler, IView view) + { + ((NativeView?)handler.NativeView)?.InvalidateMeasure(view); + } + + public static void MapContainerView(ViewHandler handler, IView view) + { + if (handler is ViewHandler viewHandler) + handler.HasContainer = viewHandler.NeedsContainer; + } + + static partial void MappingFrame(ViewHandler handler, IView view); + + public static void MapFrame(ViewHandler handler, IView view) + { + MappingFrame(handler, view); + } + } +} \ No newline at end of file diff --git a/Ooui.Maui/Handlers/View/ViewHandlerOfT.cs b/Ooui.Maui/Handlers/View/ViewHandlerOfT.cs new file mode 100644 index 0000000..27c5e07 --- /dev/null +++ b/Ooui.Maui/Handlers/View/ViewHandlerOfT.cs @@ -0,0 +1,238 @@ +#nullable enable +using System; +using System.Runtime.CompilerServices; +using Microsoft.Maui; +using Microsoft.Maui.Graphics; +using NativeView = Ooui.Element; + +namespace Ooui.Maui.Handlers +{ + public abstract partial class ViewHandler : Ooui.Maui.Handlers.ViewHandler, + INativeViewHandler + where TVirtualView : class, IView + where TNativeView : NativeView + { + Ooui.Element? INativeViewHandler.NativeView => WrappedNativeView; + // Ooui.Element? INativeViewHandler.ContainerView => ContainerView; + + protected new Ooui.Element? WrappedNativeView => (Ooui.Element?)base.WrappedNativeView; + + protected readonly PropertyMapper _defaultMapper; + protected PropertyMapper _mapper; + static bool HasSetDefaults; + + // TODO: fak: I don't know what's needed here + // [Microsoft.Maui.HotReload.OnHotReload] + static void OnHotReload() + { + HasSetDefaults = false; + } + + protected ViewHandler(PropertyMapper mapper) + { + _ = mapper ?? throw new ArgumentNullException(nameof(mapper)); + _defaultMapper = mapper; + _mapper = _defaultMapper; + } + + protected abstract TNativeView CreateNativeView(); + + public new TNativeView? NativeView + { + get => (TNativeView?)base.NativeView; + private set => base.NativeView = value; + } + + protected TNativeView NativeViewValidation([CallerMemberName] string callerName = "") + { + _ = NativeView ?? throw new InvalidOperationException($"NativeView cannot be null here: {callerName}"); + return NativeView; + } + + public override void SetVirtualView(IView view) + { + _ = view ?? throw new ArgumentNullException(nameof(view)); + + if (VirtualView == view) + return; + + if (VirtualView?.Handler != null) + VirtualView.Handler = null; + + bool setupNativeView = VirtualView == null; + + VirtualView = (TVirtualView)view; + NativeView ??= CreateNativeView(); + + if (VirtualView != null && VirtualView.Handler != this) + VirtualView.Handler = this; + + if (setupNativeView && NativeView != null) + { + ConnectHandler(NativeView); + } + + if (!HasSetDefaults) + { + if (NativeView != null) + { + SetupDefaults(NativeView); + } + + HasSetDefaults = true; + } + + _mapper = _defaultMapper; + + if (VirtualView is IPropertyMapperView imv) + { + var map = imv.GetPropertyMapperOverrides(); + var instancePropertyMapper = map as PropertyMapper; + if (map != null && instancePropertyMapper == null) + { + } + if (instancePropertyMapper != null) + { + instancePropertyMapper.Chained = _defaultMapper; + _mapper = instancePropertyMapper; + } + } + + if (_mapper is IOouiPropertyMapper omapper) { + omapper.UpdateProperties(this, VirtualView); + } + else { + throw new Exception("Expected property mapper to be a Ooui property mapper"); + } + } + + void IViewHandler.DisconnectHandler() + { + if (NativeView != null && VirtualView != null) + DisconnectHandler(NativeView); + } + + protected virtual void ConnectHandler(TNativeView nativeView) + { + base.ConnectHandler(nativeView); + } + + protected virtual void DisconnectHandler(TNativeView nativeView) + { + base.DisconnectHandler(nativeView); + } + + public override void UpdateValue(string property) { + if (_mapper == null) { + // Do nothing + } + else if (_mapper is IOouiPropertyMapper omapper) { + omapper.UpdateProperty(this, VirtualView, property); + } + else { + throw new Exception("Expected property mapper to be a Ooui property mapper"); + } + } + + protected virtual void SetupDefaults(TNativeView nativeView) { } + + public override void NativeArrange(Rectangle rect) + { + } + + public override Size GetDesiredSize(double widthConstraint, double heightConstraint) + { + return new Size(200, 100); + // var nativeView = WrappedNativeView; + + // if (nativeView == null || VirtualView == null) + // { + // return new Size(widthConstraint, heightConstraint); + // } + + // var explicitWidth = VirtualView.Width; + // var explicitHeight = VirtualView.Height; + // var hasExplicitWidth = explicitWidth >= 0; + // var hasExplicitHeight = explicitHeight >= 0; + + // var sizeThatFits = nativeView.SizeThatFits(new CoreGraphics.CGSize((float)widthConstraint, (float)heightConstraint)); + + // var size = new Size( + // sizeThatFits.Width == float.PositiveInfinity ? double.PositiveInfinity : sizeThatFits.Width, + // sizeThatFits.Height == float.PositiveInfinity ? double.PositiveInfinity : sizeThatFits.Height); + + // if (double.IsInfinity(size.Width) || double.IsInfinity(size.Height)) + // { + // nativeView.SizeToFit(); + // size = new Size(nativeView.Frame.Width, nativeView.Frame.Height); + // } + + // return new Size(hasExplicitWidth ? explicitWidth : size.Width, + // hasExplicitHeight ? explicitHeight : size.Height); + } + + + + protected override void SetupContainer() + { + if (NativeView == null || ContainerView != null) + return; + + // TODO: fak: Ooui doesn't support inspecting the parent + throw new NotImplementedException(); + + // var oldParent = (Element?)NativeView.Superview; + + // var oldIndex = oldParent?.IndexOfSubview(NativeView); + // NativeView.RemoveFromSuperview(); + + // ContainerView ??= new WrapperView(NativeView.Bounds); + // ContainerView.AddSubview(NativeView); + + // if (oldIndex is int idx && idx >= 0) + // oldParent?.InsertSubview(ContainerView, idx); + // else + // oldParent?.AddSubview(ContainerView); + } + + protected override void RemoveContainer() + { + // TODO: fak: Ooui doesn't support inspecting the parent + + if (NativeView == null || ContainerView == null)// || NativeView.Superview != ContainerView) + return; + + // var oldParent = (UIView?)ContainerView.Superview; + + // var oldIndex = oldParent?.IndexOfSubview(ContainerView); + // ContainerView.RemoveFromSuperview(); + + // ContainerView = null; + + // if (oldIndex is int idx && idx >= 0) + // oldParent?.InsertSubview(NativeView, idx); + // else + // oldParent?.AddSubview(NativeView); + } + } + + public abstract partial class ViewHandler : ViewHandler + where TVirtualView : class, IView + { + internal ViewHandler() + { + } + + public new TVirtualView? VirtualView + { + get => (TVirtualView?)base.VirtualView; + private protected set => base.VirtualView = value; + } + + protected TVirtualView VirtualViewWithValidation([CallerMemberName] string callerName = "") + { + _ = VirtualView ?? throw new InvalidOperationException($"VirtualView cannot be null here: {callerName}"); + return VirtualView; + } + } +} \ No newline at end of file diff --git a/Ooui.Maui/INativeViewHandler.cs b/Ooui.Maui/INativeViewHandler.cs new file mode 100644 index 0000000..d54fede --- /dev/null +++ b/Ooui.Maui/INativeViewHandler.cs @@ -0,0 +1,9 @@ +using Microsoft.Maui; + +namespace Ooui.Maui +{ + interface INativeViewHandler : IViewHandler + { + new Element? NativeView { get; } + } +} diff --git a/Ooui.Maui/Ooui.Maui.csproj b/Ooui.Maui/Ooui.Maui.csproj new file mode 100644 index 0000000..58e882c --- /dev/null +++ b/Ooui.Maui/Ooui.Maui.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + + + + + + + + + diff --git a/Ooui.Maui/OouiMauiContext.cs b/Ooui.Maui/OouiMauiContext.cs new file mode 100644 index 0000000..59dba26 --- /dev/null +++ b/Ooui.Maui/OouiMauiContext.cs @@ -0,0 +1,77 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Maui; +using Microsoft.Maui.Hosting; + +namespace Ooui.Maui +{ + public class OouiMauiContext : IMauiContext + { + public IServiceProvider? Services { get; } + + readonly IMauiHandlersServiceProvider? _mauiHandlersServiceProvider; + public IMauiHandlersServiceProvider Handlers => + _mauiHandlersServiceProvider ?? throw new InvalidOperationException($"No service provider was specified during construction."); + + public OouiMauiContext() + { + } + + public OouiMauiContext(IServiceProvider services) + { + Services = services ?? throw new ArgumentNullException(nameof(services)); + _mauiHandlersServiceProvider = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(services); + + if (!Controls.Compatibility.Forms.IsInitialized) + Controls.Compatibility.Forms.Init(this); + } + + public OouiMauiContext(IStartup startup) + : this (GetStartupServices (startup)) + { + } + + static OouiMauiContext() + { + Microsoft.Maui.Controls.DependencyService.Register(); + } + + public static OouiMauiContext FromStartup () + where TStartup : IStartup, new() + { + var startup = new TStartup(); + return new OouiMauiContext (startup); + } + + static IServiceProvider GetStartupServices(IStartup startup) + { + var host = startup + .CreateAppHostBuilder() + .ConfigureServices(ConfigureNativeServices) + .ConfigureUsing(startup) + .ConfigureMauiHandlers(ConfigureOouiHandlers) + .Build(); + + var Services = host.Services; + return Services; + } + + static void ConfigureOouiHandlers(IMauiHandlersCollection handlers) + { + Console.WriteLine("Registering IPage"); + // var previousHandler = handlers.GetHandler(); + handlers.AddHandler(typeof(IPage), typeof(Ooui.Maui.Handlers.PageHandler)); + handlers.AddHandler(typeof(Microsoft.Maui.Controls.Page), typeof(Ooui.Maui.Handlers.PageHandler)); + handlers.AddHandler(typeof(Microsoft.Maui.Controls.Layout2.Layout), typeof(Ooui.Maui.Handlers.LayoutHandler)); + handlers.AddHandler(typeof(Microsoft.Maui.Controls.Button), typeof(Ooui.Maui.Handlers.ButtonHandler)); + handlers.AddHandler(typeof(Microsoft.Maui.Controls.Label), typeof(Ooui.Maui.Handlers.LabelHandler)); + // var newHandler = handlers.GetHandler(); + // Console.WriteLine($"P = {previousHandler}, N = {newHandler}"); + } + + static void ConfigureNativeServices(HostBuilderContext ctx, IServiceCollection services) + { + } + } +} diff --git a/Ooui.Maui/OouiPlatformServices.cs b/Ooui.Maui/OouiPlatformServices.cs new file mode 100644 index 0000000..9a2fb5c --- /dev/null +++ b/Ooui.Maui/OouiPlatformServices.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Internals; + +namespace Ooui.Maui +{ + class OouiPlatformServices : IPlatformServices, IPlatformInvalidate + { + public bool IsInvokeRequired => false; + + public OSAppTheme RequestedTheme => OSAppTheme.Unspecified; + + public string RuntimePlatform => "Ooui"; + + public void BeginInvokeOnMainThread(Action action) + { + Task.Run(action); + } + + public Ticker CreateTicker() + { + throw new NotImplementedException(); + } + + public Assembly[] GetAssemblies() + { + return AppDomain.CurrentDomain.GetAssemblies(); + } + + public string GetHash(string input) + { + throw new NotImplementedException(); + } + + public string GetMD5Hash(string input) + { + throw new NotImplementedException(); + } + + public Microsoft.Maui.Graphics.Color GetNamedColor(string name) + { + throw new NotImplementedException(); + } + + public double GetNamedSize(NamedSize size, Type targetElementType, bool useOldSizes) + { + return size switch { + NamedSize.Body => 16.0, + _ => 16.0 + }; + } + + public SizeRequest GetNativeSize(VisualElement view, double widthConstraint, double heightConstraint) + { + throw new NotImplementedException(); + } + + public Task GetStreamAsync(Uri uri, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public IIsolatedStorageFile GetUserStoreForApplication() + { + throw new NotImplementedException(); + } + + public void Invalidate(VisualElement visualElement) + { + throw new NotImplementedException(); + } + + public void OpenUriAction(Uri uri) + { + throw new NotImplementedException(); + } + + public void QuitApplication() + { + throw new NotSupportedException(); + } + + public void StartTimer(TimeSpan interval, Func callback) + { + throw new NotImplementedException(); + } + } +} diff --git a/PlatformSamples/ConsoleNet6/.vscode/launch.json b/PlatformSamples/ConsoleNet6/.vscode/launch.json new file mode 100644 index 0000000..51f4149 --- /dev/null +++ b/PlatformSamples/ConsoleNet6/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/net6.0/ConsoleNet6.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/PlatformSamples/ConsoleNet6/.vscode/tasks.json b/PlatformSamples/ConsoleNet6/.vscode/tasks.json new file mode 100644 index 0000000..a29efc3 --- /dev/null +++ b/PlatformSamples/ConsoleNet6/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/ConsoleNet6.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/ConsoleNet6.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/ConsoleNet6.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/PlatformSamples/ConsoleNet6/App.cs b/PlatformSamples/ConsoleNet6/App.cs new file mode 100644 index 0000000..8a46acb --- /dev/null +++ b/PlatformSamples/ConsoleNet6/App.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.Maui; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific; +using Application = Microsoft.Maui.Controls.Application; + +namespace ConsoleNet6 +{ + public partial class App : Application + { + public App() + { + } + + protected override IWindow CreateWindow(IActivationState activationState) + { + Console.WriteLine("CREATING A WINDOW, YO"); + // Microsoft.Maui.Controls.Compatibility.Forms.Init(activationState); + + this.On() + .SetImageDirectory("Assets"); + + return new Microsoft.Maui.Controls.Window(new MainPage()); + } + } +} diff --git a/PlatformSamples/ConsoleNet6/ConsoleNet6.csproj b/PlatformSamples/ConsoleNet6/ConsoleNet6.csproj new file mode 100644 index 0000000..35a88c5 --- /dev/null +++ b/PlatformSamples/ConsoleNet6/ConsoleNet6.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + + + + + + + + + + + diff --git a/PlatformSamples/ConsoleNet6/MainPage.cs b/PlatformSamples/ConsoleNet6/MainPage.cs new file mode 100644 index 0000000..f5fd623 --- /dev/null +++ b/PlatformSamples/ConsoleNet6/MainPage.cs @@ -0,0 +1,25 @@ +using Microsoft.Maui; +using Microsoft.Maui.Controls; + +class MainPage : ContentPage { + + VerticalStackLayout layout = new VerticalStackLayout(); + + int counter = 0; + + public MainPage () : base () { + Title = "Maui"; + + var label = new Label { Text = "Hello Chat Room!!!!" }; + layout.Add(label); + layout.Add(new Button { + Text = "Increase Counter", + Command = new Command (() => { + counter++; + label.Text = "Count = " + counter + "..."; + }) + }); + Content = layout; + } +} + diff --git a/PlatformSamples/ConsoleNet6/Program.cs b/PlatformSamples/ConsoleNet6/Program.cs new file mode 100644 index 0000000..75bc90c --- /dev/null +++ b/PlatformSamples/ConsoleNet6/Program.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Ooui.Maui; +using Microsoft.Maui; + +namespace ConsoleNet6 +{ + public class MainWindow : Microsoft.Maui.Controls.Window + { + public MainWindow() : base(new MainPage()) + { + } + } + + public class Program + { + static void Main(string[] args) + { + + System.Console.WriteLine("Hello chat room from Maui on Ooui"); + + var context = OouiMauiContext.FromStartup(); + + var Application = context.Services.GetRequiredService(); + + var element = Application.ToOouiElement(context); + + Console.WriteLine($"Element = {element}"); + + Ooui.UI.Publish("/", element); + for (;;) { + System.Threading.Thread.Sleep(1000); + } + } + } +} \ No newline at end of file diff --git a/PlatformSamples/ConsoleNet6/Startup.cs b/PlatformSamples/ConsoleNet6/Startup.cs new file mode 100644 index 0000000..d8b25af --- /dev/null +++ b/PlatformSamples/ConsoleNet6/Startup.cs @@ -0,0 +1,20 @@ +using Microsoft.Maui; +using Microsoft.Maui.Hosting; +using Microsoft.Maui.Controls.Hosting; + + +namespace ConsoleNet6 +{ + public class Startup : IStartup + { + public void Configure(IAppHostBuilder appBuilder) + { + appBuilder + .UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + }); + } + } +}