| 1152 | } |
| 1153 | |
| 1154 | void InputController::handleMouseMove(double x, double y) { |
| 1155 | LOG_PERF("InputController::handleMouseMove pos=({},{}) drag_mode={}", |
| 1156 | x, y, static_cast<int>(drag_mode_)); |
| 1157 | auto* gui = services().guiOrNull(); |
| 1158 | |
| 1159 | if (gui && gui->isCapturingInput()) { |
| 1160 | gui->captureMouseMove(x, y); |
| 1161 | last_mouse_pos_ = {x, y}; |
| 1162 | return; |
| 1163 | } |
| 1164 | |
| 1165 | // Forward to pie menu if open — consume event to prevent viewport interaction |
| 1166 | if (gui && gui->gizmo().isPieMenuOpen()) { |
| 1167 | gui->gizmo().onPieMenuMouseMove({static_cast<float>(x), static_cast<float>(y)}); |
| 1168 | last_mouse_pos_ = {x, y}; |
| 1169 | return; |
| 1170 | } |
| 1171 | |
| 1172 | // Track if we moved significantly |
| 1173 | glm::dvec2 current_pos{x, y}; |
| 1174 | const double delta_x = x - last_mouse_pos_.x; |
| 1175 | const double delta_y = y - last_mouse_pos_.y; |
| 1176 | |
| 1177 | // Dispatch to modal operators first - if consumed, don't continue |
| 1178 | bool over_gui = false; |
| 1179 | bool over_gui_hover = false; |
| 1180 | if (input_router_) { |
| 1181 | const auto targets = input_router_->pointerTargets(x, y); |
| 1182 | over_gui = targets.pointer_target == input::InputTarget::Gui; |
| 1183 | over_gui_hover = targets.hover_target == input::InputTarget::Gui; |
| 1184 | } else { |
| 1185 | over_gui = isPointerOverBlockingUi(x, y); |
| 1186 | over_gui_hover = isPointerOverUiHover(x, y); |
| 1187 | } |
| 1188 | if (dispatchMouseMoveToModals(x, y, delta_x, delta_y, getModifierKeys(), over_gui_hover)) { |
| 1189 | last_mouse_pos_ = current_pos; |
| 1190 | return; |
| 1191 | } |
| 1192 | |
| 1193 | const bool over_viewport_gizmo = gui && gui->gizmo().isPositionInViewportGizmo(x, y); |
| 1194 | const bool over_transform_gizmo = isTransformGizmoOverOrUsing(); |
| 1195 | const bool over_splitter = |
| 1196 | drag_mode_ == DragMode::None && |
| 1197 | isInViewport(x, y) && |
| 1198 | isNearSplitter(x, y); |
| 1199 | |
| 1200 | if (pending_click_drag_.active) { |
| 1201 | if (!isMouseButtonPressed(pending_click_drag_.button)) { |
| 1202 | pending_click_drag_ = {}; |
| 1203 | } else if (glm::length(current_pos - pending_click_drag_.press_pos) >= |
| 1204 | DOUBLE_CLICK_DISTANCE) { |
| 1205 | const auto pending = pending_click_drag_; |
| 1206 | pending_click_drag_ = {}; |
| 1207 | forced_mouse_press_action_ = pending.drag_action; |
| 1208 | handleMouseButton(pending.button, input::ACTION_PRESS, |
| 1209 | pending.press_pos.x, pending.press_pos.y); |
| 1210 | forced_mouse_press_action_ = input::Action::NONE; |
| 1211 | } |