Implement square brush type; wheel on preview to change size
parent
acb305f437
commit
731c0c2062
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue