1
Fork 0

Vizualise chords

This commit is contained in:
prescientmoon 2024-02-21 08:32:04 +01:00
parent 9b089af081
commit 39f0ec8f53
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
12 changed files with 1977 additions and 766 deletions

View file

@ -0,0 +1,7 @@
# My laptop layout!
![layout preview](./lens.svg)
## Layout philosophy
This is pretty much a port of my [ferris sweep layout](../../qmk/ferris-sweep).

View file

@ -1,5 +1,11 @@
{ {
"layout": "alpha_staggered_double_switch", "layout": "alpha_staggered_double_switch",
"measurements": {
"keySize": 60,
"keyPadding": 2,
"keyCornerRadius": 5,
"keyStrokeWidth": 1.5
},
"keys": [ "keys": [
["Q", "!", "1", "f1"], ["Q", "!", "1", "f1"],
["A", "<", "6", "f6"], ["A", "<", "6", "f6"],
@ -34,5 +40,88 @@
[":", "`", "🗑️", "🔆"], [":", "`", "🗑️", "🔆"],
["O", ";", "", "🔅"], ["O", ";", "", "🔅"],
["'", "\"", ""] ["'", "\"", ""]
],
"chords": [
[
{
"input": ["Q", "W"],
"output": "⎋",
"fill": "#9ccaff"
},
{
"input": ["A", "R"],
"output": "⌥",
"fill": "#39f785"
},
{
"input": ["S", "T"],
"output": "⭾",
"fill": "#fdff80",
"fontSizeModifier": 0.8
},
{
"input": ["G", "M"],
"output": "⌫",
"fill": "#f9adff"
},
{
"input": ["N", "E"],
"output": "⊥",
"fill": "#f58e8e"
},
{
"input": ["I", "O"],
"output": "⌥",
"fill": "#39f785"
}
],
[
{
"input": ["R", "S"],
"output": "⇧",
"fill": "#f9adff"
},
{
"input": ["E", "I"],
"output": "⇧",
"fill": "#f9adff"
}
],
[
{
"input": ["R", "T"],
"output": "ctrl",
"fill": "#fdff80"
},
{
"input": ["N", "I"],
"output": "ctrl",
"fill": "#fdff80"
}
],
[
{
"input": ["C", "P"],
"output": "📋",
"fill": "#fdff80"
},
{
"input": ["K", "I"],
"output": "❖",
"fill": "#f58e8e"
}
],
[
{
"input": ["F", "T"],
"output": "↵",
"fill": "#9ccaff"
},
{
"input": ["N", "U"],
"output": "💾",
"fill": "#9ccaff"
}
]
] ]
} }

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View file

@ -1,5 +1,11 @@
{ {
"layout": "split_3x5_2", "layout": "split_3x5_2",
"measurements": {
"keySize": 60,
"keyPadding": 2,
"keyCornerRadius": 5,
"keyStrokeWidth": 1.5
},
"keys": [ "keys": [
["Q", "!", "1", "f1"], ["Q", "!", "1", "f1"],
["A", "<", "6", "f6"], ["A", "<", "6", "f6"],
@ -35,5 +41,88 @@
[":", "`", "🗑️", "🔆"], [":", "`", "🗑️", "🔆"],
["O", ";", "", "🔅"], ["O", ";", "", "🔅"],
["'", "\"", ""] ["'", "\"", ""]
],
"chords": [
[
{
"input": ["Q", "W"],
"output": "⎋",
"fill": "#9ccaff"
},
{
"input": ["A", "R"],
"output": "⌥",
"fill": "#39f785"
},
{
"input": ["S", "T"],
"output": "⭾",
"fill": "#fdff80",
"fontSizeModifier": 0.8
},
{
"input": ["G", "M"],
"output": "⌫",
"fill": "#f9adff"
},
{
"input": ["N", "E"],
"output": "⊥",
"fill": "#f58e8e"
},
{
"input": ["I", "O"],
"output": "⌥",
"fill": "#39f785"
}
],
[
{
"input": ["R", "S"],
"output": "⇧",
"fill": "#f9adff"
},
{
"input": ["E", "I"],
"output": "⇧",
"fill": "#f9adff"
}
],
[
{
"input": ["R", "T"],
"output": "ctrl",
"fill": "#fdff80"
},
{
"input": ["N", "I"],
"output": "ctrl",
"fill": "#fdff80"
}
],
[
{
"input": ["C", "P"],
"output": "📋",
"fill": "#fdff80"
},
{
"input": ["K", "I"],
"output": "❖",
"fill": "#f58e8e"
}
],
[
{
"input": ["F", "T"],
"output": "↵",
"fill": "#9ccaff"
},
{
"input": ["N", "U"],
"output": "💾",
"fill": "#9ccaff"
}
]
] ]
} }

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View file

