use panes::diff::LayoutDiff;
use panes::runtime::{Frame as PanesFrame, LayoutRuntime};
use panes::{AdapterFrame, Layout, OverlayEntry, PaneError, PanelEntry, PanelId, ResolvedLayout};
use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::widgets::Clear;
pub struct TerminalFrame<'a> {
shell: AdapterFrame<'a>,
}
impl<'a> TerminalFrame<'a> {
pub fn get(&self, id: PanelId) -> Option<Rect> {
self.shell.resolved().get(id).map(quantize)
}
pub fn panels(&self) -> impl Iterator<Item = PanelEntry<'_, Rect>> {
self.shell.resolved().panels().map(|e| e.map_rect(quantize))
}
pub fn overlays(&self) -> impl Iterator<Item = OverlayEntry<'_, Rect>> {
self.shell
.resolved()
.overlays()
.map(|e| e.map_rect(quantize))
}
pub fn focused_panels(
&self,
focused: Option<PanelId>,
) -> impl Iterator<Item = (PanelEntry<'_, Rect>, bool)> {
focused_panels_impl(self.shell.resolved(), focused)
}
pub fn render_overlays(
&self,
frame: &mut Frame,
render: impl FnMut(&mut Frame, OverlayEntry<'_, Rect>),
) {
render_overlays_impl(frame, self.overlays(), render);
}
pub fn diff(&self) -> Option<LayoutDiff<'_>> {
self.shell.diff()
}
pub fn inner(&self) -> Option<&PanesFrame> {
self.shell.inner()
}
pub fn overlay_failures(
&self,
) -> &[(panes::OverlayId, std::sync::Arc<str>, panes::AnchorFailure)] {
self.shell.overlay_failures()
}
}
pub fn resolve<'a>(rt: &'a mut LayoutRuntime, area: Rect) -> Result<TerminalFrame<'a>, PaneError> {
let frame = rt.resolve(f32::from(area.width), f32::from(area.height))?;
Ok(TerminalFrame {
shell: AdapterFrame::from_runtime(frame, rt),
})
}
pub fn resolve_layout(layout: &Layout, area: Rect) -> Result<TerminalFrame<'static>, PaneError> {
let resolved = layout.resolve(f32::from(area.width), f32::from(area.height))?;
Ok(TerminalFrame {
shell: AdapterFrame::from_stateless(resolved),
})
}
panes::impl_adapter! {
rect: Rect,
origin: Rect,
convert_fn: quantize,
convert_at_fn: |r, origin: Rect| offset_rect(quantize(r), origin),
}
pub fn convert_at(resolved: &ResolvedLayout, origin: Rect) -> panes::__FxHashMap<PanelId, Rect> {
resolved
.iter()
.map(|(pid, r)| (pid, offset_rect(quantize(r), origin)))
.collect()
}
pub fn focused_panels<'a>(
resolved: &'a ResolvedLayout,
focused: Option<PanelId>,
) -> impl Iterator<Item = (PanelEntry<'a, Rect>, bool)> {
focused_panels_impl(resolved, focused)
}
pub fn focused_panels_at<'a>(
resolved: &'a ResolvedLayout,
focused: Option<PanelId>,
origin: Rect,
) -> impl Iterator<Item = (PanelEntry<'a, Rect>, bool)> {
focused_panels_impl(resolved, focused)
.map(move |(e, is_focused)| (e.map_rect(|r| offset_rect(r, origin)), is_focused))
}
fn focused_panels_impl<'a>(
resolved: &'a ResolvedLayout,
focused: Option<PanelId>,
) -> impl Iterator<Item = (PanelEntry<'a, Rect>, bool)> {
let focused_kind = focused.and_then(|pid| resolved.kind_of(pid));
let content = resolved.panels().map(move |entry| {
let is_focused = matches!(focused, Some(fid) if entry.id == fid);
(entry.map_rect(quantize), is_focused)
});
let decorations = resolved.decoration_panels().iter().filter_map(move |d| {
let rect = resolved.get(d.id)?;
let is_focused = focused_kind.is_some_and(|fk| fk == d.content_kind.as_ref());
let kind_index = resolved.kind_index_of(d.content_kind.as_ref())?;
Some((
PanelEntry {
id: d.id,
kind: &d.content_kind,
rect: quantize(rect),
kind_index,
},
is_focused,
))
});
content.chain(decorations)
}
pub fn render_overlays(
frame: &mut Frame,
resolved: &ResolvedLayout,
render: impl FnMut(&mut Frame, OverlayEntry<'_, Rect>),
) {
render_overlays_impl(frame, overlays(resolved), render);
}
pub fn render_overlays_at(
frame: &mut Frame,
resolved: &ResolvedLayout,
origin: Rect,
render: impl FnMut(&mut Frame, OverlayEntry<'_, Rect>),
) {
render_overlays_impl(frame, overlays_at(resolved, origin), render);
}
fn render_overlays_impl<'a>(
frame: &mut Frame,
overlays: impl Iterator<Item = OverlayEntry<'a, Rect>>,
mut render: impl FnMut(&mut Frame, OverlayEntry<'a, Rect>),
) {
for entry in overlays {
frame.render_widget(Clear, entry.rect);
render(frame, entry);
}
}
fn offset_rect(r: Rect, origin: Rect) -> Rect {
Rect {
x: r.x + origin.x,
y: r.y + origin.y,
..r
}
}
fn quantize(r: &panes::Rect) -> Rect {
let left = clamp_edge(r.x.round());
let top = clamp_edge(r.y.round());
let right = clamp_edge((r.x + r.w).round());
let bottom = clamp_edge((r.y + r.h).round());
Rect {
x: left,
y: top,
width: right.saturating_sub(left),
height: bottom.saturating_sub(top),
}
}
fn clamp_edge(v: f32) -> u16 {
match v {
v if v <= 0.0 => 0,
v if v >= f32::from(u16::MAX) => u16::MAX,
_ => v as u16,
}
}