From 09ad8891e48519bc0641ea1ea08b74ef7de68772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Fri, 12 Jan 2024 10:52:54 +0100 Subject: [PATCH 1/6] Remove redundant dot for accessing index --- samples/Components/TicTacComponent/App.fs | 6 +++--- samples/TicTacToe/App.fs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/Components/TicTacComponent/App.fs b/samples/Components/TicTacComponent/App.fs index 0aa34f6..8641dd2 100644 --- a/samples/Components/TicTacComponent/App.fs +++ b/samples/Components/TicTacComponent/App.fs @@ -112,7 +112,7 @@ module App = yield [ (0, 2); (1, 1); (2, 0) ] ] /// Determine if a line is a winning line. - let getLine (board: Board) line = line |> List.map(fun p -> board.[p]) + let getLine (board: Board) line = line |> List.map(fun p -> board[p]) /// Determine if a line is a winning line. let getLineWinner line = @@ -223,13 +223,13 @@ module App = Rectangle().stroke(gridColor).strokeThickness(5.).gridColumn(3).gridRowSpan(5) for row, col as pos in positions do - if canPlay model model.Board.[pos] then + if canPlay model model.Board[pos] then Button("", Play pos) .background(Colors.LightBlue) .gridRow(row * 2) .gridColumn(col * 2) else - match model.Board.[pos] with + match model.Board[pos] with | Empty -> () | Full X -> Label("X") diff --git a/samples/TicTacToe/App.fs b/samples/TicTacToe/App.fs index 7a67f1d..5e28f21 100644 --- a/samples/TicTacToe/App.fs +++ b/samples/TicTacToe/App.fs @@ -112,7 +112,7 @@ module App = yield [ (0, 2); (1, 1); (2, 0) ] ] /// Determine if a line is a winning line. - let getLine (board: Board) line = line |> List.map(fun p -> board.[p]) + let getLine (board: Board) line = line |> List.map(fun p -> board[p]) /// Determine if a line is a winning line. let getLineWinner line = @@ -208,13 +208,13 @@ module App = Rectangle().stroke(gridColor).strokeThickness(5.).gridColumn(3).gridRowSpan(5) for row, col as pos in positions do - if canPlay model model.Board.[pos] then + if canPlay model model.Board[pos] then Button("", Play pos) .background(Colors.LightBlue) .gridRow(row * 2) .gridColumn(col * 2) else - match model.Board.[pos] with + match model.Board[pos] with | Empty -> () | Full X -> Label("X") From e796931faf1741d7ef0746a320442deedd478f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Mon, 15 Jan 2024 19:27:27 +0100 Subject: [PATCH 2/6] Refactored components as base of all Fabulous app --- samples/Components/MvuCounter/App.fs | 4 +- samples/CounterApp/App.fs | 2 +- .../AppHostBuilderExtensions.fs | 48 ++++--- src/Fabulous.MauiControls/Component.fs | 25 ++-- src/Fabulous.MauiControls/Environment.fs | 54 -------- .../Fabulous.MauiControls.fsproj | 4 +- src/Fabulous.MauiControls/Program.fs | 131 ++++-------------- src/Fabulous.MauiControls/Views/Any.fs | 6 +- .../VirtualizedCollection.fs | 2 +- src/Fabulous.MauiControls/Widgets.fs | 8 +- 10 files changed, 73 insertions(+), 211 deletions(-) delete mode 100644 src/Fabulous.MauiControls/Environment.fs diff --git a/samples/Components/MvuCounter/App.fs b/samples/Components/MvuCounter/App.fs index 7743b2b..ec43513 100644 --- a/samples/Components/MvuCounter/App.fs +++ b/samples/Components/MvuCounter/App.fs @@ -49,10 +49,10 @@ module App = else model, Cmd.none - let program = Program.ForComponent.statefulWithCmd init update + let program = Program.statefulWithCmd init update let view () = - MvuComponent(program) { + Component(program) { let! model = Mvu.State Application() { diff --git a/samples/CounterApp/App.fs b/samples/CounterApp/App.fs index 531f35b..1a41731 100644 --- a/samples/CounterApp/App.fs +++ b/samples/CounterApp/App.fs @@ -77,4 +77,4 @@ module App = ) ) - let program = Program.statefulWithCmd init update view + let program = Program.statefulWithCmd init update |> Program.withView view diff --git a/src/Fabulous.MauiControls/AppHostBuilderExtensions.fs b/src/Fabulous.MauiControls/AppHostBuilderExtensions.fs index 63f3795..2f34219 100644 --- a/src/Fabulous.MauiControls/AppHostBuilderExtensions.fs +++ b/src/Fabulous.MauiControls/AppHostBuilderExtensions.fs @@ -9,38 +9,40 @@ open System [] type AppHostBuilderExtensions = [] - static member UseFabulousApp(this: MauiAppBuilder, program: Program) : MauiAppBuilder = + static member inline private UseFabulousApp(this: MauiAppBuilder, canReuseView, logger, [] viewFn: unit -> Widget) : MauiAppBuilder = this.UseMauiApp(fun (_serviceProvider: IServiceProvider) -> - Component.registerComponentFunctions() - let app = Program.startApplication program - Theme.ListenForChanges(app) - app) + let widget = viewFn() - [] - static member UseFabulousApp(this: MauiAppBuilder, program: Program>) : MauiAppBuilder = - this.UseMauiApp(fun (_serviceProvider: IServiceProvider) -> - Component.registerComponentFunctions() - let app = Program.startApplicationMemo program + let treeContext: ViewTreeContext = + { CanReuseView = canReuseView + Logger = logger + Dispatch = ignore + GetViewNode = ViewNode.get + GetComponent = Component.get } + + let def = WidgetDefinitionStore.get widget.Key + let struct (_, view) = def.CreateView(widget, treeContext, ValueNone) + let app = view :?> Microsoft.Maui.Controls.Application Theme.ListenForChanges(app) app) [] static member UseFabulousApp(this: MauiAppBuilder, program: Program<'arg, 'model, 'msg, #IFabApplication>, arg: 'arg) : MauiAppBuilder = - this.UseMauiApp(fun (_serviceProvider: IServiceProvider) -> - Component.registerComponentFunctions() - let app = Program.startApplicationWithArgs arg program - Theme.ListenForChanges(app) - app) + this.UseFabulousApp( + program.CanReuseView, + program.Program.Logger, + fun () -> + (View.Component(program.Program, arg) { + let! model = Mvu.State + program.View model + }) + .Compile() + ) [] - static member UseFabulousApp(this: MauiAppBuilder, program: Program<'arg, 'model, 'msg, Memo.Memoized<#IFabApplication>>, arg: 'arg) : MauiAppBuilder = - this.UseMauiApp(fun (_serviceProvider: IServiceProvider) -> - Component.registerComponentFunctions() - let app = Program.startApplicationWithArgsMemo arg program - Theme.ListenForChanges(app) - app) + static member UseFabulousApp(this: MauiAppBuilder, program: Program) : MauiAppBuilder = + this.UseFabulousApp(program, ()) [] static member UseFabulousApp(this: MauiAppBuilder, view: unit -> WidgetBuilder) : MauiAppBuilder = - let program = Program.stateless view - this.UseFabulousApp(program) + this.UseFabulousApp(MauiViewHelpers.canReuseView, ProgramDefaults.defaultLogger(), (fun () -> view().Compile())) diff --git a/src/Fabulous.MauiControls/Component.fs b/src/Fabulous.MauiControls/Component.fs index 97987b9..c17a69a 100644 --- a/src/Fabulous.MauiControls/Component.fs +++ b/src/Fabulous.MauiControls/Component.fs @@ -5,29 +5,20 @@ open Microsoft.Maui.Controls module Component = let ComponentProperty = - BindableProperty.CreateAttached("Component", typeof, typeof, null) + BindableProperty.CreateAttached("Component", typeof, typeof, null) - let registerComponentFunctions () = - BaseComponent.setComponentFunctions( - (fun view -> (view :?> BindableObject).GetValue(ComponentProperty) :?> IBaseComponent), - (fun view comp -> - let previousComp = - (view :?> BindableObject).GetValue(ComponentProperty) :?> IBaseComponent + let get (target: obj) = + (target :?> BindableObject).GetValue(ComponentProperty) - if previousComp <> null then - previousComp.Dispose() - - (view :?> BindableObject).SetValue(ComponentProperty, comp)) - ) + let set (comp: obj) (target: obj) = + (target :?> BindableObject).SetValue(ComponentProperty, comp) [] module ComponentBuilders = type Fabulous.Maui.View with - static member inline Component<'msg, 'marker>() = ComponentBuilder<'msg, 'marker>() + static member inline Component<'msg, 'marker>() = ComponentBuilder() - static member inline MvuComponent<'msg, 'marker, 'cMsg, 'cModel>(program: Program) = - MvuComponentBuilder<'msg, 'marker, unit, 'cMsg, 'cModel>(program, ()) + static member inline Component(program: Program) = MvuComponentBuilder(program, ()) - static member inline MvuComponent<'msg, 'marker, 'cArg, 'cMsg, 'cModel>(program: Program<'cArg, 'cModel, 'cMsg>, arg: 'cArg) = - MvuComponentBuilder<'msg, 'marker, 'cArg, 'cMsg, 'cModel>(program, arg) + static member inline Component(program: Program<'arg, 'model, 'msg>, arg: 'arg) = MvuComponentBuilder(program, arg) diff --git a/src/Fabulous.MauiControls/Environment.fs b/src/Fabulous.MauiControls/Environment.fs deleted file mode 100644 index f3f4dd5..0000000 --- a/src/Fabulous.MauiControls/Environment.fs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Fabulous.Maui - -open System -open Fabulous -open Microsoft.Maui.ApplicationModel -open Microsoft.Maui.Controls - -open type Fabulous.Maui.View - -[] -type EnvironmentKeys = - class - end - -module EnvTheme = - let Key = EnvironmentKey("Theme", AppInfo.RequestedTheme) - - let initialize (env: EnvironmentContext) = env.Set(Key, Key.DefaultValue, false) - - let subscribe (env: EnvironmentContext) (target: obj) = - let app = target :?> Application - - let fromSource = - app.RequestedThemeChanged.Subscribe(fun args -> env.Set(Key, args.RequestedTheme, false)) - - let toSource = - env.ValueChanged.Subscribe(fun args -> - if args.Key = Key.Key && args.FromUserCode = true then - let newValue = - match args.Value with - | ValueNone -> AppTheme.Unspecified - | ValueSome value -> value :?> AppTheme - - app.UserAppTheme <- newValue) - - { new IDisposable with - member _.Dispose() = - fromSource.Dispose() - toSource.Dispose() } - -[] -module EnvironmentBuilders = - type EnvironmentKeys with - - static member Theme = EnvTheme.Key - -module EnvironmentHelpers = - let initialize (env: EnvironmentContext) = EnvTheme.initialize env - - let subscribe (env: EnvironmentContext, target: obj) = - let theme = EnvTheme.subscribe env target - - { new IDisposable with - member _.Dispose() = theme.Dispose() } diff --git a/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj b/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj index 17fb524..4b34cbf 100644 --- a/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj +++ b/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj @@ -1,11 +1,10 @@ - false + true net8.0 true true - false @@ -145,7 +144,6 @@ - diff --git a/src/Fabulous.MauiControls/Program.fs b/src/Fabulous.MauiControls/Program.fs index c24da50..d51cd8b 100644 --- a/src/Fabulous.MauiControls/Program.fs +++ b/src/Fabulous.MauiControls/Program.fs @@ -1,6 +1,5 @@ namespace Fabulous.Maui -open System open Fabulous open Fabulous.ScalarAttributeDefinitions open Fabulous.WidgetCollectionAttributeDefinitions @@ -74,99 +73,45 @@ module MauiViewHelpers = | _ -> true module Program = - let inline private define (view: 'model -> WidgetBuilder<'msg, 'marker>) (program: Program<'arg, 'model, 'msg>) : Program<'arg, 'model, 'msg, 'marker> = - let env = - { Initialize = - fun env -> - program.Environment.Initialize(env) - EnvironmentHelpers.initialize(env) - Subscribe = - fun (env, target) -> - let fab = program.Environment.Subscribe(env, target) - let maui = EnvironmentHelpers.subscribe(env, target) - - { new IDisposable with - member this.Dispose() = - fab.Dispose() - maui.Dispose() } } - - { Program = { program with Environment = env } + let withView (view: 'model -> WidgetBuilder<'msg, 'marker>) (program: Program<'arg, 'model, 'msg>) : Program<'arg, 'model, 'msg, 'marker> = + { Program = program View = view CanReuseView = MauiViewHelpers.canReuseView SyncAction = MainThread.BeginInvokeOnMainThread } - /// Create a program for a static view - let stateless (view: unit -> WidgetBuilder) = - let program = - Program.ForComponent.define (fun () -> (), Cmd.none) (fun () () -> (), Cmd.none) - - define view program - - /// Create a program using an MVU loop - let stateful (init: 'arg -> 'model) (update: 'msg -> 'model -> 'model) (view: 'model -> WidgetBuilder<'msg, 'marker>) = - define view (Program.ForComponent.stateful init update) - - /// Create a program using an MVU loop. Add support for Cmd - let statefulWithCmd - (init: 'arg -> 'model * Cmd<'msg>) - (update: 'msg -> 'model -> 'model * Cmd<'msg>) - (view: 'model -> WidgetBuilder<'msg, #IFabApplication>) - = - define view (Program.ForComponent.statefulWithCmd init update) - - /// Create a program using an MVU loop. Add support for Cmd - let statefulWithCmdMemo - (init: 'arg -> 'model * Cmd<'msg>) - (update: 'msg -> 'model -> 'model * Cmd<'msg>) - (view: 'model -> WidgetBuilder<'msg, Memo.Memoized<#IFabApplication>>) - = - define view (Program.ForComponent.statefulWithCmd init update) - - /// Create a program using an MVU loop. Add support for CmdMsg - let statefulWithCmdMsg - (init: 'arg -> 'model * 'cmdMsg list) - (update: 'msg -> 'model -> 'model * 'cmdMsg list) - (view: 'model -> WidgetBuilder<'msg, 'marker>) - (mapCmd: 'cmdMsg -> Cmd<'msg>) - = - define view (Program.ForComponent.statefulWithCmdMsg init update mapCmd) + let stateless (view: unit -> WidgetBuilder) : Program = + Program.stateful (fun _ -> ()) (fun _ _ -> ()) |> withView view /// Start the program - let startApplicationWithArgs (arg: 'arg) (program: Program<'arg, 'model, 'msg, #IFabApplication>) : Application = - let stateKey = StateStore.getNextKey() - let runner = Runners.create stateKey program.Program - runner.Start(arg) - let adapter = ViewAdapters.create ViewNode.get stateKey program runner - adapter.CreateView() |> unbox + let startApplicationWithArgs<'arg, 'model, 'msg, 'marker when 'marker :> IFabApplication> + (arg: 'arg) + (program: Program<'arg, 'model, 'msg, 'marker>) + : Application = + let view = + View.Component(program.Program, arg) { + let! model = Mvu.State + program.View model + } + + let widget = view.Compile() + + let treeContext: ViewTreeContext = + { CanReuseView = program.CanReuseView + Logger = program.Program.Logger + Dispatch = ignore + GetViewNode = ViewNode.get + GetComponent = Component.get } - /// Start the program - let startApplication (program: Program) : Application = startApplicationWithArgs () program + let def = WidgetDefinitionStore.get widget.Key + let struct (_, view) = def.CreateView(widget, treeContext, ValueNone) - /// Start the program - let startApplicationWithArgsMemo (arg: 'arg) (program: Program<'arg, 'model, 'msg, Memo.Memoized<#IFabApplication>>) : Application = - let stateKey = StateStore.getNextKey() - let runner = Runners.create stateKey program.Program - runner.Start(arg) - let adapter = ViewAdapters.create ViewNode.get stateKey program runner - let view = adapter.CreateView() unbox view /// Start the program - let startApplicationMemo (program: Program>) : Application = startApplicationWithArgsMemo () program - - /// Subscribe to external source of events. - /// The subscription is called once - with the initial model, but can dispatch new messages at any time. - let withSubscription (subscribe: 'model -> Cmd<'msg>) (program: Program<'arg, 'model, 'msg, 'marker>) = - { program with - Program = Program.ForComponent.withSubscription subscribe program.Program } - - /// Configure how the output messages from Fabulous will be handled - let withLogger (logger: Logger) (program: Program<'arg, 'model, 'msg, 'marker>) = - { program with - Program = Program.ForComponent.withLogger logger program.Program } + let startApplication (program: Program) : Application = startApplicationWithArgs () program - /// Trace all the updates to the debug output - let withTrace (trace: string * string -> unit) (program: Program<'arg, 'model, 'msg, 'marker>) = + /// Trace all the view updates to the debug output + let withViewTrace (trace: string * string -> unit) (program: Program<'arg, 'model, 'msg, 'marker>) = let traceView model = trace("View, model = {0}", $"%0A{model}") @@ -178,27 +123,7 @@ module Program = trace("Error in view function: {0}", $"%0A{e}") reraise() - { program with - Program = Program.ForComponent.withTrace trace program.Program - View = traceView } - - /// Configure how the unhandled exceptions happening during the execution of a Fabulous app with be handled - let withExceptionHandler (handler: exn -> bool) (program: Program<'arg, 'model, 'msg, 'marker>) = - { program with - Program = Program.ForComponent.withExceptionHandler handler program.Program } - - /// Allow the app to react to theme changes - let withThemeAwareness (program: Program<'arg, 'model, 'msg, #IFabApplication>) = - { Program = - { Environment = program.Program.Environment - Init = ThemeAwareProgram.init program.Program.Init - Update = ThemeAwareProgram.update program.Program.Update - Subscribe = fun model -> program.Program.Subscribe model.Model |> Cmd.map ThemeAwareProgram.Msg.ModelMsg - Logger = program.Program.Logger - ExceptionHandler = program.Program.ExceptionHandler } - View = ThemeAwareProgram.view program.View - CanReuseView = program.CanReuseView - SyncAction = program.SyncAction } + { program with View = traceView } [] module CmdMsg = diff --git a/src/Fabulous.MauiControls/Views/Any.fs b/src/Fabulous.MauiControls/Views/Any.fs index 0b7debd..bb441b1 100644 --- a/src/Fabulous.MauiControls/Views/Any.fs +++ b/src/Fabulous.MauiControls/Views/Any.fs @@ -15,7 +15,7 @@ module AnyBuilders = /// Downcast to View widget to allow to return different types of views in a single expression (e.g. if/else, match with pattern, etc.) /// Current widget static member AnyView(this: WidgetBuilder<'msg, Memo.Memoized<#IFabView>>) = - WidgetBuilder<'msg, Memo.Memoized>(this.Key, this.Attributes) + WidgetBuilder<'msg, IFabView>(this.Key, this.Attributes) /// Downcast to Page widget to allow to return different types of pages in a single expression (e.g. if/else, match with pattern, etc.) /// Current widget @@ -25,7 +25,7 @@ module AnyBuilders = /// Downcast to Page widget to allow to return different types of pages in a single expression (e.g. if/else, match with pattern, etc.) /// Current widget static member AnyPage(this: WidgetBuilder<'msg, Memo.Memoized<#IFabPage>>) = - WidgetBuilder<'msg, Memo.Memoized>(this.Key, this.Attributes) + WidgetBuilder<'msg, IFabPage>(this.Key, this.Attributes) /// Downcast to Cell widget to allow to return different types of cells in a single expression (e.g. if/else, match with pattern, etc.) /// Current widget @@ -35,4 +35,4 @@ module AnyBuilders = /// Downcast to Cell widget to allow to return different types of cells in a single expression (e.g. if/else, match with pattern, etc.) /// Current widget static member AnyCell(this: WidgetBuilder<'msg, Memo.Memoized<#IFabCell>>) = - WidgetBuilder<'msg, Memo.Memoized>(this.Key, this.Attributes) + WidgetBuilder<'msg, IFabCell>(this.Key, this.Attributes) diff --git a/src/Fabulous.MauiControls/VirtualizedCollection.fs b/src/Fabulous.MauiControls/VirtualizedCollection.fs index d515b17..cee7341 100644 --- a/src/Fabulous.MauiControls/VirtualizedCollection.fs +++ b/src/Fabulous.MauiControls/VirtualizedCollection.fs @@ -33,7 +33,7 @@ type WidgetDataTemplate(parent: IViewNode, ``type``: Type, templateFn: obj -> Wi let bindableObject = Activator.CreateInstance ``type`` :?> BindableObject let viewNode = - ViewNode(Some parent, parent.TreeContext, parent.EnvironmentContext, WeakReference(bindableObject)) + ViewNode(Some parent, parent.TreeContext, WeakReference(bindableObject)) bindableObject.SetValue(ViewNode.ViewNodeProperty, viewNode) diff --git a/src/Fabulous.MauiControls/Widgets.fs b/src/Fabulous.MauiControls/Widgets.fs index f4bed79..65d1695 100644 --- a/src/Fabulous.MauiControls/Widgets.fs +++ b/src/Fabulous.MauiControls/Widgets.fs @@ -23,7 +23,7 @@ module Widgets = Name = typeof<'T>.Name TargetType = typeof<'T> CreateView = - fun (widget, treeContext, env, parentNode) -> + fun (widget, treeContext, parentNode) -> treeContext.Logger.Debug("Creating view for {0}", typeof<'T>.Name) let view = new 'T() @@ -34,7 +34,7 @@ module Widgets = | ValueNone -> None | ValueSome node -> Some node - let node = ViewNode(parentNode, treeContext, env, weakReference) + let node = ViewNode(parentNode, treeContext, weakReference) ViewNode.set node view @@ -43,7 +43,7 @@ module Widgets = Reconciler.update treeContext.CanReuseView ValueNone widget node struct (node :> IViewNode, box view) AttachView = - fun (widget, treeContext, env, parentNode, view) -> + fun (widget, treeContext, parentNode, view) -> treeContext.Logger.Debug("Attaching view for {0}", typeof<'T>.Name) let view = unbox<'T> view @@ -54,7 +54,7 @@ module Widgets = | ValueNone -> None | ValueSome node -> Some node - let node = ViewNode(parentNode, treeContext, env, weakReference) + let node = ViewNode(parentNode, treeContext, weakReference) ViewNode.set node view From 2a53002f0e689f71775bd96338efc5ce3219e86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Tue, 16 Jan 2024 09:45:06 +0100 Subject: [PATCH 3/6] Update samples --- samples/Components/HelloComponent/App.fs | 113 +------------ samples/Components/MultipleMvus/App.fs | 8 +- samples/Components/SimpleCounter/App.fs | 4 +- samples/Components/TicTacComponent/App.fs | 6 +- samples/HelloWorld/App.fs | 189 +--------------------- samples/HelloWorld/HelloWorld.fsproj | 1 - samples/HelloWorld/MauiProgram.fs | 16 -- samples/Playground/App.fs | 9 +- samples/TicTacToe/App.fs | 3 +- src/Fabulous.MauiControls/Program.fs | 28 ---- 10 files changed, 25 insertions(+), 352 deletions(-) delete mode 100644 samples/HelloWorld/MauiProgram.fs diff --git a/samples/Components/HelloComponent/App.fs b/samples/Components/HelloComponent/App.fs index d39e439..d8d1be2 100644 --- a/samples/Components/HelloComponent/App.fs +++ b/samples/Components/HelloComponent/App.fs @@ -1,120 +1,13 @@ namespace HelloComponent -open Fabulous open Fabulous.Maui -open Microsoft.Maui.ApplicationModel open Microsoft.Maui.Hosting -open type Fabulous.Maui.View - -[] -module AppEnvironmentKeys = - let CountKey = EnvironmentKey("Count", 0) - - type EnvironmentKeys with - static member Count = CountKey - -open type Fabulous.Maui.EnvironmentKeys +open type Fabulous.Maui.View module App = - let themeViewer () = - Component() { - let! theme = Environment(Theme) - - VStack() { - Label($"[themeViewer] Current theme is %A{theme.Current}") - - Button( - "[themeViewer] Toggle theme", - fun () -> - theme.Set( - if theme.Current = AppTheme.Light then - AppTheme.Dark - else - AppTheme.Light - ) - ) - } - } - - let subCountViewer () = - Component() { - let! count = Environment(Count) - Label($"[SubCountViewer] Count = {count.Current}") - } - - let subCountSetter () = - Component() { - let! count = Environment(Count) - - VStack() { - Button("[SubCountSetter] Increment", (fun () -> count.Set(count.Current + 1))) - Button("[SubCountSetter] Decrement", (fun () -> count.Set(count.Current - 1))) - } - } - - let subCount () = - (Component() { - VStack() { - subCountViewer() - subCountSetter() - } - }) - .environment(Count, 10) - - let countViewer () = - Component() { - let! count = Environment(Count) - VStack() { Label($"[CountViewer] Count = {count.Current}") } - } - - let countSetter () = - Component() { - let! count = Environment(Count) - - VStack() { - Button("[CountSetter] Increment", (fun () -> count.Set(count.Current + 1))) - Button("[CountSetter] Decrement", (fun () -> count.Set(count.Current - 1))) - } - } - - let count' () = - Component() { - VStack() { - countViewer() - countSetter() - } - } - let view () = - (Component() { - let! count = Environment(Count) - let! theme = Environment(Theme) - - Application() { - ContentPage() { - VStack() { - Label($"[view] Current theme is %A{theme.Current}") - Label($"[view] Count = {count.Current}") - Button("[view] Increment", (fun () -> count.Set(count.Current + 1))) - Button("[view] Decrement", (fun () -> count.Set(count.Current - 1))) - - count'() - subCount() - themeViewer() - } - } - } - }) - .environment(Count, 0) + Component() { Application() { ContentPage() { Label("Hello Component").center() } } } let createMauiApp () = - MauiApp - .CreateBuilder() - .UseFabulousApp(view) - .ConfigureFonts(fun fonts -> - fonts - .AddFont("OpenSans-Regular.ttf", "OpenSansRegular") - .AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold") - |> ignore) - .Build() + MauiApp.CreateBuilder().UseFabulousApp(view).Build() diff --git a/samples/Components/MultipleMvus/App.fs b/samples/Components/MultipleMvus/App.fs index a16b340..36a52a2 100644 --- a/samples/Components/MultipleMvus/App.fs +++ b/samples/Components/MultipleMvus/App.fs @@ -19,7 +19,7 @@ module Counter = | Increment -> { Count = model.Count + 1 } | Decrement -> { Count = model.Count - 1 } - let program = Program.ForComponent.stateful init update + let program = Program.stateful init update module Form = type Model = { FirstName: string; LastName: string } @@ -35,7 +35,7 @@ module Form = | FirstNameChanged s -> { model with FirstName = s } | LastNameChanged s -> { model with LastName = s } - let program = Program.ForComponent.stateful init update + let program = Program.stateful init update module App = let view () = @@ -44,7 +44,7 @@ module App = (VStack(spacing = 25.) { Label("App") - MvuComponent(Counter.program) { + Component(Counter.program) { let! model = Mvu.State VStack() { @@ -54,7 +54,7 @@ module App = } } - MvuComponent(Form.program) { + Component(Form.program) { let! model = Mvu.State VStack() { diff --git a/samples/Components/SimpleCounter/App.fs b/samples/Components/SimpleCounter/App.fs index 0c35f0c..804ea2f 100644 --- a/samples/Components/SimpleCounter/App.fs +++ b/samples/Components/SimpleCounter/App.fs @@ -7,9 +7,9 @@ open Microsoft.Maui.Hosting open type Fabulous.Maui.View module App = - let view = + let view () = Component() { - let! count = State(0) + let! count = Context.State(0) Application( ContentPage( diff --git a/samples/Components/TicTacComponent/App.fs b/samples/Components/TicTacComponent/App.fs index 8641dd2..f6fd922 100644 --- a/samples/Components/TicTacComponent/App.fs +++ b/samples/Components/TicTacComponent/App.fs @@ -188,8 +188,8 @@ module App = (cell = Empty) && (getGameResult model = StillPlaying) let program = - Program.ForComponent.stateful init update - |> Program.ForComponent.withSubscription(fun _ -> + Program.stateful init update + |> Program.withSubscription(fun _ -> Cmd.ofSub(fun dispatch -> DeviceDisplay.MainDisplayInfoChanged.Add(fun args -> let size = @@ -201,7 +201,7 @@ module App = /// The dynamic 'view' function giving the updated content for the view let view () = - MvuComponent(program) { + Component(program) { let! model = Mvu.State Application( diff --git a/samples/HelloWorld/App.fs b/samples/HelloWorld/App.fs index 578d06e..a803f01 100644 --- a/samples/HelloWorld/App.fs +++ b/samples/HelloWorld/App.fs @@ -1,191 +1,14 @@ namespace HelloWorld -open Fabulous open Fabulous.Maui -open Microsoft.Maui.Controls -open Microsoft.Maui.Graphics open type Fabulous.Maui.View - -module Components = - type Fabulous.Maui.View with - - static member inline SimpleComponent() = - Component() { Label("Hello Component").centerHorizontal() } - - static member inline Counter() = - Component() { - let! count = State(0) - - VStack() { - Label($"Count is {count.Current}").centerHorizontal() - - Button("Increment", (fun () -> count.Set(count.Current + 1))) - Button("Decrement", (fun () -> count.Set(count.Current - 1))) - } - } - - static member inline ParentChild_Child(count: int) = - Component() { - let! multiplier = State(1) - let countMultiplied = count * multiplier.Current - - VStack() { - Label($"Count * {multiplier.Current} = {countMultiplied}").centerHorizontal() - - Button("Increment Multiplier", (fun () -> multiplier.Set(multiplier.Current + 1))) - Button("Decrement Multiplier", (fun () -> multiplier.Set(multiplier.Current - 1))) - } - } - - static member inline ParentChild_Parent() = - Component() { - let! count = State(1) - - VStack() { - Label($"Count is {count.Current}").centerHorizontal() - - Button("Increment Count", (fun () -> count.Set(count.Current + 1))) - Button("Decrement Count", (fun () -> count.Set(count.Current - 1))) - - View.ParentChild_Child(count.Current) - } - } - - static member inline Child(count: Binding) = - Component() { - let! boundCount = count - - VStack() { - Label($"Child.Count is {boundCount.Current}").centerHorizontal() - - Button("Increment", (fun () -> boundCount.Set(boundCount.Current + 1))) - Button("Decrement", (fun () -> boundCount.Set(boundCount.Current - 1))) - } - } - - static member inline BindingBetweenParentAndChild() = - Component() { - let! count = State(0) - - VStack() { - Label($"Parent.Count is {count.Current}").centerHorizontal() - - Button("Increment", (fun () -> count.Set(count.Current + 1))) - Button("Decrement", (fun () -> count.Set(count.Current - 1))) - - View.Child(``$`` count) - } - } - - static member inline SharedContextBetweenComponents() = - Component() { - let sharedContext = ComponentContext() - - VStack() { - View.Counter().withContext(sharedContext) - - View.Counter().withContext(sharedContext) - } - } - - static member inline ModifiersOnComponent() = - Component() { - let! toggle = State(false) - - VStack() { - Button("Toggle", (fun () -> toggle.Set(not toggle.Current))) - - View - .SimpleComponent() - .background(SolidColorBrush(if toggle.Current then Colors.Red else Colors.Blue)) - .padding(5.) - .textColor(Colors.White) - } - } +open Microsoft.Maui.Hosting module App = - open Components - - open type Fabulous.Maui.View - - let app () = - Component() { - let! appState = State(0) - - Application( - ContentPage( - ScrollView( - (VStack(spacing = 40.) { - // App state display - VStack(spacing = 20.) { - Label("App state").centerHorizontal().font(attributes = FontAttributes.Bold) - - VStack(spacing = 0.) { - Label($"AppState = {appState.Current}").centerHorizontal() - - Button("Increment", (fun () -> appState.Set(appState.Current + 1))) - Button("Decrement", (fun () -> appState.Set(appState.Current - 1))) - } - } - - // Simple component - VStack(spacing = 20.) { - Label("Simple component") - .centerHorizontal() - .font(attributes = FontAttributes.Bold) - - SimpleComponent() - } - - // Simple component with state - VStack(spacing = 20.) { - Label("Simple components with individual states") - .centerHorizontal() - .font(attributes = FontAttributes.Bold) - - Counter() - Counter() - } - - // Parent child component - VStack(spacing = 20.) { - Label("Parent child component") - .centerHorizontal() - .font(attributes = FontAttributes.Bold) - - ParentChild_Parent() - } - - // Binding between parent and child - VStack(spacing = 20.) { - Label("Binding between parent and child") - .centerHorizontal() - .font(attributes = FontAttributes.Bold) - - BindingBetweenParentAndChild() - } - - // Shared context between components - VStack(spacing = 20.) { - Label("Shared context between components") - .centerHorizontal() - .font(attributes = FontAttributes.Bold) - - SharedContextBetweenComponents() - } - - // Modifiers on component - VStack(spacing = 20.) { - Label("Modifiers on component") - .centerHorizontal() - .font(attributes = FontAttributes.Bold) + let view () = + Application(ContentPage(Label("Hello World").center())) - ModifiersOnComponent() - } - }) - .centerVertical() - ) - ) - ) - } +type MauiProgram = + static member CreateMauiApp() = + MauiApp.CreateBuilder().UseFabulousApp(App.view).Build() diff --git a/samples/HelloWorld/HelloWorld.fsproj b/samples/HelloWorld/HelloWorld.fsproj index 543a7ee..297ba7d 100644 --- a/samples/HelloWorld/HelloWorld.fsproj +++ b/samples/HelloWorld/HelloWorld.fsproj @@ -33,7 +33,6 @@ - diff --git a/samples/HelloWorld/MauiProgram.fs b/samples/HelloWorld/MauiProgram.fs deleted file mode 100644 index 57d2dff..0000000 --- a/samples/HelloWorld/MauiProgram.fs +++ /dev/null @@ -1,16 +0,0 @@ -namespace HelloWorld - -open Microsoft.Maui.Hosting -open Fabulous.Maui - -type MauiProgram = - static member CreateMauiApp() = - MauiApp - .CreateBuilder() - .UseFabulousApp(App.view()) - .ConfigureFonts(fun fonts -> - fonts - .AddFont("OpenSans-Regular.ttf", "OpenSansRegular") - .AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold") - |> ignore) - .Build() diff --git a/samples/Playground/App.fs b/samples/Playground/App.fs index 5791e6b..baf8340 100644 --- a/samples/Playground/App.fs +++ b/samples/Playground/App.fs @@ -1,5 +1,6 @@ namespace Playground +open Fabulous open Fabulous.Maui open type Fabulous.Maui.View @@ -24,11 +25,11 @@ module App = | TextChanged _ -> model | FocusChanged(field, isFocused) -> if isFocused then - { model with Focus = Some field } + { Focus = Some field } else - { model with Focus = None } + { Focus = None } - | SetFocus field -> { model with Focus = field } + | SetFocus field -> { Focus = field } let focusChanged field args = FocusChanged(field, args) @@ -61,4 +62,4 @@ module App = ) ) - let program = Program.stateful init update view + let program = Program.stateful init update |> Program.withView view diff --git a/samples/TicTacToe/App.fs b/samples/TicTacToe/App.fs index 5e28f21..76213a3 100644 --- a/samples/TicTacToe/App.fs +++ b/samples/TicTacToe/App.fs @@ -250,7 +250,7 @@ module App = ) let program = - Program.stateful init update view + Program.stateful init update |> Program.withSubscription(fun _ -> Cmd.ofSub(fun dispatch -> DeviceDisplay.MainDisplayInfoChanged.Add(fun args -> @@ -259,3 +259,4 @@ module App = / DeviceDisplay.MainDisplayInfo.Density dispatch(VisualBoardSizeChanged size)))) + |> Program.withView view diff --git a/src/Fabulous.MauiControls/Program.fs b/src/Fabulous.MauiControls/Program.fs index d51cd8b..073da7b 100644 --- a/src/Fabulous.MauiControls/Program.fs +++ b/src/Fabulous.MauiControls/Program.fs @@ -82,34 +82,6 @@ module Program = let stateless (view: unit -> WidgetBuilder) : Program = Program.stateful (fun _ -> ()) (fun _ _ -> ()) |> withView view - /// Start the program - let startApplicationWithArgs<'arg, 'model, 'msg, 'marker when 'marker :> IFabApplication> - (arg: 'arg) - (program: Program<'arg, 'model, 'msg, 'marker>) - : Application = - let view = - View.Component(program.Program, arg) { - let! model = Mvu.State - program.View model - } - - let widget = view.Compile() - - let treeContext: ViewTreeContext = - { CanReuseView = program.CanReuseView - Logger = program.Program.Logger - Dispatch = ignore - GetViewNode = ViewNode.get - GetComponent = Component.get } - - let def = WidgetDefinitionStore.get widget.Key - let struct (_, view) = def.CreateView(widget, treeContext, ValueNone) - - unbox view - - /// Start the program - let startApplication (program: Program) : Application = startApplicationWithArgs () program - /// Trace all the view updates to the debug output let withViewTrace (trace: string * string -> unit) (program: Program<'arg, 'model, 'msg, 'marker>) = let traceView model = From 85af6a30859409ab9d65d8f004db2de469b2f4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Tue, 16 Jan 2024 09:48:06 +0100 Subject: [PATCH 4/6] Update template --- src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj | 2 +- templates/content/blank/.template.config/template.json | 2 +- templates/content/blank/App.fs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj b/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj index 4b34cbf..d45ab47 100644 --- a/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj +++ b/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj @@ -155,7 +155,7 @@ This version will be used as the lower bound in the NuGet package --> - + diff --git a/templates/content/blank/.template.config/template.json b/templates/content/blank/.template.config/template.json index dd5f0fc..e50add5 100644 --- a/templates/content/blank/.template.config/template.json +++ b/templates/content/blank/.template.config/template.json @@ -46,7 +46,7 @@ "type": "parameter", "dataType": "string", "replaces": "FabulousPkgVersion", - "defaultValue": "2.4.0" + "defaultValue": "2.5.0-pre3" }, "FabulousMauiControlsPkgVersion": { "type": "parameter", diff --git a/templates/content/blank/App.fs b/templates/content/blank/App.fs index 778e295..911ceb3 100644 --- a/templates/content/blank/App.fs +++ b/templates/content/blank/App.fs @@ -65,4 +65,4 @@ module App = ) ) - let program = Program.statefulWithCmdMsg init update view mapCmd + let program = Program.statefulWithCmdMsg init update mapCmd |> Program.withView view From 9ed734ad616b77a63f9158afe22aed7ea0030c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Tue, 16 Jan 2024 12:29:33 +0100 Subject: [PATCH 5/6] Update changelog --- CHANGELOG.md | 8 +++++++- Fabulous.MauiControls.sln | 6 ------ src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj | 5 +---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6abd299..a437563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 _No unreleased changes_ +## [8.1.0-pre4] - 2024-01-16 + +### Changed +- Use Fabulous 2.5.0-pre3 + ## [8.1.0-pre3] - 2024-01-10 ### Changed @@ -170,7 +175,8 @@ Essentially v2.8.1 and v8.0.0 are similar except for the required .NET version. ### Changed - Fabulous.MauiControls has moved from the Fabulous repository to its own repository: [https://github.com/fabulous-dev/Fabulous.MauiControls](https://github.com/fabulous-dev/Fabulous.MauiControls) -[unreleased]: https://github.com/fabulous-dev/Fabulous.MauiControls/compare/8.1.0-pre3...HEAD +[unreleased]: https://github.com/fabulous-dev/Fabulous.MauiControls/compare/8.1.0-pre4...HEAD +[8.1.0-pre4]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/8.1.0-pre4 [8.1.0-pre3]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/8.1.0-pre3 [8.1.0-pre2]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/8.1.0-pre2 [8.1.0-pre1]: https://github.com/fabulous-dev/Fabulous.MauiControls/releases/tag/8.1.0-pre1 diff --git a/Fabulous.MauiControls.sln b/Fabulous.MauiControls.sln index 353c6b2..c209e67 100644 --- a/Fabulous.MauiControls.sln +++ b/Fabulous.MauiControls.sln @@ -35,8 +35,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Files", "_Solutio EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Gallery", "samples\Gallery\Gallery.fsproj", "{A28D6852-F21C-4A43-93AF-CC71050028A9}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fabulous", "..\Fabulous\src\Fabulous\Fabulous.fsproj", "{8BE1CC6B-2F37-43DD-B33D-F61DF161A68F}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Components", "Components", "{D3D3AA5A-1A1B-4E2F-92D7-491706251302}" EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "HelloComponent", "samples\Components\HelloComponent\HelloComponent.fsproj", "{D16A0753-18F6-4913-803E-DC20E0B08F3A}" @@ -96,10 +94,6 @@ Global {A28D6852-F21C-4A43-93AF-CC71050028A9}.Release|Any CPU.Build.0 = Release|Any CPU {A28D6852-F21C-4A43-93AF-CC71050028A9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {A28D6852-F21C-4A43-93AF-CC71050028A9}.Release|Any CPU.Deploy.0 = Release|Any CPU - {8BE1CC6B-2F37-43DD-B33D-F61DF161A68F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8BE1CC6B-2F37-43DD-B33D-F61DF161A68F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8BE1CC6B-2F37-43DD-B33D-F61DF161A68F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8BE1CC6B-2F37-43DD-B33D-F61DF161A68F}.Release|Any CPU.Build.0 = Release|Any CPU {D16A0753-18F6-4913-803E-DC20E0B08F3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D16A0753-18F6-4913-803E-DC20E0B08F3A}.Debug|Any CPU.Build.0 = Debug|Any CPU {D16A0753-18F6-4913-803E-DC20E0B08F3A}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj b/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj index d45ab47..590d9fb 100644 --- a/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj +++ b/src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj @@ -1,6 +1,6 @@ - true + false net8.0 true @@ -160,7 +160,4 @@ - - - From be6e3523f5528b714c378c83e2b347a700720782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9=20Larivi=C3=A8re?= Date: Tue, 16 Jan 2024 12:40:02 +0100 Subject: [PATCH 6/6] Fix unit tests --- Directory.Packages.props | 2 +- src/Fabulous.MauiControls.Tests/WidgetTests.fs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1d61696..bb52cdc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ - + diff --git a/src/Fabulous.MauiControls.Tests/WidgetTests.fs b/src/Fabulous.MauiControls.Tests/WidgetTests.fs index affcee5..a1da71c 100644 --- a/src/Fabulous.MauiControls.Tests/WidgetTests.fs +++ b/src/Fabulous.MauiControls.Tests/WidgetTests.fs @@ -54,15 +54,14 @@ type WidgetTests() = let treeContext: ViewTreeContext = { CanReuseView = MauiViewHelpers.canReuseView GetViewNode = ViewNode.get - Logger = ProgramHelpers.defaultLogger() - Dispatch = dispatch } - - let envContext = new EnvironmentContext() + Logger = ProgramDefaults.defaultLogger() + Dispatch = dispatch + GetComponent = Component.get } let navPage = FabNavigationPage() let weakRef = WeakReference(navPage) - let node = ViewNode(None, treeContext, envContext, weakRef) + let node = ViewNode(None, treeContext, weakRef) Reconciler.update treeContext.CanReuseView ValueNone (oldWidget.Compile()) node Reconciler.update treeContext.CanReuseView (ValueSome(oldWidget.Compile())) (newWidget.Compile()) node