diff --git a/src/lua_engine.rs b/src/lua_engine.rs index db3720d..1ce7adb 100644 --- a/src/lua_engine.rs +++ b/src/lua_engine.rs @@ -227,6 +227,7 @@ pub fn install_lua_globals( register_command!(cmd_list_logs, "listlogs"); register_command!(mud_log, "log"); register_command!(panel_merge); + register_command!(panel_swap); register_command!(sendmud_raw); register_stateless_command!(storage); register_command!(tick); diff --git a/src/lua_engine/frames.rs b/src/lua_engine/frames.rs index c89832e..60502d6 100644 --- a/src/lua_engine/frames.rs +++ b/src/lua_engine/frames.rs @@ -290,6 +290,32 @@ pub fn panel_merge<'gc>( }) } +pub fn panel_swap<'gc>( + ctx: Context<'gc>, + global_memo: &GlobalMemoCell, + global_layout: &UseStateSetter, +) -> Callback<'gc> { + let global_layout = global_layout.clone(); + let global_memo = global_memo.clone(); + Callback::from_fn(&ctx, move |ctx, _ex, mut stack| { + let path1: String = stack.from_front(ctx)?; + let path2: String = stack.from_front(ctx)?; + let new_splits = global_memo + .layout + .borrow() + .term_splits + .swap(&path1, &path2) + .map_err(|e| e.into_value(ctx))?; + let new_layout = Rc::new(GlobalLayoutState { + term_splits: new_splits.clone(), + ..(*(global_memo.layout.borrow().as_ref())).clone() + }); + global_layout.set(new_layout.clone()); + *(global_memo.layout.borrow_mut()) = new_layout; + Ok(piccolo::CallbackReturn::Return) + }) +} + pub fn try_unwrap_frame<'gc>( ctx: Context<'gc>, value: &Value<'gc>, diff --git a/src/term_split.rs b/src/term_split.rs index 55b2e74..eadb223 100644 --- a/src/term_split.rs +++ b/src/term_split.rs @@ -112,6 +112,31 @@ impl TermSplit { } } + pub fn get_at_pathstr(&self, pathstr: &str) -> Result<&Self, String> { + self.get_at_pathstr_vec(&pathstr.chars().collect::>()) + } + + fn get_at_pathstr_vec(&self, pathstr: &[char]) -> Result<&Self, String> { + match self { + TermSplit::Horizontal { left, right } => match pathstr.split_first() { + None => Ok(self), + Some(('l', path_rest)) => left.get_at_pathstr_vec(path_rest), + Some(('r', path_rest)) => right.get_at_pathstr_vec(path_rest), + Some((c, path_rest)) => Err(format!("In split path, found {} before {}, which was unexpected for a horizontal split", c, path_rest.iter().collect::())) + }, + TermSplit::Vertical { top, bottom } => match pathstr.split_first() { + None => Ok(self), + Some(('t', path_rest)) => top.get_at_pathstr_vec(path_rest), + Some(('b', path_rest)) => bottom.get_at_pathstr_vec(path_rest), + Some((c, path_rest)) => Err(format!("In split path, found {} before {}, which was unexpected for a vertical split", c, path_rest.iter().collect::())) + }, + TermSplit::Term { .. } => match pathstr.split_first() { + None => Ok(self), + Some(_) => Err(format!("In split path, found trailing junk {} after addressing terminal", pathstr.iter().collect::())) + } + } + } + pub fn hsplit(&self, pathstr: &str, new_frame: FrameId) -> Result { let new = self.modify_at_pathstr(pathstr, move |n| { Ok(TermSplit::Horizontal { @@ -143,6 +168,17 @@ impl TermSplit { TermSplit::Vertical { top, .. } => Ok((**top).clone()), }) } + + pub fn swap(&self, pathstr1: &str, pathstr2: &str) -> Result { + let split1 = self.get_at_pathstr(pathstr1)?; + let split2 = self.get_at_pathstr(pathstr2)?; + let new = self + .modify_at_pathstr(pathstr1, |_t| Ok(split2.clone()))? + .modify_at_pathstr(pathstr2, |_t| Ok(split1.clone()))?; + // It can fail if one path nests the other. + new.validate()?; + Ok(new) + } } pub struct AccessibleSplitIter<'t> { @@ -303,4 +339,44 @@ mod tests { ] ); } + + #[test] + fn swapping_works() { + use TermSplit::*; + let t = Vertical { + top: Horizontal { + left: Horizontal { + left: Term { frame: FrameId(42) }.into(), + right: Term { frame: FrameId(64) }.into(), + } + .into(), + right: Term { frame: FrameId(46) }.into(), + } + .into(), + bottom: Vertical { + top: Term { frame: FrameId(43) }.into(), + bottom: Term { frame: FrameId(44) }.into(), + } + .into(), + }; + assert_eq!( + t.swap("tl", "b"), + Ok(Vertical { + top: Horizontal { + left: Vertical { + top: Term { frame: FrameId(43) }.into(), + bottom: Term { frame: FrameId(44) }.into(), + } + .into(), + right: Term { frame: FrameId(46) }.into(), + } + .into(), + bottom: Horizontal { + left: Term { frame: FrameId(42) }.into(), + right: Term { frame: FrameId(64) }.into(), + } + .into(), + }) + ); + } }