Skip to content

Commit

Permalink
Merge pull request #62 from fabulous-dev/window-support
Browse files Browse the repository at this point in the history
Add support for Window widget
  • Loading branch information
edgarfgp committed Feb 26, 2024
2 parents 3574dd8 + 5932890 commit 6186342
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 36 deletions.
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
<PackageVersion Include="NUnit" Version="3.14.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="FSharp.Maui.WinUICompat" Version="1.1.0" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="8.0.7" />
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.7" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="8.0.6" />
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
Expand Down
82 changes: 51 additions & 31 deletions samples/Playground/App.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,55 +11,75 @@ module App =
| Entry2
| Entry3

type Model = { Focus: FieldFocused option }
type Model =
{ Focus: FieldFocused option
WindowOpened: bool }

type Msg =
| TextChanged of string
| FocusChanged of FieldFocused * bool
| SetFocus of FieldFocused option
| OpenWindow
| CloseWindow

let init () = { Focus = None }
let init () = { Focus = None; WindowOpened = false }

let update msg model =
match msg with
| TextChanged _ -> model
| FocusChanged(field, isFocused) ->
if isFocused then
{ Focus = Some field }
{ model with Focus = Some field }
else
{ Focus = None }
{ model with Focus = None }

| SetFocus field -> { Focus = field }
| SetFocus field -> { model with Focus = field }
| OpenWindow -> { model with WindowOpened = true }
| CloseWindow -> { model with WindowOpened = false }

let focusChanged field args = FocusChanged(field, args)

let view model =
Application(
ContentPage(
(VStack(spacing = 20.) {
let text =
match model.Focus with
| None -> "None"
| Some f -> f.ToString()

Label($"Field currently selected: {text}")

Entry("Entry1", TextChanged)
.focus(model.Focus = Some Entry1, focusChanged Entry1)

Entry("Entry2", TextChanged)
.focus(model.Focus = Some Entry2, focusChanged Entry2)

Entry("Entry3", TextChanged)
.focus(model.Focus = Some Entry3, focusChanged Entry3)

Button("Set focus on Entry1", SetFocus(Some Entry1))
Button("Set focus on Entry2", SetFocus(Some Entry2))
Button("Set focus on Entry3", SetFocus(Some Entry3))
Button("Unfocus", SetFocus None)
})
.margin(20.)
Application() {
Window(
ContentPage(
(VStack(spacing = 20.) {
let text =
match model.Focus with
| None -> "None"
| Some f -> f.ToString()

Label($"Field currently selected: {text}")

Entry("Entry1", TextChanged)
.focus(model.Focus = Some Entry1, focusChanged Entry1)

Entry("Entry2", TextChanged)
.focus(model.Focus = Some Entry2, focusChanged Entry2)

Entry("Entry3", TextChanged)
.focus(model.Focus = Some Entry3, focusChanged Entry3)

Button("Set focus on Entry1", SetFocus(Some Entry1))
Button("Set focus on Entry2", SetFocus(Some Entry2))
Button("Set focus on Entry3", SetFocus(Some Entry3))
Button("Unfocus", SetFocus None)
Button("Open window", OpenWindow)
})
.margin(20.)
)
)
)

if model.WindowOpened then
Window(
ContentPage(
(VStack(spacing = 20.) {
Label("Window opened")
Button("Close window", CloseWindow)
})
.margin(20.)
)
)
}

let program = Program.stateful init update |> Program.withView view
1 change: 1 addition & 0 deletions src/Fabulous.MauiControls/Fabulous.MauiControls.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
<Compile Include="Views\Collections\ListView.fs" />
<Compile Include="Views\Collections\CollectionView.fs" />
<Compile Include="Views\Collections\CarouselView.fs" />
<Compile Include="Views\Window.fs" />
<Compile Include="Views\Application.fs" />
<Compile Include="Views\Any.fs" />
<Compile Include="Views\_Semantics.fs" />
Expand Down
37 changes: 34 additions & 3 deletions src/Fabulous.MauiControls/Views/Application.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
namespace Fabulous.Maui

open System
open System.Reflection
open System.Collections.Generic
open System.Runtime.CompilerServices
open Fabulous
open Fabulous.Maui
open Fabulous.StackAllocatedCollections
open Microsoft.Maui.Controls
open Microsoft.Maui.ApplicationModel

Expand All @@ -18,6 +20,11 @@ type FabApplication() =
let resume = Event<EventHandler, EventArgs>()
let appLinkRequestReceived = Event<EventHandler<Uri>, Uri>()

let windows = List<Window>()

member this.Windows = windows
member this.EditableWindows = windows

[<CLIEvent>]
member _.Start = start.Publish

Expand All @@ -39,6 +46,16 @@ type FabApplication() =
override this.OnAppLinkRequestReceived(uri) =
appLinkRequestReceived.Trigger(this, uri)

override this.CreateWindow(activationState) = windows[0]

override this.OpenWindow(window) =
windows.Add(window)
base.OpenWindow(window)

override this.CloseWindow(window) =
windows.Remove(window) |> ignore
base.CloseWindow(window)

module Application =
let WidgetKey = Widgets.register<FabApplication>()

Expand Down Expand Up @@ -84,6 +101,9 @@ module Application =

application.UserAppTheme <- value)

