4 Commits

Author SHA1 Message Date
72a8e084ce fix: plugins can't execute plugins 2024-07-16 15:21:34 +02:00
989e844190 fix: compound order 2024-07-11 13:40:31 +02:00
500221f39a feat: experimental support for compounds 2024-07-11 13:38:19 +02:00
Raymond Li
d91273d27b Update CONTRIBUTING.md 2024-07-10 00:22:40 +02:00
8 changed files with 93 additions and 26 deletions

View File

@@ -29,7 +29,7 @@ You may need to run through some additional setup to get Rust running inside Int
- Python >=3.10 - Python >=3.10
- Rust Stable (For Tauri Development) - Rust Stable (For Tauri Development)
I know, python in JS projects is extremely annoying, unfortunately, I know, python in JS projects is extremely annoying. Unfortunately,
it seems to be the only platform that offers a functional it seems to be the only platform that offers a functional
way to subset variable woff2 fonts with ligatures. way to subset variable woff2 fonts with ligatures.

View File

@@ -104,6 +104,26 @@ actions:
<<: *tertiary_keymap <<: *tertiary_keymap
id: "KM_3_R" id: "KM_3_R"
variant: right variant: right
558:
id: HOLD_COMPOUND
title: Activate Chord Library
icon: layers
description: |
When used in a chord includes that chord as a base
compound chord for all subsequent chords.
This is effectively a library switch.
Since library activations can be nested, you
usually add a "Reset Chord Library" before this action.
559:
id: RELEASE_COMPOUND
title: Reset Chord Library
icon: layers_clear
description: |
Releases the active compound state, returning
to the default library.
While "Activate Chord Library" can only be used
as an output of a chord, this action can be assigned
to switches directly.
576: 576:
id: ACTION_DELAY_1000 id: ACTION_DELAY_1000
icon: clock_loader_90 icon: clock_loader_90

View File

@@ -55,3 +55,19 @@ export function deserializeActions(native: bigint): number[] {
return actions; return actions;
} }
/**
* Hashes a chord input the same way as CCOS
*/
export function hashChord(actions: number[]) {
const chord = new Uint8Array(16);
const view = new DataView(chord.buffer);
const serialized = serializeActions(actions);
view.setBigUint64(0, serialized & 0xffff_ffff_ffff_ffffn, true);
view.setBigUint64(8, serialized >> 64n, true);
let hash = 2166136261;
for (let i = 0; i < 16; i++) {
hash = Math.imul(hash ^ view.getUint8(i), 16777619);
}
return hash & 0x3fff_ffff;
}

View File

@@ -1,6 +1,6 @@
import { persistentWritable } from "$lib/storage"; import { persistentWritable } from "$lib/storage";
import { derived } from "svelte/store"; import { derived } from "svelte/store";
import type { Chord } from "$lib/serial/chord"; import { hashChord, type Chord } from "$lib/serial/chord";
import { import {
deviceChords, deviceChords,
deviceLayout, deviceLayout,
@@ -158,3 +158,9 @@ export const chords = derived([overlay, deviceChords], ([overlay, chords]) => {
a.localeCompare(b), a.localeCompare(b),
); );
}); });
export const chordHashes = derived(
chords,
(chords) =>
new Map(chords.map((chord) => [hashChord(chord.actions), chord] as const)),
);

View File

@@ -144,7 +144,6 @@
progress = i; progress = i;
if ("phrase" in chord) { if ("phrase" in chord) {
console.log(encodeChord(chord, osLayout));
await index.addAsync(i, encodeChord(chord, osLayout)); await index.addAsync(i, encodeChord(chord, osLayout));
} }
} }

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { ChordInfo } from "$lib/undo-redo"; import type { ChordInfo } from "$lib/undo-redo";
import { changes, ChangeType } from "$lib/undo-redo"; import { changes, chordHashes, ChangeType } from "$lib/undo-redo";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import LL from "$i18n/i18n-svelte"; import LL from "$i18n/i18n-svelte";
import ActionString from "$lib/components/ActionString.svelte"; import ActionString from "$lib/components/ActionString.svelte";
@@ -8,6 +8,7 @@
import { serialPort } from "$lib/serial/connection"; import { serialPort } from "$lib/serial/connection";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { inputToAction } from "./input-converter"; import { inputToAction } from "./input-converter";
import { hashChord } from "$lib/serial/chord";
export let chord: ChordInfo | undefined = undefined; export let chord: ChordInfo | undefined = undefined;
@@ -21,14 +22,15 @@
} }
function makeChordInput(...actions: number[]) { function makeChordInput(...actions: number[]) {
const compound = compoundIndices ?? []; const compound = compoundInputs[0]
? hashChord(compoundInputs[0].actions)
: 0;
return [ return [
...compound,
...Array.from( ...Array.from(
{ {
length: 12 - (compound.length + actions.length + 1), length: 12 - actions.length,
}, },
() => 0, (_, i) => (compound >> (i * 10)) & 0x3ff,
), ),
...actions.toSorted(compare), ...actions.toSorted(compare),
]; ];
@@ -73,7 +75,6 @@
function addSpecial(event: MouseEvent) { function addSpecial(event: MouseEvent) {
selectAction(event, (action) => { selectAction(event, (action) => {
changes.update((changes) => { changes.update((changes) => {
console.log(compoundIndices, chordActions, action);
changes.push({ changes.push({
type: ChangeType.Chord, type: ChangeType.Chord,
id: chord!.id, id: chord!.id,
@@ -85,10 +86,30 @@
}); });
} }
function* resolveCompound(chord?: ChordInfo) {
if (!chord) return;
let current = chord;
for (let i = 0; i < 10; i++) {
if (current.actions[3] !== 0) return;
const compound = current.actions
.slice(0, 3)
.reduce((a, b, i) => a | (b << (i * 10)));
if (compound === 0) return;
const next = $chordHashes.get(compound);
if (!next) {
return null;
}
current = next;
yield next;
}
return;
}
$: chordActions = chord?.actions $: chordActions = chord?.actions
.slice(chord.actions.lastIndexOf(0) + 1) .slice(chord.actions.lastIndexOf(0) + 1)
.toSorted(compare); .toSorted(compare);
$: compoundIndices = chord?.actions.slice(0, chord.actions.indexOf(0)); $: compoundInputs = [...resolveCompound(chord)].reverse();
</script> </script>
<button <button
@@ -110,12 +131,15 @@
<span>{$LL.configure.chords.NEW_CHORD()}</span> <span>{$LL.configure.chords.NEW_CHORD()}</span>
{/if} {/if}
{#if !editing} {#if !editing}
{#each compoundIndices ?? [] as index} {#each compoundInputs as compound}
<sub>{index}</sub> <sub
{/each} ><ActionString
{#if compoundIndices?.length} display="keys"
actions={compound.actions.slice(compound.actions.lastIndexOf(0) + 1)}
></ActionString>
</sub>
<span>&rarr;</span> <span>&rarr;</span>
{/if} {/each}
{/if} {/if}
<ActionString <ActionString
display="keys" display="keys"

View File

@@ -137,9 +137,6 @@
if (parent) { if (parent) {
} }
} }
console.log(nodeBefore);
console.log(context);
return null; return null;
}, },
}); });

View File

@@ -5,6 +5,8 @@
let resolveRequest: ((data: unknown) => void) | undefined = undefined; let resolveRequest: ((data: unknown) => void) | undefined = undefined;
let source: MessageEventSource | undefined = undefined; let source: MessageEventSource | undefined = undefined;
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
async function post(channel: string, args: unknown[]) { async function post(channel: string, args: unknown[]) {
while (ongoingRequest) { while (ongoingRequest) {
await ongoingRequest; await ongoingRequest;
@@ -35,15 +37,18 @@
), ),
); );
new Function("Action", "Chara", event.data.script)( const Chara = Object.fromEntries(
Action, event.data.charaChannels.map((name) => [
Object.fromEntries( name,
event.data.charaChannels.map((name) => [ (...args: unknown[]) => post(name, args),
name, ]),
(...args: unknown[]) => post(name, args),
]),
),
); );
AsyncFunction(
"Action",
"Chara",
'"use strict"\n' + event.data.script,
)(Action, Chara);
} }
} }
</script> </script>