ui/components/pane/
render.rs1use gpui::{Empty, canvas};
5
6use super::{Pane, PaneEvent};
7use crate::{
8 IconButton, IconButtonShape, IconName, IconSize, Tab, TabBar, TabCloseSide, TabPosition,
9 prelude::*,
10};
11
12fn debug_wrap(id: impl Into<ElementId>, selector: String, child: IconButton) -> impl IntoElement {
18 div().id(id).debug_selector(move || selector).child(child)
19}
20
21#[derive(Clone, Copy)]
23struct TabDragPayload {
24 from_idx: usize,
25}
26
27impl Render for Pane {
28 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29 let active_idx = self.active_idx;
30
31 let active_id = self.tabs.get(active_idx).map(|(id, _)| *id);
37 if let (Some(bounds), Some(id)) = (self.content_bounds.get(), active_id) {
38 if self.notified_resize.get() != Some((id, bounds)) {
39 self.notified_resize.set(Some((id, bounds)));
40 self.tabs[active_idx].1.on_resize(bounds, cx);
41 }
42 }
43
44 let mut tab_bar = TabBar::new("pane-tab-bar");
45
46 let tab_count = self.tabs.len();
47 for ix in 0..tab_count {
48 let tab_id = self.tabs[ix].0;
49 let title = self.tabs[ix].1.title();
50 let selected = ix == active_idx && self.focused;
53 let position = if ix == 0 {
54 TabPosition::First
55 } else if ix == tab_count - 1 {
56 TabPosition::Last
57 } else {
58 TabPosition::Middle(ix.cmp(&active_idx))
59 };
60
61 let hover_group = SharedString::from(format!("pane-tab-{}", tab_id.0));
65 let close_ib = IconButton::new(("pane-tab-close", tab_id.0), IconName::Close)
68 .icon_size(IconSize::Small)
69 .icon_color(Color::Muted)
70 .shape(IconButtonShape::Square)
71 .size(ButtonSize::None)
72 .on_click(cx.listener(move |this, _, _, cx| {
73 this.close_tab(ix, cx);
74 }))
75 .visible_on_hover(hover_group.clone());
76 let close_button = debug_wrap(
77 ("pane-tab-close-wrap", tab_id.0),
78 format!("PANE-TAB-CLOSE-{}", tab_id.0),
79 close_ib,
80 );
81
82 let tab = Tab::new(("pane-tab", tab_id.0))
83 .group(hover_group)
84 .toggle_state(selected)
85 .position(position)
86 .close_side(TabCloseSide::End)
87 .end_slot(close_button)
88 .child(Label::new(title).size(LabelSize::Small))
93 .on_click(cx.listener(move |this, _, _, cx| {
94 this.activate(ix, cx);
95 }))
96 .on_drag(TabDragPayload { from_idx: ix }, |_, _, _, cx| {
97 cx.new(|_| Empty)
98 })
99 .drag_over::<TabDragPayload>(|style, _, _, _| style.opacity(0.5))
100 .on_drop(cx.listener(move |this, payload: &TabDragPayload, _, cx| {
101 this.reorder(payload.from_idx, ix, cx);
102 }));
103
104 tab_bar = tab_bar.child(tab);
105 }
106
107 tab_bar = tab_bar.end_child(debug_wrap(
108 "pane-add-tab-wrap",
109 "PANE-ADD-TAB".to_string(),
110 IconButton::new("pane-add-tab", IconName::Plus)
111 .icon_size(IconSize::XSmall)
112 .on_click(cx.listener(|this, _, _, cx| {
113 let content = (this.new_tab_factory)();
114 this.add_tab(content, cx);
115 })),
116 ));
117
118 tab_bar = tab_bar.end_child(debug_wrap(
121 "pane-close-wrap",
122 "PANE-CLOSE".to_string(),
123 IconButton::new("pane-close", IconName::Close)
124 .icon_size(IconSize::XSmall)
125 .on_click(cx.listener(|_, _, _, cx| {
126 cx.emit(PaneEvent::CloseRequested);
127 })),
128 ));
129
130 let content = self
131 .tabs
132 .get(active_idx)
133 .map(|(_, content)| content.render(true, window, cx));
134
135 let measure = self.content_bounds.clone();
139
140 v_flex().id("pane").size_full().child(tab_bar).child(
141 div()
142 .relative()
143 .flex_1()
144 .min_h_0()
145 .overflow_hidden()
146 .child(
147 canvas(
148 move |bounds, _, _| measure.set(Some(bounds)),
149 |_, _, _, _| {},
150 )
151 .absolute()
152 .size_full(),
153 )
154 .children(content),
155 )
156 }
157}