Skip to content

Update action canvas/focus on home double click to locate target node on canvas #189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 77 additions & 46 deletions editor-packages/editor-canvas/canvas/canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import q from "@design-sdk/query";
import { LazyFrame } from "@code-editor/canvas/lazy-frame";
import { HudCustomRenderers, HudSurface } from "../hud";
import type { Box, XY, CanvasTransform, XYWH } from "../types";
import type { Box, XY, CanvasTransform, XYWH, XYWHR } from "../types";
import type { FrameOptimizationFactors } from "../frame";
// import { TransformDraftingStore } from "../drafting";
import {
Expand Down Expand Up @@ -98,23 +98,44 @@ const default_canvas_preferences: CanvsPreferences = {
},
};

type CanvasProps = CanvasCursorOptions & {
viewbound: Box;
onSelectNode?: (...node: ReflectSceneNode[]) => void;
onMoveNodeStart?: (...node: string[]) => void;
onMoveNode?: (delta: XY, ...node: string[]) => void;
onMoveNodeEnd?: (delta: XY, ...node: string[]) => void;
onClearSelection?: () => void;
} & CanvasCustomRenderers &
type CanvasProps = CanvasFocusProps &
CanvasCursorOptions & {
viewbound: Box;
onSelectNode?: (...node: ReflectSceneNode[]) => void;
onMoveNodeStart?: (...node: string[]) => void;
onMoveNode?: (delta: XY, ...node: string[]) => void;
onMoveNodeEnd?: (delta: XY, ...node: string[]) => void;
onClearSelection?: () => void;
} & CanvasCustomRenderers &
CanvasState & {
config?: CanvsPreferences;
};

type CanvasFocusProps = {
/**
* IDs of focus nodes.
*
* @default []
*/
focus?: string[];
focusRefreshkey?: string;
};

interface HovringNode {
node: ReflectSceneNode;
reason: "frame-title" | "raycast" | "external";
}

function xywhr_of(node: ReflectSceneNode): XYWHR {
return [
node.absoluteX,
node.absoluteY,
node.width,
node.height,
node.rotation,
] as XYWHR;
}

