Show progress in range calibration modal and show warning when calibration failed

This commit is contained in:
Mathias Malmqvist
2025-09-10 01:59:51 +02:00
committed by dualshock-tools
parent 7a52f1eab8
commit 68bfe1388b
4 changed files with 248 additions and 10 deletions

View File

@@ -21,6 +21,8 @@ const app = {
disable_btn: 0,
last_disable_btn: 0,
shownRangeCalibrationWarning: false,
// Language and UI state
lang_orig_text: {},
lang_orig_text: {},
@@ -669,6 +671,19 @@ function get_current_test_tab() {
return activeBtn?.id || 'haptic-test-tab';
}
function detectFailedRangeCalibration(changes) {
if (!changes.sticks || app.shownRangeCalibrationWarning) return;
const { left, right } = changes.sticks;
const failedCalibration = [left, right].some(({x, y}) => Math.abs(x) + Math.abs(y) == 2);
const hasOpenModals = document.querySelectorAll('.modal.show').length > 0;
if (failedCalibration && !app.shownRangeCalibrationWarning && !hasOpenModals) {
app.shownRangeCalibrationWarning = true;
show_popup(l("Range calibration appears to have failed. Please try again and make sure you rotate the sticks."));
}
}
// Callback function to handle UI updates after controller input processing
function handleControllerInput({ changes, inputConfig, touchPoints, batteryStatus }) {
const { buttonMap } = inputConfig;
@@ -689,6 +704,7 @@ function handleControllerInput({ changes, inputConfig, touchPoints, batteryStatu
update_stick_graphics(changes);
update_ds_button_svg(changes, buttonMap);
update_touchpad_circles(touchPoints);
detectFailedRangeCalibration(changes);
}
break;
@@ -984,6 +1000,7 @@ window.calibrate_range = () => calibrate_range(
resetStickDiagrams();
successAlert(message);
switchToRangeMode();
app.shownRangeCalibrationWarning = false
}
}
);

View File

@@ -1,14 +1,42 @@
'use strict';
import { sleep } from '../utils.js';
import { l } from '../translations.js';
import { CIRCULARITY_DATA_SIZE } from '../stick-renderer.js';
const SECONDS_UNTIL_UNLOCK = 15;
/**
* Calibrate Stick Range Modal Class
* Handles stick range calibration
*/
export class CalibRangeModal {
constructor(controllerInstance, doneCallback = null) {
constructor(controllerInstance, { resetStickDiagrams, successAlert, ll_data, rr_data }, doneCallback = null) {
// Dependencies
this.controller = controllerInstance;
this.resetStickDiagrams = resetStickDiagrams;
this.successAlert = successAlert;
this.ll_data = ll_data;
this.rr_data = rr_data;
// Progress tracking
this.buttonText = l("Done");
this.leftNonZeroCount = 0;
this.rightNonZeroCount = 0;
this.leftFullCycles = 0;
this.rightFullCycles = 0;
this.requiredFullCycles = 4;
this.progressUpdateInterval = null;
// Countdown timer
this.countdownSeconds = 0;
this.countdownInterval = null;
// Progress alert enhancement
this.leftCycleProgress = 0;
this.rightCycleProgress = 0;
this.allDonePromiseResolve = undefined;
this.doneCallback = doneCallback;
}
@@ -16,13 +44,30 @@ export class CalibRangeModal {
if(!this.controller.isConnected())
return;
$('#range-calibration-alert').hide();
$('#keep-rotating-alert').removeClass('blink-text');
$('#range-done-btn')
.prop('disabled', true)
.toggleClass('btn-primary', false)
.toggleClass('btn-outline-primary', true);
bootstrap.Modal.getOrCreateInstance('#rangeModal').show();
this.resetStickDiagrams();
this.updateProgress(); // reset progress bar
this.startProgressMonitoring();
this.resetAlertEnhancement();
this.startCountdown();
await sleep(1000);
await this.controller.calibrateRangeBegin();
}
async onClose() {
this.stopProgressMonitoring();
this.stopCountdown();
this.resetStickDiagrams();
bootstrap.Modal.getOrCreateInstance('#rangeModal').hide();
const result = await this.controller.calibrateRangeOnClose();
@@ -31,31 +76,177 @@ export class CalibRangeModal {
if (this.doneCallback && typeof this.doneCallback === 'function') {
this.doneCallback(true, result?.message);
}
this.allDonePromiseResolve();
}
/**
* Start monitoring progress by checking ll_data and rr_data arrays
*/
startProgressMonitoring() {
this.progressUpdateInterval = setInterval(() => {
this.checkDataProgress();
}, 100); // Check every 100ms
}
/**
* Stop progress monitoring
*/
stopProgressMonitoring() {
if (this.progressUpdateInterval) {
clearInterval(this.progressUpdateInterval);
this.progressUpdateInterval = null;
}
}
/**
* Start countdown timer for Done button
*/
startCountdown() {
this.countdownSeconds = SECONDS_UNTIL_UNLOCK;
this.updateCountdownButton();
// Every second, update countdown
this.countdownInterval = setInterval(() => {
this.countdownSeconds--;
if (this.countdownSeconds <= 0 || this.leftCycleProgress + this.rightCycleProgress >= 100) {
this.stopCountdown();
$('#range-calibration-alert').hide();
$('#range-done-btn')
.prop('disabled', false)
.toggleClass('btn-primary', true)
.toggleClass('btn-outline-primary', false);
this.updateCountdownButton();
} else {
this.checkAndEnhanceAlert();
}
this.updateCountdownButton();
}, 1000);
}
/**
* Stop countdown timer
*/
stopCountdown() {
if (!this.countdownInterval) return;
clearInterval(this.countdownInterval);
this.countdownInterval = null;
this.countdownSeconds = 0;
this.updateCountdownButton();
}
/**
* Update countdown button text and state
*/
updateCountdownButton() {
const seconds = this.countdownSeconds;
const text = this.buttonText + (seconds > 0 ? ` (${seconds})` : "");
$('#range-done-btn').text(text);
}
/**
* Check if ll_data and rr_data have received data
*/
checkDataProgress() {
const JOYSTICK_EXTREME_THRESHOLD = 0.95;
const CIRCLE_FILL_THRESHOLD = 0.95;
// Count the number of times the joysticks have been rotated full circle
const leftNonZeroCount = this.ll_data.filter(v => v > JOYSTICK_EXTREME_THRESHOLD).length
const leftFillRatio = leftNonZeroCount / CIRCULARITY_DATA_SIZE;
if (leftFillRatio >= CIRCLE_FILL_THRESHOLD) {
this.leftFullCycles++;
this.ll_data.fill(0);
}
const rightNonZeroCount = this.rr_data.filter(v => v > JOYSTICK_EXTREME_THRESHOLD).length;
const rightFillRatio = rightNonZeroCount / CIRCULARITY_DATA_SIZE;
if (rightFillRatio >= CIRCLE_FILL_THRESHOLD) {
this.rightFullCycles++;
this.rr_data.fill(0);
}
// Update progress if counts changed
if (leftNonZeroCount !== this.leftNonZeroCount || rightNonZeroCount !== this.rightNonZeroCount) {
this.leftNonZeroCount = leftNonZeroCount;
this.rightNonZeroCount = rightNonZeroCount;
this.updateProgress();
}
}
/**
* Update the progress bar and enable/disable Done button
*/
updateProgress() {
// Calculate progress based on full cycles completed
// Each stick needs to complete 6 full cycles to contribute 50% to total progress
const leftCycleProgress = Math.min(this.leftFullCycles / this.requiredFullCycles, 1) * 50;
const rightCycleProgress = Math.min(this.rightFullCycles / this.requiredFullCycles, 1) * 50;
this.leftCycleProgress = leftCycleProgress;
this.rightCycleProgress = rightCycleProgress;
// Add current partial progress for visual feedback
const leftCurrentProgress = (this.leftNonZeroCount / CIRCULARITY_DATA_SIZE) * (50 / this.requiredFullCycles);
const rightCurrentProgress = (this.rightNonZeroCount / CIRCULARITY_DATA_SIZE) * (50 / this.requiredFullCycles);
const totalProgress = Math.round(leftCycleProgress + rightCycleProgress + leftCurrentProgress + rightCurrentProgress);
// this.totalProgress = totalProgress;
const $progressBar = $('#range-progress-bar');
const $progressText = $('#range-progress-text');
$progressBar
.css('width', `${totalProgress}%`)
.attr('aria-valuenow', totalProgress);
$progressText.text(`${totalProgress}% (L:${this.leftFullCycles}/${this.requiredFullCycles}, R:${this.rightFullCycles}/${this.requiredFullCycles})`);
}
checkAndEnhanceAlert() {
const secondsElapsed = SECONDS_UNTIL_UNLOCK - this.countdownSeconds;
const alertIsVisible = $('#range-calibration-alert').is(":visible")
const progressBelowThreshold = this.leftCycleProgress < 10 || this.rightCycleProgress < 10;
if (secondsElapsed > 5 && progressBelowThreshold && !alertIsVisible) {
$('#range-calibration-alert').show();
}
const isBlinking = $('#keep-rotating-alert').hasClass('blink-text');
if (secondsElapsed > 10 && progressBelowThreshold && !isBlinking) {
$('#keep-rotating-alert').addClass('blink-text');
}
}
resetAlertEnhancement() {
$('#keep-rotating-alert').removeClass('blink-text');
}
}
// Global reference to the current range calibration instance
let currentCalibRangeInstance = null;
/**
* Helper function to safely clear the current calibration instance
*/
function destroyCurrentInstance() {
currentCalibRangeInstance = null;
}
// Legacy function exports for backward compatibility
export async function calibrate_range(controller, doneCallback = null) {
export async function calibrate_range(controller, dependencies, doneCallback = null) {
destroyCurrentInstance(); // Clean up any existing instance
currentCalibRangeInstance = new CalibRangeModal(controller, doneCallback);
currentCalibRangeInstance = new CalibRangeModal(controller, dependencies, doneCallback);
await currentCalibRangeInstance.open();
return new Promise((resolve) => {
currentCalibRangeInstance.allDonePromiseResolve = resolve;
});
}
async function calibrate_range_on_close() {
if (currentCalibRangeInstance) {
await currentCalibRangeInstance.onClose();
destroyCurrentInstance();
}
}
// Legacy compatibility - expose functions to window for HTML onclick handlers
// Expose functions to window for HTML onclick handlers
window.calibrate_range_on_close = calibrate_range_on_close;