@ -1,13 +1,15 @@
# Layout-lens # Layout-lens
> NOTE: this project cannot yet render combos, which will change soon
This is a quickly-thrown-together set of scripts for generating SVG previews of keyboard layouts. For example configurations check out any config in the `keyboards` directory of this repository. To run this on your config simply do This is a quickly-thrown-together set of scripts for generating SVG previews of keyboard layouts. For example configurations check out any config in the `keyboards` directory of this repository. To run this on your config simply do
```sh ```sh
nix run github:mateiadrielrafael/keyswirl#layout-lens my-config.json out.svg nix run github:mateiadrielrafael/keyswirl#layout-lens my-config.json out.svg
``` ```
## Future improvements
This project does not render chords involving rotated keys properly. Moreover, chord definitions in general can be a little verboose.
## Technical details ## Technical details
The code isn't very well written (i.e.: no error handling, only contains the features I needed myself, etc). I'd rewrite this in a better language given the motivation, but the current version does the job just fine. If you want to contribute a layout preset, add it to [./src/layouts](./src/layouts) and then modify the enum in [./src/types.ts](./src/types.ts) to know about it's existence. The code isn't very well written (i.e.: no error handling, only contains the features I needed myself, etc). I'd rewrite this in a better language given the motivation, but the current version does the job just fine. If you want to contribute a layout preset, add it to [./src/layouts](./src/layouts) and then modify the enum in [./src/types.ts](./src/types.ts) to know about it's existence.

View file

@ -9,12 +9,13 @@ import {
SpecialSymbols, SpecialSymbols,
LayoutMeasurements, LayoutMeasurements,
LayoutColorscheme, LayoutColorscheme,
ChordConfig,
ElementLayout,
} from "./types"; } from "./types";
import split_3x5_2 from "./layouts/split_3x5_2"; import split_3x5_2 from "./layouts/split_3x5_2";
import alpha_staggered_double_switch from "./layouts/alpha_staggered_double_switch"; import alpha_staggered_double_switch from "./layouts/alpha_staggered_double_switch";
const defaultMeasurements: LayoutMeasurements = { const defaultMeasurements: LayoutMeasurements = {
imagePadding: 20,
keySize: 60, keySize: 60,
keyPadding: 2, keyPadding: 2,
keyCornerRadius: 5, keyCornerRadius: 5,
@ -30,6 +31,20 @@ const defaultColorscheme: LayoutColorscheme = {
blLayerColor: "purple", blLayerColor: "purple",
}; };
const defaultElementLayout: ElementLayout = {
mainToChordsGap: 10,
imagePadding: 20,
groupsPerRow: 2,
groupPadding: 20,
};
function parseSymbol(s: string) {
const special = SpecialSymbols[s];
const isNumber = String(parseInt(s)) == s;
if (isNumber || special === undefined) return s;
return special as SpecialSymbols;
}
export function parseConfig(input: string): Config { export function parseConfig(input: string): Config {
const parsed = JSON.parse(input); const parsed = JSON.parse(input);
@ -42,16 +57,20 @@ export function parseConfig(input: string): Config {
} }
return { return {
keys: (parsed.keys as string[][]).map((k) => keys: (parsed.keys as string[][]).map((k) => k.map(parseSymbol)),
k.map((s) => { chords: ((parsed.chords as ChordConfig[][]) || []).map((group) =>
const special = SpecialSymbols[s]; group.map((chord) => ({
const isNumber = String(parseInt(s)) == s; ...chord,
if (isNumber || special === undefined) return s; input: chord.input.map((k) => parseSymbol(k as string)),
return special as SpecialSymbols; output: parseSymbol(chord.output as string),
}), })),
), ),
colorscheme: { ...defaultColorscheme, ...parsed.colorscheme }, colorscheme: { ...defaultColorscheme, ...parsed.colorscheme },
measurements: { ...defaultMeasurements, ...parsed.measurements }, measurements: { ...defaultMeasurements, ...parsed.measurements },
elementLayout: {
...defaultElementLayout,
...parsed.elementLayout,
},
layout, layout,
}; };
} }
@ -77,6 +96,8 @@ export function makeLayout(config: Config): Layout {
keys: config.keys.map((k) => key(...(k as Arguments<typeof key>))), keys: config.keys.map((k) => key(...(k as Arguments<typeof key>))),
colorscheme: config.colorscheme, colorscheme: config.colorscheme,
measurements: config.measurements, measurements: config.measurements,
elementLayout: config.elementLayout,
chords: config.chords,
visual: predefined.visual, visual: predefined.visual,
size: predefined.size, size: predefined.size,
}; };

