()
| 905 | settings.cylinderRadius = Math.max(0.1, getEffectiveCylinderRadius() * factor); |
| 906 | _scheduleCylinderPanelRedraw(); |
| 907 | _scheduleCylinderPreviewUpdate(); |
| 908 | // Wheel is a discrete gesture — persist the new value without waiting for |
| 909 | // a drag-end equivalent. |
| 910 | if (typeof _autoSaveSettings === 'function') _autoSaveSettings(); |
| 911 | } |
| 912 | |
| 913 | function _cylinderPointerLeave() { |
| 914 | if (_cylHoverMode) { |
| 915 | _cylHoverMode = null; |
| 916 | _cylinderUpdateCursor(); |
| 917 | _scheduleCylinderPanelRedraw(); |
| 918 | } |
| 919 | } |
| 920 | |
| 921 | function _cylinderPointerUp(e) { |
| 922 | if (!_cylDragMode) return; |
| 923 | const wasPan = _cylDragMode === 'pan'; |
| 924 | _cylDragMode = null; |
| 925 | try { cylinderCanvas.releasePointerCapture(e.pointerId); } catch { /* ignore */ } |
| 926 | _cylinderUpdateCursor(); |
| 927 | _scheduleCylinderPanelRedraw(); |
| 928 | if (wasPan) return; // pan doesn't change projection state |
| 929 | if (_cylPreviewThrottle) { clearTimeout(_cylPreviewThrottle); _cylPreviewThrottle = null; } |
| 930 | updatePreview(); |
| 931 | requestRender(); |
| 932 | // Persist the new center/radius — cylinderCanvas is outside #settings-panel, |
| 933 | // so the panel's input/change listener won't autosave for us. |
| 934 | if (typeof _autoSaveSettings === 'function') _autoSaveSettings(); |
| 935 | } |
| 936 | |
| 937 | cylinderCanvas.addEventListener('pointerdown', _cylinderPointerDown); |
| 938 | cylinderCanvas.addEventListener('pointermove', _cylinderPointerMove); |
| 939 | cylinderCanvas.addEventListener('pointerup', _cylinderPointerUp); |
| 940 | cylinderCanvas.addEventListener('pointercancel', _cylinderPointerUp); |
| 941 | cylinderCanvas.addEventListener('pointerleave', _cylinderPointerLeave); |
| 942 | cylinderCanvas.addEventListener('wheel', _cylinderWheel, { passive: false }); |
| 943 | // Right-click is reserved for panning, so swallow the browser context menu |
| 944 | // before it interrupts the drag. |
| 945 | cylinderCanvas.addEventListener('contextmenu', (e) => e.preventDefault()); |
| 946 | |
| 947 | function updateCylinderUIVisibility() { |
| 948 | const isCyl = settings.mappingMode === 3 /* MODE_CYLINDRICAL */; |
| 949 | // The seamless-wrap snap applies to both wrap-around modes (cylindrical |
| 950 | // and spherical); the rest of this panel is cylinder-only. |
| 951 | cylinderSnapRow.style.display = _isSeamlessWrapMode() ? '' : 'none'; |
| 952 | cylinderAxisRow.style.display = isCyl ? '' : 'none'; |
| 953 | // Show the panel whenever the user is in cylindrical mode, even without a |
| 954 | // model loaded — they get the empty placeholder until they load one, which |
| 955 | // makes it clear that the gizmo will appear there. |
| 956 | cylinderPanel.classList.toggle('hidden', !isCyl); |
| 957 | if (isCyl) { |
| 958 | if (currentGeometry) _buildCylinderSilhouette(); |
| 959 | _scheduleCylinderPanelRedraw(); |
| 960 | } |
no test coverage detected