mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-09 03:22:49 +00:00
visual layout adjustments
This commit is contained in:
@@ -1,36 +1,25 @@
|
||||
name: CC1
|
||||
col:
|
||||
- gap: 156
|
||||
- row:
|
||||
- switch: { d: 30, e: 31, n: 32, w: 33, s: 34 }
|
||||
- switch: { d: 25, e: 26, n: 27, w: 28, s: 29 }
|
||||
- switch: { d: 40, e: 41, n: 42, w: 43, s: 44 }
|
||||
- switch: { d: 20, e: 21, n: 22, w: 23, s: 24 }
|
||||
- switch: { d: 35, e: 36, n: 37, w: 38, s: 39 }
|
||||
- switch: { d: 15, e: 16, n: 17, w: 18, s: 19 }
|
||||
- switch: { d: 60, w: 61, n: 62, e: 63, s: 64 }
|
||||
- switch: { d: 65, w: 66, n: 67, e: 68, s: 69 }
|
||||
- switch: { d: 80, w: 81, n: 82, e: 83, s: 84 }
|
||||
- switch: { d: 70, w: 71, n: 72, e: 73, s: 74 }
|
||||
- switch: { d: 85, w: 86, n: 87, e: 88, s: 89 }
|
||||
- switch: { d: 75, w: 76, n: 77, e: 78, s: 79 }
|
||||
- row:
|
||||
- switch: { d: 10, e: 11, n: 12, w: 13, s: 14 }
|
||||
- switch: { d: 55, w: 56, n: 57, e: 58, s: 59 }
|
||||
- row:
|
||||
- switch: { d: 5, e: 6, n: 7, w: 8, s: 9 }
|
||||
- switch: { d: 50, w: 51, n: 52, e: 53, s: 54 }
|
||||
- offset: [ 0, 0 ]
|
||||
row:
|
||||
- row:
|
||||
- {d: 30, e: 31, n: 32, w: 33, s: 34}
|
||||
- col:
|
||||
- {d: 25, e: 26, n: 27, w: 28, s: 29}
|
||||
- {d: 40, e: 41, n: 42, w: 43, s: 44}
|
||||
- col:
|
||||
- {d: 20, e: 21, n: 22, w: 23, s: 24}
|
||||
- {d: 35, e: 36, n: 37, w: 38, s: 39}
|
||||
- {d: 15, e: 16, n: 17, w: 18, s: 19}
|
||||
- row:
|
||||
- {d: 60, w: 61, n: 62, e: 63, s: 64}
|
||||
- col:
|
||||
- {d: 65, w: 66, n: 67, e: 68, s: 69}
|
||||
- {d: 80, w: 81, n: 82, e: 83, s: 84}
|
||||
- col:
|
||||
- {d: 70, w: 71, n: 72, e: 73, s: 74}
|
||||
- {d: 85, w: 86, n: 87, e: 88, s: 89}
|
||||
- {d: 75, w: 76, n: 77, e: 78, s: 79}
|
||||
- gap: 48
|
||||
margin-top: -32
|
||||
row:
|
||||
- {d: 10, e: 11, n: 12, w: 13, s: 14}
|
||||
- {d: 55, w: 56, n: 57, e: 58, s: 59}
|
||||
- gap: 160
|
||||
row:
|
||||
- {d: 5, e: 6, n: 7, w: 8, s: 9}
|
||||
- {d: 50, w: 51, n: 52, e: 53, s: 54}
|
||||
- gap: 320
|
||||
margin-top: -12
|
||||
row:
|
||||
- {d: 0, e: 1, n: 2, w: 3, s: 4}
|
||||
- {d: 45, w: 46, n: 47, e: 48, s: 49}
|
||||
- switch: { d: 0, e: 1, n: 2, w: 3, s: 4 }
|
||||
- switch: { d: 45, w: 46, n: 47, e: 48, s: 49 }
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
name: Lite
|
||||
row:
|
||||
- col:
|
||||
- id: 53
|
||||
- id: 54
|
||||
- id: 55
|
||||
- id: 56
|
||||
- id: 57
|
||||
- id: 58
|
||||
- id: 59
|
||||
- id: 60
|
||||
- id: 61
|
||||
- id: 62
|
||||
- id: 63
|
||||
- id: 64
|
||||
- id: 65
|
||||
- id: 66
|
||||
col:
|
||||
- row:
|
||||
- key: 53
|
||||
- key: 54
|
||||
- key: 55
|
||||
- key: 56
|
||||
- key: 57
|
||||
- key: 58
|
||||
- key: 59
|
||||
- key: 60
|
||||
- key: 61
|
||||
- key: 62
|
||||
- key: 63
|
||||
- key: 64
|
||||
- key: 65
|
||||
- key: 66
|
||||
size: [ 2, 1 ]
|
||||
- col:
|
||||
- id: 39
|
||||
- row:
|
||||
- key: 39
|
||||
size: [ 1.5, 1 ]
|
||||
- id: 40
|
||||
- id: 41
|
||||
- id: 42
|
||||
- id: 43
|
||||
- id: 44
|
||||
- id: 45
|
||||
- id: 46
|
||||
- id: 47
|
||||
- id: 48
|
||||
- id: 49
|
||||
- id: 50
|
||||
- id: 51
|
||||
- id: 52
|
||||
- key: 40
|
||||
- key: 41
|
||||
- key: 42
|
||||
- key: 43
|
||||
- key: 44
|
||||
- key: 45
|
||||
- key: 46
|
||||
- key: 47
|
||||
- key: 48
|
||||
- key: 49
|
||||
- key: 50
|
||||
- key: 51
|
||||
- key: 52
|
||||
size: [ 1.5, 1 ]
|
||||
- col:
|
||||
- id: 26
|
||||
- row:
|
||||
- key: 26
|
||||
size: [ 1.75, 1 ]
|
||||
- id: 27
|
||||
- id: 28
|
||||
- id: 29
|
||||
- id: 30
|
||||
- id: 31
|
||||
- id: 32
|
||||
- id: 33
|
||||
- id: 34
|
||||
- id: 35
|
||||
- id: 36
|
||||
- id: 37
|
||||
- id: 38
|
||||
- key: 27
|
||||
- key: 28
|
||||
- key: 29
|
||||
- key: 30
|
||||
- key: 31
|
||||
- key: 32
|
||||
- key: 33
|
||||
- key: 34
|
||||
- key: 35
|
||||
- key: 36
|
||||
- key: 37
|
||||
- key: 38
|
||||
size: [ 2.25, 1 ]
|
||||
- col:
|
||||
- id: 12
|
||||
- row:
|
||||
- key: 12
|
||||
size: [ 2, 1 ]
|
||||
- id: 13
|
||||
- id: 14
|
||||
- id: 15
|
||||
- id: 16
|
||||
- id: 17
|
||||
- id: 18
|
||||
- id: 19
|
||||
- id: 20
|
||||
- id: 21
|
||||
- id: 22
|
||||
- id: 23
|
||||
- id: 24
|
||||
- id: 25
|
||||
- col:
|
||||
- id: 0
|
||||
- id: 1
|
||||
- key: 13
|
||||
- key: 14
|
||||
- key: 15
|
||||
- key: 16
|
||||
- key: 17
|
||||
- key: 18
|
||||
- key: 19
|
||||
- key: 20
|
||||
- key: 21
|
||||
- key: 22
|
||||
- key: 23
|
||||
- key: 24
|
||||
- key: 25
|
||||
- row:
|
||||
- key: 0
|
||||
- key: 1
|
||||
size: [ 1.25, 1 ]
|
||||
- id: 2
|
||||
- key: 2
|
||||
size: [ 1.25, 1 ]
|
||||
- id: 3
|
||||
- key: 3
|
||||
size: [ 2, 1 ]
|
||||
- id: 4
|
||||
- id: 5
|
||||
- id: 6
|
||||
- key: 4
|
||||
- key: 5
|
||||
- key: 6
|
||||
size: [ 2, 1 ]
|
||||
- id: 7
|
||||
- key: 7
|
||||
size: [ 1.25, 1 ]
|
||||
- id: 8
|
||||
- key: 8
|
||||
size: [ 1.25, 1 ]
|
||||
- id: 9
|
||||
- id: 10
|
||||
- id: 11
|
||||
- key: 9
|
||||
- key: 10
|
||||
- key: 11
|
||||
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
<script lang="ts">
|
||||
import rawLayout from "$lib/assets/layouts/lite.yml"
|
||||
import rawLayout from "$lib/assets/layouts/cc1.yml"
|
||||
import {compileLayout} from "$lib/serialization/visual-layout"
|
||||
import type {VisualLayout, CompiledLayoutKey} from "$lib/serialization/visual-layout"
|
||||
import {changes, layout} from "$lib/serial/connection"
|
||||
import type {Change} from "$lib/serial/connection"
|
||||
import {dev} from "$app/environment"
|
||||
import {KEYMAP_CODES} from "$lib/serial/keymap-codes.js"
|
||||
import type {KeyInfo} from "$lib/serial/keymap-codes.js"
|
||||
import ActionSelector from "$lib/components/layout/ActionSelector.svelte"
|
||||
import {get} from "svelte/store"
|
||||
import type {CharaLayout} from "$lib/serialization/layout"
|
||||
import type {Writable} from "svelte/store"
|
||||
import KeyboardKey from "$lib/components/layout/KeyboardKey.svelte"
|
||||
import {getContext} from "svelte"
|
||||
import type {VisualLayoutConfig} from "./visual-layout.js"
|
||||
|
||||
const scale = 50
|
||||
export let inactiveScale = 0.6
|
||||
export let inactiveOpacity = 0.4
|
||||
export let strokeWidth = 1
|
||||
export let margin = 5
|
||||
export let fontSize = 9
|
||||
export let iconFontSize = 14
|
||||
export let activeLayer: number
|
||||
const {scale, margin, strokeWidth, fontSize, iconFontSize} =
|
||||
getContext<VisualLayoutConfig>("visual-layout-config")
|
||||
const activeLayer = getContext<Writable<number>>("active-layer")
|
||||
|
||||
if (dev) {
|
||||
// you have absolutely no idea what a difference this makes for performance
|
||||
@@ -114,22 +109,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
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], true]
|
||||
} else {
|
||||
return [KEYMAP_CODES[actionId], false]
|
||||
}
|
||||
}
|
||||
|
||||
function edit(index: number) {
|
||||
const keyInfo = layoutInfo.keys[index]
|
||||
const clickedGroup = groupParent.children.item(index) as SVGGElement
|
||||
const component = new ActionSelector({
|
||||
target: document.body,
|
||||
props: {currentAction: get(layout)[activeLayer][keyInfo.id]},
|
||||
props: {currentAction: get(layout)[get(activeLayer)][keyInfo.id]},
|
||||
})
|
||||
const dialog = document.querySelector("dialog > div") as HTMLDivElement
|
||||
const backdrop = document.querySelector("dialog") as HTMLDialogElement
|
||||
@@ -167,7 +152,7 @@
|
||||
component.$on("close", closed)
|
||||
component.$on("select", ({detail}) => {
|
||||
changes.update(changes => {
|
||||
changes.push({layout: {[activeLayer]: {[keyInfo.id]: detail}}})
|
||||
changes.push({layout: {[get(activeLayer)]: {[keyInfo.id]: detail}}})
|
||||
return changes
|
||||
})
|
||||
closed()
|
||||
@@ -183,114 +168,24 @@
|
||||
<p>{layoutInfo.name}</p>
|
||||
<svg viewBox="0 0 {layoutInfo.size[0] * scale} {layoutInfo.size[1] * scale}" bind:this={groupParent}>
|
||||
{#each layoutInfo.keys as key, i}
|
||||
{@const posX = key.pos[0] * scale}
|
||||
{@const posY = key.pos[1] * scale}
|
||||
{@const sizeX = key.size[0] * scale}
|
||||
{@const sizeY = key.size[1] * scale}
|
||||
{@const middleX = sizeX / 2}
|
||||
{@const middleY = sizeY / 2}
|
||||
<g
|
||||
class="key-group"
|
||||
<KeyboardKey
|
||||
{i}
|
||||
{key}
|
||||
on:focusin={() => (focusKey = key)}
|
||||
on:click={() => edit(i)}
|
||||
on:keypress={({key}) => {
|
||||
if (key === "Enter") {
|
||||
edit(i)
|
||||
}
|
||||
}}
|
||||
on:focusin={() => (focusKey = key)}
|
||||
role="button"
|
||||
tabindex={i + 1}
|
||||
>
|
||||
<rect
|
||||
x={posX + margin}
|
||||
y={posY + margin}
|
||||
rx={margin}
|
||||
width={sizeX - margin * 2}
|
||||
height={sizeY - margin * 2}
|
||||
stroke="currentcolor"
|
||||
stroke-width={strokeWidth}
|
||||
/>
|
||||
{#each [1, 2, 0] as layer, i}
|
||||
{@const [action, changed] = getActions(layer, key.id, $layout, $changes)}
|
||||
{@const isActive = layer === activeLayer}
|
||||
{@const direction = [
|
||||
(middleX - margin * 3) / (i % 2 === 0 ? -1 : 1),
|
||||
(middleY - margin * 3) / (i < 2 ? -1 : 1),
|
||||
]}
|
||||
{@const layerFontSize = action?.icon ? iconFontSize : fontSize}
|
||||
<g
|
||||
style="transform: {isActive
|
||||
? `translate3d(0, 0, 0) scale(1)`
|
||||
: `translate3d(${direction[0]}px, ${direction[1]}px, 0) scale(${inactiveScale})`}"
|
||||
>
|
||||
<text
|
||||
fill={changed ? "var(--md-sys-color-primary)" : "currentcolor"}
|
||||
text-anchor="middle"
|
||||
alignment-baseline="central"
|
||||
x={posX + middleX + (changed ? fontSize / 3 : 0)}
|
||||
y={posY + middleY}
|
||||
font-size={layerFontSize}
|
||||
font-family={action?.icon ? "Material Symbols Rounded" : undefined}
|
||||
opacity={isActive ? 1 : inactiveOpacity}
|
||||
>
|
||||
{action?.icon || action?.id || action?.code || `{${key.id}}`}
|
||||
{#if changed}
|
||||
<tspan font-weight="bold">•</tspan>
|
||||
{/if}
|
||||
</text>
|
||||
</g>
|
||||
{/each}
|
||||
</g>
|
||||
/>
|
||||
{/each}
|
||||
</svg>
|
||||
|
||||
<style lang="scss">
|
||||
$focus-transition: 10ms;
|
||||
$transition: 200ms;
|
||||
|
||||
svg {
|
||||
overflow: visible;
|
||||
width: calc(min(100%, 35cm));
|
||||
max-height: calc(100% - 170px);
|
||||
}
|
||||
|
||||
text {
|
||||
transition:
|
||||
fill #{$focus-transition} ease,
|
||||
opacity #{$transition} ease;
|
||||
}
|
||||
|
||||
rect {
|
||||
fill: var(--md-sys-color-background);
|
||||
transition:
|
||||
fill #{$focus-transition} ease,
|
||||
stroke #{$focus-transition} ease,
|
||||
fill-opacity #{$focus-transition} ease;
|
||||
}
|
||||
|
||||
g {
|
||||
transform-origin: center;
|
||||
transform-box: fill-box;
|
||||
transition:
|
||||
fill #{$focus-transition} ease,
|
||||
opacity #{$transition} ease,
|
||||
transform #{$transition} ease;
|
||||
}
|
||||
|
||||
.key-group:hover {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
transition: opacity #{$transition} ease;
|
||||
}
|
||||
|
||||
.key-group:focus-within {
|
||||
color: var(--md-sys-color-primary);
|
||||
outline: none;
|
||||
|
||||
> rect {
|
||||
outline: none;
|
||||
fill: currentcolor;
|
||||
fill-opacity: 0.2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
63
src/lib/components/layout/KeyText.svelte
Normal file
63
src/lib/components/layout/KeyText.svelte
Normal file
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
import {getActions} from "$lib/components/layout/get-actions.js"
|
||||
import {changes, layout} from "$lib/serial/connection.js"
|
||||
import {getContext} from "svelte"
|
||||
import type {Writable} from "svelte/store"
|
||||
import type {VisualLayoutConfig} from "$lib/components/layout/visual-layout"
|
||||
import type {CompiledLayoutKey} from "$lib/serialization/visual-layout"
|
||||
|
||||
const {fontSize, margin, inactiveOpacity, inactiveScale, iconFontSize} =
|
||||
getContext<VisualLayoutConfig>("visual-layout-config")
|
||||
const activeLayer = getContext<Writable<number>>("active-layer")
|
||||
|
||||
export let key: CompiledLayoutKey
|
||||
export let fontSizeMultiplier = 1
|
||||
|
||||
export let middle: [number, number]
|
||||
export let pos: [number, number]
|
||||
export let rotate: number
|
||||
|
||||
export let positions: [[number, number], [number, number], [number, number]]
|
||||
</script>
|
||||
|
||||
{#each positions as position, layer}
|
||||
{@const [action, changed] = getActions(layer, key.id, $layout, $changes)}
|
||||
{@const isActive = layer === $activeLayer}
|
||||
{@const direction = [(middle[0] - margin * 3) / position[0], (middle[1] - margin * 3) / position[1]]}
|
||||
<text
|
||||
fill={changed ? "var(--md-sys-color-primary)" : "currentcolor"}
|
||||
font-weight={changed ? "bold" : ""}
|
||||
text-anchor="middle"
|
||||
alignment-baseline="central"
|
||||
x={pos[0] + middle[0] + (changed ? fontSize / 3 : 0)}
|
||||
y={pos[1] + middle[1]}
|
||||
font-size={fontSizeMultiplier * (action.icon ? iconFontSize : fontSize)}
|
||||
font-family={action.icon ? "Material Symbols Rounded" : undefined}
|
||||
opacity={isActive ? 1 : inactiveOpacity}
|
||||
style:scale={isActive ? 1 : inactiveScale}
|
||||
style:translate={isActive ? "0 0" : `${direction[0]}px ${direction[1]}px`}
|
||||
style:rotate="{rotate}rad"
|
||||
>
|
||||
{#if action.code !== 0}
|
||||
{action.icon || action.id || `0x${action.code?.toString(16)}`}
|
||||
{/if}
|
||||
{#if changed}
|
||||
<tspan>•</tspan>
|
||||
{/if}
|
||||
</text>
|
||||
{/each}
|
||||
|
||||
<style lang="scss">
|
||||
$focus-transition: 10ms;
|
||||
$transition: 200ms;
|
||||
|
||||
text {
|
||||
transform-origin: center;
|
||||
transform-box: fill-box;
|
||||
transition:
|
||||
fill #{$focus-transition} ease,
|
||||
opacity #{$transition} ease,
|
||||
translate #{$transition} ease,
|
||||
scale #{$transition} ease;
|
||||
}
|
||||
</style>
|
||||
102
src/lib/components/layout/KeyboardKey.svelte
Normal file
102
src/lib/components/layout/KeyboardKey.svelte
Normal file
@@ -0,0 +1,102 @@
|
||||
<script lang="ts">
|
||||
import type {CompiledLayoutKey} from "$lib/serialization/visual-layout"
|
||||
import {getContext} from "svelte"
|
||||
import type {VisualLayoutConfig} from "./visual-layout.js"
|
||||
import KeyText from "$lib/components/layout/KeyText.svelte"
|
||||
|
||||
const {scale, margin, strokeWidth} = getContext<VisualLayoutConfig>("visual-layout-config")
|
||||
export let i: number
|
||||
export let key: CompiledLayoutKey
|
||||
|
||||
$: posX = key.pos[0] * scale
|
||||
$: posY = key.pos[1] * scale
|
||||
$: sizeX = key.size[0] * scale
|
||||
$: sizeY = key.size[1] * scale
|
||||
</script>
|
||||
|
||||
<g class="key-group" on:click on:keypress on:focusin role="button" tabindex={i + 1}>
|
||||
{#if key.shape === "square"}
|
||||
<rect
|
||||
x={posX + margin}
|
||||
y={posY + margin}
|
||||
rx={key.cornerRadius * scale}
|
||||
width={sizeX - margin * 2}
|
||||
height={sizeY - margin * 2}
|
||||
stroke-width={strokeWidth}
|
||||
/>
|
||||
<KeyText
|
||||
{key}
|
||||
middle={[sizeX / 2, sizeY / 2]}
|
||||
pos={[posX, posY]}
|
||||
rotate={-key.rotate}
|
||||
positions={[
|
||||
[-1, 1],
|
||||
[-1, -1],
|
||||
[1, -1],
|
||||
]}
|
||||
/>
|
||||
{:else if key.shape === "quarter-circle"}
|
||||
{@const innerMargin = margin / 2}
|
||||
{@const r1 = sizeX / 2 - margin}
|
||||
{@const p1 = r1 - innerMargin}
|
||||
{@const r2 = r1 - sizeY + innerMargin * 2}
|
||||
{@const p2 = r2 - innerMargin}
|
||||
{@const multiplier = 1.4}
|
||||
<g style:transform="rotateZ({key.rotate}rad) translate({innerMargin}px, {innerMargin}px)">
|
||||
<path
|
||||
d="M{posX + p1},{posY} a{r1},{r1} 0 0,1 {-p1},{p1} l0,{-(p1 - p2)} a{r2},{r2} 0 0,0 {p2},{-p2}z"
|
||||
/>
|
||||
<KeyText
|
||||
{key}
|
||||
middle={[sizeY - margin * 2, sizeY - margin * 2]}
|
||||
pos={[posX, posY]}
|
||||
rotate={-key.rotate}
|
||||
fontSizeMultiplier={multiplier}
|
||||
positions={[
|
||||
[-0.6, -0.6],
|
||||
[0.6, -0.6],
|
||||
[-0.6, 0.6],
|
||||
]}
|
||||
/>
|
||||
</g>
|
||||
{/if}
|
||||
</g>
|
||||
|
||||
<style lang="scss">
|
||||
$focus-transition: 10ms;
|
||||
$transition: 200ms;
|
||||
|
||||
rect {
|
||||
transform-origin: center;
|
||||
transform-box: fill-box;
|
||||
}
|
||||
|
||||
g {
|
||||
transform-origin: top left;
|
||||
transform-box: fill-box;
|
||||
}
|
||||
|
||||
path,
|
||||
rect {
|
||||
fill: var(--md-sys-color-background);
|
||||
fill-opacity: 0;
|
||||
stroke: currentcolor;
|
||||
}
|
||||
|
||||
g:hover {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
transition: opacity #{$transition} ease;
|
||||
}
|
||||
|
||||
g:focus-within {
|
||||
color: var(--md-sys-color-primary);
|
||||
outline: none;
|
||||
|
||||
> path,
|
||||
> rect {
|
||||
fill: currentcolor;
|
||||
fill-opacity: 0.2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,9 +3,11 @@
|
||||
import LayoutCC1 from "$lib/components/layout/LayoutCC1.svelte"
|
||||
import {action} from "$lib/title"
|
||||
import GenericLayout from "$lib/components/layout/GenericLayout.svelte"
|
||||
import {getContext} from "svelte"
|
||||
import type {Writable} from "svelte/store"
|
||||
|
||||
$: device = $serialPort?.device ?? "ONE"
|
||||
let activeLayer = 0
|
||||
const activeLayer = getContext<Writable<number>>("active-layer")
|
||||
|
||||
const layers = [
|
||||
["Numeric Layer", "123", 1],
|
||||
@@ -20,8 +22,8 @@
|
||||
<button
|
||||
class="icon"
|
||||
use:action={{title, shortcut: `alt+${value + 1}`}}
|
||||
on:click={() => (activeLayer = value)}
|
||||
class:active={activeLayer === value}
|
||||
on:click={() => ($activeLayer = value)}
|
||||
class:active={$activeLayer === value}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
@@ -29,7 +31,7 @@
|
||||
</fieldset>
|
||||
|
||||
{#if device === "ONE"}
|
||||
<GenericLayout bind:activeLayer />
|
||||
<GenericLayout />
|
||||
<!-- <LayoutCC1 bind:activeLayer /> -->
|
||||
{:else}
|
||||
<p>Unsupported device ({$serialPort?.device})</p>
|
||||
|
||||
19
src/lib/components/layout/get-actions.ts
Normal file
19
src/lib/components/layout/get-actions.ts
Normal file
@@ -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]
|
||||
}
|
||||
}
|
||||
9
src/lib/components/layout/visual-layout.ts
Normal file
9
src/lib/components/layout/visual-layout.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface VisualLayoutConfig {
|
||||
scale: number
|
||||
inactiveScale: number
|
||||
inactiveOpacity: number
|
||||
strokeWidth: number
|
||||
margin: number
|
||||
fontSize: number
|
||||
iconFontSize: number
|
||||
}
|
||||
@@ -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<VisualLayoutKey | VisualLayoutSwitch>
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -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<VisualLayoutConfig>("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
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user