Skip to content

Commit

Permalink
feat(freehand): initialize freehand #2
Browse files Browse the repository at this point in the history
  • Loading branch information
pubuzhixing8 committed Nov 22, 2024
1 parent 61dbd52 commit eb6c07a
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 11 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"classnames": "^2.5.1",
"mobile-detect": "^1.4.5",
"open-color": "^1.9.1",
"points-on-curve": "^1.0.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"slate": "^0.101.5",
Expand Down
19 changes: 17 additions & 2 deletions packages/drawnix/src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ export const RedoIcon = createIcon(
);

export const TrashIcon = createIcon(
<svg role="img" viewBox="0 0 20 20" fill="none" stroke="currentColor">
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor">
<path
strokeWidth="1.25"
d="M3.333 5.833h13.334M8.333 9.167v5M11.667 9.167v5M4.167 5.833l.833 10c0 .92.746 1.667 1.667 1.667h6.666c.92 0 1.667-.746 1.667-1.667l.833-10M7.5 5.833v-2.5c0-.46.373-.833.833-.833h3.334c.46 0 .833.373.833.833v2.5"
Expand All @@ -426,10 +426,25 @@ export const TrashIcon = createIcon(
);

export const DuplicateIcon = createIcon(
<svg role="img" viewBox="0 0 20 20" fill="none" stroke="currentColor">
<svg
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<g strokeWidth="1.25">
<path d="M14.375 6.458H8.958a2.5 2.5 0 0 0-2.5 2.5v5.417a2.5 2.5 0 0 0 2.5 2.5h5.417a2.5 2.5 0 0 0 2.5-2.5V8.958a2.5 2.5 0 0 0-2.5-2.5Z"></path>
<path d="M11.667 3.125c.517 0 .986.21 1.325.55.34.338.55.807.55 1.325v1.458H8.333c-.485 0-.927.185-1.26.487-.343.312-.57.75-.609 1.24l-.005 5.357H5a1.87 1.87 0 0 1-1.326-.55 1.87 1.87 0 0 1-.549-1.325V5c0-.518.21-.987.55-1.326.338-.34.807-.549 1.325-.549h6.667Z"></path>
</g>
</svg>
);

export const FeltTipPenIcon = createIcon(
<svg
viewBox="0 0 1024 1024"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M170.794667 896c3.456 0 6.912-0.426667 10.325333-1.28l170.666667-42.666667c7.509333-1.877333 14.378667-5.76 19.84-11.221333L896.128 316.330667c16.128-16.128 25.002667-37.546667 25.002667-60.330667s-8.874667-44.202667-25.002667-60.330667L828.458667 128c-32.256-32.256-88.405333-32.256-120.661334 0L183.296 652.501333a42.794667 42.794667 0 0 0-11.221333 19.797334l-42.666667 170.666666A42.666667 42.666667 0 0 0 170.794667 896z m597.333333-707.669333L835.797333 256l-67.669333 67.669333L700.458667 256l67.669333-67.669333zM251.989333 704.469333l388.138667-388.138666L707.797333 384l-388.181333 388.138667-90.197333 22.528 22.570666-90.197334z"></path>
</svg>
);
7 changes: 7 additions & 0 deletions packages/drawnix/src/components/toolbar/creation-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ShapeIcon,
TextIcon,
StraightArrowLineIcon,
FeltTipPenIcon,
} from '../icons';
import { useBoard } from '@plait/react-board';
import {
Expand All @@ -25,6 +26,7 @@ import { ShapePicker } from '../shape-picker';
import { ArrowPicker } from '../arrow-picker';
import { useState } from 'react';
import { Popover, PopoverContent, PopoverTrigger } from '../popover/popover';
import { FreehandShape } from '../../plugins/freehand/type';

export enum PopupKey {
'shape' = 'shape',
Expand Down Expand Up @@ -66,6 +68,11 @@ export const BUTTONS: AppToolButtonProps[] = [
pointer: BasicShapes.text,
title: 'Text',
},
{
icon: FeltTipPenIcon,
pointer: FreehandShape.feltTipPen,
title: 'Freehand',
},
{
icon: StraightArrowLineIcon,
title: 'Arrow Line',
Expand Down
6 changes: 5 additions & 1 deletion packages/drawnix/src/drawnix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { AppToolbar } from './components/toolbar/app-toolbar/app-toolbar';
import classNames from 'classnames';
import './styles/index.scss';
import { withDrawnixHotkey } from './plugins/with-hotkey';
import { FreehandShape } from './plugins/freehand/type';
import { withFreehand } from './plugins/freehand/with-freehand';

export type DrawnixProps = {
value: PlaitElement[];
Expand All @@ -38,7 +40,8 @@ export type DrawnixProps = {
export type DrawnixPointerType =
| PlaitPointerType
| MindPointerType
| DrawPointerType;
| DrawPointerType
| FreehandShape;

export type DrawnixState = {
pointer: DrawnixPointerType;
Expand All @@ -58,6 +61,7 @@ export const Drawnix: React.FC<DrawnixProps> = ({
withMindExtend,
withCommonPlugin,
withDrawnixHotkey,
withFreehand
];
const options: PlaitBoardOptions = {};
const [appState, setAppState] = useState<DrawnixState>(() => {
Expand Down
57 changes: 57 additions & 0 deletions packages/drawnix/src/plugins/freehand/freehand.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
PlaitBoard,
PlaitPluginElementContext,
OnContextChanged,
RectangleClient,
isSelectionMoving,
} from '@plait/core';
import { ActiveGenerator, CommonElementFlavour } from '@plait/common';
import { Freehand } from './type';
import { FreehandGenerator } from './freehand.generator';

export class FreehandComponent
extends CommonElementFlavour<Freehand, PlaitBoard>
implements OnContextChanged<Freehand, PlaitBoard>
{
constructor() {
super();
}

activeGenerator!: ActiveGenerator<Freehand>;

generator!: FreehandGenerator;

initializeGenerator() {
this.activeGenerator = new ActiveGenerator<Freehand>(this.board, {
getRectangle: (element: Freehand) => {
return RectangleClient.getRectangleByPoints(element.points);
},
getStrokeWidth: () => 0,
getStrokeOpacity: () => 0,
hasResizeHandle: () => {
return !isSelectionMoving(this.board);
},
});
this.generator = new FreehandGenerator(this.board);
}

initialize(): void {
super.initialize();
this.initializeGenerator();
this.generator.processDrawing(this.element, this.getElementG());
}

onContextChanged(
value: PlaitPluginElementContext<Freehand, PlaitBoard>,
previous: PlaitPluginElementContext<Freehand, PlaitBoard>
) {
if (value.element !== previous.element) {
} else {
const hasSameSelected = value.selected === previous.selected;
}
}

destroy(): void {
super.destroy();
}
}
22 changes: 22 additions & 0 deletions packages/drawnix/src/plugins/freehand/freehand.generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Generator } from '@plait/common';
import { PlaitBoard, setStrokeLinecap } from '@plait/core';
import { Options } from 'roughjs/bin/core';
import { DefaultFreehand, Freehand } from './type';

export interface FreehandData {}

export class FreehandGenerator extends Generator<Freehand, FreehandData> {
protected draw(
element: Freehand,
data?: FreehandData | undefined
): SVGGElement | undefined {
let option: Options = { ...DefaultFreehand };
const g = PlaitBoard.getRoughSVG(this.board).curve(element.points, option);
setStrokeLinecap(g, 'round');
return g;
}

canDraw(element: Freehand, data: FreehandData): boolean {
return true;
}
}
30 changes: 30 additions & 0 deletions packages/drawnix/src/plugins/freehand/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { StrokeStyle } from '@plait/common';
import { DEFAULT_COLOR, PlaitElement, Point } from '@plait/core';

export const DefaultFreehand = {
strokeColor: DEFAULT_COLOR,
strokeWidth: 4,
};

export enum FreehandShape {
nibPen = 'nibPen',
feltTipPen = 'feltTipPen',
artisticBrush = 'artisticBrush',
markerHighlight = 'markerHighlight',
}

export interface Freehand extends PlaitElement {
type: 'freehand';
points: Point[];
shape: FreehandShape;
strokeColor?: string;
strokeWidth?: number;
strokeStyle?: StrokeStyle;
fill?: string;
}

export const Freehand = {
isFreehand: (value: any): value is Freehand => {
return value.type === 'freehand';
},
};
21 changes: 21 additions & 0 deletions packages/drawnix/src/plugins/freehand/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { idCreator, Point } from '@plait/core';
import { DefaultFreehand, Freehand, FreehandShape } from './type';

export function getFreehandPointers() {
return [FreehandShape.feltTipPen];
}

export const createFreehandElement = (
shape: FreehandShape,
points: Point[]
): Freehand => {
const element: Freehand = {
id: idCreator(),
type: 'freehand',
shape,
points,
strokeWidth: DefaultFreehand.strokeWidth,
strokeColor: DefaultFreehand.strokeColor,
};
return element;
};
85 changes: 85 additions & 0 deletions packages/drawnix/src/plugins/freehand/with-freehand-create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
PlaitBoard,
Point,
Transforms,
createG,
throttleRAF,
toHostPoint,
toViewBoxPoint,
} from '@plait/core';
import { isDrawingMode } from '@plait/common';
import { createFreehandElement, getFreehandPointers } from './utils';
import { Freehand, FreehandShape } from './type';
import { FreehandGenerator } from './freehand.generator';

export const withFreehandCreate = (board: PlaitBoard) => {
const { pointerDown, pointerMove, pointerUp, globalPointerUp } = board;

let isDrawing: boolean = false;

let points: Point[] = [];

const generator = new FreehandGenerator(board);

let temporaryElement: Freehand | null = null;

const complete = (cancel?: boolean) => {
if (isDrawing) {
const pointer = PlaitBoard.getPointer(board) as FreehandShape;
temporaryElement = createFreehandElement(pointer, points);
}
if (temporaryElement && !cancel) {
Transforms.insertNode(board, temporaryElement, [board.children.length]);
}
generator?.destroy();
temporaryElement = null;
isDrawing = false;
points = [];
};

board.pointerDown = (event: PointerEvent) => {
const freehandPointers = getFreehandPointers();
const isFreehandPointer = PlaitBoard.isInPointer(board, freehandPointers);
if (isFreehandPointer && isDrawingMode(board)) {
isDrawing = true;
const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
points.push(point);
}
pointerDown(event);
};

board.pointerMove = (event: PointerEvent) => {
if (isDrawing) {
throttleRAF(board, 'with-freehand-creation', () => {
generator?.destroy();
const newPoint = toViewBoxPoint(
board,
toHostPoint(board, event.x, event.y)
);
points.push(newPoint);
const pointer = PlaitBoard.getPointer(board) as FreehandShape;

temporaryElement = createFreehandElement(pointer, points);
generator.processDrawing(
temporaryElement,
PlaitBoard.getElementActiveHost(board)
);
});
return;
}

pointerMove(event);
};

board.pointerUp = (event: PointerEvent) => {
complete();
pointerUp(event);
};

board.globalPointerUp = (event: PointerEvent) => {
complete(true);
globalPointerUp(event);
};

return board;
};
29 changes: 29 additions & 0 deletions packages/drawnix/src/plugins/freehand/with-freehand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
PlaitBoard,
PlaitElement,
PlaitPluginElementContext,
RectangleClient,
} from '@plait/core';
import { Freehand } from './type';
import { FreehandComponent } from './freehand.component';
import { withFreehandCreate } from './with-freehand-create';

export const withFreehand = (board: PlaitBoard) => {
const { getRectangle, drawElement } = board;

board.drawElement = (context: PlaitPluginElementContext) => {
if (Freehand.isFreehand(context.element)) {
return FreehandComponent;
}
return drawElement(context);
};

board.getRectangle = (element: PlaitElement) => {
if (Freehand.isFreehand(element)) {
return RectangleClient.getRectangleByPoints(element.points);
}
return getRectangle(element);
};

return withFreehandCreate(board);
};
Loading

0 comments on commit eb6c07a

Please sign in to comment.