Implement generic draggable split panel.
This commit is contained in:
parent
adaabcc6c2
commit
a002ac1c69
@ -13,7 +13,7 @@ piccolo = "0.3.3"
|
||||
unicode-segmentation = "1.11.0"
|
||||
unicode-width = "0.1.13"
|
||||
wasm-bindgen = "0.2.92"
|
||||
web-sys = { version = "0.3.69", features = ["ResizeObserver"] }
|
||||
web-sys = { version = "0.3.69", features = ["ResizeObserver", "DomRect", "CssStyleDeclaration"] }
|
||||
yew = { version = "0.21.0", features = ["csr"] }
|
||||
minicrossterm = { git = "https://git.blastmud.org/blasthavers/minicrossterm.git", rev = "494f89daef41162fbd89d5266e261018ed5ff6dc" }
|
||||
thiserror = "1.0.63"
|
||||
|
@ -7,8 +7,10 @@ pub mod command_handler;
|
||||
pub mod lineengine;
|
||||
pub mod lua_state;
|
||||
pub mod parsing;
|
||||
pub mod split_panel;
|
||||
pub mod term_view;
|
||||
use crate::lua_state::{install_lua_globals, LuaState};
|
||||
use crate::split_panel::*;
|
||||
use crate::term_view::*;
|
||||
|
||||
#[derive(Properties)]
|
||||
@ -37,9 +39,9 @@ fn app() -> Html {
|
||||
install_lua_globals(&global).expect("Couldn't install Lua globals");
|
||||
|
||||
html! {
|
||||
<div class="vpane toplevel">
|
||||
<TermView terminal={TermFrame(0)} global={global.clone()}/>
|
||||
<TermView terminal={TermFrame(1)} global={global.clone()}/>
|
||||
<div class="toplevel">
|
||||
<SplitPanel direction={PanelDirection::Vertical} first={ html! { <TermView terminal={TermFrame(0)} global={global.clone()}/> }}
|
||||
second={ html! { <TermView terminal={TermFrame(1)} global={global.clone()}/> } }/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
101
src/split_panel.rs
Normal file
101
src/split_panel.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Element, PointerEvent};
|
||||
use yew::{function_component, html, use_state_eq, Callback, Html, Properties, UseStateHandle};
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub enum PanelDirection {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct SplitPanelProps {
|
||||
pub direction: PanelDirection,
|
||||
pub first: Html,
|
||||
pub second: Html,
|
||||
}
|
||||
|
||||
#[function_component(SplitPanel)]
|
||||
pub fn split_panel(props: &SplitPanelProps) -> Html {
|
||||
let is_dragging: Rc<UseStateHandle<Option<f64>>> = use_state_eq(|| None).into();
|
||||
let dragged_space: Rc<UseStateHandle<Option<f64>>> = use_state_eq(|| None).into();
|
||||
let wrapper_class = match props.direction {
|
||||
PanelDirection::Horizontal => "hpanelwrapper",
|
||||
PanelDirection::Vertical => "vpanelwrapper",
|
||||
};
|
||||
let wrapper_class = if (**is_dragging).is_some() {
|
||||
format!("{} dragging", wrapper_class)
|
||||
} else {
|
||||
wrapper_class.to_owned()
|
||||
};
|
||||
let panel_class = match props.direction {
|
||||
PanelDirection::Horizontal => "hpanel",
|
||||
PanelDirection::Vertical => "vpanel",
|
||||
};
|
||||
let divider_class = match props.direction {
|
||||
PanelDirection::Horizontal => "hdivider",
|
||||
PanelDirection::Vertical => "vdivider",
|
||||
};
|
||||
let row_or_col = match props.direction {
|
||||
PanelDirection::Horizontal => "columns",
|
||||
PanelDirection::Vertical => "rows",
|
||||
};
|
||||
|
||||
let layout = match **dragged_space {
|
||||
None => format!("grid-template-{}: 1fr 4px 1fr", row_or_col),
|
||||
Some(custom) => format!("grid-template-{}: {}px 4px 1fr", row_or_col, custom),
|
||||
};
|
||||
let is_dragging_forup = is_dragging.clone();
|
||||
let dragged_space_formove = dragged_space.clone();
|
||||
let dir = props.direction.clone();
|
||||
match **is_dragging {
|
||||
None => {
|
||||
html! {
|
||||
<div class={wrapper_class} style={layout}>
|
||||
<div class={panel_class}>{props.first.clone()}</div>
|
||||
<div class={divider_class}
|
||||
onpointerdown={Callback::from(move |e: PointerEvent| {
|
||||
match e.target() {
|
||||
None => {},
|
||||
Some(t) => {
|
||||
let el: Element = JsCast::unchecked_into::<Element>(
|
||||
JsCast::unchecked_into::<Element>(t)
|
||||
.parent_node().expect("Parent exists"));
|
||||
let top =
|
||||
match dir {
|
||||
PanelDirection::Horizontal => el.get_bounding_client_rect().x(),
|
||||
PanelDirection::Vertical => el.get_bounding_client_rect().y()
|
||||
};
|
||||
is_dragging.set(Some(top));
|
||||
}
|
||||
}
|
||||
})}
|
||||
/>
|
||||
<div class={panel_class}>{props.second.clone()}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Some(start) => {
|
||||
html! {
|
||||
<div class={wrapper_class} style={layout}
|
||||
onpointerup={Callback::from(move |_| {
|
||||
is_dragging_forup.set(None);
|
||||
})}
|
||||
onpointermove={Callback::from(move |e: PointerEvent| {
|
||||
let distance = match dir {
|
||||
PanelDirection::Horizontal => e.client_x() as f64,
|
||||
PanelDirection::Vertical => e.client_y() as f64
|
||||
} - start - 2.0;
|
||||
(*dragged_space_formove).set(Some(distance));
|
||||
})}
|
||||
>
|
||||
<div class={panel_class}>{props.first.clone()}</div>
|
||||
<div class={divider_class}
|
||||
/>
|
||||
<div class={panel_class}>{props.second.clone()}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
styles.css
46
styles.css
@ -10,24 +10,52 @@ body {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
}
|
||||
.vpane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
.vpanelwrapper {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 4px 1fr;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.hpane {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
.hpanelwrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 4px 1fr;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dragging.vpanelwrapper {
|
||||
cursor: row-resize;
|
||||
user-select: none;
|
||||
}
|
||||
.dragging.hpanelwrapper {
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
}
|
||||
.vdivider {
|
||||
min-height: 4px;
|
||||
background: grey;
|
||||
cursor: row-resize;
|
||||
}
|
||||
.hdivider {
|
||||
min-height: 4px;
|
||||
background: grey;
|
||||
cursor: col-resize;
|
||||
}
|
||||
.vpanel {
|
||||
min-width: 0px;
|
||||
min-height: 0px;
|
||||
}
|
||||
.hpanel {
|
||||
min-width: 0px;
|
||||
min-height: 0px;
|
||||
}
|
||||
.toplevel {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
}
|
||||
.hterminal {
|
||||
flex: 1 1 0;
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
border: solid grey 1px;
|
||||
padding: 2px;
|
||||
height: 100%;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user