diff --git a/src/components/profile/section.html b/src/components/profile/section.html index 670c7e0..8b0b517 100644 --- a/src/components/profile/section.html +++ b/src/components/profile/section.html @@ -176,35 +176,19 @@

{{getSectionTitle()}}

-
-
Analog distance calculation:
- -
-
-
Saturation:
-
- -
+ +
+
Zones
+
Response
-
+ + +
Axis overlap:
{{getSectionTitle()}} />
-
+
Deadzone:
Override global deadzone {{thumbstick.deadzone_override ? 'check_box' : 'check_box_outline_blank'}} @@ -227,7 +211,7 @@

{{getSectionTitle()}}

{{getSectionTitle()}} />
-
+
Anti-deadzone:
{{getSectionTitle()}} />
+
+
Outer threshold:
+
+ +
+
+
+
Upper limit:
+
+ +
+
+ +
+
Acceleration curve:
+
+ +
+
+
+
Sensitivity vertical ratio:
+
+ +
+
+
+
Sensitivity as mouse:
+
+ +
+
+
+
Sensitivity as scrollwheel:
+
+ +
+
+
+
Extras:
+
+
+ Push auto-toggle + + {{thumbstick.push_auto_toggle ? 'check_box' : 'check_box_outline_blank'}} + +
+
+ Axis self-align + + {{thumbstick.distance_mode ? 'check_box' : 'check_box_outline_blank'}} + +
+
+
+ +
+ + +
diff --git a/src/components/profile/section.sass b/src/components/profile/section.sass index ec5313f..2a98f42 100644 --- a/src/components/profile/section.sass +++ b/src/components/profile/section.sass @@ -17,6 +17,27 @@ p font-size: 14px + .tabs + width: 100% + display: flex + background-color: black + border-radius: 6px 6px 0 0 + border-bottom: 3px solid $green + font-size: 14px + font-weight: bold + height: 32px + line-height: 32px + cursor: pointer + user-select: none + .tab + width: 50% + text-align: center + color: hsl(0deg, 0%, 50%) + border-radius: 6px 6px 0 0 + &.active + color: black + background-color: $green + .island border-radius: 6px overflow: hidden @@ -121,6 +142,16 @@ background-color: $yellow !important color: black !important + .plots + display: flex + padding-top: 15px + .plot + background: black + border-radius: 10px + transform: scaleY(-1) + &:first-child + margin-right: 10px + #dialog-keypicker min-width: 935px min-height: 600px diff --git a/src/components/profile/section.ts b/src/components/profile/section.ts index 5172f50..65285f4 100644 --- a/src/components/profile/section.ts +++ b/src/components/profile/section.ts @@ -1,17 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-only // Copyright (C) 2023, Input Labs Oy. -import { Component, Input } from '@angular/core' +import { Component, Input, ViewChild, ElementRef} from '@angular/core' import { CommonModule } from '@angular/common' import { FormsModule } from '@angular/forms' import { ActionSelectorComponent } from './action_selector' import { InputNumberComponent } from 'components/input_number/input_number' import { WebusbService } from 'services/webusb' import { Profile } from 'lib/profile' -import { CtrlSection, CtrlSectionMeta, CtrlButton, CtrlRotary } from 'lib/ctrl' +import { CtrlSection, CtrlSectionMeta, CtrlButton, CtrlRotary, ConfigIndex } from 'lib/ctrl' import { CtrlThumbstick, CtrlGyro, CtrlGyroAxis, CtrlHome } from 'lib/ctrl' import { SectionIndex, sectionIsAnalog } from 'lib/ctrl' -import { ThumbstickMode, ThumbstickDistanceMode, GyroMode } from 'lib/ctrl' +import { ThumbstickMode, GyroMode } from 'lib/ctrl' import { ActionGroup } from 'lib/actions' import { HID, isAxis } from 'lib/hid' import { PinV0, PinV1 } from 'lib/pin' @@ -41,16 +41,27 @@ export class SectionComponent { pickerTune = 0 profileOverwriteIndex = 0 profiles = this.webusb.getProfiles()! + globalDeadzone = 0 + tab = 0 + canvasCircle!: ElementRef + canvasRamp!: ElementRef + green = 'hsl(160deg, 100%, 50%)' + purple = 'hsl(266deg, 100%, 50%)' // Template aliases. HID = HID SectionIndex = SectionIndex GyroMode = GyroMode ThumbstickMode = ThumbstickMode - ThumbstickDistanceMode = ThumbstickDistanceMode constructor( public webusb: WebusbService, - ) {} + ) { + this.afterConstructor() + } + + async afterConstructor() { + this.globalDeadzone = await this.fetchGlobalDeadzone() + } sectionIsMeta = () => this.section instanceof CtrlSectionMeta sectionIsButton = () => this.section instanceof CtrlButton && !(this.section instanceof CtrlHome) @@ -67,6 +78,18 @@ export class SectionComponent { getSectionAsGyro = () => this.section as CtrlGyro getSectionAsGyroAxis = () => this.section as CtrlGyroAxis + @ViewChild('_canvasCircle') set _canvasCircle(canvas: ElementRef) { + if (!canvas) return + this.canvasCircle = canvas + this.plot() + } + + @ViewChild('_canvasRamp') set _canvasRamp(canvas: ElementRef) { + if (!canvas) return + this.canvasRamp = canvas + this.plot() + } + getSectionTitle() { return sectionTitles[this.section.sectionIndex] } @@ -86,6 +109,11 @@ export class SectionComponent { else return PinV1 } + async fetchGlobalDeadzone() { + const preset = await this.webusb.tryGetConfig(ConfigIndex.DEADZONE) + return preset.values[preset.presetIndex] + } + isButtonBlockVisible(group: number) { const button = this.getSectionAsButton() if (group == 0) return true @@ -158,6 +186,129 @@ export class SectionComponent { a.remove() } + plot() { + this.plotCircle() + this.plotRamp() + } + + plotCircle = () => { + if (!this.canvasCircle) return + const ctx = this.canvasCircle.nativeElement.getContext('2d')! + const thumbstick = this.getSectionAsThumbstick() + const deadzone = thumbstick.deadzone_override ? thumbstick.deadzone : this.globalDeadzone + let overlap = thumbstick.overlap + if (overlap == 0) overlap = -2.5 // Force a visual gap when value is zero. + const size = this.canvasCircle.nativeElement.width + const mid = size / 2 + const max = size * 0.4 + let overlapDeg = (50-overlap) / 100 * 90 + let overlapDegNeg = -overlap / 100 * 90 + // Helper functions. + const deg = (angle: number) => { + return angle * (Math.PI / 180) + } + const drawArc = (stroke: number, angle: number, size: number) => { + let half = size / 2 + ctx.beginPath(); + ctx.arc(mid, mid, max, angle-half, angle+half) + ctx.strokeStyle = this.green + ctx.lineWidth = stroke + ctx.stroke() + } + const drawCircle = (radius: number, lineWidth: number) => { + ctx.beginPath() + ctx.arc(mid, mid, radius, 0, deg(360)) + ctx.strokeStyle = this.purple + ctx.lineWidth = lineWidth + ctx.stroke() + } + // Draw. + ctx.clearRect(0, 0, size, size); + if (overlap > 0) { + drawArc(3, deg(0), deg(45+overlapDeg)) + drawArc(3, deg(90), deg(45+overlapDeg)) + drawArc(3, deg(180), deg(45+overlapDeg)) + drawArc(3, deg(270), deg(45+overlapDeg)) + } + if (overlap > 0) { + drawArc(1, deg(45+0), deg(45-overlapDeg)) + drawArc(1, deg(45+90), deg(45-overlapDeg)) + drawArc(1, deg(45+180), deg(45-overlapDeg)) + drawArc(1, deg(45+270), deg(45-overlapDeg)) + } + if (overlap < 0) { + drawArc(3, deg(0), deg(90-overlapDegNeg)) + drawArc(3, deg(90), deg(90-overlapDegNeg)) + drawArc(3, deg(180), deg(90-overlapDegNeg)) + drawArc(3, deg(270), deg(90-overlapDegNeg)) + } + drawCircle(deadzone*max/100, 3) + drawCircle(thumbstick.outer_threshold*max/100, 3) + } + + plotRamp = () => { + if (!this.canvasRamp) return + const ctx = this.canvasRamp.nativeElement.getContext('2d')! + const thumbstick = this.getSectionAsThumbstick() + const deadzone = thumbstick.deadzone_override ? thumbstick.deadzone : this.globalDeadzone + const size = this.canvasRamp.nativeElement.width + const min = 2 + const max = size - 2 + type Point = {x: number, y:number} + const pointA = {x: min, y: min} + const pointB = {x: min + deadzone/100*max, y: min} + const pointC = {x: pointB.x, y: min + thumbstick.antideadzone/100*max} + const pointD = {x: thumbstick.saturation/100*max, y: max} + const pointE = {x: max, y: max} + // Accel curve. + const curvePoints = 20 + const startX = min + (deadzone / 100) * max + const startY = min + (thumbstick.antideadzone / 100) * max + const endX = (thumbstick.saturation / 100) * max + const scaleX = endX - startX + const scaleY = max - startY + // Helper functions. + const drawVert = (x: number) => { + ctx.beginPath(); + ctx.moveTo(x, min); + ctx.lineTo(x, max); + ctx.strokeStyle = this.purple; + ctx.lineWidth = 3; + ctx.stroke(); + } + const drawLines = (points: Point[]) => { + ctx.beginPath() + ctx.moveTo(points[0].x, points[0].y) + for (const point of points.slice(1)) { + ctx.lineTo(point.x, point.y) + } + ctx.strokeStyle = this.green + ctx.lineWidth = 3 + ctx.stroke() + } + const felixCurve = (x: number, k: number) => { + return (x*k+x) / (2*x*k-k+1) + } + let pointsCtoD: Point[] = [] + for(let i=0; i<=curvePoints; i++) { + const k = thumbstick.accel_curve / 100 + let x = i / curvePoints + let y = felixCurve(x, k) + x *= scaleX + y *= scaleY + x += startX + y += startY + pointsCtoD.push({x, y}) + } + // Draw. + ctx.clearRect(0, 0, size, size); + drawVert(pointB.x) + drawVert(thumbstick.outer_threshold / 100 * max) + drawLines([pointA, pointB, pointC]) + drawLines(pointsCtoD) // Curve. + drawLines([pointD, pointE]) + } + showDialogKeypicker = (pickerGroup: number) => { this.pickerGroup = pickerGroup const section = this.section as (CtrlButton | CtrlRotary | CtrlGyroAxis) diff --git a/src/lib/ctrl.ts b/src/lib/ctrl.ts index 0c3809d..1b4e1e3 100644 --- a/src/lib/ctrl.ts +++ b/src/lib/ctrl.ts @@ -133,11 +133,6 @@ export enum ThumbstickMode { DIR8, } -export enum ThumbstickDistanceMode { - AXIAL, - RADIAL, -} - export enum GyroMode { OFF, ALWAYS_ON, @@ -560,12 +555,18 @@ export class CtrlThumbstick extends CtrlSection { public override profileIndex: number, public override sectionIndex: SectionIndex, public mode: ThumbstickMode, - public distance_mode: ThumbstickDistanceMode, + public distance_mode: boolean, public deadzone: number, public overlap : number, public deadzone_override: boolean, public antideadzone: number, public saturation: number, + public outer_threshold: number, + public push_auto_toggle: boolean, + public sens_mouse: number, + public sens_scroll: number, + public sens_xy_ratio: number, + public accel_curve: number, ) { super(1, DeviceId.ALPAKKA, MessageType.SECTION_SHARE) } @@ -578,12 +579,18 @@ export class CtrlThumbstick extends CtrlSection { data[4], // ProfileIndex. data[5], // SectionIndex. data[6], // Mode. - data[7], // Distance mode. + Boolean(data[7]), // Distance mode / Axis self align. data[8], // Deadzone. data[9] <= 128 ? data[9] : data[9]-256, // Axis overlap (unsigned to signed). Boolean(data[10]), // Deadzone override. data[11], // Antideadzone. data[12] > 0 ? data[12] : 100, // Saturation. + data[13] > 0 ? data[13] : 80, // Outer threshold. + Boolean(data[14]), // Push auto-toggle. + data[15] > 0 ? data[15] : 10, // Sens mouse. + data[16] > 0 ? data[16] : 10, // Sens scroll. + data[17] > 0 ? data[17] : 100, // Sens Y ratio. + data[18] <= 128 ? data[18] : data[18]-256, // Accel (unsigned to signed). ) } @@ -598,6 +605,12 @@ export class CtrlThumbstick extends CtrlSection { Number(this.deadzone_override), this.antideadzone, this.saturation, + this.outer_threshold, + Number(this.push_auto_toggle), + this.sens_mouse, + this.sens_scroll, + this.sens_xy_ratio, + this.accel_curve, ] } } diff --git a/src/lib/profile.ts b/src/lib/profile.ts index e92b652..4e0c2fd 100644 --- a/src/lib/profile.ts +++ b/src/lib/profile.ts @@ -6,6 +6,10 @@ import { SectionIndex, CtrlGyro, CtrlGyroAxis, CtrlHome } from 'lib/ctrl' import { ActionGroup } from 'lib/actions' import { HID } from 'lib/hid' +const getDefaultThumbstick = () => { + return new CtrlThumbstick(0, 0, 0, !!0, 0, 0, false, 0, 0, 80, false, 100, 10, 1, 0) +} + export class Profile { home: CtrlHome @@ -30,7 +34,7 @@ export class Profile { public buttonR2: CtrlButton = new CtrlButton(0, 0, 0), public buttonR4: CtrlButton = new CtrlButton(0, 0, 0), // Left stick. - public settingsLStick: CtrlThumbstick = new CtrlThumbstick(0, 0, 0, 0, 0, 0, false, 0, 0), + public settingsLStick: CtrlThumbstick = getDefaultThumbstick(), public buttonLStickLeft: CtrlButton = new CtrlButton(0, 0, 0), public buttonLStickRight: CtrlButton = new CtrlButton(0, 0, 0), public buttonLStickUp: CtrlButton = new CtrlButton(0, 0, 0), @@ -43,7 +47,7 @@ export class Profile { public buttonLStickInner: CtrlButton = new CtrlButton(0, 0, 0), public buttonLStickOuter: CtrlButton = new CtrlButton(0, 0, 0), // Right stick (stick or dhat). - public settingsRStick: CtrlThumbstick = new CtrlThumbstick(0, 0, 0, 0, 0, 0, false, 0, 0), + public settingsRStick: CtrlThumbstick = getDefaultThumbstick(), public buttonRStickLeft: CtrlButton = new CtrlButton(0, 0, 0), public buttonRStickRight: CtrlButton = new CtrlButton(0, 0, 0), public buttonRStickUp: CtrlButton = new CtrlButton(0, 0, 0), diff --git a/src/lib/profiles.ts b/src/lib/profiles.ts index bcbf839..1441541 100644 --- a/src/lib/profiles.ts +++ b/src/lib/profiles.ts @@ -14,7 +14,6 @@ import { CtrlThumbstick, CtrlSection, ThumbstickMode, - ThumbstickDistanceMode } from 'lib/ctrl' const NUMBER_OF_PROFILES = 13 // Home + 12 builtin. @@ -180,12 +179,18 @@ export class Profiles { sections[0].profileIndex, SectionIndex.RSTICK_SETTINGS, ThumbstickMode.DIR8, - ThumbstickDistanceMode.AXIAL, + false, // Distance mode / Ignore misalignment. 60, // Deadzone. 50, // Axis overlap (unsigned to signed). true, // Deadzone override. 0, // Antideadzone. 70, // Saturation. + 80, // Outer threshold. + false, // Push auto-toggle. + 100, // Sens mouse. + 10, // Sens scroll. + 100, // Sens Y ratio. + 0, // Accel. ) sections.push(rStickSection) }