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

Proposal: Styling capabilities for the std-widgets #5392

Open
FloVanGH opened this issue Jun 12, 2024 · 10 comments
Open

Proposal: Styling capabilities for the std-widgets #5392

FloVanGH opened this issue Jun 12, 2024 · 10 comments
Labels
a:widgets Implementation of widgets (from std-widgets.slint) and their styles (mF,bS) rfc Request for comments: proposals for changes

Comments

@FloVanGH
Copy link
Member

FloVanGH commented Jun 12, 2024

While it is already possible to do custom styling with Slint by doing custom widget development like [SurrealismUI] (https://github.com/Surrealism-All/SurrealismUI) and coop a highly requested features is to do change style settings on the Slint's std-widgets like background, border-color and font-size. The profit for developers is that they have something to start with and they don't need to start from scratch. Connected to this features I see some question that we should also keep in mind to help developers by doing custom styling base on the std widgets:

  • How can we help developers to keep consistency in their styling if they want to styling that should fit in one dedicated style e.g. fluent
  • How can we help developers to keep consistency in their styling if they want to styling that should fit in all styles

What I have in mind to solve this questions it to provide design / styling guidelines and best practices. I think this could follow as seconds step after custom styling is done.

Before going in to details of this proposal I want to say about the possibility to create custom widgets that matches e.g. the colors settings of one of the provides Slint styles like fluent or material. There is already a global with that settings called Palette there will be soon a more complex example that displays how to work with it called TodoMVC, more details soon ;-).

I see there two different ways to implement this feature:

Base widgets

First one is to provide the developers a set of all std widgets as base with no predefined styling. Benefit of this is, the developer / designer is completely free, could do easily a complete custom design for the std widgets without the need to start from scratch. So it would be possible to use base widgets with predefined layout, content and behavior. Disadvantage of this is that for support all std-widget it is necessary to do styling for all std widgets:

Example

import { MyFancyPalette } from "my-fancy-styling.slint";
import { ButtonBase } from "std-widgets.slint";

export component MyFancyButton inherits ButtonBase {
    // do the styling here
}

Add styling to the current std-widget

Other approach is to add styling properties to the current std-widgets. Profit is the developer / designer don't need to start from scratch and don't need to do styling for all widgets. Disadvantage is if it should work with all styles it needs to ensured that colors e.g. that are set works with all styles. It is harder to ensure consistency with the style.

For styling in common we should keep in mind that wee need to provide this also for the different states of a widget. E.g. it's not enough to just change the background. Depending on the style there are different background brushes depending on the state e.g. a background-hover and a background-pressed.

I see there two way to add styling properties to the widgets:

Add properties directly to the widgets

I see there one disadvantage this will bloat the api of a widget a lot with properties.

import { MyFancyPalette } from "my-fancy-styling.slint";
import { Button } from "std-widgets.slint";

export component MyFancyButton inherits Button {
     background: MyFancyPalette.control-background;
     background-pressed: MyFancyPalette.control-background-pressed;
     background-hover: MyFancyPalette.control-background-hover;
}

Add a style property to each widget (style as struct)

Disadvantage right now is that it is not possible to overwrite single field of a struct. So all field that are not set will be set to default values check #5201. Profit is it give the style a clean structure. There is one extra property per widget. Styles can be also stored in a global.

import { MyFancyPalette } from "my-fancy-styling.slint";
import { Button } from "std-widgets.slint";

export component MyFancyButton inherits Button {
     style: {
          background: MyFancyPalette.control-background;
          background-pressed: MyFancyPalette.control-background-pressed;
          background-hover: MyFancyPalette.control-background-hover;
     }
}

I hope that gives people that are requesting styling features for std widgets some answers and a way how we can solve this. So I'm open for feedback and I hope that is something we can put on the agenda for the near future.

@FloVanGH FloVanGH added a:widgets Implementation of widgets (from std-widgets.slint) and their styles (mF,bS) rfc Request for comments: proposals for changes labels Jun 12, 2024
@FloVanGH
Copy link
Member Author

FloVanGH commented Jun 12, 2024