View file

@ -1,4 +1,10 @@
import { VisualKey, VisualLayout } from "./types"; import type {
KeySymbol,
Layout,
LayoutMeasurements,
VisualKey,
VisualLayout,
} from "./types";
import * as V from "./vec2"; import * as V from "./vec2";
export function visualKey( export function visualKey(
@ -70,3 +76,28 @@ export function scaleVisual(visual: VisualKey, amount: number): VisualKey {
size: V.scale(visual.size, amount), size: V.scale(visual.size, amount),
}; };
} }
export function scaleMeasurements(
measurements: LayoutMeasurements,
amount: number,
): LayoutMeasurements {
return {
imagePadding: measurements.imagePadding * amount,
keySize: measurements.keySize * amount,
keyPadding: measurements.keyPadding * amount,
keyCornerRadius: measurements.keyCornerRadius * amount,
keyStrokeWidth: measurements.keyStrokeWidth * amount,
mainToChordsGap: measurements.mainToChordsGap * amount,
};
}
export function findKeyByLabel(
layout: Layout,
label: KeySymbol,
): VisualKey | null {
for (let i = 0; i < layout.keys.length; i++) {
if (layout.keys[i].main === label) return layout.visual[i];
}
return null;
}

View file

@ -26,7 +26,7 @@ const layout: PredefinedLayout = {
L.visualKey([7.75, 3.25]), L.visualKey([7.75, 3.25]),
L.cols([5, 0], block, offsets), L.cols([5, 0], block, offsets),
].flat(), ].flat(),
size: [10.8, 4], size: [10.8, 4.25],
}; };
export default layout; export default layout;

View file

