mouse grab improvements

This commit is contained in:
Mikulas Florek 2023-10-21 18:49:55 +02:00
parent 48a4ada4f9
commit 74f50e8837
9 changed files with 102 additions and 71 deletions

View File

@ -25,6 +25,7 @@ fragment_shader [[
// https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8
// https://gist.github.com/bgolus/d49651f52b1dcf82f70421ba922ed064
// grid by Ben Golus
float PristineGrid(vec2 uv, vec2 lineWidth) {
vec4 uvDDXY = vec4(dFdx(uv), dFdy(uv));
vec2 uvDeriv = vec2(length(uvDDXY.xz), length(uvDDXY.yw));

View File

@ -685,7 +685,7 @@ struct StudioAppImpl final : StudioApp
const Array<Action*>& getActions() override { return m_actions; }
void guiBeginFrame() const
void guiBeginFrame()
{
PROFILE_FUNCTION();
@ -702,7 +702,7 @@ struct StudioAppImpl final : StudioApp
}
io.DeltaTime = m_engine->getLastTimeDelta();
if (!m_cursor_captured) {
if (!m_cursor_clipped) {
const os::Point cp = os::getMouseScreenPos();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
io.AddMousePosEvent((float)cp.x, (float)cp.y);
@ -715,7 +715,7 @@ struct StudioAppImpl final : StudioApp
const ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
ImGui::NewFrame();
if (!m_cursor_captured) {
if (!m_cursor_clipped) {
static ImGuiMouseCursor last_cursor = ImGuiMouseCursor_COUNT;
if (imgui_cursor != last_cursor) {
switch (imgui_cursor) {
@ -730,6 +730,8 @@ struct StudioAppImpl final : StudioApp
}
}
ImGui::PushFont(m_font);
if (os::getFocused() != m_main_window && m_cursor_clipped) unclipMouseCursor();
}
u32 getDockspaceID() const override {
@ -1111,7 +1113,17 @@ struct StudioAppImpl final : StudioApp
Gizmo::Config& getGizmoConfig() override { return m_gizmo_config; }
void setCursorCaptured(bool captured) override { m_cursor_captured = captured; }
void clipMouseCursor() override { m_cursor_clipped = true; }
void unclipMouseCursor() override {
os::clipCursor(os::INVALID_WINDOW, {});
m_cursor_clipped = false;
}
bool isMouseCursorClipped() const override {return m_cursor_clipped; }
void setMouseClipRect(os::WindowHandle win, const os::Rect &screen_rect) override {
if (!m_cursor_clipped) return;
os::clipCursor(win, screen_rect);
}
void addEntity() {
const EntityRef e = m_editor->addEntity();
@ -3459,7 +3471,7 @@ struct StudioAppImpl final : StudioApp
Gizmo::Config m_gizmo_config;
bool m_show_save_world_ui = false;
bool m_cursor_captured = false;
bool m_cursor_clipped = false;
bool m_confirm_exit = false;
bool m_confirm_load = false;
bool m_confirm_new = false;

View File

@ -25,8 +25,10 @@ struct Action;
struct ComponentUID;
namespace Gizmo { struct Config; }
namespace os {
using WindowHandle = void*;
enum class MouseButton;
struct Event;
struct Rect;
}
struct LUMIX_EDITOR_API StudioApp {
@ -126,11 +128,18 @@ struct LUMIX_EDITOR_API StudioApp {
virtual float getFOV() const = 0;
virtual void setFOV(float fov_radians) = 0;
virtual Gizmo::Config& getGizmoConfig() = 0;
virtual void setCursorCaptured(bool captured) = 0;
virtual void saveSettings() = 0;
virtual int getImGuiKey(int keycode) const = 0;
virtual u32 getDockspaceID() const = 0;
// clip mouse cursor = keep it in specified rectangle
// cursor is automatically unclipped when app is inactive
virtual void clipMouseCursor() = 0;
// some platforms can't clip to `screen_rect` so they ignore it and use just `win`'s client rectangle
virtual void setMouseClipRect(os::WindowHandle win, const os::Rect &screen_rect) = 0;
virtual void unclipMouseCursor() = 0;
virtual bool isMouseCursorClipped() const = 0;
virtual Span<const os::Event> getEvents() const = 0;
virtual ImFont* getDefaultFont() = 0;
virtual ImFont* getBoldFont() = 0;

View File

@ -1209,7 +1209,7 @@ bool makePath(const char* path) {
}
void grabMouse(WindowHandle window) {
void clipCursor(WindowHandle win, const Rect& rect) {
if (window == INVALID_WINDOW) {
XUngrabPointer(G.display, CurrentTime);
}

View File

@ -51,8 +51,8 @@ enum class MouseButton : i32 {
MAX = 16
};
struct Point { int x, y; };
struct Rect { int left, top, width, height; };
struct Point { i32 x, y; };
struct Rect { i32 left, top, width, height; };
using WindowHandle = void*;
constexpr WindowHandle INVALID_WINDOW = nullptr;
@ -203,7 +203,9 @@ LUMIX_ENGINE_API u64 getLastModified(StringView file);
LUMIX_ENGINE_API [[nodiscard]] bool makePath(const char* path);
LUMIX_ENGINE_API void setCursor(CursorType type);
LUMIX_ENGINE_API void grabMouse(WindowHandle win);
// clip mouse cursor to `rect`, on platforms, where this is not possible, clip to `win`
// pass INVALID_WINDOW to disable clipping
LUMIX_ENGINE_API void clipCursor(WindowHandle win, const Rect& rect);
LUMIX_ENGINE_API [[nodiscard]] bool getDropFile(const Event& event, int idx, Span<char> out);
LUMIX_ENGINE_API int getDropFileCount(const Event& event);

View File

@ -71,7 +71,6 @@ struct EventQueue {
};
static struct {
WindowHandle grabbed_window = INVALID_WINDOW;
EventQueue event_queue;
Point relative_mode_pos = {};
bool relative_mouse = false;
@ -549,17 +548,6 @@ Point toScreen(WindowHandle win, int x, int y)
return res;
}
void updateGrabbedMouse() {
if (G.grabbed_window == INVALID_WINDOW) {
DEBUG_CHECK(ClipCursor(NULL));
return;
}
RECT rect;
DEBUG_CHECK(GetWindowRect((HWND)G.grabbed_window, &rect));
DEBUG_CHECK(ClipCursor(&rect));
}
WindowHandle createWindow(const InitWindowArgs& args) {
PROFILE_FUNCTION();
WCharStr<MAX_PATH> cls_name("lunex_window");
@ -592,25 +580,20 @@ WindowHandle createWindow(const InitWindowArgs& args) {
e.win_move.x = (i16)LOWORD(lParam);
e.win_move.y = (i16)HIWORD(lParam);
G.event_queue.pushBack(e);
updateGrabbedMouse();
return 0;
case WM_SIZE:
e.type = Event::Type::WINDOW_SIZE;
e.win_size.w = LOWORD(lParam);
e.win_size.h = HIWORD(lParam);
G.event_queue.pushBack(e);
updateGrabbedMouse();
return 0;
case WM_CLOSE:
e.type = Event::Type::WINDOW_CLOSE;
G.event_queue.pushBack(e);
if (hWnd == G.grabbed_window) G.grabbed_window = INVALID_WINDOW;
updateGrabbedMouse();
return 0;
case WM_ACTIVATE:
if (wParam == WA_INACTIVE) {
showCursor(true);
grabMouse(INVALID_WINDOW);
G.key_states[(u32)os::Keycode::SHIFT] = false;
G.key_states[(u32)os::Keycode::CTRL] = false;
G.key_states[(u32)os::Keycode::ALT] = false;
@ -622,7 +605,6 @@ WindowHandle createWindow(const InitWindowArgs& args) {
e.type = Event::Type::FOCUS;
e.focus.gained = wParam != WA_INACTIVE;
G.event_queue.pushBack(e);
updateGrabbedMouse();
break;
case WM_NCPAINT:
case WM_NCACTIVATE:
@ -1361,9 +1343,16 @@ bool makePath(const char* path)
return error_code == ERROR_SUCCESS || error_code == ERROR_ALREADY_EXISTS;
}
void grabMouse(WindowHandle win) {
G.grabbed_window = win;
updateGrabbedMouse();
void clipCursor(WindowHandle win, const Rect& rect) {
if (win == INVALID_WINDOW) ClipCursor(NULL);
else {
RECT wrect;
wrect.left = rect.left;
wrect.top = rect.top;
wrect.bottom = rect.top + rect.height;
wrect.right = rect.left + rect.width;
ClipCursor(&wrect);
}
}

View File

@ -112,14 +112,13 @@ void GameView::enableIngameCursor(bool enable)
}
void GameView::captureMouse(bool capture)
{
void GameView::captureMouse(bool capture) {
if (m_is_mouse_captured == capture) return;
m_app.setCursorCaptured(capture);
m_is_mouse_captured = capture;
os::showCursor(!capture || m_is_ingame_cursor);
if (!capture) os::grabMouse(os::INVALID_WINDOW);
if (capture) m_app.clipMouseCursor();
else m_app.unclipMouseCursor();
}
void GameView::onSettingsLoaded() {
@ -245,7 +244,7 @@ void GameView::onGUI()
}
m_was_game_mode = is_game_mode;
if (m_is_mouse_captured && (ImGui::IsKeyDown(ImGuiKey_Escape) || !editor.isGameMode())) {
if (m_is_mouse_captured && (ImGui::IsKeyDown(ImGuiKey_Escape) || !editor.isGameMode() || !m_app.isMouseCursorClipped())) {
captureMouse(false);
}
@ -267,15 +266,13 @@ void GameView::onGUI()
if (!m_pipeline->isReady()) captureMouse(false);
ImVec2 view_pos;
ImVec2 view_size;
bool is_game_view_visible = false;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
if (ImGui::Begin(window_name, &m_is_open, ImGuiWindowFlags_NoNavInputs)) {
is_game_view_visible = true;
view_pos = ImGui::GetCursorScreenPos();
const ImVec2 content_min = view_pos;
const ImVec2 content_min = ImGui::GetCursorScreenPos();
view_size = ImGui::GetContentRegionAvail();
view_size.y -= ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 3;
ImVec2 content_max(content_min.x + view_size.x, content_min.y + view_size.y);
@ -306,6 +303,7 @@ void GameView::onGUI()
controlsGUI(editor);
const ImVec2 view_pos = ImGui::GetCursorScreenPos();
if (texture_handle) {
if (gpu::isOriginBottomLeft()) {
ImGui::Image(texture_handle, view_size, ImVec2(0, 1), ImVec2(1, 0));
@ -317,6 +315,15 @@ void GameView::onGUI()
else {
ImGuiEx::Rect(view_size.x, view_size.y, 0xffFF00FF);
}
if (m_is_mouse_captured) {
os::Rect rect;
rect.left = (i32)view_pos.x;
rect.top = (i32)view_pos.y;
rect.width = (i32)view_size.x;
rect.height = (i32)view_size.y;
m_app.setMouseClipRect(ImGui::GetWindowViewport()->PlatformHandle, rect);
}
const bool is_hovered = ImGui::IsItemHovered();
if (is_hovered && ImGui::IsMouseReleased(0) && editor.isGameMode()) captureMouse(true);
m_pos = ImGui::GetItemRectMin();
@ -327,10 +334,8 @@ void GameView::onGUI()
}
if (m_is_mouse_captured && os::getFocused() != ImGui::GetWindowViewport()->PlatformHandle) captureMouse(false);
if (m_is_mouse_captured && is_game_view_visible) {
os::grabMouse(ImGui::GetWindowViewport()->PlatformHandle);
}
if (m_is_mouse_captured && !is_game_view_visible) captureMouse(false);
ImGui::End();
ImGui::PopStyleVar();
}

View File

@ -1060,20 +1060,18 @@ void SceneView::renderGizmos()
}
void SceneView::captureMouse(bool capture)
{
if(m_is_mouse_captured == capture) return;
void SceneView::captureMouse(bool capture) {
if (m_is_mouse_captured == capture) return;
m_is_mouse_captured = capture;
m_app.setCursorCaptured(capture);
os::showCursor(!m_is_mouse_captured);
os::showCursor(!capture);
if (capture) {
os::grabMouse(ImGui::GetWindowViewport()->PlatformHandle);
m_app.clipMouseCursor();
const os::Point p = os::getMouseScreenPos();
m_captured_mouse_x = p.x;
m_captured_mouse_y = p.y;
}
else {
os::grabMouse(os::INVALID_WINDOW);
m_app.unclipMouseCursor();
os::setMouseScreenPos(m_captured_mouse_x, m_captured_mouse_y);
}
}
@ -1260,7 +1258,7 @@ void SceneView::onToolbar()
}
void SceneView::handleEvents() {
const bool handle_input = m_is_mouse_captured || (ImGui::IsItemHovered() && os::getFocused() == ImGui::GetWindowViewport()->PlatformHandle);
const bool handle_input = m_is_mouse_captured || ImGui::IsItemHovered();
for (const os::Event event : m_app.getEvents()) {
switch (event.type) {
case os::Event::Type::KEY: {
@ -1410,6 +1408,8 @@ void SceneView::insertModelUI() {
void SceneView::onGUI()
{
if (m_is_mouse_captured && !m_app.isMouseCursorClipped()) captureMouse(false);
m_has_focus = false;
PROFILE_FUNCTION();
m_pipeline->setWorld(m_editor.getWorld());
@ -1420,6 +1420,7 @@ void SceneView::onGUI()
if (m_log_ui.getUnreadErrorCount() > 0) title = ICON_FA_GLOBE "Scene View | " ICON_FA_EXCLAMATION_TRIANGLE " errors in log###Scene View";
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImVec2 view_size;
if (ImGui::Begin(title, nullptr, ImGuiWindowFlags_NoScrollWithMouse)) {
m_has_focus = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) || m_has_focus;
@ -1427,10 +1428,10 @@ void SceneView::onGUI()
is_open = true;
ImGui::Dummy(ImVec2(2, 2));
onToolbar();
const ImVec2 size = ImGui::GetContentRegionAvail();
view_size = ImGui::GetContentRegionAvail();
Viewport vp = m_view->getViewport();
vp.w = (int)size.x;
vp.h = (int)size.y;
vp.w = (int)view_size.x;
vp.h = (int)view_size.y;
m_view->setViewport(vp);
m_pipeline->setViewport(vp);
m_pipeline->render(false);
@ -1439,27 +1440,27 @@ void SceneView::onGUI()
m_view->inputFrame();
const gpu::TextureHandle texture_handle = m_pipeline->getOutput();
if (size.x > 0 && size.y > 0) {
if (view_size.x > 0 && view_size.y > 0) {
const ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
m_screen_x = int(cursor_pos.x);
m_screen_y = int(cursor_pos.y);
m_width = int(size.x);
m_height = int(size.y);
m_width = int(view_size.x);
m_height = int(view_size.y);
view_pos = ImGui::GetCursorScreenPos();
if (texture_handle) {
void* t = texture_handle;
if (gpu::isOriginBottomLeft()) {
ImGui::Image(t, size, ImVec2(0, 1), ImVec2(1, 0));
ImGui::Image(t, view_size, ImVec2(0, 1), ImVec2(1, 0));
}
else {
ImGui::Image(t, size);
ImGui::Image(t, view_size);
}
}
if (ImGui::BeginDragDropTarget()) {
if (auto* payload = ImGui::AcceptDragDropPayload("path")) {
const ImVec2 drop_pos = (ImGui::GetMousePos() - view_pos) / size;
const ImVec2 drop_pos = (ImGui::GetMousePos() - view_pos) / view_size;
handleDrop((const char*)payload->Data, drop_pos.x, drop_pos.y);
}
ImGui::EndDragDropTarget();
@ -1475,13 +1476,17 @@ void SceneView::onGUI()
m_view->m_draw_cmds.clear();
}
if (m_is_mouse_captured && os::getFocused() != ImGui::GetWindowViewport()->PlatformHandle) {
captureMouse(false);
}
ImGui::End();
if (m_is_mouse_captured && is_open) {
os::Rect rect;
rect.left = (i32)view_pos.x;
rect.top = (i32)view_pos.y;
rect.width = (i32)view_size.x;
rect.height = (i32)view_size.y;
m_app.setMouseClipRect(ImGui::GetWindowViewport()->PlatformHandle, rect);
}
insertModelUI();
if (is_open && m_is_measure_active) {

View File

@ -157,19 +157,28 @@ void WorldViewer::gui() {
m_pipeline->setViewport(vp);
m_pipeline->render(false);
gpu::TextureHandle preview = m_pipeline->getOutput();
const ImVec2 view_pos = ImGui::GetCursorScreenPos();
if (gpu::isOriginBottomLeft()) {
ImGui::Image(preview, image_size);
}
else {
ImGui::Image(preview, image_size, ImVec2(0, 1), ImVec2(1, 0));
}
if (m_is_mouse_captured) {
os::Rect rect;
rect.left = (i32)view_pos.x;
rect.top = (i32)view_pos.y;
rect.width = (i32)image_size.x;
rect.height = (i32)image_size.y;
m_app.setMouseClipRect(ImGui::GetWindowViewport()->PlatformHandle, rect);
}
const bool mouse_down = ImGui::IsMouseDown(ImGuiMouseButton_Right);
if (m_is_mouse_captured && !mouse_down) {
if (m_is_mouse_captured && (!mouse_down || !m_app.isMouseCursorClipped())) {
m_is_mouse_captured = false;
m_app.setCursorCaptured(false);
m_app.unclipMouseCursor();
os::showCursor(true);
os::grabMouse(os::INVALID_WINDOW);
os::setMouseScreenPos(m_captured_mouse_pos.x, m_captured_mouse_pos.y);
}
@ -183,9 +192,8 @@ void WorldViewer::gui() {
if (!m_is_mouse_captured) {
m_is_mouse_captured = true;
m_app.setCursorCaptured(true);
m_app.clipMouseCursor();
os::showCursor(false);
os::grabMouse(ImGui::GetWindowViewport()->PlatformHandle);
m_captured_mouse_pos = os::getMouseScreenPos();
}