One point I have missed. Base widget and std-widgets styling are not mutually exclusive and could be implemented both. So maybe it is just a question do we want both and if yes with what we should start first.

@tronical
Copy link
Member

Could you provide an example of how the ButtonBase API looks like, roughly?

@FloVanGH
Copy link
Member Author

Could you provide an example of how the ButtonBase API looks like, roughly?

Principle it would be the same api as std button without predefined styling and with a style or multiple style properties.

@tronical
Copy link
Member

Ahh, right, so either way we have to first answer the question how to declare the style able properties: As struct, as individual properties, or using a third method (to be determined), right?

@FloVanGH
Copy link
Member Author

Ahh, right, so either way we have to first answer the question how to declare the style able properties: As struct, as individual properties, or using a third method (to be determined), right?

Yes that's right. First we need to define how the styling should looks like multiple properties vs. styling properties. I have a favorite ;-). And then I think first step could be to implement it for the std-widgets. And after that we could also think about to provide unstyled std-widgets.

@ogoffart
Copy link
Member

I agree that we need ButtonBase, SpinBoxBase and the like, that contains the "interface" and the logic.
In fact, these should be style agnostics and be in "std-widgets-base.slint" or "@std/widgets-base.slint"

I think changing some base properties like font and palette on all style seems like a good idea as well. (See also #116 (comment) )

We could also think of a smarter @children or named children so we could replace part of a widget. This also needs Slint language extension.

@FloVanGH
Copy link
Member Author

One additional advantage of style stucts is that it is also easier to style inner elements like items of a the StandardListVIew:

export MyListView inherits StandardListView {
      style: {
            item-style: {
                  background: black;
            }
      }
}

@FloVanGH
Copy link
Member Author

We could also think of a smarter @children or named children so we could replace part of a widget. This also needs Slint language extension.

That's also a good idea. Maybe this could be a second step.

Maybe for more inspiration. I'm currently doing a refactoring on my coop widgets. For the button this looks like

foundation/button_base.slint

import { IconStyle } from "./icon_base.slint";
import { TextStyle } from "./text_base.slint";
import { FocusTouchArea } from "./focus_touch_area.slint";

export struct ButtonStyle {
    // background_layer
    background: brush,
    border_brush: brush,
    border_radius: length,
    border_width: length,

    // content_layer
    icon_style: IconStyle,
    text_style: TextStyle,

    // layout
    padding_left: length,
    padding_right: length,
    padding_top: length,
    padding_bottom: length,
    spacing: length,
    alignment: LayoutAlignment,
}

export component ButtonBase {
    // states
    in property <bool> enabled <=> touch_area.enabled;
    out property <bool> has_hover <=> touch_area.has_hover;
    out property <bool> has_focus <=> touch_area.has_focus;
    out property <bool> pressed <=> touch_area.pressed;
    out property <bool> enter_pressed <=> touch_area.enter_pressed;
    
    // styling
    in property <ButtonStyle> style;
    out property <IconStyle> icon_style: root.style.icon_style;
    out property <TextStyle> text_style: root.style.text_style;

    in property <MouseCursor> mouse_cursor <=> touch_area.mouse_cursor;

    // callbacks
    callback clicked <=> touch_area.clicked;

    forward_focus: touch_area;

    accessible_role: button;
    accessible_action_default => {
        touch_area.clicked();
    }
    
    touch_area := FocusTouchArea {
        width: 100%;
        height: 100%;
    }

    background_layer := Rectangle {
        background: root.style.background;
        border_color: root.style.border_brush;
        border_radius: root.style.border_radius;
        border_width: root.style.border_width;
    }

    @children
}

pure/pure_button_base.slint

pure is the name of coop's default style

import { ButtonBase } from "../foundation.slint";
import { StateLayer } from "./state_layer.slint";

export enum ButtonAction {
    default,
    primary,
    destructive
}

export component PureButtonBase inherits ButtonBase {
    in property <length> preferred_min_width;
    in property <length> preferred_min_height;

    min_width: max(root.preferred_min_width, content_layer.min_width);
    min_height: max(root.preferred_min_height, content_layer.min_height);
    vertical_stretch: 0;
    mouse_cursor: root.enabled ? pointer : default;

    state_layer := StateLayer {
        width: 100%;
        height: 100%;
        border_radius: root.style.border_radius;
        pressed: root.pressed || root.enter_pressed;
        has_hover: root.has_hover;
        has_focus: root.has_focus;
    }
    
    content_layer := HorizontalLayout {
        padding_left: root.style.padding_left;
        padding_right: root.style.padding_right;
        padding_top: root.style.padding_top;
        padding_bottom: root.style.padding_bottom;
        spacing: root.style.spacing;
        
        @children
    }

    states [
        disabled when !root.enabled : {
            root.opacity: 0.5;
        }
    ]
}

pure/outline_button.slint

import { ButtonStyle } from "../foundation.slint";
import { PureButtonBase } from "./pure_button_base.slint";
import { PureText } from "./pure_text.slint";
import { PureIcon } from "./pure_icon.slint";
import { PurePalette, PureFontSettings, PureIconSettings, PureSizeSettings, PureGapSettings, PureBorderSettings } from "./styling.slint";

export component OutlineButton inherits PureButtonBase {
    in property <string> text;
    in property <image> icon;

    style: {
        border_radius: PureBorderSettings.control_border_radius,
        border_width: 1px,
        border_brush: PurePalette.border,
        text_style: {
            font_size: PureFontSettings.body.font_size,
            font_weight: PureFontSettings.body.font_weight,
            foreground: PurePalette.control_foreground,
        },
        icon_style: {
            icon_size: PureIconSettings.body.icon_size,
            foreground: PurePalette.accent_background,
        },
        padding_left: PureGapSettings.control_padding,
        padding_right: PureGapSettings.control_padding,
        spacing: PureGapSettings.control_spacing,
        alignment: LayoutAlignment.center,
    };
    
    preferred_min_height: PureSizeSettings.control_height;

    accessible-label: root.text;
    
    if root.icon.width > 0 && root.icon.height > 0 : PureIcon {
        y: (root.height - self.height) / 2;
        icon: root.icon;
        style: root.icon_style;
    }

    if root.text != "" : PureText {
        text: root.text;
        style: root.text_style;
        vertical_alignment: center;
    }
}

@WilstonOreo
Copy link
Contributor

WilstonOreo commented Jun 13, 2024

I am in favor of having "interface widgets" like ButtonBase in a std-widgets-base.slint file.
But I like the idea with style structs, too.
Both style structs and Base widgets will be part of the public API, so we should be careful about naming here.

On note from my side: When I worked on Qt projects, we often distinguished between "Styling" and "Theming"
A Style defines the overall look, layout and behaviour of a widget, whereas a Theme defines only the coloring and fonts.
E.g. in a car HMI you have brand-specific style but with a day-/night theme.
You may want to consider that :)

