Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bindings #420

Merged
merged 30 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2dce4df
fix classes and styles properties on StyledElement
SilkyFowl Apr 17, 2024
883539d
control catalog: add styles demo back
SilkyFowl Apr 17, 2024
de6c577
fix IStyleHost.styles
SilkyFowl Apr 17, 2024
b61fb25
add tests for `IStyleHost.styles` and `Control.classes` properties.
SilkyFowl Apr 17, 2024
b787bb2
add dataTemplates property.
SilkyFowl Apr 18, 2024
a5942b8
add onPropertyChanged event.
SilkyFowl Apr 18, 2024
d33a7bd
add Net Event Attr functions.
SilkyFowl Apr 18, 2024
c229623
add Visual DSL functions.
SilkyFowl Apr 18, 2024
19b68aa
use `nameof` expression
SilkyFowl Apr 18, 2024
f42c4eb
add Layoutable DSL functions.
SilkyFowl Apr 18, 2024
729ba9f
add InputElement DSL functions.
SilkyFowl Apr 18, 2024
35ee9f0
add Control DSL functions.
SilkyFowl Apr 18, 2024
023dde8
add Inline DSL functions.
SilkyFowl Apr 18, 2024
e3e4dc3
add TextDecoration DSL functions.
SilkyFowl Apr 18, 2024
305d1b8
add TextBlock DSL functions.
SilkyFowl Apr 18, 2024
540dc68
add Image DSL functions.
SilkyFowl Apr 18, 2024
4230295
move stryles DSL into StyledElement.fs
SilkyFowl Apr 19, 2024
11bd931
add Flyout DSL functions.
SilkyFowl Apr 19, 2024
7276821
refactor subscription function if passing event source, to use AddHan…
SilkyFowl Apr 19, 2024
3f00708
add TemplatedControl bindings.
SilkyFowl Apr 19, 2024
86e8af8
add TextBox bindings.
SilkyFowl Apr 19, 2024
392699b
add ItemsControl bindings.
SilkyFowl Apr 19, 2024
7d0d13f
Type parameters Modified to explicitly.
SilkyFowl Apr 19, 2024
410d228
documentation for updating `Classes`' standard classes
SilkyFowl Apr 22, 2024
92e67be
fix isPseudoClass
SilkyFowl Apr 22, 2024
1f1464b
move dataTemplates binding functions to Control.fs
SilkyFowl Apr 22, 2024
7107231
Remove onTextChanged (TextBox.TextChangingEvent -> unit) binding.
SilkyFowl Apr 22, 2024
f75941f
add test for AttrBuilder<'t>.CreateSubscription<'arg>(name, factory, …
SilkyFowl Apr 23, 2024
d05943b
Refactor list / AvaloniaList / IList value bindings
SilkyFowl Apr 23, 2024
a3226c1
fix compare function.
SilkyFowl Apr 23, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,10 @@ module MainView =
TabItem.header "SplitView Demo"
TabItem.content (ViewBuilder.Create<SplitViewDemo.Host>([]))
]
// ToDo: return it back when styles will be worked
//TabItem.create [
// TabItem.header "Styles Demo"
// TabItem.content (ViewBuilder.Create<StylesDemo.Host>([]))
//]
TabItem.create [
TabItem.header "Styles Demo"
TabItem.content (ViewBuilder.Create<StylesDemo.Host>([]))
]
TabItem.create [
TabItem.header "TextBox Demo"
TabItem.content (ViewBuilder.Create<TextBoxDemo.Host>([]))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System">
<Styles.Resources>
<ResourceDictionary>
<!-- FluentTheme has no common FintSize Resources. -->
<sys:Double x:Key="FontSizeSmall">10</sys:Double>
<sys:Double x:Key="FontSizeNormal">12</sys:Double>
<sys:Double x:Key="FontSizeLarge">16</sys:Double>
</ResourceDictionary>
</Styles.Resources>

<Style Selector="Button.round /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="10"/>
</Style>
Expand All @@ -22,9 +31,9 @@
</Style>

<Style Selector="Border.drag">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightAccentBrush}"/>
</Style>
<Style Selector="Border.drop">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
<Setter Property="Background" Value="{DynamicResource SystemControlHighlightAltListAccentMediumBrush}"/>
</Style>
</Styles>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand All @@ -16,6 +16,7 @@
<Compile Include="VirtualDom\VirtualDom.ModuleTests.fs" />
<Compile Include="VirtualDom\VirtualDom.DifferTests.fs" />
<Compile Include="VirtualDom\VirtualDom.PatcherTests.fs" />
<Compile Include="DSL\Base\StyledElementTests.fs" />
<Compile Include="State.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
Expand Down
149 changes: 149 additions & 0 deletions src/Avalonia.FuncUI.UnitTests/DSL/Base/StyledElementTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
namespace Avalonia.FuncUI.UnitTests.DSL

