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