@FloVanGH
Copy link
Member Author

FloVanGH commented Jun 14, 2024

I am in favor of having "interface widgets" like ButtonBase in a std-widgets-base.slint file. But I like the idea with style structs, too. Both style structs and Base widgets will be part of the public API, so we should be careful about naming here.

Thank you for your feedback. Yes I think to to have the base stuff to give a blueprint of the api of each widget and also the base component / layout setup. So it can easily used if someone create a widget set based on a custom design.

On note from my side: When I worked on Qt projects, we often distinguished between "Styling" and "Theming" A Style defines the overall look, layout and behaviour of a widget, whereas a Theme defines only the coloring and fonts. E.g. in a car HMI you have brand-specific style but with a day-/night theme. You may want to consider that :)

Yes that's true we should be careful in the naming. So actual in my proposal a Style defines the look of a specific widget with color, size and font settings. And the global styling settings for all widgets based on the same design system (design guidelines) would be the theme. And there could be multiple variants of one theme e.g. dark, light, hight-contrast. So if you check the current structure of the std-widgets themes would be fluent, material, cosmic, cupertino and qt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a:widgets Implementation of widgets (from std-widgets.slint) and their styles (mF,bS) rfc Request for comments: proposals for changes
Projects
None yet
Development

No branches or pull requests

4 participants