Extract magic numbers to constants

Move ruler config (colors, sizes, tick ratios, badge dimensions),
font sizes, pen handle radius, and outline alpha from renderer
inline values to src/constants.ts.
This commit is contained in:
Danila Poyarkov 2026-02-28 09:52:56 +03:00
parent 68dc0ee888
commit cb8bbcf289
2 changed files with 60 additions and 38 deletions

View file

@ -23,3 +23,22 @@ export const HANDLE_SIZE = 6
export const ROTATION_HANDLE_OFFSET = 20
export const SNAP_THRESHOLD = 5
export const DRAG_DEAD_ZONE = 4
export const RULER_SIZE = 20
export const RULER_BG_COLOR = { r: 0.14, g: 0.14, b: 0.14, a: 1 } satisfies Color
export const RULER_TICK_COLOR = { r: 0.4, g: 0.4, b: 0.4, a: 1 } satisfies Color
export const RULER_TEXT_COLOR = { r: 0.55, g: 0.55, b: 0.55, a: 1 } satisfies Color
export const RULER_BADGE_HEIGHT = 14
export const RULER_BADGE_PADDING = 3
export const RULER_BADGE_RADIUS = 2
export const RULER_BADGE_EXCLUSION = 30
export const RULER_TEXT_BASELINE = 0.65
export const RULER_MAJOR_TICK = 0.5
export const RULER_MINOR_TICK = 0.25
export const RULER_HIGHLIGHT_ALPHA = 0.3
export const PEN_HANDLE_RADIUS = 3
export const PARENT_OUTLINE_ALPHA = 0.5
export const DEFAULT_FONT_SIZE = 14
export const LABEL_FONT_SIZE = 11
export const SIZE_FONT_SIZE = 10

View file

