mock: スイッチ・スタビの十字をトグルで出し、スタビ候補を選べるようにする(T-30 単位8)
スタビ候補はキーの長辺の長さで引き、キーごとに複数選べる。選択は ProjectFile.ui.phy.stabilizers に持つ。 プロパティ欄に switchOffset の入力欄を置く。異形キーは候補なし。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017yoUu9Xd8dhwyQdCfKfaXG
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import type { Point, Size } from "./phy";
|
||||
|
||||
/** One candidate stabilizer layout: two mounts at ±offset along the key's long axis, in U. */
|
||||
export type StabilizerCandidate = { id: string; label: string; offset: number };
|
||||
|
||||
const MM = 1 / 19.05;
|
||||
/** Cherry MX spacings by key length. provisional/decisions.md「単位8のスタビ候補」. */
|
||||
const TABLE: { lengths: number[]; candidates: StabilizerCandidate[] }[] = [
|
||||
{
|
||||
lengths: [2, 2.25, 2.5, 2.75],
|
||||
candidates: [{ id: "11.938mm", label: "±11.938mm", offset: 11.938 * MM }],
|
||||
},
|
||||
{ lengths: [3], candidates: [{ id: "1u", label: "±1U", offset: 1 }] },
|
||||
{ lengths: [4], candidates: [{ id: "1.5u", label: "±1.5U", offset: 1.5 }] },
|
||||
{ lengths: [4.5], candidates: [{ id: "34.671mm", label: "±34.671mm", offset: 34.671 * MM }] },
|
||||
{ lengths: [5.5], candidates: [{ id: "2.25u", label: "±2.25U", offset: 2.25 }] },
|
||||
{
|
||||
lengths: [6],
|
||||
candidates: [
|
||||
{ id: "2.5u", label: "±2.5U", offset: 2.5 },
|
||||
{ id: "2u", label: "±2U", offset: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
lengths: [6.25],
|
||||
candidates: [
|
||||
{ id: "50mm", label: "±50mm", offset: 50 * MM },
|
||||
{ id: "40mm", label: "±40mm", offset: 40 * MM },
|
||||
{ id: "42.5mm", label: "±42.5mm", offset: 42.5 * MM },
|
||||
],
|
||||
},
|
||||
{ lengths: [6.5], candidates: [{ id: "2.75u", label: "±2.75U", offset: 2.75 }] },
|
||||
{ lengths: [7], candidates: [{ id: "3u", label: "±3U", offset: 3 }] },
|
||||
{ lengths: [8, 9, 9.75, 10], candidates: [{ id: "3.5u", label: "±3.5U", offset: 3.5 }] },
|
||||
];
|
||||
|
||||
const EPS = 1e-9;
|
||||
/** Non-rectangular keys (ISO Enter, BAE, stepped) have no candidates yet. */
|
||||
function irregular(size: Size): boolean {
|
||||
return [size.x2, size.y2, size.w2, size.h2].some((v) => v !== undefined);
|
||||
}
|
||||
export function stabilizerCandidates(size: Size): StabilizerCandidate[] {
|
||||
if (irregular(size)) return [];
|
||||
const length = Math.max(size.w, size.h);
|
||||
return TABLE.find((row) => row.lengths.some((l) => Math.abs(l - length) < EPS))?.candidates ?? [];
|
||||
}
|
||||
/** A size with a single candidate shows it until chosen otherwise; several start unselected. */
|
||||
export function defaultStabilizerSelection(size: Size): string[] {
|
||||
const candidates = stabilizerCandidates(size);
|
||||
return candidates.length === 1 ? [candidates[0]!.id] : [];
|
||||
}
|
||||
/** Mount points in key-local coordinates, around the (w, h) rectangle's center, not the switch. */
|
||||
export function stabilizerPoints(size: Size, ids: string[]): Point[] {
|
||||
const cx = size.w / 2,
|
||||
cy = size.h / 2,
|
||||
vertical = size.h > size.w;
|
||||
return stabilizerCandidates(size)
|
||||
.filter((c) => ids.includes(c.id))
|
||||
.flatMap((c) =>
|
||||
vertical
|
||||
? [
|
||||
{ x: cx, y: cy - c.offset },
|
||||
{ x: cx, y: cy + c.offset },
|
||||
]
|
||||
: [
|
||||
{ x: cx - c.offset, y: cy },
|
||||
{ x: cx + c.offset, y: cy },
|
||||
],
|
||||
);
|
||||
}
|
||||
export function switchPoint(key: { size: Size; switchOffset?: Point }): Point {
|
||||
return { x: key.size.w / 2 + (key.switchOffset?.x ?? 0), y: key.size.h / 2 + (key.switchOffset?.y ?? 0) };
|
||||
}
|
||||
Reference in New Issue
Block a user