visual layout adjustments

This commit is contained in:
2023-10-31 22:09:33 +01:00
parent a7b49de6ac
commit e4d51cd51d
10 changed files with 390 additions and 249 deletions

View File

@@ -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>

View 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>

View 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>

View File

@@ -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>

View 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]
}
}

View File

@@ -0,0 +1,9 @@
export interface VisualLayoutConfig {
scale: number
inactiveScale: number
inactiveOpacity: number
strokeWidth: number
margin: number
fontSize: number
iconFontSize: number
}