mirror of
https://github.com/tsl0922/EPD-nRF5.git
synced 2025-12-05 15:32:48 +08:00
add undo/redo support
This commit is contained in:
@@ -419,6 +419,10 @@ canvas.text-placement-mode {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.tool-button.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.debug-mode .tool-button {
|
||||
background-color: var(--dark-input-bg);
|
||||
border-color: var(--dark-border);
|
||||
|
||||
@@ -146,7 +146,11 @@
|
||||
<button id="brush-mode" title="画笔" class="tool-button">✏️</button>
|
||||
<button id="eraser-mode" title="橡皮擦" class="tool-button">🧽</button>
|
||||
<button id="text-mode" title="添加文字" class="tool-button">T</button>
|
||||
<button id="undo-btn" title="撤销 (Ctrl+Z)" class="tool-button hide">↶</button>
|
||||
<button id="redo-btn" title="重做 (Ctrl+Y)" class="tool-button hide">↷</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container canvas-tools">
|
||||
<div class="flex-group brush-tools">
|
||||
<label for="brush-color">颜色:</label>
|
||||
<select id="brush-color">
|
||||
|
||||
@@ -93,6 +93,7 @@ function finishCrop() {
|
||||
convertDithering();
|
||||
|
||||
exitCropMode();
|
||||
saveToHistory(); // Save after finishing crop
|
||||
};
|
||||
image.src = URL.createObjectURL(imageFile.files[0]);
|
||||
}
|
||||
|
||||
@@ -485,6 +485,7 @@ function updateImage() {
|
||||
redrawTextElements();
|
||||
redrawLineSegments();
|
||||
convertDithering();
|
||||
saveToHistory(); // Save after loading image
|
||||
} else {
|
||||
alert("图片宽高比例与画布不匹配,将进入裁剪模式。\n请放大图片后移动图片使其充满画布,再点击“完成”按钮。");
|
||||
setActiveTool(null, '');
|
||||
@@ -531,6 +532,7 @@ function clearCanvas() {
|
||||
textElements = []; // Clear stored text positions
|
||||
lineSegments = []; // Clear stored line segments
|
||||
if (isCropMode()) exitCropMode();
|
||||
saveToHistory(); // Save cleared canvas to history
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
100
html/js/paint.js
100
html/js/paint.js
@@ -15,6 +15,11 @@ let dragOffsetY = 0;
|
||||
let textBold = false; // Track if text should be bold
|
||||
let textItalic = false; // Track if text should be italic
|
||||
|
||||
// Undo/Redo functionality
|
||||
let historyStack = []; // Stack to store canvas history
|
||||
let historyStep = -1; // Current position in history stack
|
||||
const MAX_HISTORY = 50; // Maximum number of undo steps
|
||||
|
||||
function setCanvasTitle(title) {
|
||||
const canvasTitle = document.querySelector('.canvas-title');
|
||||
if (canvasTitle) {
|
||||
@@ -23,6 +28,71 @@ function setCanvasTitle(title) {
|
||||
}
|
||||
}
|
||||
|
||||
function saveToHistory() {
|
||||
// Remove any states after current step (when user drew something after undoing)
|
||||
historyStack = historyStack.slice(0, historyStep + 1);
|
||||
|
||||
// Save current canvas state along with text and line data
|
||||
const canvasState = {
|
||||
imageData: ctx.getImageData(0, 0, canvas.width, canvas.height),
|
||||
textElements: JSON.parse(JSON.stringify(textElements)),
|
||||
lineSegments: JSON.parse(JSON.stringify(lineSegments))
|
||||
};
|
||||
|
||||
historyStack.push(canvasState);
|
||||
historyStep++;
|
||||
|
||||
// Limit history size
|
||||
if (historyStack.length > MAX_HISTORY) {
|
||||
historyStack.shift();
|
||||
historyStep--;
|
||||
}
|
||||
|
||||
updateUndoRedoButtons();
|
||||
}
|
||||
|
||||
function undo() {
|
||||
if (historyStep > 0) {
|
||||
historyStep--;
|
||||
restoreFromHistory();
|
||||
}
|
||||
}
|
||||
|
||||
function redo() {
|
||||
if (historyStep < historyStack.length - 1) {
|
||||
historyStep++;
|
||||
restoreFromHistory();
|
||||
}
|
||||
}
|
||||
|
||||
function restoreFromHistory() {
|
||||
if (historyStep >= 0 && historyStep < historyStack.length) {
|
||||
const state = historyStack[historyStep];
|
||||
|
||||
// Restore canvas image
|
||||
ctx.putImageData(state.imageData, 0, 0);
|
||||
|
||||
// Restore text and line data
|
||||
textElements = JSON.parse(JSON.stringify(state.textElements));
|
||||
lineSegments = JSON.parse(JSON.stringify(state.lineSegments));
|
||||
|
||||
updateUndoRedoButtons();
|
||||
}
|
||||
}
|
||||
|
||||
function updateUndoRedoButtons() {
|
||||
const undoBtn = document.getElementById('undo-btn');
|
||||
const redoBtn = document.getElementById('redo-btn');
|
||||
|
||||
if (undoBtn) {
|
||||
undoBtn.disabled = historyStep <= 0;
|
||||
}
|
||||
|
||||
if (redoBtn) {
|
||||
redoBtn.disabled = historyStep >= historyStack.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function initPaintTools() {
|
||||
document.getElementById('brush-mode').addEventListener('click', () => {
|
||||
if (currentTool === 'brush') {
|
||||
@@ -71,6 +141,10 @@ function initPaintTools() {
|
||||
textItalic = !textItalic;
|
||||
document.getElementById('text-italic').classList.toggle('primary', textItalic);
|
||||
});
|
||||
|
||||
// Add undo/redo button listeners
|
||||
document.getElementById('undo-btn').addEventListener('click', undo);
|
||||
document.getElementById('redo-btn').addEventListener('click', redo);
|
||||
|
||||
canvas.addEventListener('mousedown', startPaint);
|
||||
canvas.addEventListener('mousemove', paint);
|
||||
@@ -82,6 +156,23 @@ function initPaintTools() {
|
||||
canvas.addEventListener('touchstart', onTouchStart);
|
||||
canvas.addEventListener('touchmove', onTouchMove);
|
||||
canvas.addEventListener('touchend', onTouchEnd);
|
||||
|
||||
// Keyboard shortcuts for undo/redo
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// Ctrl+Z or Cmd+Z for undo
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
undo();
|
||||
}
|
||||
// Ctrl+Y or Ctrl+Shift+Z or Cmd+Shift+Z for redo
|
||||
else if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.shiftKey && e.key === 'z'))) {
|
||||
e.preventDefault();
|
||||
redo();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize history with blank canvas state
|
||||
saveToHistory();
|
||||
}
|
||||
|
||||
function setActiveTool(tool, title) {
|
||||
@@ -99,6 +190,9 @@ function setActiveTool(tool, title) {
|
||||
document.getElementById('brush-color').disabled = currentTool === 'eraser';
|
||||
document.getElementById('brush-size').disabled = currentTool === 'text';
|
||||
|
||||
document.getElementById('undo-btn').classList.toggle('hide', currentTool === null);
|
||||
document.getElementById('redo-btn').classList.toggle('hide', currentTool === null);
|
||||
|
||||
// Cancel any pending text placement
|
||||
cancelTextPlacement();
|
||||
}
|
||||
@@ -131,6 +225,9 @@ function startPaint(e) {
|
||||
}
|
||||
|
||||
function endPaint() {
|
||||
if (painting || isDraggingText) {
|
||||
saveToHistory(); // Save state after drawing or dragging text
|
||||
}
|
||||
painting = false;
|
||||
isDraggingText = false;
|
||||
lastX = 0;
|
||||
@@ -364,6 +461,9 @@ function placeText(e) {
|
||||
ctx.fillStyle = newText.color;
|
||||
ctx.fillText(newText.text, newText.x, newText.y);
|
||||
|
||||
// Save to history after placing text
|
||||
saveToHistory();
|
||||
|
||||
// Reset
|
||||
document.getElementById('text-input').value = '';
|
||||
isTextPlacementMode = false;
|
||||
|
||||
Reference in New Issue
Block a user