From fdae86b7162455d23b32e25811a7e36e6a07edd7 Mon Sep 17 00:00:00 2001 From: Exoridus Date: Fri, 28 Aug 2026 07:08:35 +0200 Subject: [PATCH 1/2] feat(ui): state a widget background the way you have it Options and setters on Panel, Button and ProgressBar now take a colour, a texture, an atlas region or a full descriptor. A texture becomes a nine-slice whose slices default to a third of the source per axis; a colour becomes a fill override, so the skin's corner radius and border survive it. A new sprite background variant stretches or tiles flat art, and ProgressBar.fillMode decides whether the bar's art is cut to the value ('clip', the default) or squashed with it ('scale') - a fill is painted at the value's width either way, which keeps the stock bar clear of the render barrier a clip imposes. --- site/src/content/api/button-options.json | 201 +++++++++++++++- site/src/content/api/button.json | 62 ++++- site/src/content/api/functions.json | 88 ++++++- site/src/content/api/panel-options.json | 150 +++++++++++- site/src/content/api/panel.json | 41 ++-- .../content/api/progress-bar-fill-mode.json | 71 ++++++ .../src/content/api/progress-bar-options.json | 219 +++++++++++++++++- site/src/content/api/progress-bar.json | 162 ++++++++++++- site/src/content/api/uibackground-input.json | 87 +++++++ .../src/content/api/uibackground-options.json | 190 +++++++++++++++ site/src/content/api/uibackground.json | 10 +- site/src/content/api/uisprite-background.json | 121 ++++++++++ site/src/content/api/uisprite-fit.json | 71 ++++++ .../content/guide/runtime/ui-and-widgets.mdx | 30 ++- src/core/serialization/uiSerializers.ts | 5 + src/ui/Button.ts | 66 +++++- src/ui/Panel.ts | 50 +++- src/ui/ProgressBar.ts | 128 +++++++++- src/ui/UIClipBox.ts | 44 ++++ src/ui/WidgetBackground.ts | 21 +- src/ui/index.ts | 8 +- src/ui/theme.ts | 77 +++++- .../root-index-snapshot.test.ts.snap | 1 + .../root-index-type-inventory.test.ts.snap | 6 + test/ui/theme.test.ts | 46 +++- test/ui/widgets.test.ts | 92 ++++++++ 26 files changed, 1963 insertions(+), 84 deletions(-) create mode 100644 site/src/content/api/progress-bar-fill-mode.json create mode 100644 site/src/content/api/uibackground-input.json create mode 100644 site/src/content/api/uibackground-options.json create mode 100644 site/src/content/api/uisprite-background.json create mode 100644 site/src/content/api/uisprite-fit.json create mode 100644 src/ui/UIClipBox.ts diff --git a/site/src/content/api/button-options.json b/site/src/content/api/button-options.json index deffe0c5b..7bdc5371c 100644 --- a/site/src/content/api/button-options.json +++ b/site/src/content/api/button-options.json @@ -6,11 +6,11 @@ "subsystem": "ui", "importPath": "@codexo/exojs", "tier": "stable", - "memberCount": 10, + "memberCount": 15, "counts": { "constructors": 0, "methods": 0, - "properties": 10, + "properties": 15, "events": 0 }, "sections": [ @@ -26,6 +26,51 @@ "id": "properties", "title": "Properties", "members": [ + { + "name": "border", + "signature": "border?: Partial | number", + "signatureTokens": [ + { + "text": "border", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "NineSliceInsets", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Destination border widths for every textured skin entry; defaults to slices." + }, { "name": "color", "signature": "color?: Color", @@ -101,6 +146,31 @@ "returnType": null, "description": "" }, + { + "name": "fit", + "signature": "fit?: UISpriteFit", + "signatureTokens": [ + { + "text": "fit", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UISpriteFit", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "Paint textured skin entries flat with this fit instead of slicing them." + }, { "name": "fontSize", "signature": "fontSize?: number", @@ -201,6 +271,31 @@ "returnType": null, "description": "" }, + { + "name": "modes", + "signature": "modes?: NineSliceModes", + "signatureTokens": [ + { + "text": "modes", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "NineSliceModes", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "" + }, { "name": "pressedColor", "signature": "pressedColor?: Color", @@ -226,6 +321,108 @@ "returnType": null, "description": "" }, + { + "name": "skin", + "signature": "skin?: Partial>", + "signatureTokens": [ + { + "text": "skin", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "Record", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "ButtonState", + "kind": "type" + }, + { + "text": ", ", + "kind": "punctuation" + }, + { + "text": "UIBackgroundInput", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": ">", + "kind": "punctuation" + } + ], + "params": [], + "returnType": null, + "description": "The background per state, each stated as a colour, a texture, a region or a full descriptor. States left out fall back to the theme's button skin." + }, + { + "name": "slices", + "signature": "slices?: Partial | number", + "signatureTokens": [ + { + "text": "slices", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "NineSliceInsets", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Source-texture slice widths for every textured skin entry; defaults to a third per axis." + }, { "name": "textColor", "signature": "textColor?: Color", diff --git a/site/src/content/api/button.json b/site/src/content/api/button.json index 9d715c6fb..72a184599 100644 --- a/site/src/content/api/button.json +++ b/site/src/content/api/button.json @@ -6,11 +6,11 @@ "subsystem": "ui", "importPath": "@codexo/exojs", "tier": "stable", - "memberCount": 127, + "memberCount": 128, "counts": { "constructors": 1, "methods": 60, - "properties": 51, + "properties": 52, "events": 15 }, "sections": [ @@ -2042,7 +2042,7 @@ }, { "name": "setBackground", - "signature": "setBackground(background: UIBackground | null, state: UIWidgetState): this", + "signature": "setBackground(background: UIBackgroundInput | null, state: UIWidgetState, options: UIBackgroundOptions): this", "signatureTokens": [ { "text": "setBackground", @@ -2061,7 +2061,7 @@ "kind": "punctuation" }, { - "text": "UIBackground", + "text": "UIBackgroundInput", "kind": "type" }, { @@ -2088,6 +2088,22 @@ "text": "UIWidgetState", "kind": "type" }, + { + "text": ", ", + "kind": "punctuation" + }, + { + "text": "options", + "kind": "param" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UIBackgroundOptions", + "kind": "type" + }, { "text": ")", "kind": "punctuation" @@ -2104,17 +2120,22 @@ "params": [ { "name": "background", - "type": "UIBackground | null", + "type": "UIBackgroundInput | null", "optional": false }, { "name": "state", "type": "UIWidgetState", "optional": false + }, + { + "name": "options", + "type": "UIBackgroundOptions", + "optional": false } ], "returnType": "this", - "description": "Replace one state's whole background descriptor; null restores the skin's." + "description": "Set one state's background from a colour, a texture, a region or a full descriptor; null returns that state to its skin. A colour becomes a fill override, so the skin's corner radius and border survive it." }, { "name": "setChildIndex", @@ -3259,6 +3280,35 @@ "returnType": null, "description": "Tab-traversal order among focusable nodes in the same focus scope. Lower values are visited first; equal values keep document (tree) order." }, + { + "name": "backgroundNode", + "signature": "backgroundNode: BackgroundNode | null", + "signatureTokens": [ + { + "text": "backgroundNode", + "kind": "name" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "BackgroundNode", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "null", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "The node painting the background, or null while it paints nothing." + }, { "name": "bottom", "signature": "bottom: number", diff --git a/site/src/content/api/functions.json b/site/src/content/api/functions.json index a9583ceb4..b50304506 100644 --- a/site/src/content/api/functions.json +++ b/site/src/content/api/functions.json @@ -6,10 +6,10 @@ "subsystem": "core", "importPath": "@codexo/exojs", "tier": "stable", - "memberCount": 76, + "memberCount": 77, "counts": { "constructors": 0, - "methods": 44, + "methods": 45, "properties": 32, "events": 0 }, @@ -821,6 +821,90 @@ "returnType": "GamepadMapping", "description": "Nintendo Switch Pro Controller over USB or Bluetooth. Index 17 is the Capture button (SWITCH_PRO_BUTTON_CAPTURE == BUTTON_INDEX_COUNT); Home maps to Guide, Minus/Plus to Select/Start. Some browsers only recognise the controller after it has been paired through Steam or a dedicated driver." }, + { + "name": "createUIBackground", + "signature": "createUIBackground(source: Texture | TextureRegion | UIBackground, options: UIBackgroundOptions): UIBackground", + "signatureTokens": [ + { + "text": "createUIBackground", + "kind": "name" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "source", + "kind": "param" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Texture", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "TextureRegion", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "UIBackground", + "kind": "type" + }, + { + "text": ", ", + "kind": "punctuation" + }, + { + "text": "options", + "kind": "param" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UIBackgroundOptions", + "kind": "type" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UIBackground", + "kind": "type" + } + ], + "params": [ + { + "name": "source", + "type": "Texture | TextureRegion | UIBackground", + "optional": false + }, + { + "name": "options", + "type": "UIBackgroundOptions", + "optional": false + } + ], + "returnType": "UIBackground", + "description": "Normalise a texture, region or descriptor into a background descriptor. A texture becomes a nine-slice whose slices default to a third of the source per axis, so a frame is usable without measuring it first; a real skin states its slices. A source edge under three pixels degenerates to a slice of zero, which stretches that axis rather than failing. Passing fit paints the texture flat instead, and a descriptor is returned unchanged." + }, { "name": "createUITheme", "signature": "createUITheme(patch: UIThemePatch, base: UITheme): UITheme", diff --git a/site/src/content/api/panel-options.json b/site/src/content/api/panel-options.json index 55d1bf986..e3bba68a4 100644 --- a/site/src/content/api/panel-options.json +++ b/site/src/content/api/panel-options.json @@ -6,11 +6,11 @@ "subsystem": "ui", "importPath": "@codexo/exojs", "tier": "stable", - "memberCount": 7, + "memberCount": 11, "counts": { "constructors": 0, "methods": 0, - "properties": 7, + "properties": 11, "events": 0 }, "sections": [ @@ -28,7 +28,7 @@ "members": [ { "name": "background", - "signature": "background?: UIBackground", + "signature": "background?: UIBackgroundInput", "signatureTokens": [ { "text": "background", @@ -43,13 +43,58 @@ "kind": "punctuation" }, { - "text": "UIBackground", + "text": "UIBackgroundInput", "kind": "type" } ], "params": [], "returnType": null, - "description": "Whole-background override, e.g. a nine-slice skin, replacing the theme's." + "description": "The panel's background, stated as a colour, a texture, a region or a full descriptor. A colour overrides the skin's fill and keeps the rest of it; anything else replaces the skin's background outright." + }, + { + "name": "border", + "signature": "border?: Partial | number", + "signatureTokens": [ + { + "text": "border", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "NineSliceInsets", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Destination border widths for a texture background; defaults to slices." }, { "name": "borderColor", @@ -151,6 +196,31 @@ "returnType": null, "description": "" }, + { + "name": "fit", + "signature": "fit?: UISpriteFit", + "signatureTokens": [ + { + "text": "fit", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UISpriteFit", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "Paint a texture background flat with this fit instead of slicing it." + }, { "name": "height", "signature": "height?: number", @@ -176,6 +246,76 @@ "returnType": null, "description": "" }, + { + "name": "modes", + "signature": "modes?: NineSliceModes", + "signatureTokens": [ + { + "text": "modes", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "NineSliceModes", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "" + }, + { + "name": "slices", + "signature": "slices?: Partial | number", + "signatureTokens": [ + { + "text": "slices", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "NineSliceInsets", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Source-texture slice widths for a texture background; defaults to a third per axis." + }, { "name": "width", "signature": "width?: number", diff --git a/site/src/content/api/panel.json b/site/src/content/api/panel.json index 5a22b0d8c..76e412834 100644 --- a/site/src/content/api/panel.json +++ b/site/src/content/api/panel.json @@ -1940,7 +1940,7 @@ }, { "name": "setBackground", - "signature": "setBackground(background: UIBackground | null): this", + "signature": "setBackground(background: UIBackgroundInput | null, options: UIBackgroundOptions): this", "signatureTokens": [ { "text": "setBackground", @@ -1959,7 +1959,7 @@ "kind": "punctuation" }, { - "text": "UIBackground", + "text": "UIBackgroundInput", "kind": "type" }, { @@ -1970,6 +1970,22 @@ "text": "null", "kind": "keyword" }, + { + "text": ", ", + "kind": "punctuation" + }, + { + "text": "options", + "kind": "param" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UIBackgroundOptions", + "kind": "type" + }, { "text": ")", "kind": "punctuation" @@ -1986,12 +2002,17 @@ "params": [ { "name": "background", - "type": "UIBackground | null", + "type": "UIBackgroundInput | null", + "optional": false + }, + { + "name": "options", + "type": "UIBackgroundOptions", "optional": false } ], "returnType": "this", - "description": "Replace the whole background descriptor, ignoring the skin's. null restores it. Layout-invalidating: a nine-slice and a fill can imply different content boxes." + "description": "Set the panel's background from a colour, a texture, a region or a full descriptor; null returns it to its skin. A colour becomes a fill override on top of the skin, so the skin's corner radius and border survive it. Layout-invalidating: a nine-slice and a fill can imply different content boxes." }, { "name": "setChildIndex", @@ -3083,7 +3104,7 @@ }, { "name": "backgroundNode", - "signature": "backgroundNode: Graphics | NineSliceSprite | null", + "signature": "backgroundNode: BackgroundNode | null", "signatureTokens": [ { "text": "backgroundNode", @@ -3094,15 +3115,7 @@ "kind": "punctuation" }, { - "text": "Graphics", - "kind": "type" - }, - { - "text": " | ", - "kind": "punctuation" - }, - { - "text": "NineSliceSprite", + "text": "BackgroundNode", "kind": "type" }, { diff --git a/site/src/content/api/progress-bar-fill-mode.json b/site/src/content/api/progress-bar-fill-mode.json new file mode 100644 index 000000000..e6c684996 --- /dev/null +++ b/site/src/content/api/progress-bar-fill-mode.json @@ -0,0 +1,71 @@ +{ + "title": "ProgressBarFillMode", + "description": "How the bar follows the value: `'scale'` paints the background at the value's width, `'clip'` paints it at full width and shows the leading fraction of it. Clipping is what keeps textured art undistorted; a fill has nothing to distort, so it is always painted at the value's width.", + "symbol": "ProgressBarFillMode", + "kind": "type", + "subsystem": "ui", + "importPath": "@codexo/exojs", + "tier": "stable", + "memberCount": 0, + "counts": { + "constructors": 0, + "methods": 0, + "properties": 0, + "events": 0 + }, + "sections": [ + { + "id": "import", + "title": "Import", + "members": [], + "paragraphs": [ + "How the bar follows the value: `'scale'` paints the background at the value's width, `'clip'` paints it at full width and shows the leading fraction of it. Clipping is what keeps textured art undistorted; a fill has nothing to distort, so it is always painted at the value's width." + ], + "importLine": "import { ProgressBarFillMode } from '@codexo/exojs'", + "sourceLink": null + }, + { + "id": "definition", + "title": "Definition", + "members": [ + { + "name": "ProgressBarFillMode", + "signature": "\"clip\" | \"scale\"", + "signatureTokens": [ + { + "text": "\"clip\"", + "kind": "keyword" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "\"scale\"", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "" + } + ], + "paragraphs": [], + "importLine": null, + "sourceLink": null + }, + { + "id": "source", + "title": "Source", + "members": [], + "paragraphs": [], + "importLine": null, + "sourceLink": { + "label": "src/ui/ProgressBar.ts", + "href": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/ProgressBar.ts" + } + } + ], + "sourcePath": "src/ui/ProgressBar.ts", + "sourceUrl": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/ProgressBar.ts" +} diff --git a/site/src/content/api/progress-bar-options.json b/site/src/content/api/progress-bar-options.json index abafe9eae..83fcdf934 100644 --- a/site/src/content/api/progress-bar-options.json +++ b/site/src/content/api/progress-bar-options.json @@ -6,11 +6,11 @@ "subsystem": "ui", "importPath": "@codexo/exojs", "tier": "stable", - "memberCount": 6, + "memberCount": 13, "counts": { "constructors": 0, "methods": 0, - "properties": 6, + "properties": 13, "events": 0 }, "sections": [ @@ -26,6 +26,76 @@ "id": "properties", "title": "Properties", "members": [ + { + "name": "barBackground", + "signature": "barBackground?: UIBackgroundInput", + "signatureTokens": [ + { + "text": "barBackground", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UIBackgroundInput", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "The bar's background, stated as a colour, a texture, a region or a descriptor." + }, + { + "name": "border", + "signature": "border?: Partial | number", + "signatureTokens": [ + { + "text": "border", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "NineSliceInsets", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Destination border widths for textured backgrounds; defaults to slices." + }, { "name": "cornerRadius", "signature": "cornerRadius?: number", @@ -76,6 +146,56 @@ "returnType": null, "description": "" }, + { + "name": "fillMode", + "signature": "fillMode?: ProgressBarFillMode", + "signatureTokens": [ + { + "text": "fillMode", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "ProgressBarFillMode", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "How the bar follows the value. Default 'clip'." + }, + { + "name": "fit", + "signature": "fit?: UISpriteFit", + "signatureTokens": [ + { + "text": "fit", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UISpriteFit", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "Paint textured backgrounds flat with this fit instead of slicing them." + }, { "name": "height", "signature": "height?: number", @@ -101,6 +221,101 @@ "returnType": null, "description": "" }, + { + "name": "modes", + "signature": "modes?: NineSliceModes", + "signatureTokens": [ + { + "text": "modes", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "NineSliceModes", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "" + }, + { + "name": "slices", + "signature": "slices?: Partial | number", + "signatureTokens": [ + { + "text": "slices", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "NineSliceInsets", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Source-texture slice widths for textured backgrounds; defaults to a third per axis." + }, + { + "name": "trackBackground", + "signature": "trackBackground?: UIBackgroundInput", + "signatureTokens": [ + { + "text": "trackBackground", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UIBackgroundInput", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "The groove's background, stated as a colour, a texture, a region or a descriptor." + }, { "name": "trackColor", "signature": "trackColor?: Color", diff --git a/site/src/content/api/progress-bar.json b/site/src/content/api/progress-bar.json index cc23ebb5e..9e3925390 100644 --- a/site/src/content/api/progress-bar.json +++ b/site/src/content/api/progress-bar.json @@ -6,11 +6,11 @@ "subsystem": "ui", "importPath": "@codexo/exojs", "tier": "stable", - "memberCount": 124, + "memberCount": 128, "counts": { "constructors": 1, "methods": 59, - "properties": 50, + "properties": 54, "events": 14 }, "sections": [ @@ -1940,7 +1940,7 @@ }, { "name": "setBarBackground", - "signature": "setBarBackground(background: UIBackground | null): this", + "signature": "setBarBackground(background: UIBackgroundInput | null, options: UIBackgroundOptions): this", "signatureTokens": [ { "text": "setBarBackground", @@ -1959,7 +1959,7 @@ "kind": "punctuation" }, { - "text": "UIBackground", + "text": "UIBackgroundInput", "kind": "type" }, { @@ -1970,6 +1970,22 @@ "text": "null", "kind": "keyword" }, + { + "text": ", ", + "kind": "punctuation" + }, + { + "text": "options", + "kind": "param" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UIBackgroundOptions", + "kind": "type" + }, { "text": ")", "kind": "punctuation" @@ -1986,12 +2002,17 @@ "params": [ { "name": "background", - "type": "UIBackground | null", + "type": "UIBackgroundInput | null", + "optional": false + }, + { + "name": "options", + "type": "UIBackgroundOptions", "optional": false } ], "returnType": "this", - "description": "Replace the bar's whole background descriptor; null restores the skin's." + "description": "Set the bar's background from a colour, a texture, a region or a full descriptor; null returns it to its skin. A colour becomes a fill override, so the skin's corner radius survives it." }, { "name": "setBarFill", @@ -2660,7 +2681,7 @@ }, { "name": "setTrackBackground", - "signature": "setTrackBackground(background: UIBackground | null): this", + "signature": "setTrackBackground(background: UIBackgroundInput | null, options: UIBackgroundOptions): this", "signatureTokens": [ { "text": "setTrackBackground", @@ -2679,7 +2700,7 @@ "kind": "punctuation" }, { - "text": "UIBackground", + "text": "UIBackgroundInput", "kind": "type" }, { @@ -2690,6 +2711,22 @@ "text": "null", "kind": "keyword" }, + { + "text": ", ", + "kind": "punctuation" + }, + { + "text": "options", + "kind": "param" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UIBackgroundOptions", + "kind": "type" + }, { "text": ")", "kind": "punctuation" @@ -2706,12 +2743,17 @@ "params": [ { "name": "background", - "type": "UIBackground | null", + "type": "UIBackgroundInput | null", + "optional": false + }, + { + "name": "options", + "type": "UIBackgroundOptions", "optional": false } ], "returnType": "this", - "description": "Replace the track's whole background descriptor; null restores the skin's." + "description": "Set the groove's background from a colour, a texture, a region or a full descriptor; null returns it to its skin. A colour becomes a fill override, so the skin's corner radius survives it." }, { "name": "setTrackFill", @@ -3191,6 +3233,56 @@ "returnType": null, "description": "The background painted for the filled portion." }, + { + "name": "barNode", + "signature": "barNode: BackgroundNode | null", + "signatureTokens": [ + { + "text": "barNode", + "kind": "name" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "BackgroundNode", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "null", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "The node painting the bar, or null while it paints nothing." + }, + { + "name": "barVisibleWidth", + "signature": "barVisibleWidth: number", + "signatureTokens": [ + { + "text": "barVisibleWidth", + "kind": "name" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Width in pixels of the bar that is actually visible: uiWidth * value." + }, { "name": "bottom", "signature": "bottom: number", @@ -3508,6 +3600,27 @@ "returnType": null, "description": "Bar colour, or null when the bar does not paint a fill." }, + { + "name": "fillMode", + "signature": "fillMode: ProgressBarFillMode", + "signatureTokens": [ + { + "text": "fillMode", + "kind": "name" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "ProgressBarFillMode", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "How the bar follows the value." + }, { "name": "fillOverrides", "signature": "fillOverrides: { bar: UIFillPatch | null; track: UIFillPatch | null }", @@ -4133,6 +4246,35 @@ "returnType": null, "description": "Track colour, or null when the track does not paint a fill." }, + { + "name": "trackNode", + "signature": "trackNode: BackgroundNode | null", + "signatureTokens": [ + { + "text": "trackNode", + "kind": "name" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "BackgroundNode", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "null", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "The node painting the groove, or null while it paints nothing." + }, { "name": "uiHeight", "signature": "uiHeight: number", diff --git a/site/src/content/api/uibackground-input.json b/site/src/content/api/uibackground-input.json new file mode 100644 index 000000000..ec8ff09aa --- /dev/null +++ b/site/src/content/api/uibackground-input.json @@ -0,0 +1,87 @@ +{ + "title": "UIBackgroundInput", + "description": "What a background can be stated as. A texture or region becomes a textured descriptor, a colour becomes a fill; a descriptor is taken as it is.", + "symbol": "UIBackgroundInput", + "kind": "type", + "subsystem": "ui", + "importPath": "@codexo/exojs", + "tier": "stable", + "memberCount": 0, + "counts": { + "constructors": 0, + "methods": 0, + "properties": 0, + "events": 0 + }, + "sections": [ + { + "id": "import", + "title": "Import", + "members": [], + "paragraphs": [ + "What a background can be stated as. A texture or region becomes a textured descriptor, a colour becomes a fill; a descriptor is taken as it is." + ], + "importLine": "import { UIBackgroundInput } from '@codexo/exojs'", + "sourceLink": null + }, + { + "id": "definition", + "title": "Definition", + "members": [ + { + "name": "UIBackgroundInput", + "signature": "Color | Texture | TextureRegion | UIBackground", + "signatureTokens": [ + { + "text": "Color", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "Texture", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "TextureRegion", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "UIBackground", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "" + } + ], + "paragraphs": [], + "importLine": null, + "sourceLink": null + }, + { + "id": "source", + "title": "Source", + "members": [], + "paragraphs": [], + "importLine": null, + "sourceLink": { + "label": "src/ui/theme.ts", + "href": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/theme.ts" + } + } + ], + "sourcePath": "src/ui/theme.ts", + "sourceUrl": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/theme.ts" +} diff --git a/site/src/content/api/uibackground-options.json b/site/src/content/api/uibackground-options.json new file mode 100644 index 000000000..e0bd286a3 --- /dev/null +++ b/site/src/content/api/uibackground-options.json @@ -0,0 +1,190 @@ +{ + "title": "UIBackgroundOptions", + "description": "How a texture is turned into a background descriptor.", + "symbol": "UIBackgroundOptions", + "kind": "interface", + "subsystem": "ui", + "importPath": "@codexo/exojs", + "tier": "stable", + "memberCount": 4, + "counts": { + "constructors": 0, + "methods": 0, + "properties": 4, + "events": 0 + }, + "sections": [ + { + "id": "import", + "title": "Import", + "members": [], + "paragraphs": [ + "How a texture is turned into a background descriptor." + ], + "importLine": "import { UIBackgroundOptions } from '@codexo/exojs'", + "sourceLink": null + }, + { + "id": "properties", + "title": "Properties", + "members": [ + { + "name": "border", + "signature": "border?: Partial | number", + "signatureTokens": [ + { + "text": "border", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "NineSliceInsets", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Destination border widths; defaults to slices." + }, + { + "name": "fit", + "signature": "fit?: UISpriteFit", + "signatureTokens": [ + { + "text": "fit", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UISpriteFit", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "Paint the texture flat with this fit instead of slicing it." + }, + { + "name": "modes", + "signature": "modes?: NineSliceModes", + "signatureTokens": [ + { + "text": "modes", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "NineSliceModes", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "" + }, + { + "name": "slices", + "signature": "slices?: Partial | number", + "signatureTokens": [ + { + "text": "slices", + "kind": "name" + }, + { + "text": "?", + "kind": "punctuation" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Partial", + "kind": "type" + }, + { + "text": "<", + "kind": "punctuation" + }, + { + "text": "NineSliceInsets", + "kind": "type" + }, + { + "text": ">", + "kind": "punctuation" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "Source-texture slice widths in texels; defaults to a third of the source per axis." + } + ], + "paragraphs": [], + "importLine": null, + "sourceLink": null + }, + { + "id": "source", + "title": "Source", + "members": [], + "paragraphs": [], + "importLine": null, + "sourceLink": { + "label": "src/ui/theme.ts", + "href": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/theme.ts" + } + } + ], + "sourcePath": "src/ui/theme.ts", + "sourceUrl": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/theme.ts" +} diff --git a/site/src/content/api/uibackground.json b/site/src/content/api/uibackground.json index 256609483..0927a660b 100644 --- a/site/src/content/api/uibackground.json +++ b/site/src/content/api/uibackground.json @@ -30,7 +30,7 @@ "members": [ { "name": "UIBackground", - "signature": "UIFillBackground | UINineSliceBackground | UINoBackground", + "signature": "UIFillBackground | UINineSliceBackground | UINoBackground | UISpriteBackground", "signatureTokens": [ { "text": "UIFillBackground", @@ -51,6 +51,14 @@ { "text": "UINoBackground", "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "UISpriteBackground", + "kind": "type" } ], "params": [], diff --git a/site/src/content/api/uisprite-background.json b/site/src/content/api/uisprite-background.json new file mode 100644 index 000000000..7dcaa64f1 --- /dev/null +++ b/site/src/content/api/uisprite-background.json @@ -0,0 +1,121 @@ +{ + "title": "UISpriteBackground", + "description": "Flat textured background: one image stretched over the widget box or tiled across it. Use a nine-slice instead for art with a frame that must keep its corner proportions.", + "symbol": "UISpriteBackground", + "kind": "interface", + "subsystem": "ui", + "importPath": "@codexo/exojs", + "tier": "stable", + "memberCount": 3, + "counts": { + "constructors": 0, + "methods": 0, + "properties": 3, + "events": 0 + }, + "sections": [ + { + "id": "import", + "title": "Import", + "members": [], + "paragraphs": [ + "Flat textured background: one image stretched over the widget box or tiled across it. Use a nine-slice instead for art with a frame that must keep its corner proportions." + ], + "importLine": "import { UISpriteBackground } from '@codexo/exojs'", + "sourceLink": null + }, + { + "id": "properties", + "title": "Properties", + "members": [ + { + "name": "fit", + "signature": "fit: UISpriteFit", + "signatureTokens": [ + { + "text": "fit", + "kind": "name" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "UISpriteFit", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "" + }, + { + "name": "kind", + "signature": "kind: \"sprite\"", + "signatureTokens": [ + { + "text": "kind", + "kind": "name" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "\"sprite\"", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "" + }, + { + "name": "texture", + "signature": "texture: Texture | TextureRegion", + "signatureTokens": [ + { + "text": "texture", + "kind": "name" + }, + { + "text": ": ", + "kind": "punctuation" + }, + { + "text": "Texture", + "kind": "type" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "TextureRegion", + "kind": "type" + } + ], + "params": [], + "returnType": null, + "description": "" + } + ], + "paragraphs": [], + "importLine": null, + "sourceLink": null + }, + { + "id": "source", + "title": "Source", + "members": [], + "paragraphs": [], + "importLine": null, + "sourceLink": { + "label": "src/ui/theme.ts", + "href": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/theme.ts" + } + } + ], + "sourcePath": "src/ui/theme.ts", + "sourceUrl": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/theme.ts" +} diff --git a/site/src/content/api/uisprite-fit.json b/site/src/content/api/uisprite-fit.json new file mode 100644 index 000000000..e1f75bd94 --- /dev/null +++ b/site/src/content/api/uisprite-fit.json @@ -0,0 +1,71 @@ +{ + "title": "UISpriteFit", + "description": "How a sprite background fills the widget box.", + "symbol": "UISpriteFit", + "kind": "type", + "subsystem": "ui", + "importPath": "@codexo/exojs", + "tier": "stable", + "memberCount": 0, + "counts": { + "constructors": 0, + "methods": 0, + "properties": 0, + "events": 0 + }, + "sections": [ + { + "id": "import", + "title": "Import", + "members": [], + "paragraphs": [ + "How a sprite background fills the widget box." + ], + "importLine": "import { UISpriteFit } from '@codexo/exojs'", + "sourceLink": null + }, + { + "id": "definition", + "title": "Definition", + "members": [ + { + "name": "UISpriteFit", + "signature": "\"stretch\" | \"tile\"", + "signatureTokens": [ + { + "text": "\"stretch\"", + "kind": "keyword" + }, + { + "text": " | ", + "kind": "punctuation" + }, + { + "text": "\"tile\"", + "kind": "keyword" + } + ], + "params": [], + "returnType": null, + "description": "" + } + ], + "paragraphs": [], + "importLine": null, + "sourceLink": null + }, + { + "id": "source", + "title": "Source", + "members": [], + "paragraphs": [], + "importLine": null, + "sourceLink": { + "label": "src/ui/theme.ts", + "href": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/theme.ts" + } + } + ], + "sourcePath": "src/ui/theme.ts", + "sourceUrl": "https://github.com/Exoridus/ExoJS/blob/main/src/ui/theme.ts" +} diff --git a/site/src/content/guide/runtime/ui-and-widgets.mdx b/site/src/content/guide/runtime/ui-and-widgets.mdx index f97728e96..2019e8a9e 100644 --- a/site/src/content/guide/runtime/ui-and-widgets.mdx +++ b/site/src/content/guide/runtime/ui-and-widgets.mdx @@ -63,7 +63,35 @@ dialog.setTheme({ panel: { normal: { background: dark.panel.normal.background } dialog.setFill({ color: new Color(40, 44, 60, 1) }); // this panel only, on top of its skin ``` -A background descriptor is either a fill (the rounded rectangle above), `{ kind: 'none' }`, or a nine-slice: `{ kind: 'nineSlice', texture, slices: 8 }` paints a texture that stretches without distorting its corners. +A background descriptor is either a fill (the rounded rectangle above), `{ kind: 'none' }`, a nine-slice that stretches a texture without distorting its corners, or a sprite that stretches or tiles flat art. + +## Textured skins + +Anywhere a background is taken you can state it the way you have it - a colour, a texture, an atlas region, or a full descriptor. A texture becomes a nine-slice; a colour becomes a fill override on top of the skin, so the skin's corner radius and border survive it: + +```ts +import { Button, Color, Panel, ProgressBar, type Texture } from '@codexo/exojs'; + +declare const frame: Texture; +declare const idle: Texture; +declare const hot: Texture; + +const panel = new Panel({ width: 280, height: 160, background: frame }); +const start = new Button({ label: 'Start', skin: { normal: idle, hover: hot, disabled: new Color(70, 76, 90, 1) } }); +``` + +Without `slices`, a texture is sliced at a third of its source size per axis - a usable starting point, not a substitute for stating the slices a skin was drawn for. Pass them (and, where the corners should be drawn at a different scale, `border`) alongside the background: `panel.setBackground(frame, { slices: 8, border: 16 })`. For flat art that has no frame to preserve, pass `fit` instead: `{ fit: 'stretch' }` or `{ fit: 'tile' }`. + +A [`ProgressBar`](/ExoJS/en/api/progress-bar/) decides how its bar follows the value with `fillMode`. The default `'clip'` paints the bar's art at full width and shows the leading fraction of it, which is what keeps a textured bar undistorted; `'scale'` paints it at the value's width instead. A fill has no art to distort and is always painted at the value's width: + +```ts +import { ProgressBar, type Texture } from '@codexo/exojs'; + +declare const barArt: Texture; + +const health = new ProgressBar({ width: 240, height: 14, value: 0.6, barBackground: barArt, slices: 6 }); +health.fillMode = 'scale'; // squash the art with the value instead of cutting it +``` Per-widget style always goes through a setter — `setFill`, `setBackground`, `setTextStyle` — so a change repaints (and re-lays out where it can move something) immediately. Reading `panel.color` gives the value in effect, including the theme's; `panel.fillOverrides` gives only what this panel overrides. diff --git a/src/core/serialization/uiSerializers.ts b/src/core/serialization/uiSerializers.ts index 982fc72b3..a4889acf8 100644 --- a/src/core/serialization/uiSerializers.ts +++ b/src/core/serialization/uiSerializers.ts @@ -2,6 +2,7 @@ import type { RenderNode } from '#rendering/RenderNode'; import { Button } from '#ui/Button'; import { Label } from '#ui/Label'; import { Panel } from '#ui/Panel'; +import type { ProgressBarFillMode } from '#ui/ProgressBar'; import { ProgressBar } from '#ui/ProgressBar'; import { ScrollContainer, type ScrollDirection } from '#ui/ScrollContainer'; import { Stack } from '#ui/Stack'; @@ -153,6 +154,8 @@ const buttonSerializer: NodeSerializer