@ -1,7 +1,8 @@
import { tag, px } from "./svg"; import { children, tag, px } from "./svg";
import * as L from "./layout"; import * as L from "./layout";
import * as V from "./vec2"; import * as V from "./vec2";
import { import {
ChordConfig,
KeyboardKey, KeyboardKey,
KeySymbol, KeySymbol,
Layout, Layout,
@ -17,40 +18,115 @@ function textContents(input: KeySymbol): string {
return input; return input;
} }
interface KeyRenderingFlags {
stroke: boolean;
text: boolean;
}
function applyKeyPadding(
key: VisualKey,
measurements: LayoutMeasurements,
): { position: V.Vec2; size: V.Vec2 } {
return {
position: V.add(key.position, measurements.keyPadding),
size: V.add(key.size, -2 * measurements.keyPadding),
};
}
function keyCenter(key: VisualKey): V.Vec2 {
const centerX = key.position[0] + key.size[0] / 2;
const centerY = key.position[1] + key.size[1] / 2;
return [centerX, centerY];
}
const textAttribs = {
"text-anchor": "middle",
"dominant-baseline": "central",
"font-family": "Helvetica",
};
function textColor(
colorscheme: LayoutColorscheme,
input: KeySymbol,
_default: string,
): string {
if (input === SpecialSymbols.TL) return colorscheme.tlLayerColor;
if (input === SpecialSymbols.TR) return colorscheme.trLayerColor;
return _default;
}
function renderKey( function renderKey(
visual: VisualKey, visual: VisualKey,
key: KeyboardKey, key: KeyboardKey,
colorscheme: LayoutColorscheme, colorscheme: LayoutColorscheme,
measurements: LayoutMeasurements, measurements: LayoutMeasurements,
flags: KeyRenderingFlags,
) { ) {
const withPadding = { const withPadding = applyKeyPadding(visual, measurements);
position: V.add(visual.position, measurements.keyPadding), const center = keyCenter(visual);
size: V.add(visual.size, -2 * measurements.keyPadding),
};
const centerX = visual.position[0] + visual.size[0] / 2; const textOverlays = flags.text
const centerY = visual.position[1] + visual.size[1] / 2; ? [
const textAttribs = { tag(
"text-anchor": "middle", "text",
"dominant-baseline": "central", {
"font-family": "Helvetica", x: center[0],
}; y: center[1],
textLength: px(withPadding.size[1] / 2),
const textColor = (input: KeySymbol, _default: string): string => { fill: textColor(colorscheme, key.main, colorscheme.mainLayerColor),
if (input === SpecialSymbols.TL) return colorscheme.tlLayerColor; ...textAttribs,
if (input === SpecialSymbols.TR) return colorscheme.trLayerColor; },
return _default; textContents(key.main),
}; ),
tag(
"text",
{
x: withPadding.position[0] + withPadding.size[0] / 6,
y: withPadding.position[1] + withPadding.size[1] / 6,
fill: textColor(colorscheme, key.tlLayer, colorscheme.tlLayerColor),
"font-size": "66%",
...textAttribs,
"text-anchor": "start",
},
textContents(key.tlLayer),
),
tag(
"text",
{
x: withPadding.position[0] + (9 * withPadding.size[0]) / 10,
y: withPadding.position[1] + withPadding.size[1] / 6,
fill: textColor(colorscheme, key.trLayer, colorscheme.trLayerColor),
"font-size": "66%",
...textAttribs,
"text-anchor": "end",
},
textContents(key.trLayer),
),
tag(
"text",
{
x: withPadding.position[0] + withPadding.size[0] / 10,
y: withPadding.position[1] + (5 * withPadding.size[1]) / 6,
fill: textColor(colorscheme, key.blLayer, colorscheme.blLayerColor),
"font-size": "66%",
...textAttribs,
"text-anchor": "start",
},
textContents(key.blLayer),
),
]
: [];
return tag( return tag(
"g", "g",
{ {
transform: transform:
visual.angle && visual.angle !== 0 visual.angle && visual.angle !== 0
? `rotate(${visual.angle}, ${centerX}, ${centerY})` ? `rotate(${visual.angle}, ${center[0]}, ${center[1]})`
: "", : "",
}, },
[ children(
tag("rect", { tag("rect", {
width: px(withPadding.size[0]), width: px(withPadding.size[0]),
height: px(withPadding.size[1]), height: px(withPadding.size[1]),
@ -59,83 +135,230 @@ function renderKey(
rx: measurements.keyCornerRadius, rx: measurements.keyCornerRadius,
fill: colorscheme.keyFill, fill: colorscheme.keyFill,
stroke: colorscheme.keyStroke, stroke: colorscheme.keyStroke,
"stroke-width": px(measurements.keyStrokeWidth), "stroke-width": px(flags.stroke ? measurements.keyStrokeWidth : 0),
}), }),
...textOverlays,
),
);
}
function keyCorners(
key: VisualKey,
measurements: LayoutMeasurements,
): V.Vec2[] {
const withPadding = applyKeyPadding(key, measurements);
return [
withPadding.position,
V.add(withPadding.position, [withPadding.size[0], 0]),
V.add(withPadding.position, withPadding.size),
V.add(withPadding.position, [0, withPadding.size[1]]),
];
}
function renderChordShape(
first: VisualKey,
second: VisualKey,
chord: ChordConfig,
measurements: LayoutMeasurements,
colorscheme: LayoutColorscheme,
) {
if (first.position[0] > second.position[0])
return renderChordShape(second, first, chord, measurements, colorscheme);
const multi = (...steps: string[]) => steps.join(" ");
const moveTo = (to: V.Vec2) => `M ${to.join(" ")}`;
const lineTo = (to: V.Vec2) => `L ${to.join(" ")}`;
const firstCorners = keyCorners(first, measurements);
const secondCorners = keyCorners(second, measurements);
const firstCenter = keyCenter(first);
const secondCenter = keyCenter(second);
const middle = V.scale(V.add(firstCenter, secondCenter), 0.5);
const halfPath = (b: V.Vec2, c: V.Vec2, d: V.Vec2) => {
if ((b[1] - c[1]) * (b[0] - d[0]) > 0) return multi(lineTo(b), lineTo(d));
else return multi(lineTo(c), lineTo(d));
};
const dottedIndicator = (key: VisualKey) => {
const withPadding = applyKeyPadding(key, measurements);
const center = keyCenter(key);
const radius = Math.min(...withPadding.size) / 7.5;
return tag("circle", {
cx: center[0],
cy: center[1],
r: radius,
fill: "gray",
stroke: "gray",
"fill-opacity": 0.1,
"stroke-opacity": 0.6,
"stroke-width": px(measurements.keyStrokeWidth),
"stroke-dasharray": (radius * 2 * Math.PI) / 12,
});
};
return tag(
"g",
{},
children(
tag("path", {
fill: chord.fill,
stroke: chord.stroke,
"stroke-width": px(measurements.keyStrokeWidth),
d: multi(
moveTo(firstCorners[0]),
halfPath(firstCorners[1], secondCorners[0], secondCorners[1]),
lineTo(secondCorners[2]),
halfPath(secondCorners[3], firstCorners[2], firstCorners[3]),
"Z", // close path
),
}),
dottedIndicator(first),
dottedIndicator(second),
tag( tag(
"text", "text",
{ {
x: centerX, x: middle[0],
y: centerY, y: middle[1],
textLength: px(withPadding.size[1] / 2), "font-size": `${(chord.fontSizeModifier || 1) * 70}%`,
fill: textColor(key.main, colorscheme.mainLayerColor), fill: textColor(
colorscheme,
chord.output,
colorscheme.mainLayerColor,
),
...textAttribs, ...textAttribs,
"dominant-baseline": "middle",
}, },
textContents(key.main), textContents(chord.output),
), ),
tag( ),
"text",
{
x: withPadding.position[0] + withPadding.size[0] / 6,
y: withPadding.position[1] + withPadding.size[1] / 6,
fill: textColor(key.tlLayer, colorscheme.tlLayerColor),
"font-size": "66%",
...textAttribs,
"text-anchor": "start",
},
textContents(key.tlLayer),
),
tag(
"text",
{
x: withPadding.position[0] + (9 * withPadding.size[0]) / 10,
y: withPadding.position[1] + withPadding.size[1] / 6,
fill: textColor(key.trLayer, colorscheme.trLayerColor),
"font-size": "66%",
...textAttribs,
"text-anchor": "end",
},
textContents(key.trLayer),
),
tag(
"text",
{
x: withPadding.position[0] + withPadding.size[0] / 10,
y: withPadding.position[1] + (5 * withPadding.size[1]) / 6,
fill: textColor(key.blLayer, colorscheme.blLayerColor),
"font-size": "66%",
...textAttribs,
"text-anchor": "start",
},
textContents(key.blLayer),
),
].join("\n"),
); );
} }
export function renderLayout(layout: Layout) { export function renderLayout(layout: Layout) {
const totalWidth = layout.size[0] * layout.measurements.keySize;
const chordKeyScalingFactor =
(totalWidth / layout.elementLayout.groupsPerRow -
layout.elementLayout.groupPadding * 2) /
totalWidth;
const chordRowCount = Math.ceil(
layout.chords.length / layout.elementLayout.groupsPerRow,
);
const totalHeight =
layout.elementLayout.mainToChordsGap +
layout.measurements.keySize *
layout.size[1] *
(1 + chordRowCount / layout.elementLayout.groupsPerRow);
const widthPerChord = totalWidth / layout.elementLayout.groupsPerRow;
// {{{ Render main keys
const mainKeys = layout.visual.map((key, index) =>
renderKey(
L.scaleVisual(key, layout.measurements.keySize),
layout.keys[index],
layout.colorscheme,
layout.measurements,
{
text: true,
stroke: true,
},
),
);
// }}}
// {{{ Render chord groups
const chordMeasurements = L.scaleMeasurements(
layout.measurements,
chordKeyScalingFactor,
);
const chords = layout.chords.map((group, index) => {
const normalKeys = layout.visual.map((key, index) => {
const keyLabels = layout.keys[index];
if (group.findIndex((c) => c.input.includes(keyLabels.main)) !== -1)
return "";
return renderKey(
L.scaleVisual(key, chordMeasurements.keySize),
keyLabels,
{ ...layout.colorscheme, keyFill: "gray" },
chordMeasurements,
{
text: false,
stroke: false,
},
);
});
const chordShapes = group.map((chord) => {
return renderChordShape(
L.scaleVisual(
L.findKeyByLabel(layout, chord.input[0])!,
chordMeasurements.keySize,
),
L.scaleVisual(
L.findKeyByLabel(layout, chord.input[1])!,
chordMeasurements.keySize,
),
chord,
chordMeasurements,
layout.colorscheme,
);
});
return tag(
"g",
{
transform: `translate(${
(index % layout.elementLayout.groupsPerRow) * widthPerChord +
layout.elementLayout.groupPadding +
(index + layout.elementLayout.groupsPerRow > layout.chords.length
? ((layout.elementLayout.groupsPerRow -
(layout.chords.length % layout.elementLayout.groupsPerRow)) *
widthPerChord) /
2
: 0)
} ${
(Math.floor(index / layout.elementLayout.groupsPerRow) *
layout.measurements.keySize *
layout.size[1]) /
layout.elementLayout.groupsPerRow +
layout.elementLayout.groupPadding
})`,
},
children(...normalKeys, ...chordShapes),
);
});
// }}}
// {{{ Put everything together
return tag( return tag(
"svg", "svg",
{ {
viewBox: [ viewBox: [
-layout.measurements.imagePadding, -layout.elementLayout.imagePadding,
-layout.measurements.imagePadding, -layout.elementLayout.imagePadding,
2 * layout.measurements.imagePadding + 2 * layout.elementLayout.imagePadding + totalWidth,
layout.size[0] * layout.measurements.keySize,
2 * layout.measurements.imagePadding + 2 * layout.elementLayout.imagePadding + totalHeight,
layout.size[1] * layout.measurements.keySize,
].join(" "), ].join(" "),
xmlns: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/svg",
"xmlns:xlink": "http://www.w3.org/1999/xlink", "xmlns:xlink": "http://www.w3.org/1999/xlink",
}, },
layout.visual children(
.map((key, index) => ...mainKeys,
renderKey( tag(
L.scaleVisual(key, layout.measurements.keySize), "g",
layout.keys[index], {
layout.colorscheme, transform: `translate(0 ${
layout.measurements, layout.size[1] * layout.measurements.keySize +
), layout.elementLayout.mainToChordsGap
) })`,
.join("\n"), },
children(...chords),
),
),
); );
// }}}
} }

View file

@ -5,6 +5,10 @@ function indent(amount: number, text: string) {
.join("\n"); .join("\n");
} }
export function children(...many: string[]): string {
return many.join("\\n");
}
export function tag( export function tag(
name: string, name: string,
attributes: Record<string, string | number | undefined>, attributes: Record<string, string | number | undefined>,

View file

@ -38,24 +38,42 @@ export interface LayoutColorscheme {
blLayerColor: string; blLayerColor: string;
} }
export enum PredefinedLayoutName {
split_3x5_2,
alpha_staggered_double_switch,
}
export interface LayoutMeasurements { export interface LayoutMeasurements {
imagePadding: number;
keySize: number; keySize: number;
keyPadding: number; keyPadding: number;
keyCornerRadius: number; keyCornerRadius: number;
keyStrokeWidth: number; keyStrokeWidth: number;
} }
export enum PredefinedLayoutName {
split_3x5_2,
alpha_staggered_double_switch,
}
export type ChordName = string;
export interface ChordConfig {
input: KeySymbol[];
output: KeySymbol;
fill: string;
stroke?: string;
fontSizeModifier?: number;
}
export interface ElementLayout {
groupsPerRow: number;
groupPadding: number;
imagePadding: number;
mainToChordsGap: number;
}
export interface Config { export interface Config {
keys: KeySymbol[][]; keys: KeySymbol[][];
chords: ChordConfig[][];
layout: PredefinedLayoutName; layout: PredefinedLayoutName;
colorscheme: LayoutColorscheme; colorscheme: LayoutColorscheme;
measurements: LayoutMeasurements; measurements: LayoutMeasurements;
elementLayout: ElementLayout;
} }
export type PredefinedLayout = { export type PredefinedLayout = {
@ -64,7 +82,10 @@ export type PredefinedLayout = {
}; };
export interface Layout extends PredefinedLayout { export interface Layout extends PredefinedLayout {
keys: KeyboardKey[];
colorscheme: LayoutColorscheme; colorscheme: LayoutColorscheme;
measurements: LayoutMeasurements; measurements: LayoutMeasurements;
elementLayout: ElementLayout;
keys: KeyboardKey[];
chords: ChordConfig[][];
} }