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
The 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 boxvarerrorBoxTemplate=newStyleTemplate().BackgroundColor(Color.Crimson).BorderColor(Color.White).BorderWidth(2).Rounded(8);
You can then register this with a unique name:
// In your app's initializationpaper.RegisterStyle("error-box",errorBoxTemplate);// And apply it to an elementpaper.Box("ErrorMessage").Style("error-box");
The StyleFamilyBuilder: Creating Interactive Styles
While 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:
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.).
Style Inheritance with 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.
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 calls DefineStyles() again.
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.
// 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!
// This one line gives the element the base style AND all its
// interactive hover/active effects.
paper.Box("MyButton").Style("button");
// 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");
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();
}
}