Skip to content

Commit

Permalink
removes unused methods
Browse files Browse the repository at this point in the history
  • Loading branch information
hiddenist committed Dec 23, 2023
1 parent 699a2b8 commit 2d7bb96
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 16 deletions.
42 changes: 42 additions & 0 deletions apps/web/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ function main() {
link.href = url
link.click()
},
onUndo() {
engine.undo()
},
onRedo() {
engine.redo()
},
tools: tools,
initialTool: engine.getCurrentToolName(),

Expand Down Expand Up @@ -103,6 +109,9 @@ function makeToolbar<T extends string>(
onExport: (name: string) => void
onLoadImage: (image: HTMLImageElement) => void

onUndo: () => void
onRedo: () => void

addListener: WebDrawingEngine["addListener"]
},
) {
Expand Down Expand Up @@ -225,6 +234,39 @@ function makeToolbar<T extends string>(
})
inputTray.append(exportButton)

const undoButton = document.createElement("button")
const redoButton = document.createElement("button")
undoButton.classList.add("undo-button")
redoButton.classList.add("redo-button")
undoButton.innerText = "Undo"
redoButton.innerText = "Redo"
undoButton.disabled = true
redoButton.disabled = true
inputTray.append(undoButton)
inputTray.append(redoButton)
undoButton.addEventListener("click", (e) => {
e.preventDefault()
options.onUndo()
})
redoButton.addEventListener("click", (e) => {
e.preventDefault()
options.onRedo()
})
options.addListener("draw", () => {
undoButton.disabled = false
redoButton.disabled = true
})
options.addListener("undo", ({ undosLeft }) => {
console.log({ undosLeft })
undoButton.disabled = undosLeft === 0
redoButton.disabled = false
})
options.addListener("redo", ({ redosLeft }) => {
console.log({ redosLeft })
undoButton.disabled = false
redoButton.disabled = redosLeft === 0
})

const clearButton = document.createElement("button")
clearButton.classList.add("clear-button")
clearButton.innerText = "Clear"
Expand Down
141 changes: 141 additions & 0 deletions libs/drawing-engine/src/engine/CanvasHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { LineDrawInfo } from "../tools/LineTool"
import { EyeDropperInfo } from "../tools/EyeDropperTool"
import { DrawingEngine } from "./DrawingEngine"

type ToolInfo = LineDrawInfo | EyeDropperInfo

export interface HistoryState {
toolInfo: ToolInfo
imageData: string | null
}

interface HistoryOptions {
maxHistory: number
}

export class CanvasHistory {
protected redoHistory: Array<Readonly<HistoryState>> = []
protected history: Array<Readonly<HistoryState>> = []
protected hasTruncated = false

constructor(
protected readonly engine: DrawingEngine,
protected options: HistoryOptions,
) {
if (!options.maxHistory || options.maxHistory < 1) {
options.maxHistory = 10
}
}

public setOptions(options: Partial<HistoryOptions>) {
this.options = {
...this.options,
...options,
}
return this
}

public getOptions(): Readonly<HistoryOptions> {
return this.options
}

protected async getBlob(canvas: HTMLCanvasElement): Promise<Blob> {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
if (!blob) {
throw new Error("Could not get blob from canvas")
}
resolve(blob)
})
})
}

public save(toolInfo: ToolInfo) {
const canvas = this.engine.gl.canvas
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error("Canvas is not an HTMLCanvasElement")
}
const tool = this.engine.tools[toolInfo.tool]
if (!tool) {
throw new Error(`Tool ${toolInfo.tool} not found`)
}
this.addHistory({
toolInfo,
imageData: tool.updatesImageData ? canvas.toDataURL() : null,
})
}

public async redo() {
const state = this.redoHistory.pop()
if (!state) {
return
}
this.history.push(state)
return await this.drawState(state)
}

public async undo() {
const state = this.history.pop()
console.log("Undoing", state)
if (!state) {
return
}
const currentState = this.history[this.history.length - 1]
this.redoHistory.push(state)
return await this.drawState(currentState)
}

protected addHistory(state: HistoryState) {
console.log("Adding history", state)
if (this.history.length >= this.options.maxHistory) {
this.hasTruncated = true
this.history.shift()
}
this.history.push(state)
this.redoHistory = []
}

protected drawState(state: Readonly<HistoryState> | null) {
if (!state) {
if (!this.hasTruncated) this.engine.clearCanvas()
return Promise.resolve(null)
}
const { toolInfo, imageData } = state
console.log("Drawing state", state)
if (!imageData) {
return Promise.resolve(toolInfo)
}
return new Promise<HistoryState["toolInfo"]>((resolve, reject) => {
const image = new Image()
image.onload = () => {
this.engine._clear()
this.engine.loadImage(image)
resolve(toolInfo)
}
image.src = imageData
image.onerror = () => {
reject(new Error("Could not load image"))
}
})
}

public clear() {
this.history = []
}

public getHistory(): Readonly<{ undo: ReadonlyArray<HistoryState>; redo: ReadonlyArray<HistoryState> }> {
return {
undo: this.history,
redo: this.redoHistory,
}
}

public canUndo() {
const minStates = this.hasTruncated ? 1 : 0
return this.history.length > minStates
}

