📎Advanced Styling: Reusable Style Definitions
As your UI grows, you'll find yourself applying the same combinations of .BackgroundColor()
, .Rounded()
, and other styling methods over and over. This is repetitive and makes updating your application's look and feel a chore.
To solve this, Paper provides a powerful Style Definition API that lets you create a centralized, reusable, and theme-able styling system, much like a CSS stylesheet.
The StyleTemplate
: A Reusable Bag of Properties
StyleTemplate
: A Reusable Bag of PropertiesThe most basic building block of this system is the StyleTemplate
. It's an object that holds a collection of style properties and their values.
// Create a new template that defines a red, rounded box
var errorBoxTemplate = new StyleTemplate()
.BackgroundColor(Color.Crimson)
.BorderColor(Color.White)
.BorderWidth(2)
.Rounded(8);
You can then register this with a unique name:
// In your app's initialization
paper.RegisterStyle("error-box", errorBoxTemplate);
// And apply it to an element
paper.Box("ErrorMessage").Style("error-box");
The StyleFamilyBuilder
: Creating Interactive Styles
StyleFamilyBuilder
: Creating Interactive StylesWhile RegisterStyle
is great for simple, static styles, most UI components need to react to user interaction (hover, click, etc.). For this, Paper provides the Style Family concept.
A Style Family is a collection of StyleTemplate
objects bundled together under a single name, defining the appearance for all of an element's interaction states. You create one using the fluent CreateStyleFamily()
builder.
.Base(new StyleTemplate())
: Defines the default, base styles for the element. It also often includes the transitions for all properties that will change..Hovered(new StyleTemplate())
: Defines the styles that will be added or overridden when the element is hovered..Active(new StyleTemplate())
: Defines the styles for when the element is being clicked/pressed..Focused(new StyleTemplate())
: Defines the styles for when the element is focused (e.g., a text field that is being typed in)..Register()
: Finalizes and registers the entire family so it can be used.
Here is how to create a complete, interactive "button" style family:
// In your theme initialization
paper.CreateStyleFamily("button")
.Base(new StyleTemplate()
// Base appearance
.Height(40)
.Rounded(8)
.BackgroundColor(Themes.secondaryColor)
// Define transitions for smooth effects
.Transition(GuiProp.BackgroundColor, 0.2)
.Transition(GuiProp.ScaleX, 0.1)
.Transition(GuiProp.ScaleY, 0.1))
.Hovered(new StyleTemplate()
// On hover, change the background
.BackgroundColor(Themes.primaryColor))
.Active(new StyleTemplate()
// When clicked, shrink slightly
.Scale(0.95))
.Register(); // Don't forget to register it!
Applying a Style Family
Applying a style family is incredibly simple. You only need to apply the base name using the .Style()
method. Paper will then automatically detect the element's state each frame and apply the correct styles from the family (:hovered
, :active
, etc.).
// This one line gives the element the base style AND all its
// interactive hover/active effects.
paper.Box("MyButton").Style("button");
Style Inheritance with DefineStyle()
DefineStyle()
A key feature for creating maintainable styles is inheritance. You can create a new style that inherits all the properties of one or more existing styles. This is perfect for creating variants of a component, like a primary button that shares base properties with a secondary button.
You use the Paper.DefineStyle(name, parentNames)
overload for this.
Let's create a base button
style and then a button-primary
variant that inherits from it.
// 1. Define the base button style. It handles size, rounding, and transitions.
paper.DefineStyle("button")
.Height(40)
.Rounded(8)
.Transition(GuiProp.BackgroundColor, 0.2)
.Transition(GuiProp.ScaleX, 0.1);
// 2. Define the primary variant, inheriting from "button"
// This template only needs to specify what's different: the colors.
paper.DefineStyle("button-primary", "button")
.BackgroundColor(Themes.primaryColor)
.Hovered(new StyleTemplate().BackgroundColor(Themes.secondaryColor)); // Note: Hovered is part of the variant
// 3. Register the full style family for the primary button
paper.CreateStyleFamily("button-primary")
.Base(Paper.GetStyle("button-primary")) // Use the inherited style as the base
.Hovered(Paper.GetStyle("button-primary:hovered"))
.Active(new StyleTemplate().Scale(0.95f))
.Register();
// Usage:
paper.Box("PrimaryAction").Style("button-primary");
This approach allows you to build a robust and hierarchical design system. Changes to the base "button"
style (like adjusting the Rounded
value) will automatically propagate to all variants that inherit from it.
Theming Your Application
The true power of this system is realized when you combine it with a theme. By defining your styles using theme color variables, you can change the entire look of your application on the fly.
Notice the pattern in the demo code:
A
Themes
class holds all the color variables (Themes.primaryColor
,Themes.cardBackground
, etc.).A
DefineStyles()
method registers all the style families using these variables.A
ToggleTheme()
method changes the color variables and then callsDefineStyles()
again.
public static class Themes
{
public static Color primaryColor;
public static Color backgroundColor;
// ... other theme colors
public static void ToggleTheme()
{
isDark = !isDark;
if (isDark)
{
primaryColor = Color.FromArgb(255, 69, 135, 235);
// ... set all dark theme colors
}
else
{
primaryColor = Color.FromArgb(255, 0, 149, 255);
// ... set all light theme colors
}
// Re-register all styles with the new colors
DefineStyles();
}
public static void DefineStyles()
{
// Use the theme variables to define styles
paper.CreateStyleFamily("button")
.Hovered(new StyleTemplate().BackgroundColor(primaryColor))
// ... etc
.Register();
paper.CreateStyleFamily("card")
.Base(new StyleTemplate().BackgroundColor(cardBackground))
// ... etc
.Register();
}
}
This approach allows you to build a robust and maintainable design system for your application, keeping your UI code clean, declarative, and completely separate from your styling logic. Next up, we'll see how to make these style changes smooth with the Animation System.
Last updated