Implement control and display of tab splits.

This commit is contained in:
Condorra 2024-12-13 23:15:02 +11:00
parent 0da685d4f5
commit 6dd6cd1f71
4 changed files with 182 additions and 3 deletions

View File

@ -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");

View File

@ -265,6 +265,99 @@ pub fn hsplit<'gc>(
})
}
pub fn tsplit<'gc>(
ctx: Context<'gc>,
global_memo: &GlobalMemoCell,
global_layout: &UseStateSetter<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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<GlobalLayoutCell>,
) -> 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,

View File

@ -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! {
<div class="container-fluid d-flex flex-column">
<div class="d-flex flex-column h-100">
<ul class="nav nav-tabs">
{props.tabs.iter().map(|(c, _)| {
if !props.tabs.is_empty() && !props.tabs.iter().any(|t| t.0 == *active_tab) {
active_tab.set(props.tabs.first().map(|v| v.0).unwrap_or('0'));
}
let class_name = if *c == *active_tab { "nav-link active" } else { "nav-link" };
let c_click: char = *c;
let active_tab_click = active_tab.clone();
@ -26,7 +29,7 @@ pub fn tab_panel_props(props: &TabPanelProps) -> Html {
}
</ul>
{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! {
<div class={class_name} key={format!("content-{}", c)}>
{v.clone()}

View File

@ -236,6 +236,36 @@ impl TermSplit {
new.validate()?;
Ok(new)
}
pub fn close_tab(&self, pathstr: &str) -> Result<TermSplit, String> {
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(),
}
);
}
}