facett-app/src/splitpane.rs
//! **`SplitPane` — host two Facets with a draggable divider.** A composition
//! container (the Fas 2 layout extra): mount two components side-by-side (or
//! stacked) with a resizable split, so a route can show a master+detail / compare
//! view. Itself a [`Facet`]; `state_json` folds in both children.
use facett_core::{Facet, FacetCaps};
/// A resizable two-pane split hosting two [`Facet`]s.
pub struct SplitPane {
title: String,
first: Box<dyn Facet>,
second: Box<dyn Facet>,
/// `true` = top/bottom, `false` = left/right (default).
vertical: bool,
/// Initial fraction of the first pane (0..1).
ratio: f32,
}
impl SplitPane {
pub fn new(title: impl Into<String>, first: Box<dyn Facet>, second: Box<dyn Facet>) -> Self {
Self { title: title.into(), first, second, vertical: false, ratio: 0.5 }
}
/// Stack the panes vertically (top/bottom) instead of side-by-side.
pub fn vertical(mut self, on: bool) -> Self {
self.vertical = on;
self
}
/// Initial fraction of the first pane (clamped to `0.1..=0.9`).
pub fn ratio(mut self, ratio: f32) -> Self {
self.ratio = ratio.clamp(0.1, 0.9);
self
}
}
impl Facet for SplitPane {
fn title(&self) -> &str {
&self.title
}
fn ui(&mut self, ui: &mut egui::Ui) {
let id = ui.id().with("splitpane");
let (first, second) = (&mut self.first, &mut self.second);
if self.vertical {
let h = ui.available_height() * self.ratio;
egui::Panel::top(id).resizable(true).default_size(h).show(ui, |ui| first.ui(ui));
second.ui(ui);
} else {
let w = ui.available_width() * self.ratio;
egui::Panel::left(id).resizable(true).default_size(w).show(ui, |ui| first.ui(ui));
second.ui(ui);
}
}
fn state_json(&self) -> serde_json::Value {
serde_json::json!({
"title": self.title,
"vertical": self.vertical,
"ratio": self.ratio,
"first": { "title": self.first.title(), "state": self.first.state_json() },
"second": { "title": self.second.title(), "state": self.second.state_json() },
})
}
fn caps(&self) -> FacetCaps {
FacetCaps::NONE.themeable().resizable()
}
}