diff --git a/data/cursors/cursors.otml b/data/cursors/cursors.otml new file mode 100644 index 00000000..36c1f391 --- /dev/null +++ b/data/cursors/cursors.otml @@ -0,0 +1,13 @@ +Cursors + target: + image: targetcursor + hot-spot: 9 9 + horizontal: + image: horizontalcursor + hot-spot: 9 4 + vertical: + image: verticalcursor + hot-spot: 4 9 + text: + image: textcursor + hot-spot: 4 9 diff --git a/data/images/cursors/horizontal.png b/data/cursors/horizontalcursor.png similarity index 100% rename from data/images/cursors/horizontal.png rename to data/cursors/horizontalcursor.png diff --git a/data/images/cursors/targetcursor.png b/data/cursors/targetcursor.png similarity index 100% rename from data/images/cursors/targetcursor.png rename to data/cursors/targetcursor.png diff --git a/data/images/cursors/text.png b/data/cursors/textcursor.png similarity index 100% rename from data/images/cursors/text.png rename to data/cursors/textcursor.png diff --git a/data/images/cursors/vertical.png b/data/cursors/verticalcursor.png similarity index 100% rename from data/images/cursors/vertical.png rename to data/cursors/verticalcursor.png diff --git a/modules/client_styles/styles.lua b/modules/client_styles/styles.lua index bb7b20eb..bd123597 100644 --- a/modules/client_styles/styles.lua +++ b/modules/client_styles/styles.lua @@ -19,6 +19,8 @@ function init() g_particles.importParticle('/particles/' .. particle) end end + + g_mouse.loadCursors('/cursors/cursors') end function terminate() diff --git a/modules/corelib/ui/uiresizeborder.lua b/modules/corelib/ui/uiresizeborder.lua index 71d49659..e3980baf 100644 --- a/modules/corelib/ui/uiresizeborder.lua +++ b/modules/corelib/ui/uiresizeborder.lua @@ -27,10 +27,10 @@ function UIResizeBorder:onHoverChange(hovered) if hovered then if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end if self:getWidth() > self:getHeight() then - g_mouse.setVerticalCursor() + g_mouse.setCursor('vertical') self.vertical = true else - g_mouse.setHorizontalCursor() + g_mouse.setCursor('horizontal') self.vertical = false end self.hovering = true diff --git a/modules/corelib/ui/uisplitter.lua b/modules/corelib/ui/uisplitter.lua index fd359dd9..6af6eca5 100644 --- a/modules/corelib/ui/uisplitter.lua +++ b/modules/corelib/ui/uisplitter.lua @@ -14,10 +14,10 @@ function UISplitter:onHoverChange(hovered) if hovered and (self:canUpdateMargin(margin + 1) ~= margin or self:canUpdateMargin(margin - 1) ~= margin) then if g_mouse.isCursorChanged() or g_mouse.isPressed() then return end if self:getWidth() > self:getHeight() then - g_mouse.setVerticalCursor() + g_mouse.setCursor('vertical') self.vertical = true else - g_mouse.setHorizontalCursor() + g_mouse.setCursor('horizontal') self.vertical = false end self.hovering = true diff --git a/modules/game_hotkeys/hotkeys_manager.lua b/modules/game_hotkeys/hotkeys_manager.lua index 6c0d79ca..073df542 100644 --- a/modules/game_hotkeys/hotkeys_manager.lua +++ b/modules/game_hotkeys/hotkeys_manager.lua @@ -235,7 +235,7 @@ function startChooseItem() connect(mouseGrabberWidget, { onMouseRelease = onChooseItemMouseRelease }) mouseGrabberWidget:grabMouse() - g_mouse.setTargetCursor() + g_mouse.setCursor('target-cursor') hide() end diff --git a/modules/game_interface/gameinterface.lua b/modules/game_interface/gameinterface.lua index a7a01e23..7adcce4d 100644 --- a/modules/game_interface/gameinterface.lua +++ b/modules/game_interface/gameinterface.lua @@ -331,14 +331,14 @@ function startUseWith(thing) selectedType = 'use' selectedThing = thing mouseGrabberWidget:grabMouse() - g_mouse.setTargetCursor() + g_mouse.setCursor('target') end function startTradeWith(thing) selectedType = 'trade' selectedThing = thing mouseGrabberWidget:grabMouse() - g_mouse.setTargetCursor() + g_mouse.setCursor('target') end function createThingMenu(menuPosition, lookThing, useThing, creatureThing) diff --git a/modules/game_interface/widgets/uigamemap.lua b/modules/game_interface/widgets/uigamemap.lua index 3beb90ac..d10ccc84 100644 --- a/modules/game_interface/widgets/uigamemap.lua +++ b/modules/game_interface/widgets/uigamemap.lua @@ -16,7 +16,8 @@ function UIGameMap:onDragEnter(mousePos) if not thing then return false end self.currentDragThing = thing - g_mouse.setTargetCursor() + + g_mouse.setCursor('target') self.allowNextRelease = false return true end diff --git a/modules/game_interface/widgets/uiitem.lua b/modules/game_interface/widgets/uiitem.lua index 3779e94d..ccd5f596 100644 --- a/modules/game_interface/widgets/uiitem.lua +++ b/modules/game_interface/widgets/uiitem.lua @@ -6,7 +6,7 @@ function UIItem:onDragEnter(mousePos) self:setBorderWidth(1) self.currentDragThing = item - g_mouse.setTargetCursor() + g_mouse.setCursor('target') return true end diff --git a/src/framework/core/graphicalapplication.cpp b/src/framework/core/graphicalapplication.cpp index 20a0abb1..5367a80c 100644 --- a/src/framework/core/graphicalapplication.cpp +++ b/src/framework/core/graphicalapplication.cpp @@ -33,6 +33,7 @@ #ifdef FW_SOUND #include +#include #endif GraphicalApplication g_app; @@ -48,6 +49,8 @@ void GraphicalApplication::init(std::vector& args) g_window.setOnInputEvent(std::bind(&GraphicalApplication::inputEvent, this, std::placeholders::_1)); g_window.setOnClose(std::bind(&GraphicalApplication::close, this)); + g_mouse.init(); + // initialize ui g_ui.init(); @@ -87,6 +90,8 @@ void GraphicalApplication::terminate() g_sounds.terminate(); #endif + g_mouse.terminate(); + // terminate graphics m_foreground = nullptr; g_graphics.terminate(); diff --git a/src/framework/input/mouse.cpp b/src/framework/input/mouse.cpp index c7a925c7..7badf3ae 100644 --- a/src/framework/input/mouse.cpp +++ b/src/framework/input/mouse.cpp @@ -23,46 +23,82 @@ #include "mouse.h" #include #include +#include Mouse g_mouse; -void Mouse::setTargetCursor() +void Mouse::init() { - //TODO: configure this in lua - g_window.setMouseCursor("/images/cursors/targetcursor", Point(9, 9)); - m_cursorChanged = true; } -void Mouse::setHorizontalCursor() +void Mouse::terminate() { - g_window.setMouseCursor("/images/cursors/horizontal", Point(9, 4)); - m_cursorChanged = true; + m_cursors.clear(); } -void Mouse::setVerticalCursor() +void Mouse::loadCursors(std::string filename) { - g_window.setMouseCursor("/images/cursors/vertical", Point(4, 9)); - m_cursorChanged = true; + filename = g_resources.guessFileType(filename, "otml"); + try { + OTMLDocumentPtr doc = OTMLDocument::parse(filename); + OTMLNodePtr cursorsNode = doc->at("Cursors"); + + for(const OTMLNodePtr& cursorNode : cursorsNode->children()) + addCursor(cursorNode->tag(), + stdext::resolve_path(cursorNode->valueAt("image"), cursorNode->source()), + cursorNode->valueAt("hot-spot")); + } catch(stdext::exception& e) { + g_logger.error(stdext::format("unable to load cursors file: %s", e.what())); + } } -void Mouse::setTextCursor() +void Mouse::addCursor(const std::string& name, const std::string& file, const Point& hotSpot) { - g_window.setMouseCursor("/images/cursors/text", Point(4, 9)); - m_cursorChanged = true; + int cursorId = g_window.loadMouseCursor(file, hotSpot); + if(cursorId >= 0) { + m_cursors[name] = cursorId; + } else + g_logger.error(stdext::format("unable to load cursor %s", name)); +} + +bool Mouse::setCursor(const std::string& name) +{ + auto it = m_cursors.find(name); + if(it == m_cursors.end()) + return false; + + int cursorId = it->second; + g_window.setMouseCursor(cursorId); + m_cursorStack.push_back(cursorId); + return true; } void Mouse::restoreCursor() { - g_window.restoreMouseCursor(); - m_cursorChanged = false; + if(m_cursorStack.size() == 0) + return; + + m_cursorStack.pop_back(); + if(m_cursorStack.size() > 0) + g_window.setMouseCursor(m_cursorStack.back()); + else + g_window.restoreMouseCursor(); } bool Mouse::isCursorChanged() { - return m_cursorChanged; + return m_cursorStack.size() > 0; } bool Mouse::isPressed(Fw::MouseButton mouseButton) { return g_window.isMouseButtonPressed(mouseButton); } + +void Mouse::checkStackSize() +{ + if(m_cursorStack.size() > 5) { + g_logger.error("mouse cursor stack is too long"); + m_cursorStack.resize(5); + } +} diff --git a/src/framework/input/mouse.h b/src/framework/input/mouse.h index 06d89fc1..4b0f13ce 100644 --- a/src/framework/input/mouse.h +++ b/src/framework/input/mouse.h @@ -25,16 +25,21 @@ class Mouse { public: - void setTargetCursor(); - void setHorizontalCursor(); - void setVerticalCursor(); - void setTextCursor(); + void init(); + void terminate(); + + void loadCursors(std::string filename); + void addCursor(const std::string& name, const std::string& file, const Point& hotSpot); + bool setCursor(const std::string& name); void restoreCursor(); bool isCursorChanged(); bool isPressed(Fw::MouseButton mouseButton); private: - bool m_cursorChanged; + void checkStackSize(); + + std::map m_cursors; + std::vector m_cursorStack; }; extern Mouse g_mouse; diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index 85fa05b2..2122a19c 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -270,10 +270,9 @@ void Application::registerLuaFunctions() // Input g_lua.registerSingletonClass("g_mouse"); - g_lua.bindSingletonFunction("g_mouse", "setTargetCursor", &Mouse::setTargetCursor, &g_mouse); - g_lua.bindSingletonFunction("g_mouse", "setHorizontalCursor", &Mouse::setHorizontalCursor, &g_mouse); - g_lua.bindSingletonFunction("g_mouse", "setVerticalCursor", &Mouse::setVerticalCursor, &g_mouse); - g_lua.bindSingletonFunction("g_mouse", "setTextCursor", &Mouse::setTextCursor, &g_mouse); + g_lua.bindSingletonFunction("g_mouse", "loadCursors", &Mouse::loadCursors, &g_mouse); + g_lua.bindSingletonFunction("g_mouse", "addCursor", &Mouse::addCursor, &g_mouse); + g_lua.bindSingletonFunction("g_mouse", "setCursor", &Mouse::setCursor, &g_mouse); g_lua.bindSingletonFunction("g_mouse", "restoreCursor", &Mouse::restoreCursor, &g_mouse); g_lua.bindSingletonFunction("g_mouse", "isCursorChanged", &Mouse::isCursorChanged, &g_mouse); g_lua.bindSingletonFunction("g_mouse", "isPressed", &Mouse::isPressed, &g_mouse); diff --git a/src/framework/platform/platformwindow.cpp b/src/framework/platform/platformwindow.cpp index 7f9cce9c..1c954272 100644 --- a/src/framework/platform/platformwindow.cpp +++ b/src/framework/platform/platformwindow.cpp @@ -32,9 +32,32 @@ X11Window window; #endif #include +#include PlatformWindow& g_window = window; +int PlatformWindow::loadMouseCursor(const std::string& file, const Point& hotSpot) +{ + ImagePtr image = Image::load(file); + + if(!image) { + g_logger.traceError(stdext::format("unable to load cursor image file %s", file)); + return -1; + } + + if(image->getBpp() != 4) { + g_logger.error("the cursor image must have 4 channels"); + return -1; + } + + if(image->getWidth() != 32 || image->getHeight() != 32) { + g_logger.error("the cursor image must have 32x32 dimension"); + return -1; + } + + return internalLoadMouseCursor(image, hotSpot); +} + void PlatformWindow::updateUnmaximizedCoords() { if(!isMaximized() && !isFullscreen()) { diff --git a/src/framework/platform/platformwindow.h b/src/framework/platform/platformwindow.h index 6b6d4454..70ce8813 100644 --- a/src/framework/platform/platformwindow.h +++ b/src/framework/platform/platformwindow.h @@ -26,6 +26,7 @@ #include #include #include +#include //@bindsingleton g_window class PlatformWindow @@ -48,12 +49,14 @@ public: virtual void maximize() = 0; virtual void poll() = 0; virtual void swapBuffers() = 0; - virtual void restoreMouseCursor() = 0; virtual void showMouse() = 0; virtual void hideMouse() = 0; virtual void displayFatalError(const std::string& message) { } - virtual void setMouseCursor(const std::string& file, const Point& hotSpot) = 0; + int loadMouseCursor(const std::string& file, const Point& hotSpot); + virtual void setMouseCursor(int cursorId) = 0; + virtual void restoreMouseCursor() = 0; + virtual void setTitle(const std::string& title) = 0; virtual void setMinimumSize(const Size& minimumSize) = 0; virtual void setFullscreen(bool fullscreen) = 0; @@ -92,6 +95,8 @@ public: void setOnInputEvent(const OnInputEventCallback& onInputEvent) { m_onInputEvent = onInputEvent; } protected: + virtual int internalLoadMouseCursor(const ImagePtr& image, const Point& hotSpot) = 0; + void updateUnmaximizedCoords(); void processKeyDown(Fw::Key keyCode); diff --git a/src/framework/platform/win32window.cpp b/src/framework/platform/win32window.cpp index 96b08d4d..1329808c 100644 --- a/src/framework/platform/win32window.cpp +++ b/src/framework/platform/win32window.cpp @@ -211,6 +211,16 @@ void WIN32Window::init() void WIN32Window::terminate() { + SetCursor(NULL); + if(m_defaultCursor) { + DestroyCursor(m_defaultCursor); + m_defaultCursor = NULL; + } + + for(HCURSOR& cursor : m_cursors) + DestroyCursor(cursor); + m_cursors.clear(); + internalDestroyGLContext(); if(m_deviceContext) { @@ -219,11 +229,6 @@ void WIN32Window::terminate() m_deviceContext = NULL; } - if(m_cursor) { - DestroyCursor(m_cursor); - m_cursor = NULL; - } - if(m_window) { if(!DestroyWindow(m_window)) g_logger.error("ERROR: Destroy window failed."); @@ -744,16 +749,6 @@ void WIN32Window::swapBuffers() #endif } -void WIN32Window::restoreMouseCursor() -{ - if(m_cursor) { - DestroyCursor(m_cursor); - m_cursor = NULL; - SetCursor(m_defaultCursor); - ShowCursor(true); - } -} - void WIN32Window::showMouse() { ShowCursor(true); @@ -769,28 +764,8 @@ void WIN32Window::displayFatalError(const std::string& message) MessageBoxW(m_window, stdext::latin1_to_utf16(message).c_str(), L"FATAL ERROR", MB_OK | MB_ICONERROR); } -void WIN32Window::setMouseCursor(const std::string& file, const Point& hotSpot) +int WIN32Window::internalLoadMouseCursor(const ImagePtr& image, const Point& hotSpot) { - ImagePtr image = Image::load(file); - - if(!image) { - g_logger.traceError(stdext::format("unable to load cursor image file %s", file)); - return; - } - - if(image->getBpp() != 4) { - g_logger.error("the cursor image must have 4 channels"); - return; - } - - if(image->getWidth() != 32 || image->getHeight() != 32) { - g_logger.error("the cursor image must have 32x32 dimension"); - return; - } - - if(m_cursor != NULL) - DestroyCursor(m_cursor); - int width = image->getWidth(); int height = image->getHeight(); int numbits = width * height; @@ -808,8 +783,28 @@ void WIN32Window::setMouseCursor(const std::string& file, const Point& hotSpot) } // otherwise 0xff000000 => black } - m_cursor = CreateCursor(m_instance, hotSpot.x, hotSpot.y, width, height, &andMask[0], &xorMask[0]); + HCURSOR cursor = CreateCursor(m_instance, hotSpot.x, hotSpot.y, width, height, &andMask[0], &xorMask[0]); + m_cursors.push_back(cursor); + return m_cursors.size()-1; +} + +void WIN32Window::setMouseCursor(int cursorId) +{ + if(cursorId >= (int)m_cursors.size() || cursorId < 0) + return; + + m_cursor = m_cursors[cursorId]; SetCursor(m_cursor); + ShowCursor(true); +} + +void WIN32Window::restoreMouseCursor() +{ + if(m_cursor) { + m_cursor = NULL; + SetCursor(m_defaultCursor); + ShowCursor(true); + } } void WIN32Window::setTitle(const std::string& title) diff --git a/src/framework/platform/win32window.h b/src/framework/platform/win32window.h index 50f72df5..88e223f8 100644 --- a/src/framework/platform/win32window.h +++ b/src/framework/platform/win32window.h @@ -61,12 +61,13 @@ public: void maximize(); void poll(); void swapBuffers(); - void restoreMouseCursor(); void showMouse(); void hideMouse(); void displayFatalError(const std::string& message); - void setMouseCursor(const std::string& file, const Point& hotSpot); + void setMouseCursor(int cursorId); + void restoreMouseCursor(); + void setTitle(const std::string& title); void setMinimumSize(const Size& minimumSize); void setFullscreen(bool fullscreen); @@ -78,11 +79,15 @@ public: std::string getClipboardText(); std::string getPlatformType(); +protected: + int internalLoadMouseCursor(const ImagePtr& image, const Point& hotSpot); + private: Rect getClientRect(); Rect getWindowRect(); Rect adjustWindowRect(const Rect& rect); + std::vector m_cursors; HWND m_window; HINSTANCE m_instance; HDC m_deviceContext; diff --git a/src/framework/platform/x11window.cpp b/src/framework/platform/x11window.cpp index 3d20f33d..de2770f9 100644 --- a/src/framework/platform/x11window.cpp +++ b/src/framework/platform/x11window.cpp @@ -37,6 +37,7 @@ X11Window::X11Window() m_rootWindow = 0; m_colormap = 0; m_cursor = 0; + m_hiddenCursor = 0; m_xim = 0; m_xic = 0; m_screen = 0; @@ -218,6 +219,20 @@ void X11Window::init() void X11Window::terminate() { + if(m_cursor != None) { + XUndefineCursor(m_display, m_window); + m_cursor = None; + } + + if(m_hiddenCursor) { + XFreeCursor(m_display, m_hiddenCursor); + m_hiddenCursor = 0; + } + + for(Cursor cursor : m_cursors) + XFreeCursor(m_display, cursor); + m_cursors.clear(); + if(m_window) { XDestroyWindow(m_display, m_window); m_window = 0; @@ -842,47 +857,40 @@ void X11Window::hideMouse() if(m_cursor != None) restoreMouseCursor(); - char bm[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - Pixmap pix = XCreateBitmapFromData(m_display, m_window, bm, 8, 8); - XColor black; - memset(&black, 0, sizeof(black)); - black.flags = DoRed | DoGreen | DoBlue; - m_cursor = XCreatePixmapCursor(m_display, pix, pix, &black, &black, 0, 0); - XFreePixmap(m_display, pix); - XDefineCursor(m_display, m_window, m_cursor); -} - -void X11Window::restoreMouseCursor() -{ - XUndefineCursor(m_display, m_window); - if(m_cursor != None) { - XFreeCursor(m_display, m_cursor); - m_cursor = None; + if(m_hiddenCursor == None) { + char bm[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + Pixmap pix = XCreateBitmapFromData(m_display, m_window, bm, 8, 8); + XColor black; + memset(&black, 0, sizeof(black)); + black.flags = DoRed | DoGreen | DoBlue; + m_hiddenCursor = XCreatePixmapCursor(m_display, pix, pix, &black, &black, 0, 0); + XFreePixmap(m_display, pix); } + + m_cursor = m_hiddenCursor; + XDefineCursor(m_display, m_window, m_cursor); } -void X11Window::setMouseCursor(const std::string& file, const Point& hotSpot) +void X11Window::setMouseCursor(int cursorId) { - ImagePtr image = Image::load(file); - - if(!image) { - g_logger.traceError(stdext::format("unable to load image file %s", file)); + if(cursorId >= (int)m_cursors.size() || cursorId < 0) return; - } - - if(image->getBpp() != 4) { - g_logger.error("the cursor image must have 4 channels"); - return; - } - - if(image->getWidth() != 32 || image->getHeight() != 32) { - g_logger.error("the cursor image must have 32x32 dimension"); - return; - } if(m_cursor != None) restoreMouseCursor(); + m_cursor = m_cursors[cursorId]; + XDefineCursor(m_display, m_window, m_cursor); +} + +void X11Window::restoreMouseCursor() +{ + XUndefineCursor(m_display, m_window); + m_cursor = None; +} + +int X11Window::internalLoadMouseCursor(const ImagePtr& image, const Point& hotSpot) +{ int width = image->getWidth(); int height = image->getHeight(); int numbits = width * height; @@ -911,10 +919,12 @@ void X11Window::setMouseCursor(const std::string& file, const Point& hotSpot) Pixmap cp = XCreateBitmapFromData(m_display, m_window, (char*)&mapBits[0], width, height); Pixmap mp = XCreateBitmapFromData(m_display, m_window, (char*)&maskBits[0], width, height); - m_cursor = XCreatePixmapCursor(m_display, cp, mp, &fg, &bg, hotSpot.x, hotSpot.y); - XDefineCursor(m_display, m_window, m_cursor); + Cursor cursor = XCreatePixmapCursor(m_display, cp, mp, &fg, &bg, hotSpot.x, hotSpot.y); XFreePixmap(m_display, cp); XFreePixmap(m_display, mp); + + m_cursors.push_back(cursor); + return m_cursors.size()-1; } void X11Window::setTitle(const std::string& title) diff --git a/src/framework/platform/x11window.h b/src/framework/platform/x11window.h index 82d91846..b41cb39f 100644 --- a/src/framework/platform/x11window.h +++ b/src/framework/platform/x11window.h @@ -63,11 +63,12 @@ public: void maximize(); void poll(); void swapBuffers(); - void restoreMouseCursor(); void showMouse(); void hideMouse(); - void setMouseCursor(const std::string& file, const Point& hotSpot); + void setMouseCursor(int cursorId); + void restoreMouseCursor(); + void setTitle(const std::string& title); void setMinimumSize(const Size& minimumSize); void setFullscreen(bool fullscreen); @@ -79,13 +80,18 @@ public: std::string getClipboardText(); std::string getPlatformType(); +protected: + int internalLoadMouseCursor(const ImagePtr& image, const Point& hotSpot); + private: Display *m_display; XVisualInfo *m_visual; Window m_window; Window m_rootWindow; Colormap m_colormap; + std::vector m_cursors; Cursor m_cursor; + Cursor m_hiddenCursor; XIM m_xim; XIC m_xic; int m_screen; diff --git a/src/framework/ui/uitextedit.cpp b/src/framework/ui/uitextedit.cpp index c50d05d5..53d5550a 100644 --- a/src/framework/ui/uitextedit.cpp +++ b/src/framework/ui/uitextedit.cpp @@ -590,7 +590,7 @@ void UITextEdit::onHoverChange(bool hovered) { if(m_changeCursorImage) { if(hovered) - g_mouse.setTextCursor(); + g_mouse.setCursor("text"); else g_mouse.restoreCursor(); }