@ -1,4 +1,10 @@
import { SELECTION_COLOR, SNAP_COLOR, CANVAS_BG_COLOR, HANDLE_SIZE, ROTATION_HANDLE_OFFSET } from '../constants'
import {
SELECTION_COLOR, SNAP_COLOR, CANVAS_BG_COLOR, HANDLE_SIZE, ROTATION_HANDLE_OFFSET,
RULER_SIZE, RULER_BG_COLOR, RULER_TICK_COLOR, RULER_TEXT_COLOR,
RULER_BADGE_HEIGHT, RULER_BADGE_PADDING, RULER_BADGE_RADIUS, RULER_BADGE_EXCLUSION,
RULER_TEXT_BASELINE, RULER_MAJOR_TICK, RULER_MINOR_TICK, RULER_HIGHLIGHT_ALPHA,
PEN_HANDLE_RADIUS, PARENT_OUTLINE_ALPHA, DEFAULT_FONT_SIZE, LABEL_FONT_SIZE, SIZE_FONT_SIZE
} from '../constants'
import type { SceneNode, SceneGraph, Fill } from './scene-graph'
import type { SnapGuide } from './snap'
import { vectorNetworkToPath } from './vector'
@ -82,7 +88,7 @@ export class SkiaRenderer {
this.parentOutlinePaint = new ck.Paint()
this.parentOutlinePaint.setStyle(ck.PaintStyle.Stroke)
this.parentOutlinePaint.setStrokeWidth(1)
this.parentOutlinePaint.setColor(this.selColor(0.5))
this.parentOutlinePaint.setColor(this.selColor(PARENT_OUTLINE_ALPHA))
this.parentOutlinePaint.setAntiAlias(true)
this.parentOutlinePaint.setPathEffect(ck.PathEffect.MakeDash([4, 4], 0))
@ -92,7 +98,7 @@ export class SkiaRenderer {
this.snapPaint.setColor(this.ck.Color4f(SNAP_COLOR.r, SNAP_COLOR.g, SNAP_COLOR.b, 1))
this.snapPaint.setAntiAlias(true)
this.textFont = new ck.Font(null, 14)
this.textFont = new ck.Font(null, DEFAULT_FONT_SIZE)
}
async loadFonts(): Promise<void> {
@ -108,9 +114,9 @@ export class SkiaRenderer {
this.textFont?.delete()
this.labelFont?.delete()
this.sizeFont?.delete()
this.textFont = new this.ck.Font(typeface, 14)
this.labelFont = new this.ck.Font(typeface, 11)
this.sizeFont = new this.ck.Font(typeface, 10)
this.textFont = new this.ck.Font(typeface, DEFAULT_FONT_SIZE)
this.labelFont = new this.ck.Font(typeface, LABEL_FONT_SIZE)
this.sizeFont = new this.ck.Font(typeface, SIZE_FONT_SIZE)
}
this.fontMgr = this.ck.FontMgr.FromData(fontData) ?? null
}
@ -234,7 +240,7 @@ export class SkiaRenderer {
this.drawHandle(canvas, x2, my)
// Rotation handle (line extending above + circle)
const rotHandleY = y1 - 24
const rotHandleY = y1 - ROTATION_HANDLE_OFFSET - 4
const rotLinePaint = new this.ck.Paint()
rotLinePaint.setStyle(this.ck.PaintStyle.Stroke)
rotLinePaint.setStrokeWidth(1)
@ -769,7 +775,7 @@ export class SkiaRenderer {
const handlePaint = new this.ck.Paint()
handlePaint.setStyle(this.ck.PaintStyle.Stroke)
handlePaint.setStrokeWidth(1)
handlePaint.setColor(this.selColor(0.5))
handlePaint.setColor(this.selColor(PARENT_OUTLINE_ALPHA))
handlePaint.setAntiAlias(true)
const vertexFill = new this.ck.Paint()
@ -840,15 +846,15 @@ export class SkiaRenderer {
const s = toScreen(vertices[seg.start].x, vertices[seg.start].y)
const cp = toScreen(vertices[seg.start].x + ts.x, vertices[seg.start].y + ts.y)
canvas.drawLine(s.x, s.y, cp.x, cp.y, handlePaint)
canvas.drawCircle(cp.x, cp.y, 3, vertexFill)
canvas.drawCircle(cp.x, cp.y, 3, handlePaint)
canvas.drawCircle(cp.x, cp.y, PEN_HANDLE_RADIUS, vertexFill)
canvas.drawCircle(cp.x, cp.y, PEN_HANDLE_RADIUS, handlePaint)
}
if (te.x !== 0 || te.y !== 0) {
const e = toScreen(vertices[seg.end].x, vertices[seg.end].y)
const cp = toScreen(vertices[seg.end].x + te.x, vertices[seg.end].y + te.y)
canvas.drawLine(e.x, e.y, cp.x, cp.y, handlePaint)
canvas.drawCircle(cp.x, cp.y, 3, vertexFill)
canvas.drawCircle(cp.x, cp.y, 3, handlePaint)
canvas.drawCircle(cp.x, cp.y, PEN_HANDLE_RADIUS, vertexFill)
canvas.drawCircle(cp.x, cp.y, PEN_HANDLE_RADIUS, handlePaint)
}
}
@ -858,10 +864,10 @@ export class SkiaRenderer {
const cp1 = toScreen(last.x + dragTangent.x, last.y + dragTangent.y)
const cp2 = toScreen(last.x - dragTangent.x, last.y - dragTangent.y)
canvas.drawLine(cp2.x, cp2.y, cp1.x, cp1.y, handlePaint)
canvas.drawCircle(cp1.x, cp1.y, 3, vertexFill)
canvas.drawCircle(cp1.x, cp1.y, 3, handlePaint)
canvas.drawCircle(cp2.x, cp2.y, 3, vertexFill)
canvas.drawCircle(cp2.x, cp2.y, 3, handlePaint)
canvas.drawCircle(cp1.x, cp1.y, PEN_HANDLE_RADIUS, vertexFill)
canvas.drawCircle(cp1.x, cp1.y, PEN_HANDLE_RADIUS, handlePaint)
canvas.drawCircle(cp2.x, cp2.y, PEN_HANDLE_RADIUS, vertexFill)
canvas.drawCircle(cp2.x, cp2.y, PEN_HANDLE_RADIUS, handlePaint)
}
// Draw vertices
@ -880,18 +886,15 @@ export class SkiaRenderer {
// --- Rulers ---
private static readonly RULER_SIZE = 20
private static readonly RULER_BG = { r: 0.14, g: 0.14, b: 0.14 }
private static readonly RULER_TICK = { r: 0.4, g: 0.4, b: 0.4 }
private static readonly RULER_TEXT = { r: 0.55, g: 0.55, b: 0.55 }
private drawRulers(canvas: Canvas, graph: SceneGraph, selectedIds: Set<string>): void {
const R = SkiaRenderer.RULER_SIZE
const R = RULER_SIZE
const vw = this.viewportWidth
const vh = this.viewportHeight
if (vw === 0 || vh === 0) return
const bg = SkiaRenderer.RULER_BG
const bg = RULER_BG_COLOR
const bgPaint = new this.ck.Paint()
bgPaint.setColor(this.ck.Color4f(bg.r, bg.g, bg.b, 1))
@ -902,11 +905,11 @@ export class SkiaRenderer {
canvas.drawRect(this.ck.LTRBRect(0, 0, R, R), bgPaint)
const tickPaint = new this.ck.Paint()
tickPaint.setColor(this.ck.Color4f(SkiaRenderer.RULER_TICK.r, SkiaRenderer.RULER_TICK.g, SkiaRenderer.RULER_TICK.b, 1))
tickPaint.setColor(this.ck.Color4f(RULER_TICK_COLOR.r, RULER_TICK_COLOR.g, RULER_TICK_COLOR.b, 1))
tickPaint.setStrokeWidth(1)
tickPaint.setAntiAlias(true)
const textColor = SkiaRenderer.RULER_TEXT
const textColor = RULER_TEXT_COLOR
const textPaint = new this.ck.Paint()
textPaint.setColor(this.ck.Color4f(textColor.r, textColor.g, textColor.b, 1))
textPaint.setAntiAlias(true)
@ -937,7 +940,7 @@ export class SkiaRenderer {
sy2 = maxY * this.zoom + this.panY
}
const badgeW = 30
const badgeW = RULER_BADGE_EXCLUSION
// Horizontal ruler (clipped)
canvas.save()
@ -950,16 +953,16 @@ export class SkiaRenderer {
const sx = wx * this.zoom + this.panX
if (sx < R) continue
const isMajor = Math.abs(wx % step) < 0.01
const tickLen = isMajor ? R * 0.5 : R * 0.25
const tickLen = isMajor ? R * RULER_MAJOR_TICK : R * RULER_MINOR_TICK
canvas.drawLine(sx, R - tickLen, sx, R, tickPaint)
if (isMajor && selNodes.length > 0) {
const tooClose = Math.abs(sx - sx1) < badgeW || Math.abs(sx - sx2) < badgeW
if (!tooClose) {
canvas.drawText(this.rulerLabel(wx), sx + 2, R * 0.65, textPaint, font)
canvas.drawText(this.rulerLabel(wx), sx + 2, R * RULER_TEXT_BASELINE, textPaint, font)
}
} else if (isMajor) {
canvas.drawText(this.rulerLabel(wx), sx + 2, R * 0.65, textPaint, font)
canvas.drawText(this.rulerLabel(wx), sx + 2, R * RULER_TEXT_BASELINE, textPaint, font)
}
}
canvas.restore()
@ -975,21 +978,21 @@ export class SkiaRenderer {
const sy = wy * this.zoom + this.panY
if (sy < R) continue
const isMajor = Math.abs(wy % step) < 0.01
const tickLen = isMajor ? R * 0.5 : R * 0.25
const tickLen = isMajor ? R * RULER_MAJOR_TICK : R * RULER_MINOR_TICK
canvas.drawLine(R - tickLen, sy, R, sy, tickPaint)
if (isMajor && selNodes.length > 0) {
const tooClose = Math.abs(sy - sy1) < badgeW || Math.abs(sy - sy2) < badgeW
if (!tooClose) {
canvas.save()
canvas.translate(R * 0.65, sy - 2)
canvas.translate(R * RULER_TEXT_BASELINE, sy - 2)
canvas.rotate(-90, 0, 0)
canvas.drawText(this.rulerLabel(wy), 0, 3, textPaint, font)
canvas.restore()
}
} else if (isMajor) {
canvas.save()
canvas.translate(R * 0.65, sy - 2)
canvas.translate(R * RULER_TEXT_BASELINE, sy - 2)
canvas.rotate(-90, 0, 0)
canvas.drawText(this.rulerLabel(wy), 0, 3, textPaint, font)
canvas.restore()
@ -1000,7 +1003,7 @@ export class SkiaRenderer {
// Selection highlight + badges
if (selNodes.length > 0) {
const hlPaint = new this.ck.Paint()
hlPaint.setColor(this.selColor(0.3))
hlPaint.setColor(this.selColor(RULER_HIGHLIGHT_ALPHA))
canvas.drawRect(this.ck.LTRBRect(Math.max(R, sx1), 0, sx2, R), hlPaint)
canvas.drawRect(this.ck.LTRBRect(0, Math.max(R, sy1), R, sy2), hlPaint)
@ -1019,12 +1022,12 @@ export class SkiaRenderer {
}
private drawRulerBadge(canvas: Canvas, font: InstanceType<CanvasKit['Font']>, label: string, x: number, y: number, axis: 'horizontal' | 'vertical'): void {
const R = SkiaRenderer.RULER_SIZE
const R = RULER_SIZE
const glyphIds = font.getGlyphIDs(label)
const widths = font.getGlyphWidths(glyphIds)
const textW = widths.reduce((s, w) => s + w, 0)
const pad = 3
const h = 14
const pad = RULER_BADGE_PADDING
const h = RULER_BADGE_HEIGHT
const badgePaint = new this.ck.Paint()
badgePaint.setColor(this.selColor())
@ -1035,8 +1038,8 @@ export class SkiaRenderer {
if (axis === 'horizontal') {
const bx = x - (textW + pad * 2) / 2
const by = (R - h) / 2
canvas.drawRRect(this.ck.RRectXY(this.ck.LTRBRect(bx, by, bx + textW + pad * 2, by + h), 2, 2), badgePaint)
canvas.drawText(label, bx + pad, R * 0.65, labelPaint, font)
canvas.drawRRect(this.ck.RRectXY(this.ck.LTRBRect(bx, by, bx + textW + pad * 2, by + h), RULER_BADGE_RADIUS, RULER_BADGE_RADIUS), badgePaint)
canvas.drawText(label, bx + pad, R * RULER_TEXT_BASELINE, labelPaint, font)
} else {
const bw = textW + pad * 2
const bx = (R - h) / 2
@ -1044,7 +1047,7 @@ export class SkiaRenderer {
canvas.save()
canvas.translate(bx + h / 2, by + bw / 2)
canvas.rotate(-90, 0, 0)
canvas.drawRRect(this.ck.RRectXY(this.ck.LTRBRect(-bw / 2, -h / 2, bw / 2, h / 2), 2, 2), badgePaint)
canvas.drawRRect(this.ck.RRectXY(this.ck.LTRBRect(-bw / 2, -h / 2, bw / 2, h / 2), RULER_BADGE_RADIUS, RULER_BADGE_RADIUS), badgePaint)
canvas.drawText(label, -bw / 2 + pad, h / 2 - 3, labelPaint, font)
canvas.restore()
}