open Avalonia
open Avalonia.Controls
open global.Xunit

module StyledElementTests =
open Avalonia.FuncUI.VirtualDom
open Avalonia.FuncUI.DSL
open Avalonia.FuncUI.Types
open Avalonia.Styling

let twoAttrs<'x, 't> (attr: 'x -> IAttr<'t>) a b =
[ attr a :> IAttr ], [ attr b :> IAttr ]

[<Fact>]
let ``classes equality with string list`` () =
let valueList() = [ "class1"; "class2" ]

let classes1 = valueList()
let classes2 = valueList()

let stringList =
(classes1, classes2)
||> twoAttrs StyledElement.classes
|> Differ.diffAttributes

Assert.Empty stringList

[<Fact>]
let ``classes equality with same classes instance`` () =
let classes = Classes()
classes.Add "class1"
classes.Add "class2"

let sameClassesInstance =
(classes, classes) ||> twoAttrs StyledElement.classes |> Differ.diffAttributes

Assert.Empty sameClassesInstance

[<Fact>]
let ``classes equality with different classes instance`` () =
let classes1 = Classes()
classes1.Add "class1"
classes1.Add "class2"

let classes2 = Classes()
classes2.Add "class1"
classes2.Add "class2"

let differentClassesInstance =
(classes1, classes2) ||> twoAttrs StyledElement.classes |> Differ.diffAttributes

Assert.Empty differentClassesInstance

let initStyle () =
let s = Style(fun x -> x.Is<Control>())
s.Setters.Add(Setter(Control.TagProperty, "foo"))
s :> IStyle

[<Fact>]
let ``styles equality with style list has same style instance`` () =
let style = initStyle ()

let styleList () = [ style ]

let styles1 = styleList ()
let styles2 = styleList ()

let styleList =
(styles1, styles2) ||> twoAttrs StyledElement.styles |> Differ.diffAttributes

Assert.Empty styleList


[<Fact>]
let ``styles equality with style list has different style instance`` () =

let style1 = initStyle ()
let style2 = initStyle ()

let styleList =
([ style1 ], [ style2 ]) ||> twoAttrs StyledElement.styles |> Differ.diffAttributes

match Assert.Single styleList with
| Delta.AttrDelta.Property { Accessor = InstanceProperty { Name = propName }
Value = Some(:? list<IStyle> as [ value ]) } ->
Assert.Equal("Styles", propName)
Assert.NotEqual(style1, value)
Assert.Equal(style2, value)

| _ -> Assert.Fail $"Not expected delta\n{styleList}"

[<Fact>]
let ``styles equality with Styles property has same instance`` () =
let style = initStyle ()

let styles = Styles()
styles.Add style

let styles1 = styles
let styles2 = styles

let styleList =
(styles1, styles2) ||> twoAttrs StyledElement.styles |> Differ.diffAttributes

Assert.Empty styleList

[<Fact>]
let ``styles equality with Styles property has different Styles instance has same style instance`` () =
let style = initStyle ()

let styles1 = Styles()
styles1.Add style
styles1.Resources.Add("key", "value")

let styles2 = Styles()
styles2.Add style
styles2.Resources.Add("key", "value")

let styleList =
(styles1, styles2) ||> twoAttrs StyledElement.styles |> Differ.diffAttributes

Assert.Empty styleList

[<Fact>]
let ``styles equality with Styles property has different Styles instance has different style instance`` () =
let style1 = initStyle ()
let style2 = initStyle ()

let styles1 = Styles()
styles1.Add style1
styles1.Resources.Add("key", "value")

let styles2 = Styles()
styles2.Add style2
styles2.Resources.Add("key", "value")

