Add redo, undo×10, brush preview, hide/show mask; drop Reload Saved

mask_canvas.py:
- Redo stack: new strokes clear it, undo pushes onto it, redo pops from it.
- undo10(): undoes up to 10 steps in one call with a single redraw.
- Brush circle preview: white Circle patch tracks mouse position and shows
  current brush radius; hidden when cursor leaves the axes.
- toggle_mask() / btn_mask: hides or shows the green mask overlay without
  affecting the underlying mask data.

annotator.py:
- Removed Reload Saved button and reload_saved() — clip already loads its
  saved state on navigation, making the button redundant.
- Added Undo×10 and Redo buttons wired to mc.undo10 / mc.redo.
- Added Hide Mask button (mc.btn_mask) to the toolbar row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 14:22:58 +02:00
parent 5b6efc7158
commit a139a2e2bd
2 changed files with 70 additions and 17 deletions

View File

@@ -111,14 +111,22 @@ class Annotator(QMainWindow):
btn_skip = QPushButton("Skip")
btn_clear = QPushButton("Clear")
btn_undo = QPushButton("Undo")
btn_reload = QPushButton("Reload Saved")
btn_undo10 = QPushButton("Undo×10")
btn_redo = QPushButton("Redo")
row1 = QHBoxLayout()
for b in [self.btn_prev, btn_next, btn_skip]:
row1.addWidget(b)
row2 = QHBoxLayout()
for b in [btn_clear, self.mc.btn_erase, btn_undo, btn_reload]:
for b in [
btn_clear,
self.mc.btn_erase,
btn_undo,
btn_undo10,
btn_redo,
self.mc.btn_mask,
]:
row2.addWidget(b)
row2.addWidget(QLabel("Brush"))
row2.addWidget(self.mc.brush_slider)
@@ -146,7 +154,8 @@ class Annotator(QMainWindow):
btn_skip.clicked.connect(self.skip_clip)
btn_clear.clicked.connect(self.mc.clear)
btn_undo.clicked.connect(self.mc.undo)
btn_reload.clicked.connect(self.reload_saved)
btn_undo10.clicked.connect(self.mc.undo10)
btn_redo.clicked.connect(self.mc.redo)
if self._pending_answers:
self._set_answers(self._pending_answers)
@@ -258,15 +267,6 @@ class Annotator(QMainWindow):
print("Saved:", out)
def reload_saved(self):
mask = self._read_saved_mask()
if mask is None:
return
self.mc.reset(mask)
answers = self._read_saved_answers()
if answers:
self._set_answers(answers)
def _switch_ui_to_clip(self):
self.frame_i = 0
self.mc.load_clip(