TimLariviere / Fabulous-new

Fabulous v2 - Work in progress
https://timothelariviere.com/Fabulous-new/
Other
41 stars 3 forks source link

Implement Style widgets #10

Open TimLariviere opened 2 years ago

TimLariviere commented 2 years ago

It would be great to enable styling through specialized Widgets like LabelStyle(), ButtonStyle(), etc. Those styles would be passed to .styles([...]) modifiers at both the control level and application level.

Label("Hello")
    .style(
        // Local style
        LabelStyle()
            .textColor(Color.Blue)
            .font(FontFamily.OpenSans)
        )
    )

Application(
    ContentPage(
        VerticalStackLayout([
            Button("Click me", OnClicked)

            Button("Destroy the world", OnDestroyClicked)
                .styleKey("destructive")
        ])
    )
)
    .styles([
        // Global style applied to all buttons
        ButtonStyle()
            .backgroundColor(light = Color.Gray, dark = Color.Yellow)

        // Global style only applied to buttons with style key `destructive`
        ButtonStyle("destructive")
            .textColor(Color.Red)
    ])

The idea of those widgets will be to make them map to

<Application>
    <Application.Resources>
        <Style TargetType="Button">
            <Setter PropertyName="BackgroundColor" Value="{AppThemeBinding Light=Color.Gray, Dark=Color.Yellow}" />
        </Style>
        <Style TargetType="Button" Key="destructive">
            <Setter PropertyName="TextColor" Value="Color.Red" />
        </Style>
    </Application.Resources>
</Application>

To implement this, you can take a look at Xamarin.Forms.Core.Attributes.fs and Xamarin.Forms.Core.fs.

I think the setters will be declared as attributes (via a custom defineWidget => defineSetter) and styles like widgets (type [<Struct>] LabelStyle (...)).

If you want to work on this task, put a message after this comment. Also try putting everything you write in a new file (eg. Xamarin.Forms.Core.Style.fs) so you won't have too many conflicts if we change stuff.

edgarfgp commented 2 years ago

@TimLariviere Will this styles also work with VisualStateManager ?

edgarfgp commented 2 years ago

I would like to implement this.

TimLariviere commented 2 years ago

Will this styles also work with VisualStateManager ?

Hmm, VSM is a separate thing even though it's very similar. I'm not an expert on VSM but from what I read it's using BindableProperty like everything else so v2 can support it with no problem.

Maybe start with only standard styles. I'll open another issue for VSM support.

edgarfgp commented 2 years ago

I was having a look at how we can make Style a Bindable object to be able to leverage the Styles widgets and I think we can subclass it expose it like in the https://github.com/xamarin/XamarinCommunityToolkit/blob/main/src/Markup/Xamarin.CommunityToolkit.Markup/Style.cs

TimLariviere commented 2 years ago

I tried to create style widgets with the base type Xamarin.Forms.Style, but it's not possible with v2 architecture.

We need each node of the UI Tree (XF controls) to have a ViewNode. ViewNode is the one applying the diffs calculated by the Reconciler. So without it, it can't work.

Then, I tried to follow your suggestion @edgarfgp, and implemented a BindableStyle. It sort of works. You can see my changes on this branch https://github.com/TimLariviere/Fabulous-new/tree/style-widgets

But there's a major problem. Fabulous traverses the UI tree to find ViewNodes and BindableStyle is not part of the UI tree since XF only expects XF.Style (which is sealed, so no inheritance).

TimLariviere commented 2 years ago

Let's keep it for a later release. @edgarfgp added the possibility to pass directly a Xamarin.Forms.Style, so should be good enough for now.

edgarfgp commented 2 years ago

Thanks for the detailed explanation :) . Agreed with the existing style extension should be good enough . Hopefully the Maui version will make easier .