Working on UI
This commit is contained in:
parent
9adcde5c05
commit
c659293bf8
441
UI.md
Normal file
441
UI.md
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
# UI System
|
||||||
|
|
||||||
|
UI layouts are defined in JSON files and loaded at runtime by `UiManager`. Each file has a single `"root"` node that is the top-level container.
|
||||||
|
|
||||||
|
The coordinate system has the origin at the **bottom-left** of the screen. Y increases upward.
|
||||||
|
The virtual canvas size is defined by `Environment::projectionWidth` × `Environment::projectionHeight`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"root": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Node Properties
|
||||||
|
|
||||||
|
These properties are available on every node type.
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `name` | string | `""` | Unique name used to find the node from C++ code |
|
||||||
|
| `x` | float | `0` | Horizontal offset from the parent's origin (or gravity-adjusted position) |
|
||||||
|
| `y` | float | `0` | Vertical offset |
|
||||||
|
| `width` | float \| `"match_parent"` | `0` | Width in virtual pixels. `"match_parent"` fills the parent |
|
||||||
|
| `height` | float \| `"match_parent"` | `0` | Height in virtual pixels |
|
||||||
|
| `horizontal_gravity` | `"left"` \| `"center"` \| `"right"` | `"left"` | Positions the node horizontally inside a **FrameLayout** parent |
|
||||||
|
| `vertical_gravity` | `"bottom"` \| `"center"` \| `"top"` | `"bottom"` | Positions the node vertically inside a **FrameLayout** parent |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Containers
|
||||||
|
|
||||||
|
### FrameLayout
|
||||||
|
|
||||||
|
Children are positioned using absolute `x`/`y` offsets and/or `horizontal_gravity` / `vertical_gravity`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "FrameLayout",
|
||||||
|
"name": "hud_root",
|
||||||
|
"width": "match_parent",
|
||||||
|
"height": "match_parent",
|
||||||
|
"children": [ ... ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `children` | array | `[]` | Child nodes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### LinearLayout
|
||||||
|
|
||||||
|
Children are stacked automatically in a row or column. Gravity and align properties control the layout of the block and its children.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "LinearLayout",
|
||||||
|
"orientation": "vertical",
|
||||||
|
"vertical_align": "center",
|
||||||
|
"horizontal_align": "center",
|
||||||
|
"spacing": 10,
|
||||||
|
"width": 400,
|
||||||
|
"height": 600,
|
||||||
|
"children": [ ... ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `orientation` | `"vertical"` \| `"horizontal"` | `"vertical"` | Direction children are stacked |
|
||||||
|
| `spacing` | float | `0` | Gap in pixels between consecutive children |
|
||||||
|
| `vertical_align` | `"top"` \| `"center"` \| `"bottom"` | `"top"` | **Vertical** alignment of the child block inside this layout. For vertical orientation, controls how the whole stack is aligned; for horizontal orientation, controls each child's cross-axis alignment |
|
||||||
|
| `horizontal_align` | `"left"` \| `"center"` \| `"right"` | `"left"` | **Horizontal** alignment of the child block. For horizontal orientation, controls how the whole row is aligned; for vertical orientation, controls each child's cross-axis alignment |
|
||||||
|
| `children` | array | `[]` | Child nodes, laid out in order |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Widgets
|
||||||
|
|
||||||
|
### Button
|
||||||
|
|
||||||
|
An image-only clickable button. Swaps textures on hover/press.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "closeButton",
|
||||||
|
"width": 90,
|
||||||
|
"height": 90,
|
||||||
|
"x": 580,
|
||||||
|
"y": 240,
|
||||||
|
"horizontal_gravity": "center",
|
||||||
|
"vertical_gravity": "center",
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/w/ui/img/Close001_State=Default.png",
|
||||||
|
"hover": "resources/w/ui/img/Close001_State=Selected.png",
|
||||||
|
"pressed": "resources/w/ui/img/Close001_State=Tap.png",
|
||||||
|
"disabled": "resources/w/ui/img/Close001_State=Disabled.png"
|
||||||
|
},
|
||||||
|
"border": 4,
|
||||||
|
"clickZoneWidth": 80,
|
||||||
|
"clickZoneHeight": 80
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `textures.normal` | string | — | Texture path shown in the default state (**required**) |
|
||||||
|
| `textures.hover` | string | — | Texture path shown when the mouse hovers |
|
||||||
|
| `textures.pressed` | string | — | Texture path shown while pressed |
|
||||||
|
| `textures.disabled` | string | — | Texture path shown when the button is disabled |
|
||||||
|
| `border` | float | `0` | Inset (pixels) applied to the hit-test zone on all sides |
|
||||||
|
| `clickZoneWidth` | float | `0` | Explicit hit-test width; `0` uses the widget width |
|
||||||
|
| `clickZoneHeight` | float | `0` | Explicit hit-test height; `0` uses the widget height |
|
||||||
|
|
||||||
|
**C++ callbacks:**
|
||||||
|
```cpp
|
||||||
|
uiManager.setButtonCallback("closeButton", [](const std::string&) { /* click */ });
|
||||||
|
uiManager.setButtonPressCallback("closeButton", [](const std::string&) { /* press */ });
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### TextButton
|
||||||
|
|
||||||
|
A button that renders a text label on top of an optional background texture.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "TextButton",
|
||||||
|
"name": "item1name",
|
||||||
|
"width": 270,
|
||||||
|
"height": 60,
|
||||||
|
"text": "Main Quest",
|
||||||
|
"fontSize": 32,
|
||||||
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
|
"textCentered": false,
|
||||||
|
"topAligned": false,
|
||||||
|
"textPaddingX": 12,
|
||||||
|
"textPaddingY": -8,
|
||||||
|
"wrap": true,
|
||||||
|
"color": [1.0, 1.0, 1.0, 1.0],
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/w/red.png",
|
||||||
|
"hover": "resources/w/red.png",
|
||||||
|
"pressed": "resources/w/red.png"
|
||||||
|
},
|
||||||
|
"border": 0,
|
||||||
|
"clickZoneWidth": 0,
|
||||||
|
"clickZoneHeight": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `text` | string | `""` | Label text. Supports Cyrillic and any codepoint in `resources/symbols.txt` |
|
||||||
|
| `fontSize` | int | `32` | Font size in pixels |
|
||||||
|
| `fontPath` | string | `"resources/fonts/DroidSans.ttf"` | Path to the TTF font file |
|
||||||
|
| `textCentered` | bool | `true` | Horizontally centers the text within the widget. When `false`, text starts at `textPaddingX` from the left edge |
|
||||||
|
| `topAligned` | bool | `false` | When `true`, the first text line is placed near the top of the widget; when `false`, the text is vertically centered |
|
||||||
|
| `textPaddingX` | float | `12` | Left padding when `textCentered` is `false`; also used to compute the wrapping width |
|
||||||
|
| `textPaddingY` | float | `0` | Vertical offset applied to the text baseline |
|
||||||
|
| `wrap` | bool | `false` | Wraps text that exceeds `width - textPaddingX * 2` pixels |
|
||||||
|
| `color` | [R, G, B, A] | `[1,1,1,1]` | Text color, each channel 0..1 |
|
||||||
|
| `textures.*` | string | — | Background textures (all optional — button can be text-only) |
|
||||||
|
| `border` | float | `0` | Hit-test inset |
|
||||||
|
| `clickZoneWidth` / `clickZoneHeight` | float | `0` | Explicit hit-test size; `0` uses the widget size |
|
||||||
|
|
||||||
|
**C++ callbacks:**
|
||||||
|
```cpp
|
||||||
|
uiManager.setTextButtonCallback("item1name", [](const std::string&) { /* click */ });
|
||||||
|
uiManager.setTextButtonPressCallback("item1name", [](const std::string&) { /* press */ });
|
||||||
|
|
||||||
|
// Programmatic updates
|
||||||
|
uiManager.setTextButtonText("item1name", "New Quest Name");
|
||||||
|
uiManager.setTextButtonColor("item1name", {1.f, 0.f, 0.f, 1.f});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### TextView
|
||||||
|
|
||||||
|
A non-interactive text display widget.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "TextView",
|
||||||
|
"name": "quest_description",
|
||||||
|
"x": 170,
|
||||||
|
"y": 390,
|
||||||
|
"width": 1000,
|
||||||
|
"height": 300,
|
||||||
|
"text": "Long description here.",
|
||||||
|
"fontSize": 32,
|
||||||
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
|
"textCentered": false,
|
||||||
|
"topAligned": true,
|
||||||
|
"wrap": true,
|
||||||
|
"paddingX": 0,
|
||||||
|
"paddingY": 4,
|
||||||
|
"maxLines": 10,
|
||||||
|
"color": [1.0, 1.0, 0.0, 1.0]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `text` | string | `""` | Display text. Supports Cyrillic and any codepoint in `resources/symbols.txt` |
|
||||||
|
| `fontSize` | int | `32` | Font size in pixels |
|
||||||
|
| `fontPath` | string | `"resources/fonts/DroidSans.ttf"` | Path to the TTF font file |
|
||||||
|
| `textCentered` | bool | `true` | Horizontally centers the text when `true`; left-aligns from `paddingX` when `false` |
|
||||||
|
| `topAligned` | bool | `false` | When `true`, the first line is placed near the top edge; when `false`, text is vertically centered |
|
||||||
|
| `wrap` | bool | `false` | Wraps text at `width - paddingX * 2` pixels |
|
||||||
|
| `paddingX` | float | `0` | Left/right padding used for alignment and wrap width |
|
||||||
|
| `paddingY` | float | `0` | Vertical inset applied when `topAligned` is `true` |
|
||||||
|
| `maxLines` | int | `0` | Maximum number of lines to display; `0` means unlimited. Truncated text gets `...` |
|
||||||
|
| `color` | [R, G, B, A] | `[1,1,1,1]` | Text color |
|
||||||
|
|
||||||
|
> **Legacy note:** If none of `wrap`, `topAligned`, `paddingX`, `paddingY`, or `maxLines` are set, the text is drawn centered on `(x + width/2, y + height/2)` for backward compatibility.
|
||||||
|
|
||||||
|
**C++ updates:**
|
||||||
|
```cpp
|
||||||
|
uiManager.setText("quest_description", "New text here.");
|
||||||
|
uiManager.setTextColor("quest_description", {1.f, 1.f, 0.f, 1.f});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### TextField
|
||||||
|
|
||||||
|
An interactive single-line text input field. Receives keyboard input when focused.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "TextField",
|
||||||
|
"name": "playerName",
|
||||||
|
"x": 100,
|
||||||
|
"y": 300,
|
||||||
|
"width": 400,
|
||||||
|
"height": 50,
|
||||||
|
"placeholder": "Enter name...",
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
|
"maxLength": 64,
|
||||||
|
"color": [1.0, 1.0, 1.0, 1.0],
|
||||||
|
"placeholderColor": [0.5, 0.5, 0.5, 1.0],
|
||||||
|
"backgroundColor": [0.2, 0.2, 0.2, 1.0],
|
||||||
|
"borderColor": [0.5, 0.5, 0.5, 1.0]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `placeholder` | string | `""` | Text shown when the field is empty |
|
||||||
|
| `fontSize` | int | `32` | Font size in pixels |
|
||||||
|
| `fontPath` | string | `"resources/fonts/DroidSans.ttf"` | Path to the TTF font file |
|
||||||
|
| `maxLength` | int | `256` | Maximum number of characters |
|
||||||
|
| `color` | [R, G, B, A] | `[1,1,1,1]` | Input text color |
|
||||||
|
| `placeholderColor` | [R, G, B, A] | `[0.5,0.5,0.5,1]` | Placeholder text color |
|
||||||
|
| `backgroundColor` | [R, G, B, A] | `[0.2,0.2,0.2,1]` | Field background color |
|
||||||
|
| `borderColor` | [R, G, B, A] | `[0.5,0.5,0.5,1]` | Border color |
|
||||||
|
|
||||||
|
**C++ callbacks and queries:**
|
||||||
|
```cpp
|
||||||
|
uiManager.setTextFieldCallback("playerName", [](const std::string& name, const std::string& value) {
|
||||||
|
// called on every keystroke
|
||||||
|
});
|
||||||
|
std::string current = uiManager.getTextFieldValue("playerName");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Slider
|
||||||
|
|
||||||
|
A draggable slider that returns a normalized value in the range `[0, 1]`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "Slider",
|
||||||
|
"name": "volumeSlider",
|
||||||
|
"x": 100,
|
||||||
|
"y": 200,
|
||||||
|
"width": 40,
|
||||||
|
"height": 300,
|
||||||
|
"orientation": "vertical",
|
||||||
|
"value": 0.75,
|
||||||
|
"textures": {
|
||||||
|
"track": "resources/w/ui/img/slider_track.png",
|
||||||
|
"knob": "resources/w/ui/img/slider_knob.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `textures.track` | string | — | Texture for the slider track |
|
||||||
|
| `textures.knob` | string | — | Texture for the draggable knob |
|
||||||
|
| `orientation` | `"vertical"` \| `"horizontal"` | `"vertical"` | Drag direction |
|
||||||
|
| `value` | float | `0` | Initial normalized value `[0, 1]` |
|
||||||
|
|
||||||
|
**C++ callbacks:**
|
||||||
|
```cpp
|
||||||
|
uiManager.setSliderCallback("volumeSlider", [](const std::string& name, float value) {
|
||||||
|
// value is 0..1
|
||||||
|
});
|
||||||
|
uiManager.setSliderValue("volumeSlider", 0.5f);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### StaticImage
|
||||||
|
|
||||||
|
A non-interactive image. Supports optional fade-in and pulse-scale animations.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "StaticImage",
|
||||||
|
"name": "background",
|
||||||
|
"width": 1266,
|
||||||
|
"height": 585,
|
||||||
|
"horizontal_gravity": "center",
|
||||||
|
"vertical_gravity": "center",
|
||||||
|
"texture": "resources/w/ui/img/journal/QuestJournal003.png",
|
||||||
|
"fadeIn": {
|
||||||
|
"durationMs": 600
|
||||||
|
},
|
||||||
|
"pulse": {
|
||||||
|
"minScale": 0.92,
|
||||||
|
"maxScale": 1.08,
|
||||||
|
"periodMs": 1500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `texture` | string | — | Path to the PNG texture |
|
||||||
|
| `fadeIn.durationMs` | float | — | If present, the image fades in over this many milliseconds each time the UI is shown |
|
||||||
|
| `pulse.minScale` | float | `0.9` | Minimum scale during the pulse cycle |
|
||||||
|
| `pulse.maxScale` | float | `1.1` | Maximum scale during the pulse cycle |
|
||||||
|
| `pulse.periodMs` | float | `1000` | Duration of one full pulse cycle in milliseconds |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Animations
|
||||||
|
|
||||||
|
Animations can be defined on **Button** and **TextButton** nodes and started from C++ code. Each animation is a named sequence of steps.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "myButton",
|
||||||
|
"width": 100,
|
||||||
|
"height": 100,
|
||||||
|
"textures": { "normal": "resources/w/btn.png" },
|
||||||
|
"animations": {
|
||||||
|
"bounce": {
|
||||||
|
"repeat": false,
|
||||||
|
"steps": [
|
||||||
|
{ "type": "move", "to": [0, 20], "duration": 0.15, "easing": "easeout" },
|
||||||
|
{ "type": "move", "to": [0, 0], "duration": 0.15, "easing": "easein" },
|
||||||
|
{ "type": "wait", "duration": 0.1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pulse": {
|
||||||
|
"repeat": true,
|
||||||
|
"steps": [
|
||||||
|
{ "type": "scale", "to": [1.1, 1.1], "duration": 0.4, "easing": "easeout" },
|
||||||
|
{ "type": "scale", "to": [1.0, 1.0], "duration": 0.4, "easing": "easein" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animation sequence properties
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `repeat` | bool | `false` | Whether the sequence loops after the last step |
|
||||||
|
| `steps` | array | — | Ordered list of animation steps |
|
||||||
|
|
||||||
|
### Step properties
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `type` | `"move"` \| `"scale"` \| `"wait"` | Step kind |
|
||||||
|
| `to` | [x, y] | Target offset (`move`) or scale factors (`scale`) |
|
||||||
|
| `duration` | float (seconds) | Duration of the step. `0` applies the target instantly |
|
||||||
|
| `easing` | `"linear"` \| `"easein"` \| `"easeout"` | Interpolation curve (default `"linear"`) |
|
||||||
|
|
||||||
|
**C++ control:**
|
||||||
|
```cpp
|
||||||
|
uiManager.startAnimationOnNode("myButton", "bounce");
|
||||||
|
uiManager.stopAnimationOnNode("myButton", "bounce");
|
||||||
|
uiManager.setAnimationCallback("myButton", "bounce", []() {
|
||||||
|
// called when the non-repeating sequence finishes
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C++ API Quick Reference
|
||||||
|
|
||||||
|
### Loading and navigation
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
uiManager.loadFromFile("resources/w/ui/screen.json", renderer);
|
||||||
|
uiManager.pushMenuFromFile("resources/w/ui/popup.json", renderer); // push on stack
|
||||||
|
uiManager.popMenu(); // restore previous UI
|
||||||
|
uiManager.clearMenuStack();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Finding nodes
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto node = uiManager.findNode("myNode");
|
||||||
|
auto btn = uiManager.findButton("myButton");
|
||||||
|
auto tbtn = uiManager.findTextButton("item1name");
|
||||||
|
auto tv = uiManager.findTextView("quest_description");
|
||||||
|
auto img = uiManager.findStaticImage("background");
|
||||||
|
auto slider = uiManager.findSlider("volumeSlider");
|
||||||
|
auto tf = uiManager.findTextField("playerName");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Visibility
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
uiManager.setNodeVisible("hint5", false);
|
||||||
|
bool visible = uiManager.getNodeVisible("hint5");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-frame update
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
uiManager.update(deltaMs); // advance animations and fade-ins
|
||||||
|
uiManager.draw(renderer); // render everything
|
||||||
|
```
|
||||||
@ -32,7 +32,7 @@
|
|||||||
"text": "QUESTS",
|
"text": "QUESTS",
|
||||||
"fontSize": 32,
|
"fontSize": 32,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": true,
|
"textCentered": true,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"paddingY": 2.0,
|
"paddingY": 2.0,
|
||||||
"color": [1.0, 1.0, 1.0, 1.0]
|
"color": [1.0, 1.0, 1.0, 1.0]
|
||||||
@ -83,7 +83,7 @@
|
|||||||
"text": "ЗАДАНИЯ",
|
"text": "ЗАДАНИЯ",
|
||||||
"fontSize": 22,
|
"fontSize": 22,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"paddingX": 4.0,
|
"paddingX": 4.0,
|
||||||
"paddingY": 0.0,
|
"paddingY": 0.0,
|
||||||
@ -234,7 +234,7 @@
|
|||||||
"text": "Выберите задание",
|
"text": "Выберите задание",
|
||||||
"fontSize": 22,
|
"fontSize": 22,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"paddingX": 8.0,
|
"paddingX": 8.0,
|
||||||
@ -252,7 +252,7 @@
|
|||||||
"text": "",
|
"text": "",
|
||||||
"fontSize": 16,
|
"fontSize": 16,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"paddingX": 8.0,
|
"paddingX": 8.0,
|
||||||
@ -270,7 +270,7 @@
|
|||||||
"text": "ЦЕЛИ",
|
"text": "ЦЕЛИ",
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"paddingX": 8.0,
|
"paddingX": 8.0,
|
||||||
"paddingY": 0.0,
|
"paddingY": 0.0,
|
||||||
@ -286,7 +286,7 @@
|
|||||||
"text": "",
|
"text": "",
|
||||||
"fontSize": 17,
|
"fontSize": 17,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"paddingX": 8.0,
|
"paddingX": 8.0,
|
||||||
@ -304,7 +304,7 @@
|
|||||||
"text": "Описание задания",
|
"text": "Описание задания",
|
||||||
"fontSize": 22,
|
"fontSize": 22,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"paddingX": 8.0,
|
"paddingX": 8.0,
|
||||||
"paddingY": 0.0,
|
"paddingY": 0.0,
|
||||||
@ -320,7 +320,7 @@
|
|||||||
"text": "",
|
"text": "",
|
||||||
"fontSize": 18,
|
"fontSize": 18,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"paddingX": 8.0,
|
"paddingX": 8.0,
|
||||||
|
|||||||
BIN
resources/w/ui/img/journal/ButtonBkg001.png
(Stored with Git LFS)
Normal file
BIN
resources/w/ui/img/journal/ButtonBkg001.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/ui/img/journal/ButtonBkgTransparent001.png
(Stored with Git LFS)
Normal file
BIN
resources/w/ui/img/journal/ButtonBkgTransparent001.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -170,7 +170,7 @@
|
|||||||
"text": "Серебряный нож.",
|
"text": "Серебряный нож.",
|
||||||
"fontSize": 24,
|
"fontSize": 24,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"paddingX": 0.0,
|
"paddingX": 0.0,
|
||||||
@ -189,7 +189,7 @@
|
|||||||
"text": "А все бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут а мы идем.",
|
"text": "А все бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут а мы идем.",
|
||||||
"fontSize": 24,
|
"fontSize": 24,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": true,
|
"topAligned": true,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"paddingX": 0.0,
|
"paddingX": 0.0,
|
||||||
|
|||||||
@ -34,11 +34,11 @@
|
|||||||
{
|
{
|
||||||
"type": "LinearLayout",
|
"type": "LinearLayout",
|
||||||
"orientation": "vertical",
|
"orientation": "vertical",
|
||||||
"vertical_align": "center",
|
"vertical_align": "top",
|
||||||
"horizontal_align": "center",
|
"horizontal_align": "center",
|
||||||
"spacing": 0,
|
"spacing": 0,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": -195,
|
"y": 195,
|
||||||
"width": 290,
|
"width": 290,
|
||||||
"height": 800,
|
"height": 800,
|
||||||
"children": [
|
"children": [
|
||||||
@ -48,25 +48,25 @@
|
|||||||
"x": 0.0,
|
"x": 0.0,
|
||||||
"y": 0.0,
|
"y": 0.0,
|
||||||
"width": 270.0,
|
"width": 270.0,
|
||||||
"height": 60.0,
|
"height": 90,
|
||||||
"text": "Задание 1",
|
"text": "Главное Задание 1",
|
||||||
"textPaddingY": -8.0,
|
"textPaddingY": 4.0,
|
||||||
|
"textPaddingX": 16.0,
|
||||||
"fontSize": 32,
|
"fontSize": 32,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
|
||||||
"textCentered": false,
|
"textCentered": false,
|
||||||
"topAligned": false,
|
"topAligned": true,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"color": [
|
"color": [
|
||||||
1.0,
|
0.996,
|
||||||
1.0,
|
0.977,
|
||||||
1.0,
|
0.761,
|
||||||
1.0
|
1.0
|
||||||
],
|
],
|
||||||
"textures": {
|
"textures": {
|
||||||
"normal": "resources/w/red.png",
|
"normal": "resources/w/ui/img/journal/ButtonBkg001.png",
|
||||||
"hover": "resources/w/red.png",
|
"hover": "resources/w/ui/img/journal/ButtonBkg001.png",
|
||||||
"pressed": "resources/w/red.png"
|
"pressed": "resources/w/ui/img/journal/ButtonBkg001.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -77,23 +77,50 @@
|
|||||||
"width": 270.0,
|
"width": 270.0,
|
||||||
"height": 60.0,
|
"height": 60.0,
|
||||||
"text": "Задание 2",
|
"text": "Задание 2",
|
||||||
"textPaddingY": -8.0,
|
"textPaddingY": 4.0,
|
||||||
|
"textPaddingX": 16.0,
|
||||||
"fontSize": 32,
|
"fontSize": 32,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
|
||||||
"textCentered": false,
|
"textCentered": false,
|
||||||
"topAligned": false,
|
"topAligned": true,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"color": [
|
"color": [
|
||||||
1.0,
|
0.996,
|
||||||
1.0,
|
0.977,
|
||||||
1.0,
|
0.761,
|
||||||
1.0
|
1.0
|
||||||
],
|
],
|
||||||
"textures": {
|
"textures": {
|
||||||
"normal": "resources/w/blue.png",
|
"normal": "resources/w/ui/img/journal/ButtonBkgTransparent001.png",
|
||||||
"hover": "resources/w/blue.png",
|
"hover": "resources/w/ui/img/journal/ButtonBkgTransparent001.png",
|
||||||
"pressed": "resources/w/blue.png"
|
"pressed": "resources/w/ui/img/journal/ButtonBkgTransparent001.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "TextButton",
|
||||||
|
"name": "item3name",
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"width": 270.0,
|
||||||
|
"height": 60.0,
|
||||||
|
"text": "Задание 3",
|
||||||
|
"textPaddingY": 4.0,
|
||||||
|
"textPaddingX": 16.0,
|
||||||
|
"fontSize": 32,
|
||||||
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
|
"textCentered": false,
|
||||||
|
"topAligned": true,
|
||||||
|
"wrap": true,
|
||||||
|
"color": [
|
||||||
|
0.02,
|
||||||
|
0.875,
|
||||||
|
0.447,
|
||||||
|
0.6
|
||||||
|
],
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/w/ui/img/journal/ButtonBkgTransparent001.png",
|
||||||
|
"hover": "resources/w/ui/img/journal/ButtonBkgTransparent001.png",
|
||||||
|
"pressed": "resources/w/ui/img/journal/ButtonBkgTransparent001.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -107,15 +134,15 @@
|
|||||||
"height": 44.0,
|
"height": 44.0,
|
||||||
"horizontal_gravity": "center",
|
"horizontal_gravity": "center",
|
||||||
"vertical_gravity": "top",
|
"vertical_gravity": "top",
|
||||||
"text": "Выбрано задание 1",
|
"text": "Главное Задание 1",
|
||||||
"fontSize": 32,
|
"fontSize": 32,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": false,
|
"topAligned": false,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"color": [
|
"color": [
|
||||||
1.0,
|
0.992,
|
||||||
1.0,
|
0.78,
|
||||||
0.0,
|
0.0,
|
||||||
1.0
|
1.0
|
||||||
]
|
]
|
||||||
@ -156,14 +183,14 @@
|
|||||||
"text": "Цель 1 Цель 1 Цель 1 Цель 1",
|
"text": "Цель 1 Цель 1 Цель 1 Цель 1",
|
||||||
"fontSize": 32,
|
"fontSize": 32,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": false,
|
"topAligned": false,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"color": [
|
"color": [
|
||||||
1.0,
|
0.02,
|
||||||
1.0,
|
0.875,
|
||||||
1.0,
|
0.447,
|
||||||
1.0
|
0.6
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -193,14 +220,14 @@
|
|||||||
"text": "Цель 2",
|
"text": "Цель 2",
|
||||||
"fontSize": 32,
|
"fontSize": 32,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": false,
|
"topAligned": false,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"color": [
|
"color": [
|
||||||
1.0,
|
0.996,
|
||||||
1.0,
|
0.977,
|
||||||
1.0,
|
0.761,
|
||||||
1.0
|
0.9
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -219,14 +246,14 @@
|
|||||||
"text": "А все бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут а мы идем.",
|
"text": "А все бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут бегут а мы идем.",
|
||||||
"fontSize": 32,
|
"fontSize": 32,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
"centered": false,
|
"textCentered": false,
|
||||||
"topAligned": false,
|
"topAligned": false,
|
||||||
"wrap": true,
|
"wrap": true,
|
||||||
"color": [
|
"color": [
|
||||||
1.0,
|
0.996,
|
||||||
1.0,
|
0.977,
|
||||||
0.0,
|
0.761,
|
||||||
1.0
|
0.75
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -234,7 +234,6 @@ namespace ZL
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uniInteriorParams.navigationJsonPaths = {
|
uniInteriorParams.navigationJsonPaths = {
|
||||||
"resources/navigation/uni_interior3_all_locked.json",
|
"resources/navigation/uni_interior3_all_locked.json",
|
||||||
"resources/navigation/uni_interior3_hall.json",
|
"resources/navigation/uni_interior3_hall.json",
|
||||||
@ -454,7 +453,6 @@ namespace ZL
|
|||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
glDepthMask(GL_FALSE);
|
glDepthMask(GL_FALSE);
|
||||||
|
|
||||||
|
|
||||||
renderer.shaderManager.PushShader(defaultShaderName);
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
|
|||||||
@ -210,12 +210,24 @@ namespace ZL {
|
|||||||
// Draw text on top (uses absolute coords, add anim offset manually)
|
// Draw text on top (uses absolute coords, add anim offset manually)
|
||||||
// use left padding, which is required for inventory/quest lists.
|
// use left padding, which is required for inventory/quest lists.
|
||||||
if (textRenderer && !text.empty()) {
|
if (textRenderer && !text.empty()) {
|
||||||
|
const float scale = 1.0f;
|
||||||
|
const std::string displayText = wrap
|
||||||
|
? wrapTextByPixels(text, *textRenderer, rect.w - textPaddingX * 2.0f, scale)
|
||||||
|
: text;
|
||||||
|
|
||||||
float tx = rect.x + rect.w / 2.0f + animOffsetX;
|
float tx = rect.x + rect.w / 2.0f + animOffsetX;
|
||||||
if (!textCentered) {
|
if (!textCentered) {
|
||||||
tx = rect.x + textPaddingX + animOffsetX;
|
tx = rect.x + textPaddingX + animOffsetX;
|
||||||
}
|
}
|
||||||
const float ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY;
|
|
||||||
textRenderer->drawText(text, tx, ty, 1.0f, textCentered, color);
|
float ty;
|
||||||
|
if (topAligned) {
|
||||||
|
ty = rect.y + rect.h - textPaddingY - static_cast<float>(fontSize) + animOffsetY;
|
||||||
|
} else {
|
||||||
|
ty = rect.y + rect.h * 0.5f + textPaddingY + animOffsetY;
|
||||||
|
}
|
||||||
|
|
||||||
|
textRenderer->drawText(displayText, tx, ty, scale, textCentered, color);
|
||||||
}
|
}
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
}
|
}
|
||||||
@ -239,7 +251,7 @@ namespace ZL {
|
|||||||
rect.x + rect.w * 0.5f,
|
rect.x + rect.w * 0.5f,
|
||||||
rect.y + rect.h * 0.5f,
|
rect.y + rect.h * 0.5f,
|
||||||
scale,
|
scale,
|
||||||
centered,
|
textCentered,
|
||||||
color
|
color
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -250,7 +262,7 @@ namespace ZL {
|
|||||||
? wrapTextByPixels(text, *textRenderer, availableWidth, scale, maxLines)
|
? wrapTextByPixels(text, *textRenderer, availableWidth, scale, maxLines)
|
||||||
: limitLines(text, maxLines);
|
: limitLines(text, maxLines);
|
||||||
|
|
||||||
float tx = centered ? rect.x + rect.w * 0.5f : rect.x + paddingX;
|
float tx = textCentered ? rect.x + rect.w * 0.5f : rect.x + paddingX;
|
||||||
float ty = rect.y + rect.h * 0.5f;
|
float ty = rect.y + rect.h * 0.5f;
|
||||||
|
|
||||||
if (topAligned) {
|
if (topAligned) {
|
||||||
@ -259,7 +271,7 @@ namespace ZL {
|
|||||||
ty = rect.y + rect.h - paddingY - static_cast<float>(fontSize);
|
ty = rect.y + rect.h - paddingY - static_cast<float>(fontSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
textRenderer->drawText(finalText, tx, ty, scale, centered, color);
|
textRenderer->drawText(finalText, tx, ty, scale, textCentered, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UiSlider::buildTrackMesh() {
|
void UiSlider::buildTrackMesh() {
|
||||||
@ -623,6 +635,8 @@ namespace ZL {
|
|||||||
if (j.contains("textCentered")) tb->textCentered = j["textCentered"].get<bool>();
|
if (j.contains("textCentered")) tb->textCentered = j["textCentered"].get<bool>();
|
||||||
if (j.contains("textPaddingX")) tb->textPaddingX = j["textPaddingX"].get<float>();
|
if (j.contains("textPaddingX")) tb->textPaddingX = j["textPaddingX"].get<float>();
|
||||||
if (j.contains("textPaddingY")) tb->textPaddingY = j["textPaddingY"].get<float>();
|
if (j.contains("textPaddingY")) tb->textPaddingY = j["textPaddingY"].get<float>();
|
||||||
|
if (j.contains("wrap")) tb->wrap = j["wrap"].get<bool>();
|
||||||
|
if (j.contains("topAligned")) tb->topAligned = j["topAligned"].get<bool>();
|
||||||
if (j.contains("color") && j["color"].is_array() && j["color"].size() == 4) {
|
if (j.contains("color") && j["color"].is_array() && j["color"].size() == 4) {
|
||||||
for (int i = 0; i < 4; ++i) tb->color[i] = j["color"][i].get<float>();
|
for (int i = 0; i < 4; ++i) tb->color[i] = j["color"][i].get<float>();
|
||||||
}
|
}
|
||||||
@ -723,7 +737,7 @@ namespace ZL {
|
|||||||
tv->color[i] = j["color"][i].get<float>();
|
tv->color[i] = j["color"][i].get<float>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (j.contains("centered")) tv->centered = j["centered"].get<bool>();
|
if (j.contains("textCentered")) tv->textCentered = j["textCentered"].get<bool>();
|
||||||
if (j.contains("wrap")) tv->wrap = j["wrap"].get<bool>();
|
if (j.contains("wrap")) tv->wrap = j["wrap"].get<bool>();
|
||||||
if (j.contains("topAligned")) tv->topAligned = j["topAligned"].get<bool>();
|
if (j.contains("topAligned")) tv->topAligned = j["topAligned"].get<bool>();
|
||||||
if (j.contains("paddingX")) tv->paddingX = j["paddingX"].get<float>();
|
if (j.contains("paddingX")) tv->paddingX = j["paddingX"].get<float>();
|
||||||
|
|||||||
@ -169,6 +169,8 @@ namespace ZL {
|
|||||||
bool textCentered = true;
|
bool textCentered = true;
|
||||||
float textPaddingX = 12.0f;
|
float textPaddingX = 12.0f;
|
||||||
float textPaddingY = 0.0f;
|
float textPaddingY = 0.0f;
|
||||||
|
bool wrap = false;
|
||||||
|
bool topAligned = false;
|
||||||
|
|
||||||
std::unique_ptr<TextRenderer> textRenderer;
|
std::unique_ptr<TextRenderer> textRenderer;
|
||||||
|
|
||||||
@ -192,7 +194,7 @@ namespace ZL {
|
|||||||
std::string fontPath = "resources/fonts/DroidSans.ttf";
|
std::string fontPath = "resources/fonts/DroidSans.ttf";
|
||||||
int fontSize = 32;
|
int fontSize = 32;
|
||||||
std::array<float, 4> color = { 1.f, 1.f, 1.f, 1.f }; // rgba
|
std::array<float, 4> color = { 1.f, 1.f, 1.f, 1.f }; // rgba
|
||||||
bool centered = true;
|
bool textCentered = true;
|
||||||
bool wrap = false;
|
bool wrap = false;
|
||||||
bool topAligned = true;
|
bool topAligned = true;
|
||||||
float paddingX = 0.0f;
|
float paddingX = 0.0f;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user