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
|
||||
let theme = app.theme().clone();
|
||||
let background_block = Block::default().style(Style::default().bg(theme.background));
|
||||
let full_area = frame.area();
|
||||
frame.render_widget(background_block, full_area);
|
||||
let frame_area = frame.area();
|
||||
frame.render_widget(background_block, frame_area);
|
||||
|
||||
let (file_area, main_area) = if app.is_file_panel_collapsed() || full_area.width < 40 {
|
||||
(None, full_area)
|
||||
let title_line = Line::from(vec![Span::styled(
|
||||
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 {
|
||||
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 segments = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Length(sidebar_width), Constraint::Min(30)])
|
||||
.split(full_area);
|
||||
.split(content_area);
|
||||
(Some(segments[0]), segments[1])
|
||||
};
|
||||
|
||||
@@ -253,10 +272,7 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
|
||||
0
|
||||
};
|
||||
|
||||
let mut constraints = vec![
|
||||
Constraint::Length(3), // Header
|
||||
Constraint::Min(8), // Messages
|
||||
];
|
||||
let mut constraints = vec![Constraint::Min(8)]; // Messages
|
||||
|
||||
if thinking_height > 0 {
|
||||
constraints.push(Constraint::Length(thinking_height)); // Thinking
|
||||
@@ -276,9 +292,6 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
|
||||
.split(chat_area);
|
||||
|
||||
let mut idx = 0;
|
||||
render_header(frame, layout[idx], app);
|
||||
idx += 1;
|
||||
|
||||
render_messages(frame, layout[idx], app);
|
||||
idx += 1;
|
||||
|
||||
@@ -318,13 +331,16 @@ pub fn render_chat(frame: &mut Frame<'_>, app: &mut ChatApp) {
|
||||
}
|
||||
|
||||
if app.is_model_info_visible() {
|
||||
let panel_width = full_area
|
||||
let panel_width = content_area
|
||||
.width
|
||||
.saturating_div(3)
|
||||
.max(30)
|
||||
.min(full_area.width.saturating_sub(20).max(30));
|
||||
let x = full_area.x + full_area.width.saturating_sub(panel_width);
|
||||
let area = Rect::new(x, full_area.y, panel_width, full_area.height);
|
||||
.min(content_area.width.saturating_sub(20).max(30))
|
||||
.min(content_area.width);
|
||||
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);
|
||||
let viewport_height = area.height.saturating_sub(2) as usize;
|
||||
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_toasts(frame, app, full_area);
|
||||
render_toasts(frame, app, content_area);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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>(
|
||||
lines: Vec<Line<'a>>,
|
||||
selection: Option<((usize, usize), (usize, usize))>,
|
||||
|
||||
Reference in New Issue
Block a user