feat(ui): better color picker

This commit is contained in:
psychedelicious 2024-08-24 10:10:04 +10:00
parent c013c55d92
commit cd3da886d6

View File

@ -2,11 +2,7 @@ import type { SerializableObject } from 'common/types';
import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule'; import type { CanvasPreviewModule } from 'features/controlLayers/konva/CanvasPreviewModule';
import { import { BRUSH_BORDER_INNER_COLOR, BRUSH_BORDER_OUTER_COLOR } from 'features/controlLayers/konva/constants';
BRUSH_BORDER_INNER_COLOR,
BRUSH_BORDER_OUTER_COLOR,
BRUSH_ERASER_BORDER_WIDTH,
} from 'features/controlLayers/konva/constants';
import { alignCoordForTool, getPrefixedId } from 'features/controlLayers/konva/util'; import { alignCoordForTool, getPrefixedId } from 'features/controlLayers/konva/util';
import type { Tool } from 'features/controlLayers/store/types'; import type { Tool } from 'features/controlLayers/store/types';
import { isDrawableEntity } from 'features/controlLayers/store/types'; import { isDrawableEntity } from 'features/controlLayers/store/types';
@ -15,6 +11,8 @@ import type { Logger } from 'roarr';
export class CanvasToolModule { export class CanvasToolModule {
readonly type = 'tool_preview'; readonly type = 'tool_preview';
static readonly COLOR_PICKER_RADIUS = 25;
static readonly COLOR_PICKER_THICKNESS = 15;
id: string; id: string;
path: string[]; path: string[];
@ -27,19 +25,21 @@ export class CanvasToolModule {
brush: { brush: {
group: Konva.Group; group: Konva.Group;
fillCircle: Konva.Circle; fillCircle: Konva.Circle;
innerBorderCircle: Konva.Circle; innerBorder: Konva.Ring;
outerBorderCircle: Konva.Circle; outerBorder: Konva.Ring;
}; };
eraser: { eraser: {
group: Konva.Group; group: Konva.Group;
fillCircle: Konva.Circle; fillCircle: Konva.Circle;
innerBorderCircle: Konva.Circle; innerBorder: Konva.Ring;
outerBorderCircle: Konva.Circle; outerBorder: Konva.Ring;
}; };
colorPicker: { colorPicker: {
group: Konva.Group; group: Konva.Group;
fillCircle: Konva.Circle; newColor: Konva.Ring;
transparentCenterCircle: Konva.Circle; oldColor: Konva.Arc;
innerBorder: Konva.Ring;
outerBorder: Konva.Ring;
}; };
}; };
@ -63,19 +63,21 @@ export class CanvasToolModule {
listening: false, listening: false,
strokeEnabled: false, strokeEnabled: false,
}), }),
innerBorderCircle: new Konva.Circle({ innerBorder: new Konva.Ring({
name: `${this.type}:brush_inner_border_circle`, name: `${this.type}:brush_inner_border_ring`,
listening: false, listening: false,
stroke: BRUSH_BORDER_INNER_COLOR, innerRadius: 0,
strokeWidth: BRUSH_ERASER_BORDER_WIDTH, outerRadius: 0,
strokeEnabled: true, fill: BRUSH_BORDER_INNER_COLOR,
strokeEnabled: false,
}), }),
outerBorderCircle: new Konva.Circle({ outerBorder: new Konva.Ring({
name: `${this.type}:brush_outer_border_circle`, name: `${this.type}:brush_outer_border_ring`,
listening: false, listening: false,
stroke: BRUSH_BORDER_OUTER_COLOR, innerRadius: 0,
strokeWidth: BRUSH_ERASER_BORDER_WIDTH, outerRadius: 0,
strokeEnabled: true, fill: BRUSH_BORDER_OUTER_COLOR,
strokeEnabled: false,
}), }),
}, },
eraser: { eraser: {
@ -87,54 +89,70 @@ export class CanvasToolModule {
fill: 'white', fill: 'white',
globalCompositeOperation: 'destination-out', globalCompositeOperation: 'destination-out',
}), }),
innerBorderCircle: new Konva.Circle({ innerBorder: new Konva.Ring({
name: `${this.type}:eraser_inner_border_circle`, name: `${this.type}:eraser_inner_border_ring`,
listening: false, listening: false,
stroke: BRUSH_BORDER_INNER_COLOR, innerRadius: 0,
strokeWidth: BRUSH_ERASER_BORDER_WIDTH, outerRadius: 0,
strokeEnabled: true, fill: BRUSH_BORDER_INNER_COLOR,
strokeEnabled: false,
}), }),
outerBorderCircle: new Konva.Circle({ outerBorder: new Konva.Ring({
name: `${this.type}:eraser_outer_border_circle`, name: `${this.type}:eraser_outer_border_ring`,
listening: false, innerRadius: 0,
stroke: BRUSH_BORDER_OUTER_COLOR, outerRadius: 0,
strokeWidth: BRUSH_ERASER_BORDER_WIDTH, fill: BRUSH_BORDER_OUTER_COLOR,
strokeEnabled: true, strokeEnabled: false,
}), }),
}, },
colorPicker: { colorPicker: {
group: new Konva.Group({ name: `${this.type}:color_picker_group`, listening: false }), group: new Konva.Group({ name: `${this.type}:color_picker_group`, listening: false }),
fillCircle: new Konva.Circle({ newColor: new Konva.Ring({
name: `${this.type}:color_picker_fill_circle`, name: `${this.type}:color_picker_new_color_ring`,
listening: false, innerRadius: 0,
fill: '', outerRadius: 0,
radius: 20, strokeEnabled: false,
strokeWidth: 1, }),
stroke: 'black', oldColor: new Konva.Arc({
strokeScaleEnabled: false, name: `${this.type}:color_picker_old_color_arc`,
}), innerRadius: 0,
transparentCenterCircle: new Konva.Circle({ outerRadius: 0,
name: `${this.type}:color_picker_fill_circle`, angle: 180,
listening: false, strokeEnabled: false,
}),
innerBorder: new Konva.Ring({
name: `${this.type}:color_picker_inner_border_ring`,
listening: false,
innerRadius: 0,
outerRadius: 0,
fill: BRUSH_BORDER_INNER_COLOR,
strokeEnabled: false,
}),
outerBorder: new Konva.Ring({
name: `${this.type}:color_picker_outer_border_ring`,
innerRadius: 0,
outerRadius: 0,
fill: BRUSH_BORDER_OUTER_COLOR,
strokeEnabled: false, strokeEnabled: false,
fill: 'white',
radius: 5,
globalCompositeOperation: 'destination-out',
}), }),
}, },
}; };
this.konva.brush.group.add(this.konva.brush.fillCircle); this.konva.brush.group.add(this.konva.brush.fillCircle, this.konva.brush.innerBorder, this.konva.brush.outerBorder);
this.konva.brush.group.add(this.konva.brush.innerBorderCircle);
this.konva.brush.group.add(this.konva.brush.outerBorderCircle);
this.konva.group.add(this.konva.brush.group); this.konva.group.add(this.konva.brush.group);
this.konva.eraser.group.add(this.konva.eraser.fillCircle); this.konva.eraser.group.add(
this.konva.eraser.group.add(this.konva.eraser.innerBorderCircle); this.konva.eraser.fillCircle,
this.konva.eraser.group.add(this.konva.eraser.outerBorderCircle); this.konva.eraser.innerBorder,
this.konva.eraser.outerBorder
);
this.konva.group.add(this.konva.eraser.group); this.konva.group.add(this.konva.eraser.group);
this.konva.colorPicker.group.add(this.konva.colorPicker.fillCircle); this.konva.colorPicker.group.add(
this.konva.colorPicker.group.add(this.konva.colorPicker.transparentCenterCircle); this.konva.colorPicker.newColor,
this.konva.colorPicker.oldColor,
this.konva.colorPicker.innerBorder,
this.konva.colorPicker.outerBorder
);
this.konva.group.add(this.konva.colorPicker.group); this.konva.group.add(this.konva.colorPicker.group);
this.subscriptions.add( this.subscriptions.add(
@ -159,21 +177,38 @@ export class CanvasToolModule {
scaleTool = () => { scaleTool = () => {
const toolState = this.manager.stateApi.getToolState(); const toolState = this.manager.stateApi.getToolState();
const scale = this.manager.stage.getScale(); const onePixel = this.manager.stage.getScaledPixels(1);
const twoPixels = this.manager.stage.getScaledPixels(2);
const brushRadius = toolState.brush.width / 2; const brushRadius = toolState.brush.width / 2;
this.konva.brush.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale); this.konva.brush.innerBorder.innerRadius(brushRadius);
this.konva.brush.outerBorderCircle.setAttrs({ this.konva.brush.innerBorder.outerRadius(brushRadius + onePixel);
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
radius: brushRadius + BRUSH_ERASER_BORDER_WIDTH / scale, this.konva.brush.outerBorder.innerRadius(brushRadius + onePixel);
}); this.konva.brush.outerBorder.outerRadius(brushRadius + twoPixels);
const eraserRadius = toolState.eraser.width / 2; const eraserRadius = toolState.eraser.width / 2;
this.konva.eraser.innerBorderCircle.strokeWidth(BRUSH_ERASER_BORDER_WIDTH / scale); this.konva.eraser.innerBorder.innerRadius(eraserRadius);
this.konva.eraser.outerBorderCircle.setAttrs({ this.konva.eraser.innerBorder.outerRadius(eraserRadius + onePixel);
strokeWidth: BRUSH_ERASER_BORDER_WIDTH / scale,
radius: eraserRadius + BRUSH_ERASER_BORDER_WIDTH / scale, this.konva.eraser.outerBorder.innerRadius(eraserRadius + onePixel);
}); this.konva.eraser.outerBorder.outerRadius(eraserRadius + twoPixels);
const colorPickerInnerRadius = this.manager.stage.getScaledPixels(CanvasToolModule.COLOR_PICKER_RADIUS);
const colorPickerOuterRadius = this.manager.stage.getScaledPixels(
CanvasToolModule.COLOR_PICKER_RADIUS + CanvasToolModule.COLOR_PICKER_THICKNESS
);
this.konva.colorPicker.oldColor.innerRadius(colorPickerInnerRadius);
this.konva.colorPicker.oldColor.outerRadius(colorPickerOuterRadius);
this.konva.colorPicker.newColor.innerRadius(colorPickerInnerRadius);
this.konva.colorPicker.newColor.outerRadius(colorPickerOuterRadius);
this.konva.colorPicker.innerBorder.innerRadius(colorPickerOuterRadius);
this.konva.colorPicker.innerBorder.outerRadius(colorPickerOuterRadius + onePixel);
this.konva.colorPicker.outerBorder.innerRadius(colorPickerOuterRadius + onePixel);
this.konva.colorPicker.outerBorder.outerRadius(colorPickerOuterRadius + twoPixels);
}; };
setToolVisibility = (tool: Tool) => { setToolVisibility = (tool: Tool) => {
@ -238,7 +273,7 @@ export class CanvasToolModule {
if (cursorPos && tool === 'brush') { if (cursorPos && tool === 'brush') {
const brushPreviewFill = this.manager.stateApi.getBrushPreviewFill(); const brushPreviewFill = this.manager.stateApi.getBrushPreviewFill();
const alignedCursorPos = alignCoordForTool(cursorPos, toolState.brush.width); const alignedCursorPos = alignCoordForTool(cursorPos, toolState.brush.width);
const scale = stage.getScale();
// Update the fill circle // Update the fill circle
const radius = toolState.brush.width / 2; const radius = toolState.brush.width / 2;
@ -248,20 +283,10 @@ export class CanvasToolModule {
radius, radius,
fill: isDrawing ? '' : rgbaColorToString(brushPreviewFill), fill: isDrawing ? '' : rgbaColorToString(brushPreviewFill),
}); });
this.konva.brush.innerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y });
// Update the inner border of the brush preview this.konva.brush.outerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y });
this.konva.brush.innerBorderCircle.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
// Update the outer border of the brush preview
this.konva.brush.outerBorderCircle.setAttrs({
x: cursorPos.x,
y: cursorPos.y,
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
});
} else if (cursorPos && tool === 'eraser') { } else if (cursorPos && tool === 'eraser') {
const alignedCursorPos = alignCoordForTool(cursorPos, toolState.eraser.width); const alignedCursorPos = alignCoordForTool(cursorPos, toolState.eraser.width);
const scale = stage.getScale();
// Update the fill circle // Update the fill circle
const radius = toolState.eraser.width / 2; const radius = toolState.eraser.width / 2;
this.konva.eraser.fillCircle.setAttrs({ this.konva.eraser.fillCircle.setAttrs({
@ -270,26 +295,21 @@ export class CanvasToolModule {
radius, radius,
fill: 'white', fill: 'white',
}); });
this.konva.eraser.innerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y });
// Update the inner border of the eraser preview this.konva.eraser.outerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y });
this.konva.eraser.innerBorderCircle.setAttrs({ x: cursorPos.x, y: cursorPos.y, radius });
// Update the outer border of the eraser preview
this.konva.eraser.outerBorderCircle.setAttrs({
x: cursorPos.x,
y: cursorPos.y,
radius: radius + BRUSH_ERASER_BORDER_WIDTH / scale,
});
} else if (cursorPos && colorUnderCursor) { } else if (cursorPos && colorUnderCursor) {
this.konva.colorPicker.fillCircle.setAttrs({ this.konva.colorPicker.newColor.setAttrs({
x: cursorPos.x, x: cursorPos.x,
y: cursorPos.y, y: cursorPos.y,
fill: rgbaColorToString(colorUnderCursor), fill: rgbaColorToString(colorUnderCursor),
}); });
this.konva.colorPicker.transparentCenterCircle.setAttrs({ this.konva.colorPicker.oldColor.setAttrs({
x: cursorPos.x, x: cursorPos.x,
y: cursorPos.y, y: cursorPos.y,
fill: rgbaColorToString(toolState.fill),
}); });
this.konva.colorPicker.innerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y });
this.konva.colorPicker.outerBorder.setAttrs({ x: cursorPos.x, y: cursorPos.y });
} }
this.scaleTool(); this.scaleTool();