use std::sync::Arc;
use crate::builder::{ContainerCtx, Grid, GridCtx, LayoutBuilder};
use crate::compiler::compile;
use crate::error::PaneError;
use crate::preset::{PanelInputKind, PresetInfo};
use crate::resolver::ResolvedLayout;
use crate::tree::LayoutTree;
pub struct Layout {
tree: LayoutTree,
}
impl std::fmt::Debug for Layout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Layout")
.field("panel_count", &self.tree.panel_count())
.finish()
}
}
impl Layout {
pub(crate) fn from_tree(tree: LayoutTree) -> Self {
Self { tree }
}
pub fn tree(&self) -> &LayoutTree {
&self.tree
}
pub fn window_panel_count(&self) -> usize {
self.tree.window_panel_count()
}
pub fn resolve(&self, width: f32, height: f32) -> Result<ResolvedLayout, PaneError> {
self.tree.resolve(width, height)
}
pub fn build_row(f: impl FnOnce(&mut ContainerCtx)) -> Result<Self, PaneError> {
Self::build_axis(true, 0.0, f)
}
pub fn build_col(f: impl FnOnce(&mut ContainerCtx)) -> Result<Self, PaneError> {
Self::build_axis(false, 0.0, f)
}
pub fn build_row_gap(gap: f32, f: impl FnOnce(&mut ContainerCtx)) -> Result<Self, PaneError> {
Self::build_axis(true, gap, f)
}
pub fn build_col_gap(gap: f32, f: impl FnOnce(&mut ContainerCtx)) -> Result<Self, PaneError> {
Self::build_axis(false, gap, f)
}
pub fn build_grid(grid: Grid, f: impl FnOnce(&mut GridCtx)) -> Result<Self, PaneError> {
let mut b = LayoutBuilder::new();
b.grid(grid, f)?;
b.build()
}
pub fn row(kinds: impl IntoIterator<Item = impl Into<Arc<str>>>) -> Result<Self, PaneError> {
Self::equal_grow_axis(true, kinds)
}
pub fn col(kinds: impl IntoIterator<Item = impl Into<Arc<str>>>) -> Result<Self, PaneError> {
Self::equal_grow_axis(false, kinds)
}
pub fn row_with(
panels: impl IntoIterator<Item = (impl Into<Arc<str>>, crate::panel::Constraints)>,
) -> Result<Self, PaneError> {
Self::constrained_axis(true, panels)
}
pub fn col_with(
panels: impl IntoIterator<Item = (impl Into<Arc<str>>, crate::panel::Constraints)>,
) -> Result<Self, PaneError> {
Self::constrained_axis(false, panels)
}
fn build_axis(
is_row: bool,
gap: f32,
f: impl FnOnce(&mut ContainerCtx),
) -> Result<Self, PaneError> {
let mut b = LayoutBuilder::new();
match is_row {
true => b.row_gap(gap, f)?,
false => b.col_gap(gap, f)?,
}
b.build()
}
fn equal_grow_axis(
is_row: bool,
kinds: impl IntoIterator<Item = impl Into<Arc<str>>>,
) -> Result<Self, PaneError> {
let kinds = kinds.into_iter();
Self::build_axis(is_row, 0.0, |ctx| {
for kind in kinds {
ctx.panel(kind.into());
}
})
}
fn constrained_axis(
is_row: bool,
panels: impl IntoIterator<Item = (impl Into<Arc<str>>, crate::panel::Constraints)>,
) -> Result<Self, PaneError> {
let panels = panels.into_iter();
Self::build_axis(is_row, 0.0, |ctx| {
for (kind, constraints) in panels {
ctx.panel_with(kind.into(), constraints);
}
})
}
pub fn presets() -> &'static [PresetInfo] {
&crate::preset::catalog::PRESETS
}
pub fn runtime_from_preset(
name: &str,
kinds: &[&str],
) -> Result<crate::runtime::LayoutRuntime, String> {
let preset = Self::presets()
.iter()
.find(|preset| preset.name == name)
.ok_or_else(|| format!("unknown preset: {name}"))?;
match preset.input {
PanelInputKind::DynamicList => Self::dynamic_runtime_from_preset(preset.name, kinds),
PanelInputKind::FixedSlots => Self::fixed_runtime_from_preset(preset.name, kinds),
}
}
fn dynamic_runtime_from_preset(
preset: &str,
kinds: &[&str],
) -> Result<crate::runtime::LayoutRuntime, String> {
let iter = || kinds.iter().copied();
let runtime = match preset {
"master-stack" => Self::master_stack(iter()).into_runtime(),
"centered-master" => Self::centered_master(iter()).into_runtime(),
"monocle" => Self::monocle(iter()).into_runtime(),
"scrollable" => Self::scrollable(iter()).into_runtime(),
"dwindle" => Self::dwindle(iter()).into_runtime(),
"spiral" => Self::spiral(iter()).into_runtime(),
"deck" => Self::deck(iter()).into_runtime(),
"tabbed" => Self::tabbed(iter()).into_runtime(),
"stacked" => Self::stacked(iter()).into_runtime(),
"dashboard" => Self::dashboard(iter().map(|kind| (kind, 1usize))).into_runtime(),
_ => return Err(format!("unsupported dynamic preset in catalog: {preset}")),
};
runtime.map_err(|error| error.to_string())
}
fn fixed_runtime_from_preset(
preset: &str,
kinds: &[&str],
) -> Result<crate::runtime::LayoutRuntime, String> {
let runtime = match preset {
"sidebar" => {
require_preset_slots(kinds, 2, preset)?;
Self::sidebar(kinds[0], kinds[1]).into_runtime()
}
"holy-grail" => {
require_preset_slots(kinds, 5, preset)?;
Self::holy_grail(kinds[0], kinds[1], kinds[2], kinds[3], kinds[4]).into_runtime()
}
"split" => {
require_preset_slots(kinds, 2, preset)?;
Self::split(kinds[0], kinds[1]).into_runtime()
}
_ => {
return Err(format!(
"unsupported fixed-slot preset in catalog: {preset}"
));
}
};
runtime.map_err(|error| error.to_string())
}
pub fn master_stack(
kinds: impl IntoIterator<Item = impl Into<Arc<str>>>,
) -> crate::preset::MasterStack {
crate::preset::MasterStack::new(kinds)
}
pub fn centered_master(
kinds: impl IntoIterator<Item = impl Into<Arc<str>>>,
) -> crate::preset::CenteredMaster {
crate::preset::CenteredMaster::new(kinds)
}
pub fn monocle(kinds: impl IntoIterator<Item = impl Into<Arc<str>>>) -> crate::preset::Monocle {
crate::preset::Monocle::new(kinds)
}
pub fn scrollable(
kinds: impl IntoIterator<Item = impl Into<Arc<str>>>,
) -> crate::preset::Scrollable {
crate::preset::Scrollable::new(kinds)
}
pub fn dwindle(kinds: impl IntoIterator<Item = impl Into<Arc<str>>>) -> crate::preset::Dwindle {
crate::preset::Dwindle::new(kinds)
}
pub fn spiral(kinds: impl IntoIterator<Item = impl Into<Arc<str>>>) -> crate::preset::Spiral {
crate::preset::Spiral::new(kinds)
}
pub fn deck(kinds: impl IntoIterator<Item = impl Into<Arc<str>>>) -> crate::preset::Deck {
crate::preset::Deck::new(kinds)
}
pub fn tabbed(kinds: impl IntoIterator<Item = impl Into<Arc<str>>>) -> crate::preset::Tabbed {
crate::preset::ActivePanelPreset::new_tabbed(kinds)
}
pub fn stacked(kinds: impl IntoIterator<Item = impl Into<Arc<str>>>) -> crate::preset::Stacked {
crate::preset::ActivePanelPreset::new_stacked(kinds)
}
pub fn sidebar(
sidebar_kind: impl Into<Arc<str>>,
content_kind: impl Into<Arc<str>>,
) -> crate::preset::Sidebar {
crate::preset::Sidebar::new(sidebar_kind, content_kind)
}
pub fn holy_grail(
header: impl Into<Arc<str>>,
footer: impl Into<Arc<str>>,
left: impl Into<Arc<str>>,
main: impl Into<Arc<str>>,
right: impl Into<Arc<str>>,
) -> crate::preset::HolyGrail {
crate::preset::HolyGrail::new(header, footer, left, main, right)
}
pub fn dashboard(
cards: impl IntoIterator<Item = (impl Into<Arc<str>>, impl Into<crate::strategy::CardSpan>)>,
) -> crate::preset::Dashboard {
crate::preset::Dashboard::new(cards)
}
pub fn split(first: impl Into<Arc<str>>, second: impl Into<Arc<str>>) -> crate::preset::Split {
crate::preset::Split::new(first, second)
}
pub fn adaptive(
panels: impl IntoIterator<Item = impl Into<Arc<str>>>,
) -> crate::breakpoint::AdaptiveBuilder {
let panels: Box<[Arc<str>]> = panels.into_iter().map(Into::into).collect();
crate::breakpoint::AdaptiveBuilder::new(panels)
}
}
fn require_preset_slots(kinds: &[&str], expected: usize, preset: &str) -> Result<(), String> {
match kinds.len() == expected {
true => Ok(()),
false => Err(format!(
"preset '{preset}' requires exactly {expected} panels, got {}",
kinds.len()
)),
}
}
impl Layout {
#[cfg(feature = "toml")]
pub fn from_toml(input: &str) -> Result<Self, crate::toml_parse::TomlError> {
crate::toml_parse::parse(input)
}
#[cfg(feature = "toml")]
pub fn from_toml_runtime(
input: &str,
) -> Result<crate::runtime::LayoutRuntime, crate::toml_parse::TomlError> {
crate::toml_parse::parse_runtime(input)
}
#[cfg(feature = "toml")]
pub fn from_toml_file(
path: impl AsRef<std::path::Path>,
) -> Result<Self, crate::toml_parse::TomlError> {
let input = std::fs::read_to_string(path)?;
crate::toml_parse::parse(&input)
}
pub fn compile(&self) -> Result<crate::compiler::CompileResult, PaneError> {
compile(&self.tree)
}
}
impl From<Layout> for LayoutTree {
fn from(layout: Layout) -> Self {
layout.tree
}
}