|
1 | 1 | import { |
2 | 2 | Button, |
| 3 | + Checkbox, |
| 4 | + CheckboxGroup, |
3 | 5 | Collection, |
4 | 6 | ComboBox, |
5 | 7 | Header, |
@@ -61,38 +63,105 @@ const UsageSection = ({id, min, max}: UsageSectionProps) => { |
61 | 63 | ); |
62 | 64 | } |
63 | 65 |
|
| 66 | +enum Mods { |
| 67 | + LeftControl = 0x01, |
| 68 | + LeftShift = 0x02, |
| 69 | + LeftAlt = 0x04, |
| 70 | + LeftGUI = 0x08, |
| 71 | + RightControl = 0x10, |
| 72 | + RightShift = 0x20, |
| 73 | + RightAlt = 0x40, |
| 74 | + RightGUI = 0x80, |
| 75 | +} |
| 76 | + |
| 77 | +const mod_labels: Record<Mods, string> = { |
| 78 | + [Mods.LeftControl]: "L Ctrl", |
| 79 | + [Mods.LeftShift]: "L Shift", |
| 80 | + [Mods.LeftAlt]: "L Alt", |
| 81 | + [Mods.LeftGUI]: "L GUI", |
| 82 | + [Mods.RightControl]: "R Ctrl", |
| 83 | + [Mods.RightShift]: "R Shift", |
| 84 | + [Mods.RightAlt]: "R Alt", |
| 85 | + [Mods.RightGUI]: "R GUI", |
| 86 | +} |
| 87 | + |
| 88 | +const all_mods = [ |
| 89 | + Mods.LeftControl, |
| 90 | + Mods.LeftShift, |
| 91 | + Mods.LeftAlt, |
| 92 | + Mods.LeftGUI, |
| 93 | + Mods.RightControl, |
| 94 | + Mods.RightShift, |
| 95 | + Mods.RightAlt, |
| 96 | + Mods.RightGUI, |
| 97 | +]; |
| 98 | + |
| 99 | +function mods_to_flags(mods: Mods[]): number { |
| 100 | + return mods.reduce((a, v) => a + v, 0); |
| 101 | +} |
| 102 | + |
| 103 | +function mask_mods(value: number) { |
| 104 | + return (value & ~(mods_to_flags(all_mods) << 24)); |
| 105 | +} |
| 106 | + |
64 | 107 | export const HidUsagePicker = ({ |
65 | 108 | label, |
66 | 109 | value, |
67 | 110 | usagePages, |
68 | 111 | onValueChanged, |
69 | 112 | }: HidUsagePickerProps) => { |
| 113 | + const mods = useMemo(() => { |
| 114 | + let flags = value ? (value >> 24) : 0; |
| 115 | + |
| 116 | + return all_mods.filter(m => m & flags).map(m => m.toLocaleString()); |
| 117 | + }, [value]); |
| 118 | + |
70 | 119 | const selectionChanged = useCallback( |
71 | 120 | (e: Key | null) => { |
72 | 121 | let value = typeof e == "number" ? e : undefined; |
| 122 | + if (value !== undefined) { |
| 123 | + let mod_flags = mods_to_flags(mods.map(m => parseInt(m))); |
| 124 | + value = value | (mod_flags << 24); |
| 125 | + } |
| 126 | + |
73 | 127 | onValueChanged(value); |
74 | 128 | }, |
75 | | - [onValueChanged], |
| 129 | + [onValueChanged, mods], |
76 | 130 | ); |
77 | 131 |
|
| 132 | + const modifiersChanged = useCallback((m: string[]) => { |
| 133 | + if (!value) { |
| 134 | + return; |
| 135 | + } |
| 136 | + |
| 137 | + let mod_flags = mods_to_flags(m.map(m => parseInt(m))); |
| 138 | + let new_value = mask_mods(value) | (mod_flags << 24); |
| 139 | + onValueChanged(new_value); |
| 140 | + }, [value]); |
| 141 | + |
78 | 142 | return ( |
79 | | - <ComboBox selectedKey={value} onSelectionChange={selectionChanged}> |
80 | | - { label && <Label>{label}</Label> } |
81 | | - <div> |
82 | | - <Input className="p-1 rounded" /> |
83 | | - <Button className="ml-[-1.75em] px-[0.25em] py-0 rounded rounded-md bg-accent w-6 h-6"> |
84 | | - ▼ |
85 | | - </Button> |
86 | | - </div> |
87 | | - <Popover className="w-[var(--trigger-width)] max-h-4 border rounded border-text-base bg-bg-base"> |
88 | | - <ListBox |
89 | | - items={usagePages} |
90 | | - className="block max-h-[30vh] min-h-[unset] overflow-auto m-2" |
91 | | - selectionMode="single" |
92 | | - > |
93 | | - {({id, min, max}) => <UsageSection id={id} min={min} max={max} />} |
94 | | - </ListBox> |
95 | | - </Popover> |
96 | | - </ComboBox> |
| 143 | + <div className="flex mt-8 relative"> |
| 144 | + <ComboBox selectedKey={value ? mask_mods(value) : undefined} onSelectionChange={selectionChanged}> |
| 145 | + { label && <Label className="absolute top-[-1.75em] left-[0.25em]">{label}</Label> } |
| 146 | + <div> |
| 147 | + <Input className="p-1 rounded" /> |
| 148 | + <Button className="ml-[-1.75em] px-[0.25em] py-0 rounded rounded-md bg-accent w-6 h-6"> |
| 149 | + ▼ |
| 150 | + </Button> |
| 151 | + </div> |
| 152 | + <Popover className="w-[var(--trigger-width)] max-h-4 border rounded border-text-base bg-bg-base"> |
| 153 | + <ListBox |
| 154 | + items={usagePages} |
| 155 | + className="block max-h-[30vh] min-h-[unset] overflow-auto m-2" |
| 156 | + selectionMode="single" |
| 157 | + > |
| 158 | + {({id, min, max}) => <UsageSection id={id} min={min} max={max} />} |
| 159 | + </ListBox> |
| 160 | + </Popover> |
| 161 | + </ComboBox> |
| 162 | + <CheckboxGroup aria-label="Implicit Modifiers" className="grid grid-flow-col gap-x-px auto-cols-fr content-stretch divide-x rounded border border-text-base" value={mods} onChange={modifiersChanged}> |
| 163 | + { all_mods.map(m => <Checkbox key={m} value={m.toLocaleString()} className="text-nowrap grid content-center justify-center rac-selected:bg-secondary first:rounded-s last:rounded-e">{mod_labels[m]}</Checkbox> )} |
| 164 | + </CheckboxGroup> |
| 165 | + </div> |
97 | 166 | ); |
98 | 167 | }; |
0 commit comments