fix(ui): floor all pixel coords

This prevents rendering objects with sub-pixel positioning, which looks soft
This commit is contained in:
psychedelicious 2024-04-20 16:46:54 +10:00 committed by Kent Keirsey
parent 944690ac8e
commit e4024bdeb9
2 changed files with 38 additions and 10 deletions

View File

@ -1,6 +1,5 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
import { import {
$cursorPosition, $cursorPosition,
$isMouseDown, $isMouseDown,
@ -17,8 +16,25 @@ const getIsFocused = (stage: Konva.Stage) => {
return stage.container().contains(document.activeElement); return stage.container().contains(document.activeElement);
}; };
export const getScaledFlooredCursorPosition = (stage: Konva.Stage) => {
const pointerPosition = stage.getPointerPosition();
const stageTransform = stage.getAbsoluteTransform().copy();
if (!pointerPosition || !stageTransform) {
return;
}
const scaledCursorPosition = stageTransform.invert().point(pointerPosition);
return {
x: Math.floor(scaledCursorPosition.x),
y: Math.floor(scaledCursorPosition.y),
};
};
const syncCursorPos = (stage: Konva.Stage) => { const syncCursorPos = (stage: Konva.Stage) => {
const pos = getScaledCursorPosition(stage); const pos = getScaledFlooredCursorPosition(stage);
if (!pos) { if (!pos) {
return null; return null;
} }
@ -47,7 +63,13 @@ export const useMouseEvents = () => {
} }
// const tool = getTool(); // const tool = getTool();
if (tool === 'brush' || tool === 'eraser') { if (tool === 'brush' || tool === 'eraser') {
dispatch(rpLayerLineAdded({ layerId: selectedLayerId, points: [pos.x, pos.y, pos.x, pos.y], tool })); dispatch(
rpLayerLineAdded({
layerId: selectedLayerId,
points: [Math.floor(pos.x), Math.floor(pos.y), Math.floor(pos.x), Math.floor(pos.y)],
tool,
})
);
} }
}, },
[dispatch, selectedLayerId, tool] [dispatch, selectedLayerId, tool]
@ -79,7 +101,7 @@ export const useMouseEvents = () => {
} }
// const tool = getTool(); // const tool = getTool();
if (getIsFocused(stage) && $isMouseOver.get() && $isMouseDown.get() && (tool === 'brush' || tool === 'eraser')) { if (getIsFocused(stage) && $isMouseOver.get() && $isMouseDown.get() && (tool === 'brush' || tool === 'eraser')) {
dispatch(rpLayerPointsAdded({ layerId: selectedLayerId, point: [pos.x, pos.y] })); dispatch(rpLayerPointsAdded({ layerId: selectedLayerId, point: [Math.floor(pos.x), Math.floor(pos.y)] }));
} }
}, },
[dispatch, selectedLayerId, tool] [dispatch, selectedLayerId, tool]
@ -117,7 +139,13 @@ export const useMouseEvents = () => {
return; return;
} }
if (tool === 'brush' || tool === 'eraser') { if (tool === 'brush' || tool === 'eraser') {
dispatch(rpLayerLineAdded({ layerId: selectedLayerId, points: [pos.x, pos.y, pos.x, pos.y], tool })); dispatch(
rpLayerLineAdded({
layerId: selectedLayerId,
points: [Math.floor(pos.x), Math.floor(pos.y), Math.floor(pos.x), Math.floor(pos.y)],
tool,
})
);
} }
} }
}, },

View File

@ -1,6 +1,6 @@
import { getStore } from 'app/store/nanostores/store'; import { getStore } from 'app/store/nanostores/store';
import { rgbColorToString } from 'features/canvas/util/colorToString'; import { rgbColorToString } from 'features/canvas/util/colorToString';
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition'; import { getScaledFlooredCursorPosition } from 'features/regionalPrompts/hooks/mouseEventHooks';
import type { Layer, RegionalPromptLayer, RPTool } from 'features/regionalPrompts/store/regionalPromptsSlice'; import type { Layer, RegionalPromptLayer, RPTool } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { import {
$isMouseOver, $isMouseOver,
@ -168,13 +168,13 @@ const renderRPLayer = (
// Create a `dragmove` listener for this layer // Create a `dragmove` listener for this layer
if (onLayerPosChanged) { if (onLayerPosChanged) {
konvaLayer.on('dragend', function (e) { konvaLayer.on('dragend', function (e) {
onLayerPosChanged(rpLayer.id, e.target.x(), e.target.y()); onLayerPosChanged(rpLayer.id, Math.floor(e.target.x()), Math.floor(e.target.y()));
}); });
} }
// The dragBoundFunc limits how far the layer can be dragged // The dragBoundFunc limits how far the layer can be dragged
konvaLayer.dragBoundFunc(function (pos) { konvaLayer.dragBoundFunc(function (pos) {
const cursorPos = getScaledCursorPosition(stage); const cursorPos = getScaledFlooredCursorPosition(stage);
if (!cursorPos) { if (!cursorPos) {
return this.getAbsolutePosition(); return this.getAbsolutePosition();
} }
@ -207,8 +207,8 @@ const renderRPLayer = (
// Update the layer's position and listening state // Update the layer's position and listening state
konvaLayer.setAttrs({ konvaLayer.setAttrs({
listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events listening: tool === 'move', // The layer only listens when using the move tool - otherwise the stage is handling mouse events
x: rpLayer.x, x: Math.floor(rpLayer.x),
y: rpLayer.y, y: Math.floor(rpLayer.y),
// There are rpLayers.length layers, plus a brush preview layer rendered on top of them, so the zIndex works // There are rpLayers.length layers, plus a brush preview layer rendered on top of them, so the zIndex works
// out to be the layerIndex. If more layers are added, this may no longer be true. // out to be the layerIndex. If more layers are added, this may no longer be true.
zIndex: rpLayerIndex, zIndex: rpLayerIndex,