Add stick dials to the range calibration modal

This commit is contained in:
Mathias Malmqvist
2025-10-29 20:14:39 +01:00
parent 35f59f3d92
commit ba10cfcbdd
7 changed files with 148 additions and 14 deletions

View File

@@ -114,6 +114,16 @@ dl.row dd {
animation: blink 1s infinite;
}
/* Pulsing animation for text */
@keyframes pulse-text {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.pulsing-text {
animation: pulse-text 0.75s ease-in-out infinite;
}
/* Set text color to red for internationalized elements */
/* .ds-i18n {
color: red;

View File

@@ -180,7 +180,7 @@
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="setCenterCalibrationMethod('quick', event)">
<i class="fas fa-check me-2" id="check-quick" style="visibility: hidden;"></i><span class="ds-i18n">Use quick calibration</span>
<i class="fas fa-check me-2" id="check-quick" style="display: none;"></i><span class="ds-i18n">Use quick calibration</span>
</a></li>
<li><a class="dropdown-item" href="#" onclick="setCenterCalibrationMethod('four-step', event)">
<i class="fas fa-check me-2" id="check-four-step"></i><span class="ds-i18n">Use four-step calibration</span>
@@ -200,7 +200,7 @@
<i class="fas fa-check me-2" id="check-range-normal"></i><span class="ds-i18n">Use normal mode</span>
</a></li>
<li><a class="dropdown-item" href="#" onclick="setRangeCalibrationMethod('expert', event)">
<i class="fas fa-check me-2" id="check-range-expert" style="visibility: hidden;"></i><span class="ds-i18n">Use expert mode</span>
<i class="fas fa-check me-2" id="check-range-expert" style="display: none;"></i><span class="ds-i18n">Use expert mode</span>
</a></li>
</ul>
</div>

View File

@@ -5,10 +5,10 @@ import { initControllerManager } from './controller-manager.js';
import ControllerFactory from './controllers/controller-factory.js';
import { lang_init, l } from './translations.js';
import { loadAllTemplates } from './template-loader.js';
import { draw_stick_position, CIRCULARITY_DATA_SIZE } from './stick-renderer.js';
import { draw_stick_dial, CIRCULARITY_DATA_SIZE } from './stick-renderer.js';
import { ds5_finetune, isFinetuneVisible, finetune_handle_controller_input } from './modals/finetune-modal.js';
import { calibrate_stick_centers, auto_calibrate_stick_centers } from './modals/calib-center-modal.js';
import { calibrate_range } from './modals/calib-range-modal.js';
import { calibrate_range, rangeCalibHandleControllerInput } from './modals/calib-range-modal.js';
import {
show_quick_test_modal,
isQuickTestVisible,
@@ -526,14 +526,14 @@ function refresh_stick_pos() {
const enable_circ_test = circ_checked();
// Draw left stick
draw_stick_position(ctx, hb, yb, sz, plx, ply, {
draw_stick_dial(ctx, hb, yb, sz, plx, ply, {
circularity_data: enable_circ_test ? ll_data : null,
enable_zoom_center,
});
if(!hasSingleStick) {
// Draw right stick
draw_stick_position(ctx, w-hb, yb, sz, prx, pry, {
draw_stick_dial(ctx, w-hb, yb, sz, prx, pry, {
circularity_data: enable_circ_test ? rr_data : null,
enable_zoom_center,
});
@@ -763,10 +763,23 @@ function detectFailedRangeCalibration(changes) {
}
}
function isRangeCalibrationVisible() {
const modal = document.getElementById('rangeModal');
if (!modal) return false;
return modal.classList.contains('show');
}
// Callback function to handle UI updates after controller input processing
function handleControllerInput({ changes, inputConfig, touchPoints, batteryStatus }) {
const { buttonMap } = inputConfig;
// Update range calibration modal stick visualization if visible
if (isRangeCalibrationVisible() && changes.sticks) {
collectCircularityData(changes.sticks, ll_data, rr_data);
rangeCalibHandleControllerInput(changes);
return;
}
// Handle Quick Test Modal input (can be open from any tab)
if (isQuickTestVisible()) {
quicktest_handle_controller_input(changes, batteryStatus);

View File

@@ -2,10 +2,9 @@
import { sleep } from '../utils.js';
import { l } from '../translations.js';
import { CIRCULARITY_DATA_SIZE } from '../stick-renderer.js';
import { CIRCULARITY_DATA_SIZE, draw_stick_dial } from '../stick-renderer.js';
const SECONDS_UNTIL_UNLOCK = 15;
const EXPERT_MODE_STORAGE_KEY = 'rangeCalibExpertMode';
/**
* Calibrate Stick Range Modal Class
@@ -41,6 +40,16 @@ export class CalibRangeModal {
this.doneCallback = doneCallback;
this.hasSingleStick = (this.controller.currentController.getNumberOfSticks() == 1);
// Stick rendering
this.leftCanvasCtx = null;
this.rightCanvasCtx = null;
this.stickRenderInterval = null;
this.currentStickPositions = {
left: { x: 0, y: 0 },
right: { x: 0, y: 0 }
};
this._initEventListeners();
}
@@ -78,6 +87,8 @@ export class CalibRangeModal {
this.ll_data.fill(0);
this.rr_data.fill(0);
this._initializeCanvases();
this._updateUIVisibility();
if (!this.expertMode) {
this.updateProgress(); // reset progress bar
@@ -92,6 +103,7 @@ export class CalibRangeModal {
}
async onClose() {
this.stopStickRendering();
this.stopProgressMonitoring();
this.stopCountdown();
@@ -292,6 +304,89 @@ export class CalibRangeModal {
$('#range-progress-text-container').show();
}
}
/**
* Initialize canvas elements for stick rendering
*/
_initializeCanvases() {
const leftCanvas = document.getElementById('range-left-stick-canvas');
const rightCanvas = document.getElementById('range-right-stick-canvas');
this.leftCanvasCtx = leftCanvas.getContext('2d');
this.rightCanvasCtx = rightCanvas.getContext('2d');
// Clear initial canvases
this._clearCanvas(this.leftCanvasCtx, leftCanvas);
this._clearCanvas(this.rightCanvasCtx, rightCanvas);
// Start rendering loop
this.startStickRendering();
}
/**
* Clear a canvas with white background
*/
_clearCanvas(ctx, canvas) {
if (!ctx) return;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
/**
* Update current stick positions for rendering
*/
handleControllerInput({sticks}) {
if (sticks?.left) {
this.currentStickPositions.left = { ...sticks.left };
}
if (sticks?.right) {
this.currentStickPositions.right = { ...sticks.right };
}
}
/**
* Start stick rendering loop
*/
startStickRendering() {
if (this.stickRenderInterval) return;
this.stickRenderInterval = setInterval(() => {
this._renderSticks();
}, 16); // ~60 FPS
}
/**
* Stop stick rendering loop
*/
stopStickRendering() {
if (this.stickRenderInterval) {
clearInterval(this.stickRenderInterval);
this.stickRenderInterval = null;
}
}
/**
* Render both stick dials
*/
_renderSticks() {
if (!this.leftCanvasCtx || !this.rightCanvasCtx) return;
const leftCanvas = this.leftCanvasCtx.canvas;
const rightCanvas = this.rightCanvasCtx.canvas;
// Clear canvases
this._clearCanvas(this.leftCanvasCtx, leftCanvas);
this._clearCanvas(this.rightCanvasCtx, rightCanvas);
// Draw stick dials in normal mode (no circularity data, no zoom)
const size = 60;
const centerX = leftCanvas.width / 2;
const centerY = leftCanvas.height / 2;
const {left, right} = this.currentStickPositions;
draw_stick_dial(this.leftCanvasCtx, centerX, centerY, size, left.x, left.y);
draw_stick_dial(this.rightCanvasCtx, centerX, centerY, size, right.x, right.y);
}
}
// Global reference to the current range calibration instance
@@ -321,5 +416,11 @@ async function calibrate_range_on_close() {
}
}
export function rangeCalibHandleControllerInput(changes) {
if (currentCalibRangeInstance) {
currentCalibRangeInstance.handleControllerInput(changes);
}
}
// Expose functions to window for HTML onclick handlers
window.calibrate_range_on_close = calibrate_range_on_close;

