Skip to content

Commit

Permalink
Better hit test
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianStehle committed Nov 4, 2024
1 parent 4aeb373 commit d96095c
Show file tree
Hide file tree
Showing 22 changed files with 448 additions and 239 deletions.
2 changes: 1 addition & 1 deletion src/wireframes/components/actions/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function useAppearanceCore<T>(selectedDiagramId: RefDiagramId, selectionS

const doChangeAppearance = useEventCallback((value: T) => {
if (selectedDiagramId && selectionSet) {
dispatch(changeItemsAppearance(selectedDiagramId, selectionSet.deepEditableItems, key, converter.write(value), force));
dispatch(changeItemsAppearance(selectedDiagramId, selectionSet.editableItems, key, converter.write(value), force));
}
});

Expand Down
18 changes: 12 additions & 6 deletions src/wireframes/engine/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Copyright (c) Sebastian Stehle. All rights reserved.
*/

import { Rect2, Vec2 } from '@app/core';
import { Vec2 } from '@app/core';
import { ShapePlugin } from '@app/wireframes/interface';
import { DiagramItem } from './../model';

Expand Down Expand Up @@ -34,9 +34,6 @@ export interface Engine {

// Unsubscribe from all events.
unsubscribe(listener: Listener): void;

// Calculates the dimensions of an object.
getLocalBounds(object: Object): Rect2;
}

export interface Listener {
Expand Down Expand Up @@ -75,11 +72,15 @@ export interface EngineLayer {

// Hides the layer.
hide(): void;

// Makes a hit and returns matching elements.
hitTest(x: number, y: number): EngineObject[];
}

export interface EngineRect extends EngineObject {
export interface EngineRectOrEllipse extends EngineObject {
// Set the stroke width of the object.
strokeWidth(width: number): void;

// Set the stroke color of the object.
strokeColor(color: string): void;

Expand All @@ -90,6 +91,12 @@ export interface EngineRect extends EngineObject {
plot(x: number, y: number, w: number, h: number, rotation?: number, rx?: number, ry?: number): void;
}

export interface EngineRect extends EngineRectOrEllipse {
}

export interface EngineEllipse extends EngineRectOrEllipse {
}

export interface EngineLine extends EngineObject {
// The color of the line.
color(value: string): void;
Expand All @@ -109,7 +116,6 @@ export interface EngineText extends EngineObject {
fontSize(value: string): void;

// Sets the font family.

fontFamily(value: string): void;

// Sets the text content.
Expand Down
14 changes: 10 additions & 4 deletions src/wireframes/engine/svg/canvas/SvgCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@

import * as svg from '@svgdotjs/svg.js';
import * as React from 'react';
import { ViewBox } from '@app/core';
import { Engine } from '../../interface';
import { Vec2, ViewBox } from '@app/core';
import { SvgEngine } from '../engine';

export interface SvgCanvasProps {
// The optional viewbox.
viewBox: ViewBox;

// The size.
size?: Vec2;

// The class name.
className?: string;

// The callback when the canvas has been initialized.
onInit: (engine: Engine) => any;
onInit: (engine: SvgEngine) => any;
}

export const SvgCanvasView = (props: SvgCanvasProps) => {
Expand Down Expand Up @@ -48,7 +50,11 @@ export const SvgCanvasView = (props: SvgCanvasProps) => {
}, [engine, onInit]);

React.useEffect(() => {
engine?.viewBox(viewBox.minX, viewBox.minY, viewBox.maxX, viewBox.maxY);
if (!engine) {
return;
}

engine.viewBox(viewBox.minX, viewBox.minY, viewBox.maxX, viewBox.maxY);
}, [engine, viewBox.minX, viewBox.minY, viewBox.maxX, viewBox.maxY]);

return (
Expand Down
208 changes: 208 additions & 0 deletions src/wireframes/engine/svg/engine.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* mydraft.cc
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved.
*/

import { Meta } from '@storybook/react';
import * as React from 'react';
import { EngineObject, Listener } from './../interface';
import { SvgCanvasView } from './canvas/SvgCanvas';
import { SvgEngine } from './engine';

const RendererHelper = ({ render }: { render: (engine: SvgEngine) => void }) => {
const [engine, setEngine] = React.useState<SvgEngine>();

React.useEffect(() => {
if (engine) {
engine?.doc.size(500, 500);
}
}, [engine, engine?.doc]);

React.useEffect(() => {
if (engine) {
engine.doc.clear();
render(engine);
}
}, [engine, render]);

return (
<div style={{ lineHeight: 0 }}>
<SvgCanvasView viewBox={{ minX: 0, minY: 0, maxX: 500, maxY: 500, zoom: 1 }} onInit={setEngine} />
</div>
);
};

const HitTest = () => {
const [engine, setEngine] = React.useState<SvgEngine>();
const [hits, setHits] = React.useState<EngineObject[]>();

React.useEffect(() => {
if (engine) {
engine?.doc.size(500, 500);
}
}, [engine, engine?.doc]);

React.useEffect(() => {
if (!engine) {
return;
}

engine.doc.clear();

const layer = engine.layer('layer1');

const rect1 = layer.rect();
rect1.strokeColor('red');
rect1.strokeWidth(2);
rect1.fill('blue');
rect1.plot(100, 150, 300, 200, 45);

const rect2 = layer.rect();
rect2.strokeColor('green');
rect2.strokeWidth(2);
rect2.fill('yellow');
rect2.plot(600, 150, 300, 200);

const listener: Listener = {
onMouseMove: (event) => {
const hits = layer.hitTest(event.position.x, event.position.y);
setHits(hits);
},
};

engine.subscribe(listener);

return () => {
engine.unsubscribe(listener);
};

}, [engine]);

return (
<div style={{ lineHeight: 0 }}>
Hits: {hits?.length}

<SvgCanvasView viewBox={{ minX: 0, minY: 0, maxX: 500, maxY: 500, zoom: 1 }} onInit={setEngine} />
</div>
);
};

export default {
component: RendererHelper,
} as Meta<typeof RendererHelper>;

export const Hits = () => {
return (
<HitTest />
);
};

export const Rect = () => {
return (
<RendererHelper
render={(engine) => {
const layer = engine.layer('layer1');

const rect = layer.rect();
rect.strokeColor('red');
rect.strokeWidth(2);
rect.fill('blue');
rect.plot(100, 150, 300, 200);
}}
/>
);
};

export const Ellipse = () => {
return (
<RendererHelper
render={(engine) => {
const layer = engine.layer('layer1');

const ellipse = layer.ellipse();
ellipse.strokeColor('red');
ellipse.strokeWidth(2);
ellipse.fill('blue');
ellipse.plot(100, 150, 300, 200);
}}
/>
);
};

export const Line1 = () => {
return (
<RendererHelper
render={(engine) => {
const layer = engine.layer('layer1');

const line1 = layer.line();
line1.color('red');
line1.plot(100, 150, 200, 250, 1);

const line2 = layer.line();
line2.color('blue');
line2.plot(140, 150, 240, 250, 2);

const line3 = layer.line();
line3.color('green');
line3.plot(180, 150, 280, 250, 4);
}}
/>
);
};

export const Text = () => {
return (
<RendererHelper
render={(engine) => {
const layer = engine.layer('layer1');

const text1 = layer.text();
text1.color('white');
text1.fontFamily('inherit');
text1.fontSize('16px');
text1.fill('black');
text1.text('Hello SVG');
text1.plot(50, 100, 200, 60, 20);

const text2 = layer.text();
text2.color('white');
text2.fontFamily('inherit');
text2.fontSize('16px');
text2.fill('red');
text2.text('Hello SVG');
text2.plot(50, 200, 200, 100, 20);
}}
/>
);
};

export const Cursors = () => {
return (
<RendererHelper
render={(engine) => {
const layer = engine.layer('layer1');

const move = layer.text();
move.color('white');
move.fontFamily('inherit');
move.fontSize('16px');
move.fill('black');
move.text('Move');
move.cursor('move');
move.plot(50, 100, 200, 60, 20);

const resize = layer.text();
resize.color('white');
resize.fontFamily('inherit');
resize.fontSize('16px');
resize.fill('red');
resize.text('resize');
resize.cursor('n-resize');
resize.plot(50, 200, 200, 100, 20);
}}
/>
);
};
Loading

0 comments on commit d96095c

Please sign in to comment.