Skip to content

Commit

Permalink
Merge pull request #585 from tuanchauict/canvas
Browse files Browse the repository at this point in the history
[Port to JS] Port canvas drawing
  • Loading branch information
tuanchauict authored Feb 20, 2024
2 parents fe667a9 + 390d0f0 commit 49e384b
Show file tree
Hide file tree
Showing 13 changed files with 576 additions and 9 deletions.
2 changes: 1 addition & 1 deletion monosketch-svelte/src/lib/mono/keycommand/controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Flow } from '../../libs/flow';
import { Flow } from '$libs/flow';
import { type KeyCommand, KeyCommandType } from './interface';
import { getCommandByType, getCommandByKey } from './keycommands';
import { DEBUG_MODE } from '../build_environment';
Expand Down
8 changes: 8 additions & 0 deletions monosketch-svelte/src/lib/mono/monobitmap/board/board.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Pixel } from '$mono/monobitmap/board/pixel';

/**
* An interface for the board of the Mono Bitmap.
*/
export interface MonoBoard {
get(left: number, top: number): Pixel;
}
19 changes: 19 additions & 0 deletions monosketch-svelte/src/lib/mono/monobitmap/board/pixel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Char } from '$libs/char';

/**
* An interface for the pixel of the Mono Bitmap.
*/
export interface Pixel {
visualChar: Char;
directionChar: Char;
highlight: HighlightType;

isTransparent: boolean;
}

export enum HighlightType {
NO,
SELECTED,
TEXT_EDITING,
LINE_CONNECT_FOCUSING
}
147 changes: 147 additions & 0 deletions monosketch-svelte/src/lib/mono/shape/interaction-bound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import type { Rect } from '$libs/graphics-geo/rect';
import { MouseCursor } from '$mono/workspace/mouse/cursor-type';

export enum InteractionBoundType {
LINE,
SCALABLE_SHAPE,
}

/**
* An interface to define all possible interaction bound types
*/
export interface InteractionBound {
readonly type: InteractionBoundType;
readonly interactionPoints: InteractionPoint[];
}

export class ScalableInteractionBound implements InteractionBound {
readonly type = InteractionBoundType.SCALABLE_SHAPE;

constructor(
public readonly interactionPoints: InteractionPoint[],
public readonly left: number,
public readonly top: number,
public readonly right: number,
public readonly bottom: number,
) {}

static of = (targetedShapeId: string, shapeBound: Rect): ScalableInteractionBound => {
const left = shapeBound.left - 0.25;
const top = shapeBound.top - 0.25;
const right = shapeBound.right + 0.25;
const bottom = shapeBound.bottom + 0.25;
const horizontalMiddle = (left + right) / 2;
const verticalMiddle = (top + bottom) / 2;
return new ScalableInteractionBound(
[
{
shapeId: targetedShapeId,
type: InteractionPointType.TOP_LEFT,
left: left,
top: top,
},
{
shapeId: targetedShapeId,
type: InteractionPointType.TOP_MIDDLE,
left: horizontalMiddle,
top: top,
},
{
shapeId: targetedShapeId,
type: InteractionPointType.TOP_RIGHT,
left: right,
top: top,
},
{
shapeId: targetedShapeId,
type: InteractionPointType.MIDDLE_LEFT,
left: left,
top: verticalMiddle,
},
{
shapeId: targetedShapeId,
type: InteractionPointType.MIDDLE_RIGHT,
left: right,
top: verticalMiddle,
},
{
shapeId: targetedShapeId,
type: InteractionPointType.BOTTOM_LEFT,
left: left,
top: bottom,
},
{
shapeId: targetedShapeId,
type: InteractionPointType.BOTTOM_MIDDLE,
left: horizontalMiddle,
top: bottom,
},
{
shapeId: targetedShapeId,
type: InteractionPointType.BOTTOM_RIGHT,
left: right,
top: bottom,
},
],
left,
top,
right,
bottom,
);
};
}

export class LineInteractionBound implements InteractionBound {
readonly type = InteractionBoundType.LINE;

constructor(public readonly interactionPoints: InteractionPoint[]) {}
}

/**
* An interface to define the interaction point for a shape and common APIs.
*
* @left the center position of the interaction point in the x-axis, in board-related unit.
* @top the center position of the interaction point in the y-axis, in board-related unit.
*/
export interface InteractionPoint {
shapeId: string;
type: InteractionPointType;
left: number;
top: number;
}

/**
* An enum to define the type of the interaction point for a shape.
*/
export enum InteractionPointType {
TOP_LEFT,
TOP_MIDDLE,
TOP_RIGHT,
MIDDLE_LEFT,
MIDDLE_RIGHT,
BOTTOM_LEFT,
BOTTOM_MIDDLE,
BOTTOM_RIGHT,
LINE_HORIZONTAL,
LINE_VERTICAL,
LINE_ANCHOR,
}

