# 4. Themes and appearance #

← Shared foundation · Contents · Language and RTL →

## 4.1 Themes: two axes #

A theme is made of a style and a mode:

Axis | Values
--- | ---
Style (`is_theme_style`) | `fluent` · `metro` · `office` · `office2007` · `office2003`
Mode (`is_theme_mode`) | `light` · `dark`

That is ten themes, named `<style>-<mode>`: `fluent-light`, `fluent-dark`, `office2007-light`, `metro-dark`…

## 4.2 The application default theme (recommended) #

Set the theme once for the whole application, before the first window opens. It is injected into every component before its first render: no flash of a light style on a dark application.

    // open event of the application object
    PBT_SetDefaultTheme("fluent-dark")
    PBT_SetDefaultThemeAccent(RGB(0, 120, 212))   // optional

You can switch on the fly at any time: every component already open is re-themed instantly.

    // Light / dark toggle from a button in the application
    PBT_SetDefaultTheme("fluent-light")

Function | Effect
--- | ---
`PBT_SetDefaultTheme (string as_nom)` | Default theme of the process (broadcast to every component)
`PBT_GetDefaultTheme ( )` → string | Current default theme
`PBT_SetDefaultThemeAccent (long al_couleur)` | Default accent color (`-1` = the theme's native accent)
`PBT_GetDefaultThemeAccent ( )` → long | Current default accent

## 4.3 The theme of one specific component #

A component can depart from the default theme:

    uo_editeur.is_theme_style = uo_editeur.THEME_STYLE_OFFICE2007
    uo_editeur.is_theme_mode  = uo_editeur.THEME_MODE_DARK
    uo_editeur.il_theme_accent = RGB(200, 60, 40)     // -1 = the theme accent

Property | Type | Default | Purpose
--- | --- | --- | ---
`is_theme_style` | string | `fluent` | Visual style (`THEME_STYLE_*` constants)
`is_theme_mode` | string | `light` | Light or dark variant (`THEME_MODE_*` constants)
`il_theme_accent` | long | `-1` | Accent color of this component (`-1` = the theme accent)

An `of_reset()` brings the component back to the process default theme.

> 💡 A single theme for the whole application still looks best. Save local themes for special cases (a deliberately contrasted area, a theme preview).

## 4.4 Recolouring a component, a group or an item #

Three scopes, the same properties. Nothing to name, nothing to guess.

    // The whole component
    uo_ruban.il_theme_accent = RGB(0, 120, 90)

    // A group: everything inside it follows
    uo_ruban.of_tab("accueil").of_group("presse-papiers").il_accent = RGB(0, 120, 90)

    // One item
    uo_liste.of_item("supprimer").il_text_color = RGB(200, 70, 70)
    uo_liste.of_item("supprimer").il_back_color = RGB(255, 235, 235)

    // The same two, under the pointer
    uo_liste.of_item("supprimer").il_back_color_hover = RGB(255, 220, 220)

    // Back to the component's colour
    uo_liste.of_item("supprimer").il_text_color = -1

Property | Where | What it recolours
--- | --- | ---
`il_theme_accent` | the component | its accent, and everything derived from it: the readable text on top, the application background, the tab underline
`il_accent` | an item, group, tab or bar handle | what that zone paints with the accent, descendants included
`il_back_color` · `il_text_color` | same | the background and the text of the item
`il_back_color_hover` · `il_text_color_hover` | same | the same two, under the pointer

`-1` restores the colour the component gives, which itself comes from the theme. An item colour survives the component being rebuilt: it is carried by a style rule targeting the item, not by a property set on the node of the moment. `of_reset()` clears everything.

`il_accent` only repaints what the zone paints with the accent — a selection, an active underline, a progress bar. A component that never uses it will show nothing: for "this entry in red", `il_back_color` and `il_text_color` are the right tools, read by every component with items.

### The font of the whole application #

    PBT_SetDefaultFont("Segoe UI", 14)

One call dresses every live component and the ones created afterwards — the font is injected before their first paint. An empty family or a size of `0` gives that half back to the theme.

## 4.5 The component background is reported back to PowerBuilder #

Every component paints its background according to the theme, then reports its color: the userobject adopts that color (`backcolor`) and raises `ue_bg_color`, so that the window and the neighboring PowerBuilder controls can match it.

    // ue_bg_color event of a component
    parent.backcolor = al_color
    st_titre.backcolor = al_color

This is what lets you mix PBToolboxAI components and native PowerBuilder controls with no visible seam in dark mode.

## 4.6 Images and icons #

Everywhere a component expects an image path (button icon, tile, `[picture=…]`…), four forms are accepted:

Form | Example | Used for
--- | --- | ---
File | `img\logo.png` | The image as-is (png, jpg, gif, bmp, ico, svg, webp)
DLL resource | `img\packimages.dll:RIBBON` | An image packaged in a resource DLL
`mono:` | `mono:img\save.svg` | Flat fill in the theme color: only the shape matters
`tint:` | `tint:img\logo_couleur.png` | Duotone: the internal shading modulates the theme color

- Use `mono:` for every monochrome glyph (white or black icons): they recolor themselves automatically, in light mode as well as dark.
- `tint:` harmonizes a color icon with the theme while preserving its gradients. Never use it on a white glyph (it would stay white).
- With no prefix, a multicolor image is left untouched.

The `path.dll:name` form loads a resource from an image DLL (`packimages.dll` style), opened read-only (`LOAD_LIBRARY_AS_DATAFILE`, no code executed). This saves you from shipping hundreds of loose files.

### Instant display: `of_icon` #

A small glyph passed through `of_icon()` is embedded in the command (no loading round trip): it shows up on the very first render, without the flicker of an icon loaded afterwards.

    lnv_bar.of_add_button("save", "Save", uo_toolbar.of_icon("mono:img\save.svg"))

Transparent in practice: beyond a certain size, `of_icon` returns the original path (the image is then loaded and cached as usual).

## 4.7 Rich text markup #

Any label of any component accepts BBCode-style markup: tab title, button caption, status bar text, toast message, panel title, tooltip text…

The entries of the built-in menus follow the same rule — a tab's context menu, the `···` list of the tabs that no longer fit, a grid's column menus: the label shown by the menu is the one on the control, markup included.

The text is rendered as text nodes and `<span>` elements: no HTML injection is possible.

Tag | Effect
--- | ---
`[b] [i] [u] [s]` / `[strike]` | Bold, italic, underline, strikethrough
`[sub] [super]` | Subscript, superscript
`[red]…[/red]` (named colors) | Text color (`red`, `green`, `blue`, `orange`, `teal`…)
`[accent]…[/accent]` | Accent color of the current theme
`[color=#rrggbb]` / `[color=accent]` | Text color
`[bk=#rrggbb]` / `[backcolor=accent]` | Background color
`[font=Consolas]` | Font
`[size=14]` | Absolute size, in points (6 to 200)
`[size+=30]` / `[size-=20]` | Relative size in % (20% by default)
`[picture=path]` / `[picture=path,width,height]` | Inline image
`[br]` / `[linebreak]` / `[br:3]` | Line break (or n breaks)
`[separator]` | Horizontal rule
`[hyperlink=url]…[/hyperlink]` | Clickable area: the link always opens in the user's browser, in every component. The `ue_hyperlink(as_url)` event is raised as well, for the components that expose it
`[action=id]…[/action]` | Clickable area → `ue_action(as_id)` event, styled as a link
`[invisibleaction=id]…[/invisibleaction]` | Clickable area → `ue_action`, without the link styling
`[bullet]…[/bullet]` | Bullet: list item whose wrapped lines align on the first one instead of running back under the marker (hanging indent). `[bullet=-]` changes the marker
`[foldarea:Title]…[/foldarea]` | Collapsible block: a clickable header (`−` / `+`) above an indented body. The title accepts markup
`[foldarea-closed:Title]…[/foldarea]` | The same block, collapsed when displayed
`[[` / `]]` | Escaping: `[[b]]` displays `[b]` without interpreting it

    uo_texte.is_text = "Welcome to [b][accent]PBToolboxAI[/accent][/b] [size-=20]v1.0[/size-=20]" &
                       + "[br]See the [hyperlink=https://pbtoolboxai.net]documentation[/hyperlink]."

    uo_tab.of_add_page("clients", "[b]Customers[/b] [size-=20](128)[/size-=20]", uo_clients)

    uo_st.is_text = "The [[b]] tag makes text [b]bold[/b]"   // displays: The [b] tag makes text bold

Displaying data as-is. A value coming from your database may contain brackets: it would be interpreted. `of_escape_markup()`, available on every component, doubles them for you — wrap the data, never the markup you wrote yourself.

    // Business data may contain brackets : without escaping it is INTERPRETED
    // and the bracketed text vanishes.
    ls_libelle = "Balance [net]"
    uo_st.is_text = "Account : " + ls_libelle                          // shows : Account :
    uo_st.is_text = "Account : " + uo_st.of_escape_markup(ls_libelle)  // shows : Account : Balance [net]

Text without any tag carries no overhead at all (fast path). Unknown tags are ignored and their content is preserved. A `[hyperlink]` opens everywhere — a label, a tab title, a status bar panel, a toast, a dialog: the core takes care of it. The `ue_action` event, on the other hand, is only emitted by interactive text components (`statictext`); elsewhere, `[action]` is formatting only.

Only `http`, `https` and `mailto` are opened. A label often carries data coming from your database: handing an arbitrary scheme to the system would turn a label into a program launcher.

A `[foldarea]` is a block: it takes the full width and folds on a click on its header, with no round trip to PowerBuilder. Blocks nest, and when the component follows the height of its content (`ib_auto_height`), that height is reported again on every fold. The title is markup too: nothing is bolded for you, `[foldarea:[b]Total[/b]]` does it.

← Shared foundation · Contents · Language and RTL →