Skip to content

Commit 0d49ec3

Browse files
committed
feat: add zoom in/out with gesture + keybindings
1 parent d010a98 commit 0d49ec3

File tree

2 files changed

+169
-2
lines changed

2 files changed

+169
-2
lines changed

src/components/terminal/terminal.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ export default class TerminalComponent {
265265
terminalSettings.touchSelectionHapticFeedback !== false,
266266
showContextMenu:
267267
terminalSettings.touchSelectionShowContextMenu !== false,
268+
onFontSizeChange: (fontSize) => this.updateFontSize(fontSize),
268269
},
269270
);
270271
}
@@ -340,6 +341,20 @@ export default class TerminalComponent {
340341
return false;
341342
}
342343

344+
// Check for Ctrl+= or Ctrl++ (increase font size)
345+
if (event.ctrlKey && (event.key === "+" || event.key === "=")) {
346+
event.preventDefault();
347+
this.increaseFontSize();
348+
return false;
349+
}
350+
351+
// Check for Ctrl+- (decrease font size)
352+
if (event.ctrlKey && event.key === "-") {
353+
event.preventDefault();
354+
this.decreaseFontSize();
355+
return false;
356+
}
357+
343358
// Only intercept specific app-wide keybindings, let terminal handle the rest
344359
if (event.ctrlKey || event.altKey || event.metaKey) {
345360
// Skip modifier-only keys
@@ -803,6 +818,56 @@ export default class TerminalComponent {
803818
}
804819
}
805820

821+
/**
822+
* Increase terminal font size
823+
*/
824+
increaseFontSize() {
825+
const currentSize = this.terminal.options.fontSize;
826+
const newSize = Math.min(currentSize + 1, 24); // Max font size 24
827+
this.updateFontSize(newSize);
828+
}
829+
830+
/**
831+
* Decrease terminal font size
832+
*/
833+
decreaseFontSize() {
834+
const currentSize = this.terminal.options.fontSize;
835+
const newSize = Math.max(currentSize - 1, 8); // Min font size 8
836+
this.updateFontSize(newSize);
837+
}
838+
839+
/**
840+
* Update terminal font size and refresh display
841+
*/
842+
updateFontSize(fontSize) {
843+
if (fontSize === this.terminal.options.fontSize) return;
844+
845+
this.terminal.options.fontSize = fontSize;
846+
this.options.fontSize = fontSize;
847+
848+
// Update terminal settings properly
849+
const currentSettings = appSettings.value.terminalSettings || {};
850+
const updatedSettings = { ...currentSettings, fontSize };
851+
appSettings.update({ terminalSettings: updatedSettings }, false);
852+
853+
// Refresh terminal display
854+
this.terminal.refresh(0, this.terminal.rows - 1);
855+
856+
// Fit terminal to container after font size change to prevent empty space
857+
setTimeout(() => {
858+
if (this.fitAddon) {
859+
this.fitAddon.fit();
860+
}
861+
}, 50);
862+
863+
// Update touch selection cell dimensions if it exists
864+
if (this.touchSelection) {
865+
setTimeout(() => {
866+
this.touchSelection.updateCellDimensions();
867+
}, 100);
868+
}
869+
}
870+
806871
/**
807872
* Terminate terminal session
808873
*/

