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