public canRedo() {
return this.redoHistory.length > 0
}
}
64 changes: 53 additions & 11 deletions libs/drawing-engine/src/engine/DrawingEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { Color } from "@libs/shared"
import { Layer, LayerSettings } from "./Layer"
import { SourceImage } from "../utils/image/SourceImage"
import { ToolName, ToolNames } from "../exports"
import { LineHistoryEntry, LineTool } from "../tools/LineTool"
import { LineDrawInfo, LineTool } from "../tools/LineTool"
import { InputPoint } from "../tools/InputPoint"
import { EyeDropperHistoryEntry, EyeDropperTool } from "../tools/EyeDropperTool"

type HistoryItem = LineHistoryEntry | EyeDropperHistoryEntry
import { EyeDropperInfo, EyeDropperTool } from "../tools/EyeDropperTool"
import { CanvasHistory, HistoryState } from "./CanvasHistory"

interface DrawingEngineState {
color: Color
Expand All @@ -28,8 +27,12 @@ export interface DrawingEngineOptions {
pixelDensity?: number
}

type ToolInfo = LineDrawInfo | EyeDropperInfo

export interface DrawingEngineEventMap {
draw: HistoryItem
draw: ToolInfo
undo: { toolInfo: HistoryState["toolInfo"]; undosLeft: number }
redo: { toolInfo: HistoryState["toolInfo"]; redosLeft: number }
pickColor: { color: Color }
previewColor: { color: Color | null }
clear: undefined
Expand All @@ -56,7 +59,6 @@ const defaultTool = ToolNames.brush
export class DrawingEngine {
protected state: DrawingEngineState
protected program: TextureDrawingProgram
protected history: Array<HistoryItem>
/**
* Saved drawing layer is the layer that actions, like brush strokes, are saved to after the user finishes drawing
* (e.g. releases the mouse, lifts the stylus, etc).
Expand All @@ -76,6 +78,7 @@ export class DrawingEngine {
}

private listeners: Partial<DrawingEventListeners> = {}
protected history: CanvasHistory

constructor(
public gl: WebGLRenderingContext,
Expand All @@ -91,12 +94,15 @@ export class DrawingEngine {
prevTool: defaultTool,
}

this.history = new CanvasHistory(this, {
maxHistory: 10,
})

this.savedDrawingLayer = this.makeLayer()
this.activeDrawingLayer = this.makeLayer({ clearBeforeDrawing: true })

this.program = new TextureDrawingProgram(gl, this.state.pixelDensity)

this.history = []
const lineProgram = new LineDrawingProgram(gl, this.state.pixelDensity)
this.tools = {
[ToolNames.brush]: new LineTool(this, lineProgram, ToolNames.brush),
Expand Down Expand Up @@ -167,10 +173,14 @@ export class DrawingEngine {
return this.state.opacity
}

public clearCanvas() {
public _clear() {
this.savedDrawingLayer.clear()
this.activeDrawingLayer.clear()
this.clearCurrent()
}

public clearCanvas() {
this._clear()
this.program.draw(this.activeDrawingLayer, this.savedDrawingLayer)

this.callListeners("clear", undefined)
Expand Down Expand Up @@ -203,7 +213,7 @@ export class DrawingEngine {
return position[0] >= 0 && position[0] <= this.state.width && position[1] >= 0 && position[1] <= this.state.height
}

public draw(drawCallback: () => HistoryItem | undefined): this {
public draw(drawCallback: () => DrawingEngineEventMap["draw"] | undefined): this {
const layer = this.activeDrawingLayer
this.program.createTextureImage(layer, () => {
const drawData = drawCallback()
Expand Down Expand Up @@ -268,7 +278,39 @@ export class DrawingEngine {
this.render("draw")
}

public addHistory(historyItem: HistoryItem) {
this.history.push(historyItem)
public addHistory(toolInfo: ToolInfo) {
this.history.save(toolInfo)
}

public async undo() {
const toolInfo = await this.history.undo()
if (!toolInfo) {
return
}
if (toolInfo.tool === "eyedropper" && "previousColor" in toolInfo) {
this.setColor(toolInfo.previousColor.copy())
} else {
this.setTool(toolInfo.tool)
}
const undosLeft = this.history.getHistory().undo.length
this.callListeners("undo", { toolInfo, undosLeft })
}

public async redo() {
const toolInfo = await this.history.redo()
if (!toolInfo) {
return
}
if (toolInfo.tool === "eyedropper" && "color" in toolInfo) {
this.setColor(toolInfo.color.copy())
} else {
this.setTool(toolInfo.tool)
}
const redosLeft = this.history.getHistory().redo.length
this.callListeners("redo", { toolInfo, redosLeft })
}

public getHistory() {
return this.history.getHistory()
}
}
9 changes: 8 additions & 1 deletion libs/drawing-engine/src/tools/EyeDropperTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { DrawingEngine, DrawingEngineEvent } from "../engine/DrawingEngine"
import { InputPoint } from "./InputPoint"
import { ToolNames } from "./Tools"

export type EyeDropperHistoryEntry = {
export type EyeDropperInfo = {
tool: "eyedropper"
color: Readonly<Color>
previousColor: Readonly<Color>
}

export class EyeDropperTool {
public readonly updatesImageData = false
public static readonly TOOL_NAME = ToolNames.eyedropper
public readonly toolName = EyeDropperTool.TOOL_NAME
constructor(public readonly engine: DrawingEngine) {
Expand Down Expand Up @@ -68,8 +69,14 @@ export class EyeDropperTool {
if (!color) {
return false
}
const previousColor = this.engine.getCurrentColor()
this.engine.setColor(color)
this.engine.callListeners("pickColor", { color })
this.engine.addHistory({
tool: this.toolName,
color,
previousColor,
})
return true
}
}
Loading

0 comments on commit 2d7bb96

Please sign in to comment.