From e2e651331b8155eba8b1ea7b2f0cd9a01fc92575 Mon Sep 17 00:00:00 2001 From: Ivica Ico Bukvic <ico@vt.edu> Date: Fri, 12 Jun 2020 02:46:48 -0400 Subject: [PATCH] First checkpoint in implementing custom scrollbars and improving the scrollbar behavior. This is a WIP that requires nw.js 0.46.2. Finished second stage of the scrollbar implementation and added scrolling with middle click. All scrollbars are now operational and to the best of my knowledge accurate. The only things remaining are: getscroll needs to be called on deselect to recalculate when the handles are erased on objects that have them (we should get rid of these and focus on bboxes instead), responding to maximizing and restoring, and addressing a weird bug where in 0.46.2 patches are saved with larger window sizes under certain conditions. Reverted scrolling by grabbing due to problematic relationship between scrollBy and the zoom factor Continuing work on the scrollbars. The only known thing now remaining is ensuring that the patch window is properly recorded when saving it (under certain conditions it spits out completely wrong values Removed debug printouts Finished the scrollbars. The only thing remaining is for someone who has a better understanding of the pd_canvas.js and how the events are captured, to refactor my registration of events that ensure scrollbars are clickable and the onresize event. Currently, they are embedded inside the pd_canvas.js under the body tag where you will find both onscroll and onresize events. Overhaul of the new scrollbars to further improve their behavior, enable as perfect of a fit of the plots both in gop and on toplevel, and dynamic updating of the plots in toplevel when resized. Reworked dynamic event adding/removal for the scrollbars and removed onscroll from the html document and moved it into the pd_canvas.js. Still need to add a delay for the backend callback to update the scrollbars on the toplevel plot/array and thereby remove scrollbar flicker. Finished implementing the scrollbar callback and addressed popup not appearing in the right place (likely due to upgrade to 0.46.2) Added names of arrays to their toplevel subpatch window title. Multiple arrays are listed in the order they have been added. Another reworking of scrollbars to and array sizing to capture all possible scenarios. Some refactoring to make use of the canvas_hasarray. Remaining known issues pertaining to the plots are: *array toplevel windows are not restored in position they were saved *last element on the plots is not clickable *make bezier plot look like bezier (may need to leverage curve_path) --- pd/nw/css/default.css | 7 + pd/nw/index.js | 3 +- pd/nw/pd_canvas.html | 2 + pd/nw/pd_canvas.js | 97 ++++++++++- pd/nw/pdgui.js | 385 ++++++++++++++++++++++++++++++++++++++---- pd/src/g_canvas.c | 56 +++++- pd/src/g_editor.c | 29 ++-- pd/src/g_graph.c | 78 ++++++++- pd/src/g_scalar.c | 33 +++- 9 files changed, 623 insertions(+), 67 deletions(-) diff --git a/pd/nw/css/default.css b/pd/nw/css/default.css index ea5ea5da9..da5b7ad6d 100644 --- a/pd/nw/css/default.css +++ b/pd/nw/css/default.css @@ -20,6 +20,13 @@ body { font-family: "DejaVu Sans Mono"; } +/* delete the default scrollbars, and let's make our own */ +body::-webkit-scrollbar { + width: 0px; + height: 0px; + background: transparent; +} + .noselect { -webkit-touch-callout: none; -webkit-user-select: none; diff --git a/pd/nw/index.js b/pd/nw/index.js index 6e9da613c..0361f595a 100644 --- a/pd/nw/index.js +++ b/pd/nw/index.js @@ -408,7 +408,8 @@ function nw_create_window(cid, type, width, height, xpos, ypos, attr_array) { // the window. Ideally we would just get rid of the canvas menu // altogether to simplify things. But we'd have to add some kind of // widget for the "Put" menu. - height: height + 23, + // ico@vt.edu: on 0.46.2 this is now 25, go figure... + height: height + 25, x: xpos, y: ypos }, function (new_win) { diff --git a/pd/nw/pd_canvas.html b/pd/nw/pd_canvas.html index 5b6da1ff2..6c2753b87 100644 --- a/pd/nw/pd_canvas.html +++ b/pd/nw/pd_canvas.html @@ -76,6 +76,8 @@ </button> </div> </dialog> + <div id="hscroll" style="background-color: #00000044; position: fixed; left: 2px; bottom: 2px; border-radius: 0px; width: 10px; height: 5px; visibility: hidden;"></div> + <div id="vscroll" style="background-color: #00000044; position: fixed; right: 2px; top: 2px; border-radius: 0px; width: 5px; height: 10px; visibility: hidden;"></div> <script type="text/javascript" src="./pd_canvas.js"></script> </body> </html> diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js index d2fb09295..6009838e4 100644 --- a/pd/nw/pd_canvas.js +++ b/pd/nw/pd_canvas.js @@ -233,6 +233,13 @@ var canvas_events = (function() { return false; }, mousedown: function(evt) { + // ico@vt.edu capture middle click for a different type of scroll + // currently disabled due to problem with scrollBy and zoom + /*if (evt.which == 2) + { + evt.stopPropagation(); + evt.preventDefault(); + }*/ var target_id, resize_type; if (target_is_scrollbar(evt)) { return; @@ -521,6 +528,64 @@ var canvas_events = (function() { } canvas_events[canvas_events.get_previous_state()](); }, + hscroll_mouseup: function(evt) { + canvas_events[canvas_events.get_previous_state()](); + }, + hscroll_mousemove: function(evt) { + if (evt.movementX != 0) { + //console.log("move: " + e.movementX); + + var hscroll = document.getElementById("hscroll"); + var svg_elem = document.getElementById("patchsvg"); + + var min_width = document.body.clientWidth + 3; + var width = svg_elem.getAttribute('width'); + var xScrollSize; + + xScrollSize = hscroll.offsetWidth; + + var xTranslate = evt.movementX * + ((width - min_width)/(min_width - xScrollSize)) * + (evt.movementX > 0 ? 1 : 0.75); + if (xTranslate > 0 && xTranslate < 1) { + xTranslate = 1; + } + if (xTranslate < 0 && xTranslate > -1) { + xTranslate = -1; + } + //console.log(xTranslate); + window.scrollBy(xTranslate, 0); + } + }, + vscroll_mouseup: function(evt) { + canvas_events[canvas_events.get_previous_state()](); + }, + vscroll_mousemove: function(evt) { + if (evt.movementY != 0) { + //console.log("move: " + e.movementY); + + var vscroll = document.getElementById("vscroll"); + var svg_elem = document.getElementById("patchsvg"); + + var min_height = document.body.clientHeight + 3; + var height = svg_elem.getAttribute('height'); + var yScrollSize; + + yScrollSize = vscroll.offsetHeight; + + var yTranslate = evt.movementY * + ((height - min_height)/(min_height - yScrollSize)) * + (evt.movementY > 0 ? 2 : 1.5); + if (yTranslate > 0 && yTranslate < 1) { + yTranslate = 1; + } + if (yTranslate < 0 && yTranslate > -1) { + yTranslate = -1; + } + //console.log(yTranslate); + window.scrollBy(0, yTranslate); + } + }, dropdown_menu_keydown: function(evt) { var select_elem = document.querySelector("#dropdown_list"), li; @@ -729,6 +794,16 @@ var canvas_events = (function() { document.addEventListener("mouseup", events.iemgui_label_mouseup, false); }, + hscroll_drag: function() { + canvas_events.none(); + document.addEventListener("mouseup", events.hscroll_mouseup, false); + document.addEventListener("mousemove", events.hscroll_mousemove, false); + }, + vscroll_drag: function() { + canvas_events.none(); + document.addEventListener("mouseup", events.vscroll_mouseup, false); + document.addEventListener("mousemove", events.vscroll_mousemove, false); + }, text: function() { canvas_events.none(); @@ -770,6 +845,9 @@ var canvas_events = (function() { document.addEventListener("keydown", events.find_keydown, false); state = "search"; }, + update_scrollbars: function() { + pdgui.gui_update_scrollbars(name); + }, register: function(n) { name = n; }, @@ -887,6 +965,13 @@ var canvas_events = (function() { console.log("tried to save something"); }, false ); + + // add listener for the scrollbars + document.getElementById("hscroll"). + addEventListener("mousedown", canvas_events.hscroll_drag, false); + document.getElementById("vscroll"). + addEventListener("mousedown", canvas_events.vscroll_drag, false); + // Whoa-- huge workaround! Right now we're getting // the popup menu the way Pd Vanilla does it: // 1) send a mouse(down) message to Pd @@ -1030,15 +1115,16 @@ var canvas_events = (function() { gui.Window.get().on("maximize", function() { pdgui.gui_canvas_get_scroll(name); }); - gui.Window.get().on("unmaximize", function() { + gui.Window.get().on("restore", function() { pdgui.gui_canvas_get_scroll(name); }); gui.Window.get().on("resize", function() { + pdgui.post("resize"); pdgui.gui_canvas_get_scroll(name); }); gui.Window.get().on("focus", function() { nw_window_focus_callback(name); - }); + }); gui.Window.get().on("blur", function() { nw_window_blur_callback(name); }); @@ -1047,6 +1133,12 @@ var canvas_events = (function() { pdgui.pdsend(name, "setbounds", x, y, x + w.width, y + w.height); }); + + // map onscroll event + document.addEventListener("scroll", function() { + pdgui.gui_update_scrollbars(name); + }); + // set minimum window size gui.Window.get().setMinimumSize(150, 100); } @@ -1514,6 +1606,7 @@ function nw_create_patch_window_menus(gui, w, name) { click: function() { var win = gui.Window.get(); win.toggleFullscreen(); + pdgui.gui_canvas_get_scroll(name); } }); diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js index a24a6f714..b3567c4df 100644 --- a/pd/nw/pdgui.js +++ b/pd/nw/pdgui.js @@ -814,16 +814,23 @@ function canvas_check_geometry(cid) { var win_w = patchwin[cid].width, // "23" is a kludge to account for the menubar size. See comment // in nw_create_window of index.js - win_h = patchwin[cid].height - 23, + // ico@vt.edu in 0.46.2 this is now 25 pixels, so I guess + // it is now officially kludge^2 + win_h = patchwin[cid].height - 25, win_x = patchwin[cid].x, win_y = patchwin[cid].y, cnv_width = patchwin[cid].window.innerWidth, - cnv_height = patchwin[cid].window.innerHeight - 23; + cnv_height = patchwin[cid].window.innerHeight - 25; // We're reusing win_x and win_y below, as it // shouldn't make a difference to the bounds - // algorithm in Pd + // algorithm in Pd (ico@vt.edu: this is not true anymore) + //post("relocate " + pd_geo_string(cnv_width, cnv_height, win_x, win_y) + " " + + // pd_geo_string(cnv_width, cnv_height, win_x, win_y)); + // ico@vt.edu: replaced first pd_geo_string's first two args (originally + // win_x and win_y with cnv_width and cnv_height + 25 to ensure the window + // reopens exactly how it was saved) pdsend(cid, "relocate", - pd_geo_string(win_w, win_h, win_x, win_y), + pd_geo_string(cnv_width, cnv_height + 25, win_x, win_y), pd_geo_string(cnv_width, cnv_height, win_x, win_y) ); } @@ -1412,7 +1419,8 @@ var scroll = {}, last_focused, // last focused canvas (doesn't include Pd window or dialogs) loading = {}, title_queue= {}, // ugly kluge to work around an ugly race condition - popup_menu = {}; + popup_menu = {}, + toplevel_scalars = {}; var patchwin = {}; // object filled with cid: [Window object] pairs var dialogwin = {}; // object filled with did: [Window object] pairs @@ -1574,9 +1582,13 @@ function create_window(cid, type, width, height, xpos, ypos, attr_array) { } // create a new canvas -function gui_canvas_new(cid, width, height, geometry, zoom, editmode, name, dir, dirty_flag, hide_scroll, hide_menu, cargs) { +function gui_canvas_new(cid, width, height, geometry, zoom, editmode, name, dir, dirty_flag, hide_scroll, hide_menu, has_toplevel_scalars, cargs) { // hack for buggy tcl popups... should go away for node-webkit //reset_ctrl_on_popup_window + + // ico@vt.edu: added has_toplevel_scalars, which is primarily + // being used for detecting toplevel garrays but may need to be + // expanded to also deal with scalars // local vars for window-specific behavior // visibility of menu and scrollbars, plus canvas background @@ -1597,6 +1609,7 @@ function gui_canvas_new(cid, width, height, geometry, zoom, editmode, name, dir, redo[cid] = false; font[cid] = 10; doscroll[cid] = 0; + toplevel_scalars[cid] = has_toplevel_scalars; // geometry is just the x/y screen offset "+xoff+yoff" geometry = geometry.slice(1); // remove the leading "+" geometry = geometry.split("+"); // x/y screen offset (in pixels) @@ -2604,6 +2617,10 @@ function gui_gobj_select(cid, tag) { function gui_gobj_deselect(cid, tag) { gui(cid).get_gobj(tag, function(e) { e.classList.remove("selected"); + // ico@vt.edu: check for scroll in case the handle disappears + // during deselect. LATER: make handles always fit inside the + // object, so this won't be necessary + gui_canvas_get_scroll(cid); }); } @@ -3430,15 +3447,88 @@ function gui_mycanvas_coords(cid, tag, vis_width, vis_height, select_width, sele }); } +/* this creates a group immediately below the patchsvg object */ function gui_scalar_new(cid, tag, isselected, t1, t2, t3, t4, t5, t6, - is_toplevel) { + is_toplevel, plot_style) { var g; // we should probably use gui_gobj_new here, but we"re doing some initial // scaling that normal gobjs don't need... + //post("gui_scalar_new " + t1 + " " + t2 + + // " " + t3 + " " + t4 + " " + t5 + " " + t6); + + /* ico@vt.edu HACKTASCTIC: calculating scrollbars is throwing 0.997 for + plots drawn inside the subpatch and it is a result of the -1 in the + (min_width - 1) / width call inside canvas_params. Yet, if we don't + call this, we don't have nice flush scrollbars with the regular edges. + This is why here we make a hacklicious hack and simply hide hscrollbar + since the scroll is not doing anything anyhow. + + After further testing, it seems that the aforesaid margin is a hit'n'miss + depending on the patch, so we will disable this and make the aforesaid + canvas_params equation min_width / width. + + if (is_toplevel === 1) { + gui(cid).get_elem("hscroll", function(elem) { + elem.style.setProperty("display", "none"); + }); + gui(cid).get_elem("vscroll", function(elem) { + elem.style.setProperty("display", "none"); + }); + }*/ + gui(cid).get_elem("patchsvg", function(svg_elem) { var matrix, transform_string, selection_rect; - matrix = [t1,t2,t3,t4,t5,t6]; - transform_string = "matrix(" + matrix.join() + ")"; + if (is_toplevel === 1) { + // here we deal with weird scrollbar offsets and + // inconsistencies for the various plot styles. + // the matrix format is xscale, 0, 0, yscale, width, height + // we don't use the matrix for the bar graph since it is + // difficult to get the right ratio, so we do the manual + // translate and scale instead. + // cases are: 0=points, 1=plot, 2=bezier, 3=bars + switch (plot_style) { + case 0: + matrix = [t1,t2,t3,t4,t5,t6+0.5]; + break; + case 1: + matrix = [t1,t2,t3,t4,t5,t6+1.5]; + break; + case 2: + matrix = [t1,t2,t3,t4,t5,t6+1.5]; + break; + case 3: + //matrix = [t1*.995,t2,t3,t4+1,t5+0.5,t6-2]; + matrix = 0; + transform_string = "translate(" + 0 + + "," + (t6+1) + ") scale(" + t1 + "," + t4 + ")"; + //post("transform_string = " + transform_string); + break; + } + } + else { + switch (plot_style) { + case 0: + matrix = [t1,t2,t3,t4,t5,t6+0.5]; + break; + case 1: + matrix = [t1,t2,t3,t4,t5,t6+1.5]; + break; + case 2: + matrix = [t1,t2,t3,t4,t5,t6+1.5]; + break; + case 3: + //matrix = [t1,t2,t3,t4+1,t5+0.5,t6+0.5]; + matrix = 0; + transform_string = "translate(" + (t5+(t1 < 1 ? 0.5 : 1.5)) + + "," + (t6+1) + ") scale(" + t1 + "," + t4 + ")"; + //post("transform_string = " + transform_string); + break; + } + } + + if (matrix !== 0) { + transform_string = "matrix(" + matrix.join() + ")"; + } g = create_item(cid, "g", { id: tag + "gobj", transform: transform_string, @@ -3516,6 +3606,7 @@ function gui_draw_vis(cid, type, attr_array, tag_array) { gui(cid).get_elem(tag_array[0]) .append(function(frag) { var item; + post("gui_draw_vis cid=" + cid + " type=" + type + " attr_array=" + attr_array); attr_array.push("id", tag_array[1]); item = create_item(cid, type, attr_array); frag.appendChild(item); @@ -3617,6 +3708,7 @@ function gui_draw_configure(cid, tag, attr, val) { // the default behavior. function gui_draw_viewbox(cid, tag, attr, val) { // Value will be an empty array if the user provided no values + post("gui_draw_viewbox cid=" + cid + " tag=" + tag + " attr=" + attr + " val=" + val); gui(cid).get_elem("patchsvg", function(svg_elem) { if (val.length) { gui_draw_configure(cid, tag, attr, val) @@ -4676,8 +4768,9 @@ function zoom_kludge(zoom_level) { function gui_canvas_popup(cid, xpos, ypos, canprop, canopen, isobject) { // Get page coords for top of window, in case we're scrolled gui(cid).get_nw_window(function(nw_win) { - var win_left = nw_win.window.document.body.scrollLeft, - win_top = nw_win.window.document.body.scrollTop, + // ico@vt.edu updated win_left and win_top for the 0.46.2 + var win_left = nw_win.window.scrollX, + win_top = nw_win.window.scrollY, zoom_level = nw_win.zoomLevel, // these were used to work zfactor, // around an old nw.js popup pos // bug. Now it's only necessary @@ -5742,11 +5835,64 @@ function gui_undo_menu(cid, undo_text, redo_text) { }); } +function zoom_level_to_chrome_percent(nw_win) { + var zoom = nw_win.zoomLevel; + switch (zoom) { + case -7: + zoom = 4; + break; + case -6: + zoom = 100/33; + break; + case -5: + zoom = 2; + break; + case -4: + zoom = 100/67; + break; + case -3: + zoom = 100/75; + break; + case -2: + zoom = 100/80; + break; + case -1: + zoom = 100/90; + break; + case 0: + zoom = 1; + break; + case 1: + zoom = 100/110; + break; + case 2: + zoom = 100/125; + break; + case 3: + zoom = 100/150; + break; + case 4: + zoom = 100/175; + break; + case 5: + zoom = 100/200; + break; + case 6: + zoom = 100/250; + break; + case 7: + zoom = 100/300; + break; + } + return zoom; +} + // leverages the get_nw_window method in the callers... function canvas_params(nw_win) { // calculate the canvas parameters (svg bounding box and window geometry) // for do_getscroll and do_optimalzoom + //post("nw_win=" + nw_win + " " + nw_win.window + " " + nw_win.window.document); var bbox, width, height, min_width, min_height, x, y, svg_elem; svg_elem = nw_win.window.document.getElementById("patchsvg"); bbox = svg_elem.getBBox(); @@ -5760,22 +5906,18 @@ function canvas_params(nw_win) x = bbox.x > 0 ? 0 : bbox.x, y = bbox.y > 0 ? 0 : bbox.y; - // The svg "overflow" attribute on an <svg> seems to be buggy-- for example, - // you can't trigger a mouseover event for a <rect> that is outside of the - // explicit bounds of the svg. - // To deal with this, we want to set the svg width/height to always be - // at least as large as the browser's viewport. There are a few ways to - // do this this, like documentElement.clientWidth, but window.innerWidth - // seems to give the best results. - // However, there is either a bug or some strange behavior regarding - // the viewport size: setting both the height and width of an <svg> to - // the viewport height/width will display the scrollbars. The height or - // width must be set to 4 less than the viewport size in order to keep - // the scrollbars from appearing. Here, we just subtract 4 from both - // of them. This could lead to some problems with event handlers but I - // haven't had a problem with it yet. - min_width = nw_win.window.innerWidth - 4; - min_height = nw_win.window.innerHeight - 4; + // ico@vt.edu: adjust body width and height to match patchsvg to ensure + // scrollbars only come up when we are indeed inside svg and not before + // with extra margins around. This is accurate to a pixel on nw 0.47.0. + // This is also needed when maximizing and restoring the window in order + // to trigger resizing of scrollbars. + min_width = nw_win.window.innerWidth; + min_height = nw_win.window.innerHeight; + + var body_elem = nw_win.window.document.body; + body_elem.style.width = min_width + "px"; + body_elem.style.height = min_height + "px"; + // Since we don't do any transformations on the patchsvg, // let's try just using ints for the height/width/viewBox // to keep things simple. @@ -5785,11 +5927,78 @@ function canvas_params(nw_win) min_height |= 0; x |= 0; y |= 0; + + /* ico@vt.edu: now let's draw/update our own scrollbars, so that we + don't have to deal with the window size nonsense caused by the + built-in ones... */ + // zoom var is used to compensate for the zoom level and keep + // the scrollbars the same height + var zoom = zoom_level_to_chrome_percent(nw_win); + var yScrollSize, yScrollTopOffset; + var vscroll = nw_win.window.document.getElementById("vscroll"); + yScrollSize = min_height / height; // used to be (min_height - 1) / height + yScrollTopOffset = Math.floor((nw_win.window.scrollY / height) * (min_height + 3)); + + // yScrollSize reflects the amount of the patch we currently see, + // so if it drops below 1, that means we need our scrollbars + if (yScrollSize < 1) { + var yHeight = Math.floor(yScrollSize * (min_height + 3)); + vscroll.style.setProperty("height", (yHeight - 6) + "px"); + vscroll.style.setProperty("top", (yScrollTopOffset + 2) + "px"); + vscroll.style.setProperty("-webkit-clip-path", + "polygon(0px 0px, 5px 0px, 5px " + (yHeight - 6) + + "px, 0px " + (yHeight - 11) + "px, 0px 5px)"); + vscroll.style.setProperty("width", (5 * zoom) + "px"); + vscroll.style.setProperty("right", (2 * zoom) + "px"); + vscroll.style.setProperty("visibility", "visible"); + } else { + vscroll.style.setProperty("visibility", "hidden"); + } + + var xScrollSize, xScrollLeftOffset; + var hscroll = nw_win.window.document.getElementById("hscroll"); + xScrollSize = min_width / width; // used to be (min_width - 1) / width + xScrollLeftOffset = Math.floor((nw_win.window.scrollX / width) * (min_width + 3)); + + if (xScrollSize < 1) { + var xWidth = Math.floor(xScrollSize * (min_width + 3)); + hscroll.style.setProperty("width", (xWidth - 6) + "px"); + hscroll.style.setProperty("left", (xScrollLeftOffset + 2) + "px"); + hscroll.style.setProperty("-webkit-clip-path", + "polygon(0px 0px, " + (xWidth - 11) + "px 0px, " + + (xWidth - 6) + "px 5px, 0px 5px)"); + hscroll.style.setProperty("height", (5 * zoom) + "px"); + hscroll.style.setProperty("bottom", (2 * zoom) + "px"); + hscroll.style.setProperty("visibility", "visible"); + } else { + hscroll.style.setProperty("visibility", "hidden"); + } + + //post("x=" + xScrollSize + " y=" + yScrollSize); + return { x: x, y: y, w: width, h: height, mw: min_width, mh: min_height }; } -function do_getscroll(cid) { + +// ico@vt.edu: +// the timeout is a bad hack and does not solve the problem consistently +// even on a fast computer, while also slowing down the overall user +// experience. As such, ti is disabled and left here for reference. +/*var pd_getscroll_var = {}; + +function pd_do_getscroll(cid) { + if (!pd_getscroll_var[cid]) { + pd_getscroll_var[cid] = setTimeout(function() { + do_getscroll(cid, 0); + pd_getscroll_var[cid] = null; + }, 250); + } +} + +exports.pd_do_getscroll = pd_do_getscroll;*/ + +function do_getscroll(cid, checkgeom) { // Since we're throttling these getscroll calls, they can happen after // the patch has been closed. We remove the cid from the patchwin // object on close, so we can just check to see if our Window object has @@ -5797,6 +6006,11 @@ function do_getscroll(cid) { // This is an awfully bad pattern. The whole scroll-checking mechanism // needs to be rethought, but in the meantime this should prevent any // errors wrt the rendering context disappearing. + //post("do_getscroll " + checkgeom); + if (checkgeom == 1) { + canvas_check_geometry(cid); + return; + } gui(cid).get_nw_window(function(nw_win) { var svg_elem = nw_win.window.document.getElementById("patchsvg"); var { x: x, y: y, w: width, h: height, @@ -5820,7 +6034,11 @@ function do_getscroll(cid) { }); } +exports.do_getscroll = do_getscroll; + var getscroll_var = {}; +var checkgeom_and_getscroll_var = {}; +var overriding_getscroll_var = {}; // We use a setTimeout here for two reasons: // 1. nw.js has a nasty Renderer bug when you try to modify the @@ -5835,16 +6053,51 @@ var getscroll_var = {}; // graphics from displaying until the user releases the mouse, // which would be a buggy UI function gui_canvas_get_scroll(cid) { + //post("win=" + cid); + //win_width = win.style.width; + //win_height = win.style.height; + if (toplevel_scalars[cid]) { + // we have scalars, so let's override the previous call + // because this will be a cpu intensive redraw, so we + // should do it only when the action that requested it + // is either done or stopped for long enough for the + // recalculation to happen + if (getscroll_var[cid]) { + clearTimeout(getscroll_var[cid]); + getscroll_var[cid] = null; + } + } if (!getscroll_var[cid]) { - getscroll_var[cid] = setTimeout(function() { - do_getscroll(cid); + getscroll_var[cid] = setTimeout(function() { + do_getscroll(cid, toplevel_scalars[cid]); getscroll_var[cid] = null; - }, 250); + }, 50); } } exports.gui_canvas_get_scroll = gui_canvas_get_scroll; +/* ico@vt.edu: here is one alternative getscroll call, it focuses on + overriding the previous call, so the getscroll is more delayed. This + is useful when manipulating a plot with a mouse, for instance, so that + we prevent excessive getscroll calls which can be rather cpu intensive. +*/ + +function gui_canvas_get_overriding_scroll(cid) { + //post("win=" + cid); + //win_width = win.style.width; + //win_height = win.style.height; + if (overriding_getscroll_var[cid]) { + clearTimeout(overriding_getscroll_var[cid]); + } + overriding_getscroll_var[cid] = setTimeout(function() { + do_getscroll(cid, 0); + overriding_getscroll_var[cid] = null; + }, 100); +} + +exports.gui_canvas_get_overriding_scroll = gui_canvas_get_overriding_scroll; + function do_optimalzoom(cid, hflag, vflag) { // determine an optimal zoom level that makes the entire patch fit within // the window @@ -6008,3 +6261,73 @@ function gui_pddplink_open(filename, dir) { post("pddplink: error: file not found: " + filename); } } + + +/* ico@vt.edu: this function is run when we scroll with a mouse wheel, + a touchpad (e.g. two-finger scroll), or some other HID. It is + linked from the pd_canvas.js and called from the garray_fittograph 1 + call when we are resizing the plot toplevel window to avoid race condition. +*/ +function gui_update_scrollbars(cid) { + //post("gui_update_scrollbars " + cid); + gui(cid).get_nw_window(function(nw_win) { + var hscroll = nw_win.window.document.getElementById("hscroll"); + var vscroll = nw_win.window.document.getElementById("vscroll"); + var svg_elem = nw_win.window.document.getElementById("patchsvg"); + + if (vscroll.style.visibility == "visible") + { + var height, min_height; + min_height = nw_win.window.innerHeight + 3; + height = svg_elem.getAttribute('height'); + + var yScrollSize, yScrollTopOffset; + yScrollSize = (min_height - 4) / height; + yScrollTopOffset = Math.floor((nw_win.window.scrollY / height) * min_height); + + if (yScrollSize < 1) { + var yHeight = Math.floor(yScrollSize * min_height); + vscroll.style.setProperty("height", (yHeight - 6) + "px"); + vscroll.style.setProperty("top", (yScrollTopOffset + 2) + "px"); + vscroll.style.setProperty("-webkit-clip-path", + "polygon(0px 0px, 5px 0px, 5px " + (yHeight - 6) + + "px, 0px " + (yHeight - 11) + "px, 0px 5px)"); + vscroll.style.setProperty("visibility", "visible"); + } else { + vscroll.style.setProperty("visibility", "hidden"); + } + } + + if (hscroll.style.visibility == "visible") + { + var min_width = nw_win.window.innerWidth + 3; + var width = svg_elem.getAttribute('width'); + var xScrollSize, xScrollTopOffset; + + xScrollSize = (min_width - 4) / width; + xScrollTopOffset = Math.floor((nw_win.window.scrollX / width) * min_width); + + /* console.log("win_width=" + min_width + " bbox=" + + width + " xScrollSize=" + (xScrollSize * min_width) + + " topOffset=" + xScrollTopOffset); */ + + if (xScrollSize < 1) { + var xWidth = Math.floor(xScrollSize * min_width); + hscroll.style.setProperty("width", (xWidth - 6) + "px"); + hscroll.style.setProperty("left", (xScrollTopOffset + 2) + "px"); + hscroll.style.setProperty("-webkit-clip-path", + "polygon(0px 0px, " + (xWidth - 11) + "px 0px, " + + (xWidth - 6) + "px 5px, 0px 5px)"); + hscroll.style.setProperty("visibility", "visible"); + } else { + hscroll.style.setProperty("visibility", "hidden"); + } + } + // for future reference + //nw_win.document.getElementById("hscroll"). + // style.setProperty("visibility", "visible"); + //console.log("width="+width); + }); +} + +exports.gui_update_scrollbars = gui_update_scrollbars; diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c index d3ed51dab..1cf072df1 100644 --- a/pd/src/g_canvas.c +++ b/pd/src/g_canvas.c @@ -713,16 +713,38 @@ void canvas_args_to_string(char *namebuf, t_canvas *x) strcpy(namebuf, " ("); for (i = 0; i < env->ce_argc; i++) { - if (strlen(namebuf) > MAXPDSTRING/2 - 5) + if (strlen(namebuf) > MAXPDSTRING / 2 - 5) break; if (i != 0) strcat(namebuf, " "); - atom_string(&env->ce_argv[i], namebuf + strlen(namebuf), - MAXPDSTRING/2); + atom_string(&env->ce_argv[i], namebuf + strlen(namebuf), + MAXPDSTRING / 2); } strcat(namebuf, ")"); } - else namebuf[0] = 0; + else + { + namebuf[0] = 0; + t_gobj *g = NULL; + t_garray *a = NULL; + t_symbol *arrayname; + int found = 0, res; + for (g = x->gl_list; g; g = g->g_next) + { + + if (pd_class(&g->g_pd) == garray_class) + { + res = garray_getname((t_garray *)g, &arrayname); + if (found) + { + strcat(namebuf, " "); + } + strcat(namebuf, arrayname->s_name); + found++; + //post("found=%d %s %s", found, arrayname->s_name, namebuf); + } + } + } } void canvas_reflecttitle(t_canvas *x) @@ -1314,8 +1336,8 @@ static void canvas_relocate(t_canvas *x, t_symbol *canvasgeom, < 4 || sscanf(topgeom->s_name, "%dx%d+%d+%d", &tw, &th, &txpix, &typix) < 4) bug("canvas_relocate"); - /* for some reason this is initially called with cw=ch=1 so - we just suppress that here. */ + /* for some reason this is initially called with cw=ch=1 so + we just suppress that here. */ if (cw > 5 && ch > 5) canvas_dosetbounds(x, txpix, typix, txpix + cw, typix + ch); @@ -1325,24 +1347,44 @@ static void canvas_relocate(t_canvas *x, t_symbol *canvasgeom, t_array *a = NULL; int num_elem = 0; + //int found_garray = 0; + for (g = x->gl_list; g; g = g->g_next) { //fprintf(stderr, "searching\n"); - + //post("searching"); //for subpatch garrays if (pd_class(&g->g_pd) == garray_class) { //fprintf(stderr,"found ya\n"); + //post("found ya"); ga = (t_garray *)g; if (ga) { a = garray_getarray(ga); num_elem = a->a_n; garray_fittograph(ga, num_elem, 1); + //found_garray = 1; } } } canvas_checkconfig(x); + + // ico@vt.edu: + // Here we update only scrollbars to avoid race condition + // caused by doing gui_canvas_get_scroll which in turn + // calls canvas_relocate to ensure garrays in subpatches + // are properly updated when those windows are resized. + // given that the scroll update will happen likely faster + // than the return value from the backend, we do this to + // get rid of the stale scrollbars, e.g. when making the + // window smaller (at first the scrollbars are there because + // the garray has not been redrawn yet, and then we update + // scrollbars once again here below. + //if (found_garray == 1) { + //post("found garray"); + gui_vmess("do_getscroll", "xi", x, 0); + //} } void canvas_popabstraction(t_canvas *x) diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c index cc98c265e..4258618a1 100644 --- a/pd/src/g_editor.c +++ b/pd/src/g_editor.c @@ -172,7 +172,7 @@ static void canvas_nlet_conf (t_canvas *x, int type) { void canvas_getscroll (t_canvas *x) { //sys_vgui("pdtk_canvas_getscroll .x%lx.c\n",(long)x); - gui_vmess("gui_canvas_get_scroll", "x", x); + gui_vmess("gui_canvas_get_overriding_scroll", "x", x); } /* ---------------- generic widget behavior ------------------------- */ @@ -2299,7 +2299,11 @@ static void canvas_rightclick(t_canvas *x, int xpos, int ypos, t_gobj *y_sel) otherwise they end-up being dirty without visible notification besides, why would one mess with their properties without seeing what is inside them? CURRENTLY DISABLED */ + //post("canvas_rightclick %d", (y && pd_class(&y->g_pd) == garray_class ? 1 : 0)); canprop = (!y || (y && class_getpropertiesfn(pd_class(&y->g_pd))) + // ico@vt.edu: the following ensures that even if we are clicking on + // a garray, we should still be able to get the canvas properties + || (y && pd_class(&y->g_pd) == garray_class) /*&& !canvas_isabstraction( ((t_glist*)y) )*/ ); canopen = (y && zgetfn(&y->g_pd, gensym("menu-open"))); /* we add an extra check for scalars to enable the "Open" button @@ -2478,9 +2482,12 @@ void canvas_vis(t_canvas *x, t_floatarg f) canvas_destroy_editor(x); } //fprintf(stderr,"new\n"); + /* ico@vt.edu: here we use hasarray only for toplevel garrays + because they require check_config every time resize is invoked. + 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", "xiisiissiiis", + gui_vmess("gui_canvas_new", "xiisiissiiiis", x, (int)(x->gl_screenx2 - x->gl_screenx1), (int)(x->gl_screeny2 - x->gl_screeny1), @@ -2492,6 +2499,7 @@ void canvas_vis(t_canvas *x, t_floatarg f) x->gl_dirty, x->gl_noscroll, x->gl_nomenu, + canvas_hasarray(x), argsbuf); /* It looks like this font size call is no longer needed, @@ -2618,13 +2626,7 @@ void canvas_setgraph(t_glist *x, int flag, int nogoprect) // check if we have array inside GOP, if so, // make sure hidetext is always hidden no matter what - t_gobj *g = x->gl_list; - int hasarray = 0; - while (g) - { - if (pd_class(&g->g_pd) == garray_class) hasarray = 1; - g = g->g_next; - } + int hasarray = canvas_hasarray(x); if (hasarray) x->gl_hidetext = 1; if (!nogoprect && !x->gl_goprect && !hasarray) @@ -2810,13 +2812,7 @@ static void canvas_donecanvasdialog(t_glist *x, // check if we have array inside GOP, if so, // make sure GOP/hidetext is always enabled no matter what - t_gobj *g = x->gl_list; - int hasarray = 0; - while (g) - { - if (pd_class(&g->g_pd) == garray_class) hasarray = 1; - g = g->g_next; - } + int hasarray = canvas_hasarray(x); if (hasarray && graphme != 3) { graphme = 3; //gop flag + bit-shifted hidetext @@ -3233,6 +3229,7 @@ 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; + //post("canvas_doclick %d", doit); t_gobj *y; int shiftmod, runmode, altmod, doublemod = 0, rightclick, diff --git a/pd/src/g_graph.c b/pd/src/g_graph.c index fcaa0b44b..d05c9c108 100644 --- a/pd/src/g_graph.c +++ b/pd/src/g_graph.c @@ -725,13 +725,34 @@ t_float glist_pixelstoy(t_glist *x, t_float ypix) /* convert an x coordinate value to an x pixel location in window */ t_float glist_xtopixels(t_glist *x, t_float xval) { + // ico@vt.edu: used to deal with the bar graph + t_float plot_offset = 0; + t_gobj *g = x->gl_list; + if (!x->gl_isgraph) return ((xval - x->gl_x1) / (x->gl_x2 - x->gl_x1)); else if (x->gl_isgraph && x->gl_havewindow) - return (x->gl_screenx2 - x->gl_screenx1) * - (xval - x->gl_x1) / (x->gl_x2 - x->gl_x1); + { + if (g != NULL && g->g_pd == garray_class) + { + t_garray *g_a = (t_garray *)g; + if (garray_get_style(g_a) == PLOTSTYLE_BARS) + plot_offset = 10; + } + //fprintf(stderr, "xtopixels xval=%f gl_x1=%f gl_x2=%f screenx1=%d screenx2=%d\n", + // xval, x->gl_x1, x->gl_x2, x->gl_screenx1, x->gl_screenx2); + return (x->gl_screenx2 - x->gl_screenx1) * + (xval - x->gl_x1 - plot_offset) / (x->gl_x2 - x->gl_x1); + } else { + /* ico@vt.edu: some really stupid code to compensate for the fact + that the svg stroke featue adds unaccounted width to the bars */ + if (g != NULL && g->g_pd == garray_class) + { + if (garray_get_style((t_garray *)g) == PLOTSTYLE_BARS) + plot_offset = 2; + } int x1, y1, x2, y2; if (!x->gl_owner) bug("glist_pixelstox"); @@ -742,13 +763,62 @@ t_float glist_xtopixels(t_glist *x, t_float xval) t_float glist_ytopixels(t_glist *x, t_float yval) { + t_float plot_offset = 0; + t_gobj *g = x->gl_list; + if (!x->gl_isgraph) return ((yval - x->gl_y1) / (x->gl_y2 - x->gl_y1)); else if (x->gl_isgraph && x->gl_havewindow) - return (x->gl_screeny2 - x->gl_screeny1) * - (yval - x->gl_y1) / (x->gl_y2 - x->gl_y1); + { + if (g != NULL && g->g_pd == garray_class) + { + /*t_garray *g_a = (t_garray *)g; + if (garray_get_style((t_garray *)g) == PLOTSTYLE_POLY || + garray_get_style((t_garray *)g) == PLOTSTYLE_BEZ)*/ + switch (garray_get_style((t_garray *)g)) + { + case PLOTSTYLE_POINTS: + plot_offset = 2; + break; + case PLOTSTYLE_POLY: + plot_offset = 2; + break; + case PLOTSTYLE_BEZ: + plot_offset = 2; + break; + case PLOTSTYLE_BARS: + plot_offset = 2; + break; + } + } + return (x->gl_screeny2 - x->gl_screeny1 - plot_offset) * + (yval - x->gl_y1) / (x->gl_y2 - x->gl_y1); + } else { + /* ico@vt.edu: some really stupid code to compensate for the fact + that the poly and bezier tend to overlap the GOP edges */ + if (g != NULL && g->g_pd == garray_class) + { + /*t_garray *g_a = (t_garray *)g; + if (garray_get_style((t_garray *)g) == PLOTSTYLE_POLY || + garray_get_style((t_garray *)g) == PLOTSTYLE_BEZ)*/ + switch (garray_get_style((t_garray *)g)) + { + case PLOTSTYLE_POINTS: + plot_offset = 2; + break; + case PLOTSTYLE_POLY: + plot_offset = 2; + break; + case PLOTSTYLE_BEZ: + plot_offset = 2; + break; + case PLOTSTYLE_BARS: + plot_offset = 2; + break; + } + } int x1, y1, x2, y2; if (!x->gl_owner) bug("glist_pixelstoy"); diff --git a/pd/src/g_scalar.c b/pd/src/g_scalar.c index 984accf40..441bb158e 100644 --- a/pd/src/g_scalar.c +++ b/pd/src/g_scalar.c @@ -1028,6 +1028,26 @@ static void scalar_vis(t_gobj *z, t_glist *owner, int vis) { t_float xscale = glist_xtopixels(owner, 1) - glist_xtopixels(owner, 0); t_float yscale = glist_ytopixels(owner, 1) - glist_ytopixels(owner, 0); + // this has been moved into pdgui.js gui_scalar_new, leaving here just + // in case the other implementation proves problematic + //t_float nw_yoffset = 0; + /*switch (plot_style) + { + case 0: + nw_yoffset = -0.5; + yscale += 1; + break; + case 1: + nw_yoffset = 1.5; + break; + case 2: + nw_yoffset = 1.5; + break; + case 3: + nw_yoffset = 0.5; + yscale += 1; + break; + }*/ /* we translate the .scalar%lx group to displace it on the tk side. This is the outermost group for the scalar, something like a poor man's viewport. @@ -1040,14 +1060,15 @@ static void scalar_vis(t_gobj *z, t_glist *owner, int vis) understand "None"-- instead we must send an empty symbol.) */ char tagbuf[MAXPDSTRING]; sprintf(tagbuf, "scalar%lx", (long unsigned int)x->sc_vec); - gui_vmess("gui_scalar_new", "xsiffffiii", - glist_getcanvas(owner), + gui_vmess("gui_scalar_new", "xsiffffffii", + glist_getcanvas(owner), tagbuf, glist_isselected(owner, &x->sc_gobj), xscale, 0.0, 0.0, yscale, - (int)glist_xtopixels(owner, basex), - (int)glist_ytopixels(owner, basey), - glist_istoplevel(owner)); + glist_xtopixels(owner, basex), + glist_ytopixels(owner, basey), + glist_istoplevel(owner), + plot_style); char groupbuf[MAXPDSTRING]; // Quick hack to make gui_scalar_draw_group more general (so we // don't have to tack on "gobj" manually) @@ -1267,7 +1288,7 @@ int scalar_doclick(t_word *data, t_template *template, t_scalar *sc, static int scalar_click(t_gobj *z, struct _glist *owner, int xpix, int ypix, int shift, int alt, int dbl, int doit) { - //fprintf(stderr,"scalar_click %d %d\n", xpix, ypix); + //post("scalar_click %d %d %d", xpix, ypix, doit); t_scalar *x = (t_scalar *)z; x->sc_bboxcache = 0; -- GitLab