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