diff --git a/lib/g.js b/lib/g.js index 8b83af2..d36691c 100644 --- a/lib/g.js +++ b/lib/g.js @@ -867,6 +867,7 @@ if (--remainingRequests.count == 0) onRemoteSketchesFinishedLoading(); + } sketchRequest.send(); } @@ -1145,6 +1146,8 @@ eval("addSketch(new " + type + "())"); sketchTypeLabels.push(sk().labels); + glyphChart.addTags(sk().tags); + // RENDER EACH OF ITS SELECTIONS ONCE TO CREATE GLYPH INFO. @@ -1166,7 +1169,7 @@ if (status == 0) { code = sketchTypeToCode(type, sk().labels[n]); - names.push(registerGlyph(code, glyphTrace, sk().labels[n])); + names.push(registerGlyph(code, glyphTrace, sk().labels[n], sk().tags)); } } @@ -1443,7 +1446,7 @@ } } - function registerGlyph(name, strokes, indexName) { + function registerGlyph(name, strokes, indexName, tags) { if (indexName === undefined) { indexName = name; var j = indexName.indexOf('Sketch'); @@ -1466,6 +1469,10 @@ var glyph = new SketchGlyph(name, strokes); glyph.indexName = indexName; + if (tags) { + glyph.assignTags(tags); + } + for (var i = 0 ; i < glyphs.length ; i++) if (indexName < glyphs[i].indexName) { glyphs.splice(i, 0, glyph); @@ -2865,6 +2872,8 @@ if (! isPhone() && window.imuData) if (sketch.labels.length == 0) sketch.labels.push(sketch.label); + if (sketch.tags.length == 0 && sketch.tag) + sketch.tags.push(sketch.tag); sketchPage.add(sketch); sk().arrows = []; sk().children = []; diff --git a/lib/glyphChart.js b/lib/glyphChart.js index ff7ece3..f5bd1d9 100644 --- a/lib/glyphChart.js +++ b/lib/glyphChart.js @@ -4,7 +4,18 @@ function GlyphChart() { this.t = 0; this.iDragged = 0; this.isDragging = false; + this.isSelectingTag = false; + this.isClicked = false; + this.iSelectedTag = 0; + this.hoveredOverTag = null; + this.strokeCountMask = 0; + + // ARRAY OF REGISTERED TAGS + this.tags = ['']; + // MAP OF ON / OFF STATES FOR EACH TAG + this.tagStates = {}; + this.numActiveTags = 0; } GlyphChart.prototype = { @@ -57,26 +68,172 @@ GlyphChart.prototype = { for (i = 0 ; i < glyphs.length ; i++) { b = this.bounds(i); if (mx >= b[0] && my >= b[1] && mx < b[2] && my < b[3]) { - if ( mx - b[0] + my - b[1] > this.gR || - mx - b[0] + b[3] - my > this.gR || - b[2] - mx + my - b[1] > this.gR || - b[2] - mx + b[3] - my > this.gR ) - this.isAnySelected = true; - break; - - } + if (mx - b[0] + my - b[1] > this.gR || + mx - b[0] + b[3] - my > this.gR || + b[2] - mx + my - b[1] > this.gR || + b[2] - mx + b[3] - my > this.gR) + this.isAnySelected = true; + break; + } } + this.drawTagList(); + for (i = 0 ; i < glyphs.length ; i++) this.drawGlyph(i); if (this.isDragging) this.drawGlyph(this.iDragged, This().mouseX, This().mouseY); + this.hoveredOverTag = null; + _g.restore(); sketchPage.setFuzzyLines(this._isFuzzyLines); }, + + // TODO: "RESET TAGS" IS TEMPORARILY A TAG BUTTON + // (SHOULD CHANGE INTO SOMETHING ELSE SO AS NOT TO BE CONFUSED WITH ACTUAL TAGS) + resetTagHeader : "Reset Tags", + + heightScale : 1.5, + tagBounds : function(i) { + const txtLen = (i == 0) ? this.resetTagHeader.length : this.tags[i].length; + var ht = this.heightScale * textHeight(); + var strLen = textWidth(((i == 0) ? this.resetTagHeader : this.tags[i]) + ' '); + var x = width() - strLen - _g.panX; + // THIS COMMENTED CODE WOULD CYCLE THE BOX AROUND TO THE NEXT COLUMN, + // DISABLED FOR NOW FOR TAGS, LIKELY TO RE-ACTIVATED LATER + var y = ((i /*% this.glyphsPerCol*/) * height()) / (height() / ht) + ht * .1 - _g.panY; + + return [ x, y , x + strLen + 20, y + ht]; + }, + addTags : function(tags) { + tags.sort(); + + for (let k = 0, i = 1; k < tags.length; k++) { + const tag = tags[k]; + if (this.tagStates[tag] !== undefined) { + continue; + } + this.tagStates[tag] = false; + for (; i < this.tags.length; i++) { + if (tag < this.tags[i]) { + this.tags.splice(i, 0, tag); + i++; + break; + } + } + if (i == this.tags.length) { + for (; k < tags.length; k++) { + this.tags.push(tags[k]); + } + } + } + }, + disableAllTags : function() { + for (let i = 1; i < this.tags.length; i++) { + this.tagStates[this.tags[i]] = false; + } + this.numActiveTags = 0; + }, + drawTagList : function() { + var mx = This().mouseX, my = This().mouseY, i, b; + + _g.save(); + + const that = this; + + function drawTag(i) { + var b = that.tagBounds(i); + var gX = b[0], gY = b[1], gW = b[2]-b[0], gH = b[3]-b[1]; + + var x = gX + (height() / (that.heightScale * textHeight())) * .1; + var y = gY; + var t = (that.t - i - 0.55) * 2.5 + i + .5; + var isSelected = mx >= b[0] && my >= b[1] && mx < b[2] && my < b[3]; + + // WHETHER THIS TAG HAS BEEN CLICKED ON THE CURRENT FRAME + var isClicked = isSelected && that.isClicked && i == that.iSelectedTag; + + // IF SO, TOGGLE ACTIVE STATE + const tag = that.tags[i]; + if (isSelected) { + that.hoveredOverTag = tag; + } + if (isClicked) { + that.isClicked = false; + // RESET ALL TO OFF STAT + if (i == 0) { + that.disableAllTags(); + } + // TOGGLE INDIVIDUAL STATE + else { + const prevWasOn = that.tagStates[tag]; + that.tagStates[tag] = !prevWasOn; + if (prevWasOn) { + that.numActiveTags--; + } + else { + that.numActiveTags++; + } + } + } + + // NEW STATE + var isActive = ((i == 0) ? false : that.tagStates[tag]); + + var isHighlighted = isSelected || isActive; + + var gR = that.gR; + var sc = height() / 2000 * 10 / (textHeight() * that.heightScale); + var n, d, j; + + function highlightTagAsActive() { + fillPolygon(createRoundRect(gX, gY, gW, gH, gR)); + lineWidth(0.5); + color(that.color()); + if (isWhiteBackground()) { + line(gX + gW, gY + gH - gR, gX + gW, gY + gR); + line(gX + gW - gR, gY + gH, gX + gR, gY + gH); + var rx = gX + gW - gR + .707 * gR; + var ry = gY + gH - gR + .707 * gR; + line(gX + gW - gR, gY + gH, rx, ry); + line(gX + gW, gY + gH - gR, rx, ry); + } + else { + line(gX + gR, gY, gX + gW - gR, gY); + line(gX, gY + gR, gX, gY + gH - gR); + var rx = gX + gR - .707 * gR; + var ry = gY + gR - .707 * gR; + line(gX + gR, gY, rx, ry); + line(gX, gY + gR, rx, ry); + } + } + // RESET-TAGS SHOULD BE A DIFFERENT ELEMENT + // (NOT THE SAME TYPE OF BUTTON...CURRENT IMPLEMENTATION TEMPORARY) + if (isActive || i == 0) { + color(that.scrim()); + highlightTagAsActive(); + } + + const stroke = (isSelected) ? defaultPenColor : that.color(); + + _g.strokeStyle = _g.fillStyle = stroke; + + _g.lineWidth = (isActive) ? 2 : + (isHighlighted ? 2 : 1); + + _g_text(((i == 0) ? that.resetTagHeader : that.tags[i]), [gX + 2, y + 2, 0]); + } + + for (let i = this.tags.length - 1; i >= 0; i--) { + color(this.scrim()); + drawTag(i); + } + + _g.restore(); + }, drawGlyph : function(i, cx, cy) { var mx = This().mouseX, my = This().mouseY; var glyph = glyphs[i]; @@ -92,7 +249,42 @@ GlyphChart.prototype = { var t = (this.t - i - .55) * 2.5 + i + .5; var txt = glyphs[i].indexName; var isSelected = mx >= b[0] && my >= b[1] && mx < b[2] && my < b[3]; - var isHighlighted = isSelected || (this.strokeCountMask & 1 << nn) != 0; + + var highlightedByTag = false; + if (glyph.tags.length > 0) { + function isHighlightedByTag(chart) { + // IF ANY TAGS ARE LOCKED-IN + if (chart.numActiveTags > 0) { + for (let i = 0; i < glyph.tags.length; i++) { + if (chart.hoveredOverTag == glyph.tags[i] || + chart.tagStates[glyph.tags[i]] == true) { + return true; + } + } + return false; + } + // OTHERWISE CHECK ONLY THE HOVERED-OVER TAG IF IT EXISTS + if (chart.hoveredOverTag != null) { + for (let i = 0; i < glyph.tags.length; i++) { + if (chart.hoveredOverTag == glyph.tags[i]) { + return true; + } + } + return false; + } + return false; + } + highlightedByTag = isHighlightedByTag(this); + } + + const maskNZ = (this.strokeCountMask & 1 << nn) != 0; + // GLYPH IS SELECTED DIRECTLY + var isHighlighted = isSelected || + // GLYPH HAS NO LOCKED-IN OR HOVERED-OVER TAGS, CONSIDER THE STROKE COUNT MASK + (this.numActiveTags == 0 && this.hoveredOverTag == null && maskNZ) || + // GLYPH HAS A LOCKED-IN OR HOVERED-OVER TAG, CONSIDER THE COUNT MASK ONLY IF IT IS ACTIVE + (highlightedByTag && (maskNZ || this.strokeCountMask == 0)); + var gR = this.gR; var sc = height() / 2000 * 10 / this.glyphsPerCol; var n, d, j; @@ -122,28 +314,34 @@ GlyphChart.prototype = { _g.strokeStyle = _g.fillStyle = isHighlighted ? defaultPenColor : this.color(); _g.lineWidth = isHighlighted ? 2 : 1; - if (isSelected || ! this.isAnySelected) + // CHECK WHETHER TO DISPLAY SKETCH NAME + if (isHighlighted || + (this.strokeCountMask == 0 && + this.numActiveTags == 0 && + this.hoveredOverTag == null && + !this.isAnySelected)) { _g_text(txt, [gX + 2, y + 2, 0]); + } y += height() / 45 * 10 / this.glyphsPerCol; for (n = 0 ; n < nn ; n++) { d = glyph.data[n]; if (isSelected) - if (mix(i, i+1, n / nn) <= t) + if (mix(i, i+1, n / nn) <= t) fillOval(x + d[0][0] * sc - 3, y + d[0][1] * sc - 3, 6, 6); else - continue; + continue; _g_beginPath(); _g_moveTo(x + d[0][0] * sc, y + d[0][1] * sc); - var incr = isSelected ? 1 : max(1, floor(d.length / 10)); + var incr = isSelected ? 1 : max(1, floor(d.length / 10)); for (j = 1 ; j < d.length ; j += incr) { if (isSelected && mix(i, i+1, (n + j / d.length) / nn) > t) break; _g_lineTo(x + d[j][0] * sc, y + d[j][1] * sc); } - if (incr > 1) + if (incr > 1) _g_lineTo(x + d[d.length-1][0] * sc, y + d[d.length-1][1] * sc); _g_stroke(); } diff --git a/lib/sketch.js b/lib/sketch.js index 229a932..dede77a 100644 --- a/lib/sketch.js +++ b/lib/sketch.js @@ -31,6 +31,7 @@ function Sketch() { this.isNegated = false; this.isShowingLiveData = false; this.labels = []; + this.tags = []; this.keyDown = function(key) {}; this.keyUp = function(key) {}; this.mouseDown = function(x, y) {}; diff --git a/lib/sketchGlyph.js b/lib/sketchGlyph.js index 888d36b..b86d217 100644 --- a/lib/sketchGlyph.js +++ b/lib/sketchGlyph.js @@ -86,6 +86,7 @@ function SketchGlyph(name, src) { this.name = name; this.data = []; + this.tags = []; if (src.length > 0 && typeof(src[0]) == 'string') { for (var n = 0 ; n < src.length ; n++) { @@ -143,4 +144,12 @@ function SketchGlyph(name, src) { this.data[n].push(mix(stroke[i-1], stroke[i], u)); } } + + this.assignTags = function(src) { + const tags = []; + for (let i = 0; i < src.length; i++) { + tags.push(src[i]); + } + this.tags = tags; + } } diff --git a/lib/sketchPage.js b/lib/sketchPage.js index 2630ea3..4c1bb2d 100644 --- a/lib/sketchPage.js +++ b/lib/sketchPage.js @@ -95,6 +95,7 @@ this.sketches = []; this.zoom = 1; palette.dragXY = null; + } SketchPage.prototype = { @@ -200,6 +201,7 @@ return; this.isPressed = true; + this.isClick = true; this.isMouseDownOverBackground = ! isHover(); this.travel = 0; @@ -223,7 +225,17 @@ if (x >= b[0] && x < b[2] && y >= b[1] && y < b[3]) { glyphChart.isDragging = true; glyphChart.iDragged = i; - break; + return; + } + } + + for (var i = 0 ; i < glyphChart.tags.length ; i++) { + var b = glyphChart.tagBounds(i); + if (x >= (width() - (2 * (width() - b[0]))) && x < b[2] && y >= b[1] && y < b[3]) { + glyphChart.isSelectingTag = true; + glyphChart.iSelectedTag = i; + glyphChart.isClicked = true; + return; } } return; @@ -530,8 +542,9 @@ return; } - if (isShowingGlyphs && ! glyphChart.isDragging) { + if (isShowingGlyphs && ! glyphChart.isDragging && ! glyphChart.isSelectingTag) { isShowingGlyphs = false; + glyphChart.disableAllTags(); return; } @@ -539,6 +552,12 @@ glyphs[glyphChart.iDragged].toFreehandSketch(This().mouseX, This().mouseY, 2.5 * height() / glyphChart.glyphsPerCol); glyphChart.isDragging = false; isShowingGlyphs = false; + glyphChart.disableAllTags(); + return; + } + + if (glyphChart.isSelectingTag) { + glyphChart.isSelectingTag = false; return; } diff --git a/sketches/bird.js b/sketches/bird.js index 98f1784..d7c5578 100644 --- a/sketches/bird.js +++ b/sketches/bird.js @@ -1,5 +1,6 @@ function() { this.label = "bird"; + this.tag = 'creatures'; this.is3D = true; this.bird = new Bird(); diff --git a/sketches/boolean.js b/sketches/boolean.js index 9f989c9..f869095 100644 --- a/sketches/boolean.js +++ b/sketches/boolean.js @@ -1,6 +1,7 @@ function() { this.label = "Boolean"; + this.tag = "logic"; this.state = true; this.onClick = ['toggle T/F', function() { this.state = ! this.state; }]; this.render = function() { diff --git a/sketches/bounce.js b/sketches/bounce.js index 4191fc5..dbad014 100644 --- a/sketches/bounce.js +++ b/sketches/bounce.js @@ -1,5 +1,6 @@ function() { this.label = "bounce"; + this.tag = 'physics'; this.isBouncing = false; this.bouncing = 0; this.y = 0; diff --git a/sketches/cube.js b/sketches/cube.js index a508c2f..6356bfe 100644 --- a/sketches/cube.js +++ b/sketches/cube.js @@ -1,5 +1,6 @@ function() { this.label = 'cube'; + this.tag = 'geometry'; this.is3D = true; this._isSolid = true; diff --git a/sketches/cube4d.js b/sketches/cube4d.js index 61894ce..69fd610 100644 --- a/sketches/cube4d.js +++ b/sketches/cube4d.js @@ -1,5 +1,6 @@ function() { this.label = 'cube4d'; + this.tag = 'geometry'; this.is3D = true; this.onClick = ['unrotate', function() { trackball.identity(); }]; this.onPress = function(B) { A.copy(B); } diff --git a/sketches/face.js b/sketches/face.js index 08a3d18..bbfee94 100644 --- a/sketches/face.js +++ b/sketches/face.js @@ -1,5 +1,6 @@ function() { this.label = 'face'; + this.tag = 'people'; this.is3D = true; this.mode = 0; this.isNeutral = false; diff --git a/sketches/fish.js b/sketches/fish.js index d956eba..57e51b8 100644 --- a/sketches/fish.js +++ b/sketches/fish.js @@ -1,5 +1,6 @@ function() { this.label = 'fish'; + this.tag = 'creatures'; this.swim = false; this.onSwipe[0] = ['SWIM!', function() { this.swim = true; }]; this.angleY = 0; diff --git a/sketches/flap.js b/sketches/flap.js index 8a09ead..8a320fd 100644 --- a/sketches/flap.js +++ b/sketches/flap.js @@ -1,5 +1,6 @@ function() { this.label = 'flap'; + this.tag = 'creatures'; this.isWandering = false; this.isExiting = false; diff --git a/sketches/holospecs.js b/sketches/holospecs.js index 3e1fa56..55c4776 100644 --- a/sketches/holospecs.js +++ b/sketches/holospecs.js @@ -1,5 +1,6 @@ function() { this.label = 'holospecs'; + this.tags = ["future", "vr", "ar"]; this.is3D = true; this.displayMode = 0; this.onClick = ['next mode', function() { this.displayMode = (this.displayMode + 1) % 4; }]; diff --git a/sketches/mask.js b/sketches/mask.js index 3720e87..e77cac1 100644 --- a/sketches/mask.js +++ b/sketches/mask.js @@ -1,5 +1,6 @@ function() { this.label = 'mask'; + this.tag = 'people'; this.is3D = true; var that = this; diff --git a/sketches/matrix.js b/sketches/matrix.js index 3f081a7..fd3c7d5 100644 --- a/sketches/matrix.js +++ b/sketches/matrix.js @@ -1,5 +1,6 @@ function() { this.labels = 'Matrix bezier bspline hermite'.split(' '); + this.tag = 'mathematics'; this.inLabel = ['', '\u2715']; function rounded(x) { return floor(x * 100) / 100; } var c = "cos"; diff --git a/sketches/midi.js b/sketches/midi.js index f955893..8738981 100644 --- a/sketches/midi.js +++ b/sketches/midi.js @@ -1,5 +1,6 @@ function() { this.label = "Midi"; + this.tags = ["music", "audio"]; // Draw as a musical note. diff --git a/sketches/mipmap.js b/sketches/mipmap.js index 8c48099..045f5bc 100644 --- a/sketches/mipmap.js +++ b/sketches/mipmap.js @@ -1,5 +1,6 @@ function() { this.label = 'mipmap'; + this.tag = 'computer graphics'; this.render = function() { for (var i = 0 ; i < 2 ; i++) { mClosedCurve([[-1,1],[1,1],[1,-1],[-1,-1]]); diff --git a/sketches/mipmap2.js b/sketches/mipmap2.js index bdb067b..e454bf6 100644 --- a/sketches/mipmap2.js +++ b/sketches/mipmap2.js @@ -1,5 +1,6 @@ function() { this.label = 'mipmap2'; + this.tag = 'computer graphics'; this.is3D = true; this.mode = 0; this.onCmdClick = function() { this.mode++; } diff --git a/sketches/mult.js b/sketches/mult.js index 7713593..216d6ef 100644 --- a/sketches/mult.js +++ b/sketches/mult.js @@ -3,6 +3,7 @@ function() { Multiply a number, vector or matrix by a number, vector or matrix. */ this.label = "Mult"; + this.tags = ["operators", "mathematics"]; this.render = function() { mLine( [-1, 1], [ 1, -1] ); mLine( [ 1, 1], [-1, -1] ); diff --git a/sketches/pendulum.js b/sketches/pendulum.js index 9c495e0..ae5a2cf 100644 --- a/sketches/pendulum.js +++ b/sketches/pendulum.js @@ -1,5 +1,6 @@ function() { this.label = "pendulum"; + this.tag = "physics"; var xx, yy, spring = new Spring(), force = 0, adjustHeight = 1, angle = 0, bobRadius, hubWidth, rodHeight, swingMode = 'swing'; diff --git a/sketches/perspective.js b/sketches/perspective.js index 66bf417..b4cd403 100644 --- a/sketches/perspective.js +++ b/sketches/perspective.js @@ -1,5 +1,6 @@ function() { this.label = 'perspect'; + this.tag = 'computer graphics'; this.point = newVec3(); this.dy = 0; this.onPress = function(point) { this.point.copy(point); } diff --git a/sketches/phong.js b/sketches/phong.js index 7e0e8fd..a741838 100644 --- a/sketches/phong.js +++ b/sketches/phong.js @@ -1,5 +1,6 @@ function() { this.label = "phong"; + this.tag = "computer graphics"; this.rx = .707; this.ry = .707; this.code = [["", "R = 2 * N * dot(N,L) - L"]]; diff --git a/sketches/piano.js b/sketches/piano.js index 7b8ef09..f9c1094 100644 --- a/sketches/piano.js +++ b/sketches/piano.js @@ -3,6 +3,7 @@ function() { TODO: Add chords. */ this.label = 'piano'; + this.tags = ['music', 'audio']; this.minOctave = 3; this.maxOctave = 3; this.pt = newVec3(-100,0,0); diff --git a/sketches/raytrace.js b/sketches/raytrace.js index 4e605dd..ac9fb0c 100644 --- a/sketches/raytrace.js +++ b/sketches/raytrace.js @@ -1,5 +1,6 @@ function() { this.label = 'raytrace'; + this.tag = 'computer graphics'; this.mode = 0; this.onCmdClick = function() { this.mode++; } this.render = function() { diff --git a/sketches/rocket.js b/sketches/rocket.js index 51f48b2..999e94f 100644 --- a/sketches/rocket.js +++ b/sketches/rocket.js @@ -1,5 +1,6 @@ function() { this.label = "rocket"; + this.tags = ["vehicles", "aircrafts"]; this.onSwipeTime = 0; this.velocity = 0; this.altitude = 0; diff --git a/sketches/smiley.js b/sketches/smiley.js index 25bc619..5e8defd 100644 --- a/sketches/smiley.js +++ b/sketches/smiley.js @@ -1,5 +1,6 @@ function() { this.label = 'smiley'; + this.tag = 'people'; this.onSwipe[6] = ['wink', function() { this.isWink = ! this.isWink; }]; this.render = function() { mDrawOval([-1,-1],[1,1],32,PI/2,PI/2-TAU); diff --git a/sketches/soundfile.js b/sketches/soundfile.js index aae176f..b699ec8 100644 --- a/sketches/soundfile.js +++ b/sketches/soundfile.js @@ -1,6 +1,7 @@ function() { var self = this; this.label = 'Soundfile'; + this.tag = 'audio'; this.soundBuffer = null; this.soundBufferChannelData = null; diff --git a/sketches/speaker.js b/sketches/speaker.js index b6b787e..ac7c98e 100644 --- a/sketches/speaker.js +++ b/sketches/speaker.js @@ -1,5 +1,6 @@ function() { this.label = 'speaker'; + this.tags = ['audio', 'signals']; this.render = function() { var a = .3; mCurve([[1,-1],[-a,-a],[-1,-a],[-1,a],[-a,a],[1,1]]); diff --git a/sketches/vec.js b/sketches/vec.js index 3f93930..b46b5b0 100644 --- a/sketches/vec.js +++ b/sketches/vec.js @@ -6,6 +6,7 @@ function() { return 1 - this.selection % 2; } this.labels = 'Hvec2 Vvec2 Hvec3 Vvec3 Hvec4 Vvec4'.split(' '); + this.tag = 'mathematics'; this.value = [1,0,0,0]; this.row = 0; this.precision = 1;