/* 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 slot _round _ratio = 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 = [ ] ; // Stores on screen box, name, lock and interpolation state for all slots
var slots _highest = 0 ; // Highest filled preset slot number
var slots _count _display = 0 ; // Number of slots to be displayed
var filled _slots = [ ] ; // List of stored slots
var active _slot = 0 ; //Last recalled slot
var previous _active _slot = 0 ; //Previously recalled slot
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 is _painting _base = 0 ;
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 ) ;
if ( is _painting _base ) {
draw _slot _bubble ( slots [ id ] [ 0 ] * scale , slots [ id ] [ 1 ] * scale , slot _size * scale , slot _size * scale , cont ) ;
} else {
draw _slot _bubble ( slots [ id ] [ 0 ] + offset , slots [ id ] [ 1 ] + offset , slot _size * scale , slot _size * scale , cont ) ;
}
cont . fill ( ) ;
if ( layout == 1 ) {
// 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 ) ;
}
// slot name
cont . set _font _size ( font _size * scale ) ;
var text = format _slot _name ( id ) ;
if ( is _painting _base ) {
draw _text _bubble ( bg _txt _pos _x * scale , bg _txt _pos _y * scale , bg _txt _dim _w * scale , bg _txt _dim _h * scale , text , cont ) ;
} else {
draw _text _bubble ( bg _txt _pos _x + offset , bg _txt _pos _y + offset , bg _txt _dim _w * scale , bg _txt _dim _h * scale , text , cont ) ;
}
}
}
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 _ratio * w , slot _round _ratio * h ) ;
} else {
cont . rectangle ( x , y , w , h ) ;
}
}
draw _slot _bubble . local = 1 ;
function draw _text _bubble ( x , y , w , h , text , cont ) {
cont = typeof cont !== 'undefined' ? cont : mgraphics ;
// slot text background
cont . rectangle _rounded ( x , y , w , h , 4 , 4 ) ;
cont . fill ( ) ;
var text _dim = cont . text _measure ( text ) ;
var txt _pos _x = x + spacing ;
var txt _pos _y = y + ( text _dim [ 1 ] + h ) / 2 - text _dim [ 1 ] * 0.18 ;
cont . set _source _rgba ( text _color ) ;
cont . move _to ( txt _pos _x , txt _pos _y ) ;
cont . show _text ( text . toString ( ) ) ;
}
function format _slot _name ( id ) {
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 ( ) ;
return text ;
}
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
is _painting _base = 1 ;
// 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 * 2 , bg _height * 2 ) ;
with ( mg ) {
set _source _rgba ( background _color ) ;
rectangle ( 0 , 0 , bg _width * 2 , bg _height * 2 ) ;
fill ( ) ;
select _font _face ( font _name ) ;
set _font _size ( font _size ) ;
// All slots
for ( var i = 1 ; i <= slots _count _display ; i ++ ) {
if ( i != drag _slot ) { //We mask the slot that is currently dragged as it is drawn at the mouse position already
if ( slots [ i ] [ 4 ] != null ) {
set _source _rgba ( stored _slot _color ) ;
} else {
set _source _rgba ( empty _slot _color ) ;
}
draw _slot ( i , 2 , mg ) ;
}
}
}
is _painting _base = 0 ;
update _umenu ( ) ;
base _drawing = new Image ( mg ) ;
mgraphics . redraw ( ) ;
}
paint _base . local = 1 ;
function paint ( )
{
// post("redraw\n");
with ( mgraphics ) {
select _font _face ( font _name ) ;
set _font _size ( font _size ) ;
translate ( 0 , y _offset ) ;
// Draw the base, which includes empty and filled slots
// It is first rendered at twice the size in order to make texts look nice and cripsy on hidpi discplays
// So we need to scale it down here
scale ( 0.5 , 0.5 ) ;
image _surface _draw ( base _drawing ) ;
scale ( 2 , 2 ) ;
set _line _width ( 1 ) ;
// Active slot
if ( is _dragging == 0 && 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 ( is _dragging == 0 && 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 ( is _dragging == 0 && 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 = format _slot _name ( last _hovered ) ;
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 _dim _w = text _dim [ 0 ] > slot _size ? text _dim [ 0 ] + 4 : slot _size + 4 ;
var bg _txt _dim _h = text _dim [ 1 ] > slot _size ? text _dim [ 1 ] + 4 : slot _size + 4 ;
var bg _txt _pos _x = text _dim [ 0 ] > slot _size || is _dragging ? slots [ last _hovered ] [ 0 ] + slot _size + 2 : slots [ last _hovered ] [ 0 ] - 2 ;
var bg _txt _pos _y = text _dim [ 1 ] > slot _size || is _dragging ? slots [ last _hovered ] [ 1 ] - 2 : slots [ last _hovered ] [ 1 ] - 2 ;
// 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 + text _dim [ 1 ] ) / 2 - text _dim [ 1 ] * 0.18 ;
// 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 ) {
if ( layout == 0 ) {
translate ( last _x , last _y ) ;
rotate ( 0.15 ) ;
scale ( 1.1 , 1.1 ) ;
// Slot 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 ( ) ;
//Flying slot
set _source _rgba ( active _slot _color ) ;
draw _slot _bubble ( - slot _size / 2 , - slot _size / 2 , slot _size , slot _size ) ;
fill ( ) ;
} else {
translate ( last _x , last _y ) ;
// rotate(0.15);
set _source _rgba ( active _slot _color ) ;
draw _slot _bubble ( - slot _size / 2 , - slot _size / 2 , slot _size , slot _size ) ;
fill ( ) ;
// slot name
var text = format _slot _name ( drag _slot ) ;
var bg _txt _pos _x = slot _size / 2 + spacing ;
var bg _txt _pos _y = - slot _size / 2 ;
var bg _txt _dim _w = ui _width - ( 2 * margin + slot _size + spacing ) ;
var bg _txt _dim _h = slot _size ;
set _source _rgba ( stored _slot _color ) ;
draw _text _bubble ( bg _txt _pos _x , bg _txt _pos _y , bg _txt _dim _w , bg _txt _dim _h , text ) ;
}
}
}
}
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 we can't declare a "function delete" (it is a reserved 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 ) ;
if ( ! is _dragging ) {
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 resync ( ) {
calc _rows _columns ( ) ;
}
function find _pattrstorage ( name ) {
active _slot = 0 ;
pattrstorage _obj = this . patcher . getnamed ( name ) ;
if ( pattrstorage _obj !== null ) {
pattrstorage _name = name ;
slots _clear ( ) ;
// this.patcher.hiddenconnect(pattrstorage_obj, 0, this.box, 0);
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 )
{
if ( pattrstorage _name != null ) {
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 ;
paint _base ( ) ;
}
} 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 ) ;
}
outlet ( 0 , "drag" , drag _slot , last _hovered , offset ) ;
is _dragging = 0 ;
drag _slot = - 1 ;
paint _base ( ) ;
set _active _slot ( last _hovered ) ;
trigger _writeagain ( ) ;
} else { // Drag released but not somewhere we can throw a slot in
is _dragging = 0 ;
drag _slot = - 1 ;
paint _base ( ) ;
// 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 ) ) ;
slot _round _ratio = slot _round / slot _size ;
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 ) ;
if ( layout == 1 ) {
paint _base ( ) ;
} else {
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 ( ) ;
if ( layout == 1 ) {
paint _base ( ) ;
} else {
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 ( ) ;
}
}