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

View File

@ -1,9 +1,11 @@
<script lang='ts'> <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 brushSize: number
export let brushType: 'circle' | 'square' = 'circle'
$: ((brushSize: number) => { $: ((brushSize: number, brushType: 'circle' | 'square') => {
if (canvas) { if (canvas) {
let {width, height} = getComputedStyle(canvas) let {width, height} = getComputedStyle(canvas)
canvas.width = parseInt(width) canvas.width = parseInt(width)
@ -12,19 +14,35 @@
ctx.fillStyle = 'black' ctx.fillStyle = 'black'
ctx.fillRect(0, 0, canvas.width, canvas.height) 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++) { for (let i = 0; i < shape.length; i++) {
ctx.fillStyle = 'white' ctx.fillStyle = 'white'
ctx.fillRect(shape[i].x, shape[i].y, 1, 1) 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 let canvas: HTMLCanvasElement
</script> </script>
<canvas bind:this={canvas}> <div on:wheel={handleWheel}>
<OverflowMenu size="sm" style="width: auto">
<canvas slot="menu" bind:this={canvas}>
</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> <style>
canvas { canvas {

View File

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

View File

@ -17,3 +17,17 @@ export function FilledCircle(x: number, y: number, radius: number, index: number
return pixels 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 { PixelPlaceUndoable, type LoadedFile, PixelsPlaceUndoable } from "./file"
import { FilledCircle, type PixelPosition } from "./shapes" import { FilledCircle, FilledSquare, type PixelPosition } from "./shapes"
export interface ToolContext { export interface ToolContext {
file: LoadedFile file: LoadedFile
@ -11,6 +11,8 @@ interface Pointer {
id: number id: number
} }
export type BrushType = "circle" | "square"
export interface Tool { export interface Tool {
isActive(): boolean isActive(): boolean
pointerDown(ctx: ToolContext, ptr: Pointer): void pointerDown(ctx: ToolContext, ptr: Pointer): void
@ -20,11 +22,13 @@ export interface Tool {
export interface BrushToolContext { export interface BrushToolContext {
brushSize: number brushSize: number
brushType: BrushType
colorIndex: number colorIndex: number
} }
export interface EraserToolContext { export interface EraserToolContext {
brushSize: number brushSize: number
brushType: BrushType
} }
export interface FloodToolContext { export interface FloodToolContext {
@ -59,7 +63,12 @@ export class BrushTool implements Tool {
} }
} }
} else { } 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)) ctx.file.push(new PixelsPlaceUndoable(shape))
} }
} }
@ -79,7 +88,12 @@ export class BrushTool implements Tool {
} }
} }
} else { } 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)) ctx.file.push(new PixelsPlaceUndoable(shape))
} }
} }