Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/controls/keybinding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# KeyBinding

> _Note_: You can check the Avalonia docs for [KeyBinding](https://docs.avaloniaui.net/docs/concepts/input/binding-key-and-mouse) if you need more information.
>
> For Avalonia.FuncUI's DSL properties you can check [KeyBinding.fs](https://github.com/AvaloniaCommunity/Avalonia.FuncUI/blob/master/src/Avalonia.FuncUI.DSL/KeyBinding.fs).

A key binding allows you to dispatch a message (or trigger some other action) when the user presses a key on the keyboard. For example:

```fsharp
KeyBinding.create [
KeyBinding.key Key.F11
KeyBinding.execute (fun _ ->
FullScreen true
|> dispatch)
]
```

### Usage

**Create a Key Binding**

```fsharp
KeyBinding.create []
```

**Bind to a Specific Key**

```fsharp
KeyBinding.key Key.Escape
```

**Trigger a Message**

```fsharp
KeyBinding.execute (fun _ ->
dispatch MyMessage)
```
64 changes: 64 additions & 0 deletions docs/controls/window.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Window

> _Note_: You can check the Avalonia docs for the [Window](https://docs.avaloniaui.net/docs/controls/window) and [Window API](http://reference.avaloniaui.net/api/Avalonia.Controls/Window/) if you need more information.
>
> For Avalonia.FuncUI's DSL properties you can check [Window.fs](https://github.com/AvaloniaCommunity/Avalonia.FuncUI/blob/master/src/Avalonia.FuncUI.DSL/Window.fs).

Window corresponds to your application's host window, and allows you to set top-level properties like window title and key bindings.

### Usage

**Create a Window**

```fsharp
Window.create []
```

**Set Window Title**

```fsharp
Window.title "My Application"
```

**Set Window Icon**

```fsharp
let icon =
Path.Combine("Assets", "Icons", "icon.ico")
|> WindowIcon

Window.icon icon
```

**Size Window to Content**

```fsharp
Window.sizeToContent SizeToContent.WidthAndHeight
```

**Switch to Full-Screen Mode**

```fsharp
Window.windowState (
if state.FullScreen then WindowState.FullScreen
else WindowState.Normal)
```

**Create Key Bindings**

```fsharp
Window.keyBindings [
KeyBinding.create [
KeyBinding.key Key.F11
KeyBinding.execute (fun _ ->
FullScreen true
|> dispatch)
]
KeyBinding.create [
KeyBinding.key Key.Escape
KeyBinding.execute (fun _ ->
FullScreen false
|> dispatch)
]
]
```
12 changes: 10 additions & 2 deletions src/Avalonia.FuncUI.Elmish/Library.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace Avalonia.FuncUI.Elmish

open Elmish
open Avalonia.Controls
open Avalonia.FuncUI.DSL
open Avalonia.FuncUI.Hosts
open Avalonia.FuncUI.Types
open Avalonia.Threading
Expand All @@ -15,8 +17,14 @@ module Program =

if stateDiffers then
stateRef.Value <- Some state
let view = ((Program.view program) state dispatch)
host.Update (Some (view :> IView))
let userView = ((Program.view program) state dispatch) :> IView

match userView with
| :? IView<Avalonia.Controls.Window> as windowView ->
host.Update(Some windowView)
| controlView ->
let wrappedView = Window.create [ Window.child controlView ]
host.Update(Some wrappedView)

program
|> Program.withSetState setState
Expand Down
9 changes: 8 additions & 1 deletion src/Avalonia.FuncUI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solutio
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
NuGet.config = NuGet.config
..\README.md = ..\README.md
..\.github\workflows\publish.yml = ..\.github\workflows\publish.yml
..\.github\workflows\pull-requests.yml = ..\.github\workflows\pull-requests.yml
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Examples.InlineText", "Examples\Component Examples\Examples.InlineText\Examples.InlineText.fsproj", "{B8D8C84B-05AD-475B-BE81-A30544CE0149}"
Expand Down Expand Up @@ -90,6 +90,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Examples.Elmish.Tetris", "E
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Examples.SettingFocus", "Examples\Examples.SettingFocus\Examples.SettingFocus.fsproj", "{2C7F4542-65CE-4EC6-9876-C1C2215EE006}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Examples.Elmish.HelloWorld", "Examples\Elmish Examples\Examples.Elmish.HelloWorld\Examples.Elmish.HelloWorld.fsproj", "{72F1A6FA-492E-0E7A-08CF-93C5737B11BB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -212,6 +214,10 @@ Global
{2C7F4542-65CE-4EC6-9876-C1C2215EE006}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C7F4542-65CE-4EC6-9876-C1C2215EE006}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C7F4542-65CE-4EC6-9876-C1C2215EE006}.Release|Any CPU.Build.0 = Release|Any CPU
{72F1A6FA-492E-0E7A-08CF-93C5737B11BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72F1A6FA-492E-0E7A-08CF-93C5737B11BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72F1A6FA-492E-0E7A-08CF-93C5737B11BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72F1A6FA-492E-0E7A-08CF-93C5737B11BB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -249,6 +255,7 @@ Global
{6D2C62FC-5634-4997-AF1F-2E8A5D27E117} = {84811DB3-C276-4F0D-B3BA-78B88E2C6EF0}
{EC63B886-E809-4B74-B533-BFF3D60017C9} = {6D2C62FC-5634-4997-AF1F-2E8A5D27E117}
{2C7F4542-65CE-4EC6-9876-C1C2215EE006} = {F50826CE-D9BC-45CF-A110-C42225B75AD3}
{72F1A6FA-492E-0E7A-08CF-93C5737B11BB} = {F6F4AAF7-2BDA-4D2F-B78D-F6D8A03F660E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4630E817-6780-4C98-9379-EA3B45224339}
Expand Down
2 changes: 2 additions & 0 deletions src/Avalonia.FuncUI/Avalonia.FuncUI.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@
<Compile Include="DSL\DataGrid.fs" />
<Compile Include="DSL\PathIcon.fs" />
<Compile Include="DSL\DragDrop.fs" />
<Compile Include="DSL\Window.fs" />
<Compile Include="DSL\KeyBinding.fs" />
</ItemGroup>

<ItemGroup>
Expand Down
54 changes: 54 additions & 0 deletions src/Avalonia.FuncUI/DSL/KeyBinding.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace Avalonia.FuncUI.DSL

open System
open System.Windows.Input

open Avalonia.Input
open Avalonia.FuncUI.DSL
open Avalonia.FuncUI.Types
open Avalonia.FuncUI.Builder

[<AutoOpen>]
module KeyBinding =

let create (attrs: IAttr<KeyBinding> list) : IView<KeyBinding> =
ViewBuilder.Create<KeyBinding>(attrs)

let private toCommand action =
let canExecuteChanged = Event<EventHandler, EventArgs>()
{
new ICommand with
member _.CanExecute(_) = true
member _.Execute(parameter) = action parameter
[<CLIEvent>]
member _.CanExecuteChanged =
canExecuteChanged.Publish
}

type KeyBinding with

static member gesture<'t when 't :> KeyBinding> (value: KeyGesture) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<KeyGesture>(KeyBinding.GestureProperty, value, ValueNone)

static member key<'t when 't :> KeyBinding> (value: Key) : IAttr<'t> =
KeyBinding.gesture (KeyGesture(value))

static member command<'t when 't :> KeyBinding> (value: ICommand) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<ICommand>(KeyBinding.CommandProperty, value, ValueNone)

static member execute<'t when 't :> KeyBinding> (value : obj -> unit) : IAttr<'t> =
KeyBinding.command (toCommand value)

static member commandParameter<'t when 't :> KeyBinding> (value: obj) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<obj>(KeyBinding.CommandParameterProperty, value, ValueNone)

type InputElement with

static member keyBindings<'t when 't :> InputElement> (bindings: IView<KeyBinding> list) : IAttr<'t> =
let getter: 't -> obj = fun control -> control.KeyBindings :> obj

AttrBuilder<'t>.CreateContentMultiple(
"KeyBindings",
ValueSome getter,
ValueNone,
bindings |> List.map (fun v -> v :> IView))
64 changes: 64 additions & 0 deletions src/Avalonia.FuncUI/DSL/Window.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
namespace Avalonia.FuncUI.DSL

open Avalonia.Controls
open Avalonia.FuncUI.Types
open Avalonia.FuncUI.Builder

[<AutoOpen>]
module Window =

let create (attrs: IAttr<Window> list) : IView<Window> =
ViewBuilder.Create<Window>(attrs)

type Window with

static member canResize<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.CanResizeProperty, value, ValueNone)

