diff --git a/reffect/src/action/element.rs b/reffect/src/action/element.rs index ef77ceb..8bc9add 100644 --- a/reffect/src/action/element.rs +++ b/reffect/src/action/element.rs @@ -35,12 +35,12 @@ impl ElementAction { Self::Cut => { let child = children.remove(index); log::debug!("Cut child {index} {}", child.kind.as_ref()); - edit.set_clipboard(child); + edit.clipboard.set(child); } Self::Copy => { let child = children[index].clone(); log::debug!("Copy child {index} {}", child.kind.as_ref()); - edit.set_clipboard(child); + edit.clipboard.set(child); } Self::Duplicate => { let child = children[index].clone(); diff --git a/reffect/src/context/clipboard.rs b/reffect/src/context/clipboard.rs new file mode 100644 index 0000000..1d7fc85 --- /dev/null +++ b/reffect/src/context/clipboard.rs @@ -0,0 +1,70 @@ +use crate::elements::{Element, ElementType}; +use nexus::imgui::Ui; +use std::{cell::UnsafeCell, fmt}; + +/// Clipboard state. +#[repr(transparent)] +pub struct Clipboard { + contents: UnsafeCell>, // never give out references to this! +} + +impl Clipboard { + unsafe fn get(&self) -> &Option { + self.contents.get().as_ref().unwrap_unchecked() + } + + #[allow(clippy::mut_from_ref)] + unsafe fn get_mut(&self) -> &mut Option { + self.contents.get().as_mut().unwrap_unchecked() + } + + pub fn has_some(&self) -> bool { + unsafe { self.get() }.is_some() + } + + pub fn take(&self) -> Option { + unsafe { self.get_mut() }.take() + } + + pub fn set(&self, element: T) { + *unsafe { self.get_mut() } = Some(element); + } +} + +impl Clipboard { + pub fn has_icon(&self) -> bool { + matches!( + unsafe { self.get() }, + Some(Element { + kind: ElementType::Icon(_), + .. + }), + ) + } + + pub fn debug(&self, ui: &Ui) { + match unsafe { self.get() } { + Some(element) => ui.text(&element.kind), + None => ui.text_disabled("empty"), + } + } +} + +impl Default for Clipboard { + fn default() -> Self { + Self { + contents: UnsafeCell::new(None), + } + } +} + +impl fmt::Debug for Clipboard +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Clipboard") + .field("element", unsafe { self.get() }) + .finish() + } +} diff --git a/reffect/src/context/edit_state.rs b/reffect/src/context/edit_state.rs index 4ed987e..e9813b6 100644 --- a/reffect/src/context/edit_state.rs +++ b/reffect/src/context/edit_state.rs @@ -1,11 +1,8 @@ -use super::UiContext; -use crate::{ - elements::{Element, ElementType}, - id::Id, -}; +use super::{Clipboard, UiContext}; +use crate::{elements::Element, id::Id}; use nexus::imgui::Ui; -use std::{cell::UnsafeCell, fmt}; +#[derive(Debug)] pub struct EditState { /// Whether edit mode is allowed in combat. pub during_combat: bool, @@ -23,8 +20,8 @@ pub struct EditState { // TODO: keep parents sorted? parents: Vec, - /// Current clipboard contents. - clipboard: UnsafeCell>, // we never give out references to this! + /// Clipboard state. + pub clipboard: Clipboard, } impl EditState { @@ -93,39 +90,6 @@ impl EditState { self.allowed = false; } - // do not expose these references! - unsafe fn get_clipboard(&self) -> &Option { - self.clipboard.get().as_ref().unwrap_unchecked() - } - - // do not expose these references! - #[allow(clippy::mut_from_ref)] - unsafe fn get_clipboard_mut(&self) -> &mut Option { - self.clipboard.get().as_mut().unwrap_unchecked() - } - - pub fn has_clipboard(&self) -> bool { - unsafe { self.get_clipboard() }.is_some() - } - - pub fn has_icon_clipboard(&self) -> bool { - matches!( - unsafe { self.get_clipboard() }, - Some(Element { - kind: ElementType::Icon(_), - .. - }), - ) - } - - pub fn take_clipboard(&self) -> Option { - unsafe { self.get_clipboard_mut() }.take() - } - - pub fn set_clipboard(&self, element: Element) { - *unsafe { self.get_clipboard_mut() } = Some(element); - } - pub fn debug(&self, ui: &Ui) { ui.text("Edit allowed:"); ui.same_line(); @@ -133,10 +97,7 @@ impl EditState { ui.text("Clipboard:"); ui.same_line(); - match unsafe { self.get_clipboard() } { - Some(element) => ui.text(&element.kind), - None => ui.text_disabled("empty"), - } + self.clipboard.debug(ui); ui.text("Selected element:"); ui.same_line(); @@ -158,20 +119,7 @@ impl Default for EditState { allowed: true, selected: Id::default(), parents: Vec::new(), - clipboard: UnsafeCell::new(None), + clipboard: Clipboard::default(), } } } - -impl fmt::Debug for EditState { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("EditState") - .field("during_combat", &self.during_combat) - .field("show_all", &self.show_all) - .field("allowed", &self.allowed) - .field("selected", &self.selected) - .field("parents", &self.parents) - .field("clipboard", unsafe { self.get_clipboard() }) - .finish() - } -} diff --git a/reffect/src/context/mod.rs b/reffect/src/context/mod.rs index e37186c..e14394e 100644 --- a/reffect/src/context/mod.rs +++ b/reffect/src/context/mod.rs @@ -1,10 +1,11 @@ +mod clipboard; mod edit_state; mod links; mod map; mod player; mod ui; -pub use self::{edit_state::*, links::*, map::*, player::*, ui::*}; +pub use self::{clipboard::*, edit_state::*, links::*, map::*, player::*, ui::*}; use crate::{ internal::{BuffMap, Interface, Internal, Resources, State}, diff --git a/reffect/src/elements/common.rs b/reffect/src/elements/common.rs index 7319a4f..12993e5 100644 --- a/reffect/src/elements/common.rs +++ b/reffect/src/elements/common.rs @@ -198,10 +198,10 @@ impl Common { }) }); if MenuItem::new("Paste") - .enabled(state.has_clipboard()) + .enabled(state.clipboard.has_some()) .build(ui) { - children.push(state.take_clipboard().expect("paste without clipboard")) + children.push(state.clipboard.take().expect("paste without clipboard")) } } } diff --git a/reffect/src/elements/list/action.rs b/reffect/src/elements/list/action.rs index 8cc97cd..acf1e43 100644 --- a/reffect/src/elements/list/action.rs +++ b/reffect/src/elements/list/action.rs @@ -44,7 +44,8 @@ impl IconAction { children.remove(index); } Self::Cut(index) => { - edit.set_clipboard(children.remove(index).into_element(size)); + edit.clipboard + .set(children.remove(index).into_element(size)); } Self::Paste(index) => { if let Some(Element { @@ -52,7 +53,7 @@ impl IconAction { filter, kind: ElementType::Icon(element), .. - }) = edit.take_clipboard() + }) = edit.clipboard.take() { children.insert(index, ListIcon::from_element(common, element, filter)); } else { diff --git a/reffect/src/elements/list/mod.rs b/reffect/src/elements/list/mod.rs index 4c7fff8..0607412 100644 --- a/reffect/src/elements/list/mod.rs +++ b/reffect/src/elements/list/mod.rs @@ -112,7 +112,7 @@ impl RenderOptions for IconList { item_context_menu("##listiconctx", || { if MenuItem::new("Paste") - .enabled(ctx.edit.has_icon_clipboard()) + .enabled(ctx.edit.clipboard.has_icon()) .build(ui) { action = IconAction::Paste(i) @@ -121,7 +121,7 @@ impl RenderOptions for IconList { action = IconAction::Cut(i); } if MenuItem::new("Copy").build(ui) { - ctx.edit.set_clipboard(icon.clone().into_element(self.size)) + ctx.edit.clipboard.set(icon.clone().into_element(self.size)) } if MenuItem::new("Duplicate").build(ui) { action = IconAction::Duplicate(i); @@ -172,7 +172,7 @@ impl RenderOptions for IconList { } item_context_menu("##addiconctx", || { if MenuItem::new("Paste") - .enabled(ctx.edit.has_icon_clipboard()) + .enabled(ctx.edit.clipboard.has_icon()) .build(ui) { action = IconAction::Paste(self.icons.len());