diff --git a/python/taichi/ui/constants.py b/python/taichi/ui/constants.py index f610e23e5ecd2..30069162db20b 100644 --- a/python/taichi/ui/constants.py +++ b/python/taichi/ui/constants.py @@ -17,6 +17,46 @@ MMB = "MMB" RMB = "RMB" +# Function keys +F1 = "F1" +F2 = "F2" +F3 = "F3" +F4 = "F4" +F5 = "F5" +F6 = "F6" +F7 = "F7" +F8 = "F8" +F9 = "F9" +F10 = "F10" +F11 = "F11" +F12 = "F12" + +# Navigation keys +INSERT = "Insert" +DELETE = "Delete" +HOME = "Home" +END = "End" +PAGEUP = "PageUp" +PAGEDOWN = "PageDown" + +# Numpad keys +NUMPAD0 = "Numpad0" +NUMPAD1 = "Numpad1" +NUMPAD2 = "Numpad2" +NUMPAD3 = "Numpad3" +NUMPAD4 = "Numpad4" +NUMPAD5 = "Numpad5" +NUMPAD6 = "Numpad6" +NUMPAD7 = "Numpad7" +NUMPAD8 = "Numpad8" +NUMPAD9 = "Numpad9" +NUMPAD_DECIMAL = "NumpadDecimal" +NUMPAD_DIVIDE = "NumpadDivide" +NUMPAD_MULTIPLY = "NumpadMultiply" +NUMPAD_SUBTRACT = "NumpadSubtract" +NUMPAD_ADD = "NumpadAdd" +NUMPAD_ENTER = "NumpadEnter" + # Event types PRESS = "Press" RELEASE = "Release" diff --git a/python/taichi/ui/window.py b/python/taichi/ui/window.py index e549cdfbd46d7..97a70a68869c7 100644 --- a/python/taichi/ui/window.py +++ b/python/taichi/ui/window.py @@ -138,6 +138,22 @@ def get_cursor_pos(self): """Get current cursor position, in the range `[0, 1] x [0, 1]`.""" return self.window.get_cursor_pos() + def get_scroll_delta(self): + """Get the accumulated scroll wheel delta since last call. + + Returns: + tuple: (dx, dy) scroll delta values. Resets to (0, 0) after being read. + + Example:: + + >>> while window.running: + ... dx, dy = window.get_scroll_delta() + ... if dy != 0: + ... zoom += dy * 0.1 + ... window.show() + """ + return self.window.get_scroll_delta() + def show(self): """Display this window.""" return self.window.show() diff --git a/taichi/python/export_ggui.cpp b/taichi/python/export_ggui.cpp index 965c54de70b01..fe1887c035077 100644 --- a/taichi/python/export_ggui.cpp +++ b/taichi/python/export_ggui.cpp @@ -648,6 +648,13 @@ struct PyWindow { return py::make_tuple(x, y); } + py::tuple py_get_scroll_delta() { + auto delta = window->get_scroll_delta(); + double dx = std::get<0>(delta); + double dy = std::get<1>(delta); + return py::make_tuple(dx, dy); + } + void destroy() { if (window) { window.reset(); @@ -671,6 +678,7 @@ void export_ggui(py::module &m) { .def("get_image_buffer_as_numpy", &PyWindow::get_image_buffer) .def("is_pressed", &PyWindow::is_pressed) .def("get_cursor_pos", &PyWindow::py_get_cursor_pos) + .def("get_scroll_delta", &PyWindow::py_get_scroll_delta) .def("is_running", &PyWindow::is_running) .def("set_is_running", &PyWindow::set_is_running) .def("get_event", &PyWindow::get_event) diff --git a/taichi/ui/common/input_handler.h b/taichi/ui/common/input_handler.h index e6987c2171ed7..0376cac3ddecf 100644 --- a/taichi/ui/common/input_handler.h +++ b/taichi/ui/common/input_handler.h @@ -87,6 +87,30 @@ class InputHandler { user_mouse_button_callbacks_.push_back(f); } + void scroll_callback(GLFWwindow *window, double xoffset, double yoffset) { + scroll_dx_ += xoffset; + scroll_dy_ += yoffset; + for (auto f : user_scroll_callbacks_) { + f(xoffset, yoffset); + } + } + + void add_scroll_callback(std::function f) { + user_scroll_callbacks_.push_back(f); + } + + double scroll_dx() const { + return scroll_dx_; + } + double scroll_dy() const { + return scroll_dy_; + } + + void reset_scroll() { + scroll_dx_ = 0; + scroll_dy_ = 0; + } + InputHandler() : keys_(1024, false) { } @@ -102,6 +126,10 @@ class InputHandler { std::vector> user_key_callbacks_; std::vector> user_mouse_pos_callbacks_; std::vector> user_mouse_button_callbacks_; + std::vector> user_scroll_callbacks_; + + double scroll_dx_ = 0; + double scroll_dy_ = 0; }; } // namespace taichi::ui diff --git a/taichi/ui/common/window_base.cpp b/taichi/ui/common/window_base.cpp index 10af909476ee6..658d6da99e8d9 100644 --- a/taichi/ui/common/window_base.cpp +++ b/taichi/ui/common/window_base.cpp @@ -22,6 +22,7 @@ void WindowBase::set_callbacks() { glfwSetKeyCallback(glfw_window_, key_callback); glfwSetCursorPosCallback(glfw_window_, mouse_pos_callback); glfwSetMouseButtonCallback(glfw_window_, mouse_button_callback); + glfwSetScrollCallback(glfw_window_, scroll_callback); input_handler_.add_key_callback([&](int key, int action) { // Catch exception from button_id_to_name(). @@ -110,6 +111,14 @@ std::pair WindowBase::get_cursor_pos() { return std::make_pair(x, y); } +std::pair WindowBase::get_scroll_delta() { + CHECK_WINDOW_SHOWING; + double dx = input_handler_.scroll_dx(); + double dy = input_handler_.scroll_dy(); + input_handler_.reset_scroll(); + return std::make_pair(dx, dy); +} + std::vector WindowBase::get_events(EventType tag) { CHECK_WINDOW_SHOWING; glfwPollEvents(); @@ -198,4 +207,12 @@ void WindowBase::mouse_button_callback(GLFWwindow *glfw_window, modifier); } +void WindowBase::scroll_callback(GLFWwindow *glfw_window, + double xoffset, + double yoffset) { + auto window = + reinterpret_cast(glfwGetWindowUserPointer(glfw_window)); + window->input_handler_.scroll_callback(glfw_window, xoffset, yoffset); +} + } // namespace taichi::ui diff --git a/taichi/ui/common/window_base.h b/taichi/ui/common/window_base.h index aea08bca5524f..42d1d9b01b4ec 100644 --- a/taichi/ui/common/window_base.h +++ b/taichi/ui/common/window_base.h @@ -30,6 +30,8 @@ class WindowBase { std::pair get_cursor_pos(); + std::pair get_scroll_delta(); + std::vector get_events(EventType tag); bool get_event(EventType tag); @@ -87,6 +89,10 @@ class WindowBase { int button, int action, int modifier); + + static void scroll_callback(GLFWwindow *glfw_window, + double xoffset, + double yoffset); }; } // namespace taichi::ui diff --git a/taichi/ui/utils/utils.h b/taichi/ui/utils/utils.h index d71020f84ebbb..d2fbd77ee9fc7 100644 --- a/taichi/ui/utils/utils.h +++ b/taichi/ui/utils/utils.h @@ -112,6 +112,46 @@ struct Keys { DEFINE_KEY(LMB); DEFINE_KEY(MMB); DEFINE_KEY(RMB); + + // Function keys + DEFINE_KEY(F1); + DEFINE_KEY(F2); + DEFINE_KEY(F3); + DEFINE_KEY(F4); + DEFINE_KEY(F5); + DEFINE_KEY(F6); + DEFINE_KEY(F7); + DEFINE_KEY(F8); + DEFINE_KEY(F9); + DEFINE_KEY(F10); + DEFINE_KEY(F11); + DEFINE_KEY(F12); + + // Navigation + DEFINE_KEY(Insert); + DEFINE_KEY(Delete); + DEFINE_KEY(Home); + DEFINE_KEY(End); + DEFINE_KEY(PageUp); + DEFINE_KEY(PageDown); + + // Numpad + DEFINE_KEY(Numpad0); + DEFINE_KEY(Numpad1); + DEFINE_KEY(Numpad2); + DEFINE_KEY(Numpad3); + DEFINE_KEY(Numpad4); + DEFINE_KEY(Numpad5); + DEFINE_KEY(Numpad6); + DEFINE_KEY(Numpad7); + DEFINE_KEY(Numpad8); + DEFINE_KEY(Numpad9); + DEFINE_KEY(NumpadDecimal); + DEFINE_KEY(NumpadDivide); + DEFINE_KEY(NumpadMultiply); + DEFINE_KEY(NumpadSubtract); + DEFINE_KEY(NumpadAdd); + DEFINE_KEY(NumpadEnter); #undef DEFINE_KEY }; @@ -132,7 +172,45 @@ inline std::unordered_map get_keys_map() { {Keys::CapsLock, GLFW_KEY_CAPS_LOCK}, {Keys::LMB, GLFW_MOUSE_BUTTON_LEFT}, {Keys::MMB, GLFW_MOUSE_BUTTON_MIDDLE}, - {Keys::RMB, GLFW_MOUSE_BUTTON_RIGHT}}; + {Keys::RMB, GLFW_MOUSE_BUTTON_RIGHT}, + // Function keys + {Keys::F1, GLFW_KEY_F1}, + {Keys::F2, GLFW_KEY_F2}, + {Keys::F3, GLFW_KEY_F3}, + {Keys::F4, GLFW_KEY_F4}, + {Keys::F5, GLFW_KEY_F5}, + {Keys::F6, GLFW_KEY_F6}, + {Keys::F7, GLFW_KEY_F7}, + {Keys::F8, GLFW_KEY_F8}, + {Keys::F9, GLFW_KEY_F9}, + {Keys::F10, GLFW_KEY_F10}, + {Keys::F11, GLFW_KEY_F11}, + {Keys::F12, GLFW_KEY_F12}, + // Navigation + {Keys::Insert, GLFW_KEY_INSERT}, + {Keys::Delete, GLFW_KEY_DELETE}, + {Keys::Home, GLFW_KEY_HOME}, + {Keys::End, GLFW_KEY_END}, + {Keys::PageUp, GLFW_KEY_PAGE_UP}, + {Keys::PageDown, GLFW_KEY_PAGE_DOWN}, + // Numpad + {Keys::Numpad0, GLFW_KEY_KP_0}, + {Keys::Numpad1, GLFW_KEY_KP_1}, + {Keys::Numpad2, GLFW_KEY_KP_2}, + {Keys::Numpad3, GLFW_KEY_KP_3}, + {Keys::Numpad4, GLFW_KEY_KP_4}, + {Keys::Numpad5, GLFW_KEY_KP_5}, + {Keys::Numpad6, GLFW_KEY_KP_6}, + {Keys::Numpad7, GLFW_KEY_KP_7}, + {Keys::Numpad8, GLFW_KEY_KP_8}, + {Keys::Numpad9, GLFW_KEY_KP_9}, + {Keys::NumpadDecimal, GLFW_KEY_KP_DECIMAL}, + {Keys::NumpadDivide, GLFW_KEY_KP_DIVIDE}, + {Keys::NumpadMultiply, GLFW_KEY_KP_MULTIPLY}, + {Keys::NumpadSubtract, GLFW_KEY_KP_SUBTRACT}, + {Keys::NumpadAdd, GLFW_KEY_KP_ADD}, + {Keys::NumpadEnter, GLFW_KEY_KP_ENTER}, + }; return keys; } @@ -155,6 +233,9 @@ inline int buttom_name_to_id(const std::string &name) { c = c - ('a' - 'A'); return (int)c; } + if (c >= '0' && c <= '9') { + return (int)c; // GLFW_KEY_0-9 are ASCII codes 48-57 + } } auto keys = get_keys_map(); @@ -173,13 +254,19 @@ inline std::string button_id_to_name(int id) { name += c; return name; } + if (id >= '0' && id <= '9') { + std::string name; + name += (char)id; + return name; + } auto keys = get_inv_keys_map(); if (keys.find(id) != keys.end()) { return keys.at(id); } else { - throw std::runtime_error(std::string("unrecognized id: ") + - std::to_string(id)); + // Fallback: return "Key_" instead of throwing for unknown keys. + // This ensures unmapped keys still generate events that users can match on. + return std::string("Key_") + std::to_string(id); } } #endif