fabulous-dev / Fabulous

Declarative UI framework for cross-platform mobile & desktop apps, using MVU and F# functional programming

Home Page:https://fabulous.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Modifier Behavior

reigam opened this issue · comments

I'm playing around with changing styles in app and noticed the following behavior:

[<Extension>]
type MyExtensions =
    [<Extension>]
    static member inline firstStyle(this: WidgetBuilder<'msg, #IVisualElement>) =
          this.verticalOptions(LayoutOptions.Start)
              .horizontalOptions(LayoutOptions.Start)
              .backgroundColor(Color.Blue.ToFabColor())
              .margin(30.)
              
    [<Extension>]
    static member inline secondStyle(this: WidgetBuilder<'msg, #IVisualElement>) =
              this.margin(60.)
    
    [<Extension>]
    static member inline myStyle(this: WidgetBuilder<'msg, #IVisualElement>, styleID)=
        match styleID with
        | 1 -> match box this with
                | :? WidgetBuilder<'msg, IButton> -> 
                    this.firstStyle()
                        .secondStyle()          
                | _ -> this                  
        | 2 -> match box this with
                | :? WidgetBuilder<'msg, IButton> ->
                    this.verticalOptions(LayoutOptions.Start)
                        .horizontalOptions(LayoutOptions.Start)
                        .margin(80.)
                        .backgroundColor(Color.Green.ToFabColor())         
                | _ ->
                    printfn "no valid IView found. Fall back to normal style"
                    this            
        | _ ->
            printfn "no valid StyleID selected. Fall back to normal style"
            this           

If I run the code like this, the button will show a margin of 60. when set to styleID 1 (so far so good). However, if set to styleID 2, the margin is set to 0. (instead of the wished for 80.). All other properties (color, layoutOptions) show up correctly.
The same behavior applies to other properties (e.g. color).
This can easily be fixed by removing margin(30.) from firstStyle.

(https://github.com/reigam/Fab2Demo/tree/Implement_Styles)

P.s.: I realize that it probably is bad practice to set the same property twice (I wanted to see what happens, if I overwrite something) . I only bring this up, because the "buggy(?)" behavior shows up further down the line, and not with the property itself.

match box this with
| :? WidgetBuilder<'msg, IButton> ->

Quick question, why are you testing the type here?
If your style only applies to Button, you can directly write myStyle(this: WidgetBuilder<'msg, #IButton>, styleID)

I realize that it probably is bad practice to set the same property twice (I wanted to see what happens, if I overwrite something)

Yes, the new DSL was not done with setting a same property multiple times in mind. It will simply keep all values for the same property and apply them one after the other and replace the old value. But Fabulous doesn't guarantee the order of applying, so the result could be inconsistent.

It's a current shortcoming of the DSL.
A fix could be done, but we need to check the cost of overwriting existing values.

@twop How can we handle that?
I guess doing a lookup on each AddScalar would not be great. Maybe we can do a deduplication pass on all attributes during Widget.Compile?

Quick question, why are you testing the type here? If your style only applies to Button, you can directly write myStyle(this: WidgetBuilder<'msg, #IButton>, styleID)

I shortened the code. The original plan was more like this:

        | 1 -> match box this with
                | :? WidgetBuilder<'msg, IButton> -> 
                    this.firstStyleSharedProperties()
                        .firstStyleButtonSpecificProperties          
                | :? WidgetBuilder<'msg, ILabel> -> 
                    this.firstStyleSharedProperties()
                        .firstStyleLabelSpecificProperties 
                | :? WidgetBuilder<'msg, IEntry> -> 
                    this.firstStyleSharedProperties()
                        .firstStyleEntrySpecificProperties 
                .....
                | _ -> this    

There are probably better ways to go about this. I'm just playing around with the concept.

It's a current shortcoming of the DSL.
A fix could be done, but we need to check the cost of overwriting existing values.

Ok. Seems easy enough to avoid.

commented

I guess doing a lookup on each AddScalar would not be great. Maybe we can do a deduplication pass on all attributes during Widget.Compile?

This is going to be on the hot path. However we can take the latest value in the diffing algorithm. It will slow down diffing but I assume not by much.

We can peek at the next attribute to see if it has the same key, if it does then just skip the current one and select the latest.

I can probably make it work in an evening, I was planning to do that initially but there were other "more important" things to tackle at the time :)

We can peek at the next attribute to see if it has the same key, if it does then just skip the current one and select the latest.

That would work because we keep the order of declaration (even with StackList) and sort by key, right?

commented

right, and the sort is stable, meaning it preserves the order of elements with the same key

We now only keep the latest modifier value in case the same modifier is applied several times