diff --git a/src/DrawCamera.cpp b/src/DrawCamera.cpp index 756d7d5..a968816 100755 --- a/src/DrawCamera.cpp +++ b/src/DrawCamera.cpp @@ -1,9 +1,11 @@ #include "DrawCamera.hpp" #include "DrawingProgram/Tools/DrawingProgramToolBase.hpp" #include "Helpers/FixedPoint.hpp" +#include "Helpers/MathExtras.hpp" #include "MainProgram.hpp" #include "World.hpp" #include "InputManager.hpp" +#include DrawCamera::DrawCamera(): c({0, 0}, WorldScalar(1000000), 0.0) @@ -138,7 +140,7 @@ void DrawCamera::input_key_callback(const InputManager::KeyCallbackArgs& key) { } void DrawCamera::input_mouse_button_on_canvas_callback(World& w, const InputManager::MouseButtonCallbackArgs& button) { - if(!smoothMove.occurring) { + if(!smoothMove.occurring && !isTouchTransforming) { bool newIsAccurateZooming = (w.drawProg.controls.middleClickHeld && w.main.input.pen.isDown && w.main.toolbar.tabletOptions.zoomWhilePenDownAndButtonHeld) || // Hold middle click (pen button assigned to middle click) while pen is down (w.drawProg.controls.middleClickHeld && w.main.input.key(InputManager::KEY_GENERIC_LCTRL).held) || // Hold middle click/pen button while holding control (w.drawProg.controls.leftClickHeld && w.drawProg.drawTool->get_type() == DrawingProgramToolType::ZOOM); // Hold left click while on zoom tool @@ -154,7 +156,7 @@ void DrawCamera::input_mouse_button_on_canvas_callback(World& w, const InputMana } void DrawCamera::input_mouse_motion_callback(World& w, const InputManager::MouseMotionCallbackArgs& motion) { - if(!smoothMove.occurring) { + if(!smoothMove.occurring && !isTouchTransforming) { if(isAccurateZooming && startZoomVal != WorldScalar(0)) { WorldScalar zoomFactor(std::pow(1.0 + w.main.toolbar.dragZoomSpeed, w.main.toolbar.flipZoomToolDirection ? motion.move.y() : -motion.move.y())); if(zoomFactor < WorldScalar(0.000001)) @@ -175,7 +177,7 @@ void DrawCamera::input_mouse_motion_callback(World& w, const InputManager::Mouse } void DrawCamera::input_mouse_wheel_callback(World& w, const InputManager::MouseWheelCallbackArgs& wheel) { - if(!smoothMove.occurring && wheel.tickAmount.y() && w.main.toolbar.check_if_position_isnt_obstructed(wheel.mousePos)) { + if(!smoothMove.occurring && !isTouchTransforming && wheel.tickAmount.y() && w.main.toolbar.check_if_position_isnt_obstructed(wheel.mousePos)) { WorldVec mouseWorldPos = c.from_space(wheel.mousePos); WorldScalar zoomFactor(1.0 + w.main.toolbar.scrollZoomSpeed); @@ -199,6 +201,40 @@ void DrawCamera::input_mouse_wheel_callback(World& w, const InputManager::MouseW } } +void DrawCamera::input_multi_finger_touch_callback(World& w, const InputManager::MultiFingerTouchArgs& touch) { + if(!smoothMove.occurring && !isAccurateZooming && !isTouchTransforming && touch.down) { + touchInitialPositions = touch.pos; + touchInitialC = c; + isTouchTransforming = true; + } + else + isTouchTransforming = false; +} + +void DrawCamera::input_multi_finger_motion_callback(World& w, const InputManager::MultiFingerMotionArgs& motion) { + if(!smoothMove.occurring && !isAccurateZooming && isTouchTransforming) { + c = touchInitialC; + + Vector2f initialCenter = (touchInitialPositions[0] + touchInitialPositions[1]) * 0.5f; + Vector2f newCenter = (motion.pos[0] + motion.pos[1]) * 0.5f; + c.pos -= c.dir_from_space(newCenter - initialCenter); + WorldVec newCenterWorld = c.from_space(newCenter); + + float initialDistance = vec_distance(touchInitialPositions[0], touchInitialPositions[1]); + float newDistance = vec_distance(motion.pos[0], motion.pos[1]); + float scaleAmount = newDistance / initialDistance; + c.scale_about_double(newCenterWorld, scaleAmount); + + Vector2f initialDiff = touchInitialPositions[0] - initialCenter; + float initialAngle = std::atan2(initialDiff.y(), initialDiff.x()) + std::numbers::pi; + Vector2f newDiff = motion.pos[0] - newCenter; + float newAngle = std::atan2(newDiff.y(), newDiff.x()) + std::numbers::pi; + float rotateAngle = initialAngle - newAngle; + rotateAngle = std::fmod(rotateAngle + std::numbers::pi, std::numbers::pi * 2.0f) - std::numbers::pi; + c.rotate_about(newCenterWorld, rotateAngle); + } +} + void DrawCamera::save_file(cereal::PortableBinaryOutputArchive& a, const World& w) const { a(c, w.main.window.size.cast().eval()); } diff --git a/src/DrawCamera.hpp b/src/DrawCamera.hpp index 67bb354..ec86db7 100644 --- a/src/DrawCamera.hpp +++ b/src/DrawCamera.hpp @@ -34,6 +34,8 @@ class DrawCamera { void input_mouse_button_on_canvas_callback(World& w, const InputManager::MouseButtonCallbackArgs& button); void input_mouse_motion_callback(World& w, const InputManager::MouseMotionCallbackArgs& motion); void input_mouse_wheel_callback(World& w, const InputManager::MouseWheelCallbackArgs& wheel); + void input_multi_finger_touch_callback(World& w, const InputManager::MultiFingerTouchArgs& touch); + void input_multi_finger_motion_callback(World& w, const InputManager::MultiFingerMotionArgs& motion); private: struct SmoothMove { CoordSpaceHelper start; @@ -49,6 +51,9 @@ class DrawCamera { WorldScalar startZoomVal; WorldVec startZoomMousePos; WorldVec startZoomCameraPos; - bool isAccurateZooming = false; + + CoordSpaceHelper touchInitialC; + std::vector touchInitialPositions; + bool isTouchTransforming = false; }; diff --git a/src/InputManager.cpp b/src/InputManager.cpp index f2c844e..6bf3825 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -607,6 +608,178 @@ void InputManager::backend_pen_axis_update(const SDL_PenAxisEvent& e) { } } +std::vector InputManager::get_multiple_finger_positions() { + std::vector pos; + for(const SDL_TouchFingerEvent& fingerEvent : touch.fingers) + pos.emplace_back(fingerEvent.x * main.window.size.x() * main.window.density, fingerEvent.y * main.window.size.y() * main.window.density); + return pos; +} + +std::vector InputManager::get_multiple_finger_motions() { + std::vector pos; + for(const SDL_TouchFingerEvent& fingerEvent : touch.fingers) + pos.emplace_back(fingerEvent.dx * main.window.size.x() * main.window.density, fingerEvent.dy * main.window.size.y() * main.window.density); + return pos; +} + +void InputManager::backend_touch_finger_down_update(const SDL_TouchFingerEvent& e) { + touch.fingers.emplace_back(e); + switch(touch.touchEventType) { + case Touch::NO_TOUCH_EVENT: { + if(touch.fingers.size() == 2) { + touch.isDown = true; + touch.touchEventType = Touch::TWO_FINGER_EVENT; + main.input_multi_finger_touch_callback({ + .down = true, + .pos = get_multiple_finger_positions() + }); + } + break; + } + case Touch::ONE_FINGER_EVENT: { + break; + } + case Touch::TWO_FINGER_EVENT: { + break; + } + case Touch::EVENT_DONE: { + if(touch.fingers.size() == 1) + touch.touchEventType = Touch::NO_TOUCH_EVENT; + break; + } + } +} + +void InputManager::backend_touch_finger_up_update(const SDL_TouchFingerEvent& e) { + switch(touch.touchEventType) { + case Touch::NO_TOUCH_EVENT: { + Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density}; + mouse.set_pos(mouseNewPos); + touch.isDown = false; + mouse.leftDown = false; + + if((std::chrono::steady_clock::now() - touch.lastLeftClickTime) > std::chrono::milliseconds(300)) + touch.leftClicksSaved = 0; + touch.leftClicksSaved++; + mouse.leftClicks = touch.leftClicksSaved; + touch.lastLeftClickTime = std::chrono::steady_clock::now(); + + main.input_mouse_button_callback({ + .button = MouseButton::LEFT, + .down = true, + .clicks = mouse.leftClicks, + .pos = mouseNewPos + }); + main.input_mouse_button_callback({ + .button = MouseButton::LEFT, + .down = false, + .clicks = 0, + .pos = mouseNewPos + }); + + touch.touchEventType = Touch::EVENT_DONE; + break; + } + case Touch::ONE_FINGER_EVENT: { + Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density}; + mouse.set_pos(mouseNewPos); + touch.isDown = false; + mouse.leftDown = false; + + main.input_mouse_button_callback({ + .button = MouseButton::LEFT, + .down = false, + .clicks = 0, + .pos = mouseNewPos + }); + + touch.touchEventType = Touch::EVENT_DONE; + break; + } + case Touch::TWO_FINGER_EVENT: { + touch.isDown = false; + + main.input_multi_finger_touch_callback({ + .down = false, + .pos = get_multiple_finger_positions() + }); + + touch.touchEventType = Touch::EVENT_DONE; + break; + } + case Touch::EVENT_DONE: { + break; + } + } + std::erase_if(touch.fingers, [&e](auto& tE) { + return e.fingerID == tE.fingerID; + }); +} + +void InputManager::backend_touch_finger_motion_update(const SDL_TouchFingerEvent& e) { + switch(touch.touchEventType) { + case Touch::NO_TOUCH_EVENT: { + { + auto& prevTouch = touch.fingers[0]; + Vector2f mouseNewPos = {prevTouch.x * main.window.size.x() * main.window.density, prevTouch.y * main.window.size.y() * main.window.density}; + mouse.set_pos(mouseNewPos); + mouse.leftDown = true; + + if((std::chrono::steady_clock::now() - touch.lastLeftClickTime) > std::chrono::milliseconds(300)) + touch.leftClicksSaved = 0; + touch.leftClicksSaved++; + mouse.leftClicks = touch.leftClicksSaved; + touch.lastLeftClickTime = std::chrono::steady_clock::now(); + + main.input_mouse_button_callback({ + .button = MouseButton::LEFT, + .down = true, + .clicks = mouse.leftClicks, + .pos = mouseNewPos + }); + } + { + Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density}; + mouse.set_pos(mouseNewPos); + Vector2f mouseRel = {e.dx * main.window.size.x() * main.window.density, e.dy * main.window.size.y() * main.window.density}; + main.input_mouse_motion_callback({ + .pos = mouseNewPos, + .move = mouseRel + }); + } + + touch.touchEventType = Touch::ONE_FINGER_EVENT; + break; + } + case Touch::ONE_FINGER_EVENT: { + Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density}; + mouse.set_pos(mouseNewPos); + Vector2f mouseRel = {e.dx * main.window.size.x() * main.window.density, e.dy * main.window.size.y() * main.window.density}; + main.input_mouse_motion_callback({ + .pos = mouseNewPos, + .move = mouseRel + }); + break; + } + case Touch::TWO_FINGER_EVENT: { + main.input_multi_finger_motion_callback({ + .pos = get_multiple_finger_positions(), + .move = get_multiple_finger_motions() + }); + break; + } + case Touch::EVENT_DONE: { + break; + } + } + for(auto& tE : touch.fingers) { + if(tE.fingerID == e.fingerID) { + tE = e; + break; + } + } +} + void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { auto kPress = e.key; auto kMod = e.mod; @@ -936,6 +1109,30 @@ void InputManager::Mouse::set_pos(const Vector2f& newPos) { move = newPos - lastPos; } +void InputManager::update() { + + if(touch.touchEventType == Touch::NO_TOUCH_EVENT && (SDL_GetTicksNS() - touch.fingers[0].timestamp) > (100 * 1000000)) { // 100ms to determine whether touch is with a single finger + auto& prevTouch = touch.fingers[0]; + Vector2f mouseNewPos = {prevTouch.x * main.window.size.x() * main.window.density, prevTouch.y * main.window.size.y() * main.window.density}; + mouse.set_pos(mouseNewPos); + mouse.leftDown = true; + + if((std::chrono::steady_clock::now() - touch.lastLeftClickTime) > std::chrono::milliseconds(300)) + touch.leftClicksSaved = 0; + touch.leftClicksSaved++; + mouse.leftClicks = touch.leftClicksSaved; + touch.lastLeftClickTime = std::chrono::steady_clock::now(); + + main.input_mouse_button_callback({ + .button = MouseButton::LEFT, + .down = true, + .clicks = mouse.leftClicks, + .pos = mouseNewPos + }); + touch.touchEventType = Touch::ONE_FINGER_EVENT; + } +} + void InputManager::frame_reset(const Vector2i& windowSize) { mouse.lastPos = mouse.pos; mouse.move = Vector2f({0.0f, 0.0f}); diff --git a/src/InputManager.hpp b/src/InputManager.hpp index 9b47c1e..009fa00 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -21,6 +21,7 @@ class MainProgram; struct InputManager { InputManager(MainProgram& initMain); + void update(); void frame_reset(const Vector2i& windowSize); // Just renamed SDL3 cursor types, but if we need to change to a different backend we can @@ -77,6 +78,13 @@ struct InputManager { bool isDown = false; int leftClicksSaved = 0; std::chrono::steady_clock::time_point lastLeftClickTime; + std::vector fingers; + enum TypeOfTouchEvent { + NO_TOUCH_EVENT, + ONE_FINGER_EVENT, + TWO_FINGER_EVENT, + EVENT_DONE + } touchEventType = EVENT_DONE; } touch; struct Pen { @@ -208,6 +216,8 @@ struct InputManager { uint32_t make_generic_key_mod(SDL_Keymod m); void set_key_up(const SDL_KeyboardEvent& e, KeyCode kCode); void set_key_down(const SDL_KeyboardEvent& e, KeyCode kCode); + std::vector get_multiple_finger_positions(); + std::vector get_multiple_finger_motions(); void backend_drop_file_event(const SDL_DropEvent& e); void backend_drop_text_event(const SDL_DropEvent& e); @@ -223,6 +233,9 @@ struct InputManager { void backend_pen_touch_up_update(const SDL_PenTouchEvent& e); void backend_pen_motion_update(const SDL_PenMotionEvent& e); void backend_pen_axis_update(const SDL_PenAxisEvent& e); + void backend_touch_finger_down_update(const SDL_TouchFingerEvent& e); + void backend_touch_finger_up_update(const SDL_TouchFingerEvent& e); + void backend_touch_finger_motion_update(const SDL_TouchFingerEvent& e); void set_clipboard_str(std::string_view s); void set_clipboard_plain_and_richtext_pair(const std::pair& plainAndRichtextPair); @@ -301,6 +314,16 @@ struct InputManager { const char* data; }; + struct MultiFingerTouchArgs { + bool down; + std::vector pos; + }; + + struct MultiFingerMotionArgs { + std::vector pos; + std::vector move; + }; + MainProgram& main; }; diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index daaf406..166ceec 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -68,6 +68,8 @@ void MainProgram::update() { while(std::chrono::duration_cast>(std::chrono::steady_clock::now() - lastFrameTime).count() < 1.0 / fpsLimit); lastFrameTime = std::chrono::steady_clock::now(); + input.update(); + for(auto& s : setTabsToClose) std::erase(worlds, s.lock()); @@ -378,6 +380,16 @@ void MainProgram::input_pen_axis_callback(const InputManager::PenAxisCallbackArg world->input_pen_axis_callback(axis); } +void MainProgram::input_multi_finger_touch_callback(const InputManager::MultiFingerTouchArgs& touch) { + if(world) + world->input_multi_finger_touch_callback(touch); +} + +void MainProgram::input_multi_finger_motion_callback(const InputManager::MultiFingerMotionArgs& motion) { + if(world) + world->input_multi_finger_motion_callback(motion); +} + bool MainProgram::network_being_used() { for(auto& w : worlds) { if(w->netObjMan.is_connected()) diff --git a/src/MainProgram.hpp b/src/MainProgram.hpp index 3a2bf02..f3edccc 100644 --- a/src/MainProgram.hpp +++ b/src/MainProgram.hpp @@ -132,6 +132,8 @@ class MainProgram { void input_pen_touch_callback(const InputManager::PenTouchCallbackArgs& touch); void input_pen_motion_callback(const InputManager::PenMotionCallbackArgs& motion); void input_pen_axis_callback(const InputManager::PenAxisCallbackArgs& axis); + void input_multi_finger_touch_callback(const InputManager::MultiFingerTouchArgs& touch); + void input_multi_finger_motion_callback(const InputManager::MultiFingerMotionArgs& motion); ~MainProgram(); private: diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index e00974f..0ff3f43 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -2347,6 +2347,7 @@ void Toolbar::input_key_callback(const InputManager::KeyCallbackArgs& key) { } void Toolbar::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { + io->mouse.globalPos = button.pos / final_gui_scale(); if(button.down) { switch(button.button) { case InputManager::MouseButton::LEFT: { diff --git a/src/World.cpp b/src/World.cpp index a7f2d41..c79bfef 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -325,6 +325,16 @@ void World::input_pen_axis_callback(const InputManager::PenAxisCallbackArgs& axi drawProg.input_pen_axis_callback(axis); } +void World::input_multi_finger_touch_callback(const InputManager::MultiFingerTouchArgs& touch) { + if(!clientStillConnecting) + drawData.cam.input_multi_finger_touch_callback(*this, touch); +} + +void World::input_multi_finger_motion_callback(const InputManager::MultiFingerMotionArgs& motion) { + if(!clientStillConnecting) + drawData.cam.input_multi_finger_motion_callback(*this, motion); +} + void World::send_chat_message(const std::string& message) { if(!clientStillConnecting) ownClientData->send_chat_message(ownClientData, *this, message); diff --git a/src/World.hpp b/src/World.hpp index 22b3dfd..27d371a 100644 --- a/src/World.hpp +++ b/src/World.hpp @@ -113,6 +113,8 @@ class World { void input_pen_touch_callback(const InputManager::PenTouchCallbackArgs& touch); void input_pen_motion_callback(const InputManager::PenMotionCallbackArgs& motion); void input_pen_axis_callback(const InputManager::PenAxisCallbackArgs& axis); + void input_multi_finger_touch_callback(const InputManager::MultiFingerTouchArgs& touch); + void input_multi_finger_motion_callback(const InputManager::MultiFingerMotionArgs& motion); private: void load_empty_canvas(); diff --git a/src/main.cpp b/src/main.cpp index 9c0294a..db41f33 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -810,28 +810,17 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { } case SDL_EVENT_FINGER_DOWN: { mS.m->update_scale_and_density(); - mS.m->input.touch.isDown = true; - mS.m->input.mouse.leftDown = true; - if((std::chrono::steady_clock::now() - mS.m->input.touch.lastLeftClickTime) > std::chrono::milliseconds(300)) - mS.m->input.touch.leftClicksSaved = 0; - mS.m->input.touch.leftClicksSaved++; - mS.m->input.mouse.leftClicks = mS.m->input.touch.leftClicksSaved; - mS.m->input.touch.lastLeftClickTime = std::chrono::steady_clock::now(); - mS.m->input.mouse.set_pos({event->tfinger.x * mS.m->window.size.x() * mS.m->window.density, event->tfinger.y * mS.m->window.size.y() * mS.m->window.density}); - mS.m->input.mouse.move = Vector2f{0.0f, 0.0f}; + mS.m->input.backend_touch_finger_down_update(event->tfinger); break; } case SDL_EVENT_FINGER_UP: { mS.m->update_scale_and_density(); - mS.m->input.touch.isDown = false; - mS.m->input.mouse.leftDown = false; - mS.m->input.mouse.set_pos({-1.0f, -1.0f}); - mS.m->input.mouse.move = Vector2f{0.0f, 0.0f}; + mS.m->input.backend_touch_finger_up_update(event->tfinger); break; } case SDL_EVENT_FINGER_MOTION: { mS.m->update_scale_and_density(); - mS.m->input.mouse.set_pos({event->tfinger.x * mS.m->window.size.x() * mS.m->window.density, event->tfinger.y * mS.m->window.size.y() * mS.m->window.density}); + mS.m->input.backend_touch_finger_motion_update(event->tfinger); break; } case SDL_EVENT_DISPLAY_ORIENTATION: