Last active
December 9, 2021 21:43
-
-
Save dmitrii-eremin/3bfe12835cc4b8a7c6041dde0b654e24 to your computer and use it in GitHub Desktop.
Codea gesture manager (pinch, move)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
# 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