generated from emilk/eframe_template
-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement basic event manipulation operations in the map editor (#40)
* `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
Showing
12 changed files
with
652 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.