Browse Source

resize in presentation mode, more robust initialization, performance improvement, small bug fixes

main
TFLCL 10 months ago
parent
commit
8f4e7ff660
  1. 18
      README.md
  2. 126
      tc.preset.js
  3. 83
      tc.preset_demo.maxpat

18
README.md

@ -3,9 +3,10 @@
A [jsui] replacement for the [preset] object in Cycling'74 Max.
## Features
- Same click + modifier key behavior as the vanilla object to store and delete presets
- Drag and drop presets to re-organize
- Display presets as a grid or a list
- Scrollable (requires Max 8.6.2)
- Scrollable list layout (requires Max 8.6.2)
- Shows active preset even if recalled directly from pattrstorage
- Shows previously active preset, with the ability to ignore preset 0 if it being used as an intermediary step
- Shows presets being interpolated (using recall or recallmulti)
@ -14,24 +15,27 @@ A [jsui] replacement for the [preset] object in Cycling'74 Max.
- Ability to rewrite json file automatically every time a preset is stored/moved/deleted/renamed/(un)locked
- Helps keeping in sync a umenu with the list of stored slotstlet
- More look customization
- Same click + modifier key behavior as the vanilla object to store or delete presets
- Dynamically adapts to resize both in Edit and Presentation mode
- Select mode: simple click selects the slot, double click recalls it (allows for organizing presets without recalling them)
- Color mode: ability to color sstored presets with 6 customizable colors (currently colors are only assigned automatically depending on slot number)
## How to use
- Place `tc.preset.js` in the same directory as your patch, or somewhere in the Max search path
- Create a [`jsui @filename tc.preset.js`]. You can either add `@jsarguments` followed by the name of the pattrstorage you want to communicate with, or set that later by sending a `pattrstorage` (followed by the pattrstorage name) message to the [jsui].
- Create a [`jsui @filename tc.preset.js`].
- Connect the [pattrstorage] outlet to the [jsui] inlet
- Set the jsui attribute named "pattrstorage" to the name of the pattrstorage you just connected (or send a message like "pattrstorage my_pattrstorage_name")
## Limitations
- Resize doesn't work in Presentation mode (jsui limitation)
- Due to the way [pattrstorage] works, some pattrstorage-specific messages should be sent to the [jsui] instead of the [pattrstorage]. Some to both:
- `recall`, `delete`: send to [pattrstorage] only
- `recall`: send to [pattrstorage] only
- `recallmulti`, `slotname`: send to [pattrstorage] first (for better timing), then to the [jsui]
- `store`: send to [jsui] only
- Some messages to pattrstorage causes the jsui to be out of sync (`clear`, `insert`, `lockall`, `read`, `readagain`, `remove`, `renumber`). If you use any of these messages, make sure to then send a `resync` to the jsui.
- The js program send a lot of message to the [pattrstorage] (using `maxobj.message()`syntax, so without patch cord), which in return send (using a patch cord) a lot of messages required for the [jsui] to stay in sync. Using one of the above messages incorrectly, or sending `getslotlist`, `getslotnamelist`, or any message that will impact the presets might cause the [pattrstorage] to get out of sync. In case something like that happens, you can send the `resync` message to the [jsui].
## Desired features (for someday)
- Select mode: simple click selects the slot, double click recalls it
## Desired features (for someday, if ever)
- No need for a patch cord (programmatically create a [send]/[receive] pair?)
- Ability to lock/unlock and rename directly in the jsui without the need of external objects
- Ability to target a [pattrstorage] in a different patcher level

126
tc.preset.js

