From 7d63d92296d524bf26c6ed7bafa520ed55b7292d Mon Sep 17 00:00:00 2001 From: white-axe Date: Fri, 15 Sep 2023 02:10:24 +0000 Subject: [PATCH 01/30] `MapView.events` is now a `Slab` instead of a `HashMap` --- src/components/map_view.rs | 4 ++-- src/graphics/event.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 7f80739f..e91c28da 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -26,7 +26,7 @@ pub struct MapView { pub pan: egui::Vec2, pub inter_tile_pan: egui::Vec2, - pub events: HashMap, + pub events: slab::Slab, pub map: Map, pub selected_layer: SelectedLayer, @@ -172,7 +172,7 @@ impl MapView { if self.event_enabled { for (_, event) in map.events.iter() { - let sprite = self.events.get(&event.id); + let sprite = self.events.get(event.id); let event_size = sprite .map(|e| e.sprite_size) .unwrap_or(egui::vec2(32., 32.)); diff --git a/src/graphics/event.rs b/src/graphics/event.rs index 839d29fc..034f12cc 100644 --- a/src/graphics/event.rs +++ b/src/graphics/event.rs @@ -21,6 +21,7 @@ use crate::prelude::*; pub struct Event { resources: Arc, pub sprite_size: egui::Vec2, + pub id: usize, } #[derive(Debug)] @@ -91,6 +92,7 @@ impl Event { Ok(Some(Self { resources: Arc::new(Resources { sprite, viewport }), sprite_size, + id: event.id, })) } From de670a59c8cf1f9892340162a7138adfa30bbc6c Mon Sep 17 00:00:00 2001 From: white-axe Date: Fri, 15 Sep 2023 04:51:43 +0000 Subject: [PATCH 02/30] Add mechanism for selecting events in the map If you hover over an event graphic in the map, it'll now show a magenta outline to show that it is selected (for things like moving it around, editing the event or deleting it). If there are multiple overlapping graphics, the one with the highest ID will be selected because event graphics are rendered in order of ID. Alternatively you can also hover over the actual tile on which the event is located, which allows you to select an event that is completely covered by another event's graphic without having to move the covering graphic aside. A green border is rendered around the selected event's tile to make this user-friendly. --- src/components/map_view.rs | 57 ++++++++++++++++++++++++++++++++++++++ src/graphics/event.rs | 2 -- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index e91c28da..4debc545 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -30,6 +30,7 @@ pub struct MapView { pub map: Map, pub selected_layer: SelectedLayer, + pub selected_event_id: Option, pub cursor_pos: egui::Pos2, pub event_enabled: bool, pub snap_to_grid: bool, @@ -68,6 +69,7 @@ impl MapView { map, selected_layer: SelectedLayer::default(), + selected_event_id: None, cursor_pos: egui::Pos2::ZERO, event_enabled: true, snap_to_grid: false, @@ -138,12 +140,14 @@ impl MapView { let canvas_pos = canvas_center + self.pan; // We check here after we calculate the scale and whatnot + let mut cursor_tile = None; if let Some(pos) = response.hover_pos() { let mut pos_tile = (pos - self.pan - canvas_center) / tile_size + egui::Vec2::new(map.width as f32 / 2., map.height as f32 / 2.); // Force the cursor to a tile instead of in-between pos_tile.x = pos_tile.x.floor().clamp(0., map.width as f32 - 1.); pos_tile.y = pos_tile.y.floor().clamp(0., map.height as f32 - 1.); + cursor_tile = Some(pos_tile); // Handle input if matches!(self.selected_layer, SelectedLayer::Tiles(_)) || dragging_event @@ -171,6 +175,9 @@ impl MapView { ); if self.event_enabled { + let mut selected_event: Option<&rpg::Event> = None; + let mut selected_event_rects = None; + for (_, event) in map.events.iter() { let sprite = self.events.get(event.id); let event_size = sprite @@ -178,6 +185,11 @@ impl MapView { .unwrap_or(egui::vec2(32., 32.)); let scaled_event_size = event_size * scale; + let tile_rect = egui::Rect::from_min_size( + map_rect.min + + egui::vec2(event.x as f32 * tile_size, event.y as f32 * tile_size), + egui::vec2(32., 32.) * scale, + ); let box_rect = egui::Rect::from_min_size( map_rect.min + egui::vec2( @@ -211,8 +223,53 @@ impl MapView { egui::Stroke::new(1., egui::Color32::WHITE), ); }); + + // Safe because rect_contains_pointer won't run unless cursor position is + // detected successfully + let cursor_tile = cursor_tile.unwrap(); + + // Handle which event should be considered selected + selected_event = match selected_event { + // If the cursor is hovering over the exact tile of an event, then that is + // the selected event + Some(e) + if cursor_tile.x == event.x as f32 + && cursor_tile.y == event.y as f32 => + { + Some(event) + } + Some(e) if cursor_tile.x == e.x as f32 && cursor_tile.y == e.y as f32 => { + selected_event + } + // Otherwise if the cursor is hovering over at least one event's graphic, + // then the one out of those with the highest ID should be the selected event + Some(e) if event.id <= e.id => selected_event, + _ => Some(event), + }; + if let Some(e) = selected_event { + if e.id == event.id { + selected_event_rects = Some((tile_rect, box_rect)); + } + } } } + + self.selected_event_id = selected_event.map(|e| e.id); + + // Draw a magenta rectangle on the border of the selected event's graphic + // and a green rectangle on the border of the selected event's tile + if let Some((tile_rect, box_rect)) = selected_event_rects { + ui.painter().rect_stroke( + tile_rect, + 12., + egui::Stroke::new(2., egui::Color32::GREEN), + ); + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(2., egui::Color32::from_rgb(255, 0, 255)), + ); + } } // Do we display the visible region? diff --git a/src/graphics/event.rs b/src/graphics/event.rs index 034f12cc..839d29fc 100644 --- a/src/graphics/event.rs +++ b/src/graphics/event.rs @@ -21,7 +21,6 @@ use crate::prelude::*; pub struct Event { resources: Arc, pub sprite_size: egui::Vec2, - pub id: usize, } #[derive(Debug)] @@ -92,7 +91,6 @@ impl Event { Ok(Some(Self { resources: Arc::new(Resources { sprite, viewport }), sprite_size, - id: event.id, })) } From 16471865f69586b3751b20877198cf330bf7216a Mon Sep 17 00:00:00 2001 From: white-axe Date: Fri, 15 Sep 2023 05:15:06 +0000 Subject: [PATCH 03/30] This arm is unreachable assuming `map.events` is sorted --- src/components/map_view.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 6d895e3d..45fbfae6 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -257,7 +257,6 @@ impl MapView { } // Otherwise if the cursor is hovering over at least one event's graphic, // then the one out of those with the highest ID should be the selected event - Some(e) if event.id <= e.id => selected_event, _ => Some(event), }; if let Some(e) = selected_event { From 8271ee4978b7ca5d38d1c05305cd176fe933c865 Mon Sep 17 00:00:00 2001 From: white-axe Date: Fri, 15 Sep 2023 16:05:41 +0000 Subject: [PATCH 04/30] Remove redundant type annotation --- src/components/map_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 45fbfae6..760171e4 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -189,7 +189,7 @@ impl MapView { ); if self.event_enabled { - let mut selected_event: Option<&rpg::Event> = None; + let mut selected_event = None; let mut selected_event_rects = None; for (_, event) in map.events.iter() { From 25248869ab5df92355556651ab501d944d01abce Mon Sep 17 00:00:00 2001 From: white-axe Date: Fri, 15 Sep 2023 16:26:37 +0000 Subject: [PATCH 05/30] Selected event is now highlighted in the hover tooltip --- src/components/map_view.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 760171e4..00e25e8e 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -231,11 +231,18 @@ impl MapView { if let Some(sprite) = sprite { sprite.paint(&painter, response.rect); } - ui.painter().rect_stroke( - response.rect, - 5., - egui::Stroke::new(1., egui::Color32::WHITE), - ); + match self.selected_event_id { + Some(id) if id == event.id => ui.painter().rect_stroke( + response.rect, + 5., + egui::Stroke::new(2., egui::Color32::from_rgb(255, 0, 255)), + ), + _ => ui.painter().rect_stroke( + response.rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ), + } }); // Safe because rect_contains_pointer won't run unless cursor position is From 5917b83f386bcffd46c2ccff0d2ae125739e3847 Mon Sep 17 00:00:00 2001 From: white-axe Date: Fri, 15 Sep 2023 16:57:24 +0000 Subject: [PATCH 06/30] Apply texture bleeding fix to events as well --- src/graphics/event.rs | 7 ++++--- src/graphics/primitives/tiles/atlas.rs | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/graphics/event.rs b/src/graphics/event.rs index 03031e4a..4556934a 100644 --- a/src/graphics/event.rs +++ b/src/graphics/event.rs @@ -67,12 +67,13 @@ impl Event { egui::vec2(cw, ch), ); + // Reduced by 0.01 px on all sides to reduce texture bleeding let tex_coords = egui::Rect::from_min_size( egui::pos2( - page.graphic.pattern as f32 * cw, - (page.graphic.direction as f32 - 2.) / 2. * ch, + page.graphic.pattern as f32 * cw + 0.01, + (page.graphic.direction as f32 - 2.) / 2. * ch + 0.01, ), - egui::vec2(cw, ch), + egui::vec2(cw - 0.02, ch - 0.02), ); let quad = primitives::Quad::new(pos, tex_coords, 0.0); diff --git a/src/graphics/primitives/tiles/atlas.rs b/src/graphics/primitives/tiles/atlas.rs index 5f240a79..4aca71f8 100644 --- a/src/graphics/primitives/tiles/atlas.rs +++ b/src/graphics/primitives/tiles/atlas.rs @@ -295,9 +295,10 @@ impl Atlas { egui::pos2(0., 0.), egui::vec2(TILE_SIZE as f32, TILE_SIZE as f32), ), + // Reduced by 0.01 px on all sides to decrease texture bleeding egui::Rect::from_min_size( - atlas_tile_position, - egui::vec2(TILE_SIZE as f32, TILE_SIZE as f32), + atlas_tile_position + egui::vec2(0.01, 0.01), + egui::vec2(TILE_SIZE as f32 - 0.02, TILE_SIZE as f32 - 0.02), ), 0.0, ) From 4d11e375fc3594ca4f69eeff312b1ff6153da78e Mon Sep 17 00:00:00 2001 From: white-axe Date: Fri, 15 Sep 2023 19:23:06 +0000 Subject: [PATCH 07/30] Don't interact with events if event layer isn't selected --- src/components/map_view.rs | 36 ++++++++++++++++++++-- src/graphics/event.rs | 4 +++ src/graphics/primitives/sprite/graphic.rs | 20 +++++++++++- src/graphics/primitives/sprite/shader.rs | 2 +- src/graphics/primitives/sprite/sprite.wgsl | 7 +++-- 5 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 00e25e8e..7405bda8 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -188,6 +188,10 @@ impl MapView { egui::Stroke::new(3., egui::Color32::DARK_GRAY), ); + if !self.event_enabled || !matches!(self.selected_layer, SelectedLayer::Events) { + self.selected_event_id = None; + } + if self.event_enabled { let mut selected_event = None; let mut selected_event_rects = None; @@ -199,6 +203,19 @@ impl MapView { .unwrap_or(egui::vec2(32., 32.)); let scaled_event_size = event_size * scale; + // Darken the graphic if required + if let Some(sprite) = sprite { + sprite.sprite().graphic.set_opacity_multiplier( + if self.darken_unselected_layers + && !matches!(self.selected_layer, SelectedLayer::Events) + { + 0.5 + } else { + 1. + }, + ); + } + let tile_rect = egui::Rect::from_min_size( map_rect.min + egui::vec2(event.x as f32 * tile_size, event.y as f32 * tile_size), @@ -217,10 +234,23 @@ impl MapView { sprite.paint(ui.painter(), box_rect); } - ui.painter() - .rect_stroke(box_rect, 5., egui::Stroke::new(1., egui::Color32::WHITE)); + if matches!(self.selected_layer, SelectedLayer::Events) { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ); + } else { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(1., egui::Color32::DARK_GRAY), + ); + } - if ui.rect_contains_pointer(box_rect) { + if matches!(self.selected_layer, SelectedLayer::Events) + && ui.rect_contains_pointer(box_rect) + { response = response.on_hover_ui_at_pointer(|ui| { ui.label(format!("Event {:0>3}: {:?}", event.id, event.name)); diff --git a/src/graphics/event.rs b/src/graphics/event.rs index 4556934a..b402b9de 100644 --- a/src/graphics/event.rs +++ b/src/graphics/event.rs @@ -97,6 +97,10 @@ impl Event { })) } + pub fn sprite(&self) -> &primitives::Sprite { + &self.resources.sprite + } + pub fn paint(&self, painter: &egui::Painter, rect: egui::Rect) { let resources = self.resources.clone(); let resource_id = Arc::new(OnceCell::new()); diff --git a/src/graphics/primitives/sprite/graphic.rs b/src/graphics/primitives/sprite/graphic.rs index c8ebbfd4..3b899092 100644 --- a/src/graphics/primitives/sprite/graphic.rs +++ b/src/graphics/primitives/sprite/graphic.rs @@ -27,13 +27,18 @@ pub struct Graphic { struct Data { hue: f32, opacity: f32, + opacity_multiplier: f32, } impl Graphic { pub fn new(hue: i32, opacity: i32) -> Self { let hue = (hue % 360) as f32 / 360.0; let opacity = opacity as f32 / 255.; - let data = Data { hue, opacity }; + let data = Data { + hue, + opacity, + opacity_multiplier: 1., + }; Self { data: AtomicCell::new(data), @@ -62,6 +67,19 @@ impl Graphic { self.data.store(Data { opacity, ..data }); } + pub fn opacity_multiplier(&self) -> f32 { + self.data.load().opacity_multiplier + } + + pub fn set_opacity_multiplier(&self, opacity_multiplier: f32) { + let data = self.data.load(); + + self.data.store(Data { + opacity_multiplier, + ..data + }); + } + pub fn as_bytes(&self) -> [u8; std::mem::size_of::()] { bytemuck::cast(self.data.load()) } diff --git a/src/graphics/primitives/sprite/shader.rs b/src/graphics/primitives/sprite/shader.rs index 4b7af3fc..370c0d72 100644 --- a/src/graphics/primitives/sprite/shader.rs +++ b/src/graphics/primitives/sprite/shader.rs @@ -44,7 +44,7 @@ impl Shader { }, wgpu::PushConstantRange { stages: wgpu::ShaderStages::FRAGMENT, - range: 64..(64 + 8), + range: 64..(64 + 4 + 4 + 4), }, ], }); diff --git a/src/graphics/primitives/sprite/sprite.wgsl b/src/graphics/primitives/sprite/sprite.wgsl index 2f7dcb32..aac20d2d 100644 --- a/src/graphics/primitives/sprite/sprite.wgsl +++ b/src/graphics/primitives/sprite/sprite.wgsl @@ -20,7 +20,8 @@ struct Viewport { struct Graphic { hue: f32, - opacity: f32 + opacity: f32, + opacity_multiplier: f32, } var push_constants: PushConstants; @@ -68,7 +69,7 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { var tex_sample = textureSample(t_diffuse, s_diffuse, in.tex_coords); - tex_sample.a *= push_constants.graphic.opacity; + tex_sample.a *= push_constants.graphic.opacity * push_constants.graphic.opacity_multiplier; if tex_sample.a <= 0. { discard; } @@ -81,4 +82,4 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { } return tex_sample; -} \ No newline at end of file +} From 4c533cf4480204339edf8781795831bdd694c5ed Mon Sep 17 00:00:00 2001 From: white-axe Date: Sat, 16 Sep 2023 01:13:20 +0000 Subject: [PATCH 08/30] Event editor now opens when double-clicking events --- src/tabs/map.rs | 19 ++++++++++++++----- src/windows/event_edit.rs | 28 ++++++++++++++++------------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index df006dae..f3d3da3c 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -224,11 +224,6 @@ impl tab::Tab for Tab { } fn show(&mut self, ui: &mut egui::Ui) { - let state = state!(); - - // Get the map. - let mut map = state.data_cache.map(self.id); - // Display the toolbar. egui::TopBottomPanel::top(format!("map_{}_toolbar", self.id)).show_inside(ui, |ui| { ui.horizontal_wrapped(|ui| { @@ -326,6 +321,9 @@ impl tab::Tab for Tab { egui::CentralPanel::default().show_inside(ui, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| { + // Get the map. + let mut map = state!().data_cache.map(self.id); + let response = self.view.ui(ui, &map, self.dragging_event); let layers_max = map.data.zsize(); @@ -342,6 +340,17 @@ impl tab::Tab for Tab { (map_x as usize, map_y as usize, tile_layer), ); } + } else { + if let Some(selected_event_id) = self.view.selected_event_id { + if let Some(selected_event) = map.events.get_mut(selected_event_id) { + if response.double_clicked() { + self.event_windows.add_window(event_edit::Window::new( + selected_event_id, + self.id, + )); + } + } + } } if ui.input(|i| { diff --git a/src/windows/event_edit.rs b/src/windows/event_edit.rs index f1c3ffa2..e9c77b07 100644 --- a/src/windows/event_edit.rs +++ b/src/windows/event_edit.rs @@ -29,19 +29,19 @@ pub struct Window { id: usize, map_id: usize, selected_page: usize, - event: rpg::Event, + name: String, viewed_tab: u8, modals: (bool, bool, bool), } impl Window { /// Create a new event editor. - pub fn new(id: usize, map_id: usize, event: rpg::Event, tileset_name: String) -> Self { + pub fn new(id: usize, map_id: usize) -> Self { Self { id, map_id, selected_page: 0, - event, + name: String::from("(unknown)"), viewed_tab: 2, modals: (false, false, false), } @@ -50,10 +50,7 @@ impl Window { impl window::Window for Window { fn name(&self) -> String { - format!( - "Event: {}, {} in Map {}", - self.event.name, self.id, self.map_id - ) + format!("Event: {}, {} in Map {}", self.name, self.id, self.map_id) } fn id(&self) -> egui::Id { @@ -61,6 +58,13 @@ impl window::Window for Window { } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + let mut map = state!().data_cache.map(self.map_id); + let event = match map.events.get_mut(self.id) { + Some(e) => e, + None => return, + }; + self.name = event.name.clone(); + let mut win_open = true; egui::Window::new(self.name()) @@ -68,7 +72,7 @@ impl window::Window for Window { .open(&mut win_open) .show(ctx, |ui| { ui.horizontal(|ui| { - ui.text_edit_singleline(&mut self.event.name); + ui.text_edit_singleline(&mut self.name); ui.button("New page").clicked(); ui.button("Copy page").clicked(); @@ -79,7 +83,7 @@ impl window::Window for Window { ui.separator(); ui.horizontal(|ui| { - for (page, _) in self.event.pages.iter().enumerate() { + for (page, _) in event.pages.iter().enumerate() { if ui .selectable_value(&mut self.selected_page, page, page.to_string()) .clicked() @@ -99,7 +103,7 @@ impl window::Window for Window { ui.separator(); - let page = self.event.pages.get_mut(self.selected_page).unwrap(); + let page = event.pages.get_mut(self.selected_page).unwrap(); match self.viewed_tab { 0 => { @@ -307,8 +311,8 @@ impl window::Window for Window { let cancel_clicked = ui.button("Cancel").clicked(); if apply_clicked || ok_clicked { - let mut map = state!().data_cache.map(self.map_id); - map.events[self.id] = self.event.clone(); + //let mut map = state!().data_cache.map(self.map_id); + //map.events[self.id] = event.clone(); } if cancel_clicked || ok_clicked { From fd3d3ff5049fba67807f21845ddd7c34d2f60760 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sat, 16 Sep 2023 02:03:06 +0000 Subject: [PATCH 09/30] I was wrong; this line is not safe --- src/components/map_view.rs | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 7405bda8..1ba20b0b 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -275,30 +275,30 @@ impl MapView { } }); - // Safe because rect_contains_pointer won't run unless cursor position is - // detected successfully - let cursor_tile = cursor_tile.unwrap(); - - // Handle which event should be considered selected - selected_event = match selected_event { - // If the cursor is hovering over the exact tile of an event, then that is - // the selected event - Some(e) - if cursor_tile.x == event.x as f32 - && cursor_tile.y == event.y as f32 => - { - Some(event) - } - Some(e) if cursor_tile.x == e.x as f32 && cursor_tile.y == e.y as f32 => { - selected_event - } - // Otherwise if the cursor is hovering over at least one event's graphic, - // then the one out of those with the highest ID should be the selected event - _ => Some(event), - }; - if let Some(e) = selected_event { - if e.id == event.id { - selected_event_rects = Some((tile_rect, box_rect)); + if let Some(cursor_tile) = cursor_tile { + // Handle which event should be considered selected + selected_event = match selected_event { + // If the cursor is hovering over the exact tile of an event, then that is + // the selected event + Some(e) + if cursor_tile.x == event.x as f32 + && cursor_tile.y == event.y as f32 => + { + Some(event) + } + Some(e) + if cursor_tile.x == e.x as f32 && cursor_tile.y == e.y as f32 => + { + selected_event + } + // Otherwise if the cursor is hovering over at least one event's graphic, + // then the one out of those with the highest ID should be the selected event + _ => Some(event), + }; + if let Some(e) = selected_event { + if e.id == event.id { + selected_event_rects = Some((tile_rect, box_rect)); + } } } } From 32d90c97cd938591029d4295df0a8422497cc5df Mon Sep 17 00:00:00 2001 From: white-axe Date: Sat, 16 Sep 2023 04:12:57 +0000 Subject: [PATCH 10/30] Fix name editing in the event editor --- src/tabs/map.rs | 12 ------------ src/windows/event_edit.rs | 6 ++++-- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index f3d3da3c..fa82a6c3 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -352,18 +352,6 @@ impl tab::Tab for Tab { } } } - - if ui.input(|i| { - i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace) - }) { - if let Some((id, _)) = map - .events - .iter() - .find(|(_, event)| event.x == map_x && event.y == map_y) - { - map.events.remove(id); - } - } }) }); diff --git a/src/windows/event_edit.rs b/src/windows/event_edit.rs index e9c77b07..8d619c0c 100644 --- a/src/windows/event_edit.rs +++ b/src/windows/event_edit.rs @@ -63,7 +63,7 @@ impl window::Window for Window { Some(e) => e, None => return, }; - self.name = event.name.clone(); + self.name.clone_from(&event.name); let mut win_open = true; @@ -72,7 +72,7 @@ impl window::Window for Window { .open(&mut win_open) .show(ctx, |ui| { ui.horizontal(|ui| { - ui.text_edit_singleline(&mut self.name); + ui.text_edit_singleline(&mut event.name); ui.button("New page").clicked(); ui.button("Copy page").clicked(); @@ -286,7 +286,9 @@ impl window::Window for Window { }); }); } + 1 => {} + 2 => { ui.vertical(|ui| { ui.group(|ui| { From 6da4fe85a01e136d66b81e3f8e45be615f3c5ce2 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sat, 16 Sep 2023 04:46:28 +0000 Subject: [PATCH 11/30] Events can now also be selected by the yellow square cursor --- src/components/map_view.rs | 135 +++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 50 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 1ba20b0b..ddc83670 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -144,14 +144,14 @@ impl MapView { let canvas_pos = canvas_center + self.pan; // We check here after we calculate the scale and whatnot - let mut cursor_tile = None; + let mut hover_tile = None; if let Some(pos) = response.hover_pos() { let mut pos_tile = (pos - self.pan - canvas_center) / tile_size + egui::Vec2::new(map.width as f32 / 2., map.height as f32 / 2.); // Force the cursor to a tile instead of in-between pos_tile.x = pos_tile.x.floor().clamp(0., map.width as f32 - 1.); pos_tile.y = pos_tile.y.floor().clamp(0., map.height as f32 - 1.); - cursor_tile = Some(pos_tile); + hover_tile = Some(pos_tile); // Handle input if matches!(self.selected_layer, SelectedLayer::Tiles(_)) || dragging_event @@ -188,6 +188,11 @@ impl MapView { egui::Stroke::new(3., egui::Color32::DARK_GRAY), ); + let cursor_rect = egui::Rect::from_min_size( + map_rect.min + (self.cursor_pos.to_vec2() * tile_size), + egui::Vec2::splat(tile_size), + ); + if !self.event_enabled || !matches!(self.selected_layer, SelectedLayer::Events) { self.selected_event_id = None; } @@ -196,6 +201,9 @@ impl MapView { let mut selected_event = None; let mut selected_event_rects = None; + // True if an event is being hovered over by the mouse, false otherwise + let mut selected_event_is_hovered = false; + for (_, event) in map.events.iter() { let sprite = self.events.get(event.id); let event_size = sprite @@ -240,60 +248,30 @@ impl MapView { 5., egui::Stroke::new(1., egui::Color32::WHITE), ); - } else { - ui.painter().rect_stroke( - box_rect, - 5., - egui::Stroke::new(1., egui::Color32::DARK_GRAY), - ); - } - if matches!(self.selected_layer, SelectedLayer::Events) - && ui.rect_contains_pointer(box_rect) - { - response = response.on_hover_ui_at_pointer(|ui| { - ui.label(format!("Event {:0>3}: {:?}", event.id, event.name)); - - let (response, painter) = ui.allocate_painter( - event_size * ui.ctx().pixels_per_point(), - egui::Sense::click(), - ); - if let Some(sprite) = sprite { - sprite.paint(&painter, response.rect); - } - match self.selected_event_id { - Some(id) if id == event.id => ui.painter().rect_stroke( - response.rect, - 5., - egui::Stroke::new(2., egui::Color32::from_rgb(255, 0, 255)), - ), - _ => ui.painter().rect_stroke( - response.rect, - 5., - egui::Stroke::new(1., egui::Color32::WHITE), - ), - } - }); - - if let Some(cursor_tile) = cursor_tile { - // Handle which event should be considered selected + // If the mouse is not hovering over an event, then we will handle the selected + // tile based on where the map cursor is + if !selected_event_is_hovered { selected_event = match selected_event { - // If the cursor is hovering over the exact tile of an event, then that is - // the selected event + // If the map cursor is on the exact tile of an event, then that is the + // selected event Some(e) - if cursor_tile.x == event.x as f32 - && cursor_tile.y == event.y as f32 => + if self.cursor_pos.x == event.x as f32 + && self.cursor_pos.y == event.y as f32 => { Some(event) } Some(e) - if cursor_tile.x == e.x as f32 && cursor_tile.y == e.y as f32 => + if self.cursor_pos.x == e.x as f32 + && self.cursor_pos.y == e.y as f32 => { selected_event } - // Otherwise if the cursor is hovering over at least one event's graphic, - // then the one out of those with the highest ID should be the selected event - _ => Some(event), + // Otherwise if the map cursor intersects at least one event's graphic, + // then the one out of those with the highest ID should be the selected + // event + _ if box_rect.contains(cursor_rect.center()) => Some(event), + _ => selected_event, }; if let Some(e) = selected_event { if e.id == event.id { @@ -301,6 +279,67 @@ impl MapView { } } } + + if ui.rect_contains_pointer(box_rect) { + response = response.on_hover_ui_at_pointer(|ui| { + ui.label(format!("Event {:0>3}: {:?}", event.id, event.name)); + + let (response, painter) = ui.allocate_painter( + event_size * ui.ctx().pixels_per_point(), + egui::Sense::click(), + ); + if let Some(sprite) = sprite { + sprite.paint(&painter, response.rect); + } + match self.selected_event_id { + Some(id) if id == event.id => ui.painter().rect_stroke( + response.rect, + 5., + egui::Stroke::new(2., egui::Color32::from_rgb(255, 0, 255)), + ), + _ => ui.painter().rect_stroke( + response.rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ), + } + }); + + if let Some(hover_tile) = hover_tile { + // Handle which event should be considered selected based on the + // hovered tile + selected_event = match selected_event { + // If the cursor is hovering over the exact tile of an event, then that is + // the selected event + Some(e) + if hover_tile.x == event.x as f32 + && hover_tile.y == event.y as f32 => + { + Some(event) + } + Some(e) + if hover_tile.x == e.x as f32 && hover_tile.y == e.y as f32 => + { + selected_event + } + // Otherwise if the cursor is hovering over at least one event's graphic, + // then the one out of those with the highest ID should be the selected event + _ => Some(event), + }; + if let Some(e) = selected_event { + if e.id == event.id { + selected_event_is_hovered = true; + selected_event_rects = Some((tile_rect, box_rect)); + } + } + } + } + } else { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(1., egui::Color32::DARK_GRAY), + ); } } @@ -343,10 +382,6 @@ impl MapView { } // Display cursor. - let cursor_rect = egui::Rect::from_min_size( - map_rect.min + (self.cursor_pos.to_vec2() * tile_size), - egui::Vec2::splat(tile_size), - ); ui.painter().rect_stroke( cursor_rect, 5., From d45f2d0d3963e42ea2ab28fe2b64527d5c308f27 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sat, 16 Sep 2023 05:14:44 +0000 Subject: [PATCH 12/30] Implement event deletion when pressing delete on the map --- src/tabs/map.rs | 12 ++++++++++++ src/windows/event_edit.rs | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index fa82a6c3..e1703f07 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -343,12 +343,24 @@ impl tab::Tab for Tab { } else { if let Some(selected_event_id) = self.view.selected_event_id { if let Some(selected_event) = map.events.get_mut(selected_event_id) { + // Double-click on events to edit them if response.double_clicked() { self.event_windows.add_window(event_edit::Window::new( selected_event_id, self.id, )); } + // Press delete or backspace to delete the selected event + else if response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| { + i.key_pressed(egui::Key::Delete) + || i.key_pressed(egui::Key::Backspace) + }) + { + map.events.remove(selected_event_id); + self.view.events.try_remove(selected_event_id); + } } } } diff --git a/src/windows/event_edit.rs b/src/windows/event_edit.rs index 8d619c0c..fcbf4259 100644 --- a/src/windows/event_edit.rs +++ b/src/windows/event_edit.rs @@ -61,7 +61,10 @@ impl window::Window for Window { let mut map = state!().data_cache.map(self.map_id); let event = match map.events.get_mut(self.id) { Some(e) => e, - None => return, + None => { + *open = false; + return; + } }; self.name.clone_from(&event.name); From bfdb95fe88e522ada2c3645c871470fa870909b7 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sat, 16 Sep 2023 05:40:18 +0000 Subject: [PATCH 13/30] Event editor windows now have (mostly) unique IDs --- src/windows/event_edit.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/windows/event_edit.rs b/src/windows/event_edit.rs index fcbf4259..b0941038 100644 --- a/src/windows/event_edit.rs +++ b/src/windows/event_edit.rs @@ -54,7 +54,9 @@ impl window::Window for Window { } fn id(&self) -> egui::Id { - egui::Id::new("Event Editor") + egui::Id::new("luminol_event_edit") + .with(self.map_id) + .with(self.id) } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { From d3b8b289236880512ed96ad74ea11ba85322830e Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 03:09:01 +0000 Subject: [PATCH 14/30] Use a vector to store the events instead of a slab A slab does not support inserting elements at arbitrary positions, which we'll need to do if we want to change the ID of an event. Also, we already know where all of the unused IDs are since we have to iterate through all the events every time we draw the map, so a regular vector will not lose any performance compared to a slab. --- rmxp-types/src/lib.rs | 3 + rmxp-types/src/option_vec.rs | 239 +++++++++++++++++++++++++++++++++++ rmxp-types/src/rmxp/map.rs | 4 +- src/components/map_view.rs | 2 +- src/tabs/map.rs | 2 +- 5 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 rmxp-types/src/option_vec.rs diff --git a/rmxp-types/src/lib.rs b/rmxp-types/src/lib.rs index 753e56ed..c694fe45 100644 --- a/rmxp-types/src/lib.rs +++ b/rmxp-types/src/lib.rs @@ -7,11 +7,14 @@ pub mod rmxp; // Shared structs with the same layout mod shared; +mod option_vec; + mod rgss_structs; mod helpers; pub use helpers::*; +pub use option_vec::OptionVec; pub use rgss_structs::{Color, Table1, Table2, Table3, Tone}; pub mod rpg { diff --git a/rmxp-types/src/option_vec.rs b/rmxp-types/src/option_vec.rs new file mode 100644 index 00000000..79e9dcfc --- /dev/null +++ b/rmxp-types/src/option_vec.rs @@ -0,0 +1,239 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +use std::ops::{Index, IndexMut}; + +use serde::ser::SerializeMap; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// A vector that can contain unused indices. +pub struct OptionVec { + vec: Vec>, +} + +pub struct Iter<'a, T> { + vec_iter: std::iter::Enumerate>>, +} + +pub struct IterMut<'a, T> { + vec_iter: std::iter::Enumerate>>, +} + +pub struct Visitor(std::marker::PhantomData); + +impl OptionVec { + /// Create a new OptionVec with no elements. + pub fn new() -> Self { + Self { vec: Vec::new() } + } + + pub fn len(&self) -> usize { + self.vec.len() + } + + pub fn is_empty(&self) -> bool { + self.vec.is_empty() + } + + pub fn get(&self, index: usize) -> Option<&T> { + self.vec.get(index).map_or(None, |x| x.as_ref()) + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.vec.get_mut(index).map_or(None, |x| x.as_mut()) + } + + pub fn capacity(&self) -> usize { + self.vec.capacity() + } + + pub fn reserve(&mut self, additional: usize) { + self.vec.reserve(additional); + } + + pub fn iter(&self) -> Iter<'_, T> { + self.into_iter() + } + + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + self.into_iter() + } + + /// Write the element at the given index. + /// If there is already an element at the given index, it will be overwritten. + /// If there isn't, a new element will be added at that index. + pub fn insert(&mut self, index: usize, element: T) { + if index >= self.len() { + let additional = self.len() - index + 1; + self.reserve(additional); + self.vec + .extend(std::iter::repeat_with(|| None).take(additional)); + } + self.vec[index] = Some(element); + } + + /// Remove the element at the given index. + /// If the OptionVec is not big enough to contain this index, this will throw an error. + /// If there isn't an element at that index, this will throw an error. + pub fn try_remove(&mut self, index: usize) -> Result<(), String> { + if index >= self.len() { + Err(String::from("index out of bounds")) + } else if self.vec[index].is_none() { + Err(String::from("index not found")) + } else { + self.vec[index] = None; + Ok(()) + } + } + + /// Remove the element at the given index. + /// If the OptionVec is not big enough to contain this index, this will panic. + /// If there isn't an element at that index, this will panic. + pub fn remove(&mut self, index: usize) { + self.try_remove(index).unwrap() + } +} + +impl Default for OptionVec { + fn default() -> Self { + OptionVec::new() + } +} + +impl FromIterator<(usize, T)> for OptionVec { + fn from_iter>(iterable: I) -> Self { + let mut vec = Vec::new(); + for (i, v) in iterable.into_iter() { + if i >= vec.len() { + let additional = i - vec.len() + 1; + vec.reserve(additional); + vec.extend(std::iter::repeat_with(|| None).take(additional)); + } + vec[i] = Some(v); + } + Self { vec } + } +} + +impl Index for OptionVec { + type Output = T; + fn index(&self, index: usize) -> &Self::Output { + self.get(index).expect("index not found") + } +} + +impl IndexMut for OptionVec { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).expect("index not found") + } +} + +impl<'a, T> IntoIterator for &'a OptionVec { + type Item = (usize, &'a T); + type IntoIter = Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter { + vec_iter: self.vec.iter().enumerate(), + } + } +} + +impl<'a, T> IntoIterator for &'a mut OptionVec { + type Item = (usize, &'a mut T); + type IntoIter = IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter { + vec_iter: self.vec.iter_mut().enumerate(), + } + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = (usize, &'a T); + fn next(&mut self) -> Option { + for (index, element) in &mut self.vec_iter { + if let Some(element) = element { + return Some((index, element)); + } + } + None + } +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = (usize, &'a mut T); + fn next(&mut self) -> Option { + for (index, element) in &mut self.vec_iter { + if let Some(element) = element { + return Some((index, element)); + } + } + None + } +} + +impl<'de, T> serde::de::Visitor<'de> for Visitor +where + T: serde::Deserialize<'de>, +{ + type Value = OptionVec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a key-value mapping") + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + std::iter::from_fn(|| match map.next_entry() { + Ok(Some(x)) => Some(Ok(x)), + Err(e) => Some(Err(e)), + Ok(None) => None, + }) + .collect() + } +} + +impl<'de, T> serde::Deserialize<'de> for OptionVec +where + T: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(Visitor(std::marker::PhantomData)) + } +} + +impl serde::Serialize for OptionVec +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut ser = serializer.serialize_map(Some(self.len()))?; + for (index, element) in self { + ser.serialize_key(&index)?; + ser.serialize_value(element)?; + } + ser.end() + } +} diff --git a/rmxp-types/src/rmxp/map.rs b/rmxp-types/src/rmxp/map.rs index 975b39ef..6af02c07 100644 --- a/rmxp-types/src/rmxp/map.rs +++ b/rmxp-types/src/rmxp/map.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . use crate::rpg::{AudioFile, Event}; -use crate::{id, Table3}; +use crate::{id, option_vec, Table3}; #[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename = "RPG::Map")] @@ -31,5 +31,5 @@ pub struct Map { pub encounter_list: Vec, pub encounter_step: i32, pub data: Table3, - pub events: slab::Slab, + pub events: option_vec::OptionVec, } diff --git a/src/components/map_view.rs b/src/components/map_view.rs index ddc83670..2cc367ca 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -26,7 +26,7 @@ pub struct MapView { pub pan: egui::Vec2, pub inter_tile_pan: egui::Vec2, - pub events: slab::Slab, + pub events: rmxp_types::OptionVec, pub map: Map, pub selected_layer: SelectedLayer, diff --git a/src/tabs/map.rs b/src/tabs/map.rs index e1703f07..5c155846 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -359,7 +359,7 @@ impl tab::Tab for Tab { }) { map.events.remove(selected_event_id); - self.view.events.try_remove(selected_event_id); + let _ = self.view.events.try_remove(selected_event_id); } } } From 35e29f1e4c98162e51f9a356f3b79e0b48c94f3e Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 05:50:06 +0000 Subject: [PATCH 15/30] Double-click on map tiles without events to create one --- src/components/map_view.rs | 4 +- src/tabs/map.rs | 80 +++++++++++++++++++++++++++++++------- 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 2cc367ca..de05bc65 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -242,7 +242,9 @@ impl MapView { sprite.paint(ui.painter(), box_rect); } - if matches!(self.selected_layer, SelectedLayer::Events) { + if matches!(self.selected_layer, SelectedLayer::Events) + && ui.input(|i| !i.modifiers.shift) + { ui.painter().rect_stroke( box_rect, 5., diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 5c155846..e14bfb44 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -207,6 +207,53 @@ impl Tab { } } } + + fn add_event(&self, map: &mut rpg::Map) { + let mut first_vacant_id = 1; + let mut max_event_id = 0; + + for (_, event) in map.events.iter() { + if event.id == first_vacant_id { + first_vacant_id += 1; + } + max_event_id = event.id; + + if event.x == self.view.cursor_pos.x as i32 && event.y == self.view.cursor_pos.y as i32 + { + state!() + .toasts + .error("Cannot create event on an existing event's tile"); + return; + } + } + + // Try first to allocate the event number directly after the current highest one. + // However, valid event number range in RPG Maker XP and VX is 1-999. + let new_event_id = if max_event_id < 999 { + max_event_id + 1 + } + // Otherwise, we'll try to use a non-allocated event ID that isn't zero. + else if first_vacant_id <= 999 { + first_vacant_id + } else { + state!() + .toasts + .error("Event limit reached, please delete some events"); + return; + }; + + map.events.insert( + new_event_id, + rpg::Event::new( + self.view.cursor_pos.x as i32, + self.view.cursor_pos.y as i32, + new_event_id, + ), + ); + + self.event_windows + .add_window(event_edit::Window::new(new_event_id, self.id)); + } } impl tab::Tab for Tab { @@ -342,25 +389,32 @@ impl tab::Tab for Tab { } } else { if let Some(selected_event_id) = self.view.selected_event_id { - if let Some(selected_event) = map.events.get_mut(selected_event_id) { + if response.double_clicked() { // Double-click on events to edit them - if response.double_clicked() { + if ui.input(|i| !i.modifiers.command) { self.event_windows.add_window(event_edit::Window::new( selected_event_id, self.id, )); } - // Press delete or backspace to delete the selected event - else if response.hovered() - && ui.memory(|m| m.focus().is_none()) - && ui.input(|i| { - i.key_pressed(egui::Key::Delete) - || i.key_pressed(egui::Key::Backspace) - }) - { - map.events.remove(selected_event_id); - let _ = self.view.events.try_remove(selected_event_id); - } + } + + // Press delete or backspace to delete the selected event + if response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| { + i.key_pressed(egui::Key::Delete) + || i.key_pressed(egui::Key::Backspace) + }) + { + map.events.remove(selected_event_id); + let _ = self.view.events.try_remove(selected_event_id); + } + } else { + // Double-click on an empty space to add an event + // (hold shift to prevent events from being selected) + if response.double_clicked() { + self.add_event(&mut map); } } } From 136e2055913a8703685d083247642a4803314cf7 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 06:02:15 +0000 Subject: [PATCH 16/30] Enter key can now also be used for event creation/editing --- src/tabs/map.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index e14bfb44..6da4d253 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -389,8 +389,12 @@ impl tab::Tab for Tab { } } else { if let Some(selected_event_id) = self.view.selected_event_id { - if response.double_clicked() { - // Double-click on events to edit them + if response.double_clicked() + || (response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| i.key_pressed(egui::Key::Enter))) + { + // Double-click/press enter on events to edit them if ui.input(|i| !i.modifiers.command) { self.event_windows.add_window(event_edit::Window::new( selected_event_id, @@ -411,9 +415,13 @@ impl tab::Tab for Tab { let _ = self.view.events.try_remove(selected_event_id); } } else { - // Double-click on an empty space to add an event + // Double-click/press enter on an empty space to add an event // (hold shift to prevent events from being selected) - if response.double_clicked() { + if response.double_clicked() + || (response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| i.key_pressed(egui::Key::Enter))) + { self.add_event(&mut map); } } From f201bab862ad3de7bc6b06e31775dd66aaa83cdd Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 06:37:48 +0000 Subject: [PATCH 17/30] Fix incorrect serialization length for `OptionVec` --- rmxp-types/src/option_vec.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/rmxp-types/src/option_vec.rs b/rmxp-types/src/option_vec.rs index 79e9dcfc..c3e2691d 100644 --- a/rmxp-types/src/option_vec.rs +++ b/rmxp-types/src/option_vec.rs @@ -19,10 +19,11 @@ use std::ops::{Index, IndexMut}; use serde::ser::SerializeMap; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq)] /// A vector that can contain unused indices. pub struct OptionVec { vec: Vec>, + num_values: usize, } pub struct Iter<'a, T> { @@ -38,13 +39,20 @@ pub struct Visitor(std::marker::PhantomData); impl OptionVec { /// Create a new OptionVec with no elements. pub fn new() -> Self { - Self { vec: Vec::new() } + Self { + vec: Vec::new(), + num_values: 0, + } } pub fn len(&self) -> usize { self.vec.len() } + pub fn size(&self) -> usize { + self.num_values + } + pub fn is_empty(&self) -> bool { self.vec.is_empty() } @@ -83,6 +91,9 @@ impl OptionVec { self.vec .extend(std::iter::repeat_with(|| None).take(additional)); } + if self.vec[index].is_none() { + self.num_values += 1; + } self.vec[index] = Some(element); } @@ -95,6 +106,7 @@ impl OptionVec { } else if self.vec[index].is_none() { Err(String::from("index not found")) } else { + self.num_values -= 1; self.vec[index] = None; Ok(()) } @@ -117,6 +129,7 @@ impl Default for OptionVec { impl FromIterator<(usize, T)> for OptionVec { fn from_iter>(iterable: I) -> Self { let mut vec = Vec::new(); + let mut num_values = 0; for (i, v) in iterable.into_iter() { if i >= vec.len() { let additional = i - vec.len() + 1; @@ -124,8 +137,9 @@ impl FromIterator<(usize, T)> for OptionVec { vec.extend(std::iter::repeat_with(|| None).take(additional)); } vec[i] = Some(v); + num_values += 1; } - Self { vec } + Self { vec, num_values } } } @@ -229,7 +243,7 @@ where where S: serde::Serializer, { - let mut ser = serializer.serialize_map(Some(self.len()))?; + let mut ser = serializer.serialize_map(Some(self.size()))?; for (index, element) in self { ser.serialize_key(&index)?; ser.serialize_value(element)?; From 8a4ef3480e6815f1d7580926f0430a1234fa7107 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 16:04:22 +0000 Subject: [PATCH 18/30] Drag-and-drop events to move them on the map --- src/components/map_view.rs | 82 +++++++++++++++++++++++++------------- src/tabs/map.rs | 30 ++++++++++++++ 2 files changed, 84 insertions(+), 28 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index de05bc65..2b7c4e1b 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -35,6 +35,11 @@ pub struct MapView { pub event_enabled: bool, pub snap_to_grid: bool, + /// The map coordinates of the tile being hovered over + pub hover_tile: Option, + /// If hovering over the exact tile of an event, this is that event's ID + pub hover_tile_event_id: Option, + pub darken_unselected_layers: bool, pub scale: f32, @@ -78,6 +83,9 @@ impl MapView { darken_unselected_layers: true, + hover_tile: None, + hover_tile_event_id: None, + scale: 100., }) } @@ -144,14 +152,14 @@ impl MapView { let canvas_pos = canvas_center + self.pan; // We check here after we calculate the scale and whatnot - let mut hover_tile = None; + self.hover_tile = None; if let Some(pos) = response.hover_pos() { let mut pos_tile = (pos - self.pan - canvas_center) / tile_size + egui::Vec2::new(map.width as f32 / 2., map.height as f32 / 2.); // Force the cursor to a tile instead of in-between pos_tile.x = pos_tile.x.floor().clamp(0., map.width as f32 - 1.); pos_tile.y = pos_tile.y.floor().clamp(0., map.height as f32 - 1.); - hover_tile = Some(pos_tile); + self.hover_tile = Some(pos_tile.to_pos2()); // Handle input if matches!(self.selected_layer, SelectedLayer::Tiles(_)) || dragging_event @@ -196,6 +204,7 @@ impl MapView { if !self.event_enabled || !matches!(self.selected_layer, SelectedLayer::Events) { self.selected_event_id = None; } + self.hover_tile_event_id = None; if self.event_enabled { let mut selected_event = None; @@ -253,7 +262,7 @@ impl MapView { // If the mouse is not hovering over an event, then we will handle the selected // tile based on where the map cursor is - if !selected_event_is_hovered { + if !selected_event_is_hovered && !dragging_event { selected_event = match selected_event { // If the map cursor is on the exact tile of an event, then that is the // selected event @@ -307,35 +316,52 @@ impl MapView { } }); - if let Some(hover_tile) = hover_tile { - // Handle which event should be considered selected based on the - // hovered tile - selected_event = match selected_event { - // If the cursor is hovering over the exact tile of an event, then that is - // the selected event - Some(e) - if hover_tile.x == event.x as f32 - && hover_tile.y == event.y as f32 => - { - Some(event) - } - Some(e) - if hover_tile.x == e.x as f32 && hover_tile.y == e.y as f32 => - { - selected_event - } - // Otherwise if the cursor is hovering over at least one event's graphic, - // then the one out of those with the highest ID should be the selected event - _ => Some(event), - }; - if let Some(e) = selected_event { - if e.id == event.id { - selected_event_is_hovered = true; - selected_event_rects = Some((tile_rect, box_rect)); + if let Some(hover_tile) = self.hover_tile { + // If the cursor is hovering over this event's tile, + // set hover_tile_event_id to its event ID + if hover_tile.x == event.x as f32 && hover_tile.y == event.y as f32 { + self.hover_tile_event_id = Some(event.id); + } + + if !dragging_event { + // Handle which event should be considered selected based on the + // hovered tile + selected_event = match selected_event { + // If the cursor is hovering over the exact tile of an event, then that is + // the selected event + Some(e) + if hover_tile.x == event.x as f32 + && hover_tile.y == event.y as f32 => + { + Some(event) + } + Some(e) + if hover_tile.x == e.x as f32 + && hover_tile.y == e.y as f32 => + { + selected_event + } + // Otherwise if the cursor is hovering over at least one event's graphic, + // then the one out of those with the highest ID should be the selected event + _ => Some(event), + }; + if let Some(e) = selected_event { + if e.id == event.id { + selected_event_is_hovered = true; + selected_event_rects = Some((tile_rect, box_rect)); + } } } } } + + // If an event is being dragged, that should always be the selected event + if let Some(id) = self.selected_event_id { + if dragging_event && id == event.id { + selected_event = Some(event); + selected_event_rects = Some((tile_rect, box_rect)); + } + } } else { ui.painter().rect_stroke( box_rect, diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 6da4d253..99fa5184 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -377,6 +377,10 @@ impl tab::Tab for Tab { let map_x = self.view.cursor_pos.x as i32; let map_y = self.view.cursor_pos.y as i32; + if self.dragging_event && self.view.selected_event_id.is_none() { + self.dragging_event = false; + } + if let SelectedLayer::Tiles(tile_layer) = self.view.selected_layer { if response.dragged_by(egui::PointerButton::Primary) && !ui.input(|i| i.modifiers.command) @@ -402,6 +406,16 @@ impl tab::Tab for Tab { )); } } + // Allow drag and drop to move events + else if !self.dragging_event + && response.drag_started_by(egui::PointerButton::Primary) + { + self.dragging_event = true; + } else if self.dragging_event + && response.drag_released_by(egui::PointerButton::Primary) + { + self.dragging_event = false; + } // Press delete or backspace to delete the selected event if response.hovered() @@ -414,6 +428,22 @@ impl tab::Tab for Tab { map.events.remove(selected_event_id); let _ = self.view.events.try_remove(selected_event_id); } + + if let Some(selected_event) = map.events.get_mut(selected_event_id) { + if let Some(hover_tile) = self.view.hover_tile { + // If dragging an event and the cursor is not hovering over the tile of + // a different event, move the dragged event's tile to the cursor + if self.dragging_event + && match self.view.hover_tile_event_id { + Some(id) => id == selected_event_id, + None => true, + } + { + selected_event.x = hover_tile.x as i32; + selected_event.y = hover_tile.y as i32; + } + } + } } else { // Double-click/press enter on an empty space to add an event // (hold shift to prevent events from being selected) From 3ea2dbbda2355527f72b6f347cf5865f1cbe0570 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 16:18:36 +0000 Subject: [PATCH 19/30] Remove unused variables from map_view.rs --- src/components/map_view.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 2b7c4e1b..fb5c5169 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -266,7 +266,7 @@ impl MapView { selected_event = match selected_event { // If the map cursor is on the exact tile of an event, then that is the // selected event - Some(e) + Some(_) if self.cursor_pos.x == event.x as f32 && self.cursor_pos.y == event.y as f32 => { @@ -329,7 +329,7 @@ impl MapView { selected_event = match selected_event { // If the cursor is hovering over the exact tile of an event, then that is // the selected event - Some(e) + Some(_) if hover_tile.x == event.x as f32 && hover_tile.y == event.y as f32 => { From 79087dab3b89fad51344a7cdda51a65a21cf5338 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 17:20:52 +0000 Subject: [PATCH 20/30] Event drag-and-drop now preserves the event offset from the cursor --- src/components/map_view.rs | 10 ------- src/tabs/map.rs | 56 ++++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index fb5c5169..3f045403 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -37,8 +37,6 @@ pub struct MapView { /// The map coordinates of the tile being hovered over pub hover_tile: Option, - /// If hovering over the exact tile of an event, this is that event's ID - pub hover_tile_event_id: Option, pub darken_unselected_layers: bool, @@ -84,7 +82,6 @@ impl MapView { darken_unselected_layers: true, hover_tile: None, - hover_tile_event_id: None, scale: 100., }) @@ -204,7 +201,6 @@ impl MapView { if !self.event_enabled || !matches!(self.selected_layer, SelectedLayer::Events) { self.selected_event_id = None; } - self.hover_tile_event_id = None; if self.event_enabled { let mut selected_event = None; @@ -317,12 +313,6 @@ impl MapView { }); if let Some(hover_tile) = self.hover_tile { - // If the cursor is hovering over this event's tile, - // set hover_tile_event_id to its event ID - if hover_tile.x == event.x as f32 && hover_tile.y == event.y as f32 { - self.hover_tile_event_id = Some(event.id); - } - if !dragging_event { // Handle which event should be considered selected based on the // hovered tile diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 99fa5184..9864cf21 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -38,6 +38,10 @@ pub struct Tab { dragging_event: bool, event_windows: window::Windows, force_close: bool, + + /// When event dragging starts, this is set to the difference between + /// the dragged event's tile and the cursor position + pub event_drag_offset: Option, } impl Tab { @@ -55,6 +59,8 @@ impl Tab { dragging_event: false, event_windows: window::Windows::default(), force_close: false, + + event_drag_offset: None, }) } @@ -379,6 +385,7 @@ impl tab::Tab for Tab { if self.dragging_event && self.view.selected_event_id.is_none() { self.dragging_event = false; + self.event_drag_offset = None; } if let SelectedLayer::Tiles(tile_layer) = self.view.selected_layer { @@ -415,6 +422,7 @@ impl tab::Tab for Tab { && response.drag_released_by(egui::PointerButton::Primary) { self.dragging_event = false; + self.event_drag_offset = None; } // Press delete or backspace to delete the selected event @@ -429,18 +437,44 @@ impl tab::Tab for Tab { let _ = self.view.events.try_remove(selected_event_id); } - if let Some(selected_event) = map.events.get_mut(selected_event_id) { - if let Some(hover_tile) = self.view.hover_tile { - // If dragging an event and the cursor is not hovering over the tile of - // a different event, move the dragged event's tile to the cursor - if self.dragging_event - && match self.view.hover_tile_event_id { - Some(id) => id == selected_event_id, - None => true, + if let Some(hover_tile) = self.view.hover_tile { + if self.dragging_event { + if let Some(selected_event) = map.events.get(selected_event_id) { + // If we just started dragging an event, save the offset between the + // cursor and the event's tile so that the event will be dragged + // with that offset from the cursor + if self.event_drag_offset.is_none() { + self.event_drag_offset = Some( + egui::Pos2::new( + selected_event.x as f32, + selected_event.y as f32, + ) - hover_tile, + ); + }; + } + + if let Some(offset) = self.event_drag_offset { + // If dragging an event and the cursor is not hovering over the tile of + // a different event, move the dragged event's tile to the cursor + let adjusted_hover_tile = hover_tile + offset; + if map + .events + .iter() + .filter(|(_, e)| { + adjusted_hover_tile.x == e.x as f32 + && adjusted_hover_tile.y == e.y as f32 + }) + .map(|(_, e)| e.id) + .next() + .is_none() + { + if let Some(selected_event) = + map.events.get_mut(selected_event_id) + { + selected_event.x = adjusted_hover_tile.x as i32; + selected_event.y = adjusted_hover_tile.y as i32; + } } - { - selected_event.x = hover_tile.x as i32; - selected_event.y = hover_tile.y as i32; } } } From afdd001f46a2afa803ed1e2f164aca1d635415d2 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 17:29:51 +0000 Subject: [PATCH 21/30] Apply clippy recommendations --- rmxp-types/src/option_vec.rs | 4 +- src/tabs/map.rs | 156 +++++++++++++++++------------------ 2 files changed, 77 insertions(+), 83 deletions(-) diff --git a/rmxp-types/src/option_vec.rs b/rmxp-types/src/option_vec.rs index c3e2691d..7c60f091 100644 --- a/rmxp-types/src/option_vec.rs +++ b/rmxp-types/src/option_vec.rs @@ -58,11 +58,11 @@ impl OptionVec { } pub fn get(&self, index: usize) -> Option<&T> { - self.vec.get(index).map_or(None, |x| x.as_ref()) + self.vec.get(index).and_then(|x| x.as_ref()) } pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { - self.vec.get_mut(index).map_or(None, |x| x.as_mut()) + self.vec.get_mut(index).and_then(|x| x.as_mut()) } pub fn capacity(&self) -> usize { diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 9864cf21..845c6e22 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -398,96 +398,90 @@ impl tab::Tab for Tab { (map_x as usize, map_y as usize, tile_layer), ); } - } else { - if let Some(selected_event_id) = self.view.selected_event_id { - if response.double_clicked() - || (response.hovered() - && ui.memory(|m| m.focus().is_none()) - && ui.input(|i| i.key_pressed(egui::Key::Enter))) - { - // Double-click/press enter on events to edit them - if ui.input(|i| !i.modifiers.command) { - self.event_windows.add_window(event_edit::Window::new( - selected_event_id, - self.id, - )); - } - } - // Allow drag and drop to move events - else if !self.dragging_event - && response.drag_started_by(egui::PointerButton::Primary) - { - self.dragging_event = true; - } else if self.dragging_event - && response.drag_released_by(egui::PointerButton::Primary) - { - self.dragging_event = false; - self.event_drag_offset = None; - } - - // Press delete or backspace to delete the selected event - if response.hovered() + } else if let Some(selected_event_id) = self.view.selected_event_id { + if response.double_clicked() + || (response.hovered() && ui.memory(|m| m.focus().is_none()) - && ui.input(|i| { - i.key_pressed(egui::Key::Delete) - || i.key_pressed(egui::Key::Backspace) - }) - { - map.events.remove(selected_event_id); - let _ = self.view.events.try_remove(selected_event_id); + && ui.input(|i| i.key_pressed(egui::Key::Enter))) + { + // Double-click/press enter on events to edit them + if ui.input(|i| !i.modifiers.command) { + self.event_windows + .add_window(event_edit::Window::new(selected_event_id, self.id)); } + } + // Allow drag and drop to move events + else if !self.dragging_event + && response.drag_started_by(egui::PointerButton::Primary) + { + self.dragging_event = true; + } else if self.dragging_event + && response.drag_released_by(egui::PointerButton::Primary) + { + self.dragging_event = false; + self.event_drag_offset = None; + } - if let Some(hover_tile) = self.view.hover_tile { - if self.dragging_event { - if let Some(selected_event) = map.events.get(selected_event_id) { - // If we just started dragging an event, save the offset between the - // cursor and the event's tile so that the event will be dragged - // with that offset from the cursor - if self.event_drag_offset.is_none() { - self.event_drag_offset = Some( - egui::Pos2::new( - selected_event.x as f32, - selected_event.y as f32, - ) - hover_tile, - ); - }; - } + // Press delete or backspace to delete the selected event + if response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| { + i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace) + }) + { + map.events.remove(selected_event_id); + let _ = self.view.events.try_remove(selected_event_id); + } - if let Some(offset) = self.event_drag_offset { - // If dragging an event and the cursor is not hovering over the tile of - // a different event, move the dragged event's tile to the cursor - let adjusted_hover_tile = hover_tile + offset; - if map - .events - .iter() - .filter(|(_, e)| { - adjusted_hover_tile.x == e.x as f32 - && adjusted_hover_tile.y == e.y as f32 - }) - .map(|(_, e)| e.id) - .next() - .is_none() + if let Some(hover_tile) = self.view.hover_tile { + if self.dragging_event { + if let Some(selected_event) = map.events.get(selected_event_id) { + // If we just started dragging an event, save the offset between the + // cursor and the event's tile so that the event will be dragged + // with that offset from the cursor + if self.event_drag_offset.is_none() { + self.event_drag_offset = Some( + egui::Pos2::new( + selected_event.x as f32, + selected_event.y as f32, + ) - hover_tile, + ); + }; + } + + if let Some(offset) = self.event_drag_offset { + // If dragging an event and the cursor is not hovering over the tile of + // a different event, move the dragged event's tile to the cursor + let adjusted_hover_tile = hover_tile + offset; + if map + .events + .iter() + .filter(|(_, e)| { + adjusted_hover_tile.x == e.x as f32 + && adjusted_hover_tile.y == e.y as f32 + }) + .next() + .is_none() + { + if let Some(selected_event) = + map.events.get_mut(selected_event_id) { - if let Some(selected_event) = - map.events.get_mut(selected_event_id) - { - selected_event.x = adjusted_hover_tile.x as i32; - selected_event.y = adjusted_hover_tile.y as i32; - } + selected_event.x = adjusted_hover_tile.x as i32; + selected_event.y = adjusted_hover_tile.y as i32; } } } } - } else { - // Double-click/press enter on an empty space to add an event - // (hold shift to prevent events from being selected) - if response.double_clicked() - || (response.hovered() - && ui.memory(|m| m.focus().is_none()) - && ui.input(|i| i.key_pressed(egui::Key::Enter))) - { - self.add_event(&mut map); - } + } + } else { + // Double-click/press enter on an empty space to add an event + // (hold shift to prevent events from being selected) + if response.double_clicked() + || (response.hovered() + && ui.memory(|m| m.focus().is_none()) + && ui.input(|i| i.key_pressed(egui::Key::Enter))) + { + self.add_event(&mut map); } } }) From 6eddc6e3fcb6bbd8003c6bd65fb9ff78cb2a8909 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 17:40:59 +0000 Subject: [PATCH 22/30] Apply another clippy recommendation --- src/tabs/map.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 845c6e22..9354a3eb 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -453,16 +453,10 @@ impl tab::Tab for Tab { // If dragging an event and the cursor is not hovering over the tile of // a different event, move the dragged event's tile to the cursor let adjusted_hover_tile = hover_tile + offset; - if map - .events - .iter() - .filter(|(_, e)| { - adjusted_hover_tile.x == e.x as f32 - && adjusted_hover_tile.y == e.y as f32 - }) - .next() - .is_none() - { + if !map.events.iter().any(|(_, e)| { + adjusted_hover_tile.x == e.x as f32 + && adjusted_hover_tile.y == e.y as f32 + }) { if let Some(selected_event) = map.events.get_mut(selected_event_id) { From bec1a79b5671f9dbb67938918fd21ea27efcf8a7 Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 18:14:53 +0000 Subject: [PATCH 23/30] Fix misleading comment in src/tabs/map.rs --- src/tabs/map.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 9354a3eb..187e4935 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -450,8 +450,9 @@ impl tab::Tab for Tab { } if let Some(offset) = self.event_drag_offset { - // If dragging an event and the cursor is not hovering over the tile of - // a different event, move the dragged event's tile to the cursor + // If moving an event, move the dragged event's tile to the cursor + // after adjusting for drag offset, unless that would put the event + // on the same tile as an existing event let adjusted_hover_tile = hover_tile + offset; if !map.events.iter().any(|(_, e)| { adjusted_hover_tile.x == e.x as f32 From bce08f2bf00a37ce607639949ad8c9a66b909b6b Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 20:24:33 +0000 Subject: [PATCH 24/30] Don't move events when the mouse is not hovering over them --- src/components/map_view.rs | 15 ++++++++++----- src/tabs/map.rs | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 3f045403..1b93b6dc 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -38,6 +38,11 @@ pub struct MapView { /// The map coordinates of the tile being hovered over pub hover_tile: Option, + /// True if selected_event_id is being hovered over by the mouse + /// (as opposed to the map cursor) + /// and false otherwise + pub selected_event_is_hovered: bool, + pub darken_unselected_layers: bool, pub scale: f32, @@ -83,6 +88,8 @@ impl MapView { hover_tile: None, + selected_event_is_hovered: false, + scale: 100., }) } @@ -201,14 +208,12 @@ impl MapView { if !self.event_enabled || !matches!(self.selected_layer, SelectedLayer::Events) { self.selected_event_id = None; } + self.selected_event_is_hovered = false; if self.event_enabled { let mut selected_event = None; let mut selected_event_rects = None; - // True if an event is being hovered over by the mouse, false otherwise - let mut selected_event_is_hovered = false; - for (_, event) in map.events.iter() { let sprite = self.events.get(event.id); let event_size = sprite @@ -258,7 +263,7 @@ impl MapView { // If the mouse is not hovering over an event, then we will handle the selected // tile based on where the map cursor is - if !selected_event_is_hovered && !dragging_event { + if !self.selected_event_is_hovered && !dragging_event { selected_event = match selected_event { // If the map cursor is on the exact tile of an event, then that is the // selected event @@ -337,7 +342,7 @@ impl MapView { }; if let Some(e) = selected_event { if e.id == event.id { - selected_event_is_hovered = true; + self.selected_event_is_hovered = true; selected_event_rects = Some((tile_rect, box_rect)); } } diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 187e4935..5922ec37 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -412,6 +412,7 @@ impl tab::Tab for Tab { } // Allow drag and drop to move events else if !self.dragging_event + && self.view.selected_event_is_hovered && response.drag_started_by(egui::PointerButton::Primary) { self.dragging_event = true; From 40d96adb64488e7e03d4050fefe0ced77fde453b Mon Sep 17 00:00:00 2001 From: white-axe Date: Sun, 17 Sep 2023 22:06:03 +0000 Subject: [PATCH 25/30] Simplify deserialization implementation for OptionVec --- rmxp-types/src/option_vec.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/rmxp-types/src/option_vec.rs b/rmxp-types/src/option_vec.rs index 7c60f091..6e9a1ea1 100644 --- a/rmxp-types/src/option_vec.rs +++ b/rmxp-types/src/option_vec.rs @@ -214,12 +214,7 @@ where where A: serde::de::MapAccess<'de>, { - std::iter::from_fn(|| match map.next_entry() { - Ok(Some(x)) => Some(Ok(x)), - Err(e) => Some(Err(e)), - Ok(None) => None, - }) - .collect() + std::iter::from_fn(|| map.next_entry().transpose()).collect() } } From fb36e0bebe3189788737b46160ec90a76d12bb63 Mon Sep 17 00:00:00 2001 From: white-axe Date: Mon, 18 Sep 2023 00:03:18 +0000 Subject: [PATCH 26/30] Remove `pub` from `event_drag_offset` --- src/tabs/map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 5922ec37..47618a8b 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -41,7 +41,7 @@ pub struct Tab { /// When event dragging starts, this is set to the difference between /// the dragged event's tile and the cursor position - pub event_drag_offset: Option, + event_drag_offset: Option, } impl Tab { From 4d2f4424c02b3fde52203311bf261d58b7049444 Mon Sep 17 00:00:00 2001 From: white-axe Date: Mon, 18 Sep 2023 01:27:02 +0000 Subject: [PATCH 27/30] Fix crash when adding event on a map with no events --- rmxp-types/src/option_vec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rmxp-types/src/option_vec.rs b/rmxp-types/src/option_vec.rs index 6e9a1ea1..76900db5 100644 --- a/rmxp-types/src/option_vec.rs +++ b/rmxp-types/src/option_vec.rs @@ -86,7 +86,7 @@ impl OptionVec { /// If there isn't, a new element will be added at that index. pub fn insert(&mut self, index: usize, element: T) { if index >= self.len() { - let additional = self.len() - index + 1; + let additional = index - self.len() + 1; self.reserve(additional); self.vec .extend(std::iter::repeat_with(|| None).take(additional)); From e7b2ab3a53bb128e3cdb48e2aa844b1820f2bf17 Mon Sep 17 00:00:00 2001 From: white-axe Date: Mon, 18 Sep 2023 02:11:41 +0000 Subject: [PATCH 28/30] Prevent event from sticking to cursor after double-clicking --- src/tabs/map.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index 47618a8b..db3c7e1e 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -406,6 +406,8 @@ impl tab::Tab for Tab { { // Double-click/press enter on events to edit them if ui.input(|i| !i.modifiers.command) { + self.dragging_event = false; + self.event_drag_offset = None; self.event_windows .add_window(event_edit::Window::new(selected_event_id, self.id)); } @@ -417,7 +419,7 @@ impl tab::Tab for Tab { { self.dragging_event = true; } else if self.dragging_event - && response.drag_released_by(egui::PointerButton::Primary) + && !response.dragged_by(egui::PointerButton::Primary) { self.dragging_event = false; self.event_drag_offset = None; @@ -477,6 +479,8 @@ impl tab::Tab for Tab { && ui.memory(|m| m.focus().is_none()) && ui.input(|i| i.key_pressed(egui::Key::Enter))) { + self.dragging_event = false; + self.event_drag_offset = None; self.add_event(&mut map); } } From 0c5d4b47dc14d21104df371e864786c1c7709d93 Mon Sep 17 00:00:00 2001 From: white-axe Date: Mon, 18 Sep 2023 02:15:02 +0000 Subject: [PATCH 29/30] Selected event border is now yellow instead of magenta --- src/components/map_view.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 1b93b6dc..21af83bb 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -307,7 +307,7 @@ impl MapView { Some(id) if id == event.id => ui.painter().rect_stroke( response.rect, 5., - egui::Stroke::new(2., egui::Color32::from_rgb(255, 0, 255)), + egui::Stroke::new(2., egui::Color32::YELLOW), ), _ => ui.painter().rect_stroke( response.rect, @@ -368,18 +368,12 @@ impl MapView { self.selected_event_id = selected_event.map(|e| e.id); - // Draw a magenta rectangle on the border of the selected event's graphic - // and a green rectangle on the border of the selected event's tile + // Draw a yellow rectangle on the border of the selected event's graphic if let Some((tile_rect, box_rect)) = selected_event_rects { - ui.painter().rect_stroke( - tile_rect, - 12., - egui::Stroke::new(2., egui::Color32::GREEN), - ); ui.painter().rect_stroke( box_rect, 5., - egui::Stroke::new(2., egui::Color32::from_rgb(255, 0, 255)), + egui::Stroke::new(2., egui::Color32::YELLOW), ); } } From 96f82991d40889f0fe27e0c28f6e2f4a20e2a0e1 Mon Sep 17 00:00:00 2001 From: white-axe Date: Mon, 18 Sep 2023 02:46:41 +0000 Subject: [PATCH 30/30] Show magenta border around events that are being edited --- rmxp-types/src/shared/event.rs | 11 +++++++++++ src/components/map_view.rs | 27 +++++++++++++++++++++------ src/tabs/map.rs | 4 ++++ src/windows/event_edit.rs | 1 + 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/rmxp-types/src/shared/event.rs b/rmxp-types/src/shared/event.rs index 5c07f2cb..3192ec2b 100644 --- a/rmxp-types/src/shared/event.rs +++ b/rmxp-types/src/shared/event.rs @@ -25,6 +25,15 @@ pub struct Event { pub x: i32, pub y: i32, pub pages: Vec, + + #[serde(skip)] + pub extra_data: EventExtraData, +} + +#[derive(Debug, Default, Clone)] +pub struct EventExtraData { + /// Whether or not the event editor for this event is open + pub is_editor_open: bool, } impl Event { @@ -36,6 +45,8 @@ impl Event { x, y, pages: vec![EventPage::default()], + + extra_data: EventExtraData::default(), } } } diff --git a/src/components/map_view.rs b/src/components/map_view.rs index 21af83bb..e0854178 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -364,17 +364,32 @@ impl MapView { egui::Stroke::new(1., egui::Color32::DARK_GRAY), ); } + + // Draw a magenta rectangle on the border of events that are being edited + if event.extra_data.is_editor_open { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(3., egui::Color32::from_rgb(255, 0, 255)), + ); + } } self.selected_event_id = selected_event.map(|e| e.id); // Draw a yellow rectangle on the border of the selected event's graphic - if let Some((tile_rect, box_rect)) = selected_event_rects { - ui.painter().rect_stroke( - box_rect, - 5., - egui::Stroke::new(2., egui::Color32::YELLOW), - ); + if let Some(selected_event) = selected_event { + // Make sure the event editor isn't open so we don't draw over the + // magenta rectangle + if !selected_event.extra_data.is_editor_open { + if let Some((tile_rect, box_rect)) = selected_event_rects { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(3., egui::Color32::YELLOW), + ); + } + } } } diff --git a/src/tabs/map.rs b/src/tabs/map.rs index db3c7e1e..c85f36fb 100644 --- a/src/tabs/map.rs +++ b/src/tabs/map.rs @@ -484,6 +484,10 @@ impl tab::Tab for Tab { self.add_event(&mut map); } } + + for (_, event) in map.events.iter_mut() { + event.extra_data.is_editor_open = false; + } }) }); diff --git a/src/windows/event_edit.rs b/src/windows/event_edit.rs index b0941038..cf740f20 100644 --- a/src/windows/event_edit.rs +++ b/src/windows/event_edit.rs @@ -68,6 +68,7 @@ impl window::Window for Window { return; } }; + event.extra_data.is_editor_open = true; self.name.clone_from(&event.name); let mut win_open = true;