Commit f4aacadc authored by Jonathan Wilkes's avatar Jonathan Wilkes
Browse files

Merge branch 'pdgui-refactor'

parents da0a9e84 ac61e201
......@@ -637,10 +637,12 @@ function pd_geo_string(w, h, x, y) {
// have it affect an empty canvas' geometry
// requires nw.js API
function gui_canvas_change_geometry(cid, w, h, x, y) {
patchwin[cid].width = w;
patchwin[cid].height = h + 23; // 23 is a kludge to account for menubar
patchwin[cid].x = x;
patchwin[cid].y = y;
gui(cid).get_nw_window(function(nw_win) {
nw_win.width = w;
nw_win.height = h + 23; // 23 is a kludge to account for menubar
nw_win.x = x;
nw_win.y = y;
});
}
// In tcl/tk, this function had some checks to apparently
......@@ -847,11 +849,11 @@ function gui_window_close(cid) {
// for some edge cases like [vis 1, vis 0(--[send subpatch] we
// may not have finished creating the window yet. So we check to
// make sure the canvas cid exists...
if (patchwin[cid]) {
nw_close_window(patchwin[cid]);
}
gui(cid).get_nw_window(function(nw_win) {
nw_close_window(nw_win);
});
// remove reference to the window from patchwin object
patchwin[cid] = null;
set_patchwin(cid, null);
loading[cid] = null;
}
......@@ -974,13 +976,14 @@ function menu_send(name) {
// requires nw.js API (Menuitem)
function canvas_set_editmode(cid, state) {
var patchsvg = patchwin[cid].window.document.querySelector("#patchsvg");
patchwin[cid].window.set_editmode_checkbox(state !== 0 ? true : false);
if (state !== 0) {
patchsvg.classList.add("editmode");
} else {
patchsvg.classList.remove("editmode");
}
gui(cid).get_elem("patchsvg", function(patchsvg, w) {
w.set_editmode_checkbox(state !== 0 ? true : false);
if (state !== 0) {
patchsvg.classList.add("editmode");
} else {
patchsvg.classList.remove("editmode");
}
});
}
exports.canvas_set_editmode = canvas_set_editmode;
......@@ -1258,10 +1261,17 @@ exports.get_patchwin = function(name) {
return patchwin[name];
}
exports.set_patchwin = function(cid, win) {
var set_patchwin = function(cid, win) {
patchwin[cid] = win;
if (win) {
gui.add(cid, win);
} else {
gui.remove(cid, win);
}
}
exports.set_patchwin = set_patchwin;
exports.get_dialogwin = function(name) {
return dialogwin[name];
}
......@@ -1284,52 +1294,53 @@ exports.last_loaded = function () {
// close a canvas window
function gui_canvas_cursor(cid, pd_event_type) {
if (!patchwin[cid]) {
return;
}
var patch = get_item(cid, "patchsvg"),
c;
// A quick mapping of events to pointers-- these can
// be revised later
switch(pd_event_type) {
case "cursor_runmode_nothing":
c = "default";
break;
case "cursor_runmode_clickme":
// The "pointer" icon seems the natural choice for "clickme" here,
// but unfortunately it creates ambiguity with the default editmode
// pointer icon. Not sure what the best solution is, but for now
// we'll just use "default" for clickme. That creates another
// ambiguity, but it's less of an issue since most of the
// clickable runtime items are fairly obvious anyway.
//c = "pointer";
c = "default";
break;
case "cursor_runmode_thicken":
c = "inherit";
break;
case "cursor_runmode_addpoint":
c = "cell";
break;
case "cursor_editmode_nothing":
c = "pointer";
break;
case "cursor_editmode_connect":
c = "-webkit-grabbing";
break;
case "cursor_editmode_disconnect":
c = "no-drop";
break;
case "cursor_editmode_resize":
c = "ew-resize";
break;
case "cursor_editmode_resize_bottom_right": c = "se-resize";
break;
case "cursor_scroll":
c = "all-scroll";
break;
}
patch.style.cursor = c;
gui(cid).get_elem("patchsvg", function(patch) {
// A quick mapping of events to pointers-- these can
// be revised later
var c;
switch(pd_event_type) {
case "cursor_runmode_nothing":
c = "default";
break;
case "cursor_runmode_clickme":
// The "pointer" icon seems the natural choice for "clickme"
// here, but unfortunately it creates ambiguity with the
// default editmode pointer icon. Not sure what the best
// solution is, but for now so we use "default" for clickme.
// That creates another ambiguity, but it's less of an issue
// since most of the clickable runtime items are fairly obvious
// anyway.
//c = "pointer";
c = "default";
break;
case "cursor_runmode_thicken":
c = "inherit";
break;
case "cursor_runmode_addpoint":
c = "cell";
break;
case "cursor_editmode_nothing":
c = "pointer";
break;
case "cursor_editmode_connect":
c = "-webkit-grabbing";
break;
case "cursor_editmode_disconnect":
c = "no-drop";
break;
case "cursor_editmode_resize":
c = "ew-resize";
break;
case "cursor_editmode_resize_bottom_right": c = "se-resize";
break;
case "cursor_scroll":
c = "all-scroll";
break;
}
patch.style.cursor = c;
});
}
// Note: cid can either be a real canvas id, or the string "pd" for the
......@@ -1461,14 +1472,12 @@ function canvas_map(name) {
}
function gui_canvas_erase_all_gobjs(cid) {
var svg_elem,
elem;
if (patchwin[cid]) {
svg_elem = get_item(cid, "patchsvg");
gui(cid).get_elem("patchsvg", function(svg_elem) {
var elem;
while (elem = svg_elem.firstChild) {
svg_elem.removeChild(elem);
}
}
});
}
exports.canvas_map = canvas_map;
......@@ -1808,14 +1817,110 @@ function configure_item(item, attributes) {
// The GUI side probably shouldn't know about "items" on SVG.
function gui_configure_item(cid, tag, attributes) {
var item = get_item(cid, tag);
configure_item(item, attributes);
gui(cid).get_elem(tag, attributes);
}
function add_gobj_to_svg(svg, gobj) {
svg.insertBefore(gobj, svg.querySelector(".cord"));
}
// New interface to incrementally move away from the tcl-like functions
// we're currently using. Basically like a trimmed down jquery, except
// we're dealing with multiple toplevel windows so we need an window id
// to get the correct Pd canvas context. Also, some of Pd's t_text tags
// still have "." in them which unfortunately means we must wrap
// getElementById instead of the more expressive querySelector[All] where
// the "." is interpreted as a CSS class selector.
// Methods:
// get_gobj(id, callbackOrObject) returns a reference to this little canvas
// interface
// get_elem(id, callbackOrObject) returns a reference to this little canvas
// interface
// get_nw_window(callback) returns a reference to the nw.js Window
// wrapper. We keep this separate from the
// others in order to annotate those parts
// of the code which rely on the nw API (and
// abstract them out later if we need to)
// objects are used to set SVG attributes (in the SVG namespace)
// function callbacks have the following args: (DOMElement, window)
// Note about checking for existence:
// Why? Because an iemgui inside a gop canvas will send drawing updates,
// __even__ __if__ that iemgui is outside the bounds of the gop and thus
// not displayed. This would be best fixed in the C code, but I'm not
// exactly sure where or how yet.
// Same problem on Pd Vanilla, except that tk canvas commands on
// non-existent tags don't throw an error.
var gui = (function() {
var c = {}; // object to hold references to all our canvas closures
var last_thing; // last thing we got
var null_fn, null_canvas;
var create_canvas = function(cid, w) {
var get = function(parent, sel, arg, suffix) {
sel = sel + (suffix ? "gobj" : "");
var elem = parent ?
parent.querySelector(sel) :
w.window.document.getElementById(sel);
last_thing = parent ? last_thing : elem;
if (elem) {
if (arg && typeof arg === "object") {
configure_item(elem, arg);
} else if (typeof arg === "function") {
arg(elem, w.window, c[cid]);
}
}
return c[cid];
}
return {
append: !w ? null_fn: function(cb) {
var frag = w.window.document.createDocumentFragment();
frag = cb(frag, w.window);
last_thing.appendChild(frag);
return c[cid];
},
get_gobj: !w ? null_fn : function(sel, arg) {
return get(null, sel, arg, "gobj");
},
get_elem: !w ? null_fn : function(sel, arg) {
return get(null, sel, arg);
},
get_nw_window: !w ? null_fn : function(cb) {
cb(w);
return c[cid];
},
q: !w ? null_fn : function(sel, arg) {
return last_thing ? get(last_thing, sel, arg) : c[cid];
},
debug: function() { return last_thing; }
}
};
// The tcl/tk interface ignores calls to configure non-existent items on
// canvases. Additionally, its interface was synchronous so that the window
// would always be guaranteed to exist. So we create a null canvas to keep
// from erroring out when things don't exist.
null_fn = function() {
return null_canvas;
}
null_canvas = create_canvas(null);
var canvas_container = function(cid) {
last_thing = c[cid] ? c[cid] : null_canvas;
return last_thing;
}
canvas_container.add = function(cid, nw_win) {
c[cid] = create_canvas(cid, nw_win);
}
canvas_container.remove = function(cid, nw_win) {
c[cid] = null;
}
return canvas_container;
}());
// For debugging
exports.gui = gui;
// Most of the following functions map either to pd.tk procs, or in some cases
// tk canvas subcommands
......@@ -1844,34 +1949,28 @@ function add_gobj_to_svg(svg, gobj) {
// creation, in which case a flag to toggle the offset would be appropriate.
function gui_gobj_new(cid, tag, type, xpos, ypos, is_toplevel) {
var svg,
g,
transform_string;
if (patchwin[cid]) {
svg = get_item(cid, "patchsvg"), // id for the svg element
xpos += 0.5;
ypos += 0.5;
transform_string = "matrix(1,0,0,1," + xpos + "," + ypos + ")",
var g;
xpos += 0.5,
ypos += 0.5,
gui(cid).get_elem("patchsvg", function(svg_elem) {
var transform_string = "matrix(1,0,0,1," + xpos + "," + ypos + ")";
g = create_item(cid, "g", {
id: tag + "gobj",
transform: transform_string,
class: type + (is_toplevel !== 0 ? "" : " gop")
});
add_gobj_to_svg(svg, g);
// hm... why returning g and not the return value of appendChild?
return g;
}
add_gobj_to_svg(svg_elem, g);
});
return g;
}
function gui_text_draw_border(cid, tag, bgcolor, isbroken, x1, y1, x2, y2) {
var g,
rect;
if (patchwin[cid]) {
g = get_gobj(cid, tag),
gui(cid).get_gobj(tag)
.append(function(frag) {
// isbroken means either
// a) the object couldn't create or
// b) the box is empty
rect = create_item(cid, "rect", {
var rect = create_item(cid, "rect", {
width: x2 - x1,
height: y2 - y1,
//"shape-rendering": "crispEdges",
......@@ -1880,46 +1979,46 @@ function gui_text_draw_border(cid, tag, bgcolor, isbroken, x1, y1, x2, y2) {
if (isbroken === 1) {
rect.classList.add("broken_border");
}
g.appendChild(rect);
}
frag.appendChild(rect);
return frag;
});
}
function gui_gobj_draw_io(cid, parenttag, tag, x1, y1, x2, y2, basex, basey,
type, i, is_signal, is_iemgui) {
var xlet_class, xlet_id, rect, g;
if (!patchwin[cid]) {
return;
}
g = get_gobj(cid, parenttag);
if (is_iemgui) {
xlet_class = "xlet_iemgui";
// We have an inconsistency here. We're setting the tag using
// string concatenation below, but the "tag" for iemguis arrives
// to us pre-concatenated. We need to remove that formatting in c, and
// in general try to simplify tag creation on the c side as much
// as possible.
xlet_id = tag;
} else if (is_signal) {
xlet_class = "xlet_signal";
xlet_id = tag + type + i;
} else {
xlet_class = "xlet_control";
xlet_id = tag + type + i;
}
rect = create_item(cid, "rect", {
width: x2 - x1,
height: y2 - y1,
x: x1 - basex,
y: y1 - basey,
id: xlet_id,
class: xlet_class,
//"shape-rendering": "crispEdges"
gui(cid).get_gobj(parenttag)
.append(function(frag) {
var xlet_class, xlet_id, rect;
if (is_iemgui) {
xlet_class = "xlet_iemgui";
// We have an inconsistency here. We're setting the tag using
// string concatenation below, but the "tag" for iemguis arrives
// to us pre-concatenated. We need to remove that formatting in c,
// and in general try to simplify tag creation on the c side as
// much as possible.
xlet_id = tag;
} else if (is_signal) {
xlet_class = "xlet_signal";
xlet_id = tag + type + i;
} else {
xlet_class = "xlet_control";
xlet_id = tag + type + i;
}
rect = create_item(cid, "rect", {
width: x2 - x1,
height: y2 - y1,
x: x1 - basex,
y: y1 - basey,
id: xlet_id,
class: xlet_class,
//"shape-rendering": "crispEdges"
});
frag.appendChild(rect);
return frag;
});
g.appendChild(rect);
}
function gui_gobj_redraw_io(cid, parenttag, tag, x, y, type, i, basex, basey) {
var xlet = get_item(cid, tag + type + i);
// We have to check for null. Here's why...
// if you create a gatom:
// canvas_atom -> glist_add -> text_vis -> glist_retext ->
......@@ -1927,45 +2026,40 @@ function gui_gobj_redraw_io(cid, parenttag, tag, x, y, type, i, basex, basey) {
// text_drawborder (firsttime=0) -> glist_drawiofor (firsttime=0)
// This means that a new gatom tries to redraw its inlets before
// it has created them.
if (xlet !== null) {
configure_item(xlet, { x: x - basex, y: y - basey });
}
gui(cid).get_elem(tag + type + i, {
x: x - basex,
y: y - basey
});
}
function gui_gobj_erase_io(cid, tag) {
var xlet = get_item(cid, tag);
xlet.parentNode.removeChild(xlet);
gui(cid).get_elem(tag, function(e) {
e.parentNode.removeChild(e);
});
}
function gui_gobj_configure_io(cid, tag, is_iemgui, is_signal, width) {
var xlet = get_item(cid, tag);
// We have to check for null here. Empty/broken object boxes
// can have "phantom" xlets as placeholders for connections
// to other objects. This may happen due to:
// * autopatching
// * objects which fail to create when loading a patch
if (xlet !== null) {
configure_item(xlet, {
"stroke-width": width,
});
gui(cid).get_elem(tag, {
"stroke-width": width
})
.get_elem(tag, function(e) {
var type;
if (is_iemgui) {
xlet.classList.add("xlet_iemgui");
type = "xlet_iemgui";
} else if (is_signal) {
xlet.classList.add("xlet_signal");
type = "xlet_signal";
} else {
xlet.classList.add("xlet_control");
"xlet_control";
}
// remove xlet_selected tag
xlet.classList.remove("xlet_selected");
}
e.classList.add(type);
e.classList.remove("xlet_selected");
});
}
function gui_gobj_highlight_io(cid, tag) {
var xlet = get_item(cid, tag);
// must check for null (see gui_gobj_configure_io)
if (xlet !== null) {
xlet.classList.add("xlet_selected");
}
gui(cid).get_elem(tag, function(e) {
e.classList.add("xlet_selected");
});
}
function message_border_points(width, height) {
......@@ -1980,42 +2074,35 @@ function message_border_points(width, height) {
}
function gui_message_draw_border(cid, tag, width, height) {
var g,
polygon;
if (!patchwin[cid]) {
return;
}
g = get_gobj(cid, tag);
polygon = create_item(cid, "polygon", {
points: message_border_points(width, height),
fill: "none",
stroke: "black",
class: "border"
//id: tag + "border"
gui(cid).get_gobj(tag)
.append(function(frag) {
var polygon = create_item(cid, "polygon", {
points: message_border_points(width, height),
fill: "none",
stroke: "black",
class: "border"
//id: tag + "border"
});
frag.appendChild(polygon);
return frag;
});
g.appendChild(polygon);
}
function gui_message_flash(cid, tag, state) {
var g = get_gobj(cid, tag);
if (state !== 0) {
g.classList.add("flashed");
} else {
g.classList.remove("flashed");
}
gui(cid).get_gobj(tag, function(e) {
if (state !== 0) {
e.classList.add("flashed");
} else {
e.classList.remove("flashed");
}
});
}
function gui_message_redraw_border(cid, tag, width, height) {
var g, b;
if (patchwin[cid]) {
g = get_gobj(cid, tag);
if (g) {
b = g.querySelector(".border");
configure_item(b, {
points: message_border_points(width, height),
});
}
}
gui(cid).get_gobj(tag)
.q(".border", {
points: message_border_points(width, height)
});
}
function atom_border_points(width, height, is_dropdown) {
......@@ -2040,67 +2127,56 @@ function atom_arrow_points(width, height) {
].join(" ");
}
function gui_atom_draw_border(cid, tag, type, width, height) {
var g, polygon, arrow, m;
if (!patchwin[cid]) {
return;
}
g = get_gobj(cid, tag),
polygon = create_item(cid, "polygon", {
points: atom_border_points(width, height, type !== 0),
fill: "none",
stroke: "gray",
"stroke-width": 1,
class: "border"
//id: tag + "border"
});
g.appendChild(polygon);
if (type !== 0) { // dropdown
// 1 = output index
// 2 = output value
// Let's make the two visually distinct so that the user can still
// reason about the patch functionality merely by reading the diagram
m = height < 20 ? 1 : height / 12;
arrow = create_item(cid, "polygon", {
points: atom_arrow_points(width, height),
"class": type === 1 ? "index_arrow" : "value_arrow"
gui(cid).get_gobj(tag)
.append(function(frag) {
var polygon = create_item(cid, "polygon", {
points: atom_border_points(width, height, type !== 0),
fill: "none",
stroke: "gray",
"stroke-width": 1,
class: "border"
//id: tag + "border"
});
g.appendChild(arrow);
}
frag.appendChild(polygon);
if (type !== 0) { // dropdown
// 1 = output index
// 2 = output value
// Let's make the two visually distinct so that the user can still
// reason about the patch functionality merely by reading the
// diagram