Skip to content

Commit 157a993

Browse files
Implements components.Button()
1 parent 706a12e commit 157a993

1 file changed

Lines changed: 184 additions & 2 deletions

File tree

res/controllers/midi-components-0.1.js

Lines changed: 184 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@
8787
* By default, sets the Mixxx control {@link value} specified by {@link Component#inKey} in the group
8888
* {@link Component#group}.
8989
*
90-
* @callback InputCallback
9190
* @param {number} channel The MIDI channel
9291
* @param {number} control The MIDI control (MIDI data 2)
9392
* @param {number} value The MIDI value (MIDI byte 3)
@@ -213,7 +212,190 @@
213212
CONTROL: 2
214213
});
215214

215+
class InputTimeWindow extends Function {
216+
/**
217+
* @param {number} windowDuration In milliseconds, duration for which to accumulate events
218+
* @param {string[]} eventNames List of event names to accumulate
219+
* @param {function(Object.<string, number>):void} dispatch The function to which to dispatch to list of events
220+
* and the number of their occurrence during the `windowDuration` period.
221+
*/
222+
constructor(windowDuration, eventNames, dispatch) {
223+
super();
224+
/** @type {function(Object.<string, number>):void}} */
225+
this.__dispatch = dispatch;
226+
this.__windowDuration = windowDuration;
227+
this.__eventNames = eventNames;
228+
this.__reset();
229+
this.__timer = script.TimerPromise.resolve(this.__events);
230+
231+
return Object.setPrototypeOf(this.__onEvent.bind(this), InputTimeWindow.prototype);
232+
}
233+
234+
__onEvent(evt) {
235+
if (evt === undefined) {
236+
this.__timer.cancel();
237+
238+
} else if (evt in this.__events) {
239+
if (this.__timer.settled) {
240+
this.__timer = new script.TimerPromise(this.__windowDuration, true).then(() => {
241+
this.__dispatch(this.__events);
242+
this.__reset();
243+
});
244+
}
245+
this.__events[evt]++;
246+
}
247+
}
248+
249+
__reset() {
250+
this.__events = this.__eventNames.reduce((obj, name) => {
251+
obj[name] = 0;
252+
return obj;
253+
}, {});
254+
}
255+
}
256+
257+
/** @typedef {function():void} ButtonActionCallback */
258+
259+
/**
260+
* @callback ButtonEventCallback
261+
* @param {Button.Events} event
262+
*/
263+
264+
/**
265+
* @typedef {ComponentOpts} ButtonOpts
266+
* @property {Button.Types} [type=Button.Types.PUSH]
267+
* @property {number} [on=127]
268+
* @property {number} [off=0]
269+
* @property {number} [longPressTimeout=275] Time millisecondsto distinguish a short press
270+
* from a long press. It is recommended to refer to it (as this.longPressTimeout)
271+
* in any Buttons that act differently with short and long presses
272+
* to keep the timeouts uniform
273+
* @property {ButtonEventCallback} [onEvent=undefined]
274+
* @property {ButtonActionCallback} [onPress=undefined]
275+
* @property {ButtonActionCallback} [onLongPress=undefined]
276+
* @property {ButtonActionCallback} [onDoublePress=undefined]
277+
*/
278+
class Button extends Component {
279+
/** @param {ButtonOpts} opts */
280+
constructor({
281+
type = Button.Types.PUSH,
282+
on = 127,
283+
off = 0,
284+
longPressTimeout = 275,
285+
onEvent = undefined,
286+
onPress = undefined,
287+
onLongPress = undefined,
288+
onDoublePress = undefined
289+
} = {}) {
290+
super(arguments[0]);
291+
292+
this.type = type;
293+
this.on = on;
294+
this.off = off;
295+
this.longPressTimeout = longPressTimeout;
296+
297+
// Replacing default methods by user specified ones
298+
if (onEvent instanceof Function) {
299+
this.onEvent = onEvent.bind(this);
300+
}
301+
302+
if (onPress instanceof Function) {
303+
this.onPress = onPress.bind(this);
304+
}
305+
306+
if (onLongPress instanceof Function) {
307+
this.onLongPress = onLongPress.bind(this);
308+
}
309+
310+
if (onDoublePress instanceof Function) {
311+
this.onDoublePress = onDoublePress.bind(this);
312+
}
313+
314+
this.__btnEvts = new InputTimeWindow(this.longPressTimeout, Object.values(Button.Events), evts => {
315+
if (evts[Button.Events.PRESS] === 1 && evts[Button.Events.RELEASE] === 1) {
316+
this.onPress();
317+
} else if (evts[Button.Events.PRESS] === 1 && evts[Button.Events.RELEASE] === 0) {
318+
this.onLongPress();
319+
} else if (evts[Button.Events.PRESS] === 2) {
320+
this.onDoublePress();
321+
} else { // Unbound action
322+
console.debug(`Unbound action to ${evts}`);
323+
}
324+
});
325+
}
326+
327+
// eslint-disable-next-line no-unused-vars
328+
isPress(channel, control, value, status) {
329+
return value > 0;
330+
}
331+
332+
// eslint-disable-next-line no-unused-vars
333+
input(channel, control, value, status, group) {
334+
// Skip checking double and long press events when no callback was provided
335+
const event = (
336+
this.isPress(channel, control, value, status)
337+
? Button.Events.PRESS
338+
: Button.Events.RELEASE
339+
);
340+
341+
this.onEvent(event);
342+
343+
if (this.onLongPress.__notImplemented__ !== true && !this.onDoublePress.__notImplemented__) {
344+
return this.onPress();
345+
}
346+
347+
this.__btnEvts(event);
348+
}
349+
350+
/** @param {Button.Events} event A dictionary of events and their occurrence number */
351+
// eslint-disable-next-line no-unused-vars
352+
onEvent(event) { /* Does nothing */ }
353+
354+
onPress() {
355+
if (this.type === Button.Types.TOGGLE) {
356+
this.inToggle();
357+
} else {
358+
this.inSetValue(true);
359+
}
360+
}
361+
362+
onLongPress() { /* Does nothing */ }
363+
364+
onDoublePress() { /* Does nothing */ }
365+
366+
outValueScale(value) {
367+
return (value > 0) ? this.on : this.off;
368+
}
369+
370+
shutdown() {
371+
this.send(this.off);
372+
}
373+
}
374+
375+
/*
376+
* Flags to indicate onLongPress and onDoublePress where not overridden
377+
* and input can skip checking double and long press
378+
*/
379+
Button.prototype.onLongPress.__notImplemented__ = true;
380+
Button.prototype.onDoublePress.__notImplemented__ = true;
381+
382+
/**
383+
* @readonly
384+
* @enum {number}
385+
*/
386+
Button.Types = Object.freeze({
387+
PUSH: 0,
388+
TOGGLE: 1,
389+
POWER_WINDOW: 2
390+
});
391+
392+
Button.Events = Object.freeze({
393+
PRESS: "press",
394+
RELEASE: "release"
395+
});
396+
216397
global.components = Object.freeze({
217-
Component
398+
Component,
399+
Button
218400
});
219401
}(this));

0 commit comments

Comments
 (0)