diff --git a/src/components/profile/section.html b/src/components/profile/section.html index 670c7e0..45ac5b3 100644 --- a/src/components/profile/section.html +++ b/src/components/profile/section.html @@ -299,6 +299,69 @@

{{getSectionTitle()}}

+ +
+
+
+
Enable Momentum
+
help
+
+ +
+ +
+
Vertical Dampening help
+
+ +
+
+ +
+
Horizontal Dampening help
+
+ +
+
+ +
+
Cutoff Velocity help
+
+ +
+
+
+ +

{{getSectionTitle()}}

@@ -630,3 +693,11 @@

{{getSectionTitle()}}

+ + +

{{helpTitle}}

+

+
+ +
+
diff --git a/src/components/profile/section.sass b/src/components/profile/section.sass index ec5313f..001e903 100644 --- a/src/components/profile/section.sass +++ b/src/components/profile/section.sass @@ -13,6 +13,11 @@ .subtitle margin-bottom: 5px font-size: 14px + .help-icon + font-size: 14px + vertical-align: middle + color: hsl(0deg, 0%, 40%) + cursor: pointer p font-size: 14px @@ -262,3 +267,50 @@ button text-align: center +// Gyro momentum settings +.gyro-momentum + margin-top: 10px + + .setting + display: flex + align-items: center + margin-bottom: 15px + .text + display: flex + flex: 1 + min-width: 0 + height: 40px + margin-right: 10px + align-items: center + background-color: hsl(0deg, 0%, 20%) + border-radius: 5px + .name + font-size: 14px + font-weight: normal + white-space: nowrap + .material + font-size: 18px + color: hsl(0deg, 0%, 40%) + padding-left: 6px + padding-right: 4px + cursor: pointer + line-height: 40px + + ::ng-deep app-input-number + width: 100px + height: 40px !important + & * + height: 40px !important + line-height: 40px !important + .number + border-radius: 5px + background-color: black + .value, .unit + font-size: 16px !important + font-weight: normal !important + color: white !important + .button + color: hsl(0deg, 0%, 40%) !important + + ::ng-deep app-input-toggle + height: 40px !important diff --git a/src/components/profile/section.ts b/src/components/profile/section.ts index 5172f50..243b665 100644 --- a/src/components/profile/section.ts +++ b/src/components/profile/section.ts @@ -6,6 +6,7 @@ import { CommonModule } from '@angular/common' import { FormsModule } from '@angular/forms' import { ActionSelectorComponent } from './action_selector' import { InputNumberComponent } from 'components/input_number/input_number' +import { InputToggleComponent } from 'components/input_toggle/input_toggle' import { WebusbService } from 'services/webusb' import { Profile } from 'lib/profile' import { CtrlSection, CtrlSectionMeta, CtrlButton, CtrlRotary } from 'lib/ctrl' @@ -24,6 +25,7 @@ import { delay } from 'lib/delay' CommonModule, FormsModule, InputNumberComponent, + InputToggleComponent, ActionSelectorComponent, ], templateUrl: './section.html', @@ -41,6 +43,10 @@ export class SectionComponent { pickerTune = 0 profileOverwriteIndex = 0 profiles = this.webusb.getProfiles()! + // Help dialog + dialogHelp: any + helpTitle = '' + helpText = '' // Template aliases. HID = HID SectionIndex = SectionIndex @@ -303,6 +309,45 @@ export class SectionComponent { save = async () => { await this.webusb.trySetSection(this.profileIndex, this.section) } + + showDialogHelp(key: string) { + const titles: {[key: string]: string} = { + gyroMomentum: "Enable Momentum", + gyroMomentumDamping: "Momentum Dampening", + gyroMomentumCutoff: "Momentum Cutoff Velocity", + } + const texts: {[key: string]: string} = { + gyroMomentum: + `When enabled, the mouse cursor will have "momementum" and continue moving after + disabling the gyro for a short time. The cursor will have the initial velocity at + the moment of disabling, and then it will slow down at an exponential rate. + This creates a more fluid, natural feel similar to trackpad or trackball momentum.

