Skip to content

Canvas optimizations #149

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 34 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2ab2e6c
use user defined canvas color
softmarshmallow Apr 18, 2022
599698c
add transform outline
softmarshmallow Apr 18, 2022
ff6dbfc
update transform highlight
softmarshmallow Apr 18, 2022
bd1233b
add grouped highlight
softmarshmallow Apr 18, 2022
c934133
ignore hover on selected nodes
softmarshmallow Apr 18, 2022
dd713de
add move node
softmarshmallow Apr 19, 2022
d56eddd
qf
softmarshmallow Apr 19, 2022
6f0bdeb
add size meter label
softmarshmallow Apr 20, 2022
bac24d3
add shift+0 - zoom to fit command on canvas
softmarshmallow Apr 20, 2022
38805bf
optimized size meter view w/o flex box
softmarshmallow Apr 20, 2022
1cfd49e
init core-drafting docs
softmarshmallow Apr 20, 2022
e5efe76
wip
softmarshmallow Apr 21, 2022
d1099b1
distroy web workers on editor dispose
softmarshmallow Apr 22, 2022
43fc7fb
safety
softmarshmallow Apr 22, 2022
4e18369
add dummy auth state to workspace state
softmarshmallow Apr 22, 2022
0ed282d
split & cleanup
softmarshmallow Apr 22, 2022
da552cb
add wwpreview provider
softmarshmallow Apr 22, 2022
497e9c0
Merge branch 'staging' of https://github.com/gridaco/designto-code in…
softmarshmallow Apr 22, 2022
0b21197
match interface
softmarshmallow Apr 22, 2022
40476ee
prevent same selection triggering state update
softmarshmallow Apr 22, 2022
bbfc2aa
made figma file store as a package
softmarshmallow Apr 22, 2022
7006705
wip: wwpreview without queue, w 50ms delay
softmarshmallow Apr 22, 2022
028c42d
extract constants
softmarshmallow Apr 22, 2022
9f591e7
Merge branch 'canvas-optimization' of https://github.com/gridaco/desi…
softmarshmallow Apr 22, 2022
7601d80
qf
softmarshmallow Apr 22, 2022
03196c7
update drafting
softmarshmallow Apr 25, 2022
251ba50
add types
softmarshmallow Apr 25, 2022
4674a76
add resize docs
softmarshmallow Apr 25, 2022
a53e6cb
update reducer placeholding
softmarshmallow Apr 25, 2022
f0e6435
Merge branch 'canvas-optimization' of https://github.com/gridaco/desi…
softmarshmallow Apr 25, 2022
62326d8
Merge branch 'editor/inspector' of https://github.com/gridaco/designt…
softmarshmallow Oct 24, 2022
892aca4
fix build
softmarshmallow Oct 24, 2022
f07afd6
disable chrome default gesture
softmarshmallow Oct 24, 2022
2360dbb
update readonly canvas
softmarshmallow Oct 24, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type OnPointerDownHandler = (
const ZOOM_WITH_SCROLL_SENSITIVITY = 0.001;

export function CanvasEventTarget({
onZoomToFit,
onPanning,
onPanningStart,
onPanningEnd,
Expand All @@ -40,6 +41,7 @@ export function CanvasEventTarget({
onDragEnd,
children,
}: {
onZoomToFit?: () => void;
onPanning: OnPanningHandler;
onPanningStart: OnPanningHandler;
onPanningEnd: OnPanningHandler;
Expand Down Expand Up @@ -69,6 +71,10 @@ export function CanvasEventTarget({
if (e.code === "Space") {
setIsSpacebarPressed(true);
}
// if shift + 0
else if (e.code === "Digit0" && e.shiftKey) {
onZoomToFit?.();
}
};
const ku = (e) => {
if (e.code === "Space") {
Expand Down Expand Up @@ -202,7 +208,6 @@ export function CanvasEventTarget({
style={{
position: "absolute",
inset: 0,
background: "transparent",
overflow: "hidden",
touchAction: "none",
cursor: isSpacebarPressed ? "grab" : "default",
Expand Down
137 changes: 117 additions & 20 deletions editor-packages/editor-canvas/canvas/canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,28 @@ import {
centerOf,
edge_scrolling,
target_of_area,
boundingbox,
is_point_inside_box,
} from "../math";
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 { FrameOptimizationFactors } from "../frame";
// import { TransformDraftingStore } from "../drafting";
import {
CANVAS_LAYER_HOVER_HIT_MARGIN,
CANVAS_INITIAL_XY,
CANVAS_INITIAL_SCALE,
CANVAS_MIN_ZOOM,
} from "../k";
import { ContextMenuRoot as ContextMenu } from "@editor-ui/context-menu";
import styled from "@emotion/styled";

const INITIAL_SCALE = 0.5;
const INITIAL_XY: XY = [0, 0];
const LAYER_HOVER_HIT_MARGIN = 3.5;
const MIN_ZOOM = 0.02;

interface CanvasState {
pageid: string;
filekey: string;
backgroundColor?: React.CSSProperties["backgroundColor"];
nodes: ReflectSceneNode[];
highlightedLayer?: string;
selectedNodes: string[];
Expand All @@ -56,6 +61,7 @@ type CanvasCustomRenderers = HudCustomRenderers & {
interface CanvsPreferences {
can_highlight_selected_layer?: boolean;
marquee: MarqueeOprions;
grouping: GroupingOptions;
}

interface MarqueeOprions {
Expand All @@ -67,11 +73,22 @@ interface MarqueeOprions {
disabled?: boolean;
}

interface GroupingOptions {
/**
* disable grouping - multiple selections will not be grouped.
* @default false
**/
disabled?: boolean;
}

const default_canvas_preferences: CanvsPreferences = {
can_highlight_selected_layer: false,
marquee: {
disabled: false,
},
grouping: {
disabled: false,
},
};

interface HovringNode {
Expand All @@ -82,6 +99,9 @@ interface HovringNode {
export function Canvas({
viewbound,
renderItem,
onMoveNodeStart,
onMoveNode,
onMoveNodeEnd,
onSelectNode: _cb_onSelectNode,
onClearSelection,
filekey,
Expand All @@ -92,20 +112,19 @@ export function Canvas({
selectedNodes,
readonly = true,
config = default_canvas_preferences,
backgroundColor,
...props
}: {
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;
}) {
const _canvas_state_store = useMemo(
() => new CanvasStateStore(filekey, pageid),
[filekey, pageid]
);

useEffect(() => {
if (transformIntitialized) {
return;
Expand Down Expand Up @@ -139,8 +158,14 @@ export function Canvas({
? [offset[0] / zoom, offset[1] / zoom]
: [0, 0];
const [isPanning, setIsPanning] = useState(false);
const [isDraggomg, setIsDragging] = useState(false);
const [marquee, setMarquee] = useState<XYWH | null>(null);
const [isDraggimg, setIsDragging] = useState(false);
const [isMovingSelections, setIsMovingSelections] = useState(false);
const [marquee, setMarquee] = useState<XYWH>(null);

const _canvas_state_store = useMemo(
() => new CanvasStateStore(filekey, pageid),
[filekey, pageid]
);

const cvtransform: CanvasTransform = {
scale: zoom,
Expand Down Expand Up @@ -195,7 +220,7 @@ export function Canvas({
}, [marquee]);

const onPointerMove: OnPointerMoveHandler = (state) => {
if (isPanning || isZooming || isDraggomg) {
if (isPanning || isZooming || isDraggimg) {
// don't perform hover calculation while transforming.
return;
}
Expand All @@ -204,8 +229,9 @@ export function Canvas({
tree: nodes,
zoom: zoom,
offset: nonscaled_offset,
margin: LAYER_HOVER_HIT_MARGIN,
margin: CANVAS_LAYER_HOVER_HIT_MARGIN,
reverse: true,
ignore: (n) => selectedNodes.includes(n.id),
});

if (!hovering) {
Expand All @@ -224,9 +250,16 @@ export function Canvas({
};

const onPointerDown: OnPointerDownHandler = (state) => {
const [x, y] = [state.event.clientX, state.event.clientY];

if (isPanning || isZooming) {
return;
}

if (shouldStartMoveSelections([x, y])) {
return; // don't do anything. onDrag will handle this. only block the event.
}

if (hoveringLayer) {
switch (hoveringLayer.reason) {
case "frame-title":
Expand Down Expand Up @@ -255,7 +288,7 @@ export function Canvas({
// the origin point of the zooming point in x, y
const [ox, oy]: XY = state.origin;

const newzoom = Math.max(zoom + zoomdelta, MIN_ZOOM);
const newzoom = Math.max(zoom + zoomdelta, CANVAS_MIN_ZOOM);

// calculate the offset that should be applied with scale with css transform.
const [newx, newy] = [
Expand All @@ -276,9 +309,35 @@ export function Canvas({
const [x, y] = s.initial;
const [ox, oy] = offset;
const [x1, y1] = [x - ox, y - oy];

// if dragging a selection group bounding box, move the selected items.
if (shouldStartMoveSelections([x, y])) {
setIsMovingSelections(true);
onMoveNodeStart?.(...selectedNodes);
return;
}

// else, clear and start a marquee
onClearSelection();
setMarquee([x1, y1, 0, 0]);
};

const shouldStartMoveSelections = ([cx, cy]) => {
// x, y is a client x, y.
const [ox, oy] = offset;
[cx, cy] = [cx - ox, cy - oy];
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];
}),
2
);

return is_point_inside_box([x, y], box);
};

const onDrag: OnDragHandler = (s) => {
const [ox, oy] = offset;
const [x, y] = [
Expand All @@ -290,6 +349,11 @@ export function Canvas({

const [x1, y1] = [x - ox, y - oy];

if (isMovingSelections) {
const [dx, dy] = s.delta;
onMoveNode?.([dx / zoom, dy / zoom], ...selectedNodes);
}

if (marquee) {
const [w, h] = [
x1 - marquee[0], // w
Expand All @@ -309,15 +373,25 @@ export function Canvas({
const onDragEnd: OnDragHandler = (s) => {
setMarquee(null);
setIsDragging(false);
if (isMovingSelections) {
const [ix, iy] = s.initial;
const [fx, fy] = [
//@ts-ignore
s.event.clientX,
//@ts-ignore
s.event.clientY,
];

onMoveNodeEnd?.([(fx - ix) / zoom, (fy - iy) / zoom], ...selectedNodes);
setIsMovingSelections(false);
}
};

const is_canvas_transforming = isPanning || isZooming;
const selected_nodes = selectedNodes
?.map((id) => qdoc.getNodeById(id))
.filter(Boolean);

console.log({ selectedNodes, highlightedLayer, selected_nodes });

const items = useMemo(() => {
return nodes?.map((node) => {
return (
Expand Down Expand Up @@ -372,6 +446,11 @@ export function Canvas({
setIsPanning(false);
_canvas_state_store.saveLastTransform(cvtransform);
}}
onZoomToFit={() => {
setZoom(1);
// setOffset([newx, newy]); // TODO: set offset to center of the viewport
_canvas_state_store.saveLastTransform(cvtransform);
}}
onZooming={onZooming}
onZoomingStart={() => {
setIsZooming(true);
Expand All @@ -394,6 +473,7 @@ export function Canvas({
hide={is_canvas_transforming}
readonly={readonly}
disableMarquee={config.marquee.disabled}
disableGrouping={config.grouping.disabled}
marquee={marquee}
labelDisplayNodes={nodes}
selectedNodes={selected_nodes}
Expand All @@ -413,13 +493,14 @@ export function Canvas({
setHoveringLayer({ node: node(id), reason: "frame-title" });
}}
onSelectNode={(id) => {
onSelectNode?.(node(id));
onSelectNode(node(id));
}}
renderFrameTitle={props.renderFrameTitle}
/>
</CanvasEventTarget>
</Container>
</ContextMenu>
<CanvasBackground backgroundColor={backgroundColor} />
<CanvasTransformRoot scale={zoom} xy={nonscaled_offset}>
<DisableBackdropFilter>{items}</DisableBackdropFilter>
</CanvasTransformRoot>
Expand Down Expand Up @@ -476,13 +557,29 @@ function DisableBackdropFilter({ children }: { children: React.ReactNode }) {
);
}

function CanvasBackground({ backgroundColor }: { backgroundColor?: string }) {
return (
<div
style={{
zIndex: -2,
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor,
}}
/>
);
}

function auto_initial_transform(
viewbound: Box,
nodes: ReflectSceneNode[]
): CanvasTransform {
const _default = {
scale: INITIAL_SCALE,
xy: INITIAL_XY,
scale: CANVAS_INITIAL_SCALE,
xy: CANVAS_INITIAL_XY,
};

if (!nodes || viewbound_not_measured(viewbound)) {
Expand Down
20 changes: 20 additions & 0 deletions editor-packages/editor-canvas/docs/core-drafting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# State (property drafting)

There are properties that are highly likely to be updated every frame (e.g. position, rotation, scale, etc.).
This are performed by drag-related user input, and this properties will not be modified direcly to the design model.

The final callback to the higher state holder will be called once after this operation is complete.

## The properties are..

**transform**

- x
- y
- width
- height
- rotation

**style**

- color
14 changes: 14 additions & 0 deletions editor-packages/editor-canvas/docs/feature-resize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Resizing selection (s)

Group resizing alg

```
| a ---- b ---- c |
```

In above scenario, width of a as x, is scaled in

- origin width of selection as w1
- new width of selection as w2

x = x \* w2 / w1
1 change: 1 addition & 0 deletions editor-packages/editor-canvas/drafting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Properties drafting
16 changes: 16 additions & 0 deletions editor-packages/editor-canvas/drafting/_.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export abstract class DraftingStore<T> {
readonly store = new Map<string, T>();
lastUpdated: number;

constructor() {
this.lastUpdated = Date.now();
}

abstract update(id: string, draft: T);

abstract get(id: string): T;

updated() {
this.lastUpdated = Date.now();
}
}
1 change: 1 addition & 0 deletions editor-packages/editor-canvas/drafting/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./transform-drafting";
Loading