Skip to main content

rustapi/docks/
tiles_editor_undo.rs

1use crate::prelude::*;
2use theframework::prelude::*;
3
4/// Undo atoms for tile editor operations
5#[derive(Clone, Debug)]
6pub enum TileEditorUndoAtom {
7    /// Tile texture edit: (tile_id, before_tile, after_tile)
8    TileEdit(Uuid, rusterix::Tile, rusterix::Tile),
9    /// Generic texture edit via PixelEditingContext: (context, before_texture, after_texture)
10    TextureEdit(PixelEditingContext, rusterix::Texture, rusterix::Texture),
11    /// Avatar weapon attachment anchor edit for the currently edited frame.
12    AvatarAnchorEdit(
13        PixelEditingContext,
14        Option<(i16, i16)>,
15        Option<(i16, i16)>,
16        Option<(i16, i16)>,
17        Option<(i16, i16)>,
18    ),
19}
20
21impl TileEditorUndoAtom {
22    pub fn undo(&self, project: &mut Project, _ui: &mut TheUI, ctx: &mut TheContext) {
23        match self {
24            TileEditorUndoAtom::TileEdit(tile_id, prev, _) => {
25                if let Some(tile) = project.tiles.get_mut(tile_id) {
26                    *tile = prev.clone();
27
28                    // Notify tile editor to refresh tile
29                    ctx.ui.send(TheEvent::Custom(
30                        TheId::named("Tile Picked"),
31                        TheValue::Id(tile.id),
32                    ));
33
34                    // Update tile picker if visible
35                    ctx.ui.send(TheEvent::Custom(
36                        TheId::named("Update Tilepicker"),
37                        TheValue::Empty,
38                    ));
39                }
40            }
41            TileEditorUndoAtom::TextureEdit(editing_ctx, prev, _) => {
42                if let Some(texture) = project.get_editing_texture_mut(editing_ctx) {
43                    *texture = prev.clone();
44                }
45                Self::send_editing_context_update(editing_ctx, ctx);
46            }
47            TileEditorUndoAtom::AvatarAnchorEdit(editing_ctx, prev_main, prev_off, _, _) => {
48                if let Some(frame) = project.get_editing_avatar_frame_mut(editing_ctx) {
49                    frame.weapon_main_anchor = *prev_main;
50                    frame.weapon_off_anchor = *prev_off;
51                }
52                Self::send_editing_context_update(editing_ctx, ctx);
53            }
54        }
55    }
56
57    pub fn redo(&self, project: &mut Project, _ui: &mut TheUI, ctx: &mut TheContext) {
58        match self {
59            TileEditorUndoAtom::TileEdit(tile_id, _, next) => {
60                if let Some(tile) = project.tiles.get_mut(tile_id) {
61                    if !tile.textures.is_empty() {
62                        *tile = next.clone();
63
64                        // Notify tile editor to refresh tile
65                        ctx.ui.send(TheEvent::Custom(
66                            TheId::named("Tile Picked"),
67                            TheValue::Id(tile.id),
68                        ));
69
70                        // Update tile picker if visible
71                        ctx.ui.send(TheEvent::Custom(
72                            TheId::named("Update Tilepicker"),
73                            TheValue::Empty,
74                        ));
75                    }
76                }
77            }
78            TileEditorUndoAtom::TextureEdit(editing_ctx, _, next) => {
79                if let Some(texture) = project.get_editing_texture_mut(editing_ctx) {
80                    *texture = next.clone();
81                }
82                Self::send_editing_context_update(editing_ctx, ctx);
83            }
84            TileEditorUndoAtom::AvatarAnchorEdit(editing_ctx, _, _, next_main, next_off) => {
85                if let Some(frame) = project.get_editing_avatar_frame_mut(editing_ctx) {
86                    frame.weapon_main_anchor = *next_main;
87                    frame.weapon_off_anchor = *next_off;
88                }
89                Self::send_editing_context_update(editing_ctx, ctx);
90            }
91        }
92    }
93
94    /// Sends the appropriate UI update events after an editing context undo/redo.
95    fn send_editing_context_update(editing_ctx: &PixelEditingContext, ctx: &mut TheContext) {
96        match editing_ctx {
97            PixelEditingContext::None => {}
98            PixelEditingContext::Tile(tile_id, _) => {
99                ctx.ui.send(TheEvent::Custom(
100                    TheId::named("Tile Updated"),
101                    TheValue::Id(*tile_id),
102                ));
103                ctx.ui.send(TheEvent::Custom(
104                    TheId::named("Update Tilepicker"),
105                    TheValue::Empty,
106                ));
107            }
108            PixelEditingContext::AvatarFrame(..) => {
109                ctx.ui.send(TheEvent::Custom(
110                    TheId::named("Editing Texture Updated"),
111                    TheValue::Empty,
112                ));
113            }
114        }
115    }
116}
117
118/// Undo stack for tile editor
119#[derive(Clone, Debug)]
120pub struct TileEditorUndo {
121    pub stack: Vec<TileEditorUndoAtom>,
122    pub index: isize,
123}
124
125impl Default for TileEditorUndo {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131impl TileEditorUndo {
132    pub fn new() -> Self {
133        Self {
134            stack: vec![],
135            index: -1,
136        }
137    }
138
139    pub fn is_empty(&self) -> bool {
140        self.stack.is_empty()
141    }
142
143    pub fn clear(&mut self) {
144        self.stack = vec![];
145        self.index = -1;
146    }
147
148    pub fn has_undo(&self) -> bool {
149        self.index >= 0
150    }
151
152    pub fn has_redo(&self) -> bool {
153        self.index >= -1 && self.index < self.stack.len() as isize - 1
154    }
155
156    pub fn has_changes(&self) -> bool {
157        // Has changes if the index is not at the beginning (i.e., not fully undone)
158        self.index >= 0
159    }
160
161    pub fn add(&mut self, atom: TileEditorUndoAtom) {
162        // Remove any redo history
163        let to_remove = self.stack.len() as isize - self.index - 1;
164        for _i in 0..to_remove {
165            self.stack.pop();
166        }
167        self.stack.push(atom);
168        self.index += 1;
169    }
170
171    pub fn undo(&mut self, project: &mut Project, ui: &mut TheUI, ctx: &mut TheContext) {
172        if self.index >= 0 {
173            self.stack[self.index as usize].undo(project, ui, ctx);
174            self.index -= 1;
175        }
176    }
177
178    pub fn redo(&mut self, project: &mut Project, ui: &mut TheUI, ctx: &mut TheContext) {
179        if self.index < self.stack.len() as isize - 1 {
180            self.index += 1;
181            self.stack[self.index as usize].redo(project, ui, ctx);
182        }
183    }
184
185    pub fn truncate_to_limit(&mut self, limit: usize) {
186        if self.stack.len() > limit {
187            let excess = self.stack.len() - limit;
188            self.stack.drain(0..excess);
189            self.index -= excess as isize;
190            if self.index < -1 {
191                self.index = -1;
192            }
193        }
194    }
195}