+ + Works in both TOUCH_ON mode (momentum continues after releasing the button) and TOUCH_OFF + mode (momentum continues after pressing the button to stop gyro input).`, + gyroMomentumDamping: + `Controls how quickly the momentum decays. Consider these values "friction" values. + with Higher values = faster decay.

+ Separate values for vertical and horizontal allow you to customize the feel for each axis. + For example, you might want faster horizontal swipes but more controlled vertical movement.

+ Recommended range: 1.0-20.0. Default: H=4.0, V=4.0`, + gyroMomentumCutoff: + `The minimum velocity (in pixels per second) below which momentum stops completely. At 250Hz, + The minimum visible velocity is about 250 pixels/second, but the cutoff can be set even lower. + This prevents the cursor from drifting indefinitely at very slow speeds.

+ Lower values = momentum lasts longer but may feel floaty
+ Higher values = momentum stops sooner but may feel abrupt

+ Recommended range: 100-10000.0. Default: 500.0`, + } + this.helpTitle = titles[key] + this.helpText = texts[key] + this.dialogHelp = document.getElementById('dialog-help') + if (this.dialogHelp) this.dialogHelp.showModal() + } + + hideDialogHelp() { + if (this.dialogHelp) this.dialogHelp.close() + } } interface SectionTitles { diff --git a/src/lib/ctrl.ts b/src/lib/ctrl.ts index 0c3809d..4245ecf 100644 --- a/src/lib/ctrl.ts +++ b/src/lib/ctrl.ts @@ -608,27 +608,56 @@ export class CtrlGyro extends CtrlSection { public override sectionIndex: SectionIndex, public mode: GyroMode, public engage: number, + // Momentum settings + public momentumEnabled: boolean = false, + public momentumDampingH: number = 4.0, + public momentumDampingV: number = 4.0, + public momentumThreshold: number = 200.0, ) { super(1, DeviceId.ALPAKKA, MessageType.SECTION_SHARE) } static override decode(buffer: Uint8Array) { const data = Array.from(buffer) + const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength) + // Buffer layout: 4-byte Ctrl header + 2-byte indices + 58-byte CtrlGyro struct + // CtrlGyro struct starts at buffer offset 6: + // byte 0 (buf 6): mode + // byte 1 (buf 7): engage + // byte 2 (buf 8): momentum_enabled + // byte 3 (buf 9): _reserved1 (padding) + // bytes 4-7 (buf 10-13): momentum_damping_h (float) + // bytes 8-11 (buf 14-17): momentum_damping_v (float) + // bytes 12-15 (buf 18-21): momentum_threshold (float) return new CtrlGyro( - data[4], // ProfileIndex. - data[5], // SectionIndex. - data[6], // Gyro mode. - data[7], // Engage button. + data[4], // profileIndex + data[5], // sectionIndex + data[6], // mode + data[7], // engage + data[8] !== 0, // momentumEnabled + view.getFloat32(10, true), // momentumDampingH + view.getFloat32(14, true), // momentumDampingV + view.getFloat32(18, true), // momentumThreshold ) } override payload() { - return [ - this.profileIndex, - this.sectionIndex, - Number(this.mode), - Number(this.engage), - ] + // Payload: profileIndex (1) + sectionIndex (1) + CtrlGyro struct (58) = 60 bytes + const buffer = new ArrayBuffer(60) + const view = new DataView(buffer) + const arr = new Uint8Array(buffer) + arr[0] = this.profileIndex + arr[1] = this.sectionIndex + // CtrlGyro struct: + arr[2] = Number(this.mode) // byte 0: mode + arr[3] = Number(this.engage) // byte 1: engage + arr[4] = this.momentumEnabled ? 1 : 0 // byte 2: momentum_enabled + arr[5] = 0 // byte 3: _reserved1 + view.setFloat32(6, this.momentumDampingH, true) // bytes 4-7 + view.setFloat32(10, this.momentumDampingV, true) // bytes 8-11 + view.setFloat32(14, this.momentumThreshold, true) // bytes 12-15 + // bytes 16-57: padding (zeroed by ArrayBuffer) + return Array.from(arr) } }