let Windows =
Attributes.defineListWidgetCollection "Application_Windows" (fun target -> (target :?> FabApplication).EditableWindows)

[<AutoOpen>]
module ApplicationBuilders =
type Fabulous.Maui.View with
Expand All @@ -93,8 +113,9 @@ module ApplicationBuilders =
static member inline Application(mainPage: WidgetBuilder<'msg, #IFabPage>) =
WidgetHelpers.buildWidgets<'msg, IFabApplication> Application.WidgetKey [| Application.MainPage.WithValue(mainPage.Compile()) |]

static member inline Application<'msg, 'childMarker>() =
SingleChildBuilder<'msg, IFabApplication, 'childMarker>(Application.WidgetKey, Application.MainPage)
/// <summary>Create an Application widget with a list of windows</summary>
static member inline Application<'msg, 'itemMarker when 'itemMarker :> IFabWindow>() =
CollectionBuilder<'msg, IFabApplication, 'itemMarker>(Application.WidgetKey, Application.Windows)

[<Extension>]
type ApplicationModifiers =
Expand Down Expand Up @@ -174,3 +195,13 @@ type ApplicationModifiers =
[<Extension>]
static member inline reference(this: WidgetBuilder<'msg, IFabApplication>, value: ViewRef<Application>) =
this.AddScalar(ViewRefAttributes.ViewRef.WithValue(value.Unbox))

[<Extension>]
type ApplicationYieldExtensions =
[<Extension>]
static member inline Yield<'msg, 'marker, 'itemType when 'marker :> IFabApplication and 'itemType :> IFabWindow>
(
_: CollectionBuilder<'msg, 'marker, IFabWindow>,
x: WidgetBuilder<'msg, 'itemType>
) : Content<'msg> =
{ Widgets = MutStackArray1.One(x.Compile()) }
154 changes: 154 additions & 0 deletions src/Fabulous.MauiControls/Views/Window.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
namespace Fabulous.Maui

open System.Runtime.CompilerServices
open Microsoft.Maui
open Microsoft.Maui.Controls
open Fabulous
open Fabulous.StackAllocatedCollections.StackList
open Fabulous.Maui

type IFabWindow =
inherit IFabNavigableElement

module Window =
let WidgetKey = Widgets.register<Window>()

let Page = Attributes.defineBindableWidget Window.PageProperty

let FlowDirection =
Attributes.defineBindableWithEquality Window.FlowDirectionProperty

let Height = Attributes.defineBindableWithEquality Window.HeightProperty

let MaximumHeight =
Attributes.defineBindableWithEquality Window.MaximumHeightProperty

let MaximumWidth = Attributes.defineBindableWithEquality Window.MaximumWidthProperty

let MinimumHeight =
Attributes.defineBindableWithEquality Window.MinimumHeightProperty

let MinimumWidth = Attributes.defineBindableWithEquality Window.MinimumWidthProperty

let Activated =
Attributes.defineEventNoArg "Window_Activated" (fun target -> (target :?> Window).Activated)

let Backgrounding =
Attributes.defineEvent "Window_Backgrounding" (fun target -> (target :?> Window).Backgrounding)

let Created =
Attributes.defineEventNoArg "Window_Created" (fun target -> (target :?> Window).Created)

let Deactivated =
Attributes.defineEventNoArg "Window_Deactivated" (fun target -> (target :?> Window).Deactivated)

let Destroying =
Attributes.defineEventNoArg "Window_Destroying" (fun target -> (target :?> Window).Destroying)

let DisplayDensityChanged =
Attributes.defineEvent "Window_DisplayDensityChanged" (fun target -> (target :?> Window).DisplayDensityChanged)

let SizeChanged =
Attributes.defineEventNoArg "Window_SizeChanged" (fun target -> (target :?> Window).SizeChanged)

let Resumed =
Attributes.defineEventNoArg "Window_Resumed" (fun target -> (target :?> Window).Resumed)

let Stopped =
Attributes.defineEventNoArg "Window_Stopped" (fun target -> (target :?> Window).Stopped)

let Title = Attributes.defineBindableWithEquality Window.TitleProperty

let Width = Attributes.defineBindableWithEquality Window.WidthProperty

let X = Attributes.defineBindableWithEquality Window.XProperty

let Y = Attributes.defineBindableWithEquality Window.YProperty

[<AutoOpen>]
module WindowBuilders =
type Fabulous.Maui.View with

static member inline Window(content: WidgetBuilder<'msg, #IFabPage>) =
WidgetBuilder<'msg, IFabWindow>(
Window.WidgetKey,
AttributesBundle(StackList.empty(), ValueSome [| Window.Page.WithValue(content.Compile()) |], ValueNone)
)

[<Extension>]
type WindowModifiers =
[<Extension>]
static member inline flowDirection(this: WidgetBuilder<'msg, #IFabWindow>, value: FlowDirection) =
this.AddScalar(Window.FlowDirection.WithValue(value))

[<Extension>]
static member inline height(this: WidgetBuilder<'msg, #IFabWindow>, value: double) =
this.AddScalar(Window.Height.WithValue(value))

[<Extension>]
static member inline maximumHeight(this: WidgetBuilder<'msg, #IFabWindow>, value: double) =
this.AddScalar(Window.MaximumHeight.WithValue(value))

[<Extension>]
static member inline maximumWidth(this: WidgetBuilder<'msg, #IFabWindow>, value: double) =
this.AddScalar(Window.MaximumWidth.WithValue(value))

[<Extension>]
static member inline minimumHeight(this: WidgetBuilder<'msg, #IFabWindow>, value: double) =
this.AddScalar(Window.MinimumHeight.WithValue(value))

[<Extension>]
static member inline minimumWidth(this: WidgetBuilder<'msg, #IFabWindow>, value: double) =
this.AddScalar(Window.MinimumWidth.WithValue(value))

[<Extension>]
static member inline onActivated(this: WidgetBuilder<'msg, #IFabWindow>, msg: 'msg) =
this.AddScalar(Window.Activated.WithValue(MsgValue msg))

[<Extension>]
static member inline onBackgrounding(this: WidgetBuilder<'msg, #IFabWindow>, fn: BackgroundingEventArgs -> 'msg) =
this.AddScalar(Window.Backgrounding.WithValue(fn))

[<Extension>]
static member inline onCreated(this: WidgetBuilder<'msg, #IFabWindow>, msg: 'msg) =
this.AddScalar(Window.Created.WithValue(MsgValue msg))

[<Extension>]
static member inline onDeactivated(this: WidgetBuilder<'msg, #IFabWindow>, msg: 'msg) =
this.AddScalar(Window.Deactivated.WithValue(MsgValue msg))

[<Extension>]
static member inline onDestroying(this: WidgetBuilder<'msg, #IFabWindow>, msg: 'msg) =
this.AddScalar(Window.Destroying.WithValue(MsgValue msg))

[<Extension>]
static member inline onDisplayDensityChanged(this: WidgetBuilder<'msg, #IFabWindow>, fn: DisplayDensityChangedEventArgs -> 'msg) =
this.AddScalar(Window.DisplayDensityChanged.WithValue(fn))

[<Extension>]
static member inline onSizeChanged(this: WidgetBuilder<'msg, #IFabWindow>, msg: 'msg) =
this.AddScalar(Window.SizeChanged.WithValue(MsgValue msg))

[<Extension>]
static member inline onResumed(this: WidgetBuilder<'msg, #IFabWindow>, msg: 'msg) =
this.AddScalar(Window.Resumed.WithValue(MsgValue msg))

[<Extension>]
static member inline onStopped(this: WidgetBuilder<'msg, #IFabWindow>, msg: 'msg) =
this.AddScalar(Window.Stopped.WithValue(MsgValue msg))

[<Extension>]
static member inline title(this: WidgetBuilder<'msg, #IFabWindow>, value: string) =
this.AddScalar(Window.Title.WithValue(value))

[<Extension>]
static member inline width(this: WidgetBuilder<'msg, #IFabWindow>, value: double) =
this.AddScalar(Window.Width.WithValue(value))

[<Extension>]
static member inline x(this: WidgetBuilder<'msg, #IFabWindow>, value: double) =
this.AddScalar(Window.X.WithValue(value))

[<Extension>]
static member inline y(this: WidgetBuilder<'msg, #IFabWindow>, value: double) =
this.AddScalar(Window.Y.WithValue(value))

0 comments on commit 6186342

Please sign in to comment.