Undo/Redo demo¶
move cubes and rewind with Ctrl+Z / Ctrl+Shift+Z.
▶ Run in browserTags: ui
Run with: uv run python examples/features/ui/undo.py
Controls: 1 / 2 / 3 Select cube Arrow keys Move selected cube (Left/Right = X, Up/Down = Z) Ctrl+Z Undo last move Ctrl+Shift+Z / Ctrl+Y Redo
Source¶
1"""Undo/Redo demo -- move cubes and rewind with Ctrl+Z / Ctrl+Shift+Z.
2
3Run with:
4 uv run python examples/features/ui/undo.py
5
6Controls:
7 1 / 2 / 3 Select cube
8 Arrow keys Move selected cube (Left/Right = X, Up/Down = Z)
9 Ctrl+Z Undo last move
10 Ctrl+Shift+Z / Ctrl+Y Redo
11"""
12
13from simvx.core import (
14 Camera3D,
15 Input,
16 Key,
17 Material,
18 Mesh,
19 MeshInstance3D,
20 Node,
21 PropertyCommand,
22 Selection,
23 UndoStack,
24 Vec3,
25)
26from simvx.graphics import App
27
28MOVE_STEP = 1.0
29COOLDOWN = 0.15 # seconds between accepted key repeats
30
31CUBE_COLOURS = [
32 (0.9, 0.2, 0.2, 1.0), # red
33 (0.2, 0.8, 0.2, 1.0), # green
34 (0.2, 0.4, 0.9, 1.0), # blue
35]
36CUBE_POSITIONS = [
37 Vec3(-4, 0, 0),
38 Vec3(0, 0, 0),
39 Vec3(4, 0, 0),
40]
41
42
43class UndoDemo(Node):
44 def on_ready(self):
45 # Camera looking down Y axis
46 cam = Camera3D(position=(0, -15, 0))
47 cam.look_at((0, 0, 0), up=(0, 0, 1))
48 self.add_child(cam)
49
50 # Shared mesh
51 cube_mesh = Mesh.cube()
52
53 # Create cubes
54 self.cubes = []
55 for i in range(3):
56 cube = MeshInstance3D(
57 name=f"Cube{i + 1}",
58 mesh=cube_mesh,
59 material=Material(colour=CUBE_COLOURS[i]),
60 position=tuple(CUBE_POSITIONS[i]),
61 )
62 self.add_child(cube)
63 self.cubes.append(cube)
64
65 # Undo system
66 self.undo_stack = UndoStack()
67 self.undo_stack.changed.connect(self._on_stack_changed)
68
69 # Selection
70 self.selection = Selection()
71 self.selection.selection_changed.connect(self._on_selection_changed)
72 self.selection.select(self.cubes[0])
73
74 self._cooldown_timer = 0.0
75
76 def on_process(self, dt):
77 self._cooldown_timer = max(0.0, self._cooldown_timer - dt)
78
79 # Cube selection (1-3)
80 for i, key in enumerate((Key.KEY_1, Key.KEY_2, Key.KEY_3)):
81 if Input.is_key_just_pressed(key):
82 self.selection.select(self.cubes[i])
83
84 ctrl = Input.is_key_pressed(Key.LEFT_CONTROL) or Input.is_key_pressed(Key.RIGHT_CONTROL)
85 shift = Input.is_key_pressed(Key.LEFT_SHIFT) or Input.is_key_pressed(Key.RIGHT_SHIFT)
86
87 # Undo / Redo (react on just-pressed only)
88 if ctrl and Input.is_key_just_pressed(Key.Z):
89 if shift:
90 self.undo_stack.redo()
91 else:
92 self.undo_stack.undo()
93 return
94 if ctrl and Input.is_key_just_pressed(Key.Y):
95 self.undo_stack.redo()
96 return
97
98 # Movement (with cooldown for key repeat)
99 if self._cooldown_timer > 0 or self.selection.empty:
100 return
101
102 direction = Vec3(0, 0, 0)
103 if Input.is_key_pressed(Key.LEFT):
104 direction = Vec3(-MOVE_STEP, 0, 0)
105 elif Input.is_key_pressed(Key.RIGHT):
106 direction = Vec3(MOVE_STEP, 0, 0)
107 elif Input.is_key_pressed(Key.UP):
108 direction = Vec3(0, 0, MOVE_STEP)
109 elif Input.is_key_pressed(Key.DOWN):
110 direction = Vec3(0, 0, -MOVE_STEP)
111
112 if direction.length() > 0:
113 cube = self.selection.primary
114 old_pos = tuple(Vec3(cube.position))
115 new_pos = tuple(Vec3(cube.position) + direction)
116 cmd = PropertyCommand(
117 cube,
118 "position",
119 old_pos,
120 new_pos,
121 description=f"Move {cube.name} to ({new_pos[0]:.0f}, {new_pos[2]:.0f})",
122 )
123 self.undo_stack.push(cmd)
124 self._cooldown_timer = COOLDOWN
125
126 def _on_selection_changed(self):
127 pass
128
129 def _on_stack_changed(self):
130 pass
131
132
133if __name__ == "__main__":
134 app = App(title="SimVX Undo Demo", width=800, height=600)
135 app.run(UndoDemo())