static member child(value: IView) =
AttrBuilder<Window>.CreateContentSingle(Window.ContentProperty, Some value)

static member closingBehavior<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.ClosingBehaviorProperty, value, ValueNone)

static member extendClientAreaChromeHints<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.ExtendClientAreaChromeHintsProperty, value, ValueNone)

static member extendClientAreaTitleBarHeightHint<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.ExtendClientAreaTitleBarHeightHintProperty, value, ValueNone)

static member extendClientAreaToDecorationsHint<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.ExtendClientAreaToDecorationsHintProperty, value, ValueNone)

static member isExtendedIntoWindowDecorations<'t when 't :> Window>(func, ?subPatchOptions) =
AttrBuilder<'t>.CreateSubscription(Window.IsExtendedIntoWindowDecorationsProperty, func, ?subPatchOptions = subPatchOptions)

static member icon<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.IconProperty, value, ValueNone)

static member offScreenMargin<'t when 't :> Window>(func, ?subPatchOptions) =
AttrBuilder<'t>.CreateSubscription(Window.OffScreenMarginProperty, func, ?subPatchOptions = subPatchOptions)

static member showActivated<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.ShowActivatedProperty, value, ValueNone)

static member showInTaskbar<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.ShowInTaskbarProperty, value, ValueNone)

