diff --git a/assets/icons.svg b/assets/icons.svg
index 8f00101..61870ac 100644
--- a/assets/icons.svg
+++ b/assets/icons.svg
@@ -24,4 +24,7 @@
+
+
+
diff --git a/css/finetune.css b/css/finetune.css
index 9938159..beeca9d 100644
--- a/css/finetune.css
+++ b/css/finetune.css
@@ -113,4 +113,33 @@ input[id^="finetune"] {
#finetuneModal:not(.hide-raw-numbers) #finetuneStickCanvasL_large,
#finetuneModal:not(.hide-raw-numbers) #finetuneStickCanvasR_large {
display: none;
+}
+
+/* Error slack button toggle states */
+.left-stick-values,
+.right-stick-values {
+ display: block;
+}
+
+.left-stick-slider,
+.right-stick-slider {
+ display: none;
+}
+
+/* When left stick is in slider mode (only in circularity mode) */
+#finetuneModal.circularity-mode #left-stick-card.show-slider .left-stick-values {
+ display: none !important;
+}
+
+#finetuneModal.circularity-mode #left-stick-card.show-slider .left-stick-slider.finetune-circularity-mode {
+ display: block !important;
+}
+
+/* When right stick is in slider mode (only in circularity mode) */
+#finetuneModal.circularity-mode #right-stick-card.show-slider .right-stick-values {
+ display: none !important;
+}
+
+#finetuneModal.circularity-mode #right-stick-card.show-slider .right-stick-slider.finetune-circularity-mode {
+ display: block !important;
}
\ No newline at end of file
diff --git a/js/modals/finetune-modal.js b/js/modals/finetune-modal.js
index a83bb6c..37a67e8 100644
--- a/js/modals/finetune-modal.js
+++ b/js/modals/finetune-modal.js
@@ -17,23 +17,41 @@ export class Finetune {
this.active_stick = null; // 'left', 'right', or null
this._centerStepSize = 5; // Default step size for center mode
this._circularityStepSize = 5; // Default step size for circularity mode
-
+
// Dependencies
this.controller = null;
this.ll_data = null;
this.rr_data = null;
this.clearCircularity = null;
-
+
// Closure functions
this.refresh_finetune_sticks = this._createRefreshSticksThrottled();
this.update_finetune_warning_messages = this._createUpdateWarningMessagesClosure();
this.flash_finetune_warning = this._createFlashWarningClosure();
-
+
// Continuous adjustment state
this.continuous_adjustment = {
initial_delay: null,
repeat_delay: null,
};
+
+ // Track previous slider values for incremental adjustments
+ this._previousSliderValues = {
+ left: 0,
+ right: 0
+ };
+
+ // Store base values when slider adjustment starts
+ this._sliderBaseValues = {
+ left: null,
+ right: null
+ };
+
+ // Track slider usage state for undo functionality
+ this._sliderUsed = {
+ left: false,
+ right: false
+ };
}
get mode() {
@@ -71,7 +89,7 @@ export class Finetune {
this._initEventListeners();
this._restoreShowRawNumbersCheckbox();
this._restoreStepSizeFromLocalStorage();
-
+
// Lock NVS before
const nv = await this.controller.queryNvStatus();
if(!nv.locked) {
@@ -110,6 +128,9 @@ export class Finetune {
this.original_data = data;
+ // Update error slack button states
+ this._updateErrorSlackButtonStates();
+
this.refresh_finetune_sticks();
}
@@ -149,6 +170,14 @@ export class Finetune {
$('#finetuneModal').on('hidden.bs.modal', () => {
console.log("Finetune modal hidden event triggered");
+ // Reset circularity sliders to zero when modal closes
+ $('#leftCircularitySlider').val(0);
+ $('#rightCircularitySlider').val(0);
+
+ // Reset slider used states
+ this._sliderUsed.left = false;
+ this._sliderUsed.right = false;
+
destroyCurrentInstance();
});
@@ -158,6 +187,62 @@ export class Finetune {
const stepSize = parseInt($(e.target).data('step'));
this.stepSize = stepSize;
});
+
+ // Circularity slider event listeners
+ $('#leftCircularitySlider').on('input', (e) => {
+ console.log('Left circularity slider changed to:', e.target.value);
+ this._onCircularitySliderChange('left', parseInt(e.target.value));
+ });
+
+ $('#rightCircularitySlider').on('input', (e) => {
+ console.log('Right circularity slider changed to:', e.target.value);
+ this._onCircularitySliderChange('right', parseInt(e.target.value));
+ });
+
+ // Circularity slider start event listeners (when user starts dragging)
+ $('#leftCircularitySlider').on('mousedown touchstart', (e) => {
+ this._onCircularitySliderStart('left', parseInt(e.target.value));
+ });
+
+ $('#rightCircularitySlider').on('mousedown touchstart', (e) => {
+ this._onCircularitySliderStart('right', parseInt(e.target.value));
+ });
+
+ // Circularity slider release event listeners
+ $('#leftCircularitySlider').on('change', (e) => {
+ this._onCircularitySliderRelease('left');
+ });
+
+ $('#rightCircularitySlider').on('change', (e) => {
+ this._onCircularitySliderRelease('right');
+ });
+
+ // Reset button event listeners
+ $('#leftCircularityResetBtn').on('click', () => {
+ this._resetCircularitySlider('left');
+ });
+
+ $('#rightCircularityResetBtn').on('click', () => {
+ this._resetCircularitySlider('right');
+ });
+
+ // Error slack button event listeners
+ $('#leftErrorSlackBtn').on('click', () => {
+ this._onErrorSlackButtonClick('left');
+ });
+
+ $('#rightErrorSlackBtn').on('click', () => {
+ this._onErrorSlackButtonClick('right');
+ });
+
+ // Error slack undo button event listeners
+ $('#leftErrorSlackUndoBtn').on('click', () => {
+ this._onErrorSlackUndoButtonClick('left');
+ });
+
+ $('#rightErrorSlackUndoBtn').on('click', () => {
+ this._onErrorSlackUndoButtonClick('right');
+ });
}
/**
@@ -179,6 +264,26 @@ export class Finetune {
$('#finetuneModal').off('hidden.bs.modal');
$('.dropdown-item[data-step]').off('click');
+
+ // Remove circularity slider event listeners
+ $('#leftCircularitySlider').off('input');
+ $('#rightCircularitySlider').off('input');
+ $('#leftCircularitySlider').off('mousedown touchstart');
+ $('#rightCircularitySlider').off('mousedown touchstart');
+ $('#leftCircularitySlider').off('change');
+ $('#rightCircularitySlider').off('change');
+
+ // Remove reset button event listeners
+ $('#leftCircularityResetBtn').off('click');
+ $('#rightCircularityResetBtn').off('click');
+
+ // Remove error slack button event listeners
+ $('#leftErrorSlackBtn').off('click');
+ $('#rightErrorSlackBtn').off('click');
+
+ // Remove error slack undo button event listeners
+ $('#leftErrorSlackUndoBtn').off('click');
+ $('#rightErrorSlackUndoBtn').off('click');
}
/**
@@ -240,7 +345,20 @@ export class Finetune {
* Set the finetune mode
*/
setMode(mode) {
- this.mode = mode;
+ this._mode = mode;
+ this._updateUI();
+
+ // Reset toggle states when switching modes
+ if (mode === 'center') {
+ $('#left-stick-card').removeClass('show-slider');
+ $('#right-stick-card').removeClass('show-slider');
+
+ // Reset slider used states and update buttons back to slack
+ this._sliderUsed.left = false;
+ this._sliderUsed.right = false;
+ this._showErrorSlackButton('left');
+ this._showErrorSlackButton('right');
+ }
}
/**
@@ -255,6 +373,12 @@ export class Finetune {
this.stopContinuousDpadAdjustment();
this._clearFinetuneAxisHighlights();
+ // Hide slider on the previously active stick (when it becomes inactive)
+ if (this.active_stick && this._mode === 'circularity') {
+ const previousStickCard = $(`#${this.active_stick}-stick-card`);
+ previousStickCard.removeClass('show-slider');
+ }
+
this.active_stick = stick;
const other_stick = stick === 'left' ? 'right' : 'left';
@@ -301,6 +425,9 @@ export class Finetune {
// Update step size UI when mode changes
this._updateStepSizeUI();
+
+ // Update error slack button states when mode changes
+ this._updateErrorSlackButtonStates();
}
async _onFinetuneChange() {
@@ -352,6 +479,7 @@ export class Finetune {
this.update_finetune_warning_messages();
this._highlightActiveFinetuneAxis();
+ this._updateErrorSlackButtonStates();
timeout = null;
}, 10);
@@ -763,6 +891,380 @@ export class Finetune {
this._updateStepSizeUI();
}
+
+ /**
+ * Reset circularity sliders to zero position
+ */
+ _resetCircularitySliders() {
+ $('#leftCircularitySlider').val(0);
+ $('#rightCircularitySlider').val(0);
+ }
+
+ /**
+ * Handle the start of circularity slider adjustment
+ * Store base values and reset previous slider value
+ */
+ _onCircularitySliderStart(stick, value) {
+ console.log(`Slider start for ${stick} stick, value: ${value}`);
+
+ // Store the base values when slider adjustment starts
+ const suffixes = stick === 'left' ? ['LL', 'LT', 'LR', 'LB'] : ['RL', 'RT', 'RR', 'RB'];
+ const baseValues = {};
+
+ suffixes.forEach(suffix => {
+ const element = $(`#finetune${suffix}`);
+ baseValues[suffix] = parseInt(element.val()) || 0;
+ });
+
+ this._sliderBaseValues[stick] = baseValues;
+ this._previousSliderValues[stick] = value;
+
+ // Store base values for ll_data and rr_data arrays
+ if (stick === 'left' && this.ll_data && Array.isArray(this.ll_data)) {
+ this._sliderBaseValues[stick].ll_data = [...this.ll_data]; // Create a copy
+ } else if (stick === 'right' && this.rr_data && Array.isArray(this.rr_data)) {
+ this._sliderBaseValues[stick].rr_data = [...this.rr_data]; // Create a copy
+ }
+
+ console.log(`Base values stored for ${stick}:`, baseValues);
+ }
+
+ /**
+ * Handle circularity slider changes with incremental adjustments
+ */
+ _onCircularitySliderChange(stick, value) {
+ this._isSliderAdjusting = true;
+
+ try {
+ // Debug: Log the data structure
+ console.log(`Slider change for ${stick} stick, value: ${value}`);
+ console.log('ll_data:', this.ll_data);
+ console.log('rr_data:', this.rr_data);
+
+ // If we don't have base values, treat this as the start
+ if (!this._sliderBaseValues[stick]) {
+ this._onCircularitySliderStart(stick, value);
+ return;
+ }
+
+ // Calculate the incremental change from the previous slider position
+ const previousValue = this._previousSliderValues[stick];
+ const deltaValue = value - previousValue;
+
+ // If no change, return early
+ if (deltaValue === 0) {
+ return;
+ }
+
+ // Get the base values and suffixes for the current stick
+ const suffixes = stick === 'left' ? ['LL', 'LT', 'LR', 'LB'] : ['RL', 'RT', 'RR', 'RB'];
+ const baseValues = this._sliderBaseValues[stick];
+
+ // Calculate the total adjustment based on slider value from 0
+ // Value 0-100 maps to adjustment range (we'll use a reasonable range)
+ const maxAdjustment = 100; // Adjust this value as needed
+ const totalAdjustment = (value / 100) * maxAdjustment;
+
+ // Apply adjustments according to the requirements:
+ // LL, LT, RL, RT decrease by adjustment
+ // LR, LB, RR, RB increase by adjustment
+ suffixes.forEach(suffix => {
+ const element = $(`#finetune${suffix}`);
+ let newValue;
+
+ if (suffix === 'LL' || suffix === 'LT' || suffix === 'RL' || suffix === 'RT') {
+ // LL, LT, RL, RT decrease
+ newValue = Math.min(65535, baseValues[suffix] + totalAdjustment);
+ } else if (suffix === 'LR' || suffix === 'LB' || suffix === 'RR' || suffix === 'RB') {
+ // LR, LB, RR, RB increase
+ newValue = Math.max(0, baseValues[suffix] - totalAdjustment);
+ }
+
+ element.val(Math.round(newValue));
+ });
+
+ // Update ll_data and rr_data with incremental changes proportional to slider movement
+ const adjustmentConstant = 0.001; // Small constant for incremental adjustments
+ const totalAdjustmentFromBase = value * adjustmentConstant; // Total adjustment from slider position 0
+
+ if (stick === 'left' && this.ll_data && Array.isArray(this.ll_data)) {
+ const baseData = this._sliderBaseValues[stick].ll_data;
+ if (baseData && Array.isArray(baseData)) {
+ // Apply total adjustment from base values to maintain relative differences
+ for (let i = 0; i < this.ll_data.length; i++) {
+ this.ll_data[i] = Math.max(0, baseData[i] + totalAdjustmentFromBase);
+ }
+
+ // Convert polar coordinates to cartesian, trim to square, and convert back
+ this._trimCircularityDataToSquare(this.ll_data);
+ }
+ } else if (stick === 'right' && this.rr_data && Array.isArray(this.rr_data)) {
+ const baseData = this._sliderBaseValues[stick].rr_data;
+ if (baseData && Array.isArray(baseData)) {
+ // Apply total adjustment from base values to maintain relative differences
+ for (let i = 0; i < this.rr_data.length; i++) {
+ this.rr_data[i] = Math.max(0, baseData[i] + totalAdjustmentFromBase);
+ }
+
+ // Convert polar coordinates to cartesian, trim to square, and convert back
+ this._trimCircularityDataToSquare(this.rr_data);
+ }
+ }
+
+ // Update previous slider value
+ this._previousSliderValues[stick] = value;
+
+ // Don't trigger _onFinetuneChange during slider movement - only on release
+ // this._onFinetuneChange();
+
+ // Refresh the stick displays to show updated circularity data
+ this.refresh_finetune_sticks();
+
+ } finally {
+ this._isSliderAdjusting = false;
+ }
+ }
+
+ /**
+ * Handle slider release - clear ll_data and rr_data
+ * @param {string} stick - 'left' or 'right'
+ */
+ _onCircularitySliderRelease(stick) {
+ console.log(`Circularity slider released for ${stick} stick`);
+
+ // Mark that this slider has been used
+ this._sliderUsed[stick] = true;
+
+ // Clear the base values for this stick
+// this._sliderBaseValues[stick] = null;
+ // Note: Don't reset _previousSliderValues[stick] to 0 to maintain slider position
+
+ // Don't reset the slider to 0 - let it maintain its position
+ // $(`#${stick}CircularitySlider`).val(0);
+
+ // Clear the circularity data - zero out the array while maintaining its size
+ if (stick === 'left' && this.ll_data && Array.isArray(this.ll_data)) {
+ this.ll_data.fill(0);
+ } else if (stick === 'right' && this.rr_data && Array.isArray(this.rr_data)) {
+ this.rr_data.fill(0);
+ }
+
+ // Call the clearCircularity function to update the display
+ this.clearCircularity();
+
+ // Trigger the change event to update the finetune data once when slider is released
+ this._onFinetuneChange();
+
+ // Toggle the slider off and change button to undo
+ const stickCard = $(`#${stick}-stick-card`);
+ stickCard.removeClass('show-slider');
+ this._showErrorSlackUndoButton(stick);
+
+ // Refresh the stick displays to show cleared circularity data
+ this.refresh_finetune_sticks();
+ }
+
+ /**
+ * Convert circularity data (polar radii) to cartesian coordinates,
+ * trim to a -1,-1 to 1,1 square, then convert back to polar radii
+ * @param {Array} data - Array of radius values representing sectors around a circle
+ */
+ _trimCircularityDataToSquare(data) {
+ const numSectors = data.length;
+
+ for (let i = 0; i < numSectors; i++) {
+ // Calculate angle for this sector
+ const angle = (i * 2 * Math.PI) / numSectors;
+ const radius = data[i];
+
+ // Convert polar to cartesian coordinates
+ const x = radius * Math.cos(angle);
+ const y = radius * Math.sin(angle);
+
+ // Trim to -1,-1 to 1,1 square
+ const trimmedX = Math.max(-1, Math.min(1, x));
+ const trimmedY = Math.max(-1, Math.min(1, y));
+
+ // Convert back to polar coordinates
+ const trimmedRadius = Math.sqrt(trimmedX * trimmedX + trimmedY * trimmedY);
+
+ // Update the data array with the trimmed radius
+ data[i] = trimmedRadius;
+ }
+ }
+
+ /**
+ * Reset circularity slider to zero and restore input values to their base state
+ * @param {string} stick - 'left' or 'right'
+ */
+ _resetCircularitySlider(stick) {
+ console.log(`Resetting circularity slider for ${stick} stick`);
+
+ // If we have base values stored, use them to reset properly
+ if (this._sliderBaseValues[stick]) {
+ // Reset the slider to zero first
+ $(`#${stick}CircularitySlider`).val(0);
+
+ // Trigger the slider change with value 0 to recalculate input values
+ this._onCircularitySliderChange(stick, 0);
+ } else {
+ // If no base values, just reset the slider
+ $(`#${stick}CircularitySlider`).val(0);
+ this._previousSliderValues[stick] = 0;
+ }
+
+ // Reset the slider used state and update button back to slack
+ this._sliderUsed[stick] = false;
+ this._showErrorSlackButton(stick);
+
+ // Clear the circularity data display
+ this.clearCircularity();
+
+ // Trigger the change event to update the finetune data
+ this._onFinetuneChange();
+
+ // Refresh the stick displays
+ this.refresh_finetune_sticks();
+ }
+
+ /**
+ * Check if data array contains only non-zero values
+ * @param {Array} data - The data array to check
+ * @returns {boolean} True if all values are non-zero, false otherwise
+ */
+ _hasOnlyNonZeroValues(data) {
+ if (!data || !Array.isArray(data)) {
+ return false;
+ }
+ return data.every(value => value !== 0);
+ }
+
+ /**
+ * Update the state of error slack buttons based on data content
+ */
+ _updateErrorSlackButtonStates() {
+ const leftHasData = this._hasOnlyNonZeroValues(this.ll_data);
+ const rightHasData = this._hasOnlyNonZeroValues(this.rr_data);
+
+ // Handle left stick buttons
+ const leftSlackBtn = $('#leftErrorSlackBtn');
+ const leftUndoBtn = $('#leftErrorSlackUndoBtn');
+
+ if (this._sliderUsed.left) {
+ // Show undo button, hide slack button
+ this._showErrorSlackUndoButton('left');
+ } else {
+ // Show slack button, hide undo button
+ this._showErrorSlackButton('left');
+
+ // Enable/disable slack button based on data
+ if (leftHasData) {
+ leftSlackBtn.prop('disabled', false);
+ leftSlackBtn.removeClass('disabled');
+ leftSlackBtn.attr('title', 'Apply error slack adjustment for left stick');
+ } else {
+ leftSlackBtn.prop('disabled', true);
+ leftSlackBtn.addClass('disabled');
+ leftSlackBtn.attr('title', 'Error slack requires circularity data (move stick in full circles first)');
+ }
+ }
+
+ // Handle right stick buttons
+ const rightSlackBtn = $('#rightErrorSlackBtn');
+ const rightUndoBtn = $('#rightErrorSlackUndoBtn');
+
+ if (this._sliderUsed.right) {
+ // Show undo button, hide slack button
+ this._showErrorSlackUndoButton('right');
+ } else {
+ // Show slack button, hide undo button
+ this._showErrorSlackButton('right');
+
+ // Enable/disable slack button based on data
+ if (rightHasData) {
+ rightSlackBtn.prop('disabled', false);
+ rightSlackBtn.removeClass('disabled');
+ rightSlackBtn.attr('title', 'Apply error slack adjustment for right stick');
+ } else {
+ rightSlackBtn.prop('disabled', true);
+ rightSlackBtn.addClass('disabled');
+ rightSlackBtn.attr('title', 'Error slack requires circularity data (move stick in full circles first)');
+ }
+ }
+ }
+
+ /**
+ * Handle error slack button click
+ * @param {string} stick - 'left' or 'right'
+ */
+ _onErrorSlackButtonClick(stick) {
+ console.log(`Error slack button clicked for ${stick} stick`);
+
+ // Only allow toggle in circularity mode
+ if (this._mode !== 'circularity') {
+ console.log('Error slack button only works in circularity mode');
+ return;
+ }
+
+ // Toggle between showing LX/LY values and circularity slider
+ const stickCard = $(`#${stick}-stick-card`);
+ const isShowingSlider = stickCard.hasClass('show-slider');
+
+ if (isShowingSlider) {
+ // Currently showing slider, switch to values
+ stickCard.removeClass('show-slider');
+ console.log(`Switched ${stick} stick to show LX/LY values`);
+ } else {
+ // Currently showing values, switch to slider
+ stickCard.addClass('show-slider');
+ console.log(`Switched ${stick} stick to show circularity slider`);
+ }
+ }
+
+ /**
+ * Handle error slack undo button click
+ * @param {string} stick - 'left' or 'right'
+ */
+ _onErrorSlackUndoButtonClick(stick) {
+ console.log(`Error slack undo button clicked for ${stick} stick`);
+
+ // Only allow undo in circularity mode
+ if (this._mode !== 'circularity') {
+ console.log('Error slack undo button only works in circularity mode');
+ return;
+ }
+
+ // Call reset function to undo the circularity adjustment
+ this._resetCircularitySlider(stick);
+ }
+
+ /**
+ * Show undo button and hide slack button
+ * @param {string} stick - 'left' or 'right'
+ */
+ _showErrorSlackUndoButton(stick) {
+ const slackBtn = $(`#${stick}ErrorSlackBtn`);
+ const undoBtn = $(`#${stick}ErrorSlackUndoBtn`);
+
+ // Add a class to hide slack button and show undo button
+ slackBtn.addClass('d-none');
+ undoBtn.removeClass('d-none');
+ undoBtn.attr('title', `Undo circularity adjustment for ${stick} stick`);
+ }
+
+ /**
+ * Show slack button and hide undo button
+ * @param {string} stick - 'left' or 'right'
+ */
+ _showErrorSlackButton(stick) {
+ const slackBtn = $(`#${stick}ErrorSlackBtn`);
+ const undoBtn = $(`#${stick}ErrorSlackUndoBtn`);
+
+ // Add a class to hide undo button and show slack button
+ undoBtn.addClass('d-none');
+ slackBtn.removeClass('d-none');
+ slackBtn.attr('title', `Apply error slack adjustment for ${stick} stick`);
+ }
}
// Global reference to the current finetune instance
diff --git a/templates/finetune-modal.html b/templates/finetune-modal.html
index b7ecc8b..00cfdf7 100644
--- a/templates/finetune-modal.html
+++ b/templates/finetune-modal.html
@@ -48,8 +48,16 @@
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+ 0
+ 100
+
+
+
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+ 0
+ 100
+
+
+