mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-04-19 12:48:55 +00:00
feat: T4G device rotation
This commit is contained in:
2
src/lib/assets/layouts/layout.d.ts
vendored
2
src/lib/assets/layouts/layout.d.ts
vendored
@@ -2,6 +2,8 @@ export interface CompiledLayout {
|
|||||||
name: string;
|
name: string;
|
||||||
size: [number, number];
|
size: [number, number];
|
||||||
keys: CompiledLayoutKey[];
|
keys: CompiledLayoutKey[];
|
||||||
|
fixedKeys: CompiledLayoutKey[];
|
||||||
|
rotationAnchor?: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompiledLayoutKey {
|
export interface CompiledLayoutKey {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ name: T4G
|
|||||||
col:
|
col:
|
||||||
- row:
|
- row:
|
||||||
- switch: { e: 3, n: 5, w: 4, s: 6 }
|
- switch: { e: 3, n: 5, w: 4, s: 6 }
|
||||||
|
rotationAnchor: true
|
||||||
- offset: [0.5, 0]
|
- offset: [0.5, 0]
|
||||||
row:
|
row:
|
||||||
- key: 2
|
- key: 2
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { deviceLayout } from "$lib/serial/connection";
|
import { deviceLayout, deviceMeta } from "$lib/serial/connection";
|
||||||
import { dev } from "$app/environment";
|
import { dev } from "$app/environment";
|
||||||
import ActionSelector from "$lib/components/layout/ActionSelector.svelte";
|
import ActionSelector from "$lib/components/layout/ActionSelector.svelte";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import KeyboardKey from "$lib/components/layout/KeyboardKey.svelte";
|
import KeyboardKey from "$lib/components/layout/KeyboardKey.svelte";
|
||||||
import { getContext, mount, unmount } from "svelte";
|
import { getContext, mount, unmount } from "svelte";
|
||||||
import type { VisualLayoutConfig } from "./visual-layout.js";
|
import type { VisualLayoutConfig } from "./visual-layout.js";
|
||||||
import { changes, ChangeType, layout } from "$lib/undo-redo";
|
import { changes, ChangeType, layout, settings } from "$lib/undo-redo";
|
||||||
import { fly } from "svelte/transition";
|
import { fly } from "svelte/transition";
|
||||||
import { expoOut } from "svelte/easing";
|
import { expoOut } from "svelte/easing";
|
||||||
import { activeLayer, activeProfile } from "$lib/serial/connection";
|
import { activeLayer, activeProfile } from "$lib/serial/connection";
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
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");
|
||||||
@@ -190,6 +191,70 @@
|
|||||||
|
|
||||||
let focusKey: CompiledLayoutKey;
|
let focusKey: CompiledLayoutKey;
|
||||||
let groupParent: SVGElement;
|
let groupParent: SVGElement;
|
||||||
|
let rotationSetting = $derived(
|
||||||
|
$deviceMeta?.settings
|
||||||
|
.find((it) => it.name === "misc")
|
||||||
|
?.items.find((it) => it.name === "device rotation"),
|
||||||
|
);
|
||||||
|
let settingRotation = $derived(
|
||||||
|
rotationSetting
|
||||||
|
? ($settings[$activeProfile]?.[rotationSetting.id]?.value ?? 90)
|
||||||
|
: 90,
|
||||||
|
);
|
||||||
|
let draggingRotation = $state(90);
|
||||||
|
let isDragging = $state(false);
|
||||||
|
let rotation = $derived(isDragging ? draggingRotation : settingRotation);
|
||||||
|
let dragOffset = 0;
|
||||||
|
|
||||||
|
function calcDragOffset(event: MouseEvent) {
|
||||||
|
const offset = groupParent.getBoundingClientRect();
|
||||||
|
const centerX =
|
||||||
|
offset.x +
|
||||||
|
(layoutInfo.rotationAnchor?.[0] ?? 0) *
|
||||||
|
scale *
|
||||||
|
(offset.width / (layoutInfo.size[0] * scale));
|
||||||
|
const centerY =
|
||||||
|
offset.y +
|
||||||
|
(layoutInfo.rotationAnchor?.[1] ?? 0) *
|
||||||
|
scale *
|
||||||
|
(offset.height / (layoutInfo.size[1] * scale));
|
||||||
|
return (
|
||||||
|
Math.atan2(event.x - centerX, event.y - centerY) * (180 / Math.PI) + 90
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragRotation(event: MouseEvent) {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const value = Math.min(
|
||||||
|
180,
|
||||||
|
Math.max(0, Math.round(calcDragOffset(event) - dragOffset)),
|
||||||
|
);
|
||||||
|
if (draggingRotation !== value) {
|
||||||
|
draggingRotation = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragEnable(event: MouseEvent) {
|
||||||
|
dragOffset = calcDragOffset(event) - rotation;
|
||||||
|
draggingRotation = rotation;
|
||||||
|
isDragging = true;
|
||||||
|
}
|
||||||
|
function dragDisable() {
|
||||||
|
isDragging = false;
|
||||||
|
if (settingRotation !== draggingRotation) {
|
||||||
|
changes.update((changes) => {
|
||||||
|
changes.push([
|
||||||
|
{
|
||||||
|
type: ChangeType.Setting,
|
||||||
|
id: rotationSetting!.id,
|
||||||
|
setting: draggingRotation,
|
||||||
|
profile: get(activeProfile),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return changes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={navigate} />
|
<svelte:window on:keydown={navigate} />
|
||||||
@@ -199,6 +264,14 @@
|
|||||||
viewBox="0 0 {layoutInfo.size[0] * scale} {layoutInfo.size[1] * scale}"
|
viewBox="0 0 {layoutInfo.size[0] * scale} {layoutInfo.size[1] * scale}"
|
||||||
bind:this={groupParent}
|
bind:this={groupParent}
|
||||||
transition:fly={{ y: 48, easing: expoOut }}
|
transition:fly={{ y: 48, easing: expoOut }}
|
||||||
|
onmousemove={dragRotation}
|
||||||
|
onmouseup={dragDisable}
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
transform-origin="{(layoutInfo.rotationAnchor?.[0] ?? 0) *
|
||||||
|
scale} {(layoutInfo.rotationAnchor?.[1] ?? 0) * scale}"
|
||||||
|
transform="rotate({-(rotation - 90)})"
|
||||||
|
class="group"
|
||||||
>
|
>
|
||||||
{#each layoutInfo.keys as key, i}
|
{#each layoutInfo.keys as key, i}
|
||||||
<KeyboardKey
|
<KeyboardKey
|
||||||
@@ -213,6 +286,48 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if rotationSetting}
|
||||||
|
<rect
|
||||||
|
role="button"
|
||||||
|
tabindex="-1"
|
||||||
|
onmousedown={dragEnable}
|
||||||
|
class="handle"
|
||||||
|
x={(layoutInfo.size[0] * scale) / 2 - (0.5 * scale) / 2}
|
||||||
|
y={layoutInfo.size[1] * scale + margin - 0.05 * scale}
|
||||||
|
width={0.5 * scale}
|
||||||
|
height={0.05 * scale}
|
||||||
|
ry={0.025 * scale}
|
||||||
|
fill="currentColor"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
/>
|
||||||
|
{#if isDragging}
|
||||||
|
<text
|
||||||
|
transition:fly={{ y: 2, easing: expoOut }}
|
||||||
|
class="handle-label"
|
||||||
|
text-anchor="middle"
|
||||||
|
font-size={fontSize}
|
||||||
|
fill="currentColor"
|
||||||
|
x={(layoutInfo.size[0] * scale) / 2}
|
||||||
|
y={layoutInfo.size[1] * scale + margin * 3}>{rotation - 90}°</text
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{#each layoutInfo.fixedKeys as key, i}
|
||||||
|
<KeyboardKey
|
||||||
|
{i}
|
||||||
|
{key}
|
||||||
|
onfocusin={() => (focusKey = key)}
|
||||||
|
onclick={() => edit(i)}
|
||||||
|
onkeypress={({ key }) => {
|
||||||
|
if (key === "Enter") {
|
||||||
|
edit(i);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -222,4 +337,27 @@
|
|||||||
max-height: calc(100% - 170px);
|
max-height: calc(100% - 170px);
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
opacity: 0.3;
|
||||||
|
transition: opacity 0.1s;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle:active {
|
||||||
|
opacity: 1;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle-label {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface VisualLayoutSwitch extends Positionable {
|
|||||||
s: number;
|
s: number;
|
||||||
d: number;
|
d: number;
|
||||||
};
|
};
|
||||||
|
rotationAnchor?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileRegex = /\.(layout\.yml)$/;
|
const fileRegex = /\.(layout\.yml)$/;
|
||||||
@@ -53,6 +54,7 @@ export function compileLayout(layout: VisualLayout): CompiledLayout {
|
|||||||
name: layout.name,
|
name: layout.name,
|
||||||
size: [0, 0],
|
size: [0, 0],
|
||||||
keys: [],
|
keys: [],
|
||||||
|
fixedKeys: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
let y = 0;
|
let y = 0;
|
||||||
@@ -80,13 +82,16 @@ export function compileLayout(layout: VisualLayout): CompiledLayout {
|
|||||||
} else if ("switch" in info) {
|
} else if ("switch" in info) {
|
||||||
const cx = x + ox + 1;
|
const cx = x + ox + 1;
|
||||||
const cy = y + oy + 1;
|
const cy = y + oy + 1;
|
||||||
|
if (info.rotationAnchor) {
|
||||||
|
compiled.rotationAnchor = [cx, cy];
|
||||||
|
}
|
||||||
for (const [i, id] of [
|
for (const [i, id] of [
|
||||||
info.switch.s,
|
info.switch.s,
|
||||||
info.switch.w,
|
info.switch.w,
|
||||||
info.switch.n,
|
info.switch.n,
|
||||||
info.switch.e,
|
info.switch.e,
|
||||||
].entries()) {
|
].entries()) {
|
||||||
compiled.keys.push({
|
(info.rotationAnchor ? compiled.fixedKeys : compiled.keys).push({
|
||||||
id,
|
id,
|
||||||
shape: "quarter-circle",
|
shape: "quarter-circle",
|
||||||
cornerRadius: 0,
|
cornerRadius: 0,
|
||||||
@@ -96,7 +101,7 @@ export function compileLayout(layout: VisualLayout): CompiledLayout {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (info.switch.d !== undefined) {
|
if (info.switch.d !== undefined) {
|
||||||
compiled.keys.push({
|
(info.rotationAnchor ? compiled.fixedKeys : compiled.keys).push({
|
||||||
id: info.switch.d,
|
id: info.switch.d,
|
||||||
shape: "square",
|
shape: "square",
|
||||||
cornerRadius: 0.5,
|
cornerRadius: 0.5,
|
||||||
|
|||||||
Reference in New Issue
Block a user