Canonicalize shared toolbar mechanics
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
export * from "./accent.js";
|
||||
export * from "./floating.js";
|
||||
export * from "./theme.js";
|
||||
|
||||
export * from "./toolbar.js";
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
export type ToolbarPlacement = "left" | "right" | "bottom";
|
||||
|
||||
export interface ToolbarSettings {
|
||||
minSize: number;
|
||||
maxSize: number;
|
||||
lensCount: number;
|
||||
gap: number;
|
||||
}
|
||||
|
||||
export interface ToolbarTarget {
|
||||
size: number;
|
||||
shift: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
export const NODEDC_TOOLBAR_DEFAULTS = Object.freeze({
|
||||
background: "#111115",
|
||||
border: "#111117",
|
||||
outline: "#1c1c1c",
|
||||
minSize: 25,
|
||||
maxSize: 87,
|
||||
lensCount: 5,
|
||||
gap: 10,
|
||||
padding: 6,
|
||||
hideDelay: 5000,
|
||||
});
|
||||
|
||||
export const NODEDC_TOOLBAR_SPRING = Object.freeze({
|
||||
mass: 0.1,
|
||||
stiffness: 170,
|
||||
damping: 12,
|
||||
maxStep: 1 / 240,
|
||||
});
|
||||
|
||||
const BASE_SCALE = 2.25;
|
||||
const BASE_DISTANCE = 110;
|
||||
const BASE_NUDGE = 40;
|
||||
const NUDGE_TIGHTNESS = 0.82;
|
||||
|
||||
export function normalizeToolbarSettings({
|
||||
minSize = NODEDC_TOOLBAR_DEFAULTS.minSize,
|
||||
maxSize = NODEDC_TOOLBAR_DEFAULTS.maxSize,
|
||||
lensCount = NODEDC_TOOLBAR_DEFAULTS.lensCount,
|
||||
gap = NODEDC_TOOLBAR_DEFAULTS.gap,
|
||||
}: Partial<ToolbarSettings> = {}): ToolbarSettings {
|
||||
const safeMin = Math.max(18, Math.min(42, Number.isFinite(minSize) ? minSize : NODEDC_TOOLBAR_DEFAULTS.minSize));
|
||||
const safeMax = Math.max(safeMin, Math.min(88, Number.isFinite(maxSize) ? maxSize : NODEDC_TOOLBAR_DEFAULTS.maxSize));
|
||||
const roundedLens = Math.max(1, Math.min(13, Math.round(Number.isFinite(lensCount) ? lensCount : NODEDC_TOOLBAR_DEFAULTS.lensCount)));
|
||||
return {
|
||||
minSize: safeMin,
|
||||
maxSize: safeMax,
|
||||
lensCount: roundedLens % 2 === 0 ? Math.min(13, roundedLens + 1) : roundedLens,
|
||||
gap: Math.max(0, Number.isFinite(gap) ? gap : NODEDC_TOOLBAR_DEFAULTS.gap),
|
||||
};
|
||||
}
|
||||
|
||||
export function stepToolbarSpring(value: number, velocity: number, target: number, dt: number) {
|
||||
let nextValue = value;
|
||||
let nextVelocity = velocity;
|
||||
const duration = Math.min(0.032, Math.max(0.001, dt));
|
||||
const steps = Math.max(1, Math.ceil(duration / NODEDC_TOOLBAR_SPRING.maxStep));
|
||||
const step = duration / steps;
|
||||
|
||||
for (let index = 0; index < steps; index += 1) {
|
||||
const displacement = nextValue - target;
|
||||
const springForce = -NODEDC_TOOLBAR_SPRING.stiffness * displacement;
|
||||
const dampingForce = -NODEDC_TOOLBAR_SPRING.damping * nextVelocity;
|
||||
const acceleration = (springForce + dampingForce) / NODEDC_TOOLBAR_SPRING.mass;
|
||||
nextVelocity += acceleration * step;
|
||||
nextValue += nextVelocity * step;
|
||||
}
|
||||
|
||||
return [nextValue, nextVelocity] as const;
|
||||
}
|
||||
|
||||
export function calculateToolbarTargets(
|
||||
centers: readonly number[],
|
||||
pointerCoordinate: number,
|
||||
settings: ToolbarSettings,
|
||||
): ToolbarTarget[] {
|
||||
const step = settings.minSize + settings.gap;
|
||||
const lensRadius = Math.max(
|
||||
settings.minSize,
|
||||
Math.floor((settings.lensCount - 1) / 2) * step + settings.gap / 2,
|
||||
);
|
||||
const maxScale = settings.minSize > 0 ? settings.maxSize / settings.minSize : 1;
|
||||
const scaledNudge = BASE_NUDGE
|
||||
* (lensRadius / BASE_DISTANCE)
|
||||
* (Math.max(0, maxScale - 1) / (BASE_SCALE - 1))
|
||||
* NUDGE_TIGHTNESS;
|
||||
|
||||
return centers.map((center) => {
|
||||
const distance = pointerCoordinate - center;
|
||||
const absoluteDistance = Math.abs(distance);
|
||||
const scale = absoluteDistance >= lensRadius
|
||||
? 1
|
||||
: 1 + (maxScale - 1) * (1 - absoluteDistance / lensRadius);
|
||||
return {
|
||||
scale,
|
||||
size: settings.minSize * scale,
|
||||
shift: absoluteDistance > lensRadius
|
||||
? Math.sign(distance || 1) * -scaledNudge
|
||||
: (-distance / lensRadius) * scaledNudge * scale,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user