export function Canvas({
viewbound,
renderItem,
Expand All @@ -126,6 +147,8 @@ export function Canvas({
filekey,
pageid,
nodes,
focus = [],
focusRefreshkey: focusRefreshKey,
initialTransform,
highlightedLayer,
selectedNodes,
Expand All @@ -135,6 +158,11 @@ export function Canvas({
cursor,
...props
}: CanvasProps) {
const viewboundmeasured = useMemo(
() => !viewbound_not_measured(viewbound),
viewbound
);

useEffect(() => {
if (transformIntitialized) {
return;
Expand All @@ -148,7 +176,7 @@ export function Canvas({
return;
}

if (viewbound_not_measured(viewbound)) {
if (!viewboundmeasured) {
return;
}

Expand All @@ -158,6 +186,39 @@ export function Canvas({
setTransformInitialized(true);
}, [viewbound]);

useEffect(() => {
// change the canvas transform to visually fit the focus nodes.

if (!viewboundmeasured) {
return;
}

if (focus.length == 0) {
return;
}

// TODO: currently only the root nodes are supported to be focused.
const _focus_nodes = nodes.filter((n) => focus.includes(n.id));
if (_focus_nodes.length == 0) {
return;
}

const _focus_center = centerOf(
viewbound,
200,
..._focus_nodes.map((n) => ({
x: n.absoluteX,
y: n.absoluteY,
width: n.width,
height: n.height,
rotation: n.rotation,
}))
);

setOffset(_focus_center.translate);
setZoom(_focus_center.scale);
}, [...focus, focusRefreshKey, viewboundmeasured]);

const [transformIntitialized, setTransformInitialized] = useState(false);
const [zoom, setZoom] = useState(initialTransform?.scale || 1);
const [isZooming, setIsZooming] = useState(false);
Expand Down Expand Up @@ -339,9 +400,7 @@ export function Canvas({
const [x, y] = [cx / zoom, cy / zoom];

const box = boundingbox(
selected_nodes.map((d) => {
return [d.absoluteX, d.absoluteY, d.width, d.height, d.rotation];
}),
selected_nodes.map((d) => xywhr_of(d)),
2
);

Expand Down Expand Up @@ -543,29 +602,12 @@ function position_guide({

const guides = [];
const a = boundingbox(
selections.map((s) => [
s.absoluteX,
s.absoluteY,
s.width,
s.height,
s.rotation,
]),
selections.map((s) => xywhr_of(s)),
2
);

if (hover) {
const hover_box = boundingbox(
[
[
hover.absoluteX,
hover.absoluteY,
hover.width,
hover.height,
hover.rotation,
],
],
2
);
const hover_box = boundingbox([xywhr_of(hover)], 2);

const guide_relative_to_hover = {
a: a,
Expand All @@ -580,18 +622,7 @@ function position_guide({
if (selections.length === 1) {
const parent = selections[0].parent;
if (parent) {
const parent_box = boundingbox(
[
[
parent.absoluteX,
parent.absoluteY,
parent.width,
parent.height,
parent.rotation,
],
],
2
);
const parent_box = boundingbox([xywhr_of(parent)], 2);
const guide_relative_to_parent = {
a: a,
b: parent_box,
Expand Down Expand Up @@ -702,7 +733,7 @@ function auto_initial_transform(
}

const fit_single_node = (n: ReflectSceneNode) => {
return centerOf(viewbound, n);
return centerOf(viewbound, 0, n);
};

if (nodes.length === 0) {
Expand All @@ -716,7 +747,7 @@ function auto_initial_transform(
};
} else if (nodes.length < 20) {
// fit bounds
const c = centerOf(viewbound, ...nodes);
const c = centerOf(viewbound, 0, ...nodes);
return {
xy: c.translate,
scale: c.scale,
Expand Down
60 changes: 60 additions & 0 deletions editor-packages/editor-canvas/math/center-of.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { X1Y1X2Y2 } from "types";
import { centerOf, scaleToFit, scaleToFit1D } from "./center-of";

test("centerof", () => {
const box = [0, 0, 100, 100] as X1Y1X2Y2;
const fit = {
x: 10,
y: 10,
width: 50,
height: 50,
rotation: 0,
};

const r = centerOf(box, 0, fit);
// to make "fit" fit into "box", we need to translate it by 10, 10 and scale it by 0.5

expect(r.scale).toBe(2);
expect(r.center).toStrictEqual([35, 35]);
// todo
expect(r.translate).toStrictEqual([-20, -20]);
});

test("scale to fit (smaller)", () => {
const a = [0, 0, 100, 100] as X1Y1X2Y2;
const b = [0, 0, 50, 50] as X1Y1X2Y2;
expect(scaleToFit1D(100, 50)).toBe(2);
expect(scaleToFit(a, b)).toBe(2);
});

test("scale to fit (bigger)", () => {
const a = [0, 0, 100, 100] as X1Y1X2Y2;
const b = [0, 0, 200, 200] as X1Y1X2Y2;
expect(scaleToFit(a, b)).toBe(0.5);
});

test("scale to fit (bigger) #1", () => {
const a = [0, 0, 100, 100] as X1Y1X2Y2;
const b = [0, 0, 10, 200] as X1Y1X2Y2;
expect(scaleToFit(a, b)).toBe(0.5);
});

test("scale to fit (bigger) #2", () => {
const a = [0, 0, 100, 100] as X1Y1X2Y2;
const b = [0, 0, 200, 10] as X1Y1X2Y2;
expect(scaleToFit(a, b)).toBe(0.5);
});

test("scale to fit with margin", () => {
const a = [0, 0, 100, 100] as X1Y1X2Y2;
const b = [0, 0, 100, 100] as X1Y1X2Y2;
expect(scaleToFit(a, b, 50)).toBe(0.5);
});

test("scale to fit 1D", () => {
expect(scaleToFit1D(100, 200)).toBe(0.5);
});

test("scale to fit 1D", () => {
expect(scaleToFit1D(100, 50, 25)).toBe(1);
});
51 changes: 47 additions & 4 deletions editor-packages/editor-canvas/math/center-of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ type Rect = {
*/
export function centerOf(
viewbound: Box,
m: number = 0,
...rects: Rect[]
): {
box: Box;
/**
* center of the givven rects
*/
center: XY;
translate: XY;
scale: number;
Expand All @@ -46,7 +50,7 @@ export function centerOf(
// center of the box, viewbound not considered.
const boxcenter: XY = [(x1 + x2) / 2, (y1 + y2) / 2];
// scale factor to fix the box to the viewbound.
const scale = Math.min(scaleToFit(box, viewbound), 1); // no need to zoom-in
const scale = scaleToFit(viewbound, box, m);
// center of the viewbound.
const vbcenter: XY = [
viewbound[0] + (viewbound[0] + viewbound[2]) / 2,
Expand All @@ -73,16 +77,55 @@ function rotate(x: number, y: number, r: number): [number, number] {
return [x * cos - y * sin, x * sin + y * cos];
}

function scaleToFit(a: Box, b: Box): number {
/**
* scale to fit a box into b box. with optional margin.
* @param a box a container
* @param b box b contained
* @param m optional margin @default 0 (does not get affected by the scale)
* @returns how much to scale should be applied to b to fit a
*
* @example
* const a = [0, 0, 100, 100];
* const b = [0, 0, 200, 200];
* const m = 50;
* => scaleToFit(a, b, m) === 0.4
*
* const a = [0, 0, 100, 100];
* const b = [0, 0, 50, 50];
* const m = 50;
* => scaleToFit(a, b, m) === 1
*
*/
export function scaleToFit(a: Box, b: Box, m: number = 0): number {
if (!a || !b) {
return 1;
}

const [ax1, ay1, ax2, ay2] = a;
const [bx1, by1, bx2, by2] = b;

const aw = ax2 - ax1;
const ah = ay2 - ay1;
const bw = bx2 - bx1;
const bh = by2 - by1;
const scale = Math.min(bw / aw, bh / ah);
return scale;

const sw = scaleToFit1D(aw, bw, m);
const sh = scaleToFit1D(ah, bh, m);

return Math.min(sw, sh);
}

/**
*
* @param a line a
* @param b line b
* @param m margin
*
* @returns the scale factor to be applied to b to fit a with margin
*/
export function scaleToFit1D(a: number, b: number, m: number = 0): number {
const aw = a;
const bw = b + m * 2;

return aw / bw;
}
6 changes: 3 additions & 3 deletions editor/core/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type Action =
| EditorModeAction
| DesignerModeSwitchActon
| SelectNodeAction
| LocateNodeAction
| CanvasFocusNodeAction
| HighlightNodeAction
| CanvasEditAction
| CanvasModeAction
Expand Down Expand Up @@ -78,8 +78,8 @@ export interface SelectNodeAction {
/**
* Select and move to the node.
*/
export interface LocateNodeAction {
type: "locate-node";
export interface CanvasFocusNodeAction {
type: "canvas/focus";
node: string;
}

Expand Down
Loading