Working on UI
This commit is contained in:
parent
9425f9b19a
commit
a0550daf4b
30
UI.md
30
UI.md
@ -352,6 +352,36 @@ Typically called immediately after making a node visible. The node is automatica
|
||||
|
||||
---
|
||||
|
||||
## Touch / Click Priority
|
||||
|
||||
All `Button` and `TextButton` nodes in a layout are collected into a single ordered list during `collectButtonsAndSliders` (depth-first traversal of the node tree, which matches JSON declaration order). When a touch or mouse-down event arrives, the list is scanned **in reverse** — later-declared nodes are checked first — and the **first hit wins**. At most one element (button or textButton, regardless of type) fires per touch.
|
||||
|
||||
**Practical rule:** place background "catch-all" elements (e.g. a full-screen transparent exit button) **early** in the JSON, and foreground interactive elements **later**. The later-declared element will always win when they overlap.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FrameLayout",
|
||||
"children": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "phoneExitButton", // declared first → lowest priority
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"textures": { "normal": "resources/transparent.png", ... }
|
||||
},
|
||||
{
|
||||
"type": "TextButton",
|
||||
"name": "chat2button", // declared later → wins over phoneExitButton
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This behaviour is consistent across `Button` and `TextButton` — there is no inherent type priority, only declaration order matters.
|
||||
|
||||
---
|
||||
|
||||
## Animations
|
||||
|
||||
Animations can be defined on **Button** and **TextButton** nodes and started from C++ code. Each animation is a named sequence of steps.
|
||||
|
||||
@ -200,7 +200,7 @@
|
||||
},
|
||||
{
|
||||
"type": "TextView",
|
||||
"name": "chat2msg",
|
||||
"name": "chat3msg",
|
||||
"x": 100.0,
|
||||
"y": 36.0,
|
||||
"width": 446.25,
|
||||
|
||||
@ -81,9 +81,9 @@ namespace ZL {
|
||||
hudStep5aRoot = loadUiFromFile("resources/w/ui/hud_step5a.json", renderer, zipFile);
|
||||
hudStep5bRoot = loadUiFromFile("resources/w/ui/hud_step5b.json", renderer, zipFile);
|
||||
hudStep5abRoot = loadUiFromFile("resources/w/ui/hud_step5ab.json", renderer, zipFile);
|
||||
//phoneScreenRoot = loadUiFromFile("resources/w/ui/screen_phone_chat_list.json", renderer, zipFile);
|
||||
phoneScreenRoot = loadUiFromFile("resources/w/ui/screen_phone_chat2.json", renderer, zipFile);
|
||||
//phoneScreenRoot = loadUiFromFile("resources/w/ui/screen_phone_chat1.json", renderer, zipFile);
|
||||
phoneChatListRoot = loadUiFromFile("resources/w/ui/screen_phone_chat_list.json", renderer, zipFile);
|
||||
phoneChat1Root = loadUiFromFile("resources/w/ui/screen_phone_chat1.json", renderer, zipFile);
|
||||
phoneChat2Root = loadUiFromFile("resources/w/ui/screen_phone_chat2.json", renderer, zipFile);
|
||||
newInventoryRoot = loadUiFromFile("resources/w/ui/screen_inventory.json", renderer, zipFile);
|
||||
questJournalRoot = loadUiFromFile("resources/w/ui/screen_journal.json", renderer, zipFile);
|
||||
|
||||
@ -197,20 +197,57 @@ namespace ZL {
|
||||
void MenuManager::openPhoneScreen() {
|
||||
state = GameState::PhoneScreen;
|
||||
tutorialPhoneScreenOpened = true;
|
||||
// Hide the phone hint on the current HUD so it stays hidden when we return.
|
||||
uiManager.setNodeVisible("hint6a", false);
|
||||
uiManager.pushMenuFromSavedRoot(phoneScreenRoot);
|
||||
uiManager.pushMenuFromSavedRoot(phoneChatListRoot);
|
||||
|
||||
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
||||
closePhoneScreen();
|
||||
});
|
||||
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
||||
uiManager.setTextButtonCallback("chat1button", [this](const std::string&) {
|
||||
openPhoneChatFromList(phoneChat1Root, "dialog_chat_parents001");
|
||||
});
|
||||
uiManager.setTextButtonCallback("chat2button", [this](const std::string&) {
|
||||
openPhoneChatFromList(phoneChat2Root, "dialog_chat_aiperi001");
|
||||
});
|
||||
uiManager.setTextButtonCallback("chat3button", [this](const std::string&) {
|
||||
// not yet implemented
|
||||
});
|
||||
}
|
||||
|
||||
// Reset chat state and start the dialogue
|
||||
void MenuManager::openPhoneChatFromList(std::shared_ptr<UiNode> chatRoot, const std::string& dialogueId) {
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
resetPhoneChatNodes();
|
||||
if (startDialogueFunc) {
|
||||
startDialogueFunc("dialog_chat_aiperi001");
|
||||
uiManager.pushMenuFromSavedRoot(chatRoot);
|
||||
|
||||
const bool firstOpen = dialogueId.empty() || startedDialogues_.find(dialogueId) == startedDialogues_.end();
|
||||
if (firstOpen) {
|
||||
resetPhoneChatNodes();
|
||||
}
|
||||
|
||||
uiManager.setButtonCallback("phoneExitButton", [this](const std::string&) {
|
||||
closePhoneScreenFromChat();
|
||||
});
|
||||
uiManager.setButtonCallback("phoneMain", [this](const std::string&) {});
|
||||
uiManager.setTextButtonCallback("chatTitleButton", [this](const std::string&) {
|
||||
returnToPhoneChatList();
|
||||
});
|
||||
|
||||
if (firstOpen && startDialogueFunc && !dialogueId.empty()) {
|
||||
startedDialogues_.insert(dialogueId);
|
||||
startDialogueFunc(dialogueId);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuManager::returnToPhoneChatList() {
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
uiManager.popMenu();
|
||||
}
|
||||
|
||||
void MenuManager::closePhoneScreenFromChat() {
|
||||
state = GameState::Gameplay;
|
||||
phoneChatVisibleBubbles_.clear();
|
||||
uiManager.popMenu();
|
||||
uiManager.popMenu();
|
||||
}
|
||||
|
||||
void MenuManager::closePhoneScreen() {
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@ -72,6 +73,9 @@ namespace ZL {
|
||||
void setupStep5Callbacks();
|
||||
void resetPhoneChatNodes();
|
||||
void recomputePhoneChatPositions();
|
||||
void openPhoneChatFromList(std::shared_ptr<UiNode> chatRoot, const std::string& dialogueId);
|
||||
void returnToPhoneChatList();
|
||||
void closePhoneScreenFromChat();
|
||||
|
||||
GameState state = GameState::Gameplay;
|
||||
Inventory* inventory = nullptr;
|
||||
@ -91,7 +95,9 @@ namespace ZL {
|
||||
std::shared_ptr<UiNode> hudStep5aRoot;
|
||||
std::shared_ptr<UiNode> hudStep5bRoot;
|
||||
std::shared_ptr<UiNode> hudStep5abRoot;
|
||||
std::shared_ptr<UiNode> phoneScreenRoot;
|
||||
std::shared_ptr<UiNode> phoneChatListRoot;
|
||||
std::shared_ptr<UiNode> phoneChat1Root;
|
||||
std::shared_ptr<UiNode> phoneChat2Root;
|
||||
std::shared_ptr<UiNode> newInventoryRoot;
|
||||
std::shared_ptr<UiNode> questJournalRoot;
|
||||
|
||||
@ -103,6 +109,9 @@ namespace ZL {
|
||||
int selectedQuestIndex = -1;
|
||||
std::vector<std::string> visibleQuestIds;
|
||||
|
||||
// Dialogues that have been started at least once; re-opening a chat won't restart them
|
||||
std::unordered_set<std::string> startedDialogues_;
|
||||
|
||||
// Phone chat state
|
||||
struct PhoneChatBubbleInfo {
|
||||
std::string nodeName;
|
||||
|
||||
@ -821,6 +821,7 @@ namespace ZL {
|
||||
);
|
||||
buttons.clear();
|
||||
textButtons.clear();
|
||||
allInteractives.clear();
|
||||
sliders.clear();
|
||||
textViews.clear();
|
||||
textFields.clear();
|
||||
@ -1066,9 +1067,11 @@ namespace ZL {
|
||||
void UiManager::collectButtonsAndSliders(const std::shared_ptr<UiNode>& node) {
|
||||
if (node->button) {
|
||||
buttons.push_back(node->button);
|
||||
allInteractives.push_back(node->button);
|
||||
}
|
||||
if (node->textButton) {
|
||||
textButtons.push_back(node->textButton);
|
||||
allInteractives.push_back(node->textButton);
|
||||
}
|
||||
if (node->slider) {
|
||||
sliders.push_back(node->slider);
|
||||
@ -1191,6 +1194,7 @@ namespace ZL {
|
||||
prev.root = root;
|
||||
prev.buttons = buttons;
|
||||
prev.textButtons = textButtons;
|
||||
prev.allInteractives = allInteractives;
|
||||
prev.sliders = sliders;
|
||||
prev.textViews = textViews;
|
||||
prev.textFields = textFields;
|
||||
@ -1255,6 +1259,7 @@ namespace ZL {
|
||||
root = s.root;
|
||||
buttons = s.buttons;
|
||||
textButtons = s.textButtons;
|
||||
allInteractives = s.allInteractives;
|
||||
sliders = s.sliders;
|
||||
textViews = s.textViews;
|
||||
textFields = s.textFields;
|
||||
@ -1646,22 +1651,22 @@ namespace ZL {
|
||||
|
||||
|
||||
void UiManager::onTouchDown(int64_t fingerId, int x, int y) {
|
||||
for (auto& b : buttons) {
|
||||
if (b->state != ButtonState::Disabled)
|
||||
{
|
||||
if (b->getClickZoneRect().containsConsideringBorder((float)x, (float)y, b->border)) {
|
||||
// Iterate allInteractives in reverse DFS order: later-declared elements have higher priority.
|
||||
// At most one button or textButton is pressed per touch.
|
||||
for (auto it = allInteractives.rbegin(); it != allInteractives.rend(); ++it) {
|
||||
if (std::holds_alternative<std::shared_ptr<UiButton>>(*it)) {
|
||||
auto& b = std::get<std::shared_ptr<UiButton>>(*it);
|
||||
if (b->state != ButtonState::Disabled &&
|
||||
b->getClickZoneRect().containsConsideringBorder((float)x, (float)y, b->border)) {
|
||||
b->state = ButtonState::Pressed;
|
||||
pressedButtons[fingerId] = b;
|
||||
if (b->onPress) b->onPress(b->name);
|
||||
break; // a single finger can only press one button
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& tb : textButtons) {
|
||||
if (tb->state != ButtonState::Disabled)
|
||||
{
|
||||
if (tb->getClickZoneRect().containsConsideringBorder((float)x, (float)y, tb->border)) {
|
||||
} else {
|
||||
auto& tb = std::get<std::shared_ptr<UiTextButton>>(*it);
|
||||
if (tb->state != ButtonState::Disabled &&
|
||||
tb->getClickZoneRect().containsConsideringBorder((float)x, (float)y, tb->border)) {
|
||||
tb->state = ButtonState::Pressed;
|
||||
pressedTextButtons[fingerId] = tb;
|
||||
if (tb->onPress) tb->onPress(tb->name);
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <variant>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ZL {
|
||||
@ -446,9 +447,14 @@ namespace ZL {
|
||||
bool stepStarted = false;
|
||||
};
|
||||
|
||||
using AnyButton = std::variant<std::shared_ptr<UiButton>, std::shared_ptr<UiTextButton>>;
|
||||
|
||||
std::shared_ptr<UiNode> root;
|
||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
||||
std::vector<std::shared_ptr<UiTextButton>> textButtons;
|
||||
// All buttons and textButtons in DFS declaration order.
|
||||
// onTouchDown iterates this in reverse so later-declared elements have higher priority.
|
||||
std::vector<AnyButton> allInteractives;
|
||||
std::vector<std::shared_ptr<UiSlider>> sliders;
|
||||
std::vector<std::shared_ptr<UiTextView>> textViews;
|
||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||
@ -469,6 +475,7 @@ namespace ZL {
|
||||
std::shared_ptr<UiNode> root;
|
||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
||||
std::vector<std::shared_ptr<UiTextButton>> textButtons;
|
||||
std::vector<AnyButton> allInteractives;
|
||||
std::vector<std::shared_ptr<UiSlider>> sliders;
|
||||
std::vector<std::shared_ptr<UiTextView>> textViews;
|
||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user