1use crate::{FollowerStatesByLeader, Pane};
2use anyhow::{anyhow, Result};
3use client::PeerId;
4use collections::HashMap;
5use gpui::{elements::*, Axis, Border, ViewHandle};
6use project::Collaborator;
7use theme::Theme;
8
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct PaneGroup {
11 root: Member,
12}
13
14impl PaneGroup {
15 pub fn new(pane: ViewHandle<Pane>) -> Self {
16 Self {
17 root: Member::Pane(pane),
18 }
19 }
20
21 pub fn split(
22 &mut self,
23 old_pane: &ViewHandle<Pane>,
24 new_pane: &ViewHandle<Pane>,
25 direction: SplitDirection,
26 ) -> Result<()> {
27 match &mut self.root {
28 Member::Pane(pane) => {
29 if pane == old_pane {
30 self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
31 Ok(())
32 } else {
33 Err(anyhow!("Pane not found"))
34 }
35 }
36 Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
37 }
38 }
39
40 pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
41 match &mut self.root {
42 Member::Pane(_) => Ok(false),
43 Member::Axis(axis) => {
44 if let Some(last_pane) = axis.remove(pane)? {
45 self.root = last_pane;
46 }
47 Ok(true)
48 }
49 }
50 }
51
52 pub(crate) fn render<'a>(
53 &self,
54 theme: &Theme,
55 follower_states: &FollowerStatesByLeader,
56 collaborators: &HashMap<PeerId, Collaborator>,
57 ) -> ElementBox {
58 self.root.render(theme, follower_states, collaborators)
59 }
60
61 pub(crate) fn panes(&self) -> Vec<&ViewHandle<Pane>> {
62 let mut panes = Vec::new();
63 self.root.collect_panes(&mut panes);
64 panes
65 }
66}
67
68#[derive(Clone, Debug, Eq, PartialEq)]
69enum Member {
70 Axis(PaneAxis),
71 Pane(ViewHandle<Pane>),
72}
73
74impl Member {
75 fn new_axis(
76 old_pane: ViewHandle<Pane>,
77 new_pane: ViewHandle<Pane>,
78 direction: SplitDirection,
79 ) -> Self {
80 use Axis::*;
81 use SplitDirection::*;
82
83 let axis = match direction {
84 Up | Down => Vertical,
85 Left | Right => Horizontal,
86 };
87
88 let members = match direction {
89 Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
90 Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
91 };
92
93 Member::Axis(PaneAxis { axis, members })
94 }
95
96 pub fn render(
97 &self,
98 theme: &Theme,
99 follower_states: &FollowerStatesByLeader,
100 collaborators: &HashMap<PeerId, Collaborator>,
101 ) -> ElementBox {
102 match self {
103 Member::Pane(pane) => {
104 let mut border = Border::default();
105 let leader = follower_states
106 .iter()
107 .find_map(|(leader_id, follower_states)| {
108 if follower_states.contains_key(pane) {
109 Some(leader_id)
110 } else {
111 None
112 }
113 })
114 .and_then(|leader_id| collaborators.get(leader_id));
115 if let Some(leader) = leader {
116 let leader_color = theme
117 .editor
118 .replica_selection_style(leader.replica_id)
119 .cursor;
120 border = Border::all(theme.workspace.leader_border_width, leader_color);
121 border
122 .color
123 .fade_out(1. - theme.workspace.leader_border_opacity);
124 border.overlay = true;
125 }
126 ChildView::new(pane).contained().with_border(border).boxed()
127 }
128 Member::Axis(axis) => axis.render(theme, follower_states, collaborators),
129 }
130 }
131
132 fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle<Pane>>) {
133 match self {
134 Member::Axis(axis) => {
135 for member in &axis.members {
136 member.collect_panes(panes);
137 }
138 }
139 Member::Pane(pane) => panes.push(pane),
140 }
141 }
142}
143
144#[derive(Clone, Debug, Eq, PartialEq)]
145struct PaneAxis {
146 axis: Axis,
147 members: Vec<Member>,
148}
149
150impl PaneAxis {
151 fn split(
152 &mut self,
153 old_pane: &ViewHandle<Pane>,
154 new_pane: &ViewHandle<Pane>,
155 direction: SplitDirection,
156 ) -> Result<()> {
157 use SplitDirection::*;
158
159 for (idx, member) in self.members.iter_mut().enumerate() {
160 match member {
161 Member::Axis(axis) => {
162 if axis.split(old_pane, new_pane, direction).is_ok() {
163 return Ok(());
164 }
165 }
166 Member::Pane(pane) => {
167 if pane == old_pane {
168 if direction.matches_axis(self.axis) {
169 match direction {
170 Up | Left => {
171 self.members.insert(idx, Member::Pane(new_pane.clone()));
172 }
173 Down | Right => {
174 self.members.insert(idx + 1, Member::Pane(new_pane.clone()));
175 }
176 }
177 } else {
178 *member =
179 Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
180 }
181 return Ok(());
182 }
183 }
184 }
185 }
186 Err(anyhow!("Pane not found"))
187 }
188
189 fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
190 let mut found_pane = false;
191 let mut remove_member = None;
192 for (idx, member) in self.members.iter_mut().enumerate() {
193 match member {
194 Member::Axis(axis) => {
195 if let Ok(last_pane) = axis.remove(pane_to_remove) {
196 if let Some(last_pane) = last_pane {
197 *member = last_pane;
198 }
199 found_pane = true;
200 break;
201 }
202 }
203 Member::Pane(pane) => {
204 if pane == pane_to_remove {
205 found_pane = true;
206 remove_member = Some(idx);
207 break;
208 }
209 }
210 }
211 }
212
213 if found_pane {
214 if let Some(idx) = remove_member {
215 self.members.remove(idx);
216 }
217
218 if self.members.len() == 1 {
219 Ok(self.members.pop())
220 } else {
221 Ok(None)
222 }
223 } else {
224 Err(anyhow!("Pane not found"))
225 }
226 }
227
228 fn render(
229 &self,
230 theme: &Theme,
231 follower_state: &FollowerStatesByLeader,
232 collaborators: &HashMap<PeerId, Collaborator>,
233 ) -> ElementBox {
234 let last_member_ix = self.members.len() - 1;
235 Flex::new(self.axis)
236 .with_children(self.members.iter().enumerate().map(|(ix, member)| {
237 let mut member = member.render(theme, follower_state, collaborators);
238 if ix < last_member_ix {
239 let mut border = theme.workspace.pane_divider;
240 border.left = false;
241 border.right = false;
242 border.top = false;
243 border.bottom = false;
244 match self.axis {
245 Axis::Vertical => border.bottom = true,
246 Axis::Horizontal => border.right = true,
247 }
248 member = Container::new(member).with_border(border).boxed();
249 }
250
251 Flexible::new(1.0, true, member).boxed()
252 }))
253 .boxed()
254 }
255}
256
257#[derive(Clone, Copy, Debug)]
258pub enum SplitDirection {
259 Up,
260 Down,
261 Left,
262 Right,
263}
264
265impl SplitDirection {
266 fn matches_axis(self, orientation: Axis) -> bool {
267 use Axis::*;
268 use SplitDirection::*;
269
270 match self {
271 Up | Down => match orientation {
272 Vertical => true,
273 Horizontal => false,
274 },
275 Left | Right => match orientation {
276 Vertical => false,
277 Horizontal => true,
278 },
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 // use super::*;
286 // use serde_json::json;
287
288 // #[test]
289 // fn test_split_and_remove() -> Result<()> {
290 // let mut group = PaneGroup::new(1);
291 // assert_eq!(
292 // serde_json::to_value(&group)?,
293 // json!({
294 // "type": "pane",
295 // "paneId": 1,
296 // })
297 // );
298
299 // group.split(1, 2, SplitDirection::Right)?;
300 // assert_eq!(
301 // serde_json::to_value(&group)?,
302 // json!({
303 // "type": "axis",
304 // "orientation": "horizontal",
305 // "members": [
306 // {"type": "pane", "paneId": 1},
307 // {"type": "pane", "paneId": 2},
308 // ]
309 // })
310 // );
311
312 // group.split(2, 3, SplitDirection::Up)?;
313 // assert_eq!(
314 // serde_json::to_value(&group)?,
315 // json!({
316 // "type": "axis",
317 // "orientation": "horizontal",
318 // "members": [
319 // {"type": "pane", "paneId": 1},
320 // {
321 // "type": "axis",
322 // "orientation": "vertical",
323 // "members": [
324 // {"type": "pane", "paneId": 3},
325 // {"type": "pane", "paneId": 2},
326 // ]
327 // },
328 // ]
329 // })
330 // );
331
332 // group.split(1, 4, SplitDirection::Right)?;
333 // assert_eq!(
334 // serde_json::to_value(&group)?,
335 // json!({
336 // "type": "axis",
337 // "orientation": "horizontal",
338 // "members": [
339 // {"type": "pane", "paneId": 1},
340 // {"type": "pane", "paneId": 4},
341 // {
342 // "type": "axis",
343 // "orientation": "vertical",
344 // "members": [
345 // {"type": "pane", "paneId": 3},
346 // {"type": "pane", "paneId": 2},
347 // ]
348 // },
349 // ]
350 // })
351 // );
352
353 // group.split(2, 5, SplitDirection::Up)?;
354 // assert_eq!(
355 // serde_json::to_value(&group)?,
356 // json!({
357 // "type": "axis",
358 // "orientation": "horizontal",
359 // "members": [
360 // {"type": "pane", "paneId": 1},
361 // {"type": "pane", "paneId": 4},
362 // {
363 // "type": "axis",
364 // "orientation": "vertical",
365 // "members": [
366 // {"type": "pane", "paneId": 3},
367 // {"type": "pane", "paneId": 5},
368 // {"type": "pane", "paneId": 2},
369 // ]
370 // },
371 // ]
372 // })
373 // );
374
375 // assert_eq!(true, group.remove(5)?);
376 // assert_eq!(
377 // serde_json::to_value(&group)?,
378 // json!({
379 // "type": "axis",
380 // "orientation": "horizontal",
381 // "members": [
382 // {"type": "pane", "paneId": 1},
383 // {"type": "pane", "paneId": 4},
384 // {
385 // "type": "axis",
386 // "orientation": "vertical",
387 // "members": [
388 // {"type": "pane", "paneId": 3},
389 // {"type": "pane", "paneId": 2},
390 // ]
391 // },
392 // ]
393 // })
394 // );
395
396 // assert_eq!(true, group.remove(4)?);
397 // assert_eq!(
398 // serde_json::to_value(&group)?,
399 // json!({
400 // "type": "axis",
401 // "orientation": "horizontal",
402 // "members": [
403 // {"type": "pane", "paneId": 1},
404 // {
405 // "type": "axis",
406 // "orientation": "vertical",
407 // "members": [
408 // {"type": "pane", "paneId": 3},
409 // {"type": "pane", "paneId": 2},
410 // ]
411 // },
412 // ]
413 // })
414 // );
415
416 // assert_eq!(true, group.remove(3)?);
417 // assert_eq!(
418 // serde_json::to_value(&group)?,
419 // json!({
420 // "type": "axis",
421 // "orientation": "horizontal",
422 // "members": [
423 // {"type": "pane", "paneId": 1},
424 // {"type": "pane", "paneId": 2},
425 // ]
426 // })
427 // );
428
429 // assert_eq!(true, group.remove(2)?);
430 // assert_eq!(
431 // serde_json::to_value(&group)?,
432 // json!({
433 // "type": "pane",
434 // "paneId": 1,
435 // })
436 // );
437
438 // assert_eq!(false, group.remove(1)?);
439 // assert_eq!(
440 // serde_json::to_value(&group)?,
441 // json!({
442 // "type": "pane",
443 // "paneId": 1,
444 // })
445 // );
446
447 // Ok(())
448 // }
449}