View File

@@ -1,6 +1,6 @@
'use strict';
import { draw_stick_position } from '../stick-renderer.js';
import { draw_stick_dial } from '../stick-renderer.js';
import { dec2hex32, float_to_str, la } from '../utils.js';
import { auto_calibrate_stick_centers } from './calib-center-modal.js';
import { calibrate_range } from './calib-range-modal.js';
@@ -698,13 +698,13 @@ export class Finetune {
if (this._mode === 'circularity') {
// Draw stick position with circle
const circularityData = lOrR === 'left' ? this.ll_data : this.rr_data;
draw_stick_position(ctx, hb, yb, sz, plx, ply, {
draw_stick_dial(ctx, hb, yb, sz, plx, ply, {
circularity_data: circularityData,
highlight
});
} else {
// Draw stick position with crosshair
draw_stick_position(ctx, hb, yb, sz, plx, ply, {
draw_stick_dial(ctx, hb, yb, sz, plx, ply, {
enable_zoom_center: true,
highlight
});

View File

@@ -16,7 +16,7 @@ export const CIRCULARITY_DATA_SIZE = 48; // Number of angular positions to sampl
* @param {boolean} opts.enable_zoom_center - Whether to apply center zoom transformation
* @param {boolean} opts.highlight - Whether to highlight the stick position
*/
export function draw_stick_position(ctx, center_x, center_y, sz, stick_x, stick_y, opts = {}) {
export function draw_stick_dial(ctx, center_x, center_y, sz, stick_x, stick_y, opts = {}) {
const { circularity_data = null, enable_zoom_center = false, highlight } = opts;
// Draw base circle

View File

@@ -6,9 +6,19 @@
<h1 class="modal-title fs-5 ds-i18n" id="staticBackdropLabel">Range calibration</h1>
</div>
<div class="modal-body">
<p class="ds-i18n"><b>The controller is now sampling data!</b></p>
<p class="ds-i18n"><b class="pulsing-text">The controller is now sampling data!</b></p>
<p class="ds-i18n">Rotate the sticks slowly at least 2 times in one direction and 2 times in the other direction to cover the whole range.</p>
<div class="progress mt-3" id="range-progress-container" role="progressbar" aria-label="Range calibration progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div style="display: flex; justify-content: center; gap: 0px;">
<div style="text-align: center; margin-right: 0px;">
<canvas id="range-left-stick-canvas" width="150" height="150"></canvas>
</div>
<div style="text-align: center; margin-left: 0px;">
<canvas id="range-right-stick-canvas" width="150" height="150"></canvas>
</div>
</div>
<div class="progress mt-2" id="range-progress-container" role="progressbar" aria-label="Range calibration progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div id="range-progress-bar" class="progress-bar" style="width:0%"></div>
</div>
<div class="mt-2" id="range-progress-text-container">