From 6dd6cd1f718711d6d29831f757aa17a42d5a915a Mon Sep 17 00:00:00 2001 From: Condorra Date: Fri, 13 Dec 2024 23:15:02 +1100 Subject: [PATCH] Implement control and display of tab splits. --- src/lua_engine.rs | 3 ++ src/lua_engine/frames.rs | 93 ++++++++++++++++++++++++++++++++++++++++ src/tab_panel.rs | 9 ++-- src/term_split.rs | 80 ++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 3 deletions(-) diff --git a/src/lua_engine.rs b/src/lua_engine.rs index f0bae60..2fe999e 100644 --- a/src/lua_engine.rs +++ b/src/lua_engine.rs @@ -208,9 +208,11 @@ pub fn install_lua_globals( } register_stateless_command!(mud_trigger, "act"); register_stateless_command!(mud_trigger, "action"); + register_command!(add_tab); register_stateless_command!(alias); register_stateless_command!(unalias); register_command!(close_mud); + register_command!(close_tab); register_command!(command); register_command!(connect_mud); register_stateless_command!(create_match_table); @@ -234,6 +236,7 @@ pub fn install_lua_globals( register_command!(sendmud_raw); register_stateless_command!(storage); register_command!(tick); + register_command!(tsplit); register_stateless_command!(mud_trigger, "untrigger"); register_stateless_command!(mud_untrigger, "unact"); register_stateless_command!(mud_untrigger, "unaction"); diff --git a/src/lua_engine/frames.rs b/src/lua_engine/frames.rs index 60502d6..347a8ed 100644 --- a/src/lua_engine/frames.rs +++ b/src/lua_engine/frames.rs @@ -265,6 +265,99 @@ pub fn hsplit<'gc>( }) } +pub fn tsplit<'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 tabid: String = stack.from_front(ctx)?; + let path: String = stack.from_front(ctx)?; + if tabid.len() != 1 { + Err(anyhow::Error::msg( + "Tab identifier should be a single character".to_owned(), + ))?; + } + let tabid = tabid.chars().next().unwrap(); + let new_splits = global_memo + .layout + .borrow() + .term_splits + .tabbed(tabid, &path) + .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(CallbackReturn::Return) + }) +} + +pub fn add_tab<'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 path: String = stack.from_front(ctx)?; + let tabid: String = stack.from_front(ctx)?; + if tabid.len() != 1 { + Err(anyhow::Error::msg( + "Tab identifier should be a single character".to_owned(), + ))?; + } + let tabid = tabid.chars().next().unwrap(); + let frame: FrameId = FrameId(stack.from_front(ctx)?); + let new_splits = global_memo + .layout + .borrow() + .term_splits + .add_tab(&path, tabid, frame.clone()) + .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(CallbackReturn::Call { + function: Function::Callback(ensure_frame_instance(ctx, &frame)), + then: None, + }) + }) +} + +pub fn close_tab<'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 path: String = stack.from_front(ctx)?; + let new_splits = global_memo + .layout + .borrow() + .term_splits + .close_tab(&path) + .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(CallbackReturn::Return) + }) +} + pub fn panel_merge<'gc>( ctx: Context<'gc>, global_memo: &GlobalMemoCell, diff --git a/src/tab_panel.rs b/src/tab_panel.rs index a85af09..271947c 100644 --- a/src/tab_panel.rs +++ b/src/tab_panel.rs @@ -7,11 +7,14 @@ pub struct TabPanelProps { #[function_component(TabPanel)] pub fn tab_panel_props(props: &TabPanelProps) -> Html { - let active_tab = use_state_eq(|| props.tabs.get(0).map(|(k, _)| *k).unwrap_or('0')); + let active_tab = use_state_eq(|| props.tabs.first().map(|(k, _)| *k).unwrap_or('0')); html! { -
+
{props.tabs.iter().map(|(c, v)| { - let class_name = if *c == *active_tab { "container-fluid visible"} else { "container-fluid invisible"}; + let class_name = if *c == *active_tab { "termtreewrapper"} else { "d-none"}; html! {
{v.clone()} diff --git a/src/term_split.rs b/src/term_split.rs index 7dd5eb5..4545e85 100644 --- a/src/term_split.rs +++ b/src/term_split.rs @@ -236,6 +236,36 @@ impl TermSplit { new.validate()?; Ok(new) } + + pub fn close_tab(&self, pathstr: &str) -> Result { + if pathstr.is_empty() { + return Err("Closing tab is invalid with empty path".to_owned()); + } + let (head, last) = pathstr.split_at(pathstr.len() - 1); + let last: char = last.chars().next().unwrap(); + + let new = self.modify_at_pathstr(head, |t| match t { + TermSplit::Tabs { tabs } => { + let tabs_with_close = tabs.without(&last); + if tabs_with_close.is_empty() { + Err(format!( + "Tried to close tab {} at path {}, but that would leave the tab set empty.", + last, head + )) + } else { + Ok(TermSplit::Tabs { + tabs: tabs_with_close, + }) + } + } + _ => Err(format!( + "Tried to close tab at path {}, which isn't a tab set.", + pathstr + )), + })?; + new.validate()?; + Ok(new) + } } pub struct AccessibleSplitIter<'t> { @@ -540,4 +570,54 @@ mod tests { } ); } + + #[test] + fn can_close_tab() { + use TermSplit::*; + let t = Vertical { + top: Horizontal { + left: Horizontal { + left: Term { frame: FrameId(42) }.into(), + right: Term { frame: FrameId(43) }.into(), + } + .into(), + right: Term { frame: FrameId(44) }.into(), + } + .into(), + bottom: Vertical { + top: Term { frame: FrameId(45) }.into(), + bottom: Tabs { + tabs: vec![ + ('0', Term { frame: FrameId(46) }), + ('1', Term { frame: FrameId(47) }), + ] + .into(), + } + .into(), + } + .into(), + }; + assert_eq!( + t.close_tab("bb1").unwrap(), + Vertical { + top: Horizontal { + left: Horizontal { + left: Term { frame: FrameId(42) }.into(), + right: Term { frame: FrameId(43) }.into(), + } + .into(), + right: Term { frame: FrameId(44) }.into(), + } + .into(), + bottom: Vertical { + top: Term { frame: FrameId(45) }.into(), + bottom: Tabs { + tabs: vec![('0', Term { frame: FrameId(46) }),].into() + } + .into() + } + .into(), + } + ); + } }