Added multi-touch, added on press down
This commit is contained in:
parent
a06cb77a66
commit
4a542fd6c8
@ -31,7 +31,7 @@
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"normal": "resources/fire.png",
|
||||
"hover": "resources/fire2.png",
|
||||
"hover": "resources/fire.png",
|
||||
"pressed": "resources/fire2.png",
|
||||
"disabled": "resources/fire_disabled.png"
|
||||
}
|
||||
@ -47,7 +47,7 @@
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"normal": "resources/fire.png",
|
||||
"hover": "resources/fire2.png",
|
||||
"hover": "resources/fire.png",
|
||||
"pressed": "resources/fire2.png",
|
||||
"disabled": "resources/fire_disabled.png"
|
||||
}
|
||||
|
||||
78
src/Game.cpp
78
src/Game.cpp
@ -369,33 +369,56 @@ namespace ZL
|
||||
if (event.type == SDL_FINGERDOWN) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleDown(mx, my);
|
||||
handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERUP) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleUp(mx, my);
|
||||
handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
}
|
||||
else if (event.type == SDL_FINGERMOTION) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleMotion(mx, my);
|
||||
handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
}
|
||||
#else
|
||||
|
||||
// Emscripten on mobile browser: handle real touch events with per-finger IDs.
|
||||
// SDL_HINT_TOUCH_MOUSE_EVENTS="0" is set in main.cpp so these don't
|
||||
// also fire SDL_MOUSEBUTTONDOWN, preventing double-processing.
|
||||
#ifdef EMSCRIPTEN
|
||||
if (event.type == SDL_FINGERDOWN) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleDown(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
//std::cout << "Finger down: id=" << event.tfinger.fingerId << " x=" << mx << " y=" << my << std::endl;
|
||||
}
|
||||
else if (event.type == SDL_FINGERUP) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleUp(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
//std::cout << "Finger up: id=" << event.tfinger.fingerId << " x=" << mx << " y=" << my << std::endl;
|
||||
}
|
||||
else if (event.type == SDL_FINGERMOTION) {
|
||||
int mx = static_cast<int>(event.tfinger.x * Environment::projectionWidth);
|
||||
int my = static_cast<int>(event.tfinger.y * Environment::projectionHeight);
|
||||
handleMotion(static_cast<int64_t>(event.tfinger.fingerId), mx, my);
|
||||
//std::cout << "Finger motion: id=" << event.tfinger.fingerId << " x=" << mx << " y=" << my << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
|
||||
// Преобразуем экранные пиксели в проекционные единицы
|
||||
int mx = static_cast<int>((float)event.button.x / Environment::width * Environment::projectionWidth);
|
||||
int my = static_cast<int>((float)event.button.y / Environment::height * Environment::projectionHeight);
|
||||
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(mx, my);
|
||||
else handleUp(mx, my);
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN) handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||
else handleUp(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||
//std::cout << "Mouse button " << (event.type == SDL_MOUSEBUTTONDOWN ? "down" : "up") << ": x=" << mx << " y=" << my << std::endl;
|
||||
}
|
||||
else if (event.type == SDL_MOUSEMOTION) {
|
||||
int mx = static_cast<int>((float)event.motion.x / Environment::width * Environment::projectionWidth);
|
||||
int my = static_cast<int>((float)event.motion.y / Environment::height * Environment::projectionHeight);
|
||||
handleMotion(mx, my);
|
||||
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||
}
|
||||
|
||||
/*if (event.type == SDL_MOUSEBUTTONDOWN) {
|
||||
@ -507,62 +530,53 @@ namespace ZL
|
||||
}
|
||||
}
|
||||
|
||||
void Game::handleDown(int mx, int my)
|
||||
void Game::handleDown(int64_t fingerId, int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseDown(uiX, uiY);
|
||||
menuManager.uiManager.onTouchDown(fingerId, uiX, uiY);
|
||||
|
||||
bool uiHandled = false;
|
||||
|
||||
for (const auto& button : menuManager.uiManager.findButton("") ? std::vector<std::shared_ptr<UiButton>>{} : std::vector<std::shared_ptr<UiButton>>{}) {
|
||||
(void)button;
|
||||
}
|
||||
|
||||
auto pressedSlider = [&]() -> std::shared_ptr<UiSlider> {
|
||||
for (const auto& slider : menuManager.uiManager.findSlider("") ? std::vector<std::shared_ptr<UiSlider>>{} : std::vector<std::shared_ptr<UiSlider>>{}) {
|
||||
(void)slider;
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (!menuManager.uiManager.isUiInteraction()) {
|
||||
if (!menuManager.uiManager.isUiInteractionForFinger(fingerId)) {
|
||||
if (menuManager.shouldRenderSpace()) {
|
||||
space.handleDown(mx, my);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Game::handleUp(int mx, int my)
|
||||
void Game::handleUp(int64_t fingerId, int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseUp(uiX, uiY);
|
||||
// Check BEFORE onTouchUp erases the finger from the map.
|
||||
// If this finger started on a UI element, don't notify space —
|
||||
// otherwise space would think the ship-control finger was released.
|
||||
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
|
||||
menuManager.uiManager.onTouchUp(fingerId, uiX, uiY);
|
||||
|
||||
if (!menuManager.uiManager.isUiInteraction()) {
|
||||
if (!wasUiInteraction) {
|
||||
if (menuManager.shouldRenderSpace()) {
|
||||
space.handleUp(mx, my);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Game::handleMotion(int mx, int my)
|
||||
void Game::handleMotion(int64_t fingerId, int mx, int my)
|
||||
{
|
||||
int uiX = mx;
|
||||
int uiY = Environment::projectionHeight - my;
|
||||
|
||||
menuManager.uiManager.onMouseMove(uiX, uiY);
|
||||
// Check before onTouchMove so the "started on UI" state is preserved
|
||||
// regardless of what onTouchMove does internally.
|
||||
bool wasUiInteraction = menuManager.uiManager.isUiInteractionForFinger(fingerId);
|
||||
menuManager.uiManager.onTouchMove(fingerId, uiX, uiY);
|
||||
|
||||
if (!menuManager.uiManager.isUiInteraction()) {
|
||||
if (!wasUiInteraction) {
|
||||
if (menuManager.shouldRenderSpace()) {
|
||||
space.handleMotion(mx, my);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <render/TextRenderer.h>
|
||||
#include "MenuManager.h"
|
||||
#include "Space.h"
|
||||
@ -50,9 +51,9 @@ namespace ZL {
|
||||
void drawUI();
|
||||
void drawUnderMainMenu();
|
||||
void drawLoading();
|
||||
void handleDown(int mx, int my);
|
||||
void handleUp(int mx, int my);
|
||||
void handleMotion(int mx, int my);
|
||||
void handleDown(int64_t fingerId, int mx, int my);
|
||||
void handleUp(int64_t fingerId, int mx, int my);
|
||||
void handleMotion(int64_t fingerId, int mx, int my);
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
static Game* s_instance;
|
||||
|
||||
@ -143,18 +143,18 @@ namespace ZL {
|
||||
|
||||
uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
|
||||
|
||||
uiManager.setButtonCallback("shootButton", [this](const std::string&) {
|
||||
uiManager.setButtonPressCallback("shootButton", [this](const std::string&) {
|
||||
if (onFirePressed) onFirePressed();
|
||||
});
|
||||
uiManager.setButtonCallback("shootButton2", [this](const std::string&) {
|
||||
uiManager.setButtonPressCallback("shootButton2", [this](const std::string&) {
|
||||
if (onFirePressed) onFirePressed();
|
||||
});
|
||||
uiManager.setButtonCallback("plusButton", [this](const std::string&) {
|
||||
uiManager.setButtonPressCallback("plusButton", [this](const std::string&) {
|
||||
int newVel = Environment::shipState.selectedVelocity + 1;
|
||||
if (newVel > 4) newVel = 4;
|
||||
if (onVelocityChanged) onVelocityChanged(newVel);
|
||||
});
|
||||
uiManager.setButtonCallback("minusButton", [this](const std::string&) {
|
||||
uiManager.setButtonPressCallback("minusButton", [this](const std::string&) {
|
||||
int newVel = Environment::shipState.selectedVelocity - 1;
|
||||
if (newVel < 0) newVel = 0;
|
||||
if (onVelocityChanged) onVelocityChanged(newVel);
|
||||
|
||||
@ -769,6 +769,16 @@ namespace ZL {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::setButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb) {
|
||||
auto b = findButton(name);
|
||||
if (!b) {
|
||||
std::cerr << "UiManager: setButtonPressCallback failed, button not found: " << name << std::endl;
|
||||
return false;
|
||||
}
|
||||
b->onPress = std::move(cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiManager::addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
|
||||
const std::string& trackPath, const std::string& knobPath, float initialValue, bool vertical) {
|
||||
|
||||
@ -855,8 +865,8 @@ namespace ZL {
|
||||
prev.sliders = sliders;
|
||||
prev.textFields = textFields;
|
||||
prev.staticImages = staticImages;
|
||||
prev.pressedButton = pressedButton;
|
||||
prev.pressedSlider = pressedSlider;
|
||||
prev.pressedButtons = pressedButtons;
|
||||
prev.pressedSliders = pressedSliders;
|
||||
prev.focusedTextField = focusedTextField;
|
||||
prev.path = "";
|
||||
|
||||
@ -906,8 +916,8 @@ namespace ZL {
|
||||
sliders = s.sliders;
|
||||
textFields = s.textFields;
|
||||
staticImages = s.staticImages;
|
||||
pressedButton = s.pressedButton;
|
||||
pressedSlider = s.pressedSlider;
|
||||
pressedButtons = s.pressedButtons;
|
||||
pressedSliders = s.pressedSliders;
|
||||
focusedTextField = s.focusedTextField;
|
||||
|
||||
animCallbacks = s.animCallbacks;
|
||||
@ -1122,7 +1132,9 @@ namespace ZL {
|
||||
}
|
||||
}
|
||||
|
||||
void UiManager::onMouseMove(int x, int y) {
|
||||
void UiManager::onTouchMove(int64_t fingerId, int x, int y) {
|
||||
// Hover state updates only make sense for mouse (single pointer)
|
||||
if (fingerId == MOUSE_FINGER_ID) {
|
||||
for (auto& b : buttons) {
|
||||
if (b->state != ButtonState::Disabled)
|
||||
{
|
||||
@ -1134,9 +1146,11 @@ namespace ZL {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pressedSlider) {
|
||||
auto s = pressedSlider;
|
||||
auto it = pressedSliders.find(fingerId);
|
||||
if (it != pressedSliders.end()) {
|
||||
auto s = it->second;
|
||||
float t;
|
||||
if (s->vertical) {
|
||||
t = (y - s->rect.y) / s->rect.h;
|
||||
@ -1153,20 +1167,22 @@ namespace ZL {
|
||||
}
|
||||
|
||||
|
||||
void UiManager::onMouseDown(int x, int y) {
|
||||
void UiManager::onTouchDown(int64_t fingerId, int x, int y) {
|
||||
for (auto& b : buttons) {
|
||||
if (b->state != ButtonState::Disabled)
|
||||
{
|
||||
if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) {
|
||||
b->state = ButtonState::Pressed;
|
||||
pressedButton = b;
|
||||
pressedButtons[fingerId] = b;
|
||||
if (b->onPress) b->onPress(b->name);
|
||||
break; // a single finger can only press one button
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& s : sliders) {
|
||||
if (s->rect.contains((float)x, (float)y)) {
|
||||
pressedSlider = s;
|
||||
pressedSliders[fingerId] = s;
|
||||
float t;
|
||||
if (s->vertical) {
|
||||
t = (y - s->rect.y) / s->rect.h;
|
||||
@ -1194,29 +1210,32 @@ namespace ZL {
|
||||
}
|
||||
}
|
||||
|
||||
void UiManager::onMouseUp(int x, int y) {
|
||||
void UiManager::onTouchUp(int64_t fingerId, int x, int y) {
|
||||
std::vector<std::shared_ptr<UiButton>> clicked;
|
||||
|
||||
for (auto& b : buttons) {
|
||||
if (!b) continue;
|
||||
auto btnIt = pressedButtons.find(fingerId);
|
||||
if (btnIt != pressedButtons.end()) {
|
||||
auto b = btnIt->second;
|
||||
if (b) {
|
||||
bool contains = b->rect.contains((float)x, (float)y);
|
||||
|
||||
if (b->state == ButtonState::Pressed) {
|
||||
if (contains && pressedButton == b) {
|
||||
if (contains) {
|
||||
clicked.push_back(b);
|
||||
}
|
||||
b->state = contains ? ButtonState::Hover : ButtonState::Normal;
|
||||
// On mouse: leave Hover if still over button. On touch: always Normal.
|
||||
b->state = (contains && fingerId == MOUSE_FINGER_ID) ? ButtonState::Hover : ButtonState::Normal;
|
||||
}
|
||||
}
|
||||
pressedButtons.erase(btnIt);
|
||||
}
|
||||
|
||||
pressedSliders.erase(fingerId);
|
||||
|
||||
for (auto& b : clicked) {
|
||||
if (b->onClick) {
|
||||
b->onClick(b->name);
|
||||
}
|
||||
}
|
||||
|
||||
pressedButton.reset();
|
||||
if (pressedSlider) pressedSlider.reset();
|
||||
}
|
||||
|
||||
void UiManager::onKeyPress(unsigned char key) {
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@ -93,6 +94,7 @@ namespace ZL {
|
||||
VertexRenderStruct mesh;
|
||||
|
||||
std::function<void(const std::string&)> onClick;
|
||||
std::function<void(const std::string&)> onPress; // fires on touch/mouse down
|
||||
|
||||
// animation runtime
|
||||
float animOffsetX = 0.0f;
|
||||
@ -224,19 +226,35 @@ namespace ZL {
|
||||
public:
|
||||
UiManager() = default;
|
||||
|
||||
// Sentinel finger ID used for mouse events on desktop/web
|
||||
static constexpr int64_t MOUSE_FINGER_ID = -1LL;
|
||||
|
||||
void replaceRoot(std::shared_ptr<UiNode> newRoot);
|
||||
void loadFromFile(const std::string& path, Renderer& renderer, const std::string& zipFile = "");
|
||||
|
||||
void draw(Renderer& renderer);
|
||||
|
||||
void onMouseMove(int x, int y);
|
||||
void onMouseDown(int x, int y);
|
||||
void onMouseUp(int x, int y);
|
||||
// Multi-touch methods (used directly for touch events with per-finger IDs)
|
||||
void onTouchDown(int64_t fingerId, int x, int y);
|
||||
void onTouchUp(int64_t fingerId, int x, int y);
|
||||
void onTouchMove(int64_t fingerId, int x, int y);
|
||||
|
||||
// Mouse convenience wrappers (delegate to touch with MOUSE_FINGER_ID)
|
||||
void onMouseMove(int x, int y) { onTouchMove(MOUSE_FINGER_ID, x, y); }
|
||||
void onMouseDown(int x, int y) { onTouchDown(MOUSE_FINGER_ID, x, y); }
|
||||
void onMouseUp(int x, int y) { onTouchUp(MOUSE_FINGER_ID, x, y); }
|
||||
|
||||
void onKeyPress(unsigned char key);
|
||||
void onKeyBackspace();
|
||||
|
||||
// Returns true if any finger is currently interacting with UI
|
||||
bool isUiInteraction() const {
|
||||
return pressedButton != nullptr || pressedSlider != nullptr || focusedTextField != nullptr;
|
||||
return !pressedButtons.empty() || !pressedSliders.empty() || focusedTextField != nullptr;
|
||||
}
|
||||
|
||||
// Returns true if this specific finger is currently interacting with UI
|
||||
bool isUiInteractionForFinger(int64_t fingerId) const {
|
||||
return pressedButtons.count(fingerId) > 0 || pressedSliders.count(fingerId) > 0 || focusedTextField != nullptr;
|
||||
}
|
||||
|
||||
void stopAllAnimations() {
|
||||
@ -255,6 +273,7 @@ namespace ZL {
|
||||
std::shared_ptr<UiButton> findButton(const std::string& name);
|
||||
|
||||
bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
||||
bool setButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb);
|
||||
|
||||
bool addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
|
||||
const std::string& trackPath, const std::string& knobPath, float initialValue = 0.0f, bool vertical = true);
|
||||
@ -322,8 +341,9 @@ namespace ZL {
|
||||
std::map<std::shared_ptr<UiNode>, std::vector<ActiveAnim>> nodeActiveAnims;
|
||||
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks; // key: (nodeName, animName)
|
||||
|
||||
std::shared_ptr<UiButton> pressedButton;
|
||||
std::shared_ptr<UiSlider> pressedSlider;
|
||||
// Per-finger tracking for multi-touch support
|
||||
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
||||
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
||||
std::shared_ptr<UiTextField> focusedTextField;
|
||||
|
||||
struct MenuState {
|
||||
@ -332,8 +352,8 @@ namespace ZL {
|
||||
std::vector<std::shared_ptr<UiSlider>> sliders;
|
||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
|
||||
std::shared_ptr<UiButton> pressedButton;
|
||||
std::shared_ptr<UiSlider> pressedSlider;
|
||||
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
||||
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
||||
std::shared_ptr<UiTextField> focusedTextField;
|
||||
std::string path;
|
||||
std::map<std::pair<std::string, std::string>, std::function<void()>> animCallbacks;
|
||||
|
||||
@ -127,7 +127,11 @@ int main(int argc, char* argv[]) {
|
||||
// канваса и отправит SDL_WINDOWEVENT_RESIZED для настройки проекции.
|
||||
applyResize(canvasW, canvasH);
|
||||
|
||||
// Prevent mouse clicks from generating fake SDL_FINGERDOWN events (desktop browser)
|
||||
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
||||
// Prevent touch events from generating fake SDL_MOUSEBUTTONDOWN events (mobile browser),
|
||||
// since we now handle SDL_FINGERDOWN directly for multi-touch support.
|
||||
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
||||
|
||||
emscripten_set_main_loop(MainLoop, 0, 1);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user