drag event
This commit is contained in:
67
FlexLove.lua
67
FlexLove.lua
@@ -2590,10 +2590,12 @@ end
|
|||||||
-- ====================
|
-- ====================
|
||||||
|
|
||||||
---@class InputEvent
|
---@class InputEvent
|
||||||
---@field type "click"|"press"|"release"|"rightclick"|"middleclick"
|
---@field type "click"|"press"|"release"|"rightclick"|"middleclick"|"drag"
|
||||||
---@field button number -- Mouse button: 1 (left), 2 (right), 3 (middle)
|
---@field button number -- Mouse button: 1 (left), 2 (right), 3 (middle)
|
||||||
---@field x number -- Mouse X position
|
---@field x number -- Mouse X position
|
||||||
---@field y number -- Mouse Y position
|
---@field y number -- Mouse Y position
|
||||||
|
---@field dx number? -- Delta X from drag start (only for drag events)
|
||||||
|
---@field dy number? -- Delta Y from drag start (only for drag events)
|
||||||
---@field modifiers {shift:boolean, ctrl:boolean, alt:boolean, super:boolean}
|
---@field modifiers {shift:boolean, ctrl:boolean, alt:boolean, super:boolean}
|
||||||
---@field clickCount number -- Number of clicks (for double/triple click detection)
|
---@field clickCount number -- Number of clicks (for double/triple click detection)
|
||||||
---@field timestamp number -- Time when event occurred
|
---@field timestamp number -- Time when event occurred
|
||||||
@@ -2601,10 +2603,12 @@ local InputEvent = {}
|
|||||||
InputEvent.__index = InputEvent
|
InputEvent.__index = InputEvent
|
||||||
|
|
||||||
---@class InputEventProps
|
---@class InputEventProps
|
||||||
---@field type "click"|"press"|"release"|"rightclick"|"middleclick"
|
---@field type "click"|"press"|"release"|"rightclick"|"middleclick"|"drag"
|
||||||
---@field button number
|
---@field button number
|
||||||
---@field x number
|
---@field x number
|
||||||
---@field y number
|
---@field y number
|
||||||
|
---@field dx number?
|
||||||
|
---@field dy number?
|
||||||
---@field modifiers {shift:boolean, ctrl:boolean, alt:boolean, super:boolean}
|
---@field modifiers {shift:boolean, ctrl:boolean, alt:boolean, super:boolean}
|
||||||
---@field clickCount number?
|
---@field clickCount number?
|
||||||
---@field timestamp number?
|
---@field timestamp number?
|
||||||
@@ -2618,6 +2622,8 @@ function InputEvent.new(props)
|
|||||||
self.button = props.button
|
self.button = props.button
|
||||||
self.x = props.x
|
self.x = props.x
|
||||||
self.y = props.y
|
self.y = props.y
|
||||||
|
self.dx = props.dx
|
||||||
|
self.dy = props.dy
|
||||||
self.modifiers = props.modifiers
|
self.modifiers = props.modifiers
|
||||||
self.clickCount = props.clickCount or 1
|
self.clickCount = props.clickCount or 1
|
||||||
self.timestamp = props.timestamp or love.timer.getTime()
|
self.timestamp = props.timestamp or love.timer.getTime()
|
||||||
@@ -2980,6 +2986,10 @@ Public API methods to access internal state:
|
|||||||
---@field _lastClickButton number? -- Button of last click
|
---@field _lastClickButton number? -- Button of last click
|
||||||
---@field _clickCount number -- Current click count for multi-click detection
|
---@field _clickCount number -- Current click count for multi-click detection
|
||||||
---@field _touchPressed table<any, boolean> -- Track touch pressed state
|
---@field _touchPressed table<any, boolean> -- Track touch pressed state
|
||||||
|
---@field _dragStartX table<number, number>? -- Track drag start X position per mouse button
|
||||||
|
---@field _dragStartY table<number, number>? -- Track drag start Y position per mouse button
|
||||||
|
---@field _lastMouseX table<number, number>? -- Last known mouse X position per button for drag tracking
|
||||||
|
---@field _lastMouseY table<number, number>? -- Last known mouse Y position per button for drag tracking
|
||||||
---@field _explicitlyAbsolute boolean?
|
---@field _explicitlyAbsolute boolean?
|
||||||
---@field gridRows number? -- Number of rows in the grid
|
---@field gridRows number? -- Number of rows in the grid
|
||||||
---@field gridColumns number? -- Number of columns in the grid
|
---@field gridColumns number? -- Number of columns in the grid
|
||||||
@@ -3126,6 +3136,12 @@ function Element.new(props)
|
|||||||
self._lastClickButton = nil
|
self._lastClickButton = nil
|
||||||
self._clickCount = 0
|
self._clickCount = 0
|
||||||
self._touchPressed = {}
|
self._touchPressed = {}
|
||||||
|
|
||||||
|
-- Initialize drag tracking for event system
|
||||||
|
self._dragStartX = {} -- Track drag start X position per mouse button
|
||||||
|
self._dragStartY = {} -- Track drag start Y position per mouse button
|
||||||
|
self._lastMouseX = {} -- Track last mouse X position per button
|
||||||
|
self._lastMouseY = {} -- Track last mouse Y position per button
|
||||||
|
|
||||||
-- Initialize theme
|
-- Initialize theme
|
||||||
self._themeState = "normal"
|
self._themeState = "normal"
|
||||||
@@ -5118,7 +5134,7 @@ function Element:update(dt)
|
|||||||
if love.mouse.isDown(button) then
|
if love.mouse.isDown(button) then
|
||||||
-- Button is pressed down
|
-- Button is pressed down
|
||||||
if not self._pressed[button] then
|
if not self._pressed[button] then
|
||||||
-- Just pressed - fire press event
|
-- Just pressed - fire press event and record drag start position
|
||||||
local modifiers = getModifiers()
|
local modifiers = getModifiers()
|
||||||
local pressEvent = InputEvent.new({
|
local pressEvent = InputEvent.new({
|
||||||
type = "press",
|
type = "press",
|
||||||
@@ -5130,6 +5146,39 @@ function Element:update(dt)
|
|||||||
})
|
})
|
||||||
self.callback(self, pressEvent)
|
self.callback(self, pressEvent)
|
||||||
self._pressed[button] = true
|
self._pressed[button] = true
|
||||||
|
|
||||||
|
-- Record drag start position per button
|
||||||
|
self._dragStartX[button] = mx
|
||||||
|
self._dragStartY[button] = my
|
||||||
|
self._lastMouseX[button] = mx
|
||||||
|
self._lastMouseY[button] = my
|
||||||
|
else
|
||||||
|
-- Button is still pressed - check for mouse movement (drag)
|
||||||
|
local lastX = self._lastMouseX[button] or mx
|
||||||
|
local lastY = self._lastMouseY[button] or my
|
||||||
|
|
||||||
|
if lastX ~= mx or lastY ~= my then
|
||||||
|
-- Mouse has moved - fire drag event
|
||||||
|
local modifiers = getModifiers()
|
||||||
|
local dx = mx - self._dragStartX[button]
|
||||||
|
local dy = my - self._dragStartY[button]
|
||||||
|
|
||||||
|
local dragEvent = InputEvent.new({
|
||||||
|
type = "drag",
|
||||||
|
button = button,
|
||||||
|
x = mx,
|
||||||
|
y = my,
|
||||||
|
dx = dx,
|
||||||
|
dy = dy,
|
||||||
|
modifiers = modifiers,
|
||||||
|
clickCount = 1,
|
||||||
|
})
|
||||||
|
self.callback(self, dragEvent)
|
||||||
|
|
||||||
|
-- Update last known position for this button
|
||||||
|
self._lastMouseX[button] = mx
|
||||||
|
self._lastMouseY[button] = my
|
||||||
|
end
|
||||||
end
|
end
|
||||||
elseif self._pressed[button] then
|
elseif self._pressed[button] then
|
||||||
-- Button was just released - fire click event
|
-- Button was just released - fire click event
|
||||||
@@ -5169,6 +5218,10 @@ function Element:update(dt)
|
|||||||
|
|
||||||
self.callback(self, clickEvent)
|
self.callback(self, clickEvent)
|
||||||
self._pressed[button] = false
|
self._pressed[button] = false
|
||||||
|
|
||||||
|
-- Clean up drag tracking
|
||||||
|
self._dragStartX[button] = nil
|
||||||
|
self._dragStartY[button] = nil
|
||||||
|
|
||||||
-- Focus editable elements on left click
|
-- Focus editable elements on left click
|
||||||
if button == 1 and self.editable then
|
if button == 1 and self.editable then
|
||||||
@@ -5187,8 +5240,12 @@ function Element:update(dt)
|
|||||||
self.callback(self, releaseEvent)
|
self.callback(self, releaseEvent)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Mouse left the element - reset pressed state
|
-- Mouse left the element - reset pressed state and drag tracking
|
||||||
self._pressed[button] = false
|
if self._pressed[button] then
|
||||||
|
self._pressed[button] = false
|
||||||
|
self._dragStartX[button] = nil
|
||||||
|
self._dragStartY[button] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end -- end if self.callback
|
end -- end if self.callback
|
||||||
|
|||||||
306
examples/14_drag_slider.lua
Normal file
306
examples/14_drag_slider.lua
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
--[[
|
||||||
|
FlexLove Example 14: Drag Event - Slider Implementation
|
||||||
|
|
||||||
|
This example demonstrates how to use the new drag event to create
|
||||||
|
interactive sliders without any first-party slider component.
|
||||||
|
|
||||||
|
Features demonstrated:
|
||||||
|
- Using drag events for continuous mouse tracking
|
||||||
|
- Converting mouse coordinates to element-relative positions
|
||||||
|
- Updating UI elements based on drag position
|
||||||
|
- Creating reusable slider components
|
||||||
|
|
||||||
|
Run with: love /path/to/libs/examples/14_drag_slider.lua
|
||||||
|
]]
|
||||||
|
|
||||||
|
local Lv = love
|
||||||
|
|
||||||
|
local FlexLove = require("../FlexLove")
|
||||||
|
local Gui = FlexLove.Gui
|
||||||
|
local Color = FlexLove.Color
|
||||||
|
local enums = FlexLove.enums
|
||||||
|
|
||||||
|
-- Slider state
|
||||||
|
local volume = 0.5 -- 0.0 to 1.0
|
||||||
|
local brightness = 0.75 -- 0.0 to 1.0
|
||||||
|
local temperature = 20 -- 0 to 40 (degrees)
|
||||||
|
|
||||||
|
-- UI elements
|
||||||
|
local volumeValueText
|
||||||
|
local brightnessValueText
|
||||||
|
local temperatureValueText
|
||||||
|
local volumeHandle
|
||||||
|
local brightnessHandle
|
||||||
|
local temperatureHandle
|
||||||
|
|
||||||
|
--- Helper function to create a slider
|
||||||
|
---@param x string|number
|
||||||
|
---@param y string|number
|
||||||
|
---@param width string|number
|
||||||
|
---@param label string
|
||||||
|
---@param min number
|
||||||
|
---@param max number
|
||||||
|
---@param initialValue number
|
||||||
|
---@param onValueChange function
|
||||||
|
---@return table -- Returns { bg, handle, valueText }
|
||||||
|
local function createSlider(x, y, width, label, min, max, initialValue, onValueChange)
|
||||||
|
-- Container for the slider
|
||||||
|
local container = Gui.new({
|
||||||
|
x = x,
|
||||||
|
y = y,
|
||||||
|
width = width,
|
||||||
|
height = "12vh",
|
||||||
|
positioning = enums.Positioning.FLEX,
|
||||||
|
flexDirection = enums.FlexDirection.VERTICAL,
|
||||||
|
gap = 5,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Label
|
||||||
|
Gui.new({
|
||||||
|
parent = container,
|
||||||
|
height = "3vh",
|
||||||
|
text = label,
|
||||||
|
textSize = "2vh",
|
||||||
|
textColor = Color.new(0.9, 0.9, 0.9, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Slider track background
|
||||||
|
local sliderBg = Gui.new({
|
||||||
|
parent = container,
|
||||||
|
height = "4vh",
|
||||||
|
backgroundColor = Color.new(0.2, 0.2, 0.25, 1),
|
||||||
|
cornerRadius = 5,
|
||||||
|
positioning = enums.Positioning.RELATIVE,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Slider handle
|
||||||
|
local normalized = (initialValue - min) / (max - min)
|
||||||
|
local handle = Gui.new({
|
||||||
|
parent = sliderBg,
|
||||||
|
x = (normalized * 95) .. "%",
|
||||||
|
y = "50%",
|
||||||
|
width = "5%",
|
||||||
|
height = "80%",
|
||||||
|
backgroundColor = Color.new(0.4, 0.6, 0.9, 1),
|
||||||
|
cornerRadius = 3,
|
||||||
|
positioning = enums.Positioning.ABSOLUTE,
|
||||||
|
-- Center the handle vertically
|
||||||
|
top = "10%",
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Value display
|
||||||
|
local valueText = Gui.new({
|
||||||
|
parent = container,
|
||||||
|
height = "3vh",
|
||||||
|
text = string.format("%.2f", initialValue),
|
||||||
|
textSize = "2vh",
|
||||||
|
textColor = Color.new(0.7, 0.8, 1, 1),
|
||||||
|
textAlign = enums.TextAlign.CENTER,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Make the background track interactive
|
||||||
|
sliderBg.callback = function(element, event)
|
||||||
|
if event.type == "press" or event.type == "drag" then
|
||||||
|
-- Get element bounds
|
||||||
|
local bg_x = element.x
|
||||||
|
local bg_width = element.width
|
||||||
|
|
||||||
|
-- Calculate relative position (0 to 1)
|
||||||
|
local mouse_x = event.x
|
||||||
|
local relative_x = mouse_x - bg_x
|
||||||
|
local new_normalized = math.max(0, math.min(1, relative_x / bg_width))
|
||||||
|
|
||||||
|
-- Calculate actual value
|
||||||
|
local new_value = min + (new_normalized * (max - min))
|
||||||
|
|
||||||
|
-- Update handle position (use percentage for responsiveness)
|
||||||
|
handle.x = (new_normalized * 95) .. "%"
|
||||||
|
|
||||||
|
-- Update value text
|
||||||
|
if max - min > 10 then
|
||||||
|
-- For larger ranges (like temperature), show integers
|
||||||
|
valueText.text = string.format("%d", new_value)
|
||||||
|
else
|
||||||
|
-- For smaller ranges (like 0-1), show decimals
|
||||||
|
valueText.text = string.format("%.2f", new_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Call the value change callback
|
||||||
|
if onValueChange then
|
||||||
|
onValueChange(new_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Re-layout to apply position changes
|
||||||
|
element:recalculateUnits(Lv.graphics.getWidth(), Lv.graphics.getHeight())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
container = container,
|
||||||
|
bg = sliderBg,
|
||||||
|
handle = handle,
|
||||||
|
valueText = valueText,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Lv.load()
|
||||||
|
Gui.init({
|
||||||
|
baseScale = { width = 1920, height = 1080 },
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Title
|
||||||
|
Gui.new({
|
||||||
|
x = "2vw",
|
||||||
|
y = "2vh",
|
||||||
|
width = "96vw",
|
||||||
|
height = "6vh",
|
||||||
|
text = "FlexLove Example 14: Drag Event - Slider Implementation",
|
||||||
|
textSize = "4vh",
|
||||||
|
textColor = Color.new(1, 1, 1, 1),
|
||||||
|
textAlign = enums.TextAlign.CENTER,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Subtitle
|
||||||
|
Gui.new({
|
||||||
|
x = "2vw",
|
||||||
|
y = "9vh",
|
||||||
|
width = "96vw",
|
||||||
|
height = "3vh",
|
||||||
|
text = "Drag the sliders to change values - built using only the drag event primitive!",
|
||||||
|
textSize = "2vh",
|
||||||
|
textColor = Color.new(0.7, 0.7, 0.7, 1),
|
||||||
|
textAlign = enums.TextAlign.CENTER,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Volume Slider (0.0 - 1.0)
|
||||||
|
local volumeSlider = createSlider("10vw", "18vh", "80vw", "Volume (0.0 - 1.0)", 0.0, 1.0, volume, function(value)
|
||||||
|
volume = value
|
||||||
|
end)
|
||||||
|
volumeValueText = volumeSlider.valueText
|
||||||
|
volumeHandle = volumeSlider.handle
|
||||||
|
|
||||||
|
-- Brightness Slider (0.0 - 1.0)
|
||||||
|
local brightnessSlider = createSlider("10vw", "35vh", "80vw", "Brightness (0.0 - 1.0)", 0.0, 1.0, brightness, function(value)
|
||||||
|
brightness = value
|
||||||
|
end)
|
||||||
|
brightnessValueText = brightnessSlider.valueText
|
||||||
|
brightnessHandle = brightnessSlider.handle
|
||||||
|
|
||||||
|
-- Temperature Slider (0 - 40°C)
|
||||||
|
local temperatureSlider = createSlider("10vw", "52vh", "80vw", "Temperature (0 - 40°C)", 0, 40, temperature, function(value)
|
||||||
|
temperature = value
|
||||||
|
end)
|
||||||
|
temperatureValueText = temperatureSlider.valueText
|
||||||
|
temperatureHandle = temperatureSlider.handle
|
||||||
|
|
||||||
|
-- Visual feedback section
|
||||||
|
Gui.new({
|
||||||
|
x = "10vw",
|
||||||
|
y = "70vh",
|
||||||
|
width = "80vw",
|
||||||
|
height = "3vh",
|
||||||
|
text = "Visual Feedback:",
|
||||||
|
textSize = "2.5vh",
|
||||||
|
textColor = Color.new(1, 1, 1, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Volume visualization
|
||||||
|
Gui.new({
|
||||||
|
x = "10vw",
|
||||||
|
y = "75vh",
|
||||||
|
width = "25vw",
|
||||||
|
height = "20vh",
|
||||||
|
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
|
||||||
|
cornerRadius = 10,
|
||||||
|
border = { top = true, right = true, bottom = true, left = true },
|
||||||
|
borderColor = Color.new(0.3, 0.3, 0.4, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Brightness visualization
|
||||||
|
Gui.new({
|
||||||
|
x = "37.5vw",
|
||||||
|
y = "75vh",
|
||||||
|
width = "25vw",
|
||||||
|
height = "20vh",
|
||||||
|
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
|
||||||
|
cornerRadius = 10,
|
||||||
|
border = { top = true, right = true, bottom = true, left = true },
|
||||||
|
borderColor = Color.new(0.3, 0.3, 0.4, 1),
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Temperature visualization
|
||||||
|
Gui.new({
|
||||||
|
x = "65vw",
|
||||||
|
y = "75vh",
|
||||||
|
width = "25vw",
|
||||||
|
height = "20vh",
|
||||||
|
backgroundColor = Color.new(0.15, 0.15, 0.2, 1),
|
||||||
|
cornerRadius = 10,
|
||||||
|
border = { top = true, right = true, bottom = true, left = true },
|
||||||
|
borderColor = Color.new(0.3, 0.3, 0.4, 1),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function Lv.update(dt)
|
||||||
|
Gui.update(dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Lv.draw()
|
||||||
|
Lv.graphics.clear(0.05, 0.05, 0.08, 1)
|
||||||
|
Gui.draw()
|
||||||
|
|
||||||
|
-- Draw volume visualization (speaker icon with bars)
|
||||||
|
local volumeX = Lv.graphics.getWidth() * 0.10 + 20
|
||||||
|
local volumeY = Lv.graphics.getHeight() * 0.75 + 30
|
||||||
|
Lv.graphics.setColor(0.4, 0.6, 0.9, 1)
|
||||||
|
Lv.graphics.print("Volume:", volumeX, volumeY, 0, 2, 2)
|
||||||
|
|
||||||
|
-- Volume bars
|
||||||
|
local barCount = 10
|
||||||
|
for i = 1, barCount do
|
||||||
|
if i / barCount <= volume then
|
||||||
|
Lv.graphics.setColor(0.4, 0.8, 0.4, 1)
|
||||||
|
else
|
||||||
|
Lv.graphics.setColor(0.2, 0.2, 0.25, 1)
|
||||||
|
end
|
||||||
|
local barX = volumeX + 20 + (i - 1) * 30
|
||||||
|
local barHeight = 20 + i * 5
|
||||||
|
Lv.graphics.rectangle("fill", barX, volumeY + 60 - barHeight, 20, barHeight, 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Draw brightness visualization (sun icon)
|
||||||
|
local brightnessX = Lv.graphics.getWidth() * 0.375 + 20
|
||||||
|
local brightnessY = Lv.graphics.getHeight() * 0.75 + 30
|
||||||
|
Lv.graphics.setColor(0.4, 0.6, 0.9, 1)
|
||||||
|
Lv.graphics.print("Brightness:", brightnessX, brightnessY, 0, 2, 2)
|
||||||
|
|
||||||
|
-- Sun circle
|
||||||
|
Lv.graphics.setColor(1, 0.9, 0.3, brightness)
|
||||||
|
Lv.graphics.circle("fill", brightnessX + 150, brightnessY + 80, 30 * brightness + 10)
|
||||||
|
|
||||||
|
-- Draw temperature visualization (thermometer)
|
||||||
|
local tempX = Lv.graphics.getWidth() * 0.65 + 20
|
||||||
|
local tempY = Lv.graphics.getHeight() * 0.75 + 30
|
||||||
|
Lv.graphics.setColor(0.4, 0.6, 0.9, 1)
|
||||||
|
Lv.graphics.print("Temperature:", tempX, tempY, 0, 2, 2)
|
||||||
|
|
||||||
|
-- Thermometer
|
||||||
|
local tempNormalized = temperature / 40
|
||||||
|
local tempColor = {
|
||||||
|
1 - tempNormalized * 0.5, -- Red increases with temp
|
||||||
|
0.3,
|
||||||
|
1 - tempNormalized, -- Blue decreases with temp
|
||||||
|
}
|
||||||
|
Lv.graphics.setColor(tempColor[1], tempColor[2], tempColor[3], 1)
|
||||||
|
Lv.graphics.rectangle("fill", tempX + 100, tempY + 50, 40, 100 * tempNormalized, 5)
|
||||||
|
Lv.graphics.setColor(0.3, 0.3, 0.4, 1)
|
||||||
|
Lv.graphics.rectangle("line", tempX + 100, tempY + 50, 40, 100, 5)
|
||||||
|
|
||||||
|
-- Temperature text
|
||||||
|
Lv.graphics.setColor(1, 1, 1, 1)
|
||||||
|
Lv.graphics.print(string.format("%.0f°C", temperature), tempX + 160, tempY + 90, 0, 2, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Lv.resize(w, h)
|
||||||
|
Gui.resize(w, h)
|
||||||
|
end
|
||||||
282
testing/__tests__/29_drag_event_tests.lua
Normal file
282
testing/__tests__/29_drag_event_tests.lua
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
-- Drag Event Tests
|
||||||
|
-- Tests for the new drag event functionality
|
||||||
|
|
||||||
|
package.path = package.path .. ";?.lua"
|
||||||
|
|
||||||
|
local lu = require("testing.luaunit")
|
||||||
|
require("testing.loveStub")
|
||||||
|
local FlexLove = require("FlexLove")
|
||||||
|
local Gui = FlexLove.Gui
|
||||||
|
|
||||||
|
TestDragEvent = {}
|
||||||
|
|
||||||
|
function TestDragEvent:setUp()
|
||||||
|
-- Initialize GUI before each test
|
||||||
|
Gui.init({ baseScale = { width = 1920, height = 1080 } })
|
||||||
|
love.window.setMode(1920, 1080)
|
||||||
|
Gui.resize(1920, 1080) -- Recalculate scale factors after setMode
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestDragEvent:tearDown()
|
||||||
|
-- Clean up after each test
|
||||||
|
Gui.destroy()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 1: Drag event is fired when mouse moves while pressed
|
||||||
|
function TestDragEvent:test_drag_event_fired_on_mouse_movement()
|
||||||
|
local dragEventReceived = false
|
||||||
|
local dragEvent = nil
|
||||||
|
|
||||||
|
local element = Gui.new({
|
||||||
|
x = 100,
|
||||||
|
y = 100,
|
||||||
|
width = 200,
|
||||||
|
height = 100,
|
||||||
|
callback = function(el, event)
|
||||||
|
if event.type == "drag" then
|
||||||
|
dragEventReceived = true
|
||||||
|
dragEvent = event
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Simulate mouse press
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(1, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
-- Move mouse while pressed (drag)
|
||||||
|
love.mouse.setPosition(160, 155)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
lu.assertTrue(dragEventReceived, "Drag event should be fired when mouse moves while pressed")
|
||||||
|
lu.assertNotNil(dragEvent, "Drag event object should exist")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 2: Drag event contains dx and dy fields
|
||||||
|
function TestDragEvent:test_drag_event_contains_delta_values()
|
||||||
|
local dragEvent = nil
|
||||||
|
|
||||||
|
local element = Gui.new({
|
||||||
|
x = 100,
|
||||||
|
y = 100,
|
||||||
|
width = 200,
|
||||||
|
height = 100,
|
||||||
|
callback = function(el, event)
|
||||||
|
if event.type == "drag" then
|
||||||
|
dragEvent = event
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Simulate mouse press at (150, 150)
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(1, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
-- Move mouse to (160, 155) - delta should be (10, 5)
|
||||||
|
love.mouse.setPosition(160, 155)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
lu.assertNotNil(dragEvent, "Drag event should be received")
|
||||||
|
lu.assertNotNil(dragEvent.dx, "Drag event should have dx field")
|
||||||
|
lu.assertNotNil(dragEvent.dy, "Drag event should have dy field")
|
||||||
|
lu.assertEquals(dragEvent.dx, 10, "dx should be 10 (160 - 150)")
|
||||||
|
lu.assertEquals(dragEvent.dy, 5, "dy should be 5 (155 - 150)")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 3: Drag event updates dx/dy as mouse continues to move
|
||||||
|
function TestDragEvent:test_drag_event_updates_delta_continuously()
|
||||||
|
local dragEvents = {}
|
||||||
|
|
||||||
|
local element = Gui.new({
|
||||||
|
x = 100,
|
||||||
|
y = 100,
|
||||||
|
width = 200,
|
||||||
|
height = 100,
|
||||||
|
callback = function(el, event)
|
||||||
|
if event.type == "drag" then
|
||||||
|
table.insert(dragEvents, { dx = event.dx, dy = event.dy })
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Press at (150, 150)
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(1, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
-- Move to (160, 155)
|
||||||
|
love.mouse.setPosition(160, 155)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
-- Move to (170, 160)
|
||||||
|
love.mouse.setPosition(170, 160)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
lu.assertEquals(#dragEvents, 2, "Should receive 2 drag events")
|
||||||
|
lu.assertEquals(dragEvents[1].dx, 10, "First drag dx should be 10")
|
||||||
|
lu.assertEquals(dragEvents[1].dy, 5, "First drag dy should be 5")
|
||||||
|
lu.assertEquals(dragEvents[2].dx, 20, "Second drag dx should be 20 (170 - 150)")
|
||||||
|
lu.assertEquals(dragEvents[2].dy, 10, "Second drag dy should be 10 (160 - 150)")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 4: No drag event when mouse doesn't move
|
||||||
|
function TestDragEvent:test_no_drag_event_when_mouse_stationary()
|
||||||
|
local dragEventCount = 0
|
||||||
|
|
||||||
|
local element = Gui.new({
|
||||||
|
x = 100,
|
||||||
|
y = 100,
|
||||||
|
width = 200,
|
||||||
|
height = 100,
|
||||||
|
callback = function(el, event)
|
||||||
|
if event.type == "drag" then
|
||||||
|
dragEventCount = dragEventCount + 1
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Press at (150, 150)
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(1, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
-- Update again without moving mouse
|
||||||
|
element:update(0.016)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
lu.assertEquals(dragEventCount, 0, "Should not receive drag events when mouse doesn't move")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 5: Drag tracking is cleaned up on release
|
||||||
|
function TestDragEvent:test_drag_tracking_cleaned_up_on_release()
|
||||||
|
local dragEvents = {}
|
||||||
|
|
||||||
|
local element = Gui.new({
|
||||||
|
x = 100,
|
||||||
|
y = 100,
|
||||||
|
width = 200,
|
||||||
|
height = 100,
|
||||||
|
callback = function(el, event)
|
||||||
|
if event.type == "drag" then
|
||||||
|
table.insert(dragEvents, { dx = event.dx, dy = event.dy })
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- First drag sequence
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(1, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
love.mouse.setPosition(160, 155)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
-- Release
|
||||||
|
love.mouse.setDown(1, false)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
-- Second drag sequence - should start fresh
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(1, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
love.mouse.setPosition(155, 152)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
lu.assertEquals(#dragEvents, 2, "Should receive 2 drag events total")
|
||||||
|
lu.assertEquals(dragEvents[1].dx, 10, "First drag dx should be 10")
|
||||||
|
lu.assertEquals(dragEvents[2].dx, 5, "Second drag dx should be 5 (new drag start)")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 6: Drag works with different mouse buttons
|
||||||
|
function TestDragEvent:test_drag_works_with_different_buttons()
|
||||||
|
local dragEvents = {}
|
||||||
|
|
||||||
|
local element = Gui.new({
|
||||||
|
x = 100,
|
||||||
|
y = 100,
|
||||||
|
width = 200,
|
||||||
|
height = 100,
|
||||||
|
callback = function(el, event)
|
||||||
|
if event.type == "drag" then
|
||||||
|
table.insert(dragEvents, { button = event.button, dx = event.dx })
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Right button drag
|
||||||
|
-- Make sure no other buttons are down
|
||||||
|
love.mouse.setDown(1, false)
|
||||||
|
love.mouse.setDown(3, false)
|
||||||
|
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(2, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
love.mouse.setPosition(160, 150)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
lu.assertEquals(#dragEvents, 1, "Should receive drag event for right button")
|
||||||
|
lu.assertEquals(dragEvents[1].button, 2, "Drag event should be for button 2")
|
||||||
|
lu.assertEquals(dragEvents[1].dx, 10, "Drag dx should be 10")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 7: Drag event contains correct mouse position
|
||||||
|
function TestDragEvent:test_drag_event_contains_mouse_position()
|
||||||
|
local dragEvent = nil
|
||||||
|
|
||||||
|
local element = Gui.new({
|
||||||
|
x = 100,
|
||||||
|
y = 100,
|
||||||
|
width = 200,
|
||||||
|
height = 100,
|
||||||
|
callback = function(el, event)
|
||||||
|
if event.type == "drag" then
|
||||||
|
dragEvent = event
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(1, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
love.mouse.setPosition(175, 165)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
lu.assertNotNil(dragEvent, "Drag event should be received")
|
||||||
|
lu.assertEquals(dragEvent.x, 175, "Drag event x should match current mouse x")
|
||||||
|
lu.assertEquals(dragEvent.y, 165, "Drag event y should match current mouse y")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Test 8: No drag event when mouse leaves element
|
||||||
|
function TestDragEvent:test_no_drag_when_mouse_leaves_element()
|
||||||
|
local dragEventCount = 0
|
||||||
|
|
||||||
|
local element = Gui.new({
|
||||||
|
x = 100,
|
||||||
|
y = 100,
|
||||||
|
width = 200,
|
||||||
|
height = 100,
|
||||||
|
callback = function(el, event)
|
||||||
|
if event.type == "drag" then
|
||||||
|
dragEventCount = dragEventCount + 1
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Press inside element
|
||||||
|
love.mouse.setPosition(150, 150)
|
||||||
|
love.mouse.setDown(1, true)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
-- Move outside element
|
||||||
|
love.mouse.setPosition(50, 50)
|
||||||
|
element:update(0.016)
|
||||||
|
|
||||||
|
lu.assertEquals(dragEventCount, 0, "Should not receive drag events when mouse leaves element")
|
||||||
|
end
|
||||||
|
|
||||||
|
lu.LuaUnit.run()
|
||||||
Reference in New Issue
Block a user