-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Engine separation. * Better hit test * Fix tests * Improve engine design. * More tests * Factories tests * More tests
- Loading branch information
1 parent
fb048ee
commit e095ac5
Showing
61 changed files
with
2,114 additions
and
1,317 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* mydraft.cc | ||
* | ||
* @license | ||
* Copyright (c) Sebastian Stehle. All rights reserved. | ||
*/ | ||
|
||
import { sizeInPx } from './react'; | ||
|
||
export interface TextMeasurer { | ||
getTextWidth(text: string, fontSize: number, fontFamily: string): number; | ||
} | ||
|
||
class DefaultTextMeasurer { | ||
private readonly measureDiv: HTMLDivElement; | ||
|
||
constructor() { | ||
this.measureDiv = document.createElement('div'); | ||
this.measureDiv.style.height = 'auto'; | ||
this.measureDiv.style.position = 'absolute'; | ||
this.measureDiv.style.visibility = 'hidden'; | ||
this.measureDiv.style.width = 'auto'; | ||
this.measureDiv.style.whiteSpace = 'nowrap'; | ||
document.body.appendChild(this.measureDiv); | ||
} | ||
|
||
public getTextWidth(text: string, fontSize: number, fontFamily: string) { | ||
this.measureDiv.textContent = text; | ||
this.measureDiv.style.fontSize = sizeInPx(fontSize); | ||
this.measureDiv.style.fontFamily = fontFamily; | ||
|
||
return this.measureDiv.clientWidth + 1; | ||
} | ||
} | ||
|
||
export module TextMeasurer { | ||
export const DEFAULT = new DefaultTextMeasurer(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
* mydraft.cc | ||
* | ||
* @license | ||
* Copyright (c) Sebastian Stehle. All rights reserved. | ||
*/ | ||
|
||
export * from './interface'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
/** | ||
* mydraft.cc | ||
* | ||
* @license | ||
* Copyright (c) Sebastian Stehle. All rights reserved. | ||
*/ | ||
|
||
import { Vec2 } from '@app/core'; | ||
import { ShapePlugin } from '@app/wireframes/interface'; | ||
import { DiagramItem } from './../model'; | ||
|
||
export type NextListener<T> = (event: T) => void; | ||
|
||
export class HitEvent { | ||
constructor( | ||
public readonly event: MouseEvent, | ||
public readonly position: Vec2, | ||
public readonly layer: EngineLayer, | ||
public readonly object?: EngineObject | null, | ||
public readonly item?: DiagramItem | null, | ||
) { | ||
} | ||
} | ||
|
||
export interface Engine { | ||
// Add a new layer to the render output with the name of the layer for debugging. | ||
layer(id?: string): EngineLayer; | ||
|
||
// Sets the layer that is used for click events. | ||
setClickLayer(layer: EngineLayer): void; | ||
|
||
// Subscribe to all events. | ||
subscribe(listener: Listener): void; | ||
|
||
// Unsubscribe from all events. | ||
unsubscribe(listener: Listener): void; | ||
} | ||
|
||
export interface Listener { | ||
onBlur?(event: FocusEvent, next: NextListener<FocusEvent>): void; | ||
onDoubleClick?(event: HitEvent, next: NextListener<HitEvent>): void; | ||
onClick?(event: HitEvent, next: NextListener<HitEvent>): boolean; | ||
onMouseDown?(event: HitEvent, next: NextListener<HitEvent>): void; | ||
onMouseDrag?(event: HitEvent, next: NextListener<HitEvent>): void; | ||
onMouseMove?(event: HitEvent, next: NextListener<HitEvent>): void; | ||
onMouseUp?(event: HitEvent, next: NextListener<HitEvent>): void; | ||
onKeyDown?(event: KeyboardEvent, next: (event: KeyboardEvent) => void): void; | ||
onKeyUp?(event: KeyboardEvent, next: (event: KeyboardEvent) => void): void; | ||
} | ||
|
||
export interface EngineLayer { | ||
// Creates a new object to render a rect. | ||
rect(): EngineRect; | ||
|
||
// Creates a new object to render a ellipse. | ||
ellipse(): EngineRect; | ||
|
||
// Creates a new object to render a line. | ||
line(): EngineLine; | ||
|
||
// Creates a new object to render a text element. | ||
text(): EngineText; | ||
|
||
// Creates a new object to render an item. | ||
item(plugin: ShapePlugin): EngineItem; | ||
|
||
// Removes the layer from the parent. | ||
remove(): void; | ||
|
||
// Shows the layer. | ||
show(): void; | ||
|
||
// Hides the layer. | ||
hide(): void; | ||
|
||
// Makes a hit and returns matching elements. | ||
hitTest(x: number, y: number): 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; | ||
|
||
// Sets the fill color. | ||
fill(value: string): void; | ||
|
||
// Renders with position, size and rotation. | ||
plot(args: { 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; | ||
|
||
// Renders the line from (x1, y1) to (x2, y2). | ||
plot(args: { x1: number; y1: number; x2: number; y2: number; width: number }): void; | ||
} | ||
|
||
export interface EngineText extends EngineObject { | ||
// Sets the text color. | ||
color(value: string): void; | ||
|
||
// Sets the background color. | ||
fill(value: string): void; | ||
|
||
// Sets the font size. | ||
fontSize(value: string): void; | ||
|
||
// Sets the font family. | ||
fontFamily(value: string): void; | ||
|
||
// Sets the text content. | ||
text(value: string): void; | ||
|
||
// Defines the dimensions. | ||
plot(args: { x: number; y: number; w: number; h: number; padding: number }): void; | ||
} | ||
|
||
export interface EngineObject { | ||
// Defines the cursor for the object. | ||
cursor(value: string | number): void; | ||
|
||
// Removes the element from the parent. | ||
remove(): void; | ||
|
||
// Shows the object. | ||
show(): void; | ||
|
||
// Hides the object. | ||
hide(): void; | ||
|
||
// Disable the object. | ||
disable(): void; | ||
|
||
// Sets or gets the label. | ||
label(value?: string): string; | ||
} | ||
|
||
export interface EngineItem extends EngineObject { | ||
// Removes the element from the parent. | ||
detach(): void; | ||
|
||
// Renders the item. | ||
plot(item: DiagramItem | null): void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* mydraft.cc | ||
* | ||
* @license | ||
* Copyright (c) Sebastian Stehle. All rights reserved. | ||
*/ | ||
|
||
import * as svg from '@svgdotjs/svg.js'; | ||
import * as React from 'react'; | ||
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: SvgEngine) => any; | ||
} | ||
|
||
export const SvgCanvasView = (props: SvgCanvasProps) => { | ||
const { | ||
className, | ||
onInit, | ||
viewBox, | ||
} = props; | ||
|
||
const [engine, setEngine] = React.useState<SvgEngine>(); | ||
|
||
const doInit = React.useCallback((ref: HTMLDivElement) => { | ||
if (!ref) { | ||
return; | ||
} | ||
|
||
const doc = svg.SVG().addTo(ref).css({ position: 'relative', overflow: 'visible' }).attr('tabindex', 0); | ||
|
||
setEngine(new SvgEngine(doc)); | ||
}, []); | ||
|
||
React.useEffect(() => { | ||
if (engine && onInit) { | ||
onInit(engine); | ||
} | ||
}, [engine, onInit]); | ||
|
||
React.useEffect(() => { | ||
if (!engine) { | ||
return; | ||
} | ||
|
||
engine.viewBox(viewBox.minX, viewBox.minY, viewBox.maxX, viewBox.maxY); | ||
}, [engine, viewBox.minX, viewBox.minY, viewBox.maxX, viewBox.maxY]); | ||
|
||
return ( | ||
<div className={className} ref={doInit} /> | ||
); | ||
}; |
Oops, something went wrong.