mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
feat(tui): allow half-width right rail
This commit is contained in:
@@ -28,9 +28,12 @@ const SIDEBAR_MIN_WIDTH: u16 = 24;
|
||||
const SIDEBAR_MAX_WIDTH: u16 = 48;
|
||||
const SUMMARY_DEFAULT_WIDTH: u16 = 34;
|
||||
const SUMMARY_MIN_WIDTH: u16 = 30;
|
||||
const SUMMARY_MAX_WIDTH: u16 = 48;
|
||||
const PANEL_SCROLL_ROWS: u16 = 3;
|
||||
|
||||
fn right_rail_max_width(frame_width: u16) -> u16 {
|
||||
frame_width / 2
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(super) enum OwnedScreenPanelPresentation {
|
||||
Docked,
|
||||
@@ -504,10 +507,12 @@ impl OwnedScreenFrameState {
|
||||
OwnedScreenPanel::Sidebar => column
|
||||
.saturating_sub(area.x)
|
||||
.clamp(SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH),
|
||||
OwnedScreenPanel::Summary => area
|
||||
.right()
|
||||
.saturating_sub(column.saturating_add(PANEL_DIVIDER_WIDTH))
|
||||
.clamp(SUMMARY_MIN_WIDTH, SUMMARY_MAX_WIDTH),
|
||||
OwnedScreenPanel::Summary => {
|
||||
let max_width = right_rail_max_width(area.width);
|
||||
area.right()
|
||||
.saturating_sub(column.saturating_add(PANEL_DIVIDER_WIDTH))
|
||||
.clamp(SUMMARY_MIN_WIDTH.min(max_width), max_width)
|
||||
}
|
||||
};
|
||||
self.panel_state_mut(panel).width = width;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,12 @@ impl OwnedScreenFrameState {
|
||||
}
|
||||
let hard_center_width = OwnedScreenLayout::minimum_width(has_side);
|
||||
let preferred_center_width = PREFERRED_CENTER_WIDTH.max(hard_center_width);
|
||||
let sidebar_width = self.sidebar.width;
|
||||
let summary_width = self.summary.width.min(right_rail_max_width(area.width));
|
||||
let panel_width = |panel| match panel {
|
||||
OwnedScreenPanel::Sidebar => sidebar_width,
|
||||
OwnedScreenPanel::Summary => summary_width,
|
||||
};
|
||||
let mut remaining_width = area.width;
|
||||
let mut sidebar_docked = false;
|
||||
let mut summary_docked = false;
|
||||
@@ -48,31 +54,31 @@ impl OwnedScreenFrameState {
|
||||
if self.preference(panel) != OwnedScreenPanelPreference::Shown {
|
||||
continue;
|
||||
}
|
||||
let panel_width = self.panel_state(panel).width + PANEL_DIVIDER_WIDTH;
|
||||
if remaining_width >= hard_center_width.saturating_add(panel_width) {
|
||||
let required_width = panel_width(panel) + PANEL_DIVIDER_WIDTH;
|
||||
if remaining_width >= hard_center_width.saturating_add(required_width) {
|
||||
match panel {
|
||||
OwnedScreenPanel::Sidebar => sidebar_docked = true,
|
||||
OwnedScreenPanel::Summary => summary_docked = true,
|
||||
}
|
||||
remaining_width = remaining_width.saturating_sub(panel_width);
|
||||
remaining_width = remaining_width.saturating_sub(required_width);
|
||||
}
|
||||
}
|
||||
for panel in [OwnedScreenPanel::Sidebar, OwnedScreenPanel::Summary] {
|
||||
if self.preference(panel) != OwnedScreenPanelPreference::Auto {
|
||||
continue;
|
||||
}
|
||||
let panel_width = self.panel_state(panel).width + PANEL_DIVIDER_WIDTH;
|
||||
if remaining_width >= preferred_center_width.saturating_add(panel_width) {
|
||||
let required_width = panel_width(panel) + PANEL_DIVIDER_WIDTH;
|
||||
if remaining_width >= preferred_center_width.saturating_add(required_width) {
|
||||
match panel {
|
||||
OwnedScreenPanel::Sidebar => sidebar_docked = true,
|
||||
OwnedScreenPanel::Summary => summary_docked = true,
|
||||
}
|
||||
remaining_width = remaining_width.saturating_sub(panel_width);
|
||||
remaining_width = remaining_width.saturating_sub(required_width);
|
||||
}
|
||||
}
|
||||
let mut center = area;
|
||||
let (sidebar, sidebar_divider) = if sidebar_docked {
|
||||
let panel_area = Rect::new(center.x, center.y, self.sidebar.width, center.height);
|
||||
let panel_area = Rect::new(center.x, center.y, sidebar_width, center.height);
|
||||
let divider = Rect::new(
|
||||
panel_area.right(),
|
||||
center.y,
|
||||
@@ -82,7 +88,7 @@ impl OwnedScreenFrameState {
|
||||
center.x = divider.right();
|
||||
center.width = center
|
||||
.width
|
||||
.saturating_sub(self.sidebar.width + PANEL_DIVIDER_WIDTH);
|
||||
.saturating_sub(sidebar_width + PANEL_DIVIDER_WIDTH);
|
||||
(
|
||||
Some(OwnedScreenPanelLayout {
|
||||
area: panel_area,
|
||||
@@ -98,16 +104,15 @@ impl OwnedScreenFrameState {
|
||||
let divider = Rect::new(
|
||||
center
|
||||
.right()
|
||||
.saturating_sub(self.summary.width + PANEL_DIVIDER_WIDTH),
|
||||
.saturating_sub(summary_width + PANEL_DIVIDER_WIDTH),
|
||||
center.y,
|
||||
PANEL_DIVIDER_WIDTH,
|
||||
center.height,
|
||||
);
|
||||
let panel_area =
|
||||
Rect::new(divider.right(), center.y, self.summary.width, center.height);
|
||||
let panel_area = Rect::new(divider.right(), center.y, summary_width, center.height);
|
||||
center.width = center
|
||||
.width
|
||||
.saturating_sub(self.summary.width + PANEL_DIVIDER_WIDTH);
|
||||
.saturating_sub(summary_width + PANEL_DIVIDER_WIDTH);
|
||||
(
|
||||
Some(OwnedScreenPanelLayout {
|
||||
area: panel_area,
|
||||
|
||||
@@ -227,6 +227,41 @@ fn divider_drag_resizes_only_the_target_panel() {
|
||||
assert_eq!(state.focus(), OwnedScreenFrameFocus::Conversation);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_rail_resize_is_capped_at_half_the_frame_width() {
|
||||
let mut state = OwnedScreenFrameState::default();
|
||||
let frame_area = area(/*width*/ 144);
|
||||
let initial = state.layout(frame_area, /*has_side*/ false);
|
||||
let divider = initial.summary_divider.expect("summary divider");
|
||||
assert!(state.handle_mouse_primary(MousePrimaryEvent {
|
||||
kind: MousePrimaryEventKind::Press,
|
||||
column: divider.x,
|
||||
row: divider.y,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
assert!(state.handle_mouse_primary(MousePrimaryEvent {
|
||||
kind: MousePrimaryEventKind::Drag,
|
||||
column: frame_area.x,
|
||||
row: divider.y,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
assert!(state.handle_mouse_primary(MousePrimaryEvent {
|
||||
kind: MousePrimaryEventKind::Release,
|
||||
column: frame_area.x,
|
||||
row: divider.y,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}));
|
||||
|
||||
let resized = state.layout(frame_area, /*has_side*/ false);
|
||||
assert_eq!(resized.summary.expect("summary rail").area.width, 72);
|
||||
|
||||
let narrower = state.layout(area(/*width*/ 120), /*has_side*/ false);
|
||||
assert_eq!(narrower.summary.expect("summary rail").area.width, 60);
|
||||
|
||||
let restored = state.layout(frame_area, /*has_side*/ false);
|
||||
assert_eq!(restored.summary.expect("summary rail").area.width, 72);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resizing_auto_panel_keeps_it_visible_at_the_auto_breakpoint() {
|
||||
let mut state = OwnedScreenFrameState::default();
|
||||
|
||||
@@ -363,6 +363,53 @@ async fn owned_screen_browser_renders_in_wide_right_rail() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn owned_screen_browser_renders_in_half_width_right_rail() {
|
||||
let mut app = app_with_owned_parent().await;
|
||||
seed_pane(
|
||||
&mut app,
|
||||
PaneSlot::Parent,
|
||||
"draft sentinel",
|
||||
&["committed response"],
|
||||
);
|
||||
app.owned_screen_frame
|
||||
.select_right_rail_content(OwnedScreenRightRailContent::Browser);
|
||||
let frame_area = Rect::new(
|
||||
/*x*/ 0, /*y*/ 0, /*width*/ 144, /*height*/ 12,
|
||||
);
|
||||
let layout = app
|
||||
.owned_screen_frame
|
||||
.layout(frame_area, /*has_side*/ false);
|
||||
let divider = layout.summary_divider.expect("browser divider");
|
||||
assert!(
|
||||
app.owned_screen_frame
|
||||
.handle_mouse_primary(primary_press(divider.x, divider.y))
|
||||
);
|
||||
assert!(app.owned_screen_frame.handle_mouse_primary(primary_event(
|
||||
MousePrimaryEventKind::Drag,
|
||||
frame_area.x,
|
||||
divider.y,
|
||||
)));
|
||||
assert!(app.owned_screen_frame.handle_mouse_primary(primary_event(
|
||||
MousePrimaryEventKind::Release,
|
||||
frame_area.x,
|
||||
divider.y,
|
||||
)));
|
||||
let browser = browser_view(/*human_control*/ false, Some((1, 2)));
|
||||
|
||||
let terminal = render_frame_app_with_browser(
|
||||
&mut app,
|
||||
frame_area.width,
|
||||
frame_area.height,
|
||||
Some(&browser),
|
||||
);
|
||||
|
||||
assert_snapshot!(
|
||||
"owned_screen_browser_renders_in_half_width_right_rail",
|
||||
terminal.backend()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn owned_screen_browser_preserves_narrow_overlay_chrome() {
|
||||
let mut app = app_with_owned_parent().await;
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
---
|
||||
source: tui/src/app/owned_screen_tests.rs
|
||||
expression: terminal.backend()
|
||||
---
|
||||
"committed response ┃ Summary | Browser "
|
||||
" ┃ [<] [>] [R] Example running "
|
||||
" ┃ URL https://example.com "
|
||||
" ┃Codex browser panel "
|
||||
" ┃interactive page "
|
||||
" ┃ready "
|
||||
" ┃ "
|
||||
" ┃ "
|
||||
" ┃ "
|
||||
"› draft sentinel ┃ "
|
||||
" ┃ "
|
||||
" gpt-5.5 default · /tmp/project ┃ Esc hide click page take control /browser close stop "
|
||||
Reference in New Issue
Block a user