android-native: auto hunt, in-client account registration, mobile UI polish
- Auto hunt (client-only): PythonPlayerAutoHunt.cpp + AutoHuntScript.inc as the embedded mt_autohunt module, AUTO touch button, F9/F8. - Account registration: tools/40250/register_server.py (rc.d mt_register, :11080) inserts into account.account; net.RegisterAccount/GetRegisterAccountResult (PythonAccountRegister.cpp) and the mt_register login-window dialog (RegisterScript.inc). - --py-exec also runs in --login-screen mode for scripted login-screen tests. - Touch controller, window manager and render command updates for the Android native client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
53d06c3c26
commit
3e3708ef06
+120
-5
@@ -19,6 +19,9 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#ifdef __ANDROID__
|
||||
#include <jni.h>
|
||||
#endif
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "stb_image.h"
|
||||
|
||||
@@ -714,6 +717,21 @@ std::string bundled_file_path(const char* name) {
|
||||
return std::string(base ? base : "./") + name;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// MainActivity.performHaptic(): the long-press vibration of the touch gestures.
|
||||
void android_haptic() {
|
||||
auto* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
|
||||
auto activity = static_cast<jobject>(SDL_GetAndroidActivity());
|
||||
if (!env || !activity) return;
|
||||
jclass cls = env->GetObjectClass(activity);
|
||||
if (jmethodID method = env->GetMethodID(cls, "performHaptic", "()V"))
|
||||
env->CallVoidMethod(activity, method);
|
||||
if (env->ExceptionCheck()) env->ExceptionClear();
|
||||
env->DeleteLocalRef(cls);
|
||||
env->DeleteLocalRef(activity);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<std::uint32_t> read_spirv(const char* name) {
|
||||
std::size_t size = 0;
|
||||
void* bytes = SDL_LoadFile(bundled_file_path(name).c_str(), &size);
|
||||
@@ -848,6 +866,8 @@ public:
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Android's back key reaches the game as SDL_SCANCODE_AC_BACK (-> ESC) instead of finishing the activity.
|
||||
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) throw std::runtime_error(SDL_GetError());
|
||||
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
|
||||
window_ = SDL_CreateWindow(
|
||||
@@ -1010,12 +1030,24 @@ public:
|
||||
return {0, 0, float(extent_.width), float(extent_.height), 0, 1};
|
||||
}
|
||||
|
||||
// Window pixels kept clear at the left and right screen edges (rounded corners, cutouts);
|
||||
// MainActivity measures them and passes --safe-inset-px.
|
||||
void set_safe_inset_px(int px) { safe_inset_px_ = std::max(0, px); }
|
||||
// Test builds: frames per second in the top-left corner (--show-fps).
|
||||
void set_show_fps(bool show) { show_fps_ = show; }
|
||||
// The same inset in logical UI pixels: the 40250 window layers are shifted and narrowed by it.
|
||||
int ui_safe_inset() const {
|
||||
int pw = 0, ph = 0;
|
||||
if (safe_inset_px_ <= 0 || !window_ || !SDL_GetWindowSizeInPixels(window_, &pw, &ph) || ph <= 0) return 0;
|
||||
return int(std::lround(double(safe_inset_px_) * logical_height() / ph));
|
||||
}
|
||||
|
||||
// D3DVIEWPORT8 (in the game's logical screen pixels) scaled to the swapchain extent.
|
||||
VkViewport draw_viewport(const Render3DDraw& draw) const {
|
||||
if (draw.viewport[2] <= 0 || draw.viewport[3] <= 0) return full_viewport();
|
||||
const float sx = float(extent_.width) / float(logical_width());
|
||||
const float sy = float(extent_.height) / float(logical_height());
|
||||
return {draw.viewport[0] * sx, draw.viewport[1] * sy, draw.viewport[2] * sx, draw.viewport[3] * sy,
|
||||
return {(draw.viewport[0] + draw.ui_offset_x) * sx, draw.viewport[1] * sy, draw.viewport[2] * sx, draw.viewport[3] * sy,
|
||||
draw.viewport_z[0], draw.viewport_z[1]};
|
||||
}
|
||||
|
||||
@@ -1160,6 +1192,24 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Touch hosts: SDL text input (and with it the system keyboard) is on only while the player
|
||||
// types into a tapped EditLine; desktop keeps the always-on text input from window creation.
|
||||
void sync_screen_keyboard() {
|
||||
const bool want = touch_controller.wants_screen_keyboard();
|
||||
const unsigned serial = touch_controller.screen_keyboard_serial();
|
||||
if (want && (!SDL_TextInputActive(window_) || serial != screen_keyboard_serial_)) {
|
||||
SDL_PropertiesID props = SDL_CreateProperties();
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT);
|
||||
SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE);
|
||||
SDL_SetBooleanProperty(props, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, false);
|
||||
SDL_StartTextInputWithProperties(window_, props);
|
||||
SDL_DestroyProperties(props);
|
||||
} else if (!want && SDL_TextInputActive(window_)) {
|
||||
SDL_StopTextInput(window_);
|
||||
}
|
||||
screen_keyboard_serial_ = serial;
|
||||
}
|
||||
|
||||
bool poll(bool forward_to_live_client = false) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
auto map_mouse = [&](float wx, float wy, int& ox, int& oy) {
|
||||
@@ -1190,6 +1240,13 @@ public:
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
// Touches arrive as SDL_EVENT_FINGER_*; the touch-synthesized mouse copy
|
||||
// would register as a second finger (101) and turn every drag into a pinch.
|
||||
if (touch_controller.is_enabled() &&
|
||||
((event.type == SDL_EVENT_MOUSE_MOTION && event.motion.which == SDL_TOUCH_MOUSEID) ||
|
||||
((event.type == SDL_EVENT_MOUSE_BUTTON_DOWN || event.type == SDL_EVENT_MOUSE_BUTTON_UP) &&
|
||||
event.button.which == SDL_TOUCH_MOUSEID)))
|
||||
continue;
|
||||
if (forward_to_live_client && event.type == SDL_EVENT_MOUSE_MOTION) {
|
||||
int mx = 0, my = 0;
|
||||
map_mouse(event.motion.x, event.motion.y, mx, my);
|
||||
@@ -1204,9 +1261,10 @@ public:
|
||||
if (event.type == SDL_EVENT_WINDOW_RESIZED || event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) {
|
||||
int ew = 0, eh = 0;
|
||||
SDL_GetWindowSize(window_, &ew, &eh);
|
||||
touch_controller.update_screen_size(int(logical_width()), int(logical_height()));
|
||||
touch_controller.update_screen_size(int(logical_width()), int(logical_height()), ui_safe_inset());
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (forward_to_live_client && ew > 0 && eh > 0) {
|
||||
PythonBoot::SetUISafeInset(ui_safe_inset());
|
||||
PythonBoot::SetUISize(int(logical_width()), int(logical_height()));
|
||||
}
|
||||
#endif
|
||||
@@ -1259,6 +1317,8 @@ public:
|
||||
PythonBoot::UIMouseWheel(int(event.wheel.y * 120.0f));
|
||||
} else if (event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_KEY_UP) {
|
||||
const bool pressed = event.type == SDL_EVENT_KEY_DOWN;
|
||||
// Android back = ESC: closes the top window, or opens the system menu (game.py).
|
||||
if (event.key.scancode == SDL_SCANCODE_AC_BACK) event.key.scancode = SDL_SCANCODE_ESCAPE;
|
||||
if (pressed) {
|
||||
if (const int vk = sdl_scancode_to_vk(event.key.scancode))
|
||||
PythonBoot::UIIMEKeyDown(vk);
|
||||
@@ -1304,6 +1364,8 @@ public:
|
||||
flush_mouse_move();
|
||||
#endif
|
||||
touch_controller.update();
|
||||
if (touch_controller.is_enabled())
|
||||
sync_screen_keyboard();
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (forward_to_live_client && !hardware_cursor_enabled_ && !os_cursor_hidden_ && !touch_controller.is_enabled()) {
|
||||
SDL_HideCursor();
|
||||
@@ -1452,6 +1514,8 @@ public:
|
||||
prepared_draw.fixed_state = make_fixed_function_state(draw);
|
||||
// XYZRHW vertices are already in screen space and bypass the viewport transform.
|
||||
prepared_draw.viewport = draw.pretransformed ? full_viewport() : draw_viewport(draw);
|
||||
if (draw.pretransformed && draw.ui_offset_x != 0)
|
||||
prepared_draw.viewport.x += draw.ui_offset_x * float(extent_.width) / float(logical_width());
|
||||
prepared_draws.push_back(prepared_draw);
|
||||
vertex_count += geometry.vertex_count;
|
||||
index_count += geometry.index_count;
|
||||
@@ -1470,10 +1534,19 @@ public:
|
||||
if (ui_mapped_) {
|
||||
const uint32_t target_ui_w = ui_width ? ui_width : 960;
|
||||
const uint32_t target_ui_h = ui_height ? ui_height : 640;
|
||||
if (touch_controller.is_enabled()) {
|
||||
touch_controller.update_screen_size(int(target_ui_w), int(target_ui_h));
|
||||
update_fps_counter();
|
||||
if (touch_controller.is_enabled() || show_fps_) {
|
||||
std::vector<UIRenderCommand> combined_ui = ui_commands;
|
||||
touch_controller.append_ui_commands(combined_ui);
|
||||
if (touch_controller.is_enabled()) {
|
||||
touch_controller.update_screen_size(int(target_ui_w), int(target_ui_h), ui_safe_inset());
|
||||
touch_controller.append_ui_commands(combined_ui);
|
||||
}
|
||||
if (show_fps_ && fps_ >= 0) {
|
||||
// Top left, inside the safe area and clear of 40250's corner icon.
|
||||
const float inset = float(std::min<int>(ui_safe_inset(), int(target_ui_w) / 8));
|
||||
TouchController::draw_segment_text(combined_ui, "FPS " + std::to_string(fps_),
|
||||
inset + 40.0f, 8.0f, 12.0f, 0xFF40FF40);
|
||||
}
|
||||
if (!combined_ui.empty()) {
|
||||
build_ui_batches(target_ui_w, target_ui_h, combined_ui, capture_textures, ui_batches, ui_quad_count);
|
||||
}
|
||||
@@ -3055,6 +3128,27 @@ private:
|
||||
float timestamp_period_ns_ = 1.0f;
|
||||
float max_anisotropy_ = 1.0f;
|
||||
SDL_Window* window_ = nullptr;
|
||||
unsigned screen_keyboard_serial_ = 0;
|
||||
int safe_inset_px_ = 0;
|
||||
bool show_fps_ = false;
|
||||
int fps_ = -1;
|
||||
int fps_frames_ = 0;
|
||||
std::chrono::steady_clock::time_point fps_window_start_{};
|
||||
|
||||
// Presented frames over the last half second.
|
||||
void update_fps_counter() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (fps_frames_++ == 0) {
|
||||
fps_window_start_ = now;
|
||||
return;
|
||||
}
|
||||
const double elapsed = std::chrono::duration<double>(now - fps_window_start_).count();
|
||||
if (elapsed >= 0.5) {
|
||||
fps_ = int(std::lround((fps_frames_ - 1) / elapsed));
|
||||
fps_frames_ = 1;
|
||||
fps_window_start_ = now;
|
||||
}
|
||||
}
|
||||
VkInstance instance_ = VK_NULL_HANDLE;
|
||||
VkSurfaceKHR surface_ = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physical_ = VK_NULL_HANDLE;
|
||||
@@ -3198,6 +3292,10 @@ void print_summary(const VulkanWindow& renderer, int completed, double wall_ms,
|
||||
}
|
||||
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
// --py-exec: a test hook run once the auto-login run reaches the GameWindow (or, with --login-screen,
|
||||
// once the LoginWindow is up).
|
||||
std::string g_py_exec_after_login;
|
||||
|
||||
bool py_exec(const std::string& code) {
|
||||
std::string err;
|
||||
if (!PythonBoot::RunLine(code.c_str(), &err)) {
|
||||
@@ -3342,6 +3440,7 @@ int run_live_client(
|
||||
#endif
|
||||
|
||||
SetPlatformServerTime(123456789);
|
||||
PythonBoot::SetUISafeInset(renderer.ui_safe_inset());
|
||||
PythonBoot::SetUISize(static_cast<int>(renderer.logical_width()), static_cast<int>(renderer.logical_height()));
|
||||
if (!PythonBoot::RunMainScript("", &error) || !PythonBoot::IsAppLooping())
|
||||
throw std::runtime_error("PythonBoot::RunMainScript: " + error);
|
||||
@@ -3396,6 +3495,8 @@ int run_live_client(
|
||||
renderer.sync_game_cursor();
|
||||
PythonBoot::UIRender();
|
||||
}
|
||||
if (!g_py_exec_after_login.empty() && !py_exec(g_py_exec_after_login))
|
||||
throw std::runtime_error("--py-exec failed");
|
||||
} else {
|
||||
char login_cmd[512];
|
||||
std::snprintf(
|
||||
@@ -3455,6 +3556,8 @@ int run_live_client(
|
||||
if (!configure_macos_numeric_quickslots())
|
||||
throw std::runtime_error("failed to configure numeric quick slots on macOS");
|
||||
#endif
|
||||
if (!g_py_exec_after_login.empty() && !py_exec(g_py_exec_after_login))
|
||||
throw std::runtime_error("--py-exec failed");
|
||||
|
||||
const int warmup_frames = std::max(10, fake_mobs / 2 + 10);
|
||||
for (int i = 0; i < warmup_frames; ++i) {
|
||||
@@ -3583,6 +3686,8 @@ int main(int argc, char** argv) {
|
||||
bool gpu_skinning = true;
|
||||
bool native_terrain = true;
|
||||
bool mobile_mode = false;
|
||||
int safe_inset_px = 0;
|
||||
bool show_fps = false;
|
||||
#ifdef __ANDROID__
|
||||
mobile_mode = true;
|
||||
#endif
|
||||
@@ -3633,6 +3738,11 @@ int main(int argc, char** argv) {
|
||||
else if (arg == "--no-gpu-skinning") gpu_skinning = false;
|
||||
else if (arg == "--no-terrain") native_terrain = false;
|
||||
else if (arg == "--mobile") mobile_mode = true;
|
||||
else if (arg == "--safe-inset-px" && i+1 < argc) safe_inset_px = std::stoi(argv[++i]);
|
||||
else if (arg == "--show-fps") show_fps = true;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
else if (arg == "--py-exec" && i+1 < argc) g_py_exec_after_login = argv[++i];
|
||||
#endif
|
||||
else throw std::runtime_error(
|
||||
"usage: mt_native_render [--frames N] [--interactive] [--login-screen|--selection-screen] "
|
||||
"[--live-server HOST:AUTH_PORT:GAME_PORT] [--width W] [--height H] "
|
||||
@@ -3677,9 +3787,14 @@ int main(int argc, char** argv) {
|
||||
triangles_per_draw == 0 || triangles_per_draw > 10000)
|
||||
throw std::runtime_error("invalid frame/draw/triangle count");
|
||||
VulkanWindow renderer(vsync, init_width, init_height);
|
||||
renderer.set_safe_inset_px(safe_inset_px);
|
||||
renderer.set_show_fps(show_fps);
|
||||
if (!screenshot_out.empty() && frames > 0) renderer.request_screenshot(screenshot_out, std::uint64_t(frames));
|
||||
if (mobile_mode) {
|
||||
renderer.touch_controller.set_enabled(true);
|
||||
#ifdef __ANDROID__
|
||||
renderer.touch_controller.set_haptic(android_haptic);
|
||||
#endif
|
||||
renderer.touch_controller.update_screen_size(init_width, init_height);
|
||||
}
|
||||
if (!live_client_dir.empty()) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
class TouchController {
|
||||
public:
|
||||
struct ButtonDef {
|
||||
int id; // 1 = attack, 2..7 = quick slots 1..6
|
||||
int id; // 1 = attack, 2..7 = quick slots 1..6, 8 = auto hunt
|
||||
int dik;
|
||||
float x, y, radius;
|
||||
const char* label;
|
||||
@@ -31,15 +31,30 @@ public:
|
||||
init_buttons();
|
||||
}
|
||||
|
||||
void set_enabled(bool enabled) { enabled_ = enabled; }
|
||||
void set_enabled(bool enabled) {
|
||||
enabled_ = enabled;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetTouchInput(enabled);
|
||||
#endif
|
||||
}
|
||||
bool is_enabled() const { return enabled_; }
|
||||
// Short vibration when a long press fires (the host wires it to the platform).
|
||||
void set_haptic(void (*haptic)()) { haptic_ = haptic; }
|
||||
|
||||
void update_screen_size(int width, int height) {
|
||||
// The system keyboard is up only after the player taps the focused EditLine, not when a
|
||||
// script focuses one on its own (intrologin.py focuses the ID field as the board opens).
|
||||
// The serial changes on every such tap so the host can re-show a keyboard the user dismissed.
|
||||
bool wants_screen_keyboard() const { return keyboard_requested_; }
|
||||
unsigned screen_keyboard_serial() const { return keyboard_serial_; }
|
||||
|
||||
// safe_inset: logical pixels kept clear at the left and right edges (rounded corners, cutouts).
|
||||
void update_screen_size(int width, int height, int safe_inset = 0) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
screen_w_ = width;
|
||||
screen_h_ = height;
|
||||
safe_inset_ = float(std::max(0, safe_inset));
|
||||
// Position joystick on lower-left
|
||||
joystick_base_x_ = std::max(90.0f, float(width) * 0.12f);
|
||||
joystick_base_x_ = safe_inset_ + std::max(90.0f, float(width) * 0.12f);
|
||||
joystick_base_y_ = float(height) - std::max(90.0f, float(height) * 0.22f);
|
||||
if (!joystick_active_) {
|
||||
joystick_knob_x_ = joystick_base_x_;
|
||||
@@ -51,6 +66,37 @@ public:
|
||||
// Called once per frame to maintain continuous analog movement
|
||||
void update() {
|
||||
if (!enabled_) return;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (keyboard_requested_ && !PythonBoot::TextInputFocused())
|
||||
keyboard_requested_ = false;
|
||||
// A world tap presses and releases on later frames than its mouse move so
|
||||
// 40250's per-frame actor picking has already hit the target under the finger.
|
||||
if (tap_stage_ == 1) {
|
||||
PythonBoot::UIMouseMove(int(tap_x_), int(tap_y_));
|
||||
PythonBoot::UIMouseButton(1, true, int(tap_x_), int(tap_y_));
|
||||
tap_stage_ = 2;
|
||||
} else if (tap_stage_ == 2) {
|
||||
PythonBoot::UIMouseButton(1, false, int(tap_x_), int(tap_y_));
|
||||
tap_stage_ = 0;
|
||||
}
|
||||
// A UI finger held still turns into a right click (use item, equip, skill up...).
|
||||
if (ui_touch_finger_id_ >= 0 && ui_gesture_ == UiGesture::Pending &&
|
||||
get_time_sec() - ui_down_time_ >= kLongPressSec) {
|
||||
ui_gesture_ = UiGesture::LongPressed;
|
||||
last_tap_time_ = -1.0;
|
||||
PythonBoot::UIMouseButton(2, true, int(ui_start_x_), int(ui_start_y_));
|
||||
PythonBoot::UIMouseButton(2, false, int(ui_start_x_), int(ui_start_y_));
|
||||
if (haptic_) haptic_();
|
||||
}
|
||||
for (auto& btn : buttons_) {
|
||||
if (btn.id == kAutoHuntButton && btn.pressed && !auto_hunt_long_pressed_ &&
|
||||
get_time_sec() - auto_hunt_down_time_ >= kLongPressSec) {
|
||||
auto_hunt_long_pressed_ = true;
|
||||
PythonBoot::AutoHuntOpenSettings();
|
||||
if (haptic_) haptic_();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
const bool controls_visible = gameplay_controls_visible();
|
||||
if (!controls_visible) {
|
||||
if (controls_were_visible_)
|
||||
@@ -79,11 +125,17 @@ public:
|
||||
if (btn_idx >= 0) {
|
||||
auto& btn = buttons_[btn_idx];
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (btn.id >= 2 && PythonBoot::TryAssignAttachedObjectToLocalQuickSlot(btn.id - 2))
|
||||
if (is_quick_slot_button(btn.id) && PythonBoot::TryAssignAttachedObjectToLocalQuickSlot(btn.id - 2))
|
||||
return true;
|
||||
#endif
|
||||
btn.pressed = true;
|
||||
btn.finger_id = finger_id;
|
||||
if (btn.id == kAutoHuntButton) {
|
||||
// Tap toggles; holding opens the settings window (update()).
|
||||
auto_hunt_down_time_ = get_time_sec();
|
||||
auto_hunt_long_pressed_ = false;
|
||||
return true;
|
||||
}
|
||||
trigger_button(btn.dik, true);
|
||||
return true;
|
||||
}
|
||||
@@ -91,16 +143,21 @@ public:
|
||||
|
||||
// Everything else uses the unchanged 40250 UI and its normal mouse path.
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
// Only hover here (the tooltip shows while the finger rests); what the finger does next picks
|
||||
// the gesture: lift = click, slide = press + drag, hold = right click, second tap = double click.
|
||||
if (PythonBoot::IsPointInsideActiveUI(int(px), int(py))) {
|
||||
ui_touch_finger_id_ = finger_id;
|
||||
ui_gesture_ = UiGesture::Pending;
|
||||
ui_start_x_ = px;
|
||||
ui_start_y_ = py;
|
||||
ui_down_time_ = get_time_sec();
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
PythonBoot::UIMouseButton(1, true, int(px), int(py));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Start movement only in the gameplay phase.
|
||||
if (controls_visible && norm_x < 0.40f && norm_y > 0.35f && !joystick_active_) {
|
||||
// Mobile-game layout: the left half moves (floating stick), the right half looks.
|
||||
if (controls_visible && norm_x < kMoveZoneRight && norm_y > kMoveZoneTop && !joystick_active_) {
|
||||
joystick_active_ = true;
|
||||
joystick_finger_id_ = finger_id;
|
||||
joystick_base_x_ = px;
|
||||
@@ -114,20 +171,28 @@ public:
|
||||
if (!controls_visible)
|
||||
return false;
|
||||
|
||||
// The remaining gameplay area controls camera and world selection.
|
||||
if (camera_finger_id_ < 0) {
|
||||
camera_finger_id_ = finger_id;
|
||||
camera_last_x_ = px;
|
||||
camera_last_y_ = py;
|
||||
camera_start_x_ = px;
|
||||
camera_start_y_ = py;
|
||||
camera_dragged_ = false;
|
||||
camera_down_time_ = get_time_sec();
|
||||
const bool in_look_zone = norm_x >= kMoveZoneRight;
|
||||
if (look_finger_id_ < 0) {
|
||||
// Outside the look zone the finger can only tap-select.
|
||||
look_finger_id_ = finger_id;
|
||||
look_can_rotate_ = in_look_zone;
|
||||
look_rotating_ = false;
|
||||
look_moved_ = false;
|
||||
look_start_x_ = look_last_x_ = px;
|
||||
look_start_y_ = look_last_y_ = py;
|
||||
look_down_time_ = get_time_sec();
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseMove(int(px), int(py)); // start picking under the finger
|
||||
#endif
|
||||
return true;
|
||||
} else if (pinch_finger2_ < 0) {
|
||||
pinch_finger1_ = camera_finger_id_;
|
||||
pinch_finger2_ = finger_id;
|
||||
pinch_last_dist_ = std::hypot(px - camera_last_x_, py - camera_last_y_);
|
||||
}
|
||||
if (pinch_finger_id_ < 0 && look_can_rotate_ && in_look_zone) {
|
||||
pinch_finger_id_ = finger_id;
|
||||
pinch_x_ = px;
|
||||
pinch_y_ = py;
|
||||
pinch_last_dist_ = std::hypot(px - look_last_x_, py - look_last_y_);
|
||||
look_rotating_ = false;
|
||||
look_moved_ = true; // a pinch never ends as a tap
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -143,6 +208,14 @@ public:
|
||||
// 1. UI Touch dragging (e.g. dragging item in inventory or scrollbar)
|
||||
if (ui_touch_finger_id_ == finger_id) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (ui_gesture_ == UiGesture::Pending &&
|
||||
std::hypot(px - ui_start_x_, py - ui_start_y_) > kUiDragSlop) {
|
||||
// 40250 drag: press where the finger landed (a slot attaches its icon, a title bar
|
||||
// or scroll bar captures), then follow the finger.
|
||||
ui_gesture_ = UiGesture::Dragging;
|
||||
last_tap_time_ = -1.0;
|
||||
PythonBoot::UIMouseButton(1, true, int(ui_start_x_), int(ui_start_y_));
|
||||
}
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
#endif
|
||||
return true;
|
||||
@@ -169,38 +242,42 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Two-finger pinch zoom
|
||||
if (pinch_finger1_ >= 0 && pinch_finger2_ >= 0 &&
|
||||
(finger_id == pinch_finger1_ || finger_id == pinch_finger2_)) {
|
||||
const float cur_dist = std::hypot(px - camera_last_x_, py - camera_last_y_);
|
||||
if (pinch_last_dist_ > 1.0f && cur_dist > 1.0f) {
|
||||
const float delta_d = cur_dist - pinch_last_dist_;
|
||||
if (std::abs(delta_d) > 2.0f) {
|
||||
// 4. Two-finger pinch zoom (look finger + second right-half finger)
|
||||
if (pinch_finger_id_ >= 0 && (finger_id == pinch_finger_id_ || finger_id == look_finger_id_)) {
|
||||
if (finger_id == pinch_finger_id_) {
|
||||
pinch_x_ = px;
|
||||
pinch_y_ = py;
|
||||
} else {
|
||||
look_last_x_ = px;
|
||||
look_last_y_ = py;
|
||||
}
|
||||
const float cur_dist = std::hypot(pinch_x_ - look_last_x_, pinch_y_ - look_last_y_);
|
||||
const float delta_d = cur_dist - pinch_last_dist_;
|
||||
if (std::abs(delta_d) > 2.0f) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseWheel(int(delta_d * 8.0f));
|
||||
PythonBoot::UIMouseWheel(int(delta_d * 8.0f));
|
||||
#endif
|
||||
pinch_last_dist_ = cur_dist;
|
||||
}
|
||||
pinch_last_dist_ = cur_dist;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Single-finger camera drag
|
||||
if (finger_id == camera_finger_id_) {
|
||||
const float total_dist = std::hypot(px - camera_start_x_, py - camera_start_y_);
|
||||
if (total_dist > 6.0f) {
|
||||
if (!camera_dragged_) {
|
||||
camera_dragged_ = true;
|
||||
// 5. Single-finger look drag: yaw by dx, pitch by dy, stops the moment the finger stops.
|
||||
if (finger_id == look_finger_id_) {
|
||||
if (!look_moved_ && std::hypot(px - look_start_x_, py - look_start_y_) > kLookDeadZone) {
|
||||
look_moved_ = true;
|
||||
look_rotating_ = look_can_rotate_;
|
||||
look_last_x_ = px; // start from here so the dead zone does not jump the camera
|
||||
look_last_y_ = py;
|
||||
}
|
||||
if (look_rotating_) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::CameraBeginDrag(int(camera_start_x_), int(camera_start_y_));
|
||||
#endif
|
||||
}
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::CameraDrag(int(px), int(py));
|
||||
PythonBoot::CameraRotateBy((px - look_last_x_) * kLookYawDegPerPx,
|
||||
(py - look_last_y_) * kLookPitchDegPerPx);
|
||||
#endif
|
||||
}
|
||||
camera_last_x_ = px;
|
||||
camera_last_y_ = py;
|
||||
look_last_x_ = px;
|
||||
look_last_y_ = py;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -217,7 +294,41 @@ public:
|
||||
if (ui_touch_finger_id_ == finger_id) {
|
||||
ui_touch_finger_id_ = -1;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseButton(1, false, int(px), int(py));
|
||||
if (ui_gesture_ == UiGesture::Dragging) {
|
||||
// Dropped on a skill button: that quick slot takes the dragged icon.
|
||||
const int btn_idx = gameplay_controls_visible() ? find_button(px, py) : -1;
|
||||
if (btn_idx >= 0 && is_quick_slot_button(buttons_[btn_idx].id) && PythonBoot::UIIsAttaching()) {
|
||||
PythonBoot::TryAssignAttachedObjectToLocalQuickSlot(buttons_[btn_idx].id - 2);
|
||||
PythonBoot::UIMouseButton(1, false, int(ui_start_x_), int(ui_start_y_));
|
||||
} else {
|
||||
PythonBoot::UIMouseButton(1, false, int(px), int(py));
|
||||
}
|
||||
} else if (ui_gesture_ == UiGesture::Pending) {
|
||||
const double now = get_time_sec();
|
||||
const int x = int(ui_start_x_), y = int(ui_start_y_);
|
||||
if (last_tap_time_ >= 0.0 && now - last_tap_time_ < kDoubleTapSec &&
|
||||
std::hypot(ui_start_x_ - last_tap_x_, ui_start_y_ - last_tap_y_) < kDoubleTapSlop) {
|
||||
// Win32 sends DOWN, UP, DBLCLK, UP; the first click picked the icon up, which a
|
||||
// mouse user would have seen and a finger hides, so put it back first.
|
||||
if (last_tap_attached_ && PythonBoot::UIIsAttaching())
|
||||
PythonBoot::UIDeattachObject();
|
||||
PythonBoot::UIMouseDoubleClick(x, y);
|
||||
PythonBoot::UIMouseButton(1, false, x, y);
|
||||
last_tap_time_ = -1.0;
|
||||
} else {
|
||||
const bool was_attaching = PythonBoot::UIIsAttaching();
|
||||
PythonBoot::UIMouseButton(1, true, x, y);
|
||||
PythonBoot::UIMouseButton(1, false, x, y);
|
||||
last_tap_time_ = now;
|
||||
last_tap_x_ = ui_start_x_;
|
||||
last_tap_y_ = ui_start_y_;
|
||||
last_tap_attached_ = !was_attaching && PythonBoot::UIIsAttaching();
|
||||
}
|
||||
keyboard_requested_ = PythonBoot::TextInputFocused() &&
|
||||
PythonBoot::IsPointInsideFocusedWindow(x, y);
|
||||
if (keyboard_requested_) ++keyboard_serial_;
|
||||
}
|
||||
ui_gesture_ = UiGesture::None;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
@@ -240,34 +351,41 @@ public:
|
||||
if (btn.pressed) {
|
||||
btn.pressed = false;
|
||||
trigger_button(btn.dik, false);
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (btn.id == kAutoHuntButton && !auto_hunt_long_pressed_)
|
||||
PythonBoot::AutoHuntToggle();
|
||||
#endif
|
||||
}
|
||||
btn.finger_id = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Pinch end
|
||||
if (finger_id == pinch_finger1_ || finger_id == pinch_finger2_) {
|
||||
pinch_finger1_ = -1;
|
||||
pinch_finger2_ = -1;
|
||||
// 4. Pinch end: the finger left down keeps looking (never a tap).
|
||||
if (pinch_finger_id_ >= 0 && (finger_id == pinch_finger_id_ || finger_id == look_finger_id_)) {
|
||||
if (finger_id == look_finger_id_) {
|
||||
look_finger_id_ = pinch_finger_id_;
|
||||
look_last_x_ = pinch_x_;
|
||||
look_last_y_ = pinch_y_;
|
||||
}
|
||||
pinch_finger_id_ = -1;
|
||||
pinch_last_dist_ = 0.0f;
|
||||
if (finger_id == camera_finger_id_) camera_finger_id_ = -1;
|
||||
look_rotating_ = look_can_rotate_;
|
||||
look_moved_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Camera finger release
|
||||
if (finger_id == camera_finger_id_) {
|
||||
camera_finger_id_ = -1;
|
||||
if (camera_dragged_) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::CameraEndDrag();
|
||||
#endif
|
||||
} else if ((get_time_sec() - camera_down_time_) < 0.35) {
|
||||
// Short tap on 3D world: select target (mob, NPC, ground)
|
||||
// 5. Look finger release; a short still touch selects a target (mob, NPC, ground).
|
||||
if (finger_id == look_finger_id_) {
|
||||
look_finger_id_ = -1;
|
||||
look_rotating_ = false;
|
||||
keyboard_requested_ = false;
|
||||
if (!look_moved_ && (get_time_sec() - look_down_time_) < kTapMaxSec && tap_stage_ == 0) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
PythonBoot::UIMouseButton(1, true, int(px), int(py));
|
||||
PythonBoot::UIMouseButton(1, false, int(px), int(py));
|
||||
tap_x_ = px;
|
||||
tap_y_ = py;
|
||||
tap_stage_ = 1;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
@@ -290,9 +408,15 @@ public:
|
||||
const float r = btn.radius;
|
||||
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (btn.id >= 2) {
|
||||
const std::string skill_icon = PythonBoot::LocalQuickSlotSkillIcon(btn.id - 2);
|
||||
if (!skill_icon.empty()) {
|
||||
if (btn.id == kAutoHuntButton) {
|
||||
draw_auto_hunt_button(commands, btn);
|
||||
continue;
|
||||
}
|
||||
if (is_quick_slot_button(btn.id)) {
|
||||
std::string skill_icon;
|
||||
float uv[4];
|
||||
int count = 0;
|
||||
if (PythonBoot::LocalQuickSlotIcon(btn.id - 2, &skill_icon, uv, &count)) {
|
||||
UIRenderCommand icon{};
|
||||
icon.kind = UIRenderCommand::Image;
|
||||
icon.x1 = btn.x - r * 0.68f;
|
||||
@@ -301,7 +425,17 @@ public:
|
||||
icon.y2 = btn.y + r * 0.68f;
|
||||
icon.argb = icon_col;
|
||||
icon.text = skill_icon;
|
||||
icon.su = uv[0];
|
||||
icon.sv = uv[1];
|
||||
icon.eu = uv[2];
|
||||
icon.ev = uv[3];
|
||||
commands.push_back(std::move(icon));
|
||||
if (count > 0) {
|
||||
const std::string digits = std::to_string(std::min(count, 9999));
|
||||
const float h = r * 0.26f;
|
||||
draw_segment_text(commands, digits, btn.x + r * 0.62f - segment_text_width(digits, h),
|
||||
btn.y + r * 0.62f - h, h);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -409,12 +543,48 @@ public:
|
||||
if (joystick_active_ && joystick_finger_id_ == 101) {
|
||||
handled |= on_finger_motion(101, norm_x, norm_y);
|
||||
}
|
||||
if (camera_finger_id_ == 101) {
|
||||
if (look_finger_id_ == 101) {
|
||||
handled |= on_finger_motion(101, norm_x, norm_y);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
// Seven-segment text (digits, 'F', 'P', 'S', ' ') with its top-left at (left, top), each glyph h
|
||||
// tall, on a dark backing. Used for quick-slot counts and the test-build FPS readout.
|
||||
static void draw_segment_text(std::vector<UIRenderCommand>& commands, const std::string& text,
|
||||
float left, float top, float h, std::uint32_t color = 0xFFFFFFFF) {
|
||||
static constexpr std::uint8_t kDigits[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
|
||||
const float w = h * 0.55f, gap = h * 0.3f;
|
||||
UIRenderCommand back{};
|
||||
back.kind = UIRenderCommand::Bar;
|
||||
back.x1 = left - 3.0f;
|
||||
back.y1 = top - 3.0f;
|
||||
back.x2 = left + segment_text_width(text, h) + 3.0f;
|
||||
back.y2 = top + h + 3.0f;
|
||||
back.argb = 0xA0000000;
|
||||
commands.push_back(back);
|
||||
float x = left;
|
||||
for (char c : text) {
|
||||
std::uint8_t seg = 0;
|
||||
if (c >= '0' && c <= '9') seg = kDigits[c - '0'];
|
||||
else if (c == 'F') seg = 0x71;
|
||||
else if (c == 'P') seg = 0x73;
|
||||
else if (c == 'S') seg = 0x6D;
|
||||
const float t = top, m = top + h * 0.5f, b = top + h;
|
||||
if (seg & 0x01) draw_line(commands, x, t, x + w, t, color);
|
||||
if (seg & 0x02) draw_line(commands, x + w, t, x + w, m, color);
|
||||
if (seg & 0x04) draw_line(commands, x + w, m, x + w, b, color);
|
||||
if (seg & 0x08) draw_line(commands, x, b, x + w, b, color);
|
||||
if (seg & 0x10) draw_line(commands, x, m, x, b, color);
|
||||
if (seg & 0x20) draw_line(commands, x, t, x, m, color);
|
||||
if (seg & 0x40) draw_line(commands, x, m, x + w, m, color);
|
||||
x += w + gap;
|
||||
}
|
||||
}
|
||||
static float segment_text_width(const std::string& text, float h) {
|
||||
return text.empty() ? 0.0f : float(text.size()) * h * 0.85f - h * 0.3f;
|
||||
}
|
||||
|
||||
private:
|
||||
bool gameplay_controls_visible() const {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
@@ -440,18 +610,47 @@ private:
|
||||
btn.pressed = false;
|
||||
btn.finger_id = -1;
|
||||
}
|
||||
if (camera_dragged_) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::CameraEndDrag();
|
||||
#endif
|
||||
}
|
||||
camera_finger_id_ = -1;
|
||||
camera_dragged_ = false;
|
||||
pinch_finger1_ = -1;
|
||||
pinch_finger2_ = -1;
|
||||
look_finger_id_ = -1;
|
||||
look_rotating_ = false;
|
||||
pinch_finger_id_ = -1;
|
||||
pinch_last_dist_ = 0.0f;
|
||||
}
|
||||
|
||||
static constexpr int kAutoHuntButton = 8;
|
||||
static bool is_quick_slot_button(int id) { return id >= 2 && id <= 7; }
|
||||
|
||||
// A circular arrow; green while the hunt runs.
|
||||
static void draw_auto_hunt_button(std::vector<UIRenderCommand>& commands, const ButtonDef& btn) {
|
||||
bool on = false;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
on = PythonBoot::AutoHuntIsEnabled();
|
||||
#endif
|
||||
const uint32_t disc = btn.pressed ? btn.color_pressed : (on ? 0xC040C040 : btn.color_idle);
|
||||
draw_filled_disc(commands, btn.x, btn.y, btn.radius, disc);
|
||||
const uint32_t col = on ? 0xFFB0FFB0 : 0xDDFFFFFF;
|
||||
const float r = btn.radius * 0.45f;
|
||||
const float kPi = 3.14159265f;
|
||||
const int segments = 12;
|
||||
const float a0 = -kPi * 0.35f, a1 = a0 + kPi * 1.6f;
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
const float t0 = a0 + (a1 - a0) * float(i) / float(segments);
|
||||
const float t1 = a0 + (a1 - a0) * float(i + 1) / float(segments);
|
||||
draw_line(commands, btn.x + std::cos(t0) * r, btn.y + std::sin(t0) * r,
|
||||
btn.x + std::cos(t1) * r, btn.y + std::sin(t1) * r, col);
|
||||
}
|
||||
// Arrowhead at the arc's end, pointing along the direction of travel.
|
||||
const float ex = btn.x + std::cos(a1) * r, ey = btn.y + std::sin(a1) * r;
|
||||
const float tx = -std::sin(a1), ty = std::cos(a1);
|
||||
const float nx = std::cos(a1), ny = std::sin(a1);
|
||||
const float h = r * 0.55f;
|
||||
draw_line(commands, ex, ey, ex - tx * h + nx * h * 0.6f, ey - ty * h + ny * h * 0.6f, col);
|
||||
draw_line(commands, ex, ey, ex - tx * h - nx * h * 0.6f, ey - ty * h - ny * h * 0.6f, col);
|
||||
if (on) {
|
||||
// A play dot in the middle while running.
|
||||
draw_rect_bar(commands, btn.x - 2.0f, btn.y - 2.0f, btn.x + 2.0f, btn.y + 2.0f, col);
|
||||
}
|
||||
}
|
||||
|
||||
void init_buttons() {
|
||||
buttons_.clear();
|
||||
// Lower-right combat controls. All menus and status UI remain the original 40250 UI.
|
||||
@@ -462,10 +661,11 @@ private:
|
||||
buttons_.push_back({5, 0x05, 0, 0, 26.0f, "S4", 0x80906030, 0xD0D08050});
|
||||
buttons_.push_back({6, 0x06, 0, 0, 26.0f, "S5", 0x80603090, 0xD08050D0});
|
||||
buttons_.push_back({7, 0x07, 0, 0, 26.0f, "S6", 0x80308090, 0xD050C0D0});
|
||||
buttons_.push_back({kAutoHuntButton, 0, 0, 0, 24.0f, "AUTO", 0x80707070, 0xD0FFFFFF});
|
||||
}
|
||||
|
||||
void layout_buttons() {
|
||||
const float W = float(screen_w_);
|
||||
const float W = float(screen_w_) - safe_inset_;
|
||||
const float H = float(screen_h_);
|
||||
for (auto& btn : buttons_) {
|
||||
switch (btn.id) {
|
||||
@@ -504,6 +704,11 @@ private:
|
||||
btn.y = H - 225.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case kAutoHuntButton:
|
||||
btn.x = W - 75.0f;
|
||||
btn.y = H - 295.0f;
|
||||
btn.radius = std::min(24.0f, H * 0.07f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -623,9 +828,26 @@ private:
|
||||
bool enabled_ = false;
|
||||
int screen_w_ = 1280;
|
||||
int screen_h_ = 720;
|
||||
float safe_inset_ = 0.0f;
|
||||
|
||||
bool controls_were_visible_ = false;
|
||||
int64_t ui_touch_finger_id_ = -1;
|
||||
enum class UiGesture { None, Pending, Dragging, LongPressed };
|
||||
UiGesture ui_gesture_ = UiGesture::None;
|
||||
float ui_start_x_ = 0.0f;
|
||||
float ui_start_y_ = 0.0f;
|
||||
double ui_down_time_ = 0.0;
|
||||
double last_tap_time_ = -1.0;
|
||||
float last_tap_x_ = 0.0f;
|
||||
float last_tap_y_ = 0.0f;
|
||||
bool last_tap_attached_ = false;
|
||||
void (*haptic_)() = nullptr;
|
||||
static constexpr float kUiDragSlop = 10.0f;
|
||||
static constexpr double kLongPressSec = 0.5;
|
||||
static constexpr double kDoubleTapSec = 0.35;
|
||||
static constexpr float kDoubleTapSlop = 24.0f;
|
||||
bool keyboard_requested_ = false;
|
||||
unsigned keyboard_serial_ = 0;
|
||||
|
||||
bool joystick_active_ = false;
|
||||
int64_t joystick_finger_id_ = -1;
|
||||
@@ -637,17 +859,34 @@ private:
|
||||
float joystick_knob_radius_ = 28.0f;
|
||||
float move_angle_ = 0.0f;
|
||||
|
||||
int64_t camera_finger_id_ = -1;
|
||||
float camera_start_x_ = 0.0f;
|
||||
float camera_start_y_ = 0.0f;
|
||||
float camera_last_x_ = 0.0f;
|
||||
float camera_last_y_ = 0.0f;
|
||||
double camera_down_time_ = 0.0;
|
||||
bool camera_dragged_ = false;
|
||||
// Zones are fractions of the screen; distances are logical UI pixels.
|
||||
static constexpr float kMoveZoneRight = 0.5f;
|
||||
static constexpr float kMoveZoneTop = 0.2f;
|
||||
static constexpr float kLookDeadZone = 8.0f;
|
||||
static constexpr float kLookYawDegPerPx = 0.25f;
|
||||
static constexpr float kLookPitchDegPerPx = 0.15f;
|
||||
static constexpr double kTapMaxSec = 0.3;
|
||||
|
||||
int64_t pinch_finger1_ = -1;
|
||||
int64_t pinch_finger2_ = -1;
|
||||
int64_t look_finger_id_ = -1;
|
||||
bool look_can_rotate_ = false;
|
||||
bool look_rotating_ = false;
|
||||
bool look_moved_ = false;
|
||||
float look_start_x_ = 0.0f;
|
||||
float look_start_y_ = 0.0f;
|
||||
float look_last_x_ = 0.0f;
|
||||
float look_last_y_ = 0.0f;
|
||||
double look_down_time_ = 0.0;
|
||||
|
||||
int64_t pinch_finger_id_ = -1;
|
||||
float pinch_x_ = 0.0f;
|
||||
float pinch_y_ = 0.0f;
|
||||
float pinch_last_dist_ = 0.0f;
|
||||
|
||||
int tap_stage_ = 0; // 1: press next frame, 2: release next frame
|
||||
float tap_x_ = 0.0f;
|
||||
float tap_y_ = 0.0f;
|
||||
|
||||
std::vector<ButtonDef> buttons_;
|
||||
double auto_hunt_down_time_ = 0.0;
|
||||
bool auto_hunt_long_pressed_ = false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user