Skip to main content

rustapi/editor_tools/
tile_eraser.rs

1use crate::docks::tiles_editor_undo::TileEditorUndoAtom;
2use crate::prelude::*;
3
4pub struct TileEraserTool {
5    id: TheId,
6    /// For tile editing: snapshot of the entire tile before the stroke.
7    before_tile: Option<rusterix::Tile>,
8    /// For non-tile editing (avatar frames, etc.): snapshot of the texture + context.
9    before_snapshot: Option<(PixelEditingContext, rusterix::Texture)>,
10    changed: bool,
11}
12
13impl EditorTool for TileEraserTool {
14    fn new() -> Self
15    where
16        Self: Sized,
17    {
18        Self {
19            id: TheId::named_with_id("Tile Eraser Tool", Uuid::new_v4()),
20            before_tile: None,
21            before_snapshot: None,
22            changed: false,
23        }
24    }
25
26    fn id(&self) -> TheId {
27        self.id.clone()
28    }
29
30    fn info(&self) -> String {
31        "Eraser Tool (E). Click and drag to clear pixels to transparent.".to_string()
32    }
33
34    fn icon_name(&self) -> String {
35        "eraser".to_string()
36    }
37
38    fn rgba_view_mode(&self) -> Option<TheRGBAViewMode> {
39        Some(TheRGBAViewMode::TileEditor)
40    }
41
42    fn accel(&self) -> Option<char> {
43        Some('E')
44    }
45
46    fn handle_event(
47        &mut self,
48        event: &TheEvent,
49        ui: &mut TheUI,
50        ctx: &mut TheContext,
51        project: &mut Project,
52        server_ctx: &mut ServerContext,
53    ) -> bool {
54        let mut redraw = false;
55
56        match event {
57            TheEvent::TileEditorClicked(id, coord) => {
58                if id.name == "Tile Editor Dock RGBA Layout View" {
59                    match server_ctx.editing_ctx {
60                        PixelEditingContext::Tile(tile_id, _) => {
61                            if let Some(tile) = project.tiles.get(&tile_id) {
62                                self.before_tile = Some(tile.clone());
63                            }
64                        }
65                        _ => {
66                            if let Some(texture) =
67                                project.get_editing_texture(&server_ctx.editing_ctx)
68                            {
69                                self.before_snapshot =
70                                    Some((server_ctx.editing_ctx, texture.clone()));
71                            }
72                        }
73                    }
74
75                    self.erase_pixel(*coord, ui, ctx, project, server_ctx);
76                    redraw = true;
77                }
78            }
79            TheEvent::TileEditorDragged(id, coord) => {
80                if id.name == "Tile Editor Dock RGBA Layout View" {
81                    self.erase_pixel(*coord, ui, ctx, project, server_ctx);
82                    redraw = true;
83                }
84            }
85            TheEvent::TileEditorUp(_) => {
86                if self.changed {
87                    if matches!(server_ctx.editing_ctx, PixelEditingContext::Tile(..)) {
88                        ctx.ui.send(TheEvent::Custom(
89                            TheId::named("Update Tilepicker"),
90                            TheValue::Empty,
91                        ));
92                    }
93                    ctx.ui.send(TheEvent::Custom(
94                        TheId::named("Tile Editor Undo Available"),
95                        TheValue::Empty,
96                    ));
97                    self.changed = false;
98                }
99            }
100            _ => {}
101        }
102
103        redraw
104    }
105
106    fn get_undo_atom(&mut self, project: &Project) -> Option<Box<dyn std::any::Any>> {
107        if let Some(before) = self.before_tile.take() {
108            if let Some(tile) = project.tiles.get(&before.id) {
109                if !tile.textures.is_empty() {
110                    let after = tile.clone();
111                    let atom = TileEditorUndoAtom::TileEdit(before.id, before, after);
112                    return Some(Box::new(atom));
113                }
114            }
115            return None;
116        }
117
118        if let Some((editing_ctx, before)) = self.before_snapshot.take() {
119            if let Some(after) = project.get_editing_texture(&editing_ctx) {
120                let atom = TileEditorUndoAtom::TextureEdit(editing_ctx, before, after.clone());
121                return Some(Box::new(atom));
122            }
123        }
124        None
125    }
126}
127
128impl TileEraserTool {
129    fn erase_pixel(
130        &mut self,
131        pos: Vec2<i32>,
132        ui: &mut TheUI,
133        ctx: &mut TheContext,
134        project: &mut Project,
135        server_ctx: &mut ServerContext,
136    ) {
137        let editing_ctx = server_ctx.editing_ctx;
138
139        if matches!(editing_ctx, PixelEditingContext::AvatarFrame(..))
140            && server_ctx.avatar_anchor_slot != AvatarAnchorEditSlot::None
141        {
142            return;
143        }
144        if let Some(editor) = ui.get_rgba_layout("Tile Editor Dock RGBA Layout")
145            && let Some(rgba_view) = editor.rgba_view_mut().as_rgba_view()
146        {
147            if rgba_view.has_paste_preview() {
148                return;
149            }
150            let selection = rgba_view.selection();
151            if !selection.is_empty() && !selection.contains(&(pos.x, pos.y)) {
152                return;
153            }
154        }
155
156        if let Some(texture) = project.get_editing_texture_mut(&editing_ctx) {
157            let width = texture.width as i32;
158            let height = texture.height as i32;
159
160            if pos.x >= 0 && pos.x < width && pos.y >= 0 && pos.y < height {
161                let current = texture.get_pixel(pos.x as u32, pos.y as u32);
162                if current == [0, 0, 0, 0] {
163                    return;
164                }
165                texture.set_pixel(pos.x as u32, pos.y as u32, [0, 0, 0, 0]);
166                texture.generate_normals(true);
167
168                match editing_ctx {
169                    PixelEditingContext::Tile(tile_id, _) => {
170                        ctx.ui.send(TheEvent::Custom(
171                            TheId::named("Tile Updated"),
172                            TheValue::Id(tile_id),
173                        ));
174                    }
175                    PixelEditingContext::AvatarFrame(..) => {
176                        ctx.ui.send(TheEvent::Custom(
177                            TheId::named("Editing Texture Updated"),
178                            TheValue::Empty,
179                        ));
180                    }
181                    PixelEditingContext::None => {}
182                }
183
184                self.changed = true;
185            }
186        }
187    }
188}