Skip to content

Commit

Permalink
Add support for RichTextBlock (#220)
Browse files Browse the repository at this point in the history
* Basic bindings for RichTextBlock

* Initial DSL for inline elements

* Use differ on inline elements

* Switch to IView + generalize VDOM to IAvaloniaObject

* Remove inline types

* Remove control casts

* Add styling via TextElement

* Add rest of inline elements

This adds Bold, Italic, LineBreak, Span and Underline.

* Do not re-initialize brush on every switch
  • Loading branch information
sleepyfran authored Oct 15, 2022
1 parent 3a32ee2 commit eaebd3f
Show file tree
Hide file tree
Showing 22 changed files with 361 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ module PatcherTests =

[<Fact>]
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 =
{
Expand Down
7 changes: 7 additions & 0 deletions src/Avalonia.FuncUI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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}
Expand Down
8 changes: 8 additions & 0 deletions src/Avalonia.FuncUI/Avalonia.FuncUI.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<Compile Include="DSL\Primitives\Thumb.fs" />
<Compile Include="DSL\Primitives\AccessText.fs" />
<Compile Include="DSL\Primitives\ScrollBar.fs" />
<Compile Include="DSL\Primitives\TextElement.fs" />
<Compile Include="DSL\Panels\Canvas.fs" />
<Compile Include="DSL\Panels\DockPanel.fs" />
<Compile Include="DSL\Panels\Grid.fs" />
Expand All @@ -80,6 +81,12 @@
<Compile Include="DSL\Shapes\Path.fs" />
<Compile Include="DSL\Calendar\Calendar.fs" />
<Compile Include="DSL\Calendar\CalendarDatePicker.fs" />
<Compile Include="DSL\Documents\Run.fs" />
<Compile Include="DSL\Documents\Span.fs" />
<Compile Include="DSL\Documents\Bold.fs" />
<Compile Include="DSL\Documents\LineBreak.fs" />
<Compile Include="DSL\Documents\Italic.fs" />
<Compile Include="DSL\Documents\Underline.fs" />
<Compile Include="DSL\DatePicker.fs" />
<Compile Include="DSL\TimePicker.fs" />
<Compile Include="DSL\ItemsControl.fs" />
Expand Down Expand Up @@ -127,6 +134,7 @@
<Compile Include="DSL\TabItem.fs" />
<Compile Include="DSL\TickBar.fs" />
<Compile Include="DSL\Viewbox.fs" />
<Compile Include="DSL\RichTextBlock.fs" />
</ItemGroup>

</Project>
19 changes: 9 additions & 10 deletions src/Avalonia.FuncUI/Builder.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -225,10 +225,10 @@ type AttrBuilder<'view>() =
/// <summary>
/// Create a Event Subscription Attribute for a .Net Event
/// </summary>
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
Expand All @@ -242,7 +242,7 @@ type AttrBuilder<'view>() =
}

attr :> IAttr<'view>

[<AbstractClass; Sealed>]
type ViewBuilder() =

Expand All @@ -252,4 +252,3 @@ type ViewBuilder() =
View.Attrs = attrs
View.ConstructorArgs = null
View.Outlet = ValueNone }
:> IView<'view>
20 changes: 20 additions & 0 deletions src/Avalonia.FuncUI/DSL/Documents/Bold.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Avalonia.FuncUI.DSL

open Avalonia.FuncUI.Types

[<AutoOpen>]
module Bold =
open Avalonia.FuncUI.Builder
open Avalonia.Controls.Documents

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

let simple (text: string): IView<Bold> =
ViewBuilder.Create([
Bold.inlines [
Run.create [
Run.text text
] :> IView
]
])
19 changes: 19 additions & 0 deletions src/Avalonia.FuncUI/DSL/Documents/Italic.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Avalonia.FuncUI.DSL

[<AutoOpen>]
module Italic =
open Avalonia.FuncUI.Builder
open Avalonia.FuncUI.Types
open Avalonia.Controls.Documents

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

let simple (text: string): IView<Italic> =
ViewBuilder.Create([
Italic.inlines [
Run.create [
Run.text text
] :> IView
]
])
14 changes: 14 additions & 0 deletions src/Avalonia.FuncUI/DSL/Documents/LineBreak.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Avalonia.FuncUI.DSL

[<AutoOpen>]
module LineBreak =
open Avalonia.FuncUI.Builder
open Avalonia.FuncUI.Types
open Avalonia.Controls.Documents

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

