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

Style DSL #86

Open
bradphelan opened this issue Mar 17, 2020 · 7 comments
Open

Style DSL #86

bradphelan opened this issue Mar 17, 2020 · 7 comments
Labels
discussion enhancement New feature or request help wanted Extra attention is needed

Comments

@bradphelan
Copy link

bradphelan commented Mar 17, 2020

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
                }
            ] 
        ]
@JaggerJo JaggerJo added the enhancement New feature or request label Mar 17, 2020
@bradphelan
Copy link
Author

bradphelan commented Mar 17, 2020

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 :(

@bradphelan
Copy link
Author

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.

@JaggerJo
Copy link
Member

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>

@bradphelan
Copy link
Author

See comment above..... you can use yield and yield! inside list builders.

@bradphelan
Copy link
Author

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.

@JaggerJo
Copy link
Member

JaggerJo commented Mar 17, 2020

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).

@bradphelan
Copy link
Author

bradphelan commented Mar 18, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants