You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1119 lines
33 KiB
1119 lines
33 KiB
4 months ago
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||
|
/*
|
||
|
This js file is meant to be used in Cycling'74 Max for the [jsui] object.
|
||
|
It is designed to mimic [preset], but better.
|
||
|
|
||
|
Copyright (C) 2024 Théophile Clet <contact@tflcl.xyz> - https://tflcl.xyz.
|
||
|
|
||
|
This program is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||
|
*/
|
||
|
|
||
|
autowatch = 0;
|
||
|
// When developping, autowatch = 1 isn't enough. You also need to manually call the loadbang function, and then re-binding the pattrstorage.
|
||
|
// A "loadbang, pattrstorage test" message does the trick.
|
||
|
|
||
|
inlets = 1
|
||
|
setinletassist(0, "Connect to the linked pattrstorage");
|
||
|
|
||
|
outlets = 4;
|
||
|
setoutletassist(0, "Outputs last triggered action");
|
||
|
setoutletassist(1, "Connect to umenu to list stored presets");
|
||
|
setoutletassist(2, "Connect to textedit to edit preset name");
|
||
|
setoutletassist(3, "Connect to toggle to show active presets lock state")
|
||
|
|
||
|
mgraphics.init();
|
||
|
mgraphics.relative_coords = 0;
|
||
|
mgraphics.autofill = 0;
|
||
|
|
||
|
// LOOK
|
||
|
var slot_size = 20;
|
||
|
var slot_round = 0;
|
||
|
|
||
|
var margin = 4;
|
||
|
var spacing = 4;
|
||
|
|
||
|
var font_size = 14;
|
||
|
var font_name = "Arial";
|
||
|
|
||
|
var background_color = [0.2, 0.2, 0.2, 1];
|
||
|
var empty_slot_color = [0.349, 0.349, 0.349, 1];
|
||
|
var active_slot_color = [0.808, 0.898, 0.910, 1];
|
||
|
var stored_slot_color = [0.502, 0.502, 0.502, 1];
|
||
|
var interp_slot_color = [1.0, 1.0, 1.0, 0.8];
|
||
|
var text_bg_color = [1,1,1, 0.5];
|
||
|
var text_color = [0.129, 0.129, 0.129, 1];
|
||
|
|
||
|
// FEEL
|
||
|
var layout = 1;
|
||
|
var display_interp = 1; // Enable/disable the UI feedback when interpolating between presets
|
||
|
var ignore_slot_zero = 1; // Makes previous_active_slot and interpolation display to ignore slot 0. Can be usefull when using slot 0 as a temporary step for interpolation.
|
||
|
var auto_writeagain = 0; // When enabled, will send a "writeagain" to pattrstorage any time a preset is stored/deleted/moved/renamed/(un)locked
|
||
|
var menu_number_only = 0; // Populates the umenu connected to 2nd outlet with stored preset number only, instead of number and name
|
||
|
var scrollable = 1; // Defines weither the object can be scrolled or not
|
||
|
var min_rows = 50; // Minimum number of rows to display is scrollable is enabled
|
||
|
|
||
|
// (WORK)
|
||
|
var pattrstorage_name, pattrstorage_obj = null;
|
||
|
|
||
|
var columns, rows = 0;
|
||
|
var slots = [];
|
||
|
var slots_highest = 0; // Highest filled preset slot number
|
||
|
var slots_count_display = 0;
|
||
|
var filled_slots = [];
|
||
|
|
||
|
var active_slot = 0;
|
||
|
var previous_active_slot = 0;
|
||
|
var previous_target = 0;
|
||
|
|
||
|
var ui_width = box.rect[2] - box.rect[0];
|
||
|
var ui_height = box.rect[3] - box.rect[1];
|
||
|
var bg_width, bg_height = 0;
|
||
|
|
||
|
var mg = new MGraphics(ui_width, ui_height);
|
||
|
var base_drawing;
|
||
|
|
||
|
var half_slot_size, half_margin, half_spacing;
|
||
|
var last_x, last_y, last_hovered = -1;
|
||
|
var y_offset = 0; // handle scrolling
|
||
|
var shift_hold, option_hold = 0;
|
||
|
var is_interpolating = 0;
|
||
|
var is_dragging = 0; // Drag flag
|
||
|
var drag_slot = -1; // Stores the slot that's being dragged
|
||
|
|
||
|
if (jsarguments.length>1) {
|
||
|
pattrstorage_name = jsarguments[1];
|
||
|
}
|
||
|
|
||
|
function loadbang() {
|
||
|
outlet(2, "set");
|
||
|
find_pattrstorage(pattrstorage_name);
|
||
|
calc_rows_columns();
|
||
|
}
|
||
|
|
||
|
// loadbang();
|
||
|
|
||
|
function calc_rows_columns() {
|
||
|
half_margin = margin / 2;
|
||
|
half_spacing = spacing / 2;
|
||
|
half_slot_size = slot_size / 2;
|
||
|
|
||
|
slots[0] = [0, 0, 0, 0, "(tmp)", 0, -1]; // Slot 0 is valid, but not represented in the GUI (and never saved by pattrstorage)
|
||
|
|
||
|
if (layout == 0) {
|
||
|
columns = Math.floor((ui_width - margin + spacing) / (slot_size + spacing));
|
||
|
rows = Math.floor((ui_height - margin + spacing) / (slot_size + spacing));
|
||
|
slots_count_display = columns * rows;
|
||
|
} else {
|
||
|
columns = 1;
|
||
|
rows = Math.floor((ui_height - margin + spacing) / (slot_size + spacing));
|
||
|
if (scrollable) {
|
||
|
rows = Math.max(rows, Math.max(min_rows, slots_highest));
|
||
|
}
|
||
|
slots_count_display = columns * rows;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < rows; i++) {
|
||
|
var top = margin + i * (spacing+slot_size);
|
||
|
var bottom = top + slot_size;
|
||
|
for (var j = 0; j < columns; j++) {
|
||
|
var left = margin + j * (spacing+slot_size);
|
||
|
var right = left + slot_size;
|
||
|
var cur = 1 + i * columns + j;
|
||
|
var prev_name = null;
|
||
|
var prev_lock = 0;
|
||
|
var prev_interp = -1;
|
||
|
if (typeof slots[cur] !== 'undefined') {
|
||
|
prev_name = slots[cur][4];
|
||
|
prev_lock = slots[cur][5];
|
||
|
prev_interp = slots[cur][6];
|
||
|
}
|
||
|
slots[cur] = [left, top, right, bottom, prev_name, prev_lock, prev_interp];
|
||
|
//0: left position
|
||
|
//1: top position
|
||
|
//2: right position
|
||
|
//3: bottom position
|
||
|
//4: name, null if nothing stored on that slot
|
||
|
//5: lock state
|
||
|
//6: is being interpolated (0 or 1)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
if (slots_count_display < slots_highest) {
|
||
|
for (var i = slots_count_display + 1; i <= slots_highest; i++) {
|
||
|
slots[i] = [0, 0, 0, 0, null, 0, -1];
|
||
|
}
|
||
|
}
|
||
|
// outlet(0, "init");
|
||
|
paint_base();
|
||
|
}
|
||
|
calc_rows_columns.local = 1;
|
||
|
|
||
|
function draw_slot(id, scale, cont) {
|
||
|
scale = typeof cont !== 'undefined' ? scale : 1; // Sets scale to 1 by default if not passed as argument
|
||
|
cont = typeof cont !== 'undefined' ? cont : mgraphics; // Sets drawing context to mgraphics by default if not passed as argument
|
||
|
|
||
|
var offset = slot_size * (1 - scale);
|
||
|
|
||
|
draw_slot_bubble(slots[id][0] + offset, slots[id][1] + offset, slot_size * scale, slot_size * scale, cont);
|
||
|
cont.fill();
|
||
|
|
||
|
if (layout == 1) {
|
||
|
// // Slot number
|
||
|
// var nb = i.toString();
|
||
|
// var nb_dim = text_measure(nb);
|
||
|
|
||
|
// var nb_pos_x = slots[i][0] + (slot_size - nb_dim[0]) / 2;
|
||
|
// var nb_pos_y = slots[i][1] + (slot_size - spacing + nb_dim[1]) / 2 ;
|
||
|
|
||
|
// set_source_rgba(text_color);
|
||
|
// move_to(nb_pos_x, nb_pos_y);
|
||
|
// show_text(nb);
|
||
|
|
||
|
// slot text background
|
||
|
var bg_txt_pos_x = margin + slot_size + spacing;
|
||
|
var bg_txt_pos_y = slots[id][1];
|
||
|
var bg_txt_dim_w = ui_width - (2*margin + slot_size + spacing);
|
||
|
var bg_txt_dim_h = slot_size;
|
||
|
|
||
|
if (slots[id][4] != null) {
|
||
|
cont.set_source_rgba(stored_slot_color);
|
||
|
} else {
|
||
|
cont.set_source_rgba(empty_slot_color);
|
||
|
}
|
||
|
cont.rectangle_rounded(bg_txt_pos_x, bg_txt_pos_y, bg_txt_dim_w, bg_txt_dim_h, 4, 4);
|
||
|
cont.fill();
|
||
|
|
||
|
|
||
|
// slot name
|
||
|
if (1) {
|
||
|
|
||
|
// if (slots[id][4] != null) {
|
||
|
var text = id;
|
||
|
// If slot is locked, add brackets around its number
|
||
|
if (slots[id][5] == 1) {
|
||
|
text = '[' + text + ']';
|
||
|
}
|
||
|
// If slot has a name, append it to the preset name
|
||
|
if (slots[id][4] != null) {
|
||
|
text += ': ' + slots[id][4];
|
||
|
}
|
||
|
|
||
|
text = text.toString();
|
||
|
var text_dim = cont.text_measure(text);
|
||
|
|
||
|
var txt_pos_x = margin + slot_size + 2 * spacing;
|
||
|
var txt_pos_y = bg_txt_pos_y + (bg_txt_dim_h - spacing + text_dim[1]) / 2 ;
|
||
|
|
||
|
cont.set_source_rgba(text_color);
|
||
|
cont.move_to(txt_pos_x, txt_pos_y);
|
||
|
cont.show_text(text.toString());
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
draw_slot.local = 1;
|
||
|
|
||
|
function draw_slot_bubble (x, y, w, h, cont) {
|
||
|
cont = typeof cont !== 'undefined' ? cont : mgraphics;
|
||
|
|
||
|
// I assume rectange is faster to draw than rectangle_rounded. Btw rectangle_rounded is wacky when showing interpolation. Maybe *interp on the first slot_round could solve this?
|
||
|
if (slot_round) {
|
||
|
cont.rectangle_rounded(x, y, w, h, slot_round, slot_round);
|
||
|
} else {
|
||
|
cont.rectangle(x, y, w, h);
|
||
|
}
|
||
|
}
|
||
|
draw_slot_bubble.local = 1;
|
||
|
|
||
|
function paint_base() {
|
||
|
// We draw all slots (empty and stored ones) so we don't have to for every redraw
|
||
|
|
||
|
// Background
|
||
|
bg_width = layout == 0 ? columns * (slot_size + spacing) - spacing + 2 * margin : ui_width;
|
||
|
bg_height = rows * (slot_size + spacing) - spacing + 2 * margin;
|
||
|
mg = new MGraphics(ui_width, bg_height);
|
||
|
with(mg) {
|
||
|
set_source_rgba(background_color);
|
||
|
rectangle(0, 0, bg_width, bg_height);
|
||
|
fill();
|
||
|
|
||
|
select_font_face(font_name);
|
||
|
set_font_size(font_size);
|
||
|
|
||
|
// All slots
|
||
|
for (var i = 1; i <= slots_count_display; i++) {
|
||
|
if (slots[i][4] != null) {
|
||
|
set_source_rgba(stored_slot_color);
|
||
|
} else {
|
||
|
set_source_rgba(empty_slot_color);
|
||
|
}
|
||
|
draw_slot(i, 1, mg);
|
||
|
// fill();
|
||
|
}
|
||
|
|
||
|
// if (layout == 0) {
|
||
|
// for (var i = 1; i <= slots_count_display; i++) {
|
||
|
// if (slots[i][4] != null) {
|
||
|
// set_source_rgba(stored_slot_color);
|
||
|
// } else {
|
||
|
// set_source_rgba(empty_slot_color);
|
||
|
// }
|
||
|
// draw_slot_bubble(i, 1, mg);
|
||
|
// fill();
|
||
|
// }
|
||
|
// } else {
|
||
|
|
||
|
// for (var i = 1; i <= slots_count_display; i++) {
|
||
|
// if (slots[i][4] != null) {
|
||
|
// set_source_rgba(stored_slot_color);
|
||
|
// } else {
|
||
|
// set_source_rgba(empty_slot_color);
|
||
|
// }
|
||
|
// draw_slot(i, 1, mg);
|
||
|
// // fill();
|
||
|
|
||
|
|
||
|
// }
|
||
|
// }
|
||
|
}
|
||
|
update_umenu();
|
||
|
base_drawing = new Image(mg);
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
paint_base.local = 1;
|
||
|
|
||
|
function paint()
|
||
|
{
|
||
|
// post("redraw\n");
|
||
|
with (mgraphics) {
|
||
|
translate(0, y_offset);
|
||
|
// Draw the base, which includes empty and filled slots
|
||
|
image_surface_draw(base_drawing);
|
||
|
|
||
|
set_line_width(1);
|
||
|
|
||
|
// Active slot
|
||
|
if (active_slot > 0 && active_slot <= slots_count_display) {
|
||
|
set_source_rgba(active_slot_color);
|
||
|
draw_slot_bubble(slots[active_slot][0], slots[active_slot][1], slot_size, slot_size);
|
||
|
fill();
|
||
|
}
|
||
|
|
||
|
// Previous active slot
|
||
|
if (previous_active_slot > 0 && previous_active_slot <= slots_count_display) {
|
||
|
set_source_rgba(active_slot_color);
|
||
|
draw_slot_bubble(slots[previous_active_slot][0], slots[previous_active_slot][1], slot_size, slot_size);
|
||
|
stroke();
|
||
|
}
|
||
|
|
||
|
// Interpolated slots
|
||
|
if (display_interp && is_interpolating) {
|
||
|
|
||
|
for (var i = 1; i <= slots_count_display; i++) {
|
||
|
var interp = slots[i][6];
|
||
|
if (interp >= 0) {
|
||
|
set_source_rgba(interp_slot_color);
|
||
|
draw_slot_bubble(slots[i][0], slots[i][1], slot_size, slot_size);
|
||
|
stroke();
|
||
|
draw_slot_bubble(slots[i][0], slots[i][1] + slot_size * (1-interp), slot_size, slot_size * interp);
|
||
|
fill();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Hovered slot
|
||
|
if (last_hovered > -1) {
|
||
|
if (shift_hold) {
|
||
|
if (option_hold) {
|
||
|
// About to delete
|
||
|
set_source_rgba(empty_slot_color[0], empty_slot_color[1], empty_slot_color[2], 0.8);
|
||
|
draw_slot_bubble(slots[last_hovered][0] + 1, slots[last_hovered][1] + 1, slot_size-2, slot_size-2);
|
||
|
fill();
|
||
|
} else {
|
||
|
// About to store
|
||
|
set_source_rgba(active_slot_color[0], active_slot_color[1], active_slot_color[2], 0.7);
|
||
|
draw_slot_bubble(slots[last_hovered][0] + 1, slots[last_hovered][1] + 1, slot_size-2, slot_size-2);
|
||
|
fill();
|
||
|
}
|
||
|
}
|
||
|
// Slot border
|
||
|
set_source_rgba(1, 1, 1, 0.8);
|
||
|
draw_slot_bubble(slots[last_hovered][0], slots[last_hovered][1], slot_size, slot_size);
|
||
|
stroke();
|
||
|
|
||
|
if (layout == 0) {
|
||
|
//Text (slot number and name)
|
||
|
var text = last_hovered;
|
||
|
if (slots[last_hovered][5] == 1) {
|
||
|
text = '[' + text + ']';
|
||
|
}
|
||
|
|
||
|
if (slots[last_hovered][4] != null) {
|
||
|
text += ': ' + slots[last_hovered][4];
|
||
|
}
|
||
|
text = text.toString();
|
||
|
select_font_face(font_name);
|
||
|
set_font_size(font_size);
|
||
|
var text_dim = text_measure(text);
|
||
|
// If the text is too big or a slot is being dragged, display the text on top of the next slot.
|
||
|
// Otherwise, it gets displayed on the hovered slot.
|
||
|
|
||
|
var bg_txt_pos_x = text_dim[0] > slot_size || is_dragging ? slots[last_hovered][0] + slot_size + half_spacing : slots[last_hovered][0] - half_spacing;
|
||
|
var bg_txt_pos_y = text_dim[1] > slot_size || is_dragging ? slots[last_hovered][1] + (slot_size - text_dim[1]) / 2 - half_spacing : slots[last_hovered][1] - half_spacing;
|
||
|
var bg_txt_dim_w = text_dim[0] > slot_size ? text_dim[0] + spacing : slot_size + spacing;
|
||
|
var bg_txt_dim_h = text_dim[1] > slot_size ? text_dim[1] + spacing : slot_size + spacing;
|
||
|
|
||
|
// If there is not enough place, text is displayed on the left
|
||
|
if (bg_txt_pos_x + bg_txt_dim_w > ui_width) {
|
||
|
bg_txt_pos_x = slots[last_hovered][0] - half_spacing - bg_txt_dim_w;
|
||
|
}
|
||
|
|
||
|
var txt_pos_x = text_dim[0] > slot_size ? bg_txt_pos_x + half_spacing : bg_txt_pos_x + (bg_txt_dim_w / 2) - (text_dim[0]/2);
|
||
|
var txt_pos_y = bg_txt_pos_y + (bg_txt_dim_h - spacing + text_dim[1]) / 2 ;
|
||
|
|
||
|
// Bubble background
|
||
|
set_source_rgba(text_bg_color);
|
||
|
rectangle_rounded(bg_txt_pos_x, bg_txt_pos_y, bg_txt_dim_w, bg_txt_dim_h, 4, 4);
|
||
|
fill();
|
||
|
|
||
|
// Buble text
|
||
|
set_source_rgba(text_color);
|
||
|
move_to(txt_pos_x, txt_pos_y);
|
||
|
show_text(text.toString());
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Drag slot
|
||
|
if (is_dragging) {
|
||
|
translate(last_x, last_y );
|
||
|
rotate(0.15);
|
||
|
scale(1.1, 1.1);
|
||
|
// scale(3, 3);
|
||
|
|
||
|
// Shadow
|
||
|
set_source_rgba(0, 0, 0, 0.15);
|
||
|
for (var i = 0; i<4; i++) {
|
||
|
draw_slot_bubble( i*0.4 + 1-slot_size/2, i*0.4 + 1-slot_size/2, slot_size + i*0.8, slot_size+i*0.8);
|
||
|
fill();
|
||
|
}
|
||
|
draw_slot_bubble( 2-slot_size/2, 2-slot_size/2, slot_size, slot_size);
|
||
|
fill();
|
||
|
set_source_rgba(stored_slot_color);
|
||
|
draw_slot_bubble( -slot_size/2, -slot_size/2, slot_size, slot_size);
|
||
|
fill();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
paint.local = 1;
|
||
|
|
||
|
function anything() {
|
||
|
// Here just to avoid error messages in case pattrstorage sends unhandled message, like when using getstoredvalue, getsubscriptionlist, getalias, etc.
|
||
|
|
||
|
// Handle the "delete" messages here because it is a reserevd word in js and cannot be used as a function name.
|
||
|
if (messagename == "delete") {
|
||
|
var v = arrayfromargs(arguments)[0];
|
||
|
v = Math.floor(v);
|
||
|
if (v >= 0) {
|
||
|
if (slots[v][5] > 0) {
|
||
|
error('cannot delete locked slot ' + v + '\n');
|
||
|
} else {
|
||
|
slots[v][4] = null;
|
||
|
slots[v][6] = -1;
|
||
|
if (active_slot == v) {
|
||
|
active_slot = 0;
|
||
|
}
|
||
|
|
||
|
// to_pattrstorage("getslotname", v);
|
||
|
to_pattrstorage("delete", v);
|
||
|
to_pattrstorage("getslotlist");
|
||
|
paint_base();
|
||
|
set_active_slot(active_slot);
|
||
|
outlet(0, "delete", v);
|
||
|
trigger_writeagain();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function bang()
|
||
|
{
|
||
|
to_pattrstorage("recall", active_slot);
|
||
|
}
|
||
|
|
||
|
function msg_int(v) {
|
||
|
to_pattrstorage("recall", v);
|
||
|
}
|
||
|
|
||
|
function msg_float(v)
|
||
|
{
|
||
|
var s = Math.floor(v);
|
||
|
var i = v % 1;
|
||
|
to_pattrstorage("recall", s, s+1, i);
|
||
|
}
|
||
|
|
||
|
function pattrstorage(v){
|
||
|
find_pattrstorage(v);
|
||
|
paint_base();
|
||
|
}
|
||
|
|
||
|
function slotlist() {
|
||
|
filled_slots = arrayfromargs(arguments);
|
||
|
if (filled_slots.length) {
|
||
|
|
||
|
// If the highest numbered preset is above the maximum number of displayed presets, we need to extend slots[]
|
||
|
slots_highest = filled_slots[filled_slots.length - 1];
|
||
|
if (slots_count_display < slots_highest) {
|
||
|
for (var i = slots_count_display + 1; i <= slots_highest; i++) {
|
||
|
slots[i] = [0, 0, 0, 0, null, 0, -1];
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0; i < filled_slots.length; i++) {
|
||
|
to_pattrstorage("getslotname", filled_slots[i]);
|
||
|
}
|
||
|
}
|
||
|
// paint_base();
|
||
|
}
|
||
|
|
||
|
function slotname() {
|
||
|
var args = arrayfromargs(arguments);
|
||
|
if (args[0] > 0 && args[1] != "(undefined)") {
|
||
|
|
||
|
slots[args[0]][4] = args[1];
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function setslotname() {
|
||
|
// Because [pattrstorage] doesn't output anything when renaming presets with "slotname", we use a custom "setslotname" instead, that will rename the active preset
|
||
|
if (active_slot > 0) {
|
||
|
var sname = arrayfromargs(arguments).join(' ');
|
||
|
slotname(active_slot, sname);
|
||
|
to_pattrstorage("slotname", active_slot, sname);
|
||
|
update_umenu();
|
||
|
set_active_slot(active_slot);
|
||
|
trigger_writeagain();
|
||
|
if (layout == 1) {
|
||
|
paint_base();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function text() {
|
||
|
setslotname(arrayfromargs(arguments).join(' '));
|
||
|
}
|
||
|
|
||
|
function recall() {
|
||
|
var args = arrayfromargs(arguments);
|
||
|
if (args.length == 1) {
|
||
|
previous_active_slot = active_slot;
|
||
|
is_interpolating = 0;
|
||
|
set_active_slot(args[0]);
|
||
|
outlet(0, 'recall', args[0]);
|
||
|
} else {
|
||
|
var src_slot = args[0];
|
||
|
var trg_slot = args[1];
|
||
|
|
||
|
for (var i = 0; i < filled_slots.length; i++) {
|
||
|
slots[filled_slots[i]][6] = -1;
|
||
|
}
|
||
|
|
||
|
if (slots[src_slot][4] != null && slots[trg_slot][4] != null) {
|
||
|
|
||
|
if (ignore_slot_zero == 1 && src_slot == 0) {
|
||
|
// Set src_slot as if we were interpolating from the last recalled preset different than 0
|
||
|
// This way we can monitor which preset we come from even if we used preset 0 as intermediary preset
|
||
|
if (previous_target != active_slot) {
|
||
|
// If the last target preset was through interpollation or direct recall
|
||
|
src_slot = previous_active_slot;
|
||
|
} else {
|
||
|
src_slot = active_slot;
|
||
|
}
|
||
|
}
|
||
|
var interp = Math.min( 1, Math.max(0, args[2]));
|
||
|
if (interp == 0.0) {
|
||
|
slots[src_slot][6] = -1;
|
||
|
slots[trg_slot][6] = -1;
|
||
|
is_interpolating = 0;
|
||
|
if (previous_target != active_slot) {
|
||
|
previous_active_slot = active_slot;
|
||
|
} else if (args[0] != 0) {
|
||
|
previous_active_slot = args[0];
|
||
|
} else {
|
||
|
previous_active_slot = previous_target;
|
||
|
}
|
||
|
|
||
|
set_active_slot(src_slot);
|
||
|
} else if (interp == 1.0) {
|
||
|
slots[src_slot][6] = -1;
|
||
|
slots[trg_slot][6] = -1;
|
||
|
is_interpolating = 0;
|
||
|
previous_target = trg_slot;
|
||
|
set_active_slot(trg_slot);
|
||
|
|
||
|
} else {
|
||
|
slots[src_slot][6] = 1 - interp;
|
||
|
slots[trg_slot][6] = interp;
|
||
|
is_interpolating = 1;
|
||
|
active_slot = 0;
|
||
|
// set_active_slot(0);
|
||
|
}
|
||
|
|
||
|
outlet(0, "recall", src_slot, trg_slot, interp);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
|
||
|
function recallmulti() {
|
||
|
var args = arrayfromargs(arguments);
|
||
|
var interp_slots = [];
|
||
|
var summed_weight = 0;
|
||
|
|
||
|
for (var i = 0; i < args.length; i++) {
|
||
|
var weight = args[i] % 1.;
|
||
|
if (weight == 0) weight = 1;
|
||
|
summed_weight += weight;
|
||
|
interp_slots.push([Math.floor(args[i]), weight]);
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < interp_slots.length; i++) {
|
||
|
var nb = interp_slots[i][0];
|
||
|
if (slots[nb][4] != null) {
|
||
|
interp_slots[i][1] /= summed_weight;
|
||
|
} else {
|
||
|
interp_slots[i][1] = -1;
|
||
|
}
|
||
|
slots[nb][6] = interp_slots[i][1]
|
||
|
}
|
||
|
|
||
|
is_interpolating = 1;
|
||
|
mgraphics.redraw();
|
||
|
|
||
|
outlet(0, "recallmulti", args);
|
||
|
|
||
|
}
|
||
|
|
||
|
function store(v) {
|
||
|
v = Math.floor(v);
|
||
|
if (v >= 0) {
|
||
|
if (slots[v][5] > 0) {
|
||
|
error('cannot overwrite locked slot ' + v + '\n');
|
||
|
} else {
|
||
|
var recalc_rows_flag = scrollable && v > slots_highest;
|
||
|
|
||
|
to_pattrstorage("store", v);
|
||
|
to_pattrstorage("getslotlist");
|
||
|
|
||
|
if (recalc_rows_flag) {
|
||
|
calc_rows_columns();
|
||
|
} else {
|
||
|
paint_base();
|
||
|
}
|
||
|
|
||
|
if (!(ignore_slot_zero && v == 0)) {
|
||
|
set_active_slot(v);
|
||
|
}
|
||
|
|
||
|
outlet(0, "store", v);
|
||
|
if (v != 0) {
|
||
|
trigger_writeagain();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function setlock(v) {
|
||
|
lock(active_slot, v);
|
||
|
}
|
||
|
|
||
|
function lock() {
|
||
|
var args = arrayfromargs(arguments);
|
||
|
if (args.length == 2) {
|
||
|
to_pattrstorage("lock", args[0], args[1]);
|
||
|
to_pattrstorage("getlockedslots");
|
||
|
outlet(0, "lock", args[0], args[1]);
|
||
|
trigger_writeagain();
|
||
|
if (layout == 1) {
|
||
|
paint_base();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function lockedslots() {
|
||
|
var locked_slots = arrayfromargs(arguments);
|
||
|
for (var i = 1; i < slots.length; i++) {
|
||
|
slots[i][5] = 0;
|
||
|
}
|
||
|
if (locked_slots.length) {
|
||
|
for (var i = 0; i < locked_slots.length; i++) {
|
||
|
slots[locked_slots[i]][5] = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function write() {
|
||
|
var args = arrayfromargs(arguments);
|
||
|
var filename = args[0];
|
||
|
var state = args[1];
|
||
|
if (state) {
|
||
|
post(pattrstorage_name + ' pattrstorage: ' + filename + ' updated\n');
|
||
|
} else {
|
||
|
error(pattrstorage_name + ': error while writing ' + filename + '\n');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function read() {
|
||
|
var args = arrayfromargs(arguments);
|
||
|
var state = args[1];
|
||
|
if (state) {
|
||
|
pattrstorage(pattrstorage_name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function find_pattrstorage(name) {
|
||
|
active_slot = 0;
|
||
|
pattrstorage_obj = this.patcher.getnamed(name);
|
||
|
if (pattrstorage_obj !== null) {
|
||
|
pattrstorage_name = name;
|
||
|
// slots_clear();
|
||
|
to_pattrstorage("getslotlist");
|
||
|
to_pattrstorage("getlockedslots");
|
||
|
} else {
|
||
|
pattrstorage_name = null;
|
||
|
slots_clear();
|
||
|
// error("Pattrstorage", name, "doesn't exist.\n");
|
||
|
}
|
||
|
}
|
||
|
find_pattrstorage.local = 1;
|
||
|
|
||
|
function to_pattrstorage() {
|
||
|
if (pattrstorage_obj !== null) {
|
||
|
pattrstorage_obj.message(arrayfromargs(arguments));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function slots_clear() {
|
||
|
slots[0] = [0, 0, 0, 0, "(tmp)", 0, -1];
|
||
|
for (var i = 1; i < slots.length; i++) {
|
||
|
slots[i][4] = null;
|
||
|
slots[i][5] = 0;
|
||
|
slots[i][6] = -1;
|
||
|
}
|
||
|
}
|
||
|
slots_clear.local = 1;
|
||
|
|
||
|
function get_slot_index(x, y) {
|
||
|
// Returns which slot is hovered by the mouse
|
||
|
for (var i = 1; i <= slots_count_display; i++) {
|
||
|
if (y > (slots[i][1] - half_spacing) && y < (slots[i][3] + half_spacing) && x > (slots[i][0] - half_spacing) && x < (slots[i][2] + half_spacing)) {
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
get_slot_index.local = 1;
|
||
|
|
||
|
function set_active_slot(int) {
|
||
|
if (int < 0) {
|
||
|
active_slot = 0;
|
||
|
} else {
|
||
|
active_slot = int;
|
||
|
}
|
||
|
outlet(0, "previous", previous_active_slot);
|
||
|
if (menu_number_only) {
|
||
|
outlet(1, "setsymbol", active_slot);
|
||
|
} else {
|
||
|
outlet(1, "setsymbol", active_slot + ' ' + slots[active_slot][4]);
|
||
|
}
|
||
|
if (active_slot != 0) {
|
||
|
outlet(2, "set", slots[active_slot][4]);
|
||
|
} else {
|
||
|
outlet(2, "set");
|
||
|
}
|
||
|
outlet(3, "set", slots[active_slot][5]);
|
||
|
}
|
||
|
set_active_slot.local = 1;
|
||
|
|
||
|
function update_umenu() {
|
||
|
if (pattrstorage_obj !== null) {
|
||
|
outlet(1, "clear");
|
||
|
|
||
|
for (var i=0; i < filled_slots.length; i++) {
|
||
|
var txt = filled_slots[i].toString();
|
||
|
if (!menu_number_only) {
|
||
|
txt += ' ' + slots[filled_slots[i]][4];
|
||
|
}
|
||
|
outlet(1, "append", txt);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
update_umenu.local = 1;
|
||
|
|
||
|
function trigger_writeagain() {
|
||
|
if (auto_writeagain && !is_dragging) {
|
||
|
to_pattrstorage("writeagain");
|
||
|
|
||
|
}
|
||
|
}
|
||
|
trigger_writeagain.local = 1;
|
||
|
|
||
|
// MOUSE EVENTS
|
||
|
function onidle(x,y,but,cmd,shift,capslock,option,ctrl)
|
||
|
{
|
||
|
if (last_x != x || last_y != y - y_offset|| shift_hold != shift || option_hold != option) {
|
||
|
last_x = x;
|
||
|
last_y = y - y_offset;
|
||
|
shift_hold = shift;
|
||
|
option_hold = option;
|
||
|
var cur = get_slot_index(x, y - y_offset);
|
||
|
if (cur != last_hovered) {
|
||
|
last_hovered = cur;
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
onidle.local = 1;
|
||
|
|
||
|
function onidleout()
|
||
|
{
|
||
|
last_hovered = -1;
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
onidleout.local = 1;
|
||
|
|
||
|
function onclick(x,y,but,cmd,shift,capslock,option,ctrl)
|
||
|
{
|
||
|
if (last_hovered > -1 && pattrstorage_name != null) {
|
||
|
var output = "recall";
|
||
|
if (shift) {
|
||
|
output = "store";
|
||
|
if (option) {
|
||
|
output = "delete";
|
||
|
}
|
||
|
} else if (slots[last_hovered][4] == null) {
|
||
|
return;
|
||
|
}
|
||
|
if (output == "store") {
|
||
|
store(last_hovered);
|
||
|
} else {
|
||
|
to_pattrstorage(output, last_hovered);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
last_x = x;
|
||
|
last_y = y - y_offset;
|
||
|
}
|
||
|
onclick.local = 1;
|
||
|
|
||
|
|
||
|
function ondrag(x,y,but,cmd,shift,capslock,option,ctrl)
|
||
|
{
|
||
|
y -= y_offset;
|
||
|
if (is_dragging == 0 && last_hovered > 0 && slots[last_hovered][4] !== null) {
|
||
|
var dist_from_start = Math.sqrt((x-last_x)*(x-last_x)+(y-last_y)*(y-last_y));
|
||
|
if (dist_from_start > 10) {
|
||
|
is_dragging = 1;
|
||
|
drag_slot = last_hovered;
|
||
|
}
|
||
|
|
||
|
} else if (is_dragging == 1) {
|
||
|
last_hovered = get_slot_index(x, y);
|
||
|
last_x = x;
|
||
|
last_y = y;
|
||
|
if (!but) {
|
||
|
// Wehen to button is released, the dragging ceases
|
||
|
if (last_hovered > 0 && last_hovered != drag_slot) {
|
||
|
var cur_active_slot = active_slot;
|
||
|
var offset = ((last_hovered <= drag_slot) && slots[last_hovered][4] != null) ? 1 : 0;
|
||
|
var drag_slot_lock = slots[drag_slot][5];
|
||
|
// If the slot we wan to drag is locked, we need to temporarily unlock it.
|
||
|
if (drag_slot_lock) {
|
||
|
lock(drag_slot, 0);
|
||
|
}
|
||
|
// If new slot is empty we just move the drag preset here. If it's not, we move al next slots to the right
|
||
|
if (slots[last_hovered][4] !== null) {
|
||
|
to_pattrstorage("insert", last_hovered);
|
||
|
}
|
||
|
|
||
|
to_pattrstorage("copy", drag_slot + offset, last_hovered);
|
||
|
to_pattrstorage("delete", drag_slot + offset);
|
||
|
|
||
|
slots_clear();
|
||
|
to_pattrstorage("getslotlist");
|
||
|
|
||
|
to_pattrstorage("getlockedslots");
|
||
|
if (cur_active_slot == drag_slot) {
|
||
|
active_slot = last_hovered;
|
||
|
}
|
||
|
// If the dragged slot was locked, relock it.
|
||
|
if (drag_slot_lock) {
|
||
|
lock(last_hovered, 1);
|
||
|
}
|
||
|
|
||
|
paint_base();
|
||
|
outlet(0, "drag", drag_slot, last_hovered, offset);
|
||
|
set_active_slot(last_hovered);
|
||
|
is_dragging = 0;
|
||
|
trigger_writeagain();
|
||
|
|
||
|
} else {
|
||
|
is_dragging = 0;
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ondrag.local = 1;
|
||
|
|
||
|
function onwheel(x, y, wheel_inc_x, wheel_inc_y, cmd, shift, caps, opt, ctrl)
|
||
|
{
|
||
|
if (scrollable) {
|
||
|
y_offset += wheel_inc_y * 100.0;
|
||
|
y_offset = Math.min(y_offset, 0);
|
||
|
y_offset = Math.max(y_offset, -1 * (bg_height - ui_height));
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
}
|
||
|
onwheel.local = 1;
|
||
|
|
||
|
function onresize(w,h)
|
||
|
{
|
||
|
ui_width = w;
|
||
|
ui_height = h;
|
||
|
calc_rows_columns();
|
||
|
// loadbang();
|
||
|
to_pattrstorage("getslotlist");
|
||
|
paint_base();
|
||
|
}
|
||
|
onresize.local = 1;
|
||
|
|
||
|
// function ondblclick(x,y,but,cmd,shift,capslock,option,ctrl)
|
||
|
// {
|
||
|
// last_x = x;
|
||
|
// last_y = y;
|
||
|
// }
|
||
|
// ondblclick.local = 1;
|
||
|
|
||
|
// ATTRIBUTES DECLARATION
|
||
|
declareattribute("bubblesize", "getslotsize", "setslotsize", 1);
|
||
|
function getslotsize() {
|
||
|
return slot_size;
|
||
|
}
|
||
|
function setslotsize(v){
|
||
|
slot_size = Math.max(2, v);
|
||
|
calc_rows_columns();
|
||
|
}
|
||
|
|
||
|
declareattribute("slot_round", "getslotround", "setslotround", 1);
|
||
|
function getslotround() {
|
||
|
return slot_round;
|
||
|
}
|
||
|
function setslotround(v){
|
||
|
slot_round = Math.max(0, Math.min(slot_size, v));
|
||
|
calc_rows_columns();
|
||
|
}
|
||
|
|
||
|
declareattribute("margin", "getmargin", "setmargin", 1);
|
||
|
function getmargin() {
|
||
|
return margin;
|
||
|
}
|
||
|
function setmargin(v){
|
||
|
margin = Math.max(0, v);
|
||
|
calc_rows_columns();
|
||
|
}
|
||
|
|
||
|
declareattribute("spacing", "getspacing", "setspacing", 1);
|
||
|
function getspacing() {
|
||
|
return spacing;
|
||
|
}
|
||
|
function setspacing(v){
|
||
|
spacing = Math.max(1, v);
|
||
|
calc_rows_columns();
|
||
|
}
|
||
|
|
||
|
declareattribute("bgcolor", "getbgcolor", "setbgcolor", 1);
|
||
|
function getbgcolor() {
|
||
|
return background_color;
|
||
|
}
|
||
|
function setbgcolor(){
|
||
|
background_color = [arguments[0], arguments[1], arguments[2], arguments[3]];
|
||
|
paint_base();
|
||
|
}
|
||
|
|
||
|
declareattribute("empty_slot_color", "getemptycolor", "setemptycolor", 1);
|
||
|
function getemptycolor() {
|
||
|
return empty_slot_color;
|
||
|
}
|
||
|
function setemptycolor(){
|
||
|
empty_slot_color = [arguments[0], arguments[1], arguments[2], arguments[3]];
|
||
|
paint_base();
|
||
|
}
|
||
|
|
||
|
declareattribute("active_slot_color", "getactiveslotcolor", "setactiveslotcolor", 1);
|
||
|
function getactiveslotcolor() {
|
||
|
return active_slot_color;
|
||
|
}
|
||
|
function setactiveslotcolor(){
|
||
|
active_slot_color = [arguments[0], arguments[1], arguments[2], arguments[3]];
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
|
||
|
declareattribute("stored_slot_color", "getstoredslotcolor", "setstoredslotcolor", 1);
|
||
|
function getstoredslotcolor() {
|
||
|
return stored_slot_color;
|
||
|
}
|
||
|
function setstoredslotcolor(){
|
||
|
stored_slot_color = [arguments[0], arguments[1], arguments[2], arguments[3]];
|
||
|
paint_base();
|
||
|
}
|
||
|
|
||
|
declareattribute("interp_slot_color", "getinterpslotcolor", "setinterpslotcolor", 1);
|
||
|
function getinterpslotcolor() {
|
||
|
return interp_slot_color;
|
||
|
}
|
||
|
function setinterpslotcolor(){
|
||
|
interp_slot_color = [arguments[0], arguments[1], arguments[2], arguments[3]];
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
|
||
|
declareattribute("text_bg_color", "gettextbgcolor", "settextbgcolor", 1);
|
||
|
function gettextbgcolor() {
|
||
|
return text_bg_color;
|
||
|
}
|
||
|
function settextbgcolor(){
|
||
|
text_bg_color = [arguments[0], arguments[1], arguments[2], arguments[3]];
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
|
||
|
declareattribute("text_color", "gettextcolor", "settextcolor", 1);
|
||
|
function gettextcolor() {
|
||
|
return text_color;
|
||
|
}
|
||
|
function settextcolor(){
|
||
|
text_color = [arguments[0], arguments[1], arguments[2], arguments[3]];
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
|
||
|
declareattribute("fontsize", "getfontsize", "setfontsize", 1);
|
||
|
function getfontsize() {
|
||
|
return font_size;
|
||
|
}
|
||
|
function setfontsize(v){
|
||
|
font_size = Math.max(2, v);
|
||
|
mgraphics.redraw();
|
||
|
}
|
||
|
|
||
|
declareattribute("fontname", "getfontname", "setfontname", 1);
|
||
|
function getfontname() {
|
||
|
return font_name;
|
||
|
}
|
||
|
function setfontname(v){
|
||
|
var fontlist = mgraphics.getfontlist();
|
||
|
if (fontlist.indexOf(v) > -1) {
|
||
|
font_name = v.toString();
|
||
|
mgraphics.redraw();
|
||
|
} else {
|
||
|
error("Font not found.\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
declareattribute("autowriteagain", "getautowriteagain", "setautowriteagain", 1);
|
||
|
function getautowriteagain() {
|
||
|
return auto_writeagain;
|
||
|
}
|
||
|
function setautowriteagain(v){
|
||
|
if (v == 0) {
|
||
|
auto_writeagain = 0;
|
||
|
} else {
|
||
|
auto_writeagain = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
declareattribute("ignoreslotzero", "getignoreslotzero", "setignoreslotzero", 1);
|
||
|
function getignoreslotzero() {
|
||
|
return ignore_slot_zero;
|
||
|
}
|
||
|
function setignoreslotzero(v){
|
||
|
if (v == 0) {
|
||
|
ignore_slot_zero = 0;
|
||
|
} else {
|
||
|
ignore_slot_zero = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
declareattribute("displayinterp", "getdisplayinterp", "setdisplayinterp", 1);
|
||
|
function getdisplayinterp() {
|
||
|
return display_interp;
|
||
|
}
|
||
|
function setdisplayinterp(v){
|
||
|
if (v == 0) {
|
||
|
display_interp = 0;
|
||
|
} else {
|
||
|
display_interp = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
declareattribute("layout", "getlayout", "setlayout", 1);
|
||
|
function getlayout() {
|
||
|
return layout;
|
||
|
}
|
||
|
function setlayout(v){
|
||
|
if (v == 0) {
|
||
|
layout = 0;
|
||
|
} else {
|
||
|
layout = 1;
|
||
|
}
|
||
|
y_offset = 0;
|
||
|
calc_rows_columns();
|
||
|
}
|
||
|
|
||
|
declareattribute("scrollable", "getscrollable", "setscrollable", 1);
|
||
|
function getscrollable() {
|
||
|
return scrollable;
|
||
|
}
|
||
|
function setscrollable(v){
|
||
|
if (v == 0) {
|
||
|
scrollable = 0;
|
||
|
} else {
|
||
|
scrollable = 1;
|
||
|
}
|
||
|
y_offset = 0;
|
||
|
calc_rows_columns();
|
||
|
}
|
||
|
|
||
|
declareattribute("min_rows", "getmin_rows", "setmin_rows", 1);
|
||
|
function getmin_rows() {
|
||
|
return min_rows;
|
||
|
}
|
||
|
function setmin_rows(v){
|
||
|
if (v > 0) {
|
||
|
min_rows = v;
|
||
|
}
|
||
|
if (scrollable) {
|
||
|
calc_rows_columns();
|
||
|
}
|
||
|
}
|