Skip to content

Commit af76aeb

Browse files
Implement undo
1 parent c14f754 commit af76aeb

2 files changed

Lines changed: 135 additions & 37 deletions

File tree

src/renderer/app_state.rs

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
use anyhow::Result;
1414
use winit::{
1515
dpi::PhysicalSize,
16-
event::{ElementState, Event, KeyEvent, MouseButton, WindowEvent},
16+
event::{ElementState, Event, KeyEvent, Modifiers, MouseButton, WindowEvent},
1717
event_loop::EventLoop,
1818
keyboard::{KeyCode, PhysicalKey},
1919
window::{CursorIcon, Window, WindowBuilder},
@@ -30,7 +30,8 @@ pub struct AppState<'a> {
3030
pub feature_uniform: FeatureUniform,
3131
pub draw_uniform: DrawUniform,
3232
pub mouse_state: MouseState,
33-
pub shape_stack: RevisionStack,
33+
pub revision_stack: RevisionStack,
34+
pub modifiers: Modifiers,
3435

3536
pub image_shader: Shader,
3637
pub shape_shader: Shader,
@@ -91,7 +92,8 @@ impl<'a> AppState<'a> {
9192
);
9293

9394
let mouse_state = MouseState::default();
94-
let shape_stack = RevisionStack::new();
95+
let revision_stack = RevisionStack::default();
96+
let modifiers = Modifiers::default();
9597

9698
Ok(Self {
9799
gpu_allocator,
@@ -100,7 +102,8 @@ impl<'a> AppState<'a> {
100102
feature_uniform,
101103
draw_uniform,
102104
mouse_state,
103-
shape_stack,
105+
revision_stack,
106+
modifiers,
104107
image_shader,
105108
shape_shader,
106109
shape_render_texture,
@@ -159,7 +162,7 @@ impl<'a> AppState<'a> {
159162
let normalized_radius =
160163
radius / (self.size.width.min(self.size.height) as f32);
161164

162-
self.shape_stack.push(Shape::Circle {
165+
self.revision_stack.push_shape(Shape::Circle {
163166
x: normalized_x,
164167
y: normalized_y,
165168
radius: normalized_radius,
@@ -180,24 +183,25 @@ impl<'a> AppState<'a> {
180183
let normalized_x = mouse_x / self.size.width as f32;
181184
let normalized_y = mouse_y / self.size.height as f32;
182185

183-
if let Some(circle_index) = self.shape_stack.find_shape_at_point(
184-
normalized_x,
185-
normalized_y,
186-
self.size.width,
187-
self.size.height,
188-
) {
186+
if let Some(circle_index) =
187+
self.revision_stack.shape_stack.find_shape_at_point(
188+
normalized_x,
189+
normalized_y,
190+
self.size.width,
191+
self.size.height,
192+
)
193+
{
189194
// Found a circle - select it and start dragging
190195
self.mouse_state.set_selected_shape(Some(circle_index));
191196
self.mouse_state.set_dragging_shape(true);
192197

193198
// Calculate offset from circle center to mouse position
194-
if let Some(Shape::Circle { x, y, .. }) =
195-
self.shape_stack.shapes().get(circle_index)
196-
{
197-
let offset_x = normalized_x - x;
198-
let offset_y = normalized_y - y;
199-
self.mouse_state.set_drag_offset((offset_x, offset_y));
200-
}
199+
let Shape::Circle { x, y, .. } =
200+
self.revision_stack.shape_stack.get_unchecked(circle_index);
201+
202+
let offset_x = normalized_x - x;
203+
let offset_y = normalized_y - y;
204+
self.mouse_state.set_drag_offset((offset_x, offset_y));
201205
} else {
202206
// Clicked on empty space - deselect any selected circle
203207
self.mouse_state.set_selected_shape(None);
@@ -234,13 +238,20 @@ impl<'a> AppState<'a> {
234238
let new_y = normalized_y - offset_y;
235239

236240
// Move the circle to the new position
237-
self.shape_stack.move_shape(selected_index, new_x, new_y);
241+
self.revision_stack.shape_stack.move_shape(
242+
selected_index,
243+
new_x,
244+
new_y,
245+
);
238246
}
239247
}
240248
}
241249

242250
self.mouse_state.update_position(x, y);
243251
}
252+
WindowEvent::ModifiersChanged(new_modifiers) => {
253+
self.modifiers = *new_modifiers;
254+
}
244255
WindowEvent::KeyboardInput {
245256
event:
246257
KeyEvent {
@@ -305,11 +316,22 @@ impl<'a> AppState<'a> {
305316
| (KeyCode::Backspace, ElementState::Pressed) => {
306317
// Delete the selected circle
307318
if let Some(selected_index) = self.mouse_state.selected_shape() {
308-
self.shape_stack.remove_shape(selected_index);
319+
self.revision_stack.shape_stack.remove_shape(selected_index);
309320
self.mouse_state.set_selected_shape(None);
310321
self.mouse_state.set_dragging_shape(false);
311322
}
312323
}
324+
(KeyCode::KeyZ, ElementState::Pressed) => {
325+
#[cfg(target_os = "macos")]
326+
if self.modifiers.state().super_key() {
327+
self.revision_stack.undo();
328+
}
329+
330+
#[cfg(not(target_os = "macos"))]
331+
if self.modifiers.state().control_key() {
332+
self.revision_stack.undo();
333+
}
334+
}
313335
_ => return false,
314336
},
315337
_ => return false,
@@ -331,8 +353,7 @@ impl<'a> AppState<'a> {
331353
}
332354

333355
fn update_shape_data(&mut self) {
334-
let shapes = self.shape_stack.shapes();
335-
let num_circles = shapes.len().min(MAX_CIRCLES);
356+
let num_circles = self.revision_stack.shape_stack.len().min(MAX_CIRCLES);
336357

337358
self.shape_uniform.set_num_circles(num_circles as u32);
338359
self.shape_uniform
@@ -345,7 +366,14 @@ impl<'a> AppState<'a> {
345366

346367
// Update circle storage buffer
347368
let mut circle_data = vec![CircleData::default(); MAX_CIRCLES];
348-
for (i, shape) in shapes.iter().take(MAX_CIRCLES).enumerate() {
369+
for (i, (_, shape)) in self
370+
.revision_stack
371+
.shape_stack
372+
.shapes()
373+
.into_iter()
374+
.take(MAX_CIRCLES)
375+
.enumerate()
376+
{
349377
circle_data[i] = CircleData::from(shape);
350378
}
351379

src/renderer/shape.rs

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,78 @@
1+
#[allow(dead_code)]
12
#[derive(Debug)]
3+
pub enum Action {
4+
Move {
5+
shape_id: usize,
6+
orig_x: f32,
7+
orig_y: f32,
8+
},
9+
Draw {
10+
shape_id: usize,
11+
},
12+
Delete {
13+
shape_id: usize,
14+
},
15+
}
16+
17+
#[derive(Debug, Default)]
218
pub struct RevisionStack {
3-
shapes: Vec<Shape>,
19+
stack: Vec<Action>,
20+
21+
pub shape_stack: ShapeStack,
422
}
523

624
impl RevisionStack {
7-
pub const fn new() -> Self {
8-
Self { shapes: vec![] }
25+
pub fn push_shape(&mut self, shape: Shape) {
26+
let id = self.shape_stack.push(shape);
27+
self.stack.push(Action::Draw { shape_id: id });
928
}
1029

11-
pub fn push(&mut self, shape: Shape) {
12-
self.shapes.push(shape);
30+
pub fn undo(&mut self) {
31+
if let Some(last_action) = self.stack.last() {
32+
match last_action {
33+
Action::Move {
34+
// shape_id,
35+
// orig_x,
36+
// orig_y,
37+
..
38+
} => todo!(),
39+
Action::Draw { shape_id } => self.shape_stack.remove_shape_by_id(*shape_id),
40+
Action::Delete { shape_id: _ } => todo!(),
41+
}
42+
}
1343
}
44+
}
1445

15-
pub fn shapes(&self) -> &[Shape] {
16-
&self.shapes
46+
#[derive(Debug, Default)]
47+
pub struct ShapeStack {
48+
next_id: usize,
49+
shapes: Vec<(usize, Shape)>,
50+
}
51+
52+
impl ShapeStack {
53+
pub fn new_id(&mut self) -> usize {
54+
let id = self.next_id;
55+
self.next_id += 1;
56+
57+
id
58+
}
59+
60+
pub fn push(&mut self, shape: Shape) -> usize {
61+
let id = self.new_id();
62+
63+
self.shapes.push((id, shape));
64+
65+
id
66+
}
67+
68+
pub fn get_unchecked(&self, i: usize) -> &Shape {
69+
let res = unsafe { self.shapes.get_unchecked(i) };
70+
71+
&res.1
72+
}
73+
74+
pub const fn len(&self) -> usize {
75+
self.shapes.len()
1776
}
1877

1978
// Find the topmost circle that contains the given point (in normalized coordinates)
@@ -30,7 +89,7 @@ impl RevisionStack {
3089
.iter()
3190
.enumerate()
3291
.rev()
33-
.find_map(|(i, s)| match s {
92+
.find_map(|(i, (_shape_id, s))| match s {
3493
Shape::Circle {
3594
x: cx,
3695
y: cy,
@@ -66,7 +125,7 @@ impl RevisionStack {
66125

67126
// Move a shape to a new position (in normalized coordinates)
68127
pub fn move_shape(&mut self, index: usize, new_x: f32, new_y: f32) {
69-
let shape = self
128+
let (_, shape) = self
70129
.shapes
71130
.get_mut(index)
72131
.expect("index should always be valid");
@@ -77,6 +136,21 @@ impl RevisionStack {
77136
pub fn remove_shape(&mut self, index: usize) {
78137
self.shapes.remove(index);
79138
}
139+
140+
pub fn remove_shape_by_id(&mut self, shape_id: usize) {
141+
if let Some(i) =
142+
self.shapes
143+
.iter()
144+
.enumerate()
145+
.find_map(|(j, (i, _))| if *i == shape_id { Some(j) } else { None })
146+
{
147+
self.remove_shape(i);
148+
}
149+
}
150+
151+
pub fn shapes(&self) -> &[(usize, Shape)] {
152+
&self.shapes
153+
}
80154
}
81155

82156
#[allow(dead_code)]
@@ -88,11 +162,7 @@ pub enum Shape {
88162
impl Shape {
89163
pub fn update_position(&mut self, new_x: f32, new_y: f32) {
90164
match self {
91-
Shape::Circle {
92-
x,
93-
y,
94-
radius: _radius,
95-
} => {
165+
Shape::Circle { x, y, .. } => {
96166
*x = new_x;
97167
*y = new_y;
98168
}

0 commit comments

Comments
 (0)