Implement square brush type; wheel on preview to change size

main
kts of kettek 2024-02-15 10:41:38 -08:00
parent acb305f437
commit 731c0c2062
5 changed files with 65 additions and 17 deletions

View File

@ -17,7 +17,7 @@
import { Close, Erase, PaintBrushAlt, RainDrop, Redo, Select_01, Undo, Scale, Eyedropper } from "carbon-icons-svelte"
import StackPreview from './sections/StackPreview.svelte'
import type { Canvas } from './types/canvas'
import { BrushTool, EraserTool, FillTool, PickerTool, type Tool } from './types/tools';
import { BrushTool, EraserTool, FillTool, PickerTool, type BrushType, type Tool } from './types/tools';
import BrushSize from './components/BrushSize.svelte';
let theme: 'white'|'g10'|'g80'|'g90'|'g100' = 'g90'
@ -41,6 +41,7 @@
let toolPicker = new PickerTool()
let currentTool: Tool = toolBrush
let brushSize: number = 1
let brushType: BrushType = 'circle'
function swapTool(tool: Tool) {
currentTool = tool
@ -110,7 +111,7 @@
<section class='middle'>
<menu class='toolsettings'>
{#if currentTool === toolBrush || currentTool === toolErase}
<BrushSize bind:brushSize/>
<BrushSize bind:brushSize bind:brushType/>
<NumberInput size="sm" min={1} max={100} step={1} bind:value={brushSize}/>
{/if}
</menu>
@ -126,7 +127,7 @@
<svelte:fragment slot="content">
{#each files as file}
<TabContent>
<Editor2D bind:file={file} refresh={refresh} bind:primaryColorIndex={primaryColorIndex} bind:secondaryColorIndex={secondaryColorIndex} bind:currentTool={currentTool} brushSize={brushSize} />
<Editor2D bind:file={file} refresh={refresh} bind:primaryColorIndex={primaryColorIndex} bind:secondaryColorIndex={secondaryColorIndex} bind:currentTool={currentTool} brushSize={brushSize} brushType={brushType} />
</TabContent>
{/each}
</svelte:fragment>

View File

@ -1,9 +1,11 @@
<script lang='ts'>
import { FilledCircle } from "../types/shapes"
import { OverflowMenu, OverflowMenuItem } from "carbon-components-svelte";
import { FilledCircle, FilledSquare, type PixelPosition } from "../types/shapes"
export let brushSize: number
export let brushType: 'circle' | 'square' = 'circle'
$: ((brushSize: number) => {
$: ((brushSize: number, brushType: 'circle' | 'square') => {
if (canvas) {
let {width, height} = getComputedStyle(canvas)
canvas.width = parseInt(width)
@ -12,19 +14,35 @@
ctx.fillStyle = 'black'
ctx.fillRect(0, 0, canvas.width, canvas.height)
let shape = FilledCircle(canvas.width/2, canvas.height/2, brushSize, 1)
let shape: PixelPosition[]
if (brushType === 'square' || brushSize <= 2) {
shape = FilledSquare(canvas.width/2, canvas.height/2, brushSize, 1)
} else if (brushType === 'circle') {
shape = FilledCircle(canvas.width/2, canvas.height/2, brushSize-2, 1)
}
for (let i = 0; i < shape.length; i++) {
ctx.fillStyle = 'white'
ctx.fillRect(shape[i].x, shape[i].y, 1, 1)
}
}
})(brushSize)
})(brushSize, brushType)
function handleWheel(event: WheelEvent) {
event.preventDefault()
brushSize = Math.max(1, brushSize - Math.sign(event.deltaY))
}
let canvas: HTMLCanvasElement
</script>
<canvas bind:this={canvas}>
</canvas>
<div on:wheel={handleWheel}>
<OverflowMenu size="sm" style="width: auto">
<canvas slot="menu" bind:this={canvas}>
</canvas>
<OverflowMenuItem primaryFocus={brushType==='circle'} text="circle" on:click={()=>brushType='circle'}/>
<OverflowMenuItem primaryFocus={brushType==='square'} text="square" on:click={()=>brushType='square'}/>
</OverflowMenu>
</div>
<style>
canvas {

View File

@ -4,7 +4,7 @@
import type { data } from '../../wailsjs/go/models.ts'
import type { LoadedFile } from '../types/file'
import type { PixelPosition } from '../types/shapes'
import { BrushTool, EraserTool, FillTool, PickerTool, type Tool } from '../types/tools'
import { BrushTool, EraserTool, FillTool, PickerTool, type BrushType, type Tool } from '../types/tools'
import { Button, NumberInput, OverflowMenu, OverflowMenuItem, Slider } from 'carbon-components-svelte';
import { ZoomIn, ZoomOut } from 'carbon-icons-svelte';
@ -28,6 +28,7 @@
export let primaryColorIndex: number
export let secondaryColorIndex: number
export let brushSize: number
export let brushType: BrushType
let rootCanvas: HTMLCanvasElement
let overlayCanvas: HTMLCanvasElement = document.createElement('canvas')
@ -209,9 +210,9 @@
if (e.button === 0) {
if (currentTool instanceof BrushTool) {
currentTool.pointerDown({file, brushSize, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: e.button })
currentTool.pointerDown({file, brushSize, brushType, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: e.button })
} else if (currentTool instanceof EraserTool) {
currentTool.pointerDown({file, brushSize}, {x: mousePixelX, y: mousePixelY, id: e.button })
currentTool.pointerDown({file, brushSize, brushType}, {x: mousePixelX, y: mousePixelY, id: e.button })
} else if (currentTool instanceof FillTool) {
currentTool.pointerDown({file, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: e.button })
} else if (currentTool instanceof PickerTool) {
@ -271,9 +272,9 @@
if (buttons.has(0)) {
if (currentTool.isActive()) {
if (currentTool instanceof BrushTool) {
currentTool.pointerMove({file, brushSize, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: 0 })
currentTool.pointerMove({file, brushSize, brushType, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: 0 })
} else if (currentTool instanceof EraserTool) {
currentTool.pointerMove({file, brushSize}, {x: mousePixelX, y: mousePixelY, id: 0 })
currentTool.pointerMove({file, brushSize, brushType}, {x: mousePixelX, y: mousePixelY, id: 0 })
} else if (currentTool instanceof FillTool) {
currentTool.pointerMove({file, colorIndex: primaryColorIndex}, {x: mousePixelX, y: mousePixelY, id: 0 })
} else if (currentTool instanceof PickerTool) {

View File

@ -15,5 +15,19 @@ export function FilledCircle(x: number, y: number, radius: number, index: number
}
}
return pixels
}
export function FilledSquare(x: number, y: number, size: number, index: number): PixelPosition[] {
let pixels: PixelPosition[] = []
let center = Math.floor(size / 2)
for (let dx = 0; dx < size; dx++) {
for (let dy = 0; dy < size; dy++) {
pixels.push({x: x + dx - center, y: y + dy - center, index})
}
}
return pixels
}

View File

@ -1,5 +1,5 @@
import { PixelPlaceUndoable, type LoadedFile, PixelsPlaceUndoable } from "./file"
import { FilledCircle, type PixelPosition } from "./shapes"
import { FilledCircle, FilledSquare, type PixelPosition } from "./shapes"
export interface ToolContext {
file: LoadedFile
@ -11,6 +11,8 @@ interface Pointer {
id: number
}
export type BrushType = "circle" | "square"
export interface Tool {
isActive(): boolean
pointerDown(ctx: ToolContext, ptr: Pointer): void
@ -20,11 +22,13 @@ export interface Tool {
export interface BrushToolContext {
brushSize: number
brushType: BrushType
colorIndex: number
}
export interface EraserToolContext {
brushSize: number
brushType: BrushType
}
export interface FloodToolContext {
@ -59,7 +63,12 @@ export class BrushTool implements Tool {
}
}
} else {
let shape = FilledCircle(ptr.x, ptr.y, ctx.brushSize-2, ctx.colorIndex)
let shape: PixelPosition[]
if (ctx.brushType == "circle") {
shape = FilledCircle(ptr.x, ptr.y, ctx.brushSize-2, ctx.colorIndex)
} else if (ctx.brushType == "square") {
shape = FilledSquare(ptr.x, ptr.y, ctx.brushSize, ctx.colorIndex)
}
ctx.file.push(new PixelsPlaceUndoable(shape))
}
}
@ -79,7 +88,12 @@ export class BrushTool implements Tool {
}
}
} else {
let shape = FilledCircle(ptr.x, ptr.y, ctx.brushSize-2, ctx.colorIndex)
let shape: PixelPosition[]
if (ctx.brushType == "circle") {
shape = FilledCircle(ptr.x, ptr.y, ctx.brushSize-2, ctx.colorIndex)
} else if (ctx.brushType == "square") {
shape = FilledSquare(ptr.x, ptr.y, ctx.brushSize, ctx.colorIndex)
}
ctx.file.push(new PixelsPlaceUndoable(shape))
}
}