Skip to content

UI extensions (QML) ​

Replace the host's UI components with QML (backplate, VU meter, caption view, decorations…). For the slot list and the background-drawing guidelines see UI slots — this page only covers how to write it.


Declaration ​

Use theme as the type (for a partial slot extension, style_layer: "effects" is recommended):

json
{
  "id": "community.theme.neon_wave",
  "name": "极光霓虹律动波形条",
  "version": "1.0.0",
  "target_app_version": "0.2",
  "type": "theme",
  "style_layer": "effects",
  "ui_extensions": {
    "audio_level": "CustomWaveform.qml"
  },
  "enabled": false
}

Only use slots the host already consumes; overlay_root / style_bar currently have no Loader — do not use them.


Injected properties ​

PropertyApplies toDescription
hostContextOverlay-window componentsstyle / subtitle / audio / window / invoke
bridgeAllPublic slots (aligned with Host SPI), e.g. bridge.getPluginConfig(pluginId)
pluginIdAllYour plugin id
appStateBackground componentsGlobal app state (bgColor / bgOpacity / neonBorderColor…)
window / targetWindowBackground componentsPointer to the target window instance
customRadiusBackground componentsCorner radius of the target window, injected by the host (Settings window 16, History window 16, generic dialogs 12…)
hoveredOverlay windowWhether the mouse is currently hovering

A property is assigned only if your component declares it — don't declare the ones you don't need.

For the complete property list and HostContext, see QML UI slots & HostContext.


Reading config: you must use Connections ​

⚠️ bridge.getPluginConfig(pluginId) is a transient function call, not a QML reactive property. If you read it only once, your component will not update when the user flips a switch in the settings panel.

qml
import QtQuick 2.15

Item {
    id: root
    anchors.fill: parent

    readonly property string myPluginId: "community.theme.my_theme"

    // 1. Initial read
    property var cfg: (typeof bridge !== "undefined" && bridge && bridge.getPluginConfig)
        ? bridge.getPluginConfig(root.myPluginId) : ({})

    // 2. Unwrap + defaults
    readonly property bool enableGlow: (cfg && typeof cfg.neon_glow !== "undefined") ? cfg.neon_glow : true

    // 3. You must listen to the signal and refresh cfg on the fly
    Connections {
        target: (typeof bridge !== "undefined") ? bridge : null
        function onPluginConfigChanged(pid) {
            if (pid === root.myPluginId) {
                root.cfg = bridge.getPluginConfig(root.myPluginId) || ({})
            }
        }
    }

    // 4. Bind to the visual layer
    Rectangle {
        anchors.fill: parent
        visible: root.enableGlow
        border.color: "#00f0ff"
        border.width: 1.5
    }
}

Visual rules (hard requirements) ​

  • Every plugin QML must adapt to theme switching automatically through ThemeTokens and PluggableWindowBackground.
  • Emojis or text characters must not be used as icons; use vector SVG (ThemeTokens.icon or an SVG shipped with the plugin).
  • Do not hard-code font families / icon colors inside components — hard-coded values give you away the moment the user changes theme; use ThemeTokens.*.
  • Background-type slots must draw their own border and adapt their corner radius; for details see UI slots § Background slot guidelines.

Calling the plugin's Python actions ​

On the QML side, call the plugin's action_* through the slot:

qml
// Synchronous (blocks the UI thread; only suitable for instant actions)
bridge.invokePluginAction(pluginId, "run", {"source": "ui"})

// For long-running actions use the async version, or have the Python side go through self.run_in_background()
bridge.invokePluginActionAsync(pluginId, "ask", {"prompt": "…"})

Events raised by the plugin are received in QML by pluginEvent(plugin_id, event_name, payload); see Event bus.


Hot debugging ​

After editing QML: disable and re-enable the plugin to reload it; or restart the app (during development you can use the host's hot-reload capability with TRANSBOX_QML_RELOAD=1, see Hot debugging).


TransBox — empowering borderless cross-language communication for everyone