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..76900db5 --- /dev/null +++ b/rmxp-types/src/option_vec.rs @@ -0,0 +1,248 @@ +// 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)] +/// A vector that can contain unused indices. +pub struct OptionVec { + vec: Vec>, + num_values: usize, +} + +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(), + 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() + } + + pub fn get(&self, index: usize) -> Option<&T> { + 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).and_then(|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 = index - self.len() + 1; + self.reserve(additional); + 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); + } + + /// 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.num_values -= 1; + 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(); + let mut num_values = 0; + 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); + num_values += 1; + } + Self { vec, num_values } + } +} + +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(|| map.next_entry().transpose()).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.size()))?; + 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/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 53cbce3e..e0854178 100644 --- a/src/components/map_view.rs +++ b/src/components/map_view.rs @@ -26,14 +26,23 @@ pub struct MapView { pub pan: egui::Vec2, pub inter_tile_pan: egui::Vec2, - pub events: HashMap, + pub events: rmxp_types::OptionVec, 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, + /// 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, @@ -70,12 +79,17 @@ impl MapView { map, selected_layer: SelectedLayer::default(), + selected_event_id: None, cursor_pos: egui::Pos2::ZERO, event_enabled: true, snap_to_grid: false, darken_unselected_layers: true, + hover_tile: None, + + selected_event_is_hovered: false, + scale: 100., }) } @@ -142,12 +156,14 @@ impl MapView { let canvas_pos = canvas_center + self.pan; // We check here after we calculate the scale and whatnot + 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.); + self.hover_tile = Some(pos_tile.to_pos2()); // Handle input if matches!(self.selected_layer, SelectedLayer::Tiles(_)) || dragging_event @@ -184,14 +200,45 @@ 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; + } + self.selected_event_is_hovered = false; + if self.event_enabled { + let mut selected_event = None; + let mut selected_event_rects = None; + 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.)); 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), + egui::vec2(32., 32.) * scale, + ); let box_rect = egui::Rect::from_min_size( map_rect.min + egui::vec2( @@ -205,26 +252,143 @@ impl MapView { sprite.paint(ui.painter(), box_rect); } - ui.painter() - .rect_stroke(box_rect, 5., egui::Stroke::new(1., egui::Color32::WHITE)); - - 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)); + if matches!(self.selected_layer, SelectedLayer::Events) + && ui.input(|i| !i.modifiers.shift) + { + ui.painter().rect_stroke( + box_rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ); + + // If the mouse is not hovering over an event, then we will handle the selected + // tile based on where the map cursor is + 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 + Some(_) + if self.cursor_pos.x == event.x as f32 + && self.cursor_pos.y == event.y as f32 => + { + Some(event) + } + Some(e) + if self.cursor_pos.x == e.x as f32 + && self.cursor_pos.y == e.y as f32 => + { + selected_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 { + selected_event_rects = Some((tile_rect, box_rect)); + } + } + } + + 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::YELLOW), + ), + _ => ui.painter().rect_stroke( + response.rect, + 5., + egui::Stroke::new(1., egui::Color32::WHITE), + ), + } + }); + + if let Some(hover_tile) = self.hover_tile { + 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(_) + 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 { + self.selected_event_is_hovered = true; + selected_event_rects = Some((tile_rect, box_rect)); + } + } + } + } + } - 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); + // 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, + 5., + 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(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( - response.rect, + box_rect, 5., - egui::Stroke::new(1., egui::Color32::WHITE), + egui::Stroke::new(3., egui::Color32::YELLOW), ); - }); + } } } } @@ -250,10 +414,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., diff --git a/src/graphics/event.rs b/src/graphics/event.rs index 03031e4a..b402b9de 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); @@ -96,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 +} 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, ) diff --git a/src/tabs/map.rs b/src/tabs/map.rs index df006dae..c85f36fb 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 + 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, }) } @@ -207,6 +213,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 { @@ -224,11 +277,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,12 +374,20 @@ 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(); 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; + self.event_drag_offset = None; + } + if let SelectedLayer::Tiles(tile_layer) = self.view.selected_layer { if response.dragged_by(egui::PointerButton::Primary) && !ui.input(|i| i.modifiers.command) @@ -342,19 +398,96 @@ 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.dragging_event = false; + self.event_drag_offset = None; + 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 + && self.view.selected_event_is_hovered + && response.drag_started_by(egui::PointerButton::Primary) + { + self.dragging_event = true; + } else if self.dragging_event + && !response.dragged_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() + && 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(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 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) + if let Some(offset) = self.event_drag_offset { + // 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 + && adjusted_hover_tile.y == e.y as f32 + }) { + 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; + } + } + } + } + } + } 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))) { - map.events.remove(id); + self.dragging_event = false; + self.event_drag_offset = None; + 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 f1c3ffa2..cf740f20 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,17 +50,27 @@ 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 { - 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) { + let mut map = state!().data_cache.map(self.map_id); + let event = match map.events.get_mut(self.id) { + Some(e) => e, + None => { + *open = false; + return; + } + }; + event.extra_data.is_editor_open = true; + self.name.clone_from(&event.name); + let mut win_open = true; egui::Window::new(self.name()) @@ -68,7 +78,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 event.name); ui.button("New page").clicked(); ui.button("Copy page").clicked(); @@ -79,7 +89,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 +109,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 => { @@ -282,7 +292,9 @@ impl window::Window for Window { }); }); } + 1 => {} + 2 => { ui.vertical(|ui| { ui.group(|ui| { @@ -307,8 +319,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 {