@ -106,18 +106,26 @@ 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) {
var has_loaded = false;
// Allows for dynamic resizing even in presentation mode (addressing the limitation of onresize())
var pres_rect = new MaxobjListener(this.box,"presentation_rect",get_prect);
function get_prect(prect) {
onresize(prect.value[2], prect.value[3])
}
if (jsarguments.length>1) { // Depreciated, use "pattrstorage" attribute instead of jsarguments.
pattrstorage_name = jsarguments[1];
}
function loadbang() {
has_loaded = true;
post("loadbang\n");
outlet(2, "set");
find_pattrstorage(pattrstorage_name);
calc_rows_columns();
}
// loadbang();
function calc_rows_columns() {
half_margin = margin / 2;
half_spacing = spacing / 2;
@ -246,6 +254,7 @@ function draw_text_bubble(x, y, w, h, text, cont) {
cont.move_to(txt_pos_x, txt_pos_y);
cont.show_text(text.toString());
}
draw_text_bubble.local = 1;
function format_slot_name(id) {
var text = id;
@ -264,7 +273,7 @@ format_slot_name.local = 1;
function paint_base() {
// We draw all slots (empty and stored ones) so we don't have to for every redraw
// post("paint_base\n");
is_painting_base = 1;
// Background
@ -305,6 +314,12 @@ paint_base.local = 1;
function paint()
{
// post("redraw\n");
// Handling Presentation mode enable/disable
var cur_size = mgraphics.size;
if (cur_size[0] != ui_width || cur_size[1] != ui_height) {
onresize(cur_size[0], cur_size[1]);
} else {
with (mgraphics) {
select_font_face(font_name);
set_font_size(font_size);
@ -349,7 +364,7 @@ function paint()
}
// Selected slot
if (select_mode && selected_slot > 0 && selected_slot <= slots_count_display) {
if (selected_slot > 0 && selected_slot <= slots_count_display) {
set_source_rgba(active_slot_color);
set_line_width(1);
draw_slot_bubble(slots[selected_slot][0] - 0.5, slots[selected_slot][1] - 0.5, slot_size + 1, slot_size + 1);
@ -466,6 +481,7 @@ function paint()
}
}
}
}
paint.local = 1;
@ -529,6 +545,8 @@ function anything() {
slots[v][6] = -1;
if (active_slot == v) {
active_slot = 0;
} else if (previous_active_slot == v) {
previous_active_slot = 0;
}
// to_pattrstorage("getslotname", v);
@ -538,6 +556,11 @@ function anything() {
set_active_slot(active_slot);
if (!is_dragging) {
outlet(0, "delete", v);
if (selected_slot == v) {
selected_slot == 0
outlet(2, 'set');
outlet(3, 'set', 0);
}
}
trigger_writeagain();
}
@ -545,8 +568,7 @@ function anything() {
}
}
function bang()
{
function bang() {
to_pattrstorage("recall", active_slot);
}
@ -554,14 +576,17 @@ function msg_int(v) {
to_pattrstorage("recall", v);
}
function msg_float(v)
{
function msg_float(v) {
var s = Math.floor(v);
var i = v % 1;
to_pattrstorage("recall", s, s+1, i);
}
function pattrstorage(v){
function init() {
loadbang();
}
function pattrstorage(v) {
find_pattrstorage(v);
paint_base();
}
@ -756,6 +781,9 @@ function lockedslots() {
if (locked_slots.length) {
for (var i = 0; i < locked_slots.length; i++) {
slots[locked_slots[i]][5] = 1;
if (locked_slots[i] == selected_slot) {
select(selected_slot);
}
}
}
}
@ -780,6 +808,7 @@ function read() {
}
function resync() {
set_active_slot(0);
calc_rows_columns();
}
@ -794,6 +823,9 @@ function find_pattrstorage(name) {
to_pattrstorage("getlockedslots");
} else {
pattrstorage_name = null;
active_slot = 0;
previous_active_slot = 0;
selected_slot = 0;
slots_clear();
// error("Pattrstorage", name, "doesn't exist.\n");
}
@ -882,15 +914,22 @@ 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) {
var redraw_flag = false;
if (last_x != x || last_y != y - y_offset) {
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;
redraw_flag = true;
}
}
if (shift_hold != shift || option_hold != option) {
shift_hold = shift;
option_hold = option;
redraw_flag = true;
}
if (redraw_flag) {
mgraphics.redraw();
}
}
@ -939,28 +978,6 @@ function ondblclick(x,y,but,cmd,shift,capslock,option,ctrl)
{
if (last_hovered > -1 && pattrstorage_name != null && filled_slots.indexOf(last_hovered) > -1) {
to_pattrstorage("recall", last_hovered);
// var output = "recall";
// if (select_mode) {
// output = "select";
// }
// if (shift) {
// output = "store";
// if (option) {
// output = "delete";
// }
// } else if (slots[last_hovered][4] == null) {
// return;
// }
// if (output == "store") {
// store(last_hovered);
// } else {
// if (output == "select") {
// select(last_hovered);
// // mgraphics.redraw();
// } else {
// to_pattrstorage(output, last_hovered);
// }
// }
}
last_x = x;
@ -1075,14 +1092,32 @@ function onresize(w,h)
}
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("pattrstorage", "getpattrstorage", "setpattrstorage", 1);
function getpattrstorage() {
if (pattrstorage_name == null) {
return
} else {
return pattrstorage_name;
}
}
function setpattrstorage(v){
// This method is called for the first time when the patch is loading, before the loadbang (not all objects are instanciated yet)
// With v being the value stored whithin the patcher
if (v == null) {
pattrstorage_name = null;
pattrstorage_obj = null;
} else {
pattrstorage_name = arrayfromargs(arguments)[0];
}
// If the loadbang already occured once, we need to retrigger here
if (has_loaded) {
find_pattrstorage(pattrstorage_name);
loadbang();
}
// Otherwise, we just wait for the patch to call loadbang automatically at the end of its startup routine.
}
declareattribute("bubblesize", "getslotsize", "setslotsize", 1);
function getslotsize() {
return slot_size;
@ -1467,3 +1502,10 @@ function setcolor6(){
error('color_6: wrong number of arguments\n');
}
}
// UTILITY
function post_keys(obj) {
post('Keys of obj: ', obj, '\n');
post(Object.keys(obj));
post('\n');
}

83
tc.preset_demo.maxpat

@ -4,13 +4,13 @@
"appversion" : {
"major" : 8,
"minor" : 6,
"revision" : 2,
"revision" : 3,
"architecture" : "x64",
"modernui" : 1
}
,
"classnamespace" : "box",
"rect" : [ 34.0, 100.0, 854.0, 848.0 ],
"rect" : [ 310.0, 100.0, 854.0, 848.0 ],
"bglocked" : 0,
"openinpresentation" : 0,
"default_fontsize" : 12.0,
@ -39,6 +39,42 @@
"subpatcher_template" : "",
"assistshowspatchername" : 0,
"boxes" : [ {
"box" : {
"id" : "obj-31",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 245.5, 565.0, 89.0, 22.0 ],
"text" : "storagewindow"
}
}
, {
"box" : {
"id" : "obj-25",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 422.0, 598.0, 45.0, 22.0 ],
"text" : "store 3"
}
}
, {
"box" : {
"id" : "obj-14",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 360.5, 586.0, 29.5, 22.0 ],
"text" : "init"
}
}
, {
"box" : {
"id" : "obj-128",
"items" : [ "Grid", ",", "List" ],
@ -69,7 +105,7 @@
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 959.0, 908.0, 131.0, 33.0 ],
"patching_rect" : [ 959.0, 908.0, 133.0, 33.0 ],
"text" : "Set a given color (from 1 to 6)"
}
@ -82,7 +118,6 @@
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 830.0, 927.0, 123.0, 22.0 ],
"presentation_linecount" : 2,
"text" : "color_wheel 3 0 1 1 1"
}
@ -129,7 +164,7 @@
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 928.0, 780.0, 150.0, 87.0 ],
"patching_rect" : [ 928.0, 780.0, 151.0, 87.0 ],
"text" : "When color mode is set to cycle, stored presets are shown in colors from color_1 to color_6, cyclically depending on their slot number."
}
@ -303,7 +338,7 @@
"appversion" : {
"major" : 8,
"minor" : 6,
"revision" : 2,
"revision" : 3,
"architecture" : "x64",
"modernui" : 1
}
@ -967,7 +1002,7 @@
, {
"box" : {
"id" : "obj-8",
"items" : [ "0 (tmp)", ",", "1 <(unnamed)>", ",", "2 <(unnamed)>", ",", "3 <(unnamed)>", ",", "4 <(unnamed)>", ",", "5 <(unnamed)>", ",", "6 <(unnamed)>", ",", "7 onze", ",", "8 up", ",", "9 <(unnamed)>", ",", "10 oui", ",", "11 <(unnamed)>", ",", "12 <(unnamed)>", ",", "13 <(unnamed)>", ",", "14 <(unnamed)>", ",", "15 <(unnamed)>" ],
"items" : [ "6 <(unnamed)>", ",", "19 <(unnamed)>", ",", "39 <(unnamed)>" ],
"maxclass" : "umenu",
"numinlets" : 1,
"numoutlets" : 3,
@ -987,7 +1022,7 @@
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 72.0, 139.0, 507.0, 87.0 ],
"text" : "Pattrstorage messages that doesn't trigger output and need to be sent to the jsui instead:\n- store (send to jsui only)\n- recallmulti (send to pattrstorage first for timing accuracy, then to jsui)\n- slotname (send \"setslotname\" or \"text\" to jsui instead, will rename active slot)\n- lock (or setlock to lock/unlock of the active slot)\n- some other. I've not tested them all. "
"text" : "Pattrstorage messages that doesn't trigger output and need to be sent to the jsui instead:\n- store (send to jsui only)\n- recallmulti (send to pattrstorage first for timing accuracy, then to jsui)\n- slotname (send \"setslotname\" or \"text\" to jsui instead, will rename selected slot)\n- lock (or setlock to lock/unlock of the active slot)\n- some other. I've not tested them all. "
}
}
@ -1056,18 +1091,17 @@
, {
"box" : {
"border" : 0,
"embedstate" : [ [ "autowriteagain", 0 ], [ "active_slot_color", 0.808, 0.898, 0.91, 1 ], [ "color_4", 0.367, 0.542, 0.712, 1 ], [ "slot_round", 0 ], [ "stored_slot_color", 0.502, 0.502, 0.502, 1 ], [ "layout", 0 ], [ "select_mode", 0 ], [ "color_5", 0.283, 0.606, 0.559, 1 ], [ "text_color", 0.129, 0.129, 0.129, 1 ], [ "color_mode", 0 ], [ "color_1", 0.743, 0.41, 0.501, 1 ], [ "bubblesize", 20 ], [ "interp_slot_color", 1, 1, 1, 0.8 ], [ "margin", 4 ], [ "color_6", 0.316, 0.616, 0.377, 1 ], [ "fontsize", 14 ], [ "scrollable", 1 ], [ "empty_slot_color", 0.349, 0.349, 0.349, 1 ], [ "color_2", 0.679, 0.405, 0.669, 1 ], [ "fontname", "Arial" ], [ "ignoreslotzero", 1 ], [ "spacing", 4 ], [ "text_bg_color", 1, 1, 1, 0.5 ], [ "min_rows", 50 ], [ "bgcolor", 0.2, 0.2, 0.2, 1 ], [ "color_3", 0.527, 0.459, 0.756, 1 ], [ "displayinterp", 1 ] ],
"embedstate" : [ [ "layout", 0 ], [ "color_6", 0.316, 0.616, 0.377, 1 ], [ "text_color", 0.129, 0.129, 0.129, 1 ], [ "ignoreslotzero", 1 ], [ "min_rows", 50 ], [ "bgcolor", 0.2, 0.2, 0.2, 1 ], [ "spacing", 4 ], [ "empty_slot_color", 0.349, 0.349, 0.349, 1 ], [ "displayinterp", 1 ], [ "color_1", 0.743, 0.41, 0.501, 1 ], [ "text_bg_color", 1, 1, 1, 0.5 ], [ "slot_round", 0 ], [ "color_2", 0.679, 0.405, 0.669, 1 ], [ "color_4", 0.367, 0.542, 0.712, 1 ], [ "autowriteagain", 0 ], [ "color_3", 0.527, 0.459, 0.756, 1 ], [ "active_slot_color", 0.808, 0.898, 0.91, 1 ], [ "color_mode", 0 ], [ "margin", 4 ], [ "interp_slot_color", 1, 1, 1, 0.8 ], [ "fontsize", 14 ], [ "select_mode", 0 ], [ "color_5", 0.283, 0.606, 0.559, 1 ], [ "bubblesize", 20 ], [ "stored_slot_color", 0.502, 0.502, 0.502, 1 ], [ "scrollable", 1 ], [ "fontname", "Arial" ], [ "pattrstorage", "test" ] ],
"filename" : "tc.preset.js",
"id" : "obj-10",
"jsarguments" : [ "test" ],
"maxclass" : "jsui",
"numinlets" : 1,
"numoutlets" : 4,
"outlettype" : [ "", "", "", "" ],
"parameter_enable" : 0,
"patching_rect" : [ 72.0, 637.0, 364.0, 124.0 ],
"patching_rect" : [ 72.0, 636.0, 364.0, 125.0 ],
"presentation" : 1,
"presentation_rect" : [ 1.0, 103.0, 269.0, 77.0 ]
"presentation_rect" : [ 1.0, 103.0, 293.0, 149.0 ]
}
}
@ -1125,10 +1159,10 @@
"outlettype" : [ "" ],
"patching_rect" : [ 72.0, 599.0, 176.0, 22.0 ],
"saved_object_attributes" : {
"client_rect" : [ 27, 158, 514, 944 ],
"client_rect" : [ 1450, 76, 2131, 1323 ],
"parameter_enable" : 0,
"parameter_mappable" : 0,
"storage_rect" : [ 25, 107, 816, 991 ]
"storage_rect" : [ 583, 69, 1034, 197 ]
}
,
"text" : "pattrstorage test @savemode 0",
@ -1334,6 +1368,13 @@
"source" : [ "obj-131", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-10", 0 ],
"source" : [ "obj-14", 0 ]
}
}
, {
"patchline" : {
@ -1387,6 +1428,13 @@
"source" : [ "obj-24", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-10", 0 ],
"source" : [ "obj-25", 0 ]
}
}
, {
"patchline" : {
@ -1408,6 +1456,13 @@
"source" : [ "obj-3", 1 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-1", 0 ],
"source" : [ "obj-31", 0 ]
}
}
, {
"patchline" : {

Loading…
Cancel
Save