wuira 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +70 -2
- package/addon.toc.in +4 -0
- package/cli/uira.cjs +55 -0
- package/package.json +24 -4
- package/src/Console.ts +10 -0
- package/src/UIra.ts +18 -0
- package/src/console-polyfill.ts +25 -0
- package/src/datasources/DataSource.ts +42 -0
- package/src/datasources/NativeNumberSource.ts +7 -0
- package/src/datasources/ProgressSource.ts +46 -0
- package/src/datasources/Resources.ts +41 -0
- package/src/examples/ProgressBars.tsx +24 -0
- package/src/index.tsx +3 -0
- package/src/platform/forever.d.ts +62 -0
- package/src/playground/PlayerFrame.ts +271 -0
- package/src/types/Anchor.ts +7 -0
- package/src/types/Color.ts +6 -0
- package/src/types/Position.ts +21 -0
- package/src/types/Spacing.ts +14 -0
- package/src/types/Texture.ts +13 -0
- package/src/ui/Cell.tsx +26 -0
- package/src/ui/FlowLayout.tsx +23 -0
- package/src/ui/GridLayout.tsx +24 -0
- package/src/ui/Layout.ts +9 -0
- package/src/ui/ProgressBar.tsx +35 -0
- package/src/ui/decorations/Background.ts +29 -0
- package/src/ui/decorations/Border.ts +71 -0
- package/src/ui/decorations/Decoration.ts +59 -0
- package/src/ui/decorations/Decorations.ts +9 -0
- package/src/ui/decorations/Glow.ts +25 -0
- package/src/ui/decorations/backgroundTextures.ts +10 -0
- package/src/ui/decorations/borderTextures.ts +13 -0
- package/src/ui/decorations/statusBarTextures.ts +11 -0
- package/src/ui/layout/calculateFlow.ts +33 -0
- package/src/ui/layout/calculateGrid.ts +22 -0
- package/src/ui/layout/measureLayout.ts +87 -0
- package/src/ui/mount.ts +138 -0
- package/src/ui/primitives/jsx.ts +75 -0
- package/tools/module-loader.lua +16 -0
- package/tools/wow-modules.cjs +40 -0
- package/tsconfig.json +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Uira contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,71 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Uira
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The npm package and CLI command are named `wuira`. The framework API remains `UIra`. Future extension packages can use the `@wuira/` organization scope once that organization is registered.
|
|
4
|
+
|
|
5
|
+
After publication:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx wuira create MyAddon
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
A TypeScript and JSX UI framework for WoW Forever. TypeScriptToLua emits separate Lua files following the source directory structure. No Rollup or single-file Lua bundle is required.
|
|
12
|
+
|
|
13
|
+
## CLI
|
|
14
|
+
|
|
15
|
+
The CLI is not published to npm yet. Install dependencies in this repository and link the local command:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install
|
|
19
|
+
npm link
|
|
20
|
+
wuira create MyAddon
|
|
21
|
+
cd MyAddon
|
|
22
|
+
npm install
|
|
23
|
+
wuira build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Without a global link, use `node /path/to/UIRa/cli/uira.cjs create MyAddon`. Generated projects also support `npm run build`.
|
|
27
|
+
|
|
28
|
+
`create` refuses to overwrite an existing directory. It generates TypeScriptToLua configuration, addon metadata, a module loader, an MIT license, and this minimal `src/index.tsx`:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import "./uira/console-polyfill";
|
|
32
|
+
import { UI } from "./uira/ui/primitives/jsx";
|
|
33
|
+
import { UIra } from "./uira/UIra";
|
|
34
|
+
|
|
35
|
+
function App() {
|
|
36
|
+
console.log("MyAddon loaded");
|
|
37
|
+
return <></>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
UIra.Mount(App);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
An empty fragment creates no native frame. Only empty fragments are currently supported; use a layout for multiple visual elements.
|
|
44
|
+
|
|
45
|
+
`console.log/info/warn/error` use a global WoW console polyfill. Output goes to the local chat window, never to other players. The addon uses no Node.js runtime or stdin/stdout. `console.error` does not throw. Arguments are formatted with Lua `tostring`; objects are not deeply inspected. The CLI uses its own Node.js terminal console.
|
|
46
|
+
|
|
47
|
+
## Build and distribution
|
|
48
|
+
|
|
49
|
+
`wuira build` transpiles through `tsconfig.json`. It writes individual Lua modules, a loader, and a startup file to `dist/`, plus `<addonName>.toc` at the project root. The addon name lives in `uira.json`; Interface version and other metadata live in `addon.toc.in`. The default Interface version, 16001, targets the Forever playground.
|
|
50
|
+
|
|
51
|
+
Copy the generated `.toc` file and `dist/` directory to `Interface/AddOns/<addonName>/`. No Node.js runtime or separate Uira addon is required in game. Module registries are isolated by addon name.
|
|
52
|
+
|
|
53
|
+
This initial CLI copies framework sources into `src/uira/`. Updating the framework through a package is not supported yet. The existing repository's `src/index.tsx` playground remains intact.
|
|
54
|
+
|
|
55
|
+
## Development
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
npm run build
|
|
59
|
+
npm run check
|
|
60
|
+
npm test
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Cell is the basic visual box. FlowLayout and GridLayout arrange children. ProgressBar uses a native StatusBar, while data sources connect values to presentation. Behavior from other WoW versions must be verified against the Forever API before use.
|
|
64
|
+
|
|
65
|
+
Licensed under MIT; see [LICENSE](LICENSE).
|
|
66
|
+
|
|
67
|
+
## Releases
|
|
68
|
+
|
|
69
|
+
Every pushed Git tag triggers `.github/workflows/publish.yml`. The workflow checks types, builds the framework, runs tests, and publishes the package to npm. Tags must match `package.json` version, optionally prefixed with `v` (for example `v0.1.0`). npm versions cannot be published twice.
|
|
70
|
+
|
|
71
|
+
Before the first release, confirm that the package name in `package.json` is available or owned by your npm account. Configure the GitHub repository secret `NPM_TOKEN` with an npm granular access token authorized to publish this package and bypass 2FA for automation. Never commit the token. The repository also needs GitHub Actions enabled. No tag or package is published by local setup alone.
|
package/addon.toc.in
ADDED
package/cli/uira.cjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
const root = path.resolve(__dirname, "..");
|
|
6
|
+
|
|
7
|
+
function create(name, cwd = process.cwd()) {
|
|
8
|
+
if (!name || !/^[A-Za-z][A-Za-z0-9_-]*$/.test(name)) throw new Error("Use an addon name containing letters, numbers, underscores or hyphens, starting with a letter.");
|
|
9
|
+
const target = path.join(cwd, name);
|
|
10
|
+
if (fs.existsSync(target)) throw new Error(`Destination already exists: ${target}`);
|
|
11
|
+
fs.mkdirSync(target, { recursive: true });
|
|
12
|
+
const write = (file, value) => fs.writeFileSync(path.join(target, file), value);
|
|
13
|
+
fs.mkdirSync(path.join(target, "src"));
|
|
14
|
+
fs.cpSync(path.join(root, "src"), path.join(target, "src/uira"), {
|
|
15
|
+
recursive: true,
|
|
16
|
+
filter: source => !["index.tsx", "playground", "examples"].includes(path.basename(source)),
|
|
17
|
+
});
|
|
18
|
+
fs.cpSync(path.join(root, "tools"), path.join(target, "tools"), { recursive: true });
|
|
19
|
+
fs.mkdirSync(path.join(target, "cli"));
|
|
20
|
+
fs.copyFileSync(__filename, path.join(target, "cli/uira.cjs"));
|
|
21
|
+
const config = JSON.parse(fs.readFileSync(path.join(root, "tsconfig.json"), "utf8"));
|
|
22
|
+
write("tsconfig.json", JSON.stringify(config, null, 2) + "\n");
|
|
23
|
+
write("uira.json", JSON.stringify({ name }, null, 2) + "\n");
|
|
24
|
+
write("addon.toc.in", `## Interface: 16001\n## Title: ${name}\n## Notes: Built with Uira\n## Version: 0.1.0\n`);
|
|
25
|
+
write("src/index.tsx", `import "./uira/console-polyfill";\nimport { UI } from "./uira/ui/primitives/jsx";\nimport { UIra } from "./uira/UIra";\n\nfunction App() {\n console.log("${name} loaded");\n return <></>;\n}\n\nUIra.Mount(App);\n`);
|
|
26
|
+
const sourcePackage = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
27
|
+
const dependencies = { ...sourcePackage.devDependencies, ...sourcePackage.dependencies };
|
|
28
|
+
write("package.json", JSON.stringify({ name: name.toLowerCase(), version: "0.1.0", private: true,
|
|
29
|
+
license: "MIT", scripts: { build: "node cli/uira.cjs build", check: "tsc -p tsconfig.json --noEmit" },
|
|
30
|
+
devDependencies: { typescript: dependencies.typescript, "typescript-to-lua": dependencies["typescript-to-lua"] },
|
|
31
|
+
}, null, 2) + "\n");
|
|
32
|
+
fs.copyFileSync(path.join(root, "LICENSE"), path.join(target, "LICENSE"));
|
|
33
|
+
write(".gitignore", "node_modules/\ndist/\n*.toc\n");
|
|
34
|
+
write("README.md", `# ${name}\n\nInstall dependencies with \`npm install\`, then run \`wuira build\` or \`npm run build\`.\n\nThe entry is \`src/index.tsx\`. Its empty JSX creates no native frame; \`console.log\` writes only to the local WoW chat.\n\nCopy \`${name}.toc\` and \`dist/\` into \`Interface/AddOns/${name}/\`, then reload the game. Interface 16001 targets the current Forever playground; edit \`addon.toc.in\` for other clients.\n\nThis development scaffold includes Uira sources under \`src/uira\`; no separate in-game Uira addon is required.\n`);
|
|
35
|
+
return target;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function build(cwd = process.cwd()) {
|
|
39
|
+
if (!fs.existsSync(path.join(cwd, "tsconfig.json"))) throw new Error("Run wuira build inside an addon project.");
|
|
40
|
+
const compiler = require.resolve("typescript-to-lua/dist/tstl.js", { paths: [cwd, root] });
|
|
41
|
+
const result = spawnSync(process.execPath, [compiler, "-p", "tsconfig.json"], { cwd, stdio: "inherit" });
|
|
42
|
+
if (result.error) throw result.error;
|
|
43
|
+
if (result.status !== 0) throw new Error("Addon build failed.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (require.main === module) {
|
|
47
|
+
try {
|
|
48
|
+
const [command, name] = process.argv.slice(2);
|
|
49
|
+
if (command === "create") console.log(`Created ${create(name)}\nNext: cd ${name}, npm install, wuira build`);
|
|
50
|
+
else if (command === "build") build();
|
|
51
|
+
else if (!command || command === "--help") console.log("wuira create <addonName>\nwuira build");
|
|
52
|
+
else throw new Error(`Unknown command: ${command}`);
|
|
53
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
54
|
+
}
|
|
55
|
+
module.exports = { create, build };
|
package/package.json
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wuira",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Kerim Cetinbas",
|
|
6
|
+
"email": "a.cetinbas@icloud.com"
|
|
7
|
+
},
|
|
8
|
+
"description": "TypeScript and JSX UI framework and addon CLI for WoW Forever",
|
|
9
|
+
"files": ["cli/", "src/", "tools/", "tsconfig.json", "addon.toc.in", "README.md", "LICENSE"],
|
|
10
|
+
"engines": { "node": ">=22" },
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"bin": { "wuira": "cli/uira.cjs" },
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tstl -p tsconfig.json",
|
|
15
|
+
"watch": "tstl -p tsconfig.json --watch",
|
|
16
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
17
|
+
"test": "node --test tests/*.test.cjs"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"fengari-node-cli": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"typescript": "6.0.2",
|
|
24
|
+
"typescript-to-lua": "1.37.1"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/Console.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare function print(this: void, message: string): void;
|
|
2
|
+
|
|
3
|
+
/** Local client output only; never sends chat messages to other players. */
|
|
4
|
+
export class Console {
|
|
5
|
+
private constructor() {}
|
|
6
|
+
|
|
7
|
+
public static Echo(message: string): void {
|
|
8
|
+
print(`|cff60d9c5[Uira]|r ${message}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/UIra.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Element } from "./ui/primitives/jsx";
|
|
2
|
+
import type { Anchor } from "./types/Position";
|
|
3
|
+
import { mount, unmount } from "./ui/mount";
|
|
4
|
+
|
|
5
|
+
/** Public entry point for mounting JSX UI trees. */
|
|
6
|
+
export class UIra {
|
|
7
|
+
private constructor() {}
|
|
8
|
+
|
|
9
|
+
public static Mount(frame: Element | (() => Element), anchor?: Anchor, parent?: UiraNativeFrame): UiraNativeFrame | undefined {
|
|
10
|
+
const view = typeof frame === "function" ? frame() : frame;
|
|
11
|
+
if (view.kind === "empty") return undefined;
|
|
12
|
+
return mount(view, parent, anchor);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public static Unmount(frame: UiraNativeFrame): void {
|
|
16
|
+
unmount(frame);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
declare const _G: Record<string, unknown>;
|
|
2
|
+
declare function print(this: void, message: string): void;
|
|
3
|
+
declare function tostring(this: void, value: unknown): string;
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
const console: {
|
|
7
|
+
log(this: void, ...values: unknown[]): void;
|
|
8
|
+
info(this: void, ...values: unknown[]): void;
|
|
9
|
+
warn(this: void, ...values: unknown[]): void;
|
|
10
|
+
error(this: void, ...values: unknown[]): void;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function write(color: string, values: unknown[]): void {
|
|
15
|
+
print(`${color}${values.map(value => tostring(value)).join(" ")}|r`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Explicit side-effect import installs the WoW console; no Node runtime is used.
|
|
19
|
+
_G.console = {
|
|
20
|
+
log: function(this: void, ...values: unknown[]) { write("|cffffffff", values); },
|
|
21
|
+
info: function(this: void, ...values: unknown[]) { write("|cff60d9c5", values); },
|
|
22
|
+
warn: function(this: void, ...values: unknown[]) { write("|cffffcc00", values); },
|
|
23
|
+
error: function(this: void, ...values: unknown[]) { write("|cffff5555", values); },
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** A value-producing source. Secrecy describes data, not native frame protection. */
|
|
2
|
+
export abstract class DataSource<T> {
|
|
3
|
+
protected constructor(private value: T, private secret: boolean = false) {}
|
|
4
|
+
|
|
5
|
+
/** Checked when consumed, rather than inferred from combat state. */
|
|
6
|
+
public IsSecret(): boolean {
|
|
7
|
+
return this.IsValueSecret(this.GetValue());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public get Value(): T {
|
|
11
|
+
const value = this.GetValue();
|
|
12
|
+
this.AssertReadable(value);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public Compute<TResult>(compute: (value: T) => TResult): TResult {
|
|
17
|
+
const value = this.GetValue();
|
|
18
|
+
this.AssertReadable(value);
|
|
19
|
+
return compute(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
protected GetValue(): T { return this.value; }
|
|
23
|
+
|
|
24
|
+
private IsValueSecret(value: T): boolean {
|
|
25
|
+
return issecretvalue(value) || this.secret || this.HasSecretFields(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Replace the value and its explicit restriction together, clearing stale flags. */
|
|
29
|
+
protected SetValue(value: T, secret: boolean = false): void {
|
|
30
|
+
this.value = value;
|
|
31
|
+
this.secret = secret;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Aggregate sources override this to check known fields without comparing them. */
|
|
35
|
+
protected HasSecretFields(_value: T): boolean {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private AssertReadable(value: T): void {
|
|
40
|
+
if (this.IsValueSecret(value)) error("DataSource: secret data cannot be read or used in computations");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DataSource } from "./DataSource";
|
|
2
|
+
|
|
3
|
+
/** Read-only API value; no cached assignment or game-state mutation. */
|
|
4
|
+
export class NativeNumberSource extends DataSource<number> {
|
|
5
|
+
public constructor(private readonly read: () => number) { super(0); }
|
|
6
|
+
protected override GetValue(): number { return this.read(); }
|
|
7
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { DataSource } from "./DataSource";
|
|
2
|
+
import { NativeNumberSource } from "./NativeNumberSource";
|
|
3
|
+
|
|
4
|
+
export interface ProgressOverrides {
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
current?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Shared contract for health, power, cooldowns, and other progress values. */
|
|
11
|
+
export abstract class ProgressSource extends DataSource<number> {
|
|
12
|
+
public readonly Min: DataSource<number>;
|
|
13
|
+
public readonly Max: DataSource<number>;
|
|
14
|
+
public readonly Percent: DataSource<number>;
|
|
15
|
+
|
|
16
|
+
protected constructor(private readonly readers: {
|
|
17
|
+
current: () => number; min: () => number; max: () => number; percent: () => number;
|
|
18
|
+
}) {
|
|
19
|
+
super(0);
|
|
20
|
+
this.Min = new NativeNumberSource(readers.min);
|
|
21
|
+
this.Max = new NativeNumberSource(readers.max);
|
|
22
|
+
this.Percent = new NativeNumberSource(readers.percent);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected override GetValue(): number { return this.readers.current(); }
|
|
26
|
+
|
|
27
|
+
public override IsSecret(): boolean {
|
|
28
|
+
return super.IsSecret() || this.Min.IsSecret() || this.Max.IsSecret() || this.Percent.IsSecret();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public BindTo(frame: UiraNativeStatusBar, options: ProgressOverrides = {}): void {
|
|
32
|
+
const refresh = () => {
|
|
33
|
+
// Do not compare or calculate opaque native values in the direct path.
|
|
34
|
+
const current = options.current !== undefined ? options.current : this.readers.current();
|
|
35
|
+
frame.SetMinMaxValues(
|
|
36
|
+
options.min !== undefined ? options.min : this.readers.min(),
|
|
37
|
+
options.max !== undefined ? options.max : this.readers.max(),
|
|
38
|
+
);
|
|
39
|
+
frame.SetValue(current);
|
|
40
|
+
};
|
|
41
|
+
this.Listen(frame, refresh);
|
|
42
|
+
refresh();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected abstract Listen(frame: UiraNativeFrame, refresh: () => void): void;
|
|
46
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ProgressSource } from "./ProgressSource";
|
|
2
|
+
|
|
3
|
+
export const Unit = { Player: "player" } as const;
|
|
4
|
+
|
|
5
|
+
/** Direct API-to-widget binding; opaque values are never compared or calculated. */
|
|
6
|
+
abstract class Resource extends ProgressSource {
|
|
7
|
+
protected constructor(
|
|
8
|
+
current: () => number,
|
|
9
|
+
maximum: () => number,
|
|
10
|
+
percent: () => number,
|
|
11
|
+
private readonly unitToken: string,
|
|
12
|
+
private readonly events: string[],
|
|
13
|
+
) {
|
|
14
|
+
super({ current, min: () => 0, max: maximum, percent });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
protected Listen(frame: UiraNativeFrame, refresh: () => void): void {
|
|
18
|
+
frame.SetScript("OnEvent", (_frame, event, unit) => {
|
|
19
|
+
if (event === "PLAYER_ENTERING_WORLD" || unit === this.unitToken) refresh();
|
|
20
|
+
});
|
|
21
|
+
frame.RegisterEvent("PLAYER_ENTERING_WORLD");
|
|
22
|
+
for (const event of this.events) frame.RegisterEvent(event);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class HP extends Resource {
|
|
27
|
+
public constructor(public readonly unit: string) {
|
|
28
|
+
super(() => UnitHealth(unit), () => UnitHealthMax(unit),
|
|
29
|
+
() => UnitHealthPercent(unit, true, CurveConstants.ScaleTo100), unit, ["UNIT_HEALTH", "UNIT_MAXHEALTH"]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Explicit mana (power type 0), even while the unit uses rage/energy. */
|
|
34
|
+
export class Mana extends Resource {
|
|
35
|
+
public constructor(public readonly unit: string) {
|
|
36
|
+
super(() => UnitPower(unit, 0), () => UnitPowerMax(unit, 0),
|
|
37
|
+
() => UnitPowerPercent(unit, 0, true, CurveConstants.ScaleTo100), unit, ["UNIT_POWER_UPDATE", "UNIT_MAXPOWER", "UNIT_DISPLAYPOWER"]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const DataSources = { Resources: { HP, Mana } };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { UIra } from "../UIra";
|
|
2
|
+
import { HP, Mana } from "../datasources/Resources";
|
|
3
|
+
import { Cell } from "../ui/Cell";
|
|
4
|
+
import { FlowLayout } from "../ui/FlowLayout";
|
|
5
|
+
import { ProgressBar } from "../ui/ProgressBar";
|
|
6
|
+
import { UI } from "../ui/primitives/jsx";
|
|
7
|
+
|
|
8
|
+
function App() {
|
|
9
|
+
|
|
10
|
+
const mana = new Mana("player")
|
|
11
|
+
const hp = new HP("player")
|
|
12
|
+
return (
|
|
13
|
+
<FlowLayout width={160} height={80} >
|
|
14
|
+
<ProgressBar source={mana} />
|
|
15
|
+
<ProgressBar foreground={{
|
|
16
|
+
texture: "blizzard",
|
|
17
|
+
color: { r: 0.1, g: 0.1, b: 0.1 }
|
|
18
|
+
}} source={hp} />
|
|
19
|
+
</FlowLayout>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
UIra.Mount(App, "CENTER");
|
|
24
|
+
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Minimal API surface observed in locally installed Forever addons.
|
|
2
|
+
// Global functions use dot calls; widget methods use Lua's colon calls.
|
|
3
|
+
type UiraAnchor = import("../types/Position").Anchor;
|
|
4
|
+
type UiraDrawLayer = "BACKGROUND" | "OVERLAY";
|
|
5
|
+
interface UiraNativeTexture {
|
|
6
|
+
SetAllPoints(frame: UiraNativeFrame): void;
|
|
7
|
+
SetColorTexture(red: number, green: number, blue: number, alpha: number): void;
|
|
8
|
+
SetTexture(path: string): void;
|
|
9
|
+
SetVertexColor(red: number, green: number, blue: number, alpha: number): void;
|
|
10
|
+
SetBlendMode(mode: import("../types/Texture").BlendMode): void;
|
|
11
|
+
SetHorizTile(enabled: boolean): void;
|
|
12
|
+
SetVertTile(enabled: boolean): void;
|
|
13
|
+
SetTexCoord(left: number, right: number, top: number, bottom: number): void;
|
|
14
|
+
SetSize(width: number, height: number): void;
|
|
15
|
+
SetPoint(point: UiraAnchor, relativeTo: UiraNativeFrame, relativePoint: UiraAnchor, x: number, y: number): void;
|
|
16
|
+
SetParent(parent: UiraNativeFrame): void;
|
|
17
|
+
ClearAllPoints(): void;
|
|
18
|
+
Show(): void;
|
|
19
|
+
Hide(): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface UiraNativeFrame {
|
|
23
|
+
EnableMouse(enabled: boolean): void;
|
|
24
|
+
RegisterEvent(event: string): void;
|
|
25
|
+
UnregisterAllEvents(): void;
|
|
26
|
+
SetScript(script: "OnEvent", handler: ((this: void, frame: UiraNativeFrame, event: string, unit?: string) => void) | undefined): void;
|
|
27
|
+
GetWidth(): number;
|
|
28
|
+
GetHeight(): number;
|
|
29
|
+
SetSize(width: number, height: number): void;
|
|
30
|
+
SetPoint(point: UiraAnchor, relativeTo: UiraNativeFrame, relativePoint: UiraAnchor, x: number, y: number): void;
|
|
31
|
+
CreateTexture(name: undefined, layer: UiraDrawLayer): UiraNativeTexture;
|
|
32
|
+
Show(): void;
|
|
33
|
+
Hide(): void;
|
|
34
|
+
SetParent(parent: UiraNativeFrame): void;
|
|
35
|
+
ClearAllPoints(): void;
|
|
36
|
+
SetAllPoints(frame: UiraNativeFrame): void;
|
|
37
|
+
SetBackdrop(backdrop: { edgeFile: string; edgeSize: number }): void;
|
|
38
|
+
SetBackdropBorderColor(red: number, green: number, blue: number, alpha: number): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare const UIParent: UiraNativeFrame;
|
|
42
|
+
interface UiraNativeStatusBar extends UiraNativeFrame {
|
|
43
|
+
GetStatusBarTexture(): UiraNativeTexture;
|
|
44
|
+
SetMinMaxValues(min: number, max: number): void;
|
|
45
|
+
SetValue(value: number): void;
|
|
46
|
+
SetStatusBarTexture(path: string): void;
|
|
47
|
+
SetStatusBarColor(red: number, green: number, blue: number, alpha: number): void;
|
|
48
|
+
SetOrientation(orientation: "HORIZONTAL" | "VERTICAL"): void;
|
|
49
|
+
SetReverseFill(reverse: boolean): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare function CreateFrame(this: void, kind: "StatusBar", name: undefined, parent: UiraNativeFrame): UiraNativeStatusBar;
|
|
53
|
+
declare function CreateFrame(this: void, kind: "Frame", name: undefined, parent: UiraNativeFrame, template?: "BackdropTemplate"): UiraNativeFrame;
|
|
54
|
+
declare function error(this: void, message: string): never;
|
|
55
|
+
declare function issecretvalue(this: void, value: unknown): boolean;
|
|
56
|
+
declare function UnitHealth(this: void, unit: string): number;
|
|
57
|
+
declare function UnitHealthMax(this: void, unit: string): number;
|
|
58
|
+
declare function UnitPower(this: void, unit: string, powerType: number): number;
|
|
59
|
+
declare function UnitPowerMax(this: void, unit: string, powerType: number): number;
|
|
60
|
+
declare const CurveConstants: { readonly ScaleTo100: unknown };
|
|
61
|
+
declare function UnitHealthPercent(this: void, unit: string, predicted: boolean, curve: unknown): number;
|
|
62
|
+
declare function UnitPowerPercent(this: void, unit: string, powerType: number, predicted: boolean, curve: unknown): number;
|