├── LICENSE ├── README.md ├── lovr-mouse.lua └── main.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Bjorn Swenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lovr-mouse 2 | === 3 | 4 | A mouse module for LÖVR. 5 | 6 | Usage 7 | --- 8 | 9 | Copy `lovr-mouse.lua` to your project and require it. I like to put it on the `lovr` global like this: 10 | 11 | ```lua 12 | lovr.mouse = require 'lovr-mouse' 13 | 14 | function lovr.update(dt) 15 | print(lovr.mouse.getPosition()) 16 | end 17 | ``` 18 | 19 | lovr-mouse can only run on systems where LÖVR uses both LuaJIT and GLFW. If your project needs to be compatible with Android or WebVR, you can optionally include lovr-mouse with: 20 | 21 | ```lua 22 | if type(jit) == 'table' and lovr.system.getOS() ~= 'Android' and lovr.system.getOS() ~= 'Web' then 23 | lovr.mouse = require 'lovr-mouse' 24 | end 25 | ``` 26 | 27 | Note that `lovr.mouse.setRelativeMode` will conflict with the mouselook behavior of the VR simulator. To fix it, add this to `conf.lua` to disable the headset module: 28 | 29 | ```lua 30 | function lovr.conf(t) 31 | t.modules.headset = false 32 | end 33 | ``` 34 | 35 | API 36 | --- 37 | 38 | - `mouse.getX()` Returns the x position of the cursor. 39 | - `mouse.getY()` Returns the y position of the cursor. 40 | - `mouse.getPosition()` Returns the x and y position of the cursor. 41 | - `mouse.setX(x)` Sets the x position of the cursor. 42 | - `mouse.setY(y)` Sets the y position of the cursor. 43 | - `mouse.setPosition(x, y)` Sets the position of the cursor. 44 | - `mouse.isDown(button, ...)` Returns whether any of the specified buttons are currently pressed. 45 | Available buttons: 46 | - `1` the left mouse button. 47 | - `2` the right mouse button. 48 | - `3` the middle mouse button. 49 | - Other higher numbers may be supported for fancier mice. 50 | - `mouse.setRelativeMode(enable)` Sets or disables relative mode. In relative mode the cursor is 51 | hidden and it can move infinitely (you can use the dx and dy parameters of mousemoved to get the 52 | movement amounts). 53 | - `mouse.getRelativeMode()` Returns whether relative mode is currently enabled. 54 | - `mouse.getSystemCursor(systemCursorKind)` Returns a Cursor UserData pointer which is GCed, so keep it in scope as long as you want to use it. 55 | Available Cursor kinds: 56 | - `arrow` Your regular system cursor. The pointy one. 57 | - `ibeam` I style cursor. You see this kind when you hover over text. 58 | - `crosshair` The + cursor. 59 | - `hand` The hand, usually with the pointer finger raised. 60 | - `sizewe` You see this cursor when you resize a window horizontally. 61 | - `sizens` You see this cursor when you resize a window vertically. 62 | - `mouse.newCursor(string | blob | image, hotx, hoty)` Returns a Cursor UserData pointer which is GCed, so keep it in scope as long as you want to use it. `hotx` and `hoty` define the cursor's "hot spot" (where the little click pixel is). 63 | - `mouse.setCursor(cursor)` Sets the cursor. This is the Cursor UserData pointer returned from the `newCursor` and `getSystemCursor` functions. If the Cursor that you set gets GCed, it will reset back to the default Cursor automatically. 64 | - `lovr.mousepressed(x, y, button)` Called when a mouse button is pressed. 65 | - `lovr.mousereleased(x, y, button)` Called when a mouse button is released. 66 | - `lovr.mousemoved(x, y, dx, dy)` Called when the mouse is moved. The arguments represent the new 67 | cursor position as well as the delta from the previous position, in window coordinates. 68 | - `lovr.wheelmoved(dx, dy)` Called when the mouse is scrolled. 69 | 70 | License 71 | --- 72 | 73 | MIT 74 | -------------------------------------------------------------------------------- /lovr-mouse.lua: -------------------------------------------------------------------------------- 1 | assert(type(jit) == 'table' and lovr.system.getOS() ~= 'Android', 'lovr-mouse cannot run on this platform') 2 | local ffi = require 'ffi' 3 | local C = ffi.os == 'Windows' and ffi.load('glfw3') or ffi.C 4 | 5 | ffi.cdef [[ 6 | enum { 7 | GLFW_CURSOR = 0x00033001, 8 | GLFW_CURSOR_NORMAL = 0x00034001, 9 | GLFW_CURSOR_HIDDEN = 0x00034002, 10 | GLFW_CURSOR_DISABLED = 0x00034003, 11 | GLFW_ARROW_CURSOR = 0x00036001, 12 | GLFW_IBEAM_CURSOR = 0x00036002, 13 | GLFW_CROSSHAIR_CURSOR = 0x00036003, 14 | GLFW_HAND_CURSOR = 0x00036004, 15 | GLFW_HRESIZE_CURSOR = 0x00036005, 16 | GLFW_VRESIZE_CURSOR = 0x00036006 17 | }; 18 | 19 | typedef struct { 20 | int width; 21 | int height; 22 | unsigned char* pixels; 23 | } GLFWimage; 24 | 25 | typedef struct GLFWcursor GLFWcursor; 26 | typedef struct GLFWwindow GLFWwindow; 27 | typedef void(*GLFWmousebuttonfun)(GLFWwindow*, int, int, int); 28 | typedef void(*GLFWcursorposfun)(GLFWwindow*, double, double); 29 | typedef void(*GLFWscrollfun)(GLFWwindow*, double, double); 30 | 31 | GLFWwindow* os_get_glfw_window(void); 32 | int glfwGetInputMode(GLFWwindow* window, int mode); 33 | void glfwSetInputMode(GLFWwindow* window, int mode, int value); 34 | void glfwGetCursorPos(GLFWwindow* window, double* x, double* y); 35 | void glfwSetCursorPos(GLFWwindow* window, double x, double y); 36 | GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot); 37 | GLFWcursor* glfwCreateStandardCursor(int kind); 38 | void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor); 39 | int glfwGetMouseButton(GLFWwindow* window, int button); 40 | void glfwGetWindowSize(GLFWwindow* window, int* width, int* height); 41 | void glfwDestroyCursor(GLFWcursor* cursor); 42 | GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* window, GLFWmousebuttonfun callback); 43 | GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* window, GLFWcursorposfun callback); 44 | GLFWcursorposfun glfwSetScrollCallback(GLFWwindow* window, GLFWscrollfun callback); 45 | ]] 46 | 47 | local window = ffi.C.os_get_glfw_window() 48 | 49 | local mouse = {} 50 | 51 | -- LÖVR uses framebuffer scale for everything, but glfw uses window scale for events. 52 | -- It is necessary to convert between the two at all boundaries. 53 | function mouse.getScale() 54 | local x, _ = ffi.new('int[1]'), ffi.new('int[1]') 55 | C.glfwGetWindowSize(window, x, _) 56 | return lovr.system.getWindowWidth() / x[0] 57 | end 58 | 59 | function mouse.getX() 60 | local x = ffi.new('double[1]') 61 | C.glfwGetCursorPos(window, x, nil) 62 | return x[0] * mouse.getScale() 63 | end 64 | 65 | function mouse.getY() 66 | local y = ffi.new('double[1]') 67 | C.glfwGetCursorPos(window, nil, y) 68 | return y[0] * mouse.getScale() 69 | end 70 | 71 | function mouse.getPosition() 72 | local x, y = ffi.new('double[1]'), ffi.new('double[1]') 73 | local scale = mouse.getScale() 74 | C.glfwGetCursorPos(window, x, y) 75 | return x[0] * scale, y[0] * scale 76 | end 77 | 78 | function mouse.setX(x) 79 | local y = mouse.getY() 80 | local scale = mouse.getScale() 81 | C.glfwSetCursorPos(window, x / scale, y / scale) 82 | end 83 | 84 | function mouse.setY(y) 85 | local x = mouse.getX() 86 | local scale = mouse.getScale() 87 | C.glfwSetCursorPos(window, x / scale, y / scale) 88 | end 89 | 90 | function mouse.setPosition(x, y) 91 | local scale = mouse.getScale() 92 | C.glfwSetCursorPos(window, x / scale, y / scale) 93 | end 94 | 95 | function mouse.isDown(button, ...) 96 | if not button then return false end 97 | return C.glfwGetMouseButton(window, button - 1) > 0 or mouse.isDown(...) 98 | end 99 | 100 | function mouse.getRelativeMode() 101 | return C.glfwGetInputMode(window, C.GLFW_CURSOR) == C.GLFW_CURSOR_DISABLED 102 | end 103 | 104 | function mouse.setRelativeMode(enable) 105 | C.glfwSetInputMode(window, C.GLFW_CURSOR, enable and C.GLFW_CURSOR_DISABLED or C.GLFW_CURSOR_NORMAL) 106 | end 107 | 108 | function mouse.newCursor(source, hotx, hoty) 109 | if type(source) == 'string' or tostring(source) == 'Blob' then 110 | source = lovr.data.newImage(source) 111 | else 112 | assert(tostring(source) == 'Image', 'Bad argument #1 to newCursor (Image expected)') 113 | end 114 | local image = ffi.new('GLFWimage', source:getWidth(), source:getHeight(), source:getPointer()) 115 | return ffi.gc(C.glfwCreateCursor(image, hotx or 0, hoty or 0), C.glfwDestroyCursor) 116 | end 117 | 118 | function mouse.getSystemCursor(kind) 119 | local kinds = { 120 | arrow = C.GLFW_ARROW_CURSOR, 121 | ibeam = C.GLFW_IBEAM_CURSOR, 122 | crosshair = C.GLFW_CROSSHAIR_CURSOR, 123 | hand = C.GLFW_HAND_CURSOR, 124 | sizewe = C.GLFW_HRESIZE_CURSOR, 125 | sizens = C.GLFW_VRESIZE_CURSOR 126 | } 127 | assert(kinds[kind], string.format('Unknown cursor %q', tostring(kind))) 128 | return ffi.gc(C.glfwCreateStandardCursor(kinds[kind]), C.glfwDestroyCursor) 129 | end 130 | 131 | function mouse.setCursor(cursor) 132 | C.glfwSetCursor(window, cursor) 133 | end 134 | 135 | C.glfwSetMouseButtonCallback(window, function(target, button, action, mods) 136 | if target == window then 137 | local x, y = mouse.getPosition() 138 | lovr.event.push(action > 0 and 'mousepressed' or 'mousereleased', x, y, button + 1, false) 139 | end 140 | end) 141 | 142 | local px, py = mouse.getPosition() 143 | C.glfwSetCursorPosCallback(window, function(target, x, y) 144 | if target == window then 145 | local scale = mouse.getScale() 146 | x = x * scale 147 | y = y * scale 148 | lovr.event.push('mousemoved', x, y, x - px, y - py, false) 149 | px, py = x, y 150 | end 151 | end) 152 | 153 | C.glfwSetScrollCallback(window, function(target, x, y) 154 | if target == window then 155 | local scale = mouse.getScale() 156 | lovr.event.push('wheelmoved', x * scale, y * scale) 157 | end 158 | end) 159 | 160 | return mouse 161 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | lovr.mouse = require 'lovr-mouse' 2 | 3 | function lovr.mousepressed(x, y, button) 4 | print('press', x, y, button) 5 | end 6 | 7 | function lovr.mousereleased(x, y, button) 8 | print('release', x, y, button) 9 | end 10 | 11 | function lovr.mousemoved(x, y, dx, dy) 12 | if lovr.mouse.isDown(1, 2, 3) then 13 | print('move', x, y, dx, dy) 14 | end 15 | end 16 | 17 | function lovr.wheelmoved(dx, dy) 18 | print('wheel', dx, dy) 19 | end 20 | --------------------------------------------------------------------------------