feat: device flip

This commit is contained in:
2026-03-31 19:40:01 +02:00
parent 04540a55ef
commit 07491b9741
2 changed files with 128 additions and 78 deletions

View File

@@ -14,7 +14,6 @@
CompiledLayout, CompiledLayout,
CompiledLayoutKey, CompiledLayoutKey,
} from "$lib/assets/layouts/layout.d.ts"; } from "$lib/assets/layouts/layout.d.ts";
import { setting } from "$lib/setting.js";
const { scale, margin, strokeWidth, fontSize, iconFontSize } = const { scale, margin, strokeWidth, fontSize, iconFontSize } =
getContext<VisualLayoutConfig>("visual-layout-config"); getContext<VisualLayoutConfig>("visual-layout-config");
@@ -120,9 +119,13 @@
} }
function edit(index: number) { function edit(index: number) {
const keyInfo = layoutInfo.keys[index]; const keyInfo =
layoutInfo.keys.find(({ id }) => id === index) ??
layoutInfo.fixedKeys.find(({ id }) => id === index);
if (!keyInfo) return; if (!keyInfo) return;
const clickedGroup = groupParent.children.item(index) as SVGGElement; const clickedGroup = groupParent.querySelector(
`g[data-id="${index}"]`,
) as SVGGElement;
const nextAction = const nextAction =
get(layout)[get(activeProfile)]![get(activeLayer)]?.[keyInfo.id]; get(layout)[get(activeProfile)]![get(activeLayer)]?.[keyInfo.id];
const currentAction = const currentAction =
@@ -190,7 +193,8 @@
} }
let focusKey: CompiledLayoutKey; let focusKey: CompiledLayoutKey;
let groupParent: SVGElement; let groupParent: SVGGElement;
let rotationTarget: SVGCircleElement;
let rotationSetting = $derived( let rotationSetting = $derived(
$deviceMeta?.settings $deviceMeta?.settings
.find((it) => it.name === "misc") .find((it) => it.name === "misc")
@@ -201,26 +205,43 @@
? ($settings[$activeProfile]?.[rotationSetting.id]?.value ?? 90) ? ($settings[$activeProfile]?.[rotationSetting.id]?.value ?? 90)
: 90, : 90,
); );
let flippedSetting = $derived(
$deviceMeta?.settings
.find((it) => it.name === "misc")
?.items.find((it) => it.name === "device orientation"),
);
let flipped = $derived(
flippedSetting
? $settings[$activeProfile]?.[flippedSetting.id]?.value !== 0
: false,
);
let draggingRotation = $state(90); let draggingRotation = $state(90);
let isDragging = $state(false); let isDragging = $state(false);
let rotation = $derived(isDragging ? draggingRotation : settingRotation); let rotation = $derived(isDragging ? draggingRotation : settingRotation);
let dragOffset = 0; let dragOffset = 0;
function calcDragOffset(event: MouseEvent) { function calcDragOffset(event: MouseEvent) {
const offset = groupParent.getBoundingClientRect(); const offset = rotationTarget.getBoundingClientRect();
const centerX = const cx = offset.x + offset.width / 2;
offset.x + const cy = offset.y + offset.height / 2;
(layoutInfo.rotationAnchor?.[0] ?? 0) * const a = Math.atan2(event.x - cx, event.y - cy) * (180 / Math.PI) + 90;
scale * return flipped ? (a + 180) % 360 : a;
(offset.width / (layoutInfo.size[0] * scale)); }
const centerY =
offset.y + function toggleFlip() {
(layoutInfo.rotationAnchor?.[1] ?? 0) * changes.update((changes) => {
scale * changes.push([
(offset.height / (layoutInfo.size[1] * scale)); {
return ( type: ChangeType.Setting,
Math.atan2(event.x - centerX, event.y - centerY) * (180 / Math.PI) + 90 id: flippedSetting!.id,
); setting: flipped ? 0 : 1,
profile: get(activeProfile),
},
]);
return changes;
});
} }
function dragRotation(event: MouseEvent) { function dragRotation(event: MouseEvent) {
@@ -261,27 +282,33 @@
<svg <svg
class="print" class="print"
viewBox="0 0 {layoutInfo.size[0] * scale} {layoutInfo.size[1] * scale}" viewBox="0 {flipped ? margin * -3 : 0} {layoutInfo.size[0] *
bind:this={groupParent} scale} {layoutInfo.size[1] * scale}"
transition:fly={{ y: 48, easing: expoOut }} transition:fly={{ y: 48, easing: expoOut }}
onmousemove={dragRotation} onmousemove={dragRotation}
onmouseup={dragDisable} onmouseup={dragDisable}
> >
<g
bind:this={groupParent}
transform="rotate({flipped ? 180 : 0})"
transform-origin="{(layoutInfo.rotationAnchor?.[0] ?? 0) *
scale} {(layoutInfo.size[1] * scale) / 2}"
>
<g <g
transform-origin="{(layoutInfo.rotationAnchor?.[0] ?? 0) * transform-origin="{(layoutInfo.rotationAnchor?.[0] ?? 0) *
scale} {(layoutInfo.rotationAnchor?.[1] ?? 0) * scale}" scale} {(layoutInfo.rotationAnchor?.[1] ?? 0) * scale}"
transform="rotate({-(rotation - 90)})" transform="rotate({-(rotation - 90)})"
class="group" class="group"
> >
{#each layoutInfo.keys as key, i} {#each layoutInfo.keys as key}
<KeyboardKey <KeyboardKey
{i}
{key} {key}
{flipped}
onfocusin={() => (focusKey = key)} onfocusin={() => (focusKey = key)}
onclick={() => edit(i)} onclick={() => edit(key.id)}
onkeypress={({ key }) => { onkeypress={(event) => {
if (key === "Enter") { if (event.key === "Enter") {
edit(i); edit(key.id);
} }
}} }}
/> />
@@ -291,6 +318,7 @@
role="button" role="button"
tabindex="-1" tabindex="-1"
onmousedown={dragEnable} onmousedown={dragEnable}
ondblclick={toggleFlip}
class="handle" class="handle"
x={(layoutInfo.size[0] * scale) / 2 - (0.5 * scale) / 2} x={(layoutInfo.size[0] * scale) / 2 - (0.5 * scale) / 2}
y={layoutInfo.size[1] * scale + margin - 0.05 * scale} y={layoutInfo.size[1] * scale + margin - 0.05 * scale}
@@ -302,32 +330,48 @@
stroke-width={strokeWidth} stroke-width={strokeWidth}
/> />
{#if isDragging} {#if isDragging}
{@const x = (layoutInfo.size[0] * scale) / 2}
{@const y = layoutInfo.size[1] * scale + margin * (flipped ? 2 : 3)}
<text <text
transition:fly={{ y: 2, easing: expoOut }} transition:fly={{ y: 2, easing: expoOut }}
transform={flipped ? "rotate(180)" : undefined}
transform-origin="{x} {y}"
class="handle-label" class="handle-label"
text-anchor="middle" text-anchor="middle"
font-size={fontSize} font-size={fontSize}
fill="currentColor" fill="currentColor"
x={(layoutInfo.size[0] * scale) / 2} {x}
y={layoutInfo.size[1] * scale + margin * 3}>{rotation - 90}°</text {y}>{rotation - 90}°</text
> >
{/if} {/if}
{/if} {/if}
</g> </g>
<circle
bind:this={rotationTarget}
cx={(layoutInfo.rotationAnchor?.[0] ?? 0) * scale}
cy={(layoutInfo.rotationAnchor?.[1] ?? 0) * scale}
r="0"
/>
{#each layoutInfo.fixedKeys as key, i} <g
transform="rotate({flipped ? 180 : 0})"
transform-origin="{(layoutInfo.rotationAnchor?.[0] ?? 0) *
scale} {(layoutInfo.rotationAnchor?.[1] ?? 0) * scale}"
>
{#each layoutInfo.fixedKeys as key}
<KeyboardKey <KeyboardKey
{i}
{key} {key}
onfocusin={() => (focusKey = key)} onfocusin={() => (focusKey = key)}
onclick={() => edit(i)} onclick={() => edit(key.id)}
onkeypress={({ key }) => { onkeypress={(event) => {
if (key === "Enter") { if (event.key === "Enter") {
edit(i); edit(key.id);
} }
}} }}
/> />
{/each} {/each}
</g>
</g>
</svg> </svg>
<style lang="scss"> <style lang="scss">

View File

@@ -17,14 +17,14 @@
const highlight = getContext<Writable<Set<number>> | undefined>("highlight"); const highlight = getContext<Writable<Set<number>> | undefined>("highlight");
let { let {
i,
key, key,
flipped = false,
onclick, onclick,
onkeypress, onkeypress,
onfocusin, onfocusin,
}: { }: {
i: number;
key: CompiledLayoutKey; key: CompiledLayoutKey;
flipped?: boolean;
onclick: MouseEventHandler<SVGGElement>; onclick: MouseEventHandler<SVGGElement>;
onkeypress: KeyboardEventHandler<SVGGElement>; onkeypress: KeyboardEventHandler<SVGGElement>;
onfocusin: FocusEventHandler<SVGGElement>; onfocusin: FocusEventHandler<SVGGElement>;
@@ -44,7 +44,10 @@
{onkeypress} {onkeypress}
{onfocusin} {onfocusin}
role="button" role="button"
tabindex={i + 1} tabindex={key.id + 1}
transform={flipped ? "rotate(180)" : undefined}
transform-origin="center"
data-id={key.id}
> >
{#if key.shape === "square"} {#if key.shape === "square"}
<rect <rect
@@ -122,6 +125,9 @@
path, path,
g { g {
transform-box: fill-box; transform-box: fill-box;
}
path {
transform-origin: top left; transform-origin: top left;
} }