Skip to content

Commit

Permalink
Implement basic event manipulation operations in the map editor (#40)
Browse files Browse the repository at this point in the history
* `MapView.events` is now a `Slab` instead of a `HashMap`

* 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.

* This arm is unreachable assuming `map.events` is sorted

* Remove redundant type annotation

* Selected event is now highlighted in the hover tooltip

* Apply texture bleeding fix to events as well

* Don't interact with events if event layer isn't selected

* Event editor now opens when double-clicking events

* I was wrong; this line is not safe

* Fix name editing in the event editor

* Events can now also be selected by the yellow square cursor

* Implement event deletion when pressing delete on the map

* Event editor windows now have (mostly) unique IDs

* 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.

* Double-click on map tiles without events to create one

* Enter key can now also be used for event creation/editing

* Fix incorrect serialization length for `OptionVec`

* Drag-and-drop events to move them on the map

* Remove unused variables from map_view.rs

* Event drag-and-drop now preserves the event offset from the cursor

* Apply clippy recommendations

* Apply another clippy recommendation

* Fix misleading comment in src/tabs/map.rs

* Don't move events when the mouse is not hovering over them

* Simplify deserialization implementation for OptionVec

* Remove `pub` from `event_drag_offset`

* Fix crash when adding event on a map with no events

* Prevent event from sticking to cursor after double-clicking

* Selected event border is now yellow instead of magenta

* Show magenta border around events that are being edited
  • Loading branch information
white-axe authored Sep 18, 2023
1 parent 0137571 commit ee16d97
Show file tree
Hide file tree
Showing 12 changed files with 652 additions and 60 deletions.
3 changes: 3 additions & 0 deletions rmxp-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
248 changes: 248 additions & 0 deletions rmxp-types/src/option_vec.rs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

use std::ops::{Index, IndexMut};

use serde::ser::SerializeMap;

#[derive(Debug, Clone, PartialEq, Eq)]
/// A vector that can contain unused indices.
pub struct OptionVec<T> {
vec: Vec<Option<T>>,
num_values: usize,
}

pub struct Iter<'a, T> {
vec_iter: std::iter::Enumerate<std::slice::Iter<'a, Option<T>>>,
}

pub struct IterMut<'a, T> {
vec_iter: std::iter::Enumerate<std::slice::IterMut<'a, Option<T>>>,
}

pub struct Visitor<T>(std::marker::PhantomData<T>);

impl<T> OptionVec<T> {
/// 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<T> Default for OptionVec<T> {
fn default() -> Self {
OptionVec::new()
}
}

impl<T> FromIterator<(usize, T)> for OptionVec<T> {
fn from_iter<I: IntoIterator<Item = (usize, T)>>(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<T> Index<usize> for OptionVec<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
self.get(index).expect("index not found")
}
}

impl<T> IndexMut<usize> for OptionVec<T> {
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<T> {
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<T> {
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<Self::Item> {
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<Self::Item> {
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<T>
where
T: serde::Deserialize<'de>,
{
type Value = OptionVec<T>;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a key-value mapping")
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
std::iter::from_fn(|| map.next_entry().transpose()).collect()
}
}

impl<'de, T> serde::Deserialize<'de> for OptionVec<T>
where
T: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_map(Visitor(std::marker::PhantomData))
}
}

impl<T> serde::Serialize for OptionVec<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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()
}
}
4 changes: 2 additions & 2 deletions rmxp-types/src/rmxp/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License
// along with Luminol. If not, see <http://www.gnu.org/licenses/>.
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")]
Expand All @@ -31,5 +31,5 @@ pub struct Map {
pub encounter_list: Vec<i32>,
pub encounter_step: i32,
pub data: Table3,
pub events: slab::Slab<Event>,
pub events: option_vec::OptionVec<Event>,
}
11 changes: 11 additions & 0 deletions rmxp-types/src/shared/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ pub struct Event {
pub x: i32,
pub y: i32,
pub pages: Vec<EventPage>,

#[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 {
Expand All @@ -36,6 +45,8 @@ impl Event {
x,
y,
pages: vec![EventPage::default()],

extra_data: EventExtraData::default(),
}
}
}
Expand Down
Loading

0 comments on commit ee16d97

Please sign in to comment.