when selecting text, highlight rountangle to which text belongs

This commit is contained in:
Joeri Exelmans 2025-10-06 17:25:51 +02:00
parent da0e56e17c
commit 41f34ab65e
3 changed files with 28 additions and 4 deletions

View file

@ -4,7 +4,7 @@ import { ArcDirection, Line2D, Rect2D, Vec2D, addV2D, arcDirection, area, euclid
import "./VisualEditor.css";
import { getBBoxInSvgCoords } from "./svg_helper";
import { VisualEditorState, Rountangle, emptyState, Arrow, ArrowPart, RountanglePart, findNearestRountangleSide, findNearestArrow, Text } from "./editor_types";
import { VisualEditorState, Rountangle, emptyState, Arrow, ArrowPart, RountanglePart, findNearestRountangleSide, findNearestArrow, Text, findRountangle } from "./editor_types";
import { parseStatechart } from "./parser";
import { CORNER_HELPER_OFFSET, CORNER_HELPER_RADIUS, MIN_ROUNTANGLE_SIZE, ROUNTANGLE_RADIUS } from "./parameters";
@ -401,6 +401,7 @@ export function VisualEditor() {
const side2ArrowMap = new Map<string, Set<["start"|"end", string]>>();
const text2ArrowMap = new Map<string,string>();
const arrow2TextMap = new Map<string,string[]>();
const text2RountangleMap = new Map<string, string>();
for (const arrow of state.arrows) {
const startSide = findNearestRountangleSide(arrow, "start", state.rountangles);
const endSide = findNearestRountangleSide(arrow, "end", state.rountangles);
@ -421,17 +422,26 @@ export function VisualEditor() {
for (const text of state.texts) {
const nearestArrow = findNearestArrow(text.topLeft, state.arrows);
if (nearestArrow) {
// prioritize text belonging to arrows:
text2ArrowMap.set(text.uid, nearestArrow.uid);
const textsOfArrow = arrow2TextMap.get(nearestArrow.uid) || [];
textsOfArrow.push(text.uid);
arrow2TextMap.set(nearestArrow.uid, textsOfArrow);
}
else {
// no arrow, then the text belongs to the rountangle it is in
const rountangle = findRountangle(text.topLeft, state.rountangles);
if (rountangle) {
text2RountangleMap.set(text.uid, rountangle.uid);
}
}
}
// for visual feedback, when selecting/moving one thing, we also highlight (in green) all the things that belong to the thing we selected.
const sidesToHighlight: {[key: string]: RountanglePart[]} = {};
const arrowsToHighlight: {[key: string]: boolean} = {};
const textsToHighlight: {[key: string]: boolean} = {};
const rountanglesToHighlight: {[key: string]: boolean} = {};
for (const selected of selection) {
const sides = arrow2SideMap.get(selected.uid);
if (sides) {
@ -455,6 +465,10 @@ export function VisualEditor() {
if (arrow2) {
arrowsToHighlight[arrow2] = true;
}
const rountangleUid = text2RountangleMap.get(selected.uid)
if (rountangleUid) {
rountanglesToHighlight[rountangleUid] = true;
}
}
const rootErrors = errors.filter(([uid]) => uid === "root").map(err=>err[1]);
@ -484,7 +498,7 @@ export function VisualEditor() {
key={rountangle.uid}
rountangle={rountangle}
selected={selection.find(r => r.uid === rountangle.uid)?.parts || []}
highlight={sidesToHighlight[rountangle.uid] || []}
highlight={[...(sidesToHighlight[rountangle.uid] || []), ...(rountanglesToHighlight[rountangle.uid]?["left","right","top","bottom"]:[])]}
errors={errors.filter(([uid,msg])=>uid===rountangle.uid).map(err=>err[1])}
/>)}

View file

@ -1,4 +1,4 @@
import { Rect2D, Vec2D, Line2D, euclideanDistance, intersectLines, isWithin, lineBBox } from "./geometry";
import { Rect2D, Vec2D, Line2D, euclideanDistance, intersectLines, isWithin, lineBBox, isEntirelyWithin } from "./geometry";
import { ARROW_SNAP_THRESHOLD, TEXT_SNAP_THRESHOLD } from "./parameters";
import { sides } from "./VisualEditor";
@ -106,3 +106,12 @@ export function findNearestArrow(point: Vec2D, candidates: Arrow[]): Arrow | und
return best;
}
// precondition: candidates are sorted from big to small
export function findRountangle(point: Vec2D, candidates: Rountangle[]): Rountangle | undefined {
for (let i=candidates.length-1; i>=0; i--) {
if (isWithin(point, candidates[i])) {
return candidates[i];
}
}
}

View file

@ -24,3 +24,4 @@ export function recursiveEnter(state: ConcreteState): RT_ConcreteState {
};
}
}