>("active-layer")
const layers = [
["Numeric Layer", "123", 1],
@@ -20,8 +22,8 @@
@@ -29,7 +31,7 @@
{#if device === "ONE"}
-
+
{:else}
Unsupported device ({$serialPort?.device})
diff --git a/src/lib/components/layout/get-actions.ts b/src/lib/components/layout/get-actions.ts
new file mode 100644
index 00000000..97a95825
--- /dev/null
+++ b/src/lib/components/layout/get-actions.ts
@@ -0,0 +1,19 @@
+import type {CharaLayout} from "$lib/serialization/layout"
+import type {Change} from "$lib/serial/connection"
+import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
+import type {KeyInfo} from "$lib/serial/keymap-codes"
+
+export function getActions(
+ layer: number,
+ id: number,
+ layout: CharaLayout,
+ changes: Change[],
+): [KeyInfo, boolean] {
+ const actionId = layout?.[layer][id]
+ const changedId = changes.findLast(it => it?.layout?.[layer]?.[id] !== undefined)?.layout?.[layer]?.[id]
+ if (changedId !== undefined) {
+ return [KEYMAP_CODES[changedId] ?? {code: changedId}, true]
+ } else {
+ return [KEYMAP_CODES[actionId] ?? {code: actionId}, false]
+ }
+}
diff --git a/src/lib/components/layout/visual-layout.ts b/src/lib/components/layout/visual-layout.ts
new file mode 100644
index 00000000..1647c496
--- /dev/null
+++ b/src/lib/components/layout/visual-layout.ts
@@ -0,0 +1,9 @@
+export interface VisualLayoutConfig {
+ scale: number
+ inactiveScale: number
+ inactiveOpacity: number
+ strokeWidth: number
+ margin: number
+ fontSize: number
+ iconFontSize: number
+}
diff --git a/src/lib/serialization/visual-layout.ts b/src/lib/serialization/visual-layout.ts
index 3d10b895..dc3999ea 100644
--- a/src/lib/serialization/visual-layout.ts
+++ b/src/lib/serialization/visual-layout.ts
@@ -1,17 +1,32 @@
export interface VisualLayout {
name: string
- row: VisualLayoutRow[]
+ col: VisualLayoutRow[]
}
-export interface VisualLayoutRow {
- col: VisualLayoutKey[]
+interface Positionable {
+ offset: [number, number]
+ rotate: number
}
-export interface VisualLayoutKey {
- id: number
+export interface VisualLayoutRow extends Positionable {
+ row: Array
+}
+
+export interface VisualLayoutKey extends Positionable {
+ key: number
size?: [number, number]
}
+export interface VisualLayoutSwitch extends Positionable {
+ switch: {
+ n: number
+ e: number
+ w: number
+ s: number
+ d: number
+ }
+}
+
export interface CompiledLayout {
name: string
size: [number, number]
@@ -20,9 +35,11 @@ export interface CompiledLayout {
export interface CompiledLayoutKey {
id: number
- type: "key" | "dpad"
+ shape: "quarter-circle" | "square"
+ cornerRadius: number
size: [number, number]
pos: [number, number]
+ rotate: number
}
export function compileLayout(layout: VisualLayout): CompiledLayout {
@@ -33,21 +50,52 @@ export function compileLayout(layout: VisualLayout): CompiledLayout {
}
let y = 0
- for (const {col} of layout.row) {
- let x = 0
+ for (const {row, offset} of layout.col) {
+ let x = offset?.[0] ?? 0
+ y += offset?.[1] ?? 0
let maxHeight = 0
- for (const {id, size} of col) {
- const [width, height] = size ?? [1, 1]
+ for (const info of row) {
+ const [ox, oy] = info.offset || [0, 0]
+ const rotate = info.rotate || 0
+ if ("key" in info) {
+ const [width, height] = info.size ?? [1, 1]
- compiled.keys.push({
- id,
- type: "key",
- size: [width, height],
- pos: [x, y],
- })
+ compiled.keys.push({
+ id: info.key,
+ shape: "square",
+ size: [width, height],
+ pos: [x + ox, y + oy],
+ cornerRadius: 0.1,
+ rotate,
+ })
- x += width
- maxHeight = Math.max(maxHeight, height)
+ x += width + ox
+ maxHeight = Math.max(maxHeight, height + oy)
+ } else if ("switch" in info) {
+ const cx = x + ox + 1
+ const cy = y + oy + 1
+ for (const [id, i] of [info.switch.n, info.switch.e, info.switch.s, info.switch.w].entries()) {
+ compiled.keys.push({
+ id,
+ shape: "quarter-circle",
+ cornerRadius: 0,
+ size: [2, 0.6],
+ pos: [cx, cy],
+ rotate: (Math.PI / 2) * i + Math.PI / 4,
+ })
+ }
+ compiled.keys.push({
+ id: info.switch.d,
+ shape: "square",
+ cornerRadius: 0.5,
+ size: [0.8, 0.8],
+ pos: [x + 0.6 + ox, y + 0.6 + oy],
+ rotate: 0,
+ })
+
+ x += 2 + ox
+ maxHeight = Math.max(maxHeight, 2 + oy)
+ }
}
y += maxHeight
compiled.size[0] = Math.max(compiled.size[0], x)
diff --git a/src/routes/config/layout/+page.svelte b/src/routes/config/layout/+page.svelte
index a790f9a6..12e048e0 100644
--- a/src/routes/config/layout/+page.svelte
+++ b/src/routes/config/layout/+page.svelte
@@ -2,12 +2,14 @@
import {share} from "$lib/share"
import {layout} from "$lib/serial/connection"
import tippy from "tippy.js"
- import {onMount} from "svelte"
+ import {onMount, setContext} from "svelte"
import Layout from "$lib/components/layout/Layout.svelte"
import {csvLayoutToJson, isCsvLayout} from "$lib/compat/legacy-layout"
import {charaFileFromUriComponent, charaFileToUriComponent} from "$lib/share/share-url"
import type {CharaLayoutFile} from "$lib/share/chara-file"
import SharePopup from "../SharePopup.svelte"
+ import type {VisualLayoutConfig} from "$lib/components/layout/visual-layout"
+ import {writable} from "svelte/store"
onMount(async () => {
const url = new URL(window.location.href)
@@ -53,6 +55,18 @@
if (importedLayout.type === "layout" && importedLayout.charaVersion === 1) $layout = importedLayout.layout
}
+ setContext("visual-layout-config", {
+ scale: 50,
+ inactiveScale: 0.6,
+ inactiveOpacity: 0.4,
+ strokeWidth: 1,
+ margin: 5,
+ fontSize: 9,
+ iconFontSize: 14,
+ })
+
+ setContext("active-layer", writable(0))
+
let fileInput: HTMLInputElement