diff --git a/pd/nw/css/default.css b/pd/nw/css/default.css index ea5ea5da9a31f6bdc2d6545e40fcb06a8a72dfd0..da5b7ad6d899dfb3713ab621aa5f5db4802aa9d4 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 6e9da613c3d48c96ffb10ddc1ee07eee7a9542da..0361f595a9f7488770f1f81f4ffc2654d448a438 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 5b6da1ff2d362252e9e38dbd5d5a75b087490679..6c2753b8775a52ec9c7360ae64f55b05aefb0956 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 d2fb09295342f414dc8ab45e2d84e14cc51c0cad..6009838e427c97d726d8208a2125975d93411035 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 a24a6f714f1e53b5b07a87d0f22fa5ad1f7ff6ca..b3567c4df4b0b6b33853b7461fbc8ae30d64ae19 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 d3ed51dab44bf74937fcc6d2a66a9f6b9094c246..1cf072df1179d239037484ed6ace020fb5da397c 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 cc98c265ebed858a603eaead924e56a07925b445..4258618a15b8734d44d8161909cf814af658c5b7 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 fcaa0b44babeb15240c7b6d364fdce636f56a37a..d05c9c1081c47b338423f7d35d2b57902577a082 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 984accf40c4d82eeeb45a4253d3def5e9d2bf26d..441bb158ec428df3fc10149085679023d22f9d5b 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;