static member sizeToContent<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.SizeToContentProperty, value, ValueNone)

static member systemDecorations<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.SystemDecorationsProperty, value, ValueNone)

static member title<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.TitleProperty, value, ValueNone)

static member windowDecorationMargin<'t when 't :> Window>(func, ?subPatchOptions) =
AttrBuilder<'t>.CreateSubscription(Window.WindowDecorationMarginProperty, func, ?subPatchOptions = subPatchOptions)

static member windowStartupLocation<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.WindowStartupLocationProperty, value, ValueNone)

static member windowState<'t when 't :> Window>(value) =
AttrBuilder<'t>.CreateProperty(Window.WindowStateProperty, value, ValueNone)
46 changes: 25 additions & 21 deletions src/Avalonia.FuncUI/VirtualDom/VirtualDom.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,6 @@ module rec VirtualDom =
Patcher.patch(root, delta)

let updateRoot (host: ContentControl, last: IView option, next: IView option) =
let root : Control voption =
if host.Content <> null then
match host.Content with
| :? Control as control -> ValueSome control
| _ -> ValueNone
else
ValueNone

let delta : ViewDelta voption =
match last with
| Some last ->
Expand All @@ -35,21 +27,33 @@ module rec VirtualDom =
match next with
| Some next -> ViewDelta.From next |> ValueSome
| None -> ValueNone

match host, delta with
| :? Window as windowHost, ValueSome viewDelta when viewDelta.ViewType.IsSubclassOf(typeof<Window>) || viewDelta.ViewType = typeof<Window> ->
Patcher.patch(windowHost, viewDelta)
| _ ->
let root : Control voption =
if host.Content <> null then
match host.Content with
| :? Control as control -> ValueSome control
| _ -> ValueNone
else
ValueNone

match root with
| ValueSome control ->
match delta with
| ValueSome delta ->
match control.GetType () = delta.ViewType && not delta.KeyDidChange with
| true -> Patcher.patch (control, delta)
| false -> host.Content <- Patcher.create delta
| ValueNone ->
host.Content <- null
match root with
| ValueSome control ->
match delta with
| ValueSome delta ->
match control.GetType () = delta.ViewType && not delta.KeyDidChange with
| true -> Patcher.patch (control, delta)
| false -> host.Content <- Patcher.create delta
| ValueNone ->
host.Content <- null

| ValueNone ->
match delta with
| ValueSome delta -> host.Content <- Patcher.create delta
| ValueNone -> host.Content <- null
| ValueNone ->
match delta with
| ValueSome delta -> host.Content <- Patcher.create delta
| ValueNone -> host.Content <- null

// TODO: share code with updateRoot
let internal updateBorderRoot (host: Border, last: IView option, next: IView option) =
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>Examples.CounterApp</RootNamespace>
</PropertyGroup>

<ItemGroup>
<Content Include="Assets\Icons\icon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Compile Include="HelloWorld.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
<ProjectReference Include="..\..\..\Avalonia.FuncUI.Elmish\Avalonia.FuncUI.Elmish.fsproj" />
<ProjectReference Include="..\..\..\Avalonia.FuncUI\Avalonia.FuncUI.fsproj" />
</ItemGroup>

<ItemGroup>
</ItemGroup>

</Project>
Loading