src/components/terminal/terminalTouchSelection.js

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ export default class TerminalTouchSelection {
3131
this.tapHoldTimeout = null;
3232
this.dragHandle = null;
3333

34+
// Zoom tracking
35+
this.pinchStartDistance = 0;
36+
this.lastPinchDistance = 0;
37+
this.isPinching = false;
38+
this.initialFontSize = 0;
39+
this.lastZoomTime = 0;
40+
this.zoomThrottle = 50; // ms
41+
3442
// DOM elements
3543
this.selectionOverlay = null;
3644
this.startHandle = null;
@@ -208,7 +216,14 @@ export default class TerminalTouchSelection {
208216
}
209217

210218
onTerminalTouchStart(event) {
211-
// Only handle single touch
219+
// Handle pinch zoom
220+
if (event.touches.length === 2) {
221+
event.preventDefault();
222+
this.startPinchZoom(event);
223+
return;
224+
}
225+
226+
// Only handle single touch for selection
212227
if (event.touches.length !== 1) return;
213228

214229
const touch = event.touches[0];
@@ -228,15 +243,25 @@ export default class TerminalTouchSelection {
228243

229244
// Start tap-hold timer
230245
this.tapHoldTimeout = setTimeout(() => {
231-
if (!this.isSelecting) {
246+
if (!this.isSelecting && !this.isPinching) {
232247
this.startSelection(touch);
233248
}
234249
}, this.options.tapHoldDuration);
235250
}
236251

237252
onTerminalTouchMove(event) {
253+
// Handle pinch zoom
254+
if (event.touches.length === 2) {
255+
event.preventDefault();
256+
this.handlePinchZoom(event);
257+
return;
258+
}
259+
238260
if (event.touches.length !== 1) return;
239261

262+
// Don't handle single touch if we're pinching
263+
if (this.isPinching) return;
264+
240265
const touch = event.touches[0];
241266
const deltaX = Math.abs(touch.clientX - this.touchStartPos.x);
242267
const deltaY = Math.abs(touch.clientY - this.touchStartPos.y);
@@ -260,6 +285,12 @@ export default class TerminalTouchSelection {
260285
}
261286

262287
onTerminalTouchEnd(event) {
288+
// Handle end of pinch zoom
289+
if (this.isPinching) {
290+
this.endPinchZoom();
291+
return;
292+
}
293+
263294
if (this.tapHoldTimeout) {
264295
clearTimeout(this.tapHoldTimeout);
265296
this.tapHoldTimeout = null;
@@ -916,6 +947,77 @@ export default class TerminalTouchSelection {
916947
}
917948
}
918949

950+
/**
951+
* Start pinch zoom gesture
952+
*/
953+
startPinchZoom(event) {
954+
if (event.touches.length !== 2) return;
955+
956+
this.isPinching = true;
957+
this.initialFontSize = this.terminal.options.fontSize;
958+
959+
const touch1 = event.touches[0];
960+
const touch2 = event.touches[1];
961+
962+
this.pinchStartDistance = this.getDistance(touch1, touch2);
963+
this.lastPinchDistance = this.pinchStartDistance;
964+
965+
// Clear any selection timeouts
966+
if (this.tapHoldTimeout) {
967+
clearTimeout(this.tapHoldTimeout);
968+
this.tapHoldTimeout = null;
969+
}
970+
}
971+
972+
/**
973+
* Handle pinch zoom gesture
974+
*/
975+
handlePinchZoom(event) {
976+
if (!this.isPinching || event.touches.length !== 2) return;
977+
978+
const now = Date.now();
979+
if (now - this.lastZoomTime < this.zoomThrottle) return;
980+
this.lastZoomTime = now;
981+
982+
const touch1 = event.touches[0];
983+
const touch2 = event.touches[1];
984+
const currentDistance = this.getDistance(touch1, touch2);
985+
986+
const scale = currentDistance / this.pinchStartDistance;
987+
const newFontSize = Math.round(this.initialFontSize * scale);
988+
989+
// Clamp font size between reasonable limits
990+
const minFontSize = 8;
991+
const maxFontSize = 24;
992+
const clampedFontSize = Math.max(
993+
minFontSize,
994+
Math.min(maxFontSize, newFontSize),
995+
);
996+
997+
if (clampedFontSize !== this.terminal.options.fontSize) {
998+
this.options.onFontSizeChange(clampedFontSize);
999+
}
1000+
}
1001+
1002+
/**
1003+
* End pinch zoom gesture
1004+
*/
1005+
endPinchZoom() {
1006+
this.isPinching = false;
1007+
this.pinchStartDistance = 0;
1008+
this.lastPinchDistance = 0;
1009+
this.initialFontSize = 0;
1010+
}
1011+
1012+
/**
1013+
* Get distance between two touch points
1014+
*/
1015+
getDistance(touch1, touch2) {
1016+
const dx = touch2.clientX - touch1.clientX;
1017+
const dy = touch2.clientY - touch1.clientY;
1018+
return Math.sqrt(dx * dx + dy * dy);
1019+
}
1020+
9191021
destroy() {
9201022
// Clear selection
9211023
this.clearSelection();

0 commit comments

Comments
 (0)