Skip to content

Instantly share code, notes, and snippets.

@dmitrii-eremin
Last active December 9, 2021 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmitrii-eremin/3bfe12835cc4b8a7c6041dde0b654e24 to your computer and use it in GitHub Desktop.
Save dmitrii-eremin/3bfe12835cc4b8a7c6041dde0b654e24 to your computer and use it in GitHub Desktop.
Codea gesture manager (pinch, move)
--[[
# Gesture manager class
** version 1.2 **
This class is used to perform graphic transformations such as pinch and move.
Feel free to modify this class as you need or as you want.
# Usage
```
function setup() gestures = GestureManager() end
function touched(touch) gestures:touched(touch) end
function draw()
gestures:draw(function()
-- draw your world
end)
end
```
]]
GestureManager = class()
local CLICK_TRESHOLD = 12
local function create_vec2_from_vec3(v3)
return vec2(v3.x, v3.y)
end
local function get_distance_vec3(touch1, touch2)
local touch1_2d = create_vec2_from_vec3(touch1)
local touch2_2d = create_vec2_from_vec3(touch2)
return touch1_2d:dist(touch2_2d)
end
function GestureManager:init(on_move, on_click, callback_context)
self.original_touch1 = vec3(0, 0, 0)
self.touch1 = vec3(0, 0, 0)
self.touch2 = vec3(0, 0, 0)
self.scale = 1
self.translation = vec2(0, 0)
self.on_move = on_move or function()
end
self.on_click = on_click or function()
end
self.callback_context = callback_context
self.limits = {}
end
local function apply_limits(self)
local left, top, width, height = self:get_rect()
if self.limits.right and self.limits.bottom then
local scale_x = WIDTH / self.limits.right
local scale_y = HEIGHT / self.limits.bottom
local max_scale = math.max(scale_x, scale_y)
self.scale = math.max(self.scale, max_scale)
print(self.scale, scale_x, scale_y)
end
if self.limits.left and left < self.limits.left then
self.translation.x = self.limits.left
end
if self.limits.top and top < self.limits.top then
self.translation.y = self.limits.top
end
if self.limits.right and left + width > self.limits.right then
self.translation.x = WIDTH / self.scale - self.limits.right
end
if self.limits.bottom and top + height > self.limits.bottom then
self.translation.y = HEIGHT / self.scale - self.limits.bottom
end
end
function GestureManager:set_limits(left, top, right, bottom)
self.limits = {left = left, top = top, right = right, bottom = bottom}
end
function GestureManager:get_translation()
return self.translation.x, self.translation.y
end
function GestureManager:get_scale()
return self.scale
end
function GestureManager:set_scale(new_scale)
assert(type(new_scale) == "number")
local old_width = WIDTH / self.scale
local new_width = WIDTH / new_scale
local width_diff = new_width - old_width
self.translation.x = self.translation.x / WIDTH * width_diff
local old_height = HEIGHT / self.scale
local new_height = HEIGHT / new_scale
local height_diff = new_height - old_height
self.translation.y = self.translation.y / HEIGHT * height_diff
self.scale = new_scale
apply_limits(self)
self.on_move(self.callback_context, self:get_rect())
end
function GestureManager:set_center(x, y)
if type(x) == "userdata" and type(x.x) == "number" and type(x.y) == "number" and y == nil then
x, y = x.x, x.y
end
assert(type(x) == "number" and type(y) == "number")
self.translation.x = -x + WIDTH / (2 * self.scale)
self.translation.y = -y + HEIGHT / (2 * self.scale)
apply_limits(self)
self.on_move(self.callback_context, self:get_rect())
end
function GestureManager:screen_to_world(x, y)
x = x / self.scale - self.translation.x
y = y / self.scale - self.translation.y
return x, y
end
function GestureManager:world_to_screen(x, y)
x = (x + self.translation.x) * self.scale
y = (y + self.translation.y) * self.scale
return x, y
end
function GestureManager:apply()
scale(self:get_scale())
translate(self:get_translation())
end
function GestureManager:get_rect()
local left, bottom = self:screen_to_world(0, 0)
local right, top = self:screen_to_world(WIDTH, HEIGHT)
local width, height = right - left, top - bottom
return left, bottom, width, height
end
function GestureManager:draw(fn)
pushMatrix()
self:apply()
fn(self:get_rect())
popMatrix()
end
function GestureManager:touched(touch)
if touch.state == BEGAN then
local touch_pos = vec3(touch.x, touch.y, touch.id)
if self.touch1.z == 0 then
self.original_touch1 = vec3(touch.x, touch.y, touch.id)
self.touch1 = touch_pos
elseif self.touch2.z == 0 then
self.touch2 = touch_pos
end
elseif touch.state == CHANGED then
local old_touch1 = self.touch1
local old_touch2 = self.touch2
local touch_pos = vec3(touch.x, touch.y, touch.id)
if self.touch1.z == touch.id then
self.touch1 = touch_pos
elseif self.touch2.z == touch.id then
self.touch2 = touch_pos
end
if self.touch1.z ~= 0 and self.touch2.z == 0 then
self.translation.x = self.translation.x + (self.touch1.x - old_touch1.x) / self.scale
self.translation.y = self.translation.y + (self.touch1.y - old_touch1.y) / self.scale
self.on_move(self.callback_context, self:get_rect())
elseif self.touch1.z ~= 0 and self.touch2.z ~= 0 then
local old_center = vec2((old_touch1.x + old_touch2.x) / 2, (old_touch1.y + old_touch2.y) / 2)
local new_center = vec2((self.touch1.x + self.touch2.x) / 2, (self.touch1.y + self.touch2.y) / 2)
local old_distance = get_distance_vec3(old_touch1, old_touch2)
local distance = get_distance_vec3(self.touch1, self.touch2)
local scale_multiplier = distance / old_distance
local new_scale = self.scale * scale_multiplier
local old_width = WIDTH / self.scale
local new_width = WIDTH / new_scale
local width_diff = new_width - old_width
self.translation.x = self.translation.x + new_center.x / WIDTH * width_diff
local old_height = HEIGHT / self.scale
local new_height = HEIGHT / new_scale
local height_diff = new_height - old_height
self.translation.y = self.translation.y + new_center.y / HEIGHT * height_diff
self.scale = new_scale
self.translation = self.translation + (new_center - old_center) / self.scale
self.on_move(self.callback_context, self:get_rect())
end
apply_limits(self)
elseif touch.state == ENDED then
if self.touch1.z == touch.id and self.touch2.z == 0 then
local distance = get_distance_vec3(self.touch1, self.original_touch1)
if distance < CLICK_TRESHOLD then
local screen_x, screen_y = touch.x, touch.y
local world_x, world_y = self:screen_to_world(screen_x, screen_y)
self.on_click(self.callback_context, screen_x, screen_y, world_x, world_y)
end
end
if self.touch1.z == touch.id then
self.touch1 = vec3(0, 0, 0)
self.original_touch1 = vec3(0, 0, 0)
elseif self.touch2.z == touch.id then
self.touch2 = vec3(0, 0, 0)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment