From 81f388abf150ec911d4dc24394f2456992c9b3ca Mon Sep 17 00:00:00 2001 From: BafS Date: Fri, 11 Apr 2025 01:08:28 +0200 Subject: [PATCH 01/12] Show Layer-Tap information on keys --- src-tauri/Cargo.lock | 2 +- src/AppFooter.tsx | 2 +- src/behaviors/BehaviorBindingPicker.tsx | 2 +- src/keyboard/HidUsageLabel.tsx | 2 +- src/keyboard/Keyboard.tsx | 8 ++--- src/keyboard/Keymap.tsx | 42 ++++++++++++++++++++++--- src/keyboard/PhysicalLayout.tsx | 4 +-- 7 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 91fbadf2..63959285 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -113,7 +113,7 @@ checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "app" -version = "0.1.0" +version = "0.3.1" dependencies = [ "async-std", "blocking", diff --git a/src/AppFooter.tsx b/src/AppFooter.tsx index e0d9af43..9bf2dd7c 100644 --- a/src/AppFooter.tsx +++ b/src/AppFooter.tsx @@ -8,7 +8,7 @@ export const AppFooter = ({ onShowLicenseNotice, }: AppFooterProps) => { return ( -
+
© 2024 - The ZMK Contributors -{" "} diff --git a/src/behaviors/BehaviorBindingPicker.tsx b/src/behaviors/BehaviorBindingPicker.tsx index 8c3f54e7..d5062fbc 100644 --- a/src/behaviors/BehaviorBindingPicker.tsx +++ b/src/behaviors/BehaviorBindingPicker.tsx @@ -28,7 +28,7 @@ function validateBinding( return true; } - let matchingSet = metadata.find((s) => + const matchingSet = metadata.find((s) => validateValue(layerIds, param1, s.param1) ); diff --git a/src/keyboard/HidUsageLabel.tsx b/src/keyboard/HidUsageLabel.tsx index aa15195c..e28c9172 100644 --- a/src/keyboard/HidUsageLabel.tsx +++ b/src/keyboard/HidUsageLabel.tsx @@ -17,7 +17,7 @@ export const HidUsageLabel = ({ hid_usage }: HidUsageLabelProps) => { // TODO: Do something with implicit mods! page &= 0xff; - let labels = hid_usage_get_labels(page, id); + const labels = hid_usage_get_labels(page, id); return ( ; function useBehaviors(): BehaviorMap { - let connection = useContext(ConnectionContext); - let lockState = useContext(LockStateContext); + const connection = useContext(ConnectionContext); + const lockState = useContext(LockStateContext); const [behaviors, setBehaviors] = useState({}); @@ -105,8 +105,8 @@ function useLayouts(): [ number, React.Dispatch> ] { - let connection = useContext(ConnectionContext); - let lockState = useContext(LockStateContext); + const connection = useContext(ConnectionContext); + const lockState = useContext(LockStateContext); const [layouts, setLayouts] = useState( undefined diff --git a/src/keyboard/Keymap.tsx b/src/keyboard/Keymap.tsx index a23563d6..a4d4cbd9 100644 --- a/src/keyboard/Keymap.tsx +++ b/src/keyboard/Keymap.tsx @@ -22,6 +22,36 @@ export interface KeymapProps { onKeyPositionClicked: (keyPosition: number) => void; } +const getBindingChildren = ( + header: string, + binding: { param1: number; param2: number }, + keymap: KeymapMsg +) => { + if (header === "Layer-Tap") { + return ( + + + + + + + + + {keymap.layers[binding.param1].name || "Unknown"} + + + ); + } + + return ( + + ); +}; + export const Keymap = ({ layout, keymap, @@ -48,11 +78,17 @@ export const Keymap = ({ }; } + const binding = keymap.layers[selectedLayerIndex].bindings[i]; + const header = behaviors[binding.behaviorId]?.displayName || "Unknown"; + + const children = getBindingChildren(header, binding, keymap); + return { id: `${keymap.layers[selectedLayerIndex].id}-${i}`, header: behaviors[keymap.layers[selectedLayerIndex].bindings[i].behaviorId] ?.displayName || "Unknown", + header: header, x: k.x / 100.0, y: k.y / 100.0, width: k.width / 100, @@ -60,11 +96,7 @@ export const Keymap = ({ r: (k.r || 0) / 100.0, rx: (k.rx || 0) / 100.0, ry: (k.ry || 0) / 100.0, - children: ( - - ), + children: children, }; }); diff --git a/src/keyboard/PhysicalLayout.tsx b/src/keyboard/PhysicalLayout.tsx index db8719c7..2df858b7 100644 --- a/src/keyboard/PhysicalLayout.tsx +++ b/src/keyboard/PhysicalLayout.tsx @@ -116,10 +116,10 @@ export const PhysicalLayout = ({ }, [props.zoom]); // TODO: Add a bit of padding for rotation when supported - let rightMost = positions + const rightMost = positions .map((k) => k.x + k.width) .reduce((a, b) => Math.max(a, b), 0); - let bottomMost = positions + const bottomMost = positions .map((k) => k.y + k.height) .reduce((a, b) => Math.max(a, b), 0); From b6abd807268b0e9a3353941193eca4f832aa15b2 Mon Sep 17 00:00:00 2001 From: BafS Date: Fri, 11 Apr 2025 18:34:12 +0200 Subject: [PATCH 02/12] Update design, make it work with mod-tap too --- src/keyboard/Keymap.tsx | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/keyboard/Keymap.tsx b/src/keyboard/Keymap.tsx index a4d4cbd9..8889b33e 100644 --- a/src/keyboard/Keymap.tsx +++ b/src/keyboard/Keymap.tsx @@ -25,30 +25,44 @@ export interface KeymapProps { const getBindingChildren = ( header: string, binding: { param1: number; param2: number }, - keymap: KeymapMsg ) => { if (header === "Layer-Tap") { - return ( - + return [ - - - + , + + - {keymap.layers[binding.param1].name || "Unknown"} + {binding.param1} + + ]; + } + + if (header === "Mod-Tap") { + return [ + + + , + + - - ); + ]; } return ( - + + + ); }; @@ -81,7 +95,7 @@ export const Keymap = ({ const binding = keymap.layers[selectedLayerIndex].bindings[i]; const header = behaviors[binding.behaviorId]?.displayName || "Unknown"; - const children = getBindingChildren(header, binding, keymap); + const children = getBindingChildren(header, binding); return { id: `${keymap.layers[selectedLayerIndex].id}-${i}`, From 85604acaa54f3729bd088416fafd899b326ba193 Mon Sep 17 00:00:00 2001 From: BafS Date: Fri, 11 Apr 2025 20:46:02 +0200 Subject: [PATCH 03/12] Add indicators --- src/keyboard/Keymap.tsx | 46 +----------------- src/keyboard/KeymapBindingChildren.tsx | 67 ++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 45 deletions(-) create mode 100644 src/keyboard/KeymapBindingChildren.tsx diff --git a/src/keyboard/Keymap.tsx b/src/keyboard/Keymap.tsx index 8889b33e..a1a08a85 100644 --- a/src/keyboard/Keymap.tsx +++ b/src/keyboard/Keymap.tsx @@ -8,7 +8,7 @@ import { LayoutZoom, PhysicalLayout as PhysicalLayoutComp, } from "./PhysicalLayout"; -import { HidUsageLabel } from "./HidUsageLabel"; +import { getBindingChildren } from "./KeymapBindingChildren"; type BehaviorMap = Record; @@ -22,50 +22,6 @@ export interface KeymapProps { onKeyPositionClicked: (keyPosition: number) => void; } -const getBindingChildren = ( - header: string, - binding: { param1: number; param2: number }, -) => { - if (header === "Layer-Tap") { - return [ - - - , - - - - - {binding.param1} - - ]; - } - - if (header === "Mod-Tap") { - return [ - - - , - - - - ]; - } - - return ( - - - - ); -}; - export const Keymap = ({ layout, keymap, diff --git a/src/keyboard/KeymapBindingChildren.tsx b/src/keyboard/KeymapBindingChildren.tsx new file mode 100644 index 00000000..6f4e71ff --- /dev/null +++ b/src/keyboard/KeymapBindingChildren.tsx @@ -0,0 +1,67 @@ +import { HidUsageLabel } from "./HidUsageLabel"; + +export interface KeyBinding { + param1: number; + param2: number; +} + +const renderIndicator = (numbers: 0|1|2|3 = 1): JSX.Element => ( +
+ {Array.from({ length: Math.max(1, numbers) }, (_, i) => ( +
+ ))} +
+ ); + +export const getBindingChildren = ( + header: string, + binding: KeyBinding, +): JSX.Element | JSX.Element[] => { + if (header === "Layer-Tap") { + return [ +
+ {renderIndicator(1)} + +
, +
+ {renderIndicator(0)} + + + + {binding.param1} +
+ ]; + } + + if (header === "Mod-Tap") { + return [ +
+ {renderIndicator(1)} + + + +
, +
+ {renderIndicator(0)} + +
+ ]; + } + + return ( +
+ + + +
+ ); +}; \ No newline at end of file From 5503f7cc6b15f21a97eeb003d4fd17ed19e1e7e8 Mon Sep 17 00:00:00 2001 From: BafS Date: Sat, 12 Apr 2025 20:59:21 +0200 Subject: [PATCH 04/12] Handle "transparent" behavior --- src-tauri/src/transport/commands.rs | 2 +- src/keyboard/Keymap.tsx | 3 --- src/keyboard/KeymapBindingChildren.tsx | 22 +++++++++++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src-tauri/src/transport/commands.rs b/src-tauri/src/transport/commands.rs index 8b638535..b6027387 100644 --- a/src-tauri/src/transport/commands.rs +++ b/src-tauri/src/transport/commands.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use tauri::ipc::InvokeBody; use tauri::{ command, - ipc::{Request}, + ipc::Request, State, }; diff --git a/src/keyboard/Keymap.tsx b/src/keyboard/Keymap.tsx index a1a08a85..28b0c79a 100644 --- a/src/keyboard/Keymap.tsx +++ b/src/keyboard/Keymap.tsx @@ -55,9 +55,6 @@ export const Keymap = ({ return { id: `${keymap.layers[selectedLayerIndex].id}-${i}`, - header: - behaviors[keymap.layers[selectedLayerIndex].bindings[i].behaviorId] - ?.displayName || "Unknown", header: header, x: k.x / 100.0, y: k.y / 100.0, diff --git a/src/keyboard/KeymapBindingChildren.tsx b/src/keyboard/KeymapBindingChildren.tsx index 6f4e71ff..f5fff319 100644 --- a/src/keyboard/KeymapBindingChildren.tsx +++ b/src/keyboard/KeymapBindingChildren.tsx @@ -21,10 +21,10 @@ const renderIndicator = (numbers: 0|1|2|3 = 1): JSX.Element => ( ); export const getBindingChildren = ( - header: string, + behavior: string, binding: KeyBinding, ): JSX.Element | JSX.Element[] => { - if (header === "Layer-Tap") { + if (behavior === "Layer-Tap") { return [
{renderIndicator(1)} @@ -40,7 +40,7 @@ export const getBindingChildren = ( ]; } - if (header === "Mod-Tap") { + if (behavior === "Mod-Tap") { return [
{renderIndicator(1)} @@ -55,13 +55,17 @@ export const getBindingChildren = ( ]; } + if (behavior === "Transparent") { + return ( +
+ ▽ +
+ ); + } + return ( -
- - - +
+
); }; \ No newline at end of file From 0246fbac2adf35428d237b0378dd4cf37f305e1d Mon Sep 17 00:00:00 2001 From: BafS Date: Mon, 23 Jun 2025 22:43:31 +0200 Subject: [PATCH 05/12] Change logic to not depend on behavior names --- src/keyboard/Keymap.tsx | 8 +- src/keyboard/KeymapBindingChildren.tsx | 128 ++++++++++++++++++++----- 2 files changed, 109 insertions(+), 27 deletions(-) diff --git a/src/keyboard/Keymap.tsx b/src/keyboard/Keymap.tsx index 28b0c79a..2f4656e6 100644 --- a/src/keyboard/Keymap.tsx +++ b/src/keyboard/Keymap.tsx @@ -49,9 +49,13 @@ export const Keymap = ({ } const binding = keymap.layers[selectedLayerIndex].bindings[i]; - const header = behaviors[binding.behaviorId]?.displayName || "Unknown"; + const behavior = behaviors[binding.behaviorId]; + const header = behavior?.displayName || "Unknown"; - const children = getBindingChildren(header, binding); + // Get layer IDs for metadata-driven rendering + const layerIds = keymap.layers.map(layer => layer.id); + + const children = getBindingChildren(behavior, binding, layerIds); return { id: `${keymap.layers[selectedLayerIndex].id}-${i}`, diff --git a/src/keyboard/KeymapBindingChildren.tsx b/src/keyboard/KeymapBindingChildren.tsx index f5fff319..5cd6f249 100644 --- a/src/keyboard/KeymapBindingChildren.tsx +++ b/src/keyboard/KeymapBindingChildren.tsx @@ -1,4 +1,6 @@ import { HidUsageLabel } from "./HidUsageLabel"; +import { GetBehaviorDetailsResponse, BehaviorBindingParametersSet } from "@zmkfirmware/zmk-studio-ts-client/behaviors"; +import { validateValue } from "../behaviors/parameters"; export interface KeyBinding { param1: number; @@ -20,47 +22,123 @@ const renderIndicator = (numbers: 0|1|2|3 = 1): JSX.Element => (
); +// Helper function to check if a parameter is a HID usage +function isHidUsageParameter( + value: number, + metadata: BehaviorBindingParametersSet[], + layerIds: number[] +): boolean { + if (value === 0) return false; + + // Check if any parameter set has this value as a HID usage + return metadata.some(set => + set.param1.some(param => + param.hidUsage && validateValue(layerIds, value, [param]) + ) || + set.param2.some(param => + param.hidUsage && validateValue(layerIds, value, [param]) + ) + ); +} + +// Helper function to check if a parameter is a layer ID +function isLayerIdParameter( + value: number, + metadata: BehaviorBindingParametersSet[], + layerIds: number[] +): boolean { + if (value === 0) return false; + + // Check if any parameter set has this value as a layer ID + return metadata.some(set => + set.param1.some(param => + param.layerId && validateValue(layerIds, value, [param]) + ) || + set.param2.some(param => + param.layerId && validateValue(layerIds, value, [param]) + ) + ); +} + export const getBindingChildren = ( - behavior: string, + behavior: GetBehaviorDetailsResponse | undefined, binding: KeyBinding, + layerIds: number[] = [] ): JSX.Element | JSX.Element[] => { - if (behavior === "Layer-Tap") { - return [ + if (!behavior || !behavior.metadata) { + if (binding.param1 !== 0 && binding.param2 !== 0) { + return [
- {renderIndicator(1)}
,
- {renderIndicator(0)} - - - - {binding.param1} + +
+ ]; + } + + if (binding.param1 === 0 && binding.param2 === 0) { + return ( +
+ ▽
+ ); + } + + return ( +
+ +
+ ); + } + + // Check if both parameters are HID usages (like Mod-Tap, Layer-Tap, etc.) + const param1IsHidUsage = isHidUsageParameter(binding.param1, behavior.metadata, layerIds); + const param2IsHidUsage = isHidUsageParameter(binding.param2, behavior.metadata, layerIds); + const param1IsLayerId = isLayerIdParameter(binding.param1, behavior.metadata, layerIds); + const param2IsLayerId = isLayerIdParameter(binding.param2, behavior.metadata, layerIds); + + // If both parameters are HID usages, render with indicators (Mod-Tap style) + if (param1IsHidUsage && param2IsHidUsage) { + return [ +
+ {renderIndicator(1)} + +
, +
+ {renderIndicator(0)} + +
]; } - if (behavior === "Mod-Tap") { + // If param1 is a layer ID and param2 is a HID usage (Layer-Tap style) + if (param1IsLayerId && param2IsHidUsage) { return [ -
- {renderIndicator(1)} - - - -
, -
- {renderIndicator(0)} - -
+
+ {renderIndicator(1)} + +
, +
+ {renderIndicator(0)} + + + + {binding.param1} +
]; } - if (behavior === "Transparent") { - return ( -
- ▽ + // If both parameters are non-zero but not the special cases above + if (binding.param1 !== 0 && binding.param2 !== 0) { + return [ +
+ +
, +
+
- ); + ]; } return ( From 7ad3b9d78ad5476a402123f324733a3052d4a58d Mon Sep 17 00:00:00 2001 From: BafS Date: Mon, 17 Nov 2025 23:24:42 +0100 Subject: [PATCH 06/12] Simplify the key visual, use logic from BehaviorBindingPicker (WIP) --- src/behaviors/BehaviorBindingPicker.tsx | 27 +--- src/behaviors/behaviorBindingUtils.tsx | 91 ++++++++++++++ src/keyboard/Keymap.tsx | 6 +- src/keyboard/KeymapBindingChildren.tsx | 160 +++++++----------------- 4 files changed, 140 insertions(+), 144 deletions(-) create mode 100644 src/behaviors/behaviorBindingUtils.tsx diff --git a/src/behaviors/BehaviorBindingPicker.tsx b/src/behaviors/BehaviorBindingPicker.tsx index d5062fbc..00d2eab4 100644 --- a/src/behaviors/BehaviorBindingPicker.tsx +++ b/src/behaviors/BehaviorBindingPicker.tsx @@ -2,11 +2,10 @@ import { useEffect, useMemo, useState } from "react"; import { GetBehaviorDetailsResponse, - BehaviorBindingParametersSet, } from "@zmkfirmware/zmk-studio-ts-client/behaviors"; import { BehaviorBinding } from "@zmkfirmware/zmk-studio-ts-client/keymap"; import { BehaviorParametersPicker } from "./BehaviorParametersPicker"; -import { validateValue } from "./parameters"; +import { validateBinding } from "./behaviorBindingUtils"; export interface BehaviorBindingPickerProps { binding: BehaviorBinding; @@ -15,30 +14,6 @@ export interface BehaviorBindingPickerProps { onBindingChanged: (binding: BehaviorBinding) => void; } -function validateBinding( - metadata: BehaviorBindingParametersSet[], - layerIds: number[], - param1?: number, - param2?: number -): boolean { - if ( - (param1 === undefined || param1 === 0) && - metadata.every((s) => !s.param1 || s.param1.length === 0) - ) { - return true; - } - - const matchingSet = metadata.find((s) => - validateValue(layerIds, param1, s.param1) - ); - - if (!matchingSet) { - return false; - } - - return validateValue(layerIds, param2, matchingSet.param2); -} - export const BehaviorBindingPicker = ({ binding, layers, diff --git a/src/behaviors/behaviorBindingUtils.tsx b/src/behaviors/behaviorBindingUtils.tsx new file mode 100644 index 00000000..b7307586 --- /dev/null +++ b/src/behaviors/behaviorBindingUtils.tsx @@ -0,0 +1,91 @@ +import { HidUsageLabel } from "../keyboard/HidUsageLabel"; +import { BehaviorBindingParametersSet, BehaviorParameterValueDescription } from "@zmkfirmware/zmk-studio-ts-client/behaviors"; +import { validateValue } from "./parameters"; + +/** + * Find the matching parameter set based on param1 value. + * This is critical for determining param2 type, as param2's type can depend on param1's value. + */ +export function findMatchingParameterSet( + param1: number | undefined, + metadata: BehaviorBindingParametersSet[], + layerIds: number[] +): BehaviorBindingParametersSet | undefined { + return metadata.find(set => validateValue(layerIds, param1, set.param1)); +} + +/** + * Validate that a binding's parameters match the behavior's metadata. + */ +export function validateBinding( + metadata: BehaviorBindingParametersSet[], + layerIds: number[], + param1?: number, + param2?: number +): boolean { + if ( + (param1 === undefined || param1 === 0) && + metadata.every((s) => !s.param1 || s.param1.length === 0) + ) { + return true; + } + + const matchingSet = findMatchingParameterSet(param1, metadata, layerIds); + + if (!matchingSet) { + return false; + } + + return validateValue(layerIds, param2, matchingSet.param2); +} + +/** + * Get a readable display for a parameter value based on its metadata. + * Returns a JSX element, string, number, or null if nothing should be displayed. + * Returns null when the parameter shouldn't be displayed (empty metadata or nil type). + */ +export function getParameterDisplay( + value: number, + paramDescriptions: BehaviorParameterValueDescription[], + layers?: { id: number; name: string }[] +): JSX.Element | string | number | null { + // If no parameter descriptions, don't display anything (matches ParameterValuePicker behavior) + if (!paramDescriptions || paramDescriptions.length === 0) { + return null; + } + + // Check if it's a constant with a name + if (paramDescriptions.every(v => v.constant !== undefined)) { + const match = paramDescriptions.find(v => v.constant === value); + if (match?.name) { + return match.name; + } + // If no match found in constants, don't display + return null; + } + + // For single parameter descriptions, check the type + if (paramDescriptions.length === 1) { + const desc = paramDescriptions[0]; + + if (desc.hidUsage) { + return ; + } + + if (desc.layerId && layers) { + // Look up the layer name by ID + const layer = layers.find(l => l.id === value); + return layer?.name || `Layer ${value}`; + } + + if (desc.range) { + return value; + } + + // If it's a nil type or unrecognized, don't display + return null; + } + + // For multiple parameter descriptions or unhandled cases, don't display + return null; +} diff --git a/src/keyboard/Keymap.tsx b/src/keyboard/Keymap.tsx index 2f4656e6..6cd025fe 100644 --- a/src/keyboard/Keymap.tsx +++ b/src/keyboard/Keymap.tsx @@ -52,10 +52,10 @@ export const Keymap = ({ const behavior = behaviors[binding.behaviorId]; const header = behavior?.displayName || "Unknown"; - // Get layer IDs for metadata-driven rendering - const layerIds = keymap.layers.map(layer => layer.id); + // Get layers for metadata-driven rendering + const layers = keymap.layers.map(layer => ({ id: layer.id, name: layer.name })); - const children = getBindingChildren(behavior, binding, layerIds); + const children = getBindingChildren(behavior, binding, layers); return { id: `${keymap.layers[selectedLayerIndex].id}-${i}`, diff --git a/src/keyboard/KeymapBindingChildren.tsx b/src/keyboard/KeymapBindingChildren.tsx index 5cd6f249..933c0480 100644 --- a/src/keyboard/KeymapBindingChildren.tsx +++ b/src/keyboard/KeymapBindingChildren.tsx @@ -1,149 +1,79 @@ -import { HidUsageLabel } from "./HidUsageLabel"; -import { GetBehaviorDetailsResponse, BehaviorBindingParametersSet } from "@zmkfirmware/zmk-studio-ts-client/behaviors"; -import { validateValue } from "../behaviors/parameters"; +import { GetBehaviorDetailsResponse } from "@zmkfirmware/zmk-studio-ts-client/behaviors"; +import { findMatchingParameterSet, getParameterDisplay } from "../behaviors/behaviorBindingUtils"; export interface KeyBinding { param1: number; param2: number; } -const renderIndicator = (numbers: 0|1|2|3 = 1): JSX.Element => ( -
- {Array.from({ length: Math.max(1, numbers) }, (_, i) => ( -
- ))} -
- ); - -// Helper function to check if a parameter is a HID usage -function isHidUsageParameter( - value: number, - metadata: BehaviorBindingParametersSet[], - layerIds: number[] -): boolean { - if (value === 0) return false; - - // Check if any parameter set has this value as a HID usage - return metadata.some(set => - set.param1.some(param => - param.hidUsage && validateValue(layerIds, value, [param]) - ) || - set.param2.some(param => - param.hidUsage && validateValue(layerIds, value, [param]) - ) - ); -} - -// Helper function to check if a parameter is a layer ID -function isLayerIdParameter( - value: number, - metadata: BehaviorBindingParametersSet[], - layerIds: number[] -): boolean { - if (value === 0) return false; - - // Check if any parameter set has this value as a layer ID - return metadata.some(set => - set.param1.some(param => - param.layerId && validateValue(layerIds, value, [param]) - ) || - set.param2.some(param => - param.layerId && validateValue(layerIds, value, [param]) - ) - ); -} - export const getBindingChildren = ( behavior: GetBehaviorDetailsResponse | undefined, binding: KeyBinding, - layerIds: number[] = [] + layers: { id: number; name: string }[] = [] ): JSX.Element | JSX.Element[] => { + // If no behavior metadata, show behavior name if (!behavior || !behavior.metadata) { - if (binding.param1 !== 0 && binding.param2 !== 0) { - return [ -
- -
, -
- -
- ]; - } + return ( +
+ {behavior?.displayName || "?"} +
+ ); + } - if (binding.param1 === 0 && binding.param2 === 0) { - return ( -
- ▽ -
- ); - } + // Find the matching parameter set for param1 (critical for getting param2 type!) + const layerIds = layers.map(l => l.id); + const matchingSet = findMatchingParameterSet(binding.param1, behavior.metadata, layerIds); + // If both parameters are zero + if (binding.param1 === 0 && binding.param2 === 0) { return ( -
- +
); } - // Check if both parameters are HID usages (like Mod-Tap, Layer-Tap, etc.) - const param1IsHidUsage = isHidUsageParameter(binding.param1, behavior.metadata, layerIds); - const param2IsHidUsage = isHidUsageParameter(binding.param2, behavior.metadata, layerIds); - const param1IsLayerId = isLayerIdParameter(binding.param1, behavior.metadata, layerIds); - const param2IsLayerId = isLayerIdParameter(binding.param2, behavior.metadata, layerIds); + // Get displays for both parameters + const param1Display = binding.param1 !== 0 ? + getParameterDisplay(binding.param1, behavior.metadata.flatMap(m => m.param1), layers) : + null; - // If both parameters are HID usages, render with indicators (Mod-Tap style) - if (param1IsHidUsage && param2IsHidUsage) { + const param2Display = binding.param2 !== 0 && matchingSet ? + getParameterDisplay(binding.param2, matchingSet.param2, layers) : + null; + + // Both parameters present and should be displayed + if (param1Display && param2Display) { return [ -
- {renderIndicator(1)} - +
+ {param2Display}
, -
- {renderIndicator(0)} - +
+ {param1Display}
]; } - // If param1 is a layer ID and param2 is a HID usage (Layer-Tap style) - if (param1IsLayerId && param2IsHidUsage) { - return [ -
- {renderIndicator(1)} - -
, -
- {renderIndicator(0)} - - - - {binding.param1} + // Only param1 should be displayed + if (param1Display) { + return ( +
+ {param1Display}
- ]; + ); } - // If both parameters are non-zero but not the special cases above - if (binding.param1 !== 0 && binding.param2 !== 0) { - return [ -
- -
, -
- + // Only param2 should be displayed (unusual but handle it) + if (param2Display) { + return ( +
+ {param2Display}
- ]; + ); } + // Nothing to display - show behavior name or empty return ( -
- +
); -}; \ No newline at end of file +}; From 4624d557efd80aabec754ac9ce9a141ea88d163e Mon Sep 17 00:00:00 2001 From: BafS Date: Wed, 3 Dec 2025 00:33:39 +0100 Subject: [PATCH 07/12] Revert some unrelated changes --- src-tauri/src/transport/commands.rs | 2 +- src/AppFooter.tsx | 2 +- src/keyboard/HidUsageLabel.tsx | 2 +- src/keyboard/Keyboard.tsx | 8 ++++---- src/keyboard/PhysicalLayout.tsx | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/transport/commands.rs b/src-tauri/src/transport/commands.rs index b6027387..8b638535 100644 --- a/src-tauri/src/transport/commands.rs +++ b/src-tauri/src/transport/commands.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use tauri::ipc::InvokeBody; use tauri::{ command, - ipc::Request, + ipc::{Request}, State, }; diff --git a/src/AppFooter.tsx b/src/AppFooter.tsx index 9bf2dd7c..e0d9af43 100644 --- a/src/AppFooter.tsx +++ b/src/AppFooter.tsx @@ -8,7 +8,7 @@ export const AppFooter = ({ onShowLicenseNotice, }: AppFooterProps) => { return ( -
+
© 2024 - The ZMK Contributors -{" "} diff --git a/src/keyboard/HidUsageLabel.tsx b/src/keyboard/HidUsageLabel.tsx index e28c9172..aa15195c 100644 --- a/src/keyboard/HidUsageLabel.tsx +++ b/src/keyboard/HidUsageLabel.tsx @@ -17,7 +17,7 @@ export const HidUsageLabel = ({ hid_usage }: HidUsageLabelProps) => { // TODO: Do something with implicit mods! page &= 0xff; - const labels = hid_usage_get_labels(page, id); + let labels = hid_usage_get_labels(page, id); return ( ; function useBehaviors(): BehaviorMap { - const connection = useContext(ConnectionContext); - const lockState = useContext(LockStateContext); + let connection = useContext(ConnectionContext); + let lockState = useContext(LockStateContext); const [behaviors, setBehaviors] = useState({}); @@ -105,8 +105,8 @@ function useLayouts(): [ number, React.Dispatch> ] { - const connection = useContext(ConnectionContext); - const lockState = useContext(LockStateContext); + let connection = useContext(ConnectionContext); + let lockState = useContext(LockStateContext); const [layouts, setLayouts] = useState( undefined diff --git a/src/keyboard/PhysicalLayout.tsx b/src/keyboard/PhysicalLayout.tsx index 2df858b7..db8719c7 100644 --- a/src/keyboard/PhysicalLayout.tsx +++ b/src/keyboard/PhysicalLayout.tsx @@ -116,10 +116,10 @@ export const PhysicalLayout = ({ }, [props.zoom]); // TODO: Add a bit of padding for rotation when supported - const rightMost = positions + let rightMost = positions .map((k) => k.x + k.width) .reduce((a, b) => Math.max(a, b), 0); - const bottomMost = positions + let bottomMost = positions .map((k) => k.y + k.height) .reduce((a, b) => Math.max(a, b), 0); From 7b9bf07e6a473f3b3ed79c5a5e37b0c2828913ca Mon Sep 17 00:00:00 2001 From: BafS Date: Wed, 3 Dec 2025 01:27:24 +0100 Subject: [PATCH 08/12] Simplify logic and improve key UI --- src/keyboard/KeymapBindingChildren.tsx | 34 +++++++++----------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/keyboard/KeymapBindingChildren.tsx b/src/keyboard/KeymapBindingChildren.tsx index 933c0480..e7929d10 100644 --- a/src/keyboard/KeymapBindingChildren.tsx +++ b/src/keyboard/KeymapBindingChildren.tsx @@ -20,41 +20,32 @@ export const getBindingChildren = ( ); } - // Find the matching parameter set for param1 (critical for getting param2 type!) + // Find the matching parameter set for param1 (critical for getting param2 type) const layerIds = layers.map(l => l.id); const matchingSet = findMatchingParameterSet(binding.param1, behavior.metadata, layerIds); - // If both parameters are zero - if (binding.param1 === 0 && binding.param2 === 0) { - return ( -
-
- ); - } - // Get displays for both parameters - const param1Display = binding.param1 !== 0 ? - getParameterDisplay(binding.param1, behavior.metadata.flatMap(m => m.param1), layers) : - null; + const param1Display = + getParameterDisplay(binding.param1, behavior.metadata.flatMap(m => m.param1), layers); - const param2Display = binding.param2 !== 0 && matchingSet ? + const param2Display = matchingSet ? getParameterDisplay(binding.param2, matchingSet.param2, layers) : null; // Both parameters present and should be displayed - if (param1Display && param2Display) { + if (param1Display !== null && param2Display !== null) { return [ -
+
{param2Display}
, -
+
{param1Display}
]; } // Only param1 should be displayed - if (param1Display) { + if (param1Display !== null) { return (
{param1Display} @@ -63,7 +54,7 @@ export const getBindingChildren = ( } // Only param2 should be displayed (unusual but handle it) - if (param2Display) { + if (param2Display !== null) { return (
{param2Display} @@ -71,9 +62,6 @@ export const getBindingChildren = ( ); } - // Nothing to display - show behavior name or empty - return ( -
-
- ); + // Nothing to display + return
; }; From 4ec09be337f695b51dc20cd879db8c971c5aab56 Mon Sep 17 00:00:00 2001 From: BafS Date: Wed, 3 Dec 2025 01:38:31 +0100 Subject: [PATCH 09/12] Fix spacing when multilines param --- src/keyboard/Key.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/keyboard/Key.tsx b/src/keyboard/Key.tsx index 8944e00c..eab56463 100644 --- a/src/keyboard/Key.tsx +++ b/src/keyboard/Key.tsx @@ -53,6 +53,7 @@ export const Key = ({ style={{ width: `${pixelWidth}px`, height: `${pixelHeight}px`, + lineHeight: 1, }} onClick={onClick} > From 7c8619c20c05296b26fb49730abd18daeb7cad14 Mon Sep 17 00:00:00 2001 From: BafS Date: Tue, 16 Dec 2025 00:20:11 +0100 Subject: [PATCH 10/12] Revert validateBinding move --- src/behaviors/BehaviorBindingPicker.tsx | 27 ++++++++++++++++++++++++- src/behaviors/behaviorBindingUtils.tsx | 25 ----------------------- src/keyboard/Keymap.tsx | 3 +-- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/behaviors/BehaviorBindingPicker.tsx b/src/behaviors/BehaviorBindingPicker.tsx index 00d2eab4..d5062fbc 100644 --- a/src/behaviors/BehaviorBindingPicker.tsx +++ b/src/behaviors/BehaviorBindingPicker.tsx @@ -2,10 +2,11 @@ import { useEffect, useMemo, useState } from "react"; import { GetBehaviorDetailsResponse, + BehaviorBindingParametersSet, } from "@zmkfirmware/zmk-studio-ts-client/behaviors"; import { BehaviorBinding } from "@zmkfirmware/zmk-studio-ts-client/keymap"; import { BehaviorParametersPicker } from "./BehaviorParametersPicker"; -import { validateBinding } from "./behaviorBindingUtils"; +import { validateValue } from "./parameters"; export interface BehaviorBindingPickerProps { binding: BehaviorBinding; @@ -14,6 +15,30 @@ export interface BehaviorBindingPickerProps { onBindingChanged: (binding: BehaviorBinding) => void; } +function validateBinding( + metadata: BehaviorBindingParametersSet[], + layerIds: number[], + param1?: number, + param2?: number +): boolean { + if ( + (param1 === undefined || param1 === 0) && + metadata.every((s) => !s.param1 || s.param1.length === 0) + ) { + return true; + } + + const matchingSet = metadata.find((s) => + validateValue(layerIds, param1, s.param1) + ); + + if (!matchingSet) { + return false; + } + + return validateValue(layerIds, param2, matchingSet.param2); +} + export const BehaviorBindingPicker = ({ binding, layers, diff --git a/src/behaviors/behaviorBindingUtils.tsx b/src/behaviors/behaviorBindingUtils.tsx index b7307586..1f58baec 100644 --- a/src/behaviors/behaviorBindingUtils.tsx +++ b/src/behaviors/behaviorBindingUtils.tsx @@ -14,31 +14,6 @@ export function findMatchingParameterSet( return metadata.find(set => validateValue(layerIds, param1, set.param1)); } -/** - * Validate that a binding's parameters match the behavior's metadata. - */ -export function validateBinding( - metadata: BehaviorBindingParametersSet[], - layerIds: number[], - param1?: number, - param2?: number -): boolean { - if ( - (param1 === undefined || param1 === 0) && - metadata.every((s) => !s.param1 || s.param1.length === 0) - ) { - return true; - } - - const matchingSet = findMatchingParameterSet(param1, metadata, layerIds); - - if (!matchingSet) { - return false; - } - - return validateValue(layerIds, param2, matchingSet.param2); -} - /** * Get a readable display for a parameter value based on its metadata. * Returns a JSX element, string, number, or null if nothing should be displayed. diff --git a/src/keyboard/Keymap.tsx b/src/keyboard/Keymap.tsx index 6cd025fe..4d0209e3 100644 --- a/src/keyboard/Keymap.tsx +++ b/src/keyboard/Keymap.tsx @@ -50,7 +50,6 @@ export const Keymap = ({ const binding = keymap.layers[selectedLayerIndex].bindings[i]; const behavior = behaviors[binding.behaviorId]; - const header = behavior?.displayName || "Unknown"; // Get layers for metadata-driven rendering const layers = keymap.layers.map(layer => ({ id: layer.id, name: layer.name })); @@ -59,7 +58,7 @@ export const Keymap = ({ return { id: `${keymap.layers[selectedLayerIndex].id}-${i}`, - header: header, + header: behavior?.displayName || "Unknown", x: k.x / 100.0, y: k.y / 100.0, width: k.width / 100, From 89268876f273e5770c1c62a2d20244bbd0d40044 Mon Sep 17 00:00:00 2001 From: BafS Date: Tue, 16 Dec 2025 00:25:08 +0100 Subject: [PATCH 11/12] Don't truncate param --- src/behaviors/BehaviorBindingPicker.tsx | 2 +- src/keyboard/KeymapBindingChildren.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/behaviors/BehaviorBindingPicker.tsx b/src/behaviors/BehaviorBindingPicker.tsx index d5062fbc..8c3f54e7 100644 --- a/src/behaviors/BehaviorBindingPicker.tsx +++ b/src/behaviors/BehaviorBindingPicker.tsx @@ -28,7 +28,7 @@ function validateBinding( return true; } - const matchingSet = metadata.find((s) => + let matchingSet = metadata.find((s) => validateValue(layerIds, param1, s.param1) ); diff --git a/src/keyboard/KeymapBindingChildren.tsx b/src/keyboard/KeymapBindingChildren.tsx index e7929d10..f1f56463 100644 --- a/src/keyboard/KeymapBindingChildren.tsx +++ b/src/keyboard/KeymapBindingChildren.tsx @@ -38,7 +38,7 @@ export const getBindingChildren = (
{param2Display}
, -
+
{param1Display}
]; From a177a63e7b332e19896b511104f5df3737dadfee Mon Sep 17 00:00:00 2001 From: BafS Date: Tue, 16 Dec 2025 00:37:56 +0100 Subject: [PATCH 12/12] Cleanup --- src/keyboard/Keymap.tsx | 2 +- src/keyboard/KeymapBindingChildren.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/keyboard/Keymap.tsx b/src/keyboard/Keymap.tsx index 4d0209e3..8220defc 100644 --- a/src/keyboard/Keymap.tsx +++ b/src/keyboard/Keymap.tsx @@ -66,7 +66,7 @@ export const Keymap = ({ r: (k.r || 0) / 100.0, rx: (k.rx || 0) / 100.0, ry: (k.ry || 0) / 100.0, - children: children, + children, }; }); diff --git a/src/keyboard/KeymapBindingChildren.tsx b/src/keyboard/KeymapBindingChildren.tsx index f1f56463..acb6afb0 100644 --- a/src/keyboard/KeymapBindingChildren.tsx +++ b/src/keyboard/KeymapBindingChildren.tsx @@ -11,11 +11,11 @@ export const getBindingChildren = ( binding: KeyBinding, layers: { id: number; name: string }[] = [] ): JSX.Element | JSX.Element[] => { - // If no behavior metadata, show behavior name + // If no behavior metadata, try to show behavior name if (!behavior || !behavior.metadata) { return (
- {behavior?.displayName || "?"} + {behavior?.displayName || ""}
); }