Avalonia.FuncUI
Avalonia.FuncUI copied to clipboard
Style DSL
I had a go at hacking a style DSL. It seems to work. I'm sure you could pick it apart but maybe it's a start for some ideas. XAML really should die!!
module Control =
open Avalonia.Styling
open Avalonia.Controls
open FSharpx
let styling stylesList =
let styles = Styles()
for style in stylesList do
styles.Add style
Control.styles styles
let style (selector:Selector->Selector) (setters:IAttr<'a> seq) =
let s = Style(fun x -> selector x )
for attr in setters do
match attr.Property with
| Some p ->
match p.accessor with
| InstanceProperty x -> failwith "Can't support instance property"
| AvaloniaProperty x -> s.Setters.Add(Setter(x,p.value))
| None -> ()
s
and then in my view
let private binFileTemplate (indexedBinFile: IndexedBinFile) (binFileViewer:BinFile->unit) (dispatch: Msg -> unit) =
let (id, binFile) = indexedBinFile
let foreground =
match binFile.eq_status with
| DEGREE_EQUAL -> "green"
| DEGREE_DIFFERENT -> "red"
| DEGREE_EXCEPTION_0|DEGREE_EXCEPTION_1|DEGREE_EXCEPTION_2 -> "yellow"
| DEGREE_SIMILAR|DEGREE_EQUAL_IN_TOLERANCE -> "darkgreen"
| _ -> "brightred"
(* create row for name, eq_status, deviation *)
StackPanel.create [
StackPanel.orientation Orientation.Horizontal
StackPanel.background "black"
StackPanel.onDoubleTapped (fun _ -> ViewBinFile(binFileViewer, binFile) |> dispatch )
let style = [
TextBlock.width columnWidth
TextBlock.foreground foreground
TextBlock.horizontalAlignment HorizontalAlignment.Left
]
StackPanel.children [
TextBlock.createFromSeq <| seq {
yield TextBlock.text binFile.name
yield! style
}
TextBlock.createFromSeq <| seq {
TextBlock.text (binFile.eq_status |> DU.toString )
yield! style
}
TextBlock.createFromSeq <| seq {
TextBlock.text (binFile.deviation |> sprintf "%g")
yield! style
}
]
]
Strangely the above didn't quite work. On the first rendering of the list box, that the above was a data template for, it rendered the correct colors for each row. On the second rendering after filtering or sorting the style was locked to the index of the list and not reapplied. For the moment I have used a more FPish strategy for styling.
let private binFileTemplate (indexedBinFile: IndexedBinFile) (binFileViewer:BinFile->unit) (dispatch: Msg -> unit) =
let (id, binFile) = indexedBinFile
let foreground =
match binFile.eq_status with
| DEGREE_EQUAL -> "green"
| DEGREE_DIFFERENT -> "red"
| DEGREE_EXCEPTION_0|DEGREE_EXCEPTION_1|DEGREE_EXCEPTION_2 -> "yellow"
| DEGREE_SIMILAR|DEGREE_EQUAL_IN_TOLERANCE -> "darkgreen"
| _ -> "brightred"
(* create row for name, eq_status, deviation *)
StackPanel.create [
StackPanel.orientation Orientation.Horizontal
StackPanel.background "black"
StackPanel.onDoubleTapped (fun _ -> ViewBinFile(binFileViewer, binFile) |> dispatch )
let style = [
TextBlock.width columnWidth
TextBlock.foreground foreground
TextBlock.horizontalAlignment HorizontalAlignment.Left
]
StackPanel.children [
TextBlock.createFromSeq <| seq {
yield TextBlock.text binFile.name
yield! style
}
TextBlock.createFromSeq <| seq {
TextBlock.text (binFile.eq_status |> DU.toString )
yield! style
}
TextBlock.createFromSeq <| seq {
TextBlock.text (binFile.deviation |> sprintf "%g")
yield! style
}
]
]
It's a pity that the create methods accept List<'a> rather than Seq<'a>. I've had to create an overload
module TextBlock =
let createFromSeq s = TextBlock.create (s |> Seq.toList)
but that would have to be done for all controls which is unfortunate. Is there any way to change the create methods to Seq<'a> and have them continue to work. I don't think so because create is a module function and you can't overload module functions and F# doesn't understand covariance :(
Ahhh...I just realized you can use yield and yield inside a list builder so I can write
StackPanel.children [
TextBlock.create [
yield TextBlock.text binFile.name
yield! style
]
TextBlock.create [
TextBlock.text (binFile.eq_status |> DU.toString )
yield! style
]
TextBlock.create [
TextBlock.text (binFile.deviation |> sprintf "%g")
yield! style
]
]
and no need for anything to be changed.
Strangely the above didn't quite work. On the first rendering of the list box, that the above was a data template for, it rendered the correct colors for each row. On the second rendering after filtering or sorting the style was locked to the index of the list and not reapplied. For the moment I have used a more FPish strategy for styling.
let private binFileTemplate (indexedBinFile: IndexedBinFile) (binFileViewer:BinFile->unit) (dispatch: Msg -> unit) = let (id, binFile) = indexedBinFile let foreground = match binFile.eq_status with | DEGREE_EQUAL -> "green" | DEGREE_DIFFERENT -> "red" | DEGREE_EXCEPTION_0|DEGREE_EXCEPTION_1|DEGREE_EXCEPTION_2 -> "yellow" | DEGREE_SIMILAR|DEGREE_EQUAL_IN_TOLERANCE -> "darkgreen" | _ -> "brightred" (* create row for name, eq_status, deviation *) StackPanel.create [ StackPanel.orientation Orientation.Horizontal StackPanel.background "black" StackPanel.onDoubleTapped (fun _ -> ViewBinFile(binFileViewer, binFile) |> dispatch ) let style = [ TextBlock.width columnWidth TextBlock.foreground foreground TextBlock.horizontalAlignment HorizontalAlignment.Left ] StackPanel.children [ TextBlock.createFromSeq <| seq { yield TextBlock.text binFile.name yield! style } TextBlock.createFromSeq <| seq { TextBlock.text (binFile.eq_status |> DU.toString ) yield! style } TextBlock.createFromSeq <| seq { TextBlock.text (binFile.deviation |> sprintf "%g") yield! style } ] ]
Hmm, need to look into this further. I think patching / diffing Styles is the hard part. Classes do work well if they styles need to change.
It would be really nice to have this for style classes.
It's a pity that the create methods accept List<'a> rather than Seq<'a>. I've had to create an overload
module TextBlock = let createFromSeq s = TextBlock.create (s |> Seq.toList)
but that would have to be done for all controls which is unfortunate. Is there any way to change the create methods to Seq<'a> and have them continue to work. I don't think so because create is a module function and you can't overload module functions and F# doesn't understand covariance :(
Hmm, yeah. maybe it would make sense to change it everywhere to:
#seq<IAttr<'view>> -> IView<'view>
See comment above..... you can use yield
and yield!
inside list builders.
I don't really understand how the virtual dom / diffing / patching works. If you happen to write a blog post on what's going on then I would love to read it.
Yeah, I should do that (and maybe add it to the Wiki - not just because I don't have a blog 😃). It's actually not that complicated (or at least I try to keep things simple).
Another (maybe) bug with style diffing. If I add duplicate properties to a textblock then I would assume that the last one wins. ie
TextBlock.create [
TextBlock.width 100.0
TextBlock.width 200.0
]
However I've seen, at least in the context of my datatemplate that it seems to randomly pick between the two. This is important when using yield!
to do styling. My code looked like
TextBlock.create [
yield! style
TextBlock.text binFile.name
ToolTip.tip binFile.name
TextBlock.width columnWidth
]
with the defaults in style and overrides in the array.