let styleList =
(styles1, styles2) ||> twoAttrs StyledElement.styles |> Differ.diffAttributes

match Assert.Single styleList with
| Delta.AttrDelta.Property { Accessor = InstanceProperty { Name = propName }
Value = Some(:? Styles as value) } ->
Assert.Equal("Styles", propName)
Assert.NotEqual<Styles>(styles1, value)
Assert.Equal<Styles>(styles2, value)

| _ -> Assert.Fail $"Not expected delta\n{styleList}"
5 changes: 4 additions & 1 deletion src/Avalonia.FuncUI/Avalonia.FuncUI.fsproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand Down Expand Up @@ -63,6 +63,7 @@
<Compile Include="DSL\Base\Layoutable.fs" />
<Compile Include="DSL\Base\Visual.fs" />
<Compile Include="DSL\Base\InputElement.fs" />
<Compile Include="DSL\Base\IDataTemplateHost.fs" />
<Compile Include="DSL\Base\Control.fs" />
<Compile Include="DSL\Base\StyledElement.fs" />
<Compile Include="DSL\Base\Panel.fs" />
Expand Down Expand Up @@ -97,6 +98,8 @@
<Compile Include="DSL\Shapes\Path.fs" />
<Compile Include="DSL\Calendar\Calendar.fs" />
<Compile Include="DSL\Calendar\CalendarDatePicker.fs" />
<Compile Include="DSL\Documents\TextDecoration.fs" />
<Compile Include="DSL\Documents\Inline.fs" />
<Compile Include="DSL\Documents\Run.fs" />
<Compile Include="DSL\Documents\Span.fs" />
<Compile Include="DSL\Documents\Bold.fs" />
Expand Down
14 changes: 14 additions & 0 deletions src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
open Avalonia
open Avalonia.FuncUI
open Avalonia.FuncUI.Types
open System.Threading

[<AutoOpen>]
module AvaloniaObject =
open Avalonia.FuncUI.Types
open Avalonia.FuncUI.Builder

type AvaloniaObject with

