1use panes::diff::LayoutDiff;
7use panes::runtime::{Frame as PanesFrame, LayoutRuntime};
8use panes::{AdapterFrame, Layout, OverlayEntry, PaneError, PanelEntry, PanelId, ResolvedLayout};
9use ratatui::Frame;
10use ratatui::layout::Rect;
11use ratatui::widgets::Clear;
12
13pub struct TerminalFrame<'a> {
22 shell: AdapterFrame<'a>,
23}
24
25impl<'a> TerminalFrame<'a> {
26 pub fn get(&self, id: PanelId) -> Option<Rect> {
28 self.shell.resolved().get(id).map(quantize)
29 }
30
31 pub fn panels(&self) -> impl Iterator<Item = PanelEntry<'_, Rect>> {
33 self.shell.resolved().panels().map(|e| e.map_rect(quantize))
34 }
35
36 pub fn overlays(&self) -> impl Iterator<Item = OverlayEntry<'_, Rect>> {
38 self.shell
39 .resolved()
40 .overlays()
41 .map(|e| e.map_rect(quantize))
42 }
43
44 pub fn focused_panels(
49 &self,
50 focused: Option<PanelId>,
51 ) -> impl Iterator<Item = (PanelEntry<'_, Rect>, bool)> {
52 focused_panels_impl(self.shell.resolved(), focused)
53 }
54
55 pub fn render_overlays(
57 &self,
58 frame: &mut Frame,
59 render: impl FnMut(&mut Frame, OverlayEntry<'_, Rect>),
60 ) {
61 render_overlays_impl(frame, self.overlays(), render);
62 }
63
64 pub fn diff(&self) -> Option<LayoutDiff<'_>> {
66 self.shell.diff()
67 }
68
69 pub fn inner(&self) -> Option<&PanesFrame> {
71 self.shell.inner()
72 }
73
74 pub fn overlay_failures(
76 &self,
77 ) -> &[(panes::OverlayId, std::sync::Arc<str>, panes::AnchorFailure)] {
78 self.shell.overlay_failures()
79 }
80}
81
82pub fn resolve<'a>(rt: &'a mut LayoutRuntime, area: Rect) -> Result<TerminalFrame<'a>, PaneError> {
88 let frame = rt.resolve(f32::from(area.width), f32::from(area.height))?;
89 Ok(TerminalFrame {
90 shell: AdapterFrame::from_runtime(frame, rt),
91 })
92}
93
94pub fn resolve_layout(layout: &Layout, area: Rect) -> Result<TerminalFrame<'static>, PaneError> {
100 let resolved = layout.resolve(f32::from(area.width), f32::from(area.height))?;
101 Ok(TerminalFrame {
102 shell: AdapterFrame::from_stateless(resolved),
103 })
104}
105
106panes::impl_adapter! {
111 rect: Rect,
112 origin: Rect,
113 convert_fn: quantize,
114 convert_at_fn: |r, origin: Rect| offset_rect(quantize(r), origin),
115}
116
117pub fn convert_at(resolved: &ResolvedLayout, origin: Rect) -> panes::__FxHashMap<PanelId, Rect> {
118 resolved
119 .iter()
120 .map(|(pid, r)| (pid, offset_rect(quantize(r), origin)))
121 .collect()
122}
123
124pub fn focused_panels<'a>(
127 resolved: &'a ResolvedLayout,
128 focused: Option<PanelId>,
129) -> impl Iterator<Item = (PanelEntry<'a, Rect>, bool)> {
130 focused_panels_impl(resolved, focused)
131}
132
133pub fn focused_panels_at<'a>(
134 resolved: &'a ResolvedLayout,
135 focused: Option<PanelId>,
136 origin: Rect,
137) -> impl Iterator<Item = (PanelEntry<'a, Rect>, bool)> {
138 focused_panels_impl(resolved, focused)
139 .map(move |(e, is_focused)| (e.map_rect(|r| offset_rect(r, origin)), is_focused))
140}
141
142fn focused_panels_impl<'a>(
143 resolved: &'a ResolvedLayout,
144 focused: Option<PanelId>,
145) -> impl Iterator<Item = (PanelEntry<'a, Rect>, bool)> {
146 let focused_kind = focused.and_then(|pid| resolved.kind_of(pid));
147
148 let content = resolved.panels().map(move |entry| {
149 let is_focused = matches!(focused, Some(fid) if entry.id == fid);
150 (entry.map_rect(quantize), is_focused)
151 });
152
153 let decorations = resolved.decoration_panels().iter().filter_map(move |d| {
154 let rect = resolved.get(d.id)?;
155 let is_focused = focused_kind.is_some_and(|fk| fk == d.content_kind.as_ref());
156 let kind_index = resolved.kind_index_of(d.content_kind.as_ref())?;
157 Some((
158 PanelEntry {
159 id: d.id,
160 kind: &d.content_kind,
161 rect: quantize(rect),
162 kind_index,
163 },
164 is_focused,
165 ))
166 });
167
168 content.chain(decorations)
169}
170
171pub fn render_overlays(
173 frame: &mut Frame,
174 resolved: &ResolvedLayout,
175 render: impl FnMut(&mut Frame, OverlayEntry<'_, Rect>),
176) {
177 render_overlays_impl(frame, overlays(resolved), render);
178}
179
180pub fn render_overlays_at(
181 frame: &mut Frame,
182 resolved: &ResolvedLayout,
183 origin: Rect,
184 render: impl FnMut(&mut Frame, OverlayEntry<'_, Rect>),
185) {
186 render_overlays_impl(frame, overlays_at(resolved, origin), render);
187}
188
189fn render_overlays_impl<'a>(
190 frame: &mut Frame,
191 overlays: impl Iterator<Item = OverlayEntry<'a, Rect>>,
192 mut render: impl FnMut(&mut Frame, OverlayEntry<'a, Rect>),
193) {
194 for entry in overlays {
195 frame.render_widget(Clear, entry.rect);
196 render(frame, entry);
197 }
198}
199
200fn offset_rect(r: Rect, origin: Rect) -> Rect {
201 Rect {
202 x: r.x + origin.x,
203 y: r.y + origin.y,
204 ..r
205 }
206}
207
208fn quantize(r: &panes::Rect) -> Rect {
211 let left = clamp_edge(r.x.round());
212 let top = clamp_edge(r.y.round());
213 let right = clamp_edge((r.x + r.w).round());
214 let bottom = clamp_edge((r.y + r.h).round());
215
216 Rect {
217 x: left,
218 y: top,
219 width: right.saturating_sub(left),
220 height: bottom.saturating_sub(top),
221 }
222}
223
224fn clamp_edge(v: f32) -> u16 {
225 match v {
226 v if v <= 0.0 => 0,
227 v if v >= f32::from(u16::MAX) => u16::MAX,
228 _ => v as u16,
229 }
230}