From 043241d7d62fdb2fd939e6916509e98526f971ea Mon Sep 17 00:00:00 2001 From: Damiano Di Vincenzo Date: Fri, 22 Sep 2023 03:39:53 +0200 Subject: [PATCH] edgePoint autohide --- src/App.scss | 22 ++++++++++---- src/common/DV.tsx | 21 ++++++++----- src/common/Geom.ts | 10 +++---- src/model/dataStructure/GraphDataElements.tsx | 30 +++++++++++++------ 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/src/App.scss b/src/App.scss index 5524edda4..ac938af55 100644 --- a/src/App.scss +++ b/src/App.scss @@ -15,9 +15,9 @@ .hoverable{ position: relative; } -.hoverable:hover>.preview{ display: none; } -.hoverable:hover>.content{ display: block; position: absolute; z-index:1;} -.hoverable>.content{ display: none; } +.hoverable:hover>.preview, .hoverable:hover>svg>.preview{ display: none; } +.hoverable:hover>.content, .hoverable:hover>svg>.content{ display: block; position: absolute; z-index:1;} +.hoverable>.content, .hoverable>svg>.content{ display: none; } [data-nodetype]{ position: relative; @@ -109,7 +109,9 @@ body .ui-icon{ // override jqueryui fixed resizable handles at 16px, making drag }*/ //////////// edge paths - +.hoverable>.preview, .hoverable>svg>.preview{ + pointer-events: all; +} svg.hoverable { path{ @@ -120,7 +122,8 @@ svg.hoverable { &>.content { visibility: hidden; } } - &:hover:has(path.clickable) { + &:hover:has(path.clickable), + &:focus-within:has(path.clickable) { &>.preview { visibility: hidden; } &>.content { visibility: visible; } } @@ -136,5 +139,14 @@ svg.hoverable { // transform: scale(attr(hoverscale number, 1)); correct but unsupported yet. i'm forced to hardcode a number } +[data-nodetype="Edge"]>.hide-ep [data-nodetype="EdgePoint"]{ + display: none !important; +} + +[data-nodetype="Edge"]>.hide-ep:hover [data-nodetype="EdgePoint"], +[data-nodetype="Edge"]>.hide-ep:focus-within [data-nodetype="EdgePoint"]{ + display: block !important; +} + //.EdgePoint{ transform: translate(-50%, -50%); } diff --git a/src/common/DV.tsx b/src/common/DV.tsx index 3c92e358a..b88be2154 100644 --- a/src/common/DV.tsx +++ b/src/common/DV.tsx @@ -29,7 +29,7 @@ export class DV { console.error("error in view:", {publicmsg, debuginfo:debughiddenmsg}); return DefaultView.error(visibleMessage); } static edgePointView(): string { return beautify( - `
` + `
` )} static edgePointViewSVG(): string { return beautify( `` @@ -83,11 +83,19 @@ export class DV { // about label rotation in .edge > foreignObect > div (label) // first transform is h-center. second is rotate, third adds [0, 50%] of 50% vertical offset AFTER rotation to take label out of edge. fourth is to add a margin. static edgeView(modename: EdgeHead, head: DocString<"JSX">, tail: DocString<"JSX">, dashing: string | undefined): string { return beautify( - `
- - { /* edge full segment */ } - + `
+ + { /* edge full paths + + first is preview path, normally seen + third (segmented) is path onHover + second is to enlarge the hover area of path.preview to the same as path.content, so i avoid hover loop enter-leave and graphical flashing + + */ } + + { /* edge separate segments */ } {this.segments.all.flatMap(s => [ ) }{ - edge.end.model.attributes.map( (m, index, arr) => { let segs = parent.segments.segments; let pos = segs[0].start.pt.multiply(1-(index+1)/(arr.length+1), true).add(segs[segs.length-1].end.pt.multiply((index+1)/(arr.length+1), true)); diff --git a/src/common/Geom.ts b/src/common/Geom.ts index 542065f6d..f37a4a60e 100644 --- a/src/common/Geom.ts +++ b/src/common/Geom.ts @@ -464,18 +464,18 @@ export class GraphSize extends ISize { const maxY = Math.max(firstPt.y, secondPt.y); return new GraphSize(minX, minY, maxX - minX, maxY - minY); } - - public static closestIntersection(size: GraphSize, pt0: GraphPoint, targetPt: GraphPoint, gridAlign?: GraphPoint, m0?:number, q0?:number): GraphPoint | undefined { - let pt: GraphPoint = pt0.duplicate(); + // both pt and targetPt are readonly-safe parameters + public static closestIntersection(size: GraphSize, pt: GraphPoint, targetPt: GraphPoint, gridAlign?: GraphPoint, m0?:number, q0?:number): GraphPoint | undefined { + // let pt: GraphPoint = pt0.duplicate(); const m = m0 || GraphPoint.getM(targetPt, pt); const q = q0 || GraphPoint.getQ(targetPt, pt); // console.log("closestIntersection()", {size, pt0, targetPt, m, q}); // if perfectly vertical line if (m === Number.POSITIVE_INFINITY/* && q === Number.NEGATIVE_INFINITY*/) { // top center - if (Math.abs(targetPt.y - size.y) <= Math.abs(targetPt.y - size.y - size.h)) return new GraphPoint(pt0.x, size.y); + if (Math.abs(targetPt.y - size.y) <= Math.abs(targetPt.y - size.y - size.h)) return pt; // bottom center - else return new GraphPoint(pt0.x, size.y + size.h); + else return new GraphPoint(pt.x, size.y + size.h); } let tl = size.tl(), tr = size.tr(), bl = size.bl(), br = size.br(); diff --git a/src/model/dataStructure/GraphDataElements.tsx b/src/model/dataStructure/GraphDataElements.tsx index 3876e5495..a40f5e906 100644 --- a/src/model/dataStructure/GraphDataElements.tsx +++ b/src/model/dataStructure/GraphDataElements.tsx @@ -1284,7 +1284,10 @@ export class EdgeSegment{ let prev: EdgeSegment | undefined = this.prev; if (!prev) return; let prevedgemakerbezier: segmentmaker = (prev.bezier[prev.bezier.length-1] || prev.start); - let mirroredBezier: segmentmaker = {...prevedgemakerbezier, pt: EdgeSegment.invertLastBezierPt(prevedgemakerbezier.pt, prev.end.pt)}; + let mirroredBezier: segmentmaker = {...prevedgemakerbezier, + pt: EdgeSegment.invertLastBezierPt(prevedgemakerbezier.pt, prev.end.pt), + uncutPt: EdgeSegment.invertLastBezierPt(prevedgemakerbezier.uncutPt, prev.end.uncutPt), + }; this.bezier = [mirroredBezier, ...this.bezier]; // always only 1 assumed pt both in cubic and quadratic. // let next: this | undefined = this.segments[this.index+1]; @@ -1309,14 +1312,17 @@ export class EdgeSegment{ let bezierpts = [...this.bezier.map( b => b.pt), this.end.pt]; let finalpart = svgLetter + " " + bezierpts.map((p)=> p.x + " " + p.y).join(", "); this.dpart = "M " + this.start.pt.x + " " + this.start.pt.y + ", " + finalpart; + let bezierptsUncut = [...this.bezier.map( b => b.uncutPt), this.end.pt]; // uncutPt exist for start and end too, but i want to use the cut one for those. or edgehead is off + let finalpartUncut = svgLetter + " " + bezierptsUncut.map((p)=> p.x + " " + p.y).join(", "); + this.d = (index === 0 ? "M" + this.start.pt.x + " " + this.start.pt.y + ", " : "") + finalpartUncut; + //midp = [this.startp, ...this.midp]; // d = M sp X mp2 ep // X = custom letter // dpart = T sp X mp2 ep // S = S if X = C, // sp is the startingpoint from the prev node, which might be != from endpoint of last node if last node have w>0 && h>0 // so i'm "filling" the gap with a T, or L arc wich can use only 1 parameter (they are the only 1-parameter arcs) - let startletter: string; if (this.prev && this.prev.end.pt.equals(this.start.pt)) gapMode = EdgeGapMode.average; // if the 2 points coincide, i use any 1 of the gapmodes that are continuous - switch (gapMode){ + /*switch (gapMode){ case EdgeGapMode.center: case EdgeGapMode.average: // continuous gap modes. they only differ in how the "joining" point is found, but not in how they behave after that. @@ -1331,7 +1337,7 @@ export class EdgeSegment{ case SvgLetter.L: case SvgLetter.M: startletter = SvgLetter.L + " "; break; } - }*/ + }* / if (index) { this.d = finalpart; } @@ -1346,7 +1352,7 @@ export class EdgeSegment{ break; default: Log.exDevv("unexpected EdgeGapMode:" + gapMode, {gapMode}); - } + }*/ break; default: return Log.exDevv("unexpected bending mode length:" + this.svgLetter + " or fillMode: " + gapMode, {bendingMode: this.svgLetter, index, gapMode}); } @@ -1399,7 +1405,7 @@ export class EdgeFillSegment extends EdgeSegment{ } -type segmentmaker = {size: GraphSize, view: LViewElement, ge: LGraphElement, pt: GraphPoint}; +type segmentmaker = {size: GraphSize, view: LViewElement, ge: LGraphElement, pt: GraphPoint, uncutPt: GraphPoint}; @RuntimeAccessible export class LVoidEdge = any, D extends DEdge = DEdge> extends LGraphElement { public static cname: string = "LVoidEdge"; @@ -1657,18 +1663,20 @@ replaced by startPoint return size.tl().add(offset, false); } const all: segmentmaker[] = allNodes.flatMap((ge, i) => { - let base: segmentmaker = {view: ge.view, size: outer ? ge.outerSize : ge.innerSize, ge, pt: null as any}; + let base: segmentmaker = {view: ge.view, size: outer ? ge.outerSize : ge.innerSize, ge, pt: null as any, uncutPt: null as any}; let rets: segmentmaker | undefined;// = base as any; let rete: segmentmaker | undefined;// = {...base} as any; if (i !== 0){ rete = {...base}; rete.pt = (LEdgePoint.singleton as LEdgePoint).get_endPoint(undefined as any, rete.size, rete.view); rete.pt = getAnchorOffset(rete.size, rete.view.edgeStartOffset, rete.view.edgeStartOffset_isPercentage); + rete.uncutPt = rete.pt; } if (i !== allNodes.length - 1){ rets = {...base}; rets.pt = (LEdgePoint.singleton as LEdgePoint).get_startPoint(undefined as any, rets.size, rets.view); rets.pt = getAnchorOffset(rets.size, rets.view.edgeStartOffset, rets.view.edgeStartOffset_isPercentage); + rets.uncutPt = rets.pt; } // ret.pt = ge.startPoint return rets && rete ? [rete, rets] : (rets ? [rets] : [rete as segmentmaker]); } @@ -1764,7 +1772,7 @@ replaced by startPoint // cut i === 0 is cut regardless of gapmode. if (canCutStart) { ci = GraphSize.closestIntersection(ret[0].start.size, ret[0].start.pt, (ret[0].bezier[0] || ret[0].end).pt, grid); - if (ci) ret[0].start.pt = ci; + if (ci) ret[0].start.pt = ci; /* ret[0].start.pt = GraphSize.closestIntersection(ret[0].start.size, ret[0].start.pt, (ret[0].bezier[0] || ret[0].end).pt, grid) as any @@ -1817,13 +1825,17 @@ replaced by startPoint // they merge if just 1 of cutting sides are true. (and if they are both false we don't even enter the for loop) curr.start.pt.add(prev.end.pt, false).divide(2, false); prev.end.pt = curr.start.pt.duplicate() // intentionally not the same pt because during snap to edge they can temporarly diverge.again, + prev.start.uncutPt = prev.start.pt; + prev.end.uncutPt = prev.end.pt; break; // center: first move it to center of edgePoint/node, then snap to edge. // this mode might be as well deleted, it can be specified with anchor points case EdgeGapMode.center: - doEndCut = true; doStartCut = true; + doEndCut = false; doStartCut = false; curr.start.pt = curr.start.size.tl().add(curr.start.size.br(), false).divide(2, false); prev.end.pt = curr.start.pt.duplicate(); // intentionally not the same pt because during snap to edge they can diverge.again, + prev.start.uncutPt = prev.start.pt; // only update them when point moves without being cut (average and center) + prev.end.uncutPt = prev.end.pt; break; default: return Log.exDevv("unexpected EdgeGapMode:" + gapMode);