Expand All @@ -31,9 +34,20 @@
InitFunction.Function = (fun (control: obj) -> func (control :?> 't))
}

static member onPropertyChanged<'t when 't :> AvaloniaObject>(func: AvaloniaPropertyChangedEventArgs -> unit, ?subPatchOptions) : IAttr<'t> =
let name = nameof Unchecked.defaultof<'t>.PropertyChanged
let factory: AvaloniaObject * (AvaloniaPropertyChangedEventArgs -> unit) * CancellationToken -> unit =
(fun (control, func, token) ->
let control = control :?> 't
let disposable = control.PropertyChanged.Subscribe(func)

token.Register(fun () -> disposable.Dispose()) |> ignore)

AttrBuilder<'t>.CreateSubscription<AvaloniaPropertyChangedEventArgs>(name, factory, func, ?subPatchOptions = subPatchOptions)

member this.Bind(prop: DirectPropertyBase<'value>, readable: #IReadable<'value>) : unit =
let _ = this.Bind(property = prop, source = readable.ImmediateObservable)

Check warning on line 49 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 49 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 49 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 49 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 49 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 49 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.
()

member this.Bind(prop: StyledProperty<'value>, readable: #IReadable<'value>) : unit =
let _ = this.Bind(property = prop, source = readable.ImmediateObservable)

Check warning on line 53 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 53 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 53 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 53 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 53 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.

Check warning on line 53 in src/Avalonia.FuncUI/DSL/Base/AvaloniaObject.fs

View workflow job for this annotation

GitHub Actions / build

Same as Observable, but fires once immediately after subscribing. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.
Expand Down
27 changes: 27 additions & 0 deletions src/Avalonia.FuncUI/DSL/Base/Control.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ namespace Avalonia.FuncUI.DSL
[<AutoOpen>]
module Control =
open Avalonia.Controls
open Avalonia.Controls.Primitives
open Avalonia.Interactivity
open Avalonia.FuncUI.Types
open Avalonia.FuncUI.Builder

Expand Down Expand Up @@ -32,3 +34,28 @@ module Control =
static member contextMenu<'t when 't :> Control>(menu: ContextMenu) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<ContextMenu>(Control.ContextMenuProperty, menu, ValueNone)

static member contextFlyout<'t when 't :> Control>(flyoutView: IView<FlyoutBase> option) : IAttr<'t> =
let view =
match flyoutView with
| Some view -> Some (view :> IView)
| None -> None

AttrBuilder<'t>.CreateContentSingle(Control.ContextFlyoutProperty, view)

static member contextFlyout<'t when 't :> Control>(flyoutView: IView<FlyoutBase>) : IAttr<'t> =
AttrBuilder<'t>.CreateContentSingle(Control.ContextFlyoutProperty, Some (flyoutView :> IView))

static member contextFlyout<'t when 't :> Control>(flyout: FlyoutBase) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<FlyoutBase>(Control.ContextFlyoutProperty, flyout, ValueNone)

static member onContextRequested<'t when 't :> Control>(func: ContextRequestedEventArgs -> unit, ?subPatchOptions) =
AttrBuilder<'t>.CreateSubscription<ContextRequestedEventArgs>(Control.ContextRequestedEvent, func, ?subPatchOptions = subPatchOptions)

static member onLoaded<'t when 't :> Control>(func: RoutedEventArgs -> unit, ?subPatchOptions) =
AttrBuilder<'t>.CreateSubscription<RoutedEventArgs>(Control.LoadedEvent, func, ?subPatchOptions = subPatchOptions)

static member onUnloaded<'t when 't :> Control>(func: RoutedEventArgs -> unit, ?subPatchOptions) =
AttrBuilder<'t>.CreateSubscription<RoutedEventArgs>(Control.UnloadedEvent, func, ?subPatchOptions = subPatchOptions)

static member onSizeChanged<'t when 't :> Control>(func: SizeChangedEventArgs -> unit, ?subPatchOptions) =
AttrBuilder<'t>.CreateSubscription<SizeChangedEventArgs>(Control.SizeChangedEvent, func, ?subPatchOptions = subPatchOptions)
49 changes: 49 additions & 0 deletions src/Avalonia.FuncUI/DSL/Base/IDataTemplateHost.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace Avalonia.FuncUI.DSL

[<AutoOpen>]
module IDataTemplateHost =
open Avalonia.FuncUI.Types
open Avalonia.FuncUI.Builder
open Avalonia.Controls.Templates

module private Internals =
let patchDataTemplates(dataTemplates: DataTemplates) (templates: IDataTemplate seq) =

if Seq.isEmpty templates then
dataTemplates.Clear()
else
templates
|> Seq.except dataTemplates
|> dataTemplates.RemoveAll

for newIndex, template in Seq.indexed templates do
let oldIndex = dataTemplates |> Seq.tryFindIndex template.Equals

match oldIndex with
| Some oldIndex when oldIndex = newIndex -> ()
| Some oldIndex -> dataTemplates.Move(oldIndex, newIndex)
| None -> dataTemplates.Insert(newIndex, template)

type IDataTemplateHost with
static member dataTemplates<'t when 't :> IDataTemplateHost>(templates: DataTemplates) : IAttr<'t> =
let getter: 't -> DataTemplates = (fun control -> control.DataTemplates)

let setter: ('t * DataTemplates -> unit) = (fun (control, value) -> Internals.patchDataTemplates control.DataTemplates value)

let compare: obj * obj -> bool = (fun (a, b) ->
let a = a :?> DataTemplates
let b = b :?> DataTemplates
System.Linq.Enumerable.SequenceEqual(a, b))

let factory = fun () -> DataTemplates()

AttrBuilder<'t>.CreateProperty<DataTemplates>("DataTemplates", templates, ValueSome getter, ValueSome setter, ValueSome compare, factory)

static member dataTemplates<'t when 't :> IDataTemplateHost>(templates: IDataTemplate list) : IAttr<'t> =
let getter: 't -> IDataTemplate list = (fun control -> control.DataTemplates |> Seq.toList)

let setter: ('t * IDataTemplate list -> unit) = (fun (control, value) -> Internals.patchDataTemplates control.DataTemplates value)

let factory = fun () -> []

AttrBuilder<'t>.CreateProperty<IDataTemplate list>("DataTemplates", templates, ValueSome getter, ValueSome setter, ValueNone, factory)
Loading
Loading