diff --git a/crates/ely_app/src/shell/splits.rs b/crates/ely_app/src/shell/splits.rs index ca45a6e..5870b1d 100644 --- a/crates/ely_app/src/shell/splits.rs +++ b/crates/ely_app/src/shell/splits.rs @@ -73,18 +73,45 @@ impl ElyShell { .overflow_hidden() .bg(rgb(colors::CANVAS)); - let body = match layout.axis() { - SplitAxis::Vertical => body.flex().flex_col(), - SplitAxis::Horizontal | SplitAxis::Grid => body.flex(), - }; + match layout.axis() { + SplitAxis::Horizontal => body + .flex() + .children( + panes + .into_iter() + .enumerate() + .map(|(index, tab)| self.render_split_pane(index, tab, snapshot, cx)), + ) + .into_any_element(), + SplitAxis::Vertical => body + .flex() + .flex_col() + .children( + panes + .into_iter() + .enumerate() + .map(|(index, tab)| self.render_split_pane(index, tab, snapshot, cx)), + ) + .into_any_element(), + SplitAxis::Grid => self.render_split_grid(body, panes, snapshot, cx).into_any_element(), + } + } - body.children( - panes - .into_iter() - .enumerate() - .map(|(index, tab)| self.render_split_pane(index, tab, snapshot, cx)), - ) - .into_any_element() + fn render_split_grid( + &mut self, + body: gpui::Div, + panes: Vec<&BrowserTab>, + snapshot: &BrowserSnapshot, + cx: &mut Context, + ) -> gpui::Div { + body.flex().flex_col().children(panes.chunks(2).enumerate().map(|(row_index, row)| { + div().flex().flex_1().min_h(px(180.0)).gap_3().children(row.iter().enumerate().map( + |(column_index, tab)| { + let pane_index = row_index * 2 + column_index; + self.render_split_pane(pane_index, tab, snapshot, cx) + }, + )) + })) } fn render_split_pane( diff --git a/crates/ely_browser_core/src/state/commands.rs b/crates/ely_browser_core/src/state/commands.rs index 1ce31b5..f34e311 100644 --- a/crates/ely_browser_core/src/state/commands.rs +++ b/crates/ely_browser_core/src/state/commands.rs @@ -1,6 +1,8 @@ use std::time::SystemTime; -use ely_domain::{ArchivePolicy, CommandIntent, CommandScope, ProfileId, ProfileKind, SpaceId}; +use ely_domain::{ + ArchivePolicy, CommandIntent, CommandScope, ProfileId, ProfileKind, SpaceId, SplitAxis, +}; use crate::{ CoreError, @@ -162,6 +164,15 @@ impl BrowserCore { self.split_active_tab_right()?; Ok(true) } + "split-horizontal" | "split horizontal" => { + Ok(self.set_active_split_axis(SplitAxis::Horizontal)?.is_some()) + } + "split-vertical" | "split vertical" => { + Ok(self.set_active_split_axis(SplitAxis::Vertical)?.is_some()) + } + "split-grid" | "split grid" => { + Ok(self.set_active_split_axis(SplitAxis::Grid)?.is_some()) + } "save-split-view" | "save split view" => Ok(self.save_active_split_view()?.is_some()), "split-tab-group" | "split tab group" | "tab-group-to-split" | "tab group to split" => { Ok(self.split_active_tab_group()?.is_some()) diff --git a/crates/ely_browser_core/src/state/splits.rs b/crates/ely_browser_core/src/state/splits.rs index ddc022b..89e5bc5 100644 --- a/crates/ely_browser_core/src/state/splits.rs +++ b/crates/ely_browser_core/src/state/splits.rs @@ -68,6 +68,20 @@ impl BrowserCore { Ok(Some(split_id)) } + pub fn set_active_split_axis(&mut self, axis: SplitAxis) -> Result, CoreError> { + let Some(split_id) = self.active_tab()?.split_id().cloned() else { + return Ok(None); + }; + let layout = self + .split_layouts + .iter_mut() + .find(|layout| layout.id() == &split_id) + .ok_or_else(|| CoreError::SplitNotFound { id: split_id.clone() })?; + + layout.set_axis(axis); + Ok(Some(split_id)) + } + pub fn split_active_tab_right(&mut self) -> Result { let active_index = self.active_tab_index()?; let active_tab_id = self.tabs[active_index].id().clone(); diff --git a/crates/ely_browser_core/tests/splits.rs b/crates/ely_browser_core/tests/splits.rs index 70ba2e6..5812e25 100644 --- a/crates/ely_browser_core/tests/splits.rs +++ b/crates/ely_browser_core/tests/splits.rs @@ -75,6 +75,77 @@ fn save_split_view_command_preserves_query_without_active_split() -> Result<(), Ok(()) } +#[test] +fn split_axis_commands_update_active_layout() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + core.split_active_tab_right()?; + + core.set_command_query(">split-vertical"); + let vertical_intent = core.submit_command()?; + let vertical_snapshot = core.snapshot()?; + let vertical_layout = vertical_snapshot.split_layouts.first().ok_or("missing split layout")?; + + assert_eq!(vertical_intent, Some(CommandIntent::Command("split-vertical".to_string()))); + assert_eq!(vertical_layout.axis(), &SplitAxis::Vertical); + assert_eq!(vertical_snapshot.command_query, ""); + + core.set_command_query(">split-grid"); + let grid_intent = core.submit_command()?; + let grid_snapshot = core.snapshot()?; + let grid_layout = grid_snapshot.split_layouts.first().ok_or("missing split layout")?; + + assert_eq!(grid_intent, Some(CommandIntent::Command("split-grid".to_string()))); + assert_eq!(grid_layout.axis(), &SplitAxis::Grid); + assert_eq!(grid_snapshot.command_query, ""); + + core.set_command_query(">split-horizontal"); + let horizontal_intent = core.submit_command()?; + let horizontal_snapshot = core.snapshot()?; + let horizontal_layout = + horizontal_snapshot.split_layouts.first().ok_or("missing split layout")?; + + assert_eq!(horizontal_intent, Some(CommandIntent::Command("split-horizontal".to_string()))); + assert_eq!(horizontal_layout.axis(), &SplitAxis::Horizontal); + assert_eq!(horizontal_snapshot.command_query, ""); + Ok(()) +} + +#[test] +fn split_axis_command_preserves_query_without_active_split() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let active_tab_id = core.active_tab()?.id().clone(); + + core.set_command_query(">split-grid"); + let intent = core.submit_command()?; + let snapshot = core.snapshot()?; + + assert_eq!(intent, Some(CommandIntent::Command("split-grid".to_string()))); + assert_eq!(snapshot.active_tab_id, active_tab_id); + assert!(snapshot.split_layouts.is_empty()); + assert_eq!(snapshot.command_query, ">split-grid"); + Ok(()) +} + +#[test] +fn split_grid_command_keeps_four_pane_layout() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + core.split_active_tab_right()?; + core.split_active_tab_right()?; + core.split_active_tab_right()?; + + core.set_command_query(">split-grid"); + let intent = core.submit_command()?; + let snapshot = core.snapshot()?; + let layout = snapshot.split_layouts.first().ok_or("missing split layout")?; + + assert_eq!(intent, Some(CommandIntent::Command("split-grid".to_string()))); + assert_eq!(layout.axis(), &SplitAxis::Grid); + assert_eq!(layout.pane_count(), 4); + assert_eq!(snapshot.tabs.len(), 4); + assert_eq!(snapshot.command_query, ""); + Ok(()) +} + #[test] fn close_active_tab_archives_saved_split_view() -> Result<(), Box> { let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; diff --git a/crates/ely_domain/src/split.rs b/crates/ely_domain/src/split.rs index f2d563c..13e5b6e 100644 --- a/crates/ely_domain/src/split.rs +++ b/crates/ely_domain/src/split.rs @@ -57,6 +57,10 @@ impl SplitLayout { &self.axis } + pub fn set_axis(&mut self, axis: SplitAxis) { + self.axis = axis; + } + #[must_use] pub fn panes(&self) -> &[SplitPane] { &self.panes