-
Notifications
You must be signed in to change notification settings - Fork 1
/
gestures.lua
307 lines (266 loc) · 9.14 KB
/
gestures.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
-- touchscreen and mouse gestures for mpv.
-- in order to use this, you have to set
-- to configure create a configuration under script-opts, following keys are respected
local OPTS = {
-- general
autostart = 0,
deadzone = 50,
sample_rate_ms = 48,
input_delay = mp.get_property_native("input-doubleclick-time"),
-- seeking / volume controller
seek_volume_button = "MOUSE_BTN0",
-- seeking
pixels_per_second = 10,
inertia_tick_ms = 16, -- lower values achieve smoother inertia at the cost of performance
inertia_lower_bound_pixels = 10,
-- volume
volume_modifier = 50,
-- speed
speed_enabled = 1,
speed_button = "MBTN_MID",
pps = 1000,
}
(require 'mp.options').read_options(OPTS)
local mp = require 'mp'
local msg = require 'mp.msg'
math.randomseed(mp.get_time()); math.random(); math.random(); math.random()
local function noop()
end
local function if_enabled(enable) return function(wrappee) if enable then
local wrap = wrappee();
return { start = function() wrap.start() end, stop = function() wrap.stop() end }
else
return { start = noop, stop = noop }
end end end
local function drag_handler(onDrag, onStart, onEnd, options)
if onStart == nil then onStart = noop end
if onEnd == nil then onEnd = noop end
if options == nil then options = {} end
if options.deadzone == nil then options.deadzone = false end
if options.deadzone_reset == nil then options.deadzone_reset = "auto" end
if options.tick_ms == nil then options.tick_ms = 48 end
if options.button == nil then options.button = "MOUSE_BTN0" end
if options.input_delay == nil then options.input_delay = mp.get_property_native("input-doubleclick-time") end
local mouse = "up"
local state = 0 -- 0: off, 1: drag
local startx, starty = -1
local startw, starth = -1;
local x, y = -1
local haste = false; -- whether there is a latency (for eg doubleclick) before acting on input, can be controlled externally via set_haste
local active_deadzone = false;
local ticker -- hoist timer
local drag_start_deferrer -- hoist timer
local function reset()
drag_start_deferrer:kill();
ticker:kill();
if options.deadzone_reset ~= "manual" then active_deadzone = false end
state = 0
startx, starty = -1
x, y = -1
end
ticker = mp.add_periodic_timer(options.tick_ms / 1000, function()
if mouse == "down" and state == 1 then -- in drag
local w, h = mp.get_osd_size();
if (w ~= startw or h ~= starth) then -- window size invalidated (ex fullscreen), bail
reset()
msg.trace("bailed")
return
end
x, y = mp.get_mouse_pos()
msg.trace("dragging")
local diffx, diffy = x - startx, y - starty
if not options.deadzone then
onDrag(diffx, diffy)
else
if not active_deadzone then
local abdiffx, abdiffy = math.abs(diffx), math.abs(diffy)
if options.deadzone > abdiffx and options.deadzone > abdiffy then -- both inside deadzone, no active
active_deadzone = false;
else
if abdiffy >= abdiffx then
active_deadzone = "y"
else
active_deadzone = "x"
end
end
end
if active_deadzone then -- a deadzone has been breached, act
if active_deadzone == "y" then
onDrag(false, diffy)
elseif active_deadzone == "x" then
onDrag(diffx, false)
else
msg.warn("unspecified active deadzone value")
end
end
end
end
end); ticker:kill() -- don't run unless initiated
local function drag_start()
onStart()
state = 1
ticker:resume()
msg.trace("start drag")
end
drag_start_deferrer = mp.add_timeout(options.input_delay / 1000, function()
drag_start()
end); drag_start_deferrer:kill();
local bindingname = "a-" .. math.random(1, 1000000) .. "-b";
local binding = function(t)
mouse = t.event
if mouse == "down" and state == 0 then -- start dragging
startx, starty = mp.get_mouse_pos()
startw, starth = mp.get_osd_size()
x, y = startx, starty
if haste then drag_start() else drag_start_deferrer:resume() end
elseif mouse == "up" then -- drag stopped
local ostate = state
reset()
if ostate == 1 then
msg.trace("end drag")
onEnd()
end
end
end
return { start = function()
mp.remove_key_binding(bindingname)
mp.add_forced_key_binding(options.button, bindingname, binding, {complex = true, repeatable = true})
end, stop = function()
mp.remove_key_binding(bindingname)
reset();
end, set_haste = function(h)
haste = h
end, reset_deadzone = function()
active_deadzone = false
end}
end
local function seek_n_volume()
local time
local inertia_start;
local x
local dx
local init_pos
local init_vol
local max_vol
local osd_height
local control_pos
local control_vol
local drag
local function speedup(x)
-- in dire need of refactoring, do not expose those as opts yet as it's too finnicky
local elapsed = (time - inertia_start)/10 + 1
local speedup = math.max(math.abs(x), 250) * (elapsed)
if x > 0 then return -speedup else return speedup end
end
local function set_pos()
local setpos = init_pos + x/OPTS.pixels_per_second
if setpos < 0 then setpos = 0 end
mp.command("seek " .. setpos .. " absolute exact")
end
local function digestX(newx)
if not control_pos then return end
local delta = mp.get_time() - time;
time = delta + time;
dx = (newx - x) / delta
x = newx
set_pos()
end
local function digestY(y)
if not control_vol then return end
y = -y
local vol = OPTS.volume_modifier*(y / (osd_height / 2))
mp.command("set volume " .. math.min(max_vol, math.max(0, init_vol + vol)))
end
local function calculX()
if not control_pos then return end
local delta = mp.get_time() - time;
time = delta + time;
dx = dx + speedup(dx)*delta
x = x + dx*delta
set_pos()
end
local inertia; inertia = mp.add_periodic_timer(OPTS.inertia_tick_ms / 1000, function()
msg.trace("on inertia", dx, x)
calculX()
if math.abs(dx) < OPTS.inertia_lower_bound_pixels then
msg.trace("end inertia")
inertia:kill()
drag.set_haste(false)
drag.reset_deadzone()
end
end); inertia:kill();
drag = drag_handler(function(newx, y)
msg.trace("on drag")
inertia:kill()
if newx ~= false then
control_pos = true
else
control_pos = false
end
if y ~= false then
control_vol = true
else
control_vol = false
end
digestX(newx)
digestY(y)
end, function()
msg.trace("start drag")
drag.set_haste(true)
inertia:kill()
init_pos = mp.get_property_native("time-pos")
init_vol = mp.get_property_native("volume")
max_vol = mp.get_property_native("volume-max")
local _w; w, osd_height = mp.get_osd_size()
time = mp.get_time()
x = 0
dx = 0
end, function()
msg.trace("end drag / start inertia")
inertia_start = mp.get_time()
inertia:resume()
end, { deadzone = OPTS.deadzone, deadzone_reset = "manual",
button = OPTS.seek_volume_button, tick_ms = OPTS.sample_rate_ms, input_delay = OPTS.input_delay })
return {
start = function() drag.start() end,
stop = function() drag.stop() end
}
end
local function speed()
local init_speed
local drag = drag_handler(function(x, y)
if y ~= false then
mp.command("set speed 1.0")
return
end
if x ~= false then
mp.command("set speed " .. math.max(0.01, init_speed + x/OPTS.pps))
return
end
end, function()
init_speed = mp.get_property_native("speed")
end, nil, { button = OPTS.speed_button, deadzone = OPTS.deadzone })
return {
start = function() drag.start() end,
stop = function() drag.stop() end
}
end
local ctl1 = seek_n_volume();
local ctl2 = if_enabled(OPTS.speed_enabled)(speed);
local state = OPTS.autostart;
if state then
ctl1.start()
ctl2.start()
end
function toggle_gestures()
if state == 1 then
state = 0
ctl1.stop()
ctl2.stop()
else
state = 1
ctl1.start()
ctl2.start()
end
end
mp.add_key_binding(nil, "toggle", toggle_gestures)