diff --git a/src/Avalonia.FuncUI.UnitTests/VirtualDom/VirtualDom.PatcherTests.fs b/src/Avalonia.FuncUI.UnitTests/VirtualDom/VirtualDom.PatcherTests.fs index 868e5b6b..9c7efa7e 100644 --- a/src/Avalonia.FuncUI.UnitTests/VirtualDom/VirtualDom.PatcherTests.fs +++ b/src/Avalonia.FuncUI.UnitTests/VirtualDom/VirtualDom.PatcherTests.fs @@ -44,19 +44,19 @@ module PatcherTests = [] let ``Patch Styles, Classes or Resources`` () = - let stylesGetter: IControl -> obj = (fun c -> (c :?> StyledElement).Styles :> obj) - let stylesSetter: IControl * obj -> unit = + let stylesGetter: IAvaloniaObject -> obj = (fun c -> (c :?> StyledElement).Styles :> obj) + let stylesSetter: IAvaloniaObject * obj -> unit = (fun (c, v) -> let se = (c :?> StyledElement) let s = v :?> Styles se.Styles.Clear() se.Styles.AddRange(s)) - let classesGetter: IControl -> obj = (fun c -> (c :?> StyledElement).Classes :> obj) - let classesSetter: IControl * obj -> unit = (fun (c, v) -> (c :?> StyledElement).Classes <- v :?> Classes) + let classesGetter: IAvaloniaObject -> obj = (fun c -> (c :?> StyledElement).Classes :> obj) + let classesSetter: IAvaloniaObject * obj -> unit = (fun (c, v) -> (c :?> StyledElement).Classes <- v :?> Classes) - let resourcesGetter: IControl -> obj = (fun c -> (c :?> StyledElement).Resources :> obj) - let resourcesSetter: IControl * obj -> unit = (fun (c, v) -> (c :?> StyledElement).Resources <- v :?> IResourceDictionary) + let resourcesGetter: IAvaloniaObject -> obj = (fun c -> (c :?> StyledElement).Resources :> obj) + let resourcesSetter: IAvaloniaObject * obj -> unit = (fun (c, v) -> (c :?> StyledElement).Resources <- v :?> IResourceDictionary) let delta : Delta.ViewDelta = { diff --git a/src/Avalonia.FuncUI.sln b/src/Avalonia.FuncUI.sln index 3671d46f..a77147c7 100644 --- a/src/Avalonia.FuncUI.sln +++ b/src/Avalonia.FuncUI.sln @@ -55,6 +55,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solutio NuGet.config = NuGet.config EndProjectSection EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Examples.InlineText", "Examples\Component Examples\Examples.InlineText\Examples.InlineText.fsproj", "{B8D8C84B-05AD-475B-BE81-A30544CE0149}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -133,6 +135,10 @@ Global {A0158F2D-0EA1-4D4B-9958-D3A364FC1C36}.Debug|Any CPU.Build.0 = Debug|Any CPU {A0158F2D-0EA1-4D4B-9958-D3A364FC1C36}.Release|Any CPU.ActiveCfg = Release|Any CPU {A0158F2D-0EA1-4D4B-9958-D3A364FC1C36}.Release|Any CPU.Build.0 = Release|Any CPU + {B8D8C84B-05AD-475B-BE81-A30544CE0149}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8D8C84B-05AD-475B-BE81-A30544CE0149}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8D8C84B-05AD-475B-BE81-A30544CE0149}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8D8C84B-05AD-475B-BE81-A30544CE0149}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -154,6 +160,7 @@ Global {BF5DC7CC-7ABF-40AB-97DD-6774C17FE005} = {F50826CE-D9BC-45CF-A110-C42225B75AD3} {57D0AF41-5482-47FC-B75A-51FADD2FFEBD} = {F50826CE-D9BC-45CF-A110-C42225B75AD3} {5CC37986-D6E0-438B-B895-BC82DFD22307} = {F50826CE-D9BC-45CF-A110-C42225B75AD3} + {B8D8C84B-05AD-475B-BE81-A30544CE0149} = {F50826CE-D9BC-45CF-A110-C42225B75AD3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4630E817-6780-4C98-9379-EA3B45224339} diff --git a/src/Avalonia.FuncUI/Avalonia.FuncUI.fsproj b/src/Avalonia.FuncUI/Avalonia.FuncUI.fsproj index 3f43ad37..83393c18 100644 --- a/src/Avalonia.FuncUI/Avalonia.FuncUI.fsproj +++ b/src/Avalonia.FuncUI/Avalonia.FuncUI.fsproj @@ -65,6 +65,7 @@ + @@ -80,6 +81,12 @@ + + + + + + @@ -127,6 +134,7 @@ + diff --git a/src/Avalonia.FuncUI/Builder.fs b/src/Avalonia.FuncUI/Builder.fs index b70055dc..cb178d3c 100644 --- a/src/Avalonia.FuncUI/Builder.fs +++ b/src/Avalonia.FuncUI/Builder.fs @@ -30,15 +30,15 @@ open Avalonia.FuncUI.Types open Avalonia.FuncUI.Library module private Helpers = - let wrappedGetter<'view, 'value>(func: 'view -> 'value) : IControl -> obj = - let wrapper (control: IControl) : obj = + let wrappedGetter<'view, 'value>(func: 'view -> 'value) : IAvaloniaObject -> obj = + let wrapper (control: IAvaloniaObject) : obj = let view = control :> obj :?> 'view let value = func view value :> obj wrapper - let wrappedSetter<'view, 'value>(func: 'view * 'value -> unit) : IControl * obj -> unit = - let wrapper (control: IControl, value: obj) : unit = + let wrappedSetter<'view, 'value>(func: 'view * 'value -> unit) : IAvaloniaObject * obj -> unit = + let wrapper (control: IAvaloniaObject, value: obj) : unit = let view = control :> obj :?> 'view let value = value :?> 'value func(view, value) @@ -162,7 +162,7 @@ type AttrBuilder<'view>() = static member CreateSubscription<'arg, 'owner when 'owner :> AvaloniaObject>(property: DirectProperty<'owner, 'arg>, func: 'arg -> unit, ?subPatchOptions: SubPatchOptions) : IAttr<'view> = // subscribe to avalonia property // TODO: extract to helpers module - let subscribeFunc (control: IControl, _handler: 'h) = + let subscribeFunc (control: IAvaloniaObject, _handler: 'h) = let cts = new CancellationTokenSource() control .GetObservable(property) @@ -184,7 +184,7 @@ type AttrBuilder<'view>() = static member CreateSubscription<'arg>(property: AvaloniaProperty<'arg>, func: 'arg -> unit, ?subPatchOptions: SubPatchOptions) : IAttr<'view> = // subscribe to avalonia property // TODO: extract to helpers module - let subscribeFunc (control: IControl, _handler: 'h) = + let subscribeFunc (control: IAvaloniaObject, _handler: 'h) = let cts = new CancellationTokenSource() control .GetObservable(property) @@ -225,10 +225,10 @@ type AttrBuilder<'view>() = /// /// Create a Event Subscription Attribute for a .Net Event /// - static member CreateSubscription<'arg>(name: string, factory: IControl * ('arg -> unit) * CancellationToken -> unit, func: 'arg -> unit, ?subPatchOptions: SubPatchOptions) = + static member CreateSubscription<'arg>(name: string, factory: IAvaloniaObject * ('arg -> unit) * CancellationToken -> unit, func: 'arg -> unit, ?subPatchOptions: SubPatchOptions) = // TODO: extract to helpers module // subscribe to event - let subscribeFunc (control: IControl, _handler: 'h) = + let subscribeFunc (control: IAvaloniaObject, _handler: 'h) = let cts = new CancellationTokenSource() factory(control, func, cts.Token) cts @@ -242,7 +242,7 @@ type AttrBuilder<'view>() = } attr :> IAttr<'view> - + [] type ViewBuilder() = @@ -252,4 +252,3 @@ type ViewBuilder() = View.Attrs = attrs View.ConstructorArgs = null View.Outlet = ValueNone } - :> IView<'view> \ No newline at end of file diff --git a/src/Avalonia.FuncUI/DSL/Documents/Bold.fs b/src/Avalonia.FuncUI/DSL/Documents/Bold.fs new file mode 100644 index 00000000..6da726ce --- /dev/null +++ b/src/Avalonia.FuncUI/DSL/Documents/Bold.fs @@ -0,0 +1,20 @@ +namespace Avalonia.FuncUI.DSL + +open Avalonia.FuncUI.Types + +[] +module Bold = + open Avalonia.FuncUI.Builder + open Avalonia.Controls.Documents + + let create (attrs: IAttr list): IView = + ViewBuilder.Create(attrs) + + let simple (text: string): IView = + ViewBuilder.Create([ + Bold.inlines [ + Run.create [ + Run.text text + ] :> IView + ] + ]) diff --git a/src/Avalonia.FuncUI/DSL/Documents/Italic.fs b/src/Avalonia.FuncUI/DSL/Documents/Italic.fs new file mode 100644 index 00000000..82026324 --- /dev/null +++ b/src/Avalonia.FuncUI/DSL/Documents/Italic.fs @@ -0,0 +1,19 @@ +namespace Avalonia.FuncUI.DSL + +[] +module Italic = + open Avalonia.FuncUI.Builder + open Avalonia.FuncUI.Types + open Avalonia.Controls.Documents + + let create (attrs: IAttr list): IView = + ViewBuilder.Create(attrs) + + let simple (text: string): IView = + ViewBuilder.Create([ + Italic.inlines [ + Run.create [ + Run.text text + ] :> IView + ] + ]) diff --git a/src/Avalonia.FuncUI/DSL/Documents/LineBreak.fs b/src/Avalonia.FuncUI/DSL/Documents/LineBreak.fs new file mode 100644 index 00000000..5f2fa9e4 --- /dev/null +++ b/src/Avalonia.FuncUI/DSL/Documents/LineBreak.fs @@ -0,0 +1,14 @@ +namespace Avalonia.FuncUI.DSL + +[] +module LineBreak = + open Avalonia.FuncUI.Builder + open Avalonia.FuncUI.Types + open Avalonia.Controls.Documents + + let create (attrs: IAttr list): IView = + ViewBuilder.Create(attrs) + + /// Creates a simple line-break with no attributes. + let simple : IView = + create([]) diff --git a/src/Avalonia.FuncUI/DSL/Documents/Run.fs b/src/Avalonia.FuncUI/DSL/Documents/Run.fs new file mode 100644 index 00000000..4cd57e23 --- /dev/null +++ b/src/Avalonia.FuncUI/DSL/Documents/Run.fs @@ -0,0 +1,13 @@ +namespace Avalonia.FuncUI.DSL + +[] +module Run = + open Avalonia.FuncUI.Builder + open Avalonia.FuncUI.Types + open Avalonia.Controls.Documents + + let create (attrs: IAttr list) : IView = ViewBuilder.Create(attrs) + + type Run with + static member text<'t when 't :> Run>(value: string) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(Run.TextProperty, value, ValueNone) diff --git a/src/Avalonia.FuncUI/DSL/Documents/Span.fs b/src/Avalonia.FuncUI/DSL/Documents/Span.fs new file mode 100644 index 00000000..467109f3 --- /dev/null +++ b/src/Avalonia.FuncUI/DSL/Documents/Span.fs @@ -0,0 +1,18 @@ +namespace Avalonia.FuncUI.DSL + +[] +module Span = + open Avalonia.FuncUI.Builder + open Avalonia.FuncUI.Types + open Avalonia.Controls.Documents + + let create (attrs: IAttr list): IView = + ViewBuilder.Create(attrs) + + type Span with + static member inlines<'t when 't :> Span>(value: InlineCollection) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(Span.InlinesProperty, value, ValueNone) + + static member inlines<'t when 't :> Span>(values: IView list (* TODO: Change to IView *)) : IAttr<'t> = + let getter : ('t -> obj) = (fun control -> control.Inlines :> obj) + AttrBuilder<'t>.CreateContentMultiple("Inlines", ValueSome getter, ValueNone, values) diff --git a/src/Avalonia.FuncUI/DSL/Documents/Underline.fs b/src/Avalonia.FuncUI/DSL/Documents/Underline.fs new file mode 100644 index 00000000..dd8bddef --- /dev/null +++ b/src/Avalonia.FuncUI/DSL/Documents/Underline.fs @@ -0,0 +1,19 @@ +namespace Avalonia.FuncUI.DSL + +[] +module Underline = + open Avalonia.FuncUI.Builder + open Avalonia.FuncUI.Types + open Avalonia.Controls.Documents + + let create (attrs: IAttr list): IView = + ViewBuilder.Create(attrs) + + let simple (text: string): IView = + ViewBuilder.Create([ + Underline.inlines [ + Run.create [ + Run.text text + ] :> IView + ] + ]) diff --git a/src/Avalonia.FuncUI/DSL/Primitives/TextElement.fs b/src/Avalonia.FuncUI/DSL/Primitives/TextElement.fs new file mode 100644 index 00000000..61a59392 --- /dev/null +++ b/src/Avalonia.FuncUI/DSL/Primitives/TextElement.fs @@ -0,0 +1,41 @@ +namespace Avalonia.FuncUI.DSL + +open Avalonia.Controls.Documents + +[] +module TextElement = + open Avalonia.Media.Immutable + open Avalonia.FuncUI.Types + open Avalonia.FuncUI.Builder + open Avalonia.Media + + let create (attrs: IAttr list): IView = + ViewBuilder.Create(attrs) + + type TextElement with + static member background<'t when 't :> TextElement>(value: IBrush) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(TextElement.BackgroundProperty, value, ValueNone) + + static member background<'t when 't :> TextElement>(color: string) : IAttr<'t> = + Color.Parse(color) |> ImmutableSolidColorBrush |> TextElement.background + + static member fontFamily<'t when 't :> TextElement>(value: FontFamily) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(TextElement.FontFamilyProperty, value, ValueNone) + + static member fontSize<'t when 't :> TextElement>(value: double) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(TextElement.FontSizeProperty, value, ValueNone) + + static member fontStyle<'t when 't :> TextElement>(value: FontStyle) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(TextElement.FontStyleProperty, value, ValueNone) + + static member fontStretch<'t when 't :> TextElement>(value: FontStretch) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(TextElement.FontStretchProperty, value, ValueNone) + + static member fontWeight<'t when 't :> TextElement>(value: FontWeight) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(TextElement.FontWeightProperty, value, ValueNone) + + static member foreground<'t when 't :> TextElement>(value: IBrush) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(TextElement.ForegroundProperty, value, ValueNone) + + static member foreground<'t when 't :> TextElement>(color: string) : IAttr<'t> = + Color.Parse(color) |> ImmutableSolidColorBrush |> TextElement.foreground diff --git a/src/Avalonia.FuncUI/DSL/RichTextBlock.fs b/src/Avalonia.FuncUI/DSL/RichTextBlock.fs new file mode 100644 index 00000000..bab27114 --- /dev/null +++ b/src/Avalonia.FuncUI/DSL/RichTextBlock.fs @@ -0,0 +1,20 @@ +namespace Avalonia.FuncUI.DSL + + +[] +module RichTextBlock = + open Avalonia.Controls + open Avalonia.Controls.Documents + open Avalonia.FuncUI.Builder + open Avalonia.FuncUI.Types + + let create (attrs: IAttr list): IView = + ViewBuilder.Create(attrs) + + type RichTextBlock with + static member inlines<'t when 't :> RichTextBlock>(value: InlineCollection) : IAttr<'t> = + AttrBuilder<'t>.CreateProperty(RichTextBlock.InlinesProperty, value, ValueNone) + + static member inlines<'t when 't :> RichTextBlock>(values: IView list (* TODO: Change to IView *)) : IAttr<'t> = + let getter : ('t -> obj) = (fun control -> control.Inlines :> obj) + AttrBuilder<'t>.CreateContentMultiple("Inlines", ValueSome getter, ValueNone, values) \ No newline at end of file diff --git a/src/Avalonia.FuncUI/Library.fs b/src/Avalonia.FuncUI/Library.fs index 1b61db96..c9a02a49 100644 --- a/src/Avalonia.FuncUI/Library.fs +++ b/src/Avalonia.FuncUI/Library.fs @@ -35,7 +35,6 @@ module internal Extensions = type IInteractive with member this.GetObservable<'args when 'args :> RoutedEventArgs>(routedEvent: RoutedEvent<'args>) : IObservable<'args> = - let sub = Func, IDisposable>(fun observer -> // push new update to subscribers let handler = EventHandler<'args>(fun _ e -> diff --git a/src/Avalonia.FuncUI/Types.fs b/src/Avalonia.FuncUI/Types.fs index 47d7a08a..b7241c27 100644 --- a/src/Avalonia.FuncUI/Types.fs +++ b/src/Avalonia.FuncUI/Types.fs @@ -1,8 +1,8 @@ namespace rec Avalonia.FuncUI +open Avalonia open Avalonia.Controls open System -open System.Reactive.Linq open System.Threading module Types = @@ -10,8 +10,8 @@ module Types = [] type PropertyAccessor = { Name: string - Getter: (IControl -> obj) voption - Setter: (IControl * obj -> unit) voption } + Getter: (IAvaloniaObject -> obj) voption + Setter: (IAvaloniaObject * obj -> unit) voption } override this.Equals (other: obj) : bool = match other with @@ -61,7 +61,7 @@ module Types = [] type Subscription = { Name: string - Subscribe: IControl * Delegate -> CancellationTokenSource + Subscribe: IControl * Delegate -> CancellationTokenSource Func: Delegate FuncType: Type Scope: obj } @@ -76,7 +76,7 @@ module Types = override this.GetHashCode () = (this.Name, this.FuncType, this.Scope).GetHashCode() - + type IAttr = abstract member UniqueName : string abstract member Property : Property option @@ -128,7 +128,7 @@ module Types = abstract member ViewKey: string voption abstract member Attrs: IAttr list with get abstract member ConstructorArgs: obj array with get - abstract member Outlet: (IControl -> unit) voption with get + abstract member Outlet: (IAvaloniaObject -> unit) voption with get type IView<'viewType> = inherit IView @@ -139,7 +139,7 @@ module Types = ViewKey: string voption Attrs: IAttr<'viewType> list ConstructorArgs: obj array - Outlet: (IControl -> unit) voption } + Outlet: (IAvaloniaObject-> unit) voption } interface IView with member this.ViewType = this.ViewType @@ -153,7 +153,6 @@ module Types = interface IView<'viewType> with member this.Attrs = this.Attrs - // TODO: maybe move active patterns to Virtual DON Misc let internal (|Property'|_|) (attr: IAttr) = @@ -163,4 +162,5 @@ module Types = attr.Content let internal (|Subscription'|_|) (attr: IAttr) = - attr.Subscription \ No newline at end of file + attr.Subscription + \ No newline at end of file diff --git a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Delta.fs b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Delta.fs index 2ae1c9f9..d73468d0 100644 --- a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Delta.fs +++ b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Delta.fs @@ -3,6 +3,8 @@ namespace Avalonia.FuncUI.VirtualDom open System open System.Threading +open Avalonia +open Avalonia.Controls open Avalonia.FuncUI.Types module internal rec Delta = @@ -45,7 +47,7 @@ module internal rec Delta = [] type SubscriptionDelta = { Name: string - Subscribe: Avalonia.Controls.IControl * Delegate -> CancellationTokenSource + Subscribe: IControl * Delegate -> CancellationTokenSource Func: Delegate option } override this.Equals (other: obj) : bool = @@ -64,14 +66,14 @@ module internal rec Delta = member this.UniqueName = this.Name - type ContentDelta = + type ContentDelta = { Accessor: Accessor Content: ViewContentDelta } static member From (content: Content) : ContentDelta = { Accessor = content.Accessor; Content = ViewContentDelta.From content.Content } - + type ViewContentDelta = | Single of ViewDelta option | Multiple of ViewDelta list @@ -99,7 +101,7 @@ module internal rec Delta = Attrs: AttrDelta list ConstructorArgs: obj array KeyDidChange: bool - Outlet: (Avalonia.Controls.IControl -> unit) voption } + Outlet: (Avalonia.IAvaloniaObject -> unit) voption } static member From (view: IView, ?keyDidChange: bool) : ViewDelta = { ViewType = view.ViewType diff --git a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Differ.fs b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Differ.fs index 6babcd16..ac60074c 100644 --- a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Differ.fs +++ b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Differ.fs @@ -1,8 +1,7 @@ namespace Avalonia.FuncUI.VirtualDom -open System.Collections.Generic open Avalonia.FuncUI.Types -open Delta +open Avalonia.FuncUI.VirtualDom.Delta module internal rec Differ = let private update (last: IAttr) (next: IAttr) : AttrDelta = diff --git a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Misc.fs b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Misc.fs index 6fb43f11..287959d7 100644 --- a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Misc.fs +++ b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Misc.fs @@ -19,16 +19,16 @@ type ViewMetaData() = static member ViewSubscriptionsProperty = viewSubscriptions - static member GetViewId(control: IControl) : Guid = + static member GetViewId(control: IAvaloniaObject) : Guid = control.GetValue(ViewMetaData.ViewIdProperty) - static member SetViewId(control: IControl, value: Guid) : unit = + static member SetViewId(control: IAvaloniaObject, value: Guid) : unit = control.SetValue(ViewMetaData.ViewIdProperty, value) |> ignore - static member GetViewSubscriptions(control: IControl) : ConcurrentDictionary<_, _> = + static member GetViewSubscriptions(control: IAvaloniaObject) : ConcurrentDictionary<_, _> = control.GetValue(ViewMetaData.ViewSubscriptionsProperty) - static member SetViewSubscriptions(control: IControl, value) : unit = + static member SetViewSubscriptions(control: IAvaloniaObject, value) : unit = control.SetValue(ViewMetaData.ViewSubscriptionsProperty, value) |> ignore diff --git a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Patcher.fs b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Patcher.fs index de9a52ff..85efc106 100644 --- a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Patcher.fs +++ b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.Patcher.fs @@ -5,6 +5,7 @@ module internal rec Patcher = open System.Collections open System.Collections.Concurrent open Avalonia.Controls + open Avalonia.Controls.Documents open Avalonia open Avalonia.FuncUI.VirtualDom.Delta open Avalonia.FuncUI.Library @@ -46,7 +47,7 @@ module internal rec Patcher = value.Cancel() subscriptions.TryRemove(attr.UniqueName) |> ignore - let private patchProperty (view: IControl) (attr: PropertyDelta) : unit = + let internal patchProperty (view: IAvaloniaObject) (attr: PropertyDelta) : unit = match attr.Accessor with | Accessor.AvaloniaProperty avaloniaProperty -> match attr.Value with @@ -81,7 +82,7 @@ module internal rec Patcher = | ValueSome setter -> setter (view, value) | ValueNone _ -> failwithf "instance property ('%s') has no setter. " instanceProperty.Name - let private patchContentMultiple (view: IControl) (accessor: Accessor) (delta: ViewDelta list) : unit = + let private patchContentMultiple (view: IAvaloniaObject) (accessor: Accessor) (delta: ViewDelta list) : unit = (* often lists only have a get accessor *) let patch_IList (collection: IList) : unit = if List.isEmpty delta then @@ -95,7 +96,7 @@ module internal rec Patcher = if shouldPatch item viewElement then // patch match item with - | :? IControl as control -> patch(control, viewElement) + | :? IAvaloniaObject as control -> patch(control, viewElement) | _ -> // replace let newItem = Patcher.create viewElement @@ -160,7 +161,7 @@ module internal rec Patcher = let setter = Some (fun obj -> view.SetValue(property, obj) |> ignore) patch (getter, setter) - let private patchContentSingle (view: IControl) (accessor: Accessor) (viewElement: ViewDelta option) : unit = + let private patchContentSingle (view: IAvaloniaObject) (accessor: Accessor) (viewElement: ViewDelta option) : unit = let patch_avalonia (property: AvaloniaProperty) = match viewElement with @@ -168,7 +169,7 @@ module internal rec Patcher = let value = view.GetValue(property) if shouldPatch value viewElement then - Patcher.patch(value :?> IControl, viewElement) + Patcher.patch(value :?> IAvaloniaObject, viewElement) else let createdControl = Patcher.create viewElement view.SetValue(property, createdControl) @@ -185,7 +186,7 @@ module internal rec Patcher = | _ -> failwith "Property Accessor needs a getter" if shouldPatch value viewElement then - Patcher.patch(value :?> IControl, viewElement) + Patcher.patch(value :?> IAvaloniaObject, viewElement) else let createdControl = Patcher.create(viewElement) @@ -201,30 +202,34 @@ module internal rec Patcher = | Accessor.InstanceProperty instanceProperty -> patch_instance instanceProperty | Accessor.AvaloniaProperty property -> patch_avalonia property - let private patchContent (view: IControl) (attr: ContentDelta) : unit = + let private patchContent (view: IAvaloniaObject) (attr: ContentDelta) : unit = match attr.Content with | ViewContentDelta.Single single -> patchContentSingle view attr.Accessor single | ViewContentDelta.Multiple multiple -> patchContentMultiple view attr.Accessor multiple - - let patch (view: IControl, viewElement: ViewDelta) : unit = + + let patch (view: IAvaloniaObject, viewElement: ViewDelta) : unit = for attr in viewElement.Attrs do match attr with | AttrDelta.Property property -> patchProperty view property | AttrDelta.Content content -> patchContent view content - | AttrDelta.Subscription subscription -> patchSubscription view subscription + | AttrDelta.Subscription subscription -> + match view with + | :? IControl as control -> + patchSubscription control subscription + | _ -> failwith "Only controls can have subscriptions" - let create (viewElement: ViewDelta) : IControl = + let create (viewElement: ViewDelta) : IAvaloniaObject = let control = if viewElement.ConstructorArgs <> null && viewElement.ConstructorArgs.Length > 0 then (viewElement.ViewType, viewElement.ConstructorArgs) |> Activator.CreateInstance - |> Utils.cast + |> Utils.cast else viewElement.ViewType |> Activator.CreateInstance - |> Utils.cast + |> Utils.cast match viewElement.Outlet with | ValueSome outlet -> outlet control diff --git a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.fs b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.fs index 0d068a6d..8ec1e4c1 100644 --- a/src/Avalonia.FuncUI/VirtualDom/VirtualDom.fs +++ b/src/Avalonia.FuncUI/VirtualDom/VirtualDom.fs @@ -10,7 +10,7 @@ module rec VirtualDom = let create (view: IView) : IControl = view |> ViewDelta.From - |> Patcher.create + |> Patcher.create :?> IControl let update (root: IControl, last: IView, next: IView) : unit = let delta = Differ.diff(last, next) @@ -76,11 +76,11 @@ module rec VirtualDom = | ValueSome delta -> match control.GetType () = delta.ViewType && not delta.KeyDidChange with | true -> Patcher.patch (control, delta) - | false -> host.Child <- Patcher.create delta + | false -> host.Child <- (Patcher.create delta) :?> IControl | ValueNone -> host.Child <- null | ValueNone -> match delta with - | ValueSome delta -> host.Child <- Patcher.create delta + | ValueSome delta -> host.Child <- (Patcher.create delta) :?> IControl | ValueNone -> host.Child <- null \ No newline at end of file diff --git a/src/Examples/Component Examples/Examples.InlineText/Examples.InlineText.fsproj b/src/Examples/Component Examples/Examples.InlineText/Examples.InlineText.fsproj new file mode 100644 index 00000000..92ef7a9f --- /dev/null +++ b/src/Examples/Component Examples/Examples.InlineText/Examples.InlineText.fsproj @@ -0,0 +1,20 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + + diff --git a/src/Examples/Component Examples/Examples.InlineText/Program.fs b/src/Examples/Component Examples/Examples.InlineText/Program.fs new file mode 100644 index 00000000..ab82f37c --- /dev/null +++ b/src/Examples/Component Examples/Examples.InlineText/Program.fs @@ -0,0 +1,40 @@ +namespace Examples.InlineText + +open Avalonia +open Avalonia.Controls.ApplicationLifetimes +open Avalonia.Themes.Fluent +open Avalonia.FuncUI.Hosts + +type MainWindow() as this = + inherit HostWindow() + do + base.Title <- "Examples.InlineText" + base.Width <- 1200.0 + base.Height <- 400.0 + this.Content <- View.view + + //this.VisualRoot.VisualRoot.Renderer.DrawFps <- true + //this.VisualRoot.VisualRoot.Renderer.DrawDirtyRects <- true + + +type App() = + inherit Application() + + override this.Initialize() = + this.Styles.Add (FluentTheme(baseUri = null, Mode = FluentThemeMode.Dark)) + + override this.OnFrameworkInitializationCompleted() = + match this.ApplicationLifetime with + | :? IClassicDesktopStyleApplicationLifetime as desktopLifetime -> + desktopLifetime.MainWindow <- MainWindow() + | _ -> () + +module Program = + + [] + let main(args: string[]) = + AppBuilder + .Configure() + .UsePlatformDetect() + .UseSkia() + .StartWithClassicDesktopLifetime(args) \ No newline at end of file diff --git a/src/Examples/Component Examples/Examples.InlineText/View.fs b/src/Examples/Component Examples/Examples.InlineText/View.fs new file mode 100644 index 00000000..50a83dff --- /dev/null +++ b/src/Examples/Component Examples/Examples.InlineText/View.fs @@ -0,0 +1,66 @@ +namespace Examples.InlineText + +module View = + open Avalonia.FuncUI.DSL + open Avalonia.FuncUI.Types + open Avalonia.FuncUI + open Avalonia.Controls + open Avalonia.Layout + open Avalonia.Media + open Avalonia.Media.Immutable + open Avalonia.Controls.Documents + + let private redBrush = 0x0ffB2474Du |> Color.FromUInt32 |> ImmutableSolidColorBrush + let private blueBrush = 0x0ff47B2A6u |> Color.FromUInt32 |> ImmutableSolidColorBrush + + let view = + Component (fun ctx -> + let colorMode = ctx.useState 0 + + StackPanel.create [ + StackPanel.verticalAlignment VerticalAlignment.Center + StackPanel.horizontalAlignment HorizontalAlignment.Center + StackPanel.children [ + Button.create [ + Button.content "Invert color!" + Button.onClick (fun _ -> + if colorMode.Current = 0 then + colorMode.Set 1 + else + colorMode.Set 0) + ] + RichTextBlock.create [ + RichTextBlock.dock Dock.Top + RichTextBlock.fontSize 48.0 + RichTextBlock.horizontalAlignment HorizontalAlignment.Center + RichTextBlock.inlines [ + Run.create [ + Run.text "You" + ] :> IView + Run.create [ + Run.text "Inline" + if colorMode.Current = 0 then + redBrush + else + blueBrush + |> Run.background + ] + + LineBreak.simple + + Span.create [ + Span.inlines [ + Bold.simple "Oh, so bold!" :> IView + LineBreak.simple + + Italic.simple "Although, " + Run.create [ + Run.text "I always wanted to be " + ] + Underline.simple "underlined" + ] + ] + ] + ] + ] + ])