diff --git a/pd/nw/dialog_prefs.html b/pd/nw/dialog_prefs.html index 07a857749594c6722cde64de2092bf787ecd773b..8308b66de68e7e468aaca4a9b2c98675cfdc218c 100644 --- a/pd/nw/dialog_prefs.html +++ b/pd/nw/dialog_prefs.html @@ -415,9 +415,16 @@ select { </select> <br/> <label data-i18n="[title]prefs.gui.grid.show_grid_tt"> - <input type="checkbox" id="show_grid" name="show_grid"> + <input type="checkbox" id="show_grid" name="show_grid" + onchange="grid_toggle(this.checked)"> <span data-i18n="prefs.gui.grid.show_grid"></span> </label> + <select id="grid_size"> + <option value="5">5</option> + <option value="10">10</option> + <option value="20">20</option> + <option value="25">25</option> + </select> <br/> <label data-i18n="[title]prefs.gui.zoom.save_zoom_tt"> <input type="checkbox" id="save_zoom" name="save_zoom"> @@ -783,6 +790,7 @@ function apply(save_prefs) { pdgui.pdsend("pd gui-prefs", get_gui_preset(), get_bool_elem("show_grid"), + document.getElementById("grid_size").value, get_bool_elem("save_zoom"), get_bool_elem("browser_doc"), get_bool_elem("browser_path"), @@ -796,7 +804,8 @@ function apply(save_prefs) { get_bool_elem("browser_path") ); // Update the grid on all open windows. - pdgui.update_grid(get_bool_elem("show_grid")); + pdgui.update_grid(get_bool_elem("show_grid"), + document.getElementById("grid_size").value); // Send the startup config data to Pd pdgui.pdsend.apply(null, ["pd path-dialog", startup_use_stdpath, startup_verbose].concat(get_path_array())); @@ -1035,13 +1044,18 @@ function midi_prefs_callback(attrs) { pdgui.resize_window(pd_object_callback); } +function grid_toggle(checked) { + document.getElementById("grid_size").disabled = !checked; + document.getElementById("show_grid").checked = checked; +} + function autopatch_yoffset_toggle(checked) { document.getElementById("autopatch_yoffset_value").disabled = !checked; document.getElementById("autopatch_yoffset").checked = checked; } -function gui_prefs_callback(name, show_grid, save_zoom, browser_doc, browser_path, - browser_init, autopatch_yoffset) { +function gui_prefs_callback(name, show_grid, grid_size, save_zoom, browser_doc, + browser_path, browser_init, autopatch_yoffset) { var s = document.getElementById("gui_preset"); // ag: scan the css subdir for user-defined styles @@ -1071,6 +1085,8 @@ function gui_prefs_callback(name, show_grid, save_zoom, browser_doc, browser_pat } } document.getElementById("show_grid").checked = !!show_grid; + document.getElementById("grid_size").value = grid_size; + grid_toggle(!!show_grid); document.getElementById("save_zoom").checked = !!save_zoom; document.getElementById("browser_doc").checked = !!browser_doc; document.getElementById("browser_path").checked = !!browser_path; diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js index 64e80ab85453a7bca051f3509c3a068ff54e36fd..dad6f9f51163ce1043e1b59f17956ae5b47b86eb 100644 --- a/pd/nw/pdgui.js +++ b/pd/nw/pdgui.js @@ -1405,36 +1405,41 @@ function menu_send(name) { } } -/* -ico@vt.edu 20200907: added svg tiled background to reflect edit mode and -integrated it into the canvas_set_editmode below. - -LATER: consider adding an interim version that reflects only the ctrl button press -*/ -var gui_editmode_svg_background = "url(\"data:image/svg+xml,%3Csvg " + - "xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 " + - " 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%239C92AC' fill-opacity" + - "='0.4'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1" + - "v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-" + - "1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9" + - "H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9" + - "V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v" + - "1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h" + - "9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-1" + - "0 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9" + - "h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-" + - "10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9" + - "h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9" + - "v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h" + - "9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h" + - "9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-1" + - "0 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-" + - "9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm1" + - "0 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9" + - "h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9" + - "h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0" + - "h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d" + - "='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\")"; +// Background for edit mode. Currently, we use a grid if snap-to-grid +// functionality is turned on in the GUI preferences. If not, we just use +// the same grid with a lower opacity. That way the edit mode is always +// visually distinct from run mode. +var create_editmode_background = function(grid, size) { + var head, body, tail, cell_data_str, opacity_str; + // if snap-to-grid isn't turned on, just use cell size of 10 and make the + // grid partially transparent + size = grid ? size : 10; + opacity_str = '"' + (grid ? 1 : 0.25) + '"'; +post("size is " + size); + cell_data_str = ['"', "M", size, 0, "L", 0, 0, 0, size, '"'].join(" "); + + head = ['<svg xmlns="http://www.w3.org/2000/svg" ', + 'width="100%" height="100%" ', + 'opacity=', opacity_str, '>'] + .join(""); + body = ['<defs>', + '<pattern id="cell" patternUnits="userSpaceOnUse" ', + 'width="', size, '" height="', size, '">', + '<path fill="none" stroke="#ddd" stroke-width="1" ', + 'd=', cell_data_str,'/>', + '</pattern>', + '<pattern id="grid" patternUnits="userSpaceOnUse" ', + 'width="100" height="100">', + '<rect width="100" height="100" fill="url(#cell)" />', + '<path fill="none" stroke="#bbb" stroke-width="1" ', + 'd="M 100 0 L 0 0 0 100"/>', + '</pattern>', + '</defs>', + '<rect width="100%" height="100%" fill="url(#grid)" />' + ].join(""); + tail = '</svg>'; + return "url('data:image/svg+xml;utf8," + head + body + tail + "')"; +} // requires nw.js API (Menuitem) function canvas_set_editmode(cid, state) { @@ -1442,15 +1447,17 @@ function canvas_set_editmode(cid, state) { w.set_editmode_checkbox(state !== 0 ? true : false); if (state !== 0) { patchsvg.classList.add("editmode"); - if (showgrid[cid]) { - //post("editmode:" + gui_editmode_svg_background); - patchwin[cid].window.document.body.style.setProperty - ("background-image", gui_editmode_svg_background); - } + // For now, we just change the opacity of the background grid + // depending on whether snap-to-grid is turned on. This way + // edit mode is always visually distinct. + patchwin[cid].window.document.body.style + .setProperty("background-image", + create_editmode_background(showgrid[cid], gridsize[cid])); } else { patchsvg.classList.remove("editmode"); - patchwin[cid].window.document.body.style.setProperty("background-image", - "none"); + patchwin[cid].window.document.body.style + .setProperty("background-image", "none" + ); } }); } @@ -1468,21 +1475,21 @@ function canvas_query_editmode(cid) { exports.canvas_query_editmode = canvas_query_editmode; -function update_grid(grid) { +function update_grid(grid, grid_size_value) { // Update the grid background of all canvas windows when the corresponding // option in the gui prefs changes. - var bg = grid != 0 ? gui_editmode_svg_background : "none"; for (var cid in patchwin) { gui(cid).get_elem("patchsvg", function(patchsvg, w) { var editmode = patchsvg.classList.contains("editmode"); if (editmode) { patchwin[cid].window.document.body.style.setProperty - ("background-image", bg); + ("background-image", create_editmode_background(grid !== 0, + grid_size_value)); } }); } // Also update the showgrid flags. - set_showgrid(grid); + set_grid(grid, grid_size_value); } exports.update_grid = update_grid; @@ -1744,6 +1751,7 @@ var scroll = {}, font = {}, doscroll = {}, showgrid = {}, + gridsize = {}, last_loaded, // last loaded canvas last_focused, // last focused canvas (doesn't include Pd window or dialogs) loading = {}, @@ -1754,9 +1762,11 @@ var scroll = {}, var patchwin = {}; // object filled with cid: [Window object] pairs var dialogwin = {}; // object filled with did: [Window object] pairs -var set_showgrid = function(grid) { - for (var cid in showgrid) { +var set_grid = function(grid, gridsize_value) { + var cid; + for (cid in showgrid) { showgrid[cid] = grid; + gridsize[cid] = gridsize_value; } } @@ -1919,7 +1929,9 @@ function create_window(cid, type, width, height, xpos, ypos, attr_array) { } // create a new canvas -function gui_canvas_new(cid, width, height, geometry, grid, zoom, editmode, name, dir, dirty_flag, warid, hide_scroll, hide_menu, has_toplevel_scalars, cargs) { +function gui_canvas_new(cid, width, height, geometry, grid, grid_size_value, + zoom, editmode, name, dir, dirty_flag, warid, hide_scroll, hide_menu, + has_toplevel_scalars, cargs) { // hack for buggy tcl popups... should go away for node-webkit //reset_ctrl_on_popup_window @@ -1947,6 +1959,7 @@ function gui_canvas_new(cid, width, height, geometry, grid, zoom, editmode, name font[cid] = 10; doscroll[cid] = 0; showgrid[cid] = grid != 0; + gridsize[cid] = grid_size_value; toplevel_scalars[cid] = has_toplevel_scalars; // geometry is just the x/y screen offset "+xoff+yoff" geometry = geometry.slice(1); // remove the leading "+" @@ -6190,10 +6203,10 @@ function gui_midi_properties(gfxstub, sys_indevs, sys_outdevs, } } -function gui_gui_properties(dummy, name, show_grid, save_zoom, browser_doc, browser_path, +function gui_gui_properties(dummy, name, show_grid, grid_size, save_zoom, browser_doc, browser_path, browser_init, autopatch_yoffset) { if (dialogwin["prefs"] !== null) { - dialogwin["prefs"].window.gui_prefs_callback(name, show_grid, save_zoom, + dialogwin["prefs"].window.gui_prefs_callback(name, show_grid, grid_size, save_zoom, browser_doc, browser_path, browser_init, autopatch_yoffset); } } diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c index ef71817264cea7a3ee78a7b194252418b37c434b..1eee2dcdf168840e40df6719a6393f20401717b1 100644 --- a/pd/src/g_editor.c +++ b/pd/src/g_editor.c @@ -2799,6 +2799,9 @@ void canvas_init_menu(t_canvas *x) gui_vmess("gui_menu_font_set_initial_size", "xi", x, x->gl_font); } +extern int sys_snaptogrid; /* whether we are snapping to grid or not */ +extern int sys_gridsize; + void canvas_vis(t_canvas *x, t_floatarg f) { //fprintf(stderr,"canvas_vis .x%zx %f\n", (t_int)x, f); @@ -2809,7 +2812,6 @@ void canvas_vis(t_canvas *x, t_floatarg f) t_gobj *g; t_int properties; - extern int sys_grid; int flag = (f != 0); if (flag) @@ -2865,12 +2867,13 @@ void canvas_vis(t_canvas *x, t_floatarg f) We may need to expand this to include scalars, as well. */ canvas_create_editor(x); canvas_args_to_string(argsbuf, x); - gui_vmess("gui_canvas_new", "xiisiiissiiiiis", + gui_vmess("gui_canvas_new", "xiisiiiissiiiiis", x, (int)(x->gl_screenx2 - x->gl_screenx1), (int)(x->gl_screeny2 - x->gl_screeny1), geobuf, - sys_grid, + sys_snaptogrid, + sys_gridsize, x->gl_zoom, x->gl_edit, x->gl_name->s_name, @@ -3637,6 +3640,8 @@ static int canvas_upx, canvas_upy; static int ctrl_runmode_warned; +static int snap_got_anchor; + /* mouse click */ void canvas_doclick(t_canvas *x, int xpos, int ypos, int which, int mod, int doit) @@ -3646,6 +3651,10 @@ void canvas_doclick(t_canvas *x, int xpos, int ypos, int which, to array_motion so that we can update corresponding send when the array has been changed */ array_garray = NULL; + + /* reset the snap_got_anchor variable so the the snap_to_grid feature + can find its anchor object before it starts to displace a selection */ + snap_got_anchor = 0; //post("canvas_doclick %d", doit); t_gobj *y; @@ -5767,15 +5776,78 @@ void canvas_key(t_canvas *x, t_symbol *s, int ac, t_atom *av) extern void graph_checkgop_rect(t_gobj *z, t_glist *glist, int *xp1, int *yp1, int *xp2, int *yp2); + /* We get the bbox for the object under the mouse the first time around, + then cache its offset from the mouse for future motion messages. + Since there are cases where snapping to a grid can move the object + relative to the mouse pointer, we can't rely on our "anchor" object to + always be directly under the mouse coordinates. */ +static void snap_get_anchor_xy(t_canvas *x, int *gobj_x, int *gobj_y) +{ + t_selection *s = x->gl_editor->e_selection; + int x1, y1, x2, y2; + while (s) + { + if (canvas_hitbox(x, s->sel_what, x->gl_editor->e_xwas, + x->gl_editor->e_ywas, &x1, &y1, &x2, &y2)) + { + *gobj_x = x1; + *gobj_y = y1; + return; + } + s = s->sel_next; + } + bug("canvas_get_snap_offset"); +} + +int anchor_xoff; +int anchor_yoff; + +static void canvas_snap_to_grid(t_canvas *x, int xwas, int ywas, int xnew, + int ynew, int *dx, int *dy) +{ + int gsize = sys_gridsize; + /* If we're snapping to grid, we need an initial delta to align + the object under the mouse to the given gridlines. We keep + that in the variables below, which will have no affect after + our initial grid adjustment. */ + int snap_dx = 0, snap_dy = 0; + if (!snap_got_anchor) + { + int obx = xnew, oby = ynew; + snap_get_anchor_xy(x, &obx, &oby); + /* First, get the distance the selection should be displaced + in order to align the anchor object with a grid line. */ + snap_dx = floor((obx + (gsize / 2)) / gsize) * gsize - obx; + + snap_dy = floor((oby + (gsize / 2)) / gsize) * gsize - oby; + obx = floor(obx / gsize) * gsize; + oby = floor(oby / gsize) * gsize; + anchor_xoff = xnew - obx; + anchor_yoff = ynew - oby; + snap_got_anchor = 1; + } + *dx = floor((xnew - anchor_xoff) / gsize) * gsize - + floor((xwas - anchor_xoff) / gsize) * gsize + snap_dx; + *dy = floor((ynew - anchor_yoff) / gsize) * gsize - + floor((ywas - anchor_yoff) / gsize) * gsize + snap_dy; +} static void delay_move(t_canvas *x) { - canvas_displaceselection(x, - x->gl_editor->e_xnew - x->gl_editor->e_xwas, - x->gl_editor->e_ynew - x->gl_editor->e_ywas); - x->gl_editor->e_xwas = x->gl_editor->e_xnew; - x->gl_editor->e_ywas = x->gl_editor->e_ynew; + int dx, dy; + int xwas = x->gl_editor->e_xwas, ywas = x->gl_editor->e_ywas, + xnew = x->gl_editor->e_xnew, ynew = x->gl_editor->e_ynew; + if (sys_snaptogrid) + canvas_snap_to_grid(x, xwas, ywas, xnew, ynew, &dx, &dy); + else + { + dx = xnew - xwas; + dy = ynew - ywas; + } + canvas_displaceselection(x, dx, dy); + x->gl_editor->e_xwas = xnew; + x->gl_editor->e_ywas = ynew; } void canvas_motion(t_canvas *x, t_floatarg xpos, t_floatarg ypos, diff --git a/pd/src/m_glob.c b/pd/src/m_glob.c index 2147047271868a021bb4f899b9aefdeecf56f6a2..610192b811029ba8455dbeafd98a1be682a7fb3e 100644 --- a/pd/src/m_glob.c +++ b/pd/src/m_glob.c @@ -79,13 +79,14 @@ static void glob_perf(t_pd *dummy, float f) sys_perf = (f != 0); } -extern int sys_grid, sys_zoom, sys_browser_doc, sys_browser_path, sys_browser_init, +extern int sys_snaptogrid, sys_gridsize, sys_zoom, sys_browser_doc, sys_browser_path, sys_browser_init, sys_autopatch_yoffset; extern t_symbol *sys_gui_preset; static void glob_gui_prefs(t_pd *dummy, t_symbol *s, int argc, t_atom *argv) { sys_gui_preset = atom_getsymbolarg(0, argc--, argv++); - sys_grid = !!atom_getintarg(0, argc--, argv++); + sys_snaptogrid = !!atom_getintarg(0, argc--, argv++); + sys_gridsize = atom_getintarg(0, argc--, argv++); sys_zoom = !!atom_getintarg(0, argc--, argv++); sys_browser_doc = !!atom_getintarg(0, argc--, argv++); sys_browser_path = !!atom_getintarg(0, argc--, argv++); @@ -96,10 +97,11 @@ static void glob_gui_prefs(t_pd *dummy, t_symbol *s, int argc, t_atom *argv) /* just the gui-preset, the save-zoom toggle and various help browser options for now */ static void glob_gui_properties(t_pd *dummy) { - gui_vmess("gui_gui_properties", "xsiiiiii", + gui_vmess("gui_gui_properties", "xsiiiiiii", dummy, sys_gui_preset->s_name, - sys_grid, + sys_snaptogrid, + sys_gridsize, sys_zoom, sys_browser_doc, sys_browser_path, diff --git a/pd/src/s_file.c b/pd/src/s_file.c index 0f0b18f258c23d9ad266a71eb86638fcb0ccd320..e78cabcc4ff0ae6ec6db62c4c6765ab6d7548de7 100644 --- a/pd/src/s_file.c +++ b/pd/src/s_file.c @@ -42,7 +42,7 @@ #define snprintf sprintf_s #endif -int sys_defeatrt, sys_autopatch_yoffset, sys_grid = 1, sys_zoom, sys_browser_doc = 1, +int sys_defeatrt, sys_autopatch_yoffset, sys_snaptogrid = 1, sys_gridsize = 10, sys_zoom, sys_browser_doc = 1, sys_browser_path, sys_browser_init; t_symbol *sys_flags = &s_; void sys_doflags( void); @@ -671,7 +671,9 @@ void sys_loadpreferences( void) if (sys_getpreference("defeatrt", prefbuf, MAXPDSTRING)) sscanf(prefbuf, "%d", &sys_defeatrt); if (sys_getpreference("showgrid", prefbuf, MAXPDSTRING)) - sscanf(prefbuf, "%d", &sys_grid); + sscanf(prefbuf, "%d", &sys_snaptogrid); + if (sys_getpreference("gridsize", prefbuf, MAXPDSTRING)) + sscanf(prefbuf, "%d", &sys_gridsize); if (sys_getpreference("savezoom", prefbuf, MAXPDSTRING)) sscanf(prefbuf, "%d", &sys_zoom); if (sys_getpreference("browser_doc", prefbuf, MAXPDSTRING)) @@ -819,8 +821,10 @@ void glob_savepreferences(t_pd *dummy) sys_putpreference("nloadlib", buf1); sprintf(buf1, "%d", sys_defeatrt); sys_putpreference("defeatrt", buf1); - sprintf(buf1, "%d", sys_grid); + sprintf(buf1, "%d", sys_snaptogrid); sys_putpreference("showgrid", buf1); + sprintf(buf1, "%d", sys_gridsize); + sys_putpreference("gridsize", buf1); sprintf(buf1, "%d", sys_zoom); sys_putpreference("savezoom", buf1); sprintf(buf1, "%d", sys_browser_doc);