Skip to content

Commit d52b13e

Browse files
author
Eaglenait
committed
Merge branch 'advanced-ui'
2 parents 3f30c8c + 8e50fe4 commit d52b13e

18 files changed

+1266
-53
lines changed

angular.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
{
3333
"glob": "**/*",
3434
"input": "public"
35+
},
36+
{
37+
"glob": "**/*.template.ts",
38+
"input": "src/app/advanced-presets/templates",
39+
"output": "templates"
3540
}
3641
],
3742
"styles": [
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { getElementNamesRecursive, UIAdvancedPresetDefinition, UIAnchor, UIBgFill, UIElement, UIParams } from '../../models/types';
2+
import { IAdvancedExportGenerator, loadTemplate, replacePlaceholders } from '../services/export/advanced-export.builder';
3+
4+
/**
5+
* Export generator for the counter preset.
6+
*
7+
* Generates a TypeScript class that wraps a counter widget with automatic increment functionality.
8+
* This implementation uses a template file (counter.widget.template.ts) for better maintainability.
9+
* The template approach provides:
10+
* - Proper TypeScript syntax highlighting and validation in the template file
11+
* - Easier testing and iteration on the generated code structure
12+
* - Clear separation between template logic and placeholder replacement
13+
* - Ability to share common patterns across multiple presets
14+
*
15+
* @implements {IAdvancedExportGenerator}
16+
*
17+
* @example
18+
* ```typescript
19+
* // Generated class usage in Battlefield modding:
20+
* const counter = new CounterWidget();
21+
* const widget = counter.create(); // Creates the UI widget
22+
* counter.increment(); // Increments from 0 to 1
23+
* counter.decrement(); // Increments from 1 to 0
24+
* ```
25+
*/
26+
export class CounterExportGenerator implements IAdvancedExportGenerator {
27+
private templateCache: string | null = null;
28+
29+
/**
30+
* Generates TypeScript class code for a counter widget instance.
31+
*
32+
* Loads the counter widget template and replaces placeholders with actual values:
33+
* - {{CLASS_NAME}} - Unique class name for this instance
34+
* - {{TIMESTAMP}} - Generation timestamp
35+
* - {{UI_PARAMS}} - Serialized UI parameters for modlib.ParseUI()
36+
*
37+
* The template is cached after first load to avoid repeated network requests.
38+
*
39+
* @param rootElement - The root container element of the counter preset
40+
* @param preset - The counter preset definition
41+
* @param className - Unique class name for this counter instance
42+
* @param serializeHelpers - Helper functions for converting UI elements to TypeScript
43+
* @param serializeHelpers.serializeParamToTypescript - Converts UIParams to object literal code
44+
* @param serializeHelpers.serializeElement - Extracts UIParams from UIElement
45+
* @param strings - Localization strings (passed through to serialization)
46+
* @param timestamp - ISO timestamp for code generation comment header
47+
* @returns Complete TypeScript class definition as a string
48+
*/
49+
async generateClassCode(
50+
rootElement: UIElement,
51+
preset: UIAdvancedPresetDefinition,
52+
className: string,
53+
serializeHelpers: {
54+
serializeParamToTypescript: (param: UIParams, indentLevel: number, strings: Record<string, string>) => string;
55+
serializeElement: (e: UIElement) => UIParams;
56+
},
57+
strings: Record<string, string>,
58+
timestamp: string
59+
): Promise<string> {
60+
const { serializeParamToTypescript, serializeElement } = serializeHelpers;
61+
62+
// Load template (cached after first load)
63+
if (!this.templateCache) {
64+
this.templateCache = await loadTemplate('counter.widget.template.ts');
65+
}
66+
67+
// Serialize the root element into TypeScript object literal format
68+
const params = serializeElement(rootElement);
69+
const uiParams = serializeParamToTypescript(params, 3, strings);
70+
71+
// Replace placeholders in template
72+
return replacePlaceholders(this.templateCache, {
73+
CLASS_NAME: className,
74+
TIMESTAMP: timestamp,
75+
UI_PARAMS: uiParams
76+
});
77+
}
78+
}
79+
80+
/**
81+
* Unique identifier for the counter preset.
82+
* Used to register and look up this preset in the preset registry.
83+
*/
84+
export const COUNTER_PRESET_ID = 'counter-container';
85+
86+
/**
87+
* UI element blueprint for the counter preset.
88+
*
89+
* Defines the visual structure of the counter widget:
90+
* - A semi-transparent dark container (360x140px)
91+
* - A centered text element inside for displaying the counter value
92+
*
93+
* This blueprint is instantiated when a user adds a counter preset to the canvas.
94+
* The actual counter value (0, 1, 2...) is set at runtime via the increment() decrement() methods
95+
* in the generated TypeScript class.
96+
*/
97+
const blueprint: UIParams = {
98+
name: "CounterContainer",
99+
type: "Container",
100+
position: [49.97, 28.93],
101+
size: [210, 210],
102+
anchor: UIAnchor.TopLeft,
103+
visible: true,
104+
padding: 0,
105+
bgColor: [0.2, 0.2, 0.2],
106+
bgAlpha: 1,
107+
bgFill: UIBgFill.Blur,
108+
children: [
109+
{
110+
name: "CounterText",
111+
type: "Text",
112+
position: [0, 0],
113+
size: [150, 150],
114+
anchor: UIAnchor.Center,
115+
visible: true,
116+
padding: 0,
117+
bgColor: [0.2, 0.2, 0.2],
118+
bgAlpha: 1,
119+
bgFill: UIBgFill.None,
120+
textLabel: "0",
121+
textColor: [1, 1, 1],
122+
textAlpha: 1,
123+
textSize: 38,
124+
textAnchor: UIAnchor.Center
125+
}
126+
]
127+
};
128+
129+
/**
130+
* Counter preset definition.
131+
*
132+
* Defines a reusable counter widget that can be added to the UI builder canvas.
133+
* When exported, generates a TypeScript class with:
134+
* - A create() method that instantiates the widget
135+
* - A increment() and decrement() method to modify the counter value
136+
* - Public properties for accessing the container and text widgets
137+
*
138+
* This preset demonstrates the advanced preset system's capabilities:
139+
* - Complex multi-element widgets
140+
* - Custom export logic via CounterExportGenerator
141+
* - Slot definitions for customizable child elements
142+
* - Integration with the Battlefield modlib.ParseUI() system
143+
*
144+
* @see CounterExportGenerator for the export code generation logic
145+
* @see UIAdvancedPresetDefinition for the preset type definition
146+
*/
147+
export const counterPreset: UIAdvancedPresetDefinition = {
148+
id: COUNTER_PRESET_ID,
149+
label: 'Counter Container',
150+
version: '1.0.0',
151+
description: 'Container with a text widget that be incremented or decremented.',
152+
defaultClassName: 'CounterWidget',
153+
defaultRootName: 'CounterContainer',
154+
blueprint,
155+
slots: [
156+
{
157+
id: 'counterText',
158+
label: 'Counter Text',
159+
description: 'Text widget displaying the numeric counter.',
160+
allowedTypes: ['Text'],
161+
},
162+
],
163+
exportGenerator: new CounterExportGenerator(),
164+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { UiBuilderService } from '../services/ui-builder.service';
2+
import { counterPreset, COUNTER_PRESET_ID } from './counter.preset';
3+
4+
/**
5+
* Registers all built-in advanced presets with the UI builder service.
6+
*
7+
* This function is called during service initialization to make all advanced presets
8+
* available in the UI builder. To add a new advanced preset:
9+
*
10+
* 1. Create a new preset file in this directory (e.g., `my-widget.preset.ts`)
11+
* 2. Define the preset definition and export generator class
12+
* 3. Import the preset in this file
13+
* 4. Call `service.registerAdvancedPreset()` with your preset
14+
* 5. Add the preset ID to `getBuiltInPresetIds()` array
15+
*
16+
* @param service - The UI builder service instance
17+
*
18+
* @example
19+
* ```typescript
20+
* // Adding a new preset:
21+
* import { myPreset, MY_PRESET_ID } from './my-widget.preset';
22+
*
23+
* export function registerAllAdvancedPresets(service: UiBuilderService) {
24+
* service.registerAdvancedPreset(counterPreset as any);
25+
* service.registerAdvancedPreset(myPreset as any); // Add your preset here
26+
* }
27+
*
28+
* export function getBuiltInPresetIds(): string[] {
29+
* return [COUNTER_PRESET_ID, MY_PRESET_ID]; // Add your preset ID here
30+
* }
31+
* ```
32+
*/
33+
export function registerAllAdvancedPresets(service: UiBuilderService) {
34+
// Register built-in presets
35+
service.registerAdvancedPreset(counterPreset as any);
36+
}
37+
38+
/**
39+
* Returns an array of all built-in advanced preset IDs.
40+
*
41+
* This function provides a centralized list of all preset IDs for validation,
42+
* filtering, and management purposes. Update this list whenever adding or
43+
* removing built-in presets.
44+
*
45+
* @returns Array of preset ID strings
46+
*
47+
* @example
48+
* ```typescript
49+
* const builtInIds = getBuiltInPresetIds();
50+
* // Returns: ['counter-container']
51+
*
52+
* // Check if a preset is built-in:
53+
* const isBuiltIn = getBuiltInPresetIds().includes(presetId);
54+
* ```
55+
*/
56+
export function getBuiltInPresetIds(): string[] {
57+
return [COUNTER_PRESET_ID];
58+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Advanced Preset Templates
2+
3+
This directory contains TypeScript template files for advanced preset export generators.
4+
5+
## Overview
6+
7+
Template files allow you to write the generated TypeScript code in actual `.ts` files that are then correctly serialized when generating UIs.
8+
9+
## How It Works
10+
11+
1. **Template Files**: Write your widget class in a `.template.ts` file using placeholder syntax `{{PLACEHOLDER_NAME}}`
12+
2. **Build Process**: Template files are excluded from TypeScript compilation but copied as-is to `dist/templates/`
13+
3. **Runtime Loading**: Export generators use `loadTemplate()` to fetch templates and `replacePlaceholders()` to substitute values
14+
15+
## Available Placeholders
16+
17+
Common placeholders you can use in your templates:
18+
19+
- `{{CLASS_NAME}}` - The unique class name for this widget instance
20+
- `{{TIMESTAMP}}` - ISO timestamp for generation comment headers
21+
- `{{UI_PARAMS}}` - Serialized UIParams for `modlib.ParseUI()`
22+
23+
You can define custom placeholders specific to your preset needs.
24+
25+
## Common Interface (Documentation Only)
26+
27+
The `common-interface.ts` file documents the expected interface for all generated widgets:
28+
- `create()` - Creates and initializes the widget
29+
- `update()` - Updates widget state
30+
- `destroy()` - Cleanup (optional)
31+
32+
This file is for reference only and is not compiled or exported.
33+
It's also expected to expose as public properties the important widget instances for easier manipulation.
34+
35+
## Adding New Templates
36+
37+
1. Create a new `.template.ts` file in this directory
38+
2. Write your widget class using placeholder syntax
39+
3. Create an export generator class that loads and uses your template
40+
4. Register the preset in `registry.ts`
41+
42+
## File Naming Convention
43+
44+
- Use `.template.ts` extension for actual template files
45+
- Templates will be copied to `dist/templates/` during build
46+
- Use descriptive names: `[preset-name].widget.template.ts`
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//@ts-ignore
2+
/**
3+
* Common interface that all advanced widget classes should implement.
4+
* This ensures consistent API across all generated widgets.
5+
*
6+
* NOTE: This file is for documentation purposes only. The actual interface
7+
* is not enforced at runtime since widgets are generated as standalone classes.
8+
* Use this as a reference when creating new widget templates.
9+
*
10+
* Ideally if someone generates ui with the tool they can implement this interface manually in exported code and handle widget lifecycle properly.
11+
*/
12+
export interface IAdvancedWidget {
13+
/**
14+
* Useful widget instances should be created and stored in public properties.
15+
* We can also, at the end of the create() method, chain `mod.FindUIWidgetWithName(...)` and assign them there.
16+
*/
17+
rootWidget: mod.UIWidget;
18+
19+
/**
20+
* Creates and initializes the widget.
21+
* Should call modlib.ParseUI() and set up any widget references.
22+
* @returns The root widget instance
23+
*/
24+
create(): mod.UIWidget;
25+
26+
/**
27+
* Updates the widget state and refreshes the display.
28+
* The specific behavior depends on the widget type.
29+
* @returns Optional return value (e.g., new counter value)
30+
*/
31+
refreshUi?(): any;
32+
33+
/**
34+
* Destroys the widget and cleans up resources.
35+
* Should be implemented if the widget needs cleanup.
36+
*/
37+
destroy?(): void;
38+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export class {{CLASS_NAME}} {
2+
public containerWidget: mod.UIWidget = null;
3+
public textWidget: mod.UIWidget = null;
4+
5+
private counterValue = 0;
6+
7+
public create(): mod.UIWidget {
8+
this.counterValue = 0;
9+
this.containerWidget = modlib.ParseUI(
10+
{{UI_PARAMS}}
11+
);
12+
this.textWidget = mod.FindUIWidgetWithName('CounterText');
13+
this.refreshUi();
14+
return this.containerWidget;
15+
}
16+
17+
public increment(): number {
18+
this.counterValue += 1;
19+
this.refreshUi();
20+
return this.counterValue;
21+
}
22+
23+
public decrement(): number {
24+
this.counterValue -= 1;
25+
this.refreshUi();
26+
return this.counterValue;
27+
}
28+
29+
pulic destroy() {
30+
if (this.containerWidget) {
31+
mod.DeleteUIWidget(this.containerWidget);
32+
}
33+
}
34+
35+
private refreshUi(): void {
36+
mod.SetUITextLabel(this.textWidget, mod.Message(this.counterValue.toString()));
37+
}
38+
39+
}

0 commit comments

Comments
 (0)