export const scalableInteractionPointTypeToCursor = (type: InteractionPointType): MouseCursor => {
switch (type) {
case InteractionPointType.TOP_LEFT:
case InteractionPointType.BOTTOM_RIGHT:
return MouseCursor.RESIZE_NWSE;
case InteractionPointType.TOP_MIDDLE:
case InteractionPointType.BOTTOM_MIDDLE:
return MouseCursor.RESIZE_NS;
case InteractionPointType.TOP_RIGHT:
case InteractionPointType.BOTTOM_LEFT:
return MouseCursor.RESIZE_NESW;
case InteractionPointType.MIDDLE_LEFT:
case InteractionPointType.MIDDLE_RIGHT:
return MouseCursor.RESIZE_EW;
default:
return MouseCursor.DEFAULT;
}
};
3 changes: 3 additions & 0 deletions monosketch-svelte/src/lib/mono/ui-state-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ScrollMode, type ThemeColor, type ThemeMode } from '$mono/ui-state-mana
import { AppThemeManager } from '$mono/ui-state-manager/theme-manager';
import { ScrollModeManager } from '$mono/ui-state-manager/scroll-mode-manager';
import { PanelVisibilityManager } from '$mono/ui-state-manager/panel-visibility-manager';
import { type KeyCommand, KeyCommandController } from '$mono/keycommand';

/**
* A domain class for managing UI state of the app.
Expand All @@ -12,11 +13,13 @@ export class AppUiStateManager {
private appThemeManager = new AppThemeManager();
private scrollModeManager = new ScrollModeManager();
private panelVisibilityManager = new PanelVisibilityManager();
private keyCommandController = new KeyCommandController(document.body);

themeModeFlow: Flow<ThemeMode> = this.appThemeManager.themeModeFlow;
scrollModeFlow: Flow<ScrollMode> = this.scrollModeManager.scrollModeFlow;
shapeFormatPanelVisibilityFlow: Flow<boolean> =
this.panelVisibilityManager.shapeFormatPanelVisibilityFlow;
keyCommandFlow: Flow<KeyCommand> = this.keyCommandController.keyCommandFlow;

constructor(private appLifecycleOwner: LifecycleOwner) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export class AxisCanvasViewController extends BaseCanvasViewController {
}

setDrawingInfo = (drawingInfo: DrawingInfo) => {
console.log(drawingInfo);
const canvasSizePx = Size.of(
drawingInfo.canvasSizePx.width + AXIS_Y_WIDTH,
drawingInfo.canvasSizePx.height + AXIS_X_HEIGHT,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { BaseCanvasViewController } from '$mono/workspace/canvas/base-canvas-controller';
import { type ThemeManager } from '$mono/ui-state-manager/theme-manager';
import { HighlightType, type Pixel } from '$mono/monobitmap/board/pixel';
import { ThemeColors } from '$mono/ui-state-manager/states';
import type { MonoBoard } from '$mono/monobitmap/board/board';

/**
* A class for managing the board canvas.
*/
export class BoardCanvasViewController extends BaseCanvasViewController {

constructor(
canvas: HTMLCanvasElement,
private board: MonoBoard,
private themeManager: ThemeManager,
) {
super(canvas);
}

protected drawInternal() {
const drawingInfo = this.drawingInfo;
this.context.font = drawingInfo.font;
for (let row of drawingInfo.boardRowRange) {
for (let column of drawingInfo.boardColumnRange) {
this.drawPixel(this.board.get(row, column), row, column);
}
}
}

private drawPixel = (pixel: Pixel, row: number, column: number) => {
if (pixel.isTransparent) {
return;
}
this.context.fillStyle = this.themeManager.getThemedColorCode(this.getPixelColor(pixel));
this.drawText(pixel.visualChar, row, column);
};

private getPixelColor = (pixel: Pixel) => {
switch (pixel.highlight) {
case HighlightType.NO:
return ThemeColors.Shape;
case HighlightType.SELECTED:
return ThemeColors.ShapeSelected;
case HighlightType.TEXT_EDITING:
return ThemeColors.ShapeTextEditing;
case HighlightType.LINE_CONNECT_FOCUSING:
return ThemeColors.ShapeLineConnectTarget;
default:
throw new Error(`Unknown highlight type: ${pixel.highlight}`);
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { BaseCanvasViewController } from '$mono/workspace/canvas/base-canvas-controller';
import type { ThemeManager } from '$mono/ui-state-manager/theme-manager';
import { ThemeColors } from '$mono/ui-state-manager/states';

/**
* A class for managing the grid canvas.
*/
export class GridCanvasViewController extends BaseCanvasViewController {
constructor(
canvas: HTMLCanvasElement,
private themeManager: ThemeManager,
) {
super(canvas);
}

protected drawInternal = () => {
const context = this.context;
context.strokeStyle = this.themeManager.getThemedColorCode(ThemeColors.GridLine);
context.lineWidth = 0.25;
context.stroke(this.createGridPath());
};

private createGridPath = (): Path2D => {
const drawingInfo = this.drawingInfo;
const zeroX = drawingInfo.toXPx(drawingInfo.boardColumnRange.start - 1.0);
const maxX = drawingInfo.toXPx(drawingInfo.boardColumnRange.endExclusive + 1.0);
const zeroY = drawingInfo.toYPx(drawingInfo.boardRowRange.start - 1.0);
const maxY = drawingInfo.toYPx(drawingInfo.boardRowRange.endExclusive + 1.0);

const path = new Path2D();

for (let row of drawingInfo.boardRowRange) {
const yPx = drawingInfo.toYPx(row);
this.addHLine(path, zeroX, yPx, maxX - zeroX);
}

for (let column of drawingInfo.boardColumnRange) {
const xPx = drawingInfo.toXPx(column);
this.addVLine(path, xPx, zeroY, maxY - zeroY);
}

return path;
};
}
Loading

0 comments on commit 49e384b

Please sign in to comment.