feat(ui): embed header in main block and base layout on inner content area
- Render the app title with version as the block title instead of a separate header widget. - Compute `content_area` via `main_block.inner` and use it for file panel, main area, model info panel, and toast rendering. - Remove header constraints and the `render_header` function, simplifying the layout. - Add early exit when `content_area` has zero width or height to avoid rendering errors.
This commit is contained in:
@@ -186,18 +186,37 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
|
|||||||
// Set terminal background color
|
// Set terminal background color
|
||||||
let theme = app.theme().clone();
|
let theme = app.theme().clone();
|
||||||
let background_block = Block::default().style(Style::default().bg(theme.background));
|
let background_block = Block::default().style(Style::default().bg(theme.background));
|
||||||
let full_area = frame.area();
|
let frame_area = frame.area();
|
||||||
frame.render_widget(background_block, full_area);
|
frame.render_widget(background_block, frame_area);
|
||||||
|
|
||||||
let (file_area, main_area) = if app.is_file_panel_collapsed() || full_area.width < 40 {
|
let title_line = Line::from(vec![Span::styled(
|
||||||
(None, full_area)
|
format!(" 🦉 OWLEN v{} – AI Assistant ", APP_VERSION),
|
||||||
|
Style::default()
|
||||||
|
.fg(theme.focused_panel_border)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)]);
|
||||||
|
let main_block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(Style::default().fg(theme.unfocused_panel_border))
|
||||||
|
.style(Style::default().bg(theme.background).fg(theme.text))
|
||||||
|
.title(title_line);
|
||||||
|
|
||||||
|
let content_area = main_block.inner(frame_area);
|
||||||
|
frame.render_widget(main_block, frame_area);
|
||||||
|
|
||||||
|
if content_area.width == 0 || content_area.height == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (file_area, main_area) = if app.is_file_panel_collapsed() || content_area.width < 40 {
|
||||||
|
(None, content_area)
|
||||||
} else {
|
} else {
|
||||||
let max_sidebar = full_area.width.saturating_sub(30).max(10);
|
let max_sidebar = content_area.width.saturating_sub(30).max(10);
|
||||||
let sidebar_width = app.file_panel_width().min(max_sidebar).max(10);
|
let sidebar_width = app.file_panel_width().min(max_sidebar).max(10);
|
||||||
let segments = Layout::default()
|
let segments = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Length(sidebar_width), Constraint::Min(30)])
|
.constraints([Constraint::Length(sidebar_width), Constraint::Min(30)])
|
||||||
.split(full_area);
|
.split(content_area);
|
||||||
(Some(segments[0]), segments[1])
|
(Some(segments[0]), segments[1])
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,10 +272,7 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
|
|||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut constraints = vec![
|
let mut constraints = vec![Constraint::Min(8)]; // Messages
|
||||||
Constraint::Length(3), // Header
|
|
||||||
Constraint::Min(8), // Messages
|
|
||||||
];
|
|
||||||
|
|
||||||
if thinking_height > 0 {
|
if thinking_height > 0 {
|
||||||
constraints.push(Constraint::Length(thinking_height)); // Thinking
|
constraints.push(Constraint::Length(thinking_height)); // Thinking
|
||||||
@@ -276,9 +292,6 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
|
|||||||
.split(chat_area);
|
.split(chat_area);
|
||||||
|
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
render_header(frame, layout[idx], app);
|
|
||||||
idx += 1;
|
|
||||||
|
|
||||||
render_messages(frame, layout[idx], app);
|
render_messages(frame, layout[idx], app);
|
||||||
idx += 1;
|
idx += 1;
|
||||||
|
|
||||||
@@ -318,13 +331,16 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if app.is_model_info_visible() {
|
if app.is_model_info_visible() {
|
||||||
let panel_width = full_area
|
let panel_width = content_area
|
||||||
.width
|
.width
|
||||||
.saturating_div(3)
|
.saturating_div(3)
|
||||||
.max(30)
|
.max(30)
|
||||||
.min(full_area.width.saturating_sub(20).max(30));
|
.min(content_area.width.saturating_sub(20).max(30))
|
||||||
let x = full_area.x + full_area.width.saturating_sub(panel_width);
|
.min(content_area.width);
|
||||||
let area = Rect::new(x, full_area.y, panel_width, full_area.height);
|
let x = content_area
|
||||||
|
.x
|
||||||
|
.saturating_add(content_area.width.saturating_sub(panel_width));
|
||||||
|
let area = Rect::new(x, content_area.y, panel_width, content_area.height);
|
||||||
frame.render_widget(Clear, area);
|
frame.render_widget(Clear, area);
|
||||||
let viewport_height = area.height.saturating_sub(2) as usize;
|
let viewport_height = area.height.saturating_sub(2) as usize;
|
||||||
app.set_model_info_viewport_height(viewport_height);
|
app.set_model_info_viewport_height(viewport_height);
|
||||||
@@ -335,7 +351,7 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
|
|||||||
render_code_workspace(frame, area, app);
|
render_code_workspace(frame, area, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
render_toasts(frame, app, full_area);
|
render_toasts(frame, app, content_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toast_palette(level: ToastLevel, theme: &Theme) -> (&'static str, Style, Style) {
|
fn toast_palette(level: ToastLevel, theme: &Theme) -> (&'static str, Style, Style) {
|
||||||
@@ -1109,24 +1125,6 @@ fn wrap_line_segments(line: &str, width: usize) -> Vec<String> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) {
|
|
||||||
let theme = app.theme();
|
|
||||||
let title_span = Span::styled(
|
|
||||||
format!(" 🦉 OWLEN v{} – AI Assistant ", APP_VERSION),
|
|
||||||
Style::default()
|
|
||||||
.fg(theme.focused_panel_border)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
);
|
|
||||||
|
|
||||||
let header_block = Block::default()
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.border_style(Style::default().fg(theme.unfocused_panel_border))
|
|
||||||
.style(Style::default().bg(theme.background).fg(theme.text))
|
|
||||||
.title(Line::from(vec![title_span]));
|
|
||||||
|
|
||||||
frame.render_widget(header_block, area);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_visual_selection<'a>(
|
fn apply_visual_selection<'a>(
|
||||||
lines: Vec<Line<'a>>,
|
lines: Vec<Line<'a>>,
|
||||||
selection: Option<((usize, usize), (usize, usize))>,
|
selection: Option<((usize, usize), (usize, usize))>,
|
||||||
|
|||||||
Reference in New Issue
Block a user