/// Creates a simple line-break with no attributes.
let simple : IView<LineBreak> =
create([])
13 changes: 13 additions & 0 deletions src/Avalonia.FuncUI/DSL/Documents/Run.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Avalonia.FuncUI.DSL

[<AutoOpen>]
module Run =
open Avalonia.FuncUI.Builder
open Avalonia.FuncUI.Types
open Avalonia.Controls.Documents

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

type Run with
static member text<'t when 't :> Run>(value: string) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<string>(Run.TextProperty, value, ValueNone)
18 changes: 18 additions & 0 deletions src/Avalonia.FuncUI/DSL/Documents/Span.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Avalonia.FuncUI.DSL

[<AutoOpen>]
module Span =
open Avalonia.FuncUI.Builder
open Avalonia.FuncUI.Types
open Avalonia.Controls.Documents

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

type Span with
static member inlines<'t when 't :> Span>(value: InlineCollection) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<InlineCollection>(Span.InlinesProperty, value, ValueNone)

static member inlines<'t when 't :> Span>(values: IView list (* TODO: Change to IView<Inline> *)) : IAttr<'t> =
let getter : ('t -> obj) = (fun control -> control.Inlines :> obj)
AttrBuilder<'t>.CreateContentMultiple("Inlines", ValueSome getter, ValueNone, values)
19 changes: 19 additions & 0 deletions src/Avalonia.FuncUI/DSL/Documents/Underline.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Avalonia.FuncUI.DSL

[<AutoOpen>]
module Underline =
open Avalonia.FuncUI.Builder
open Avalonia.FuncUI.Types
open Avalonia.Controls.Documents

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

let simple (text: string): IView<Underline> =
ViewBuilder.Create([
Underline.inlines [
Run.create [
Run.text text
] :> IView
]
])
41 changes: 41 additions & 0 deletions src/Avalonia.FuncUI/DSL/Primitives/TextElement.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Avalonia.FuncUI.DSL

open Avalonia.Controls.Documents

[<AutoOpen>]
module TextElement =
open Avalonia.Media.Immutable
open Avalonia.FuncUI.Types
open Avalonia.FuncUI.Builder
open Avalonia.Media

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

type TextElement with
static member background<'t when 't :> TextElement>(value: IBrush) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<IBrush>(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<FontFamily>(TextElement.FontFamilyProperty, value, ValueNone)

static member fontSize<'t when 't :> TextElement>(value: double) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<double>(TextElement.FontSizeProperty, value, ValueNone)

static member fontStyle<'t when 't :> TextElement>(value: FontStyle) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<FontStyle>(TextElement.FontStyleProperty, value, ValueNone)

static member fontStretch<'t when 't :> TextElement>(value: FontStretch) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<FontStretch>(TextElement.FontStretchProperty, value, ValueNone)

static member fontWeight<'t when 't :> TextElement>(value: FontWeight) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<FontWeight>(TextElement.FontWeightProperty, value, ValueNone)

static member foreground<'t when 't :> TextElement>(value: IBrush) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<IBrush>(TextElement.ForegroundProperty, value, ValueNone)

static member foreground<'t when 't :> TextElement>(color: string) : IAttr<'t> =
Color.Parse(color) |> ImmutableSolidColorBrush |> TextElement.foreground
20 changes: 20 additions & 0 deletions src/Avalonia.FuncUI/DSL/RichTextBlock.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Avalonia.FuncUI.DSL


[<AutoOpen>]
module RichTextBlock =
open Avalonia.Controls
open Avalonia.Controls.Documents
open Avalonia.FuncUI.Builder
open Avalonia.FuncUI.Types

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

type RichTextBlock with
static member inlines<'t when 't :> RichTextBlock>(value: InlineCollection) : IAttr<'t> =
AttrBuilder<'t>.CreateProperty<InlineCollection>(RichTextBlock.InlinesProperty, value, ValueNone)

static member inlines<'t when 't :> RichTextBlock>(values: IView list (* TODO: Change to IView<Inline> *)) : IAttr<'t> =
let getter : ('t -> obj) = (fun control -> control.Inlines :> obj)
AttrBuilder<'t>.CreateContentMultiple("Inlines", ValueSome getter, ValueNone, values)
1 change: 0 additions & 1 deletion src/Avalonia.FuncUI/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IObserver<'args>, IDisposable>(fun observer ->
// push new update to subscribers
let handler = EventHandler<'args>(fun _ e ->
Expand Down
Loading

0 comments on commit eaebd3f

Please sign in to comment.