Suppose you would like to display some word-wrapped text in a block in a popup. How can one setup a layout such that the popup's width is 50% of the window, but the popups height is calculated based on the height of the contents, ie the number of lines in the wrapped text at that particular width? Is such a thing possible?
We can’t help without knowing what framework/platform you are building this UI in.
1 Like
Apologies, in my late night haste I assumed this was the ratatui discourse. I am working with ratatui. I have updated the post title.
Maye you need this:
use ratatui::crossterm::event;
use ratatui::{
Frame,
layout::{Alignment, Constraint, Layout},
style::{Color, Style},
text::{Line, Text},
widgets::{Block, Borders, Paragraph, Wrap},
};
const POPUP_WIDTH_PERCENTAGE: u16 = 50;
const TEXT_CONTENT: &str = "Ratatui is a Rust library for building terminal user interfaces. \
It provides a set of widgets and utilities for creating rich and interactive TUI applications. \
This text will automatically wrap to fit within the popup width.";
fn render_popup(frame: &mut Frame) {
let terminal_area = frame.area();
// Render some background content to show position relationship
let background_text = vec![
Line::from("Background Content - Line 1"),
Line::from("Background Content - Line 2"),
Line::from("Background Content - Line 3"),
Line::from(""),
Line::from("This is the main terminal content."),
Line::from("The popup should appear centered above this text."),
Line::from(""),
Line::from("More background content..."),
]
.into_iter()
.cycle()
.take(30)
.collect::<Vec<_>>();
let background = Paragraph::new(background_text).style(Style::new().fg(Color::Green));
frame.render_widget(background, terminal_area);
// Calculate popup dimensions based on content
let popup_width = terminal_area.width * POPUP_WIDTH_PERCENTAGE / 100;
let inner_width = popup_width.saturating_sub(2); // Account for borders
// Estimate required height based on text content
let text_lines = TEXT_CONTENT.len() as f32 / (inner_width as f32 * 0.8); // Rough estimation
let estimated_height = text_lines.ceil() as u16 + 2; // Add borders
let popup_height = estimated_height.min(terminal_area.height.saturating_sub(4)); // Limit to terminal height
// Use ratatui's Layout system for proper centering (both horizontal and vertical)
let vertical_layout = Layout::vertical([
Constraint::Percentage((100 - popup_height * 100 / terminal_area.height) / 2),
Constraint::Length(popup_height),
Constraint::Percentage((100 - popup_height * 100 / terminal_area.height) / 2),
])
.flex(ratatui::layout::Flex::Legacy)
.split(terminal_area);
let popup_area = Layout::horizontal([
Constraint::Percentage((100 - POPUP_WIDTH_PERCENTAGE) / 2),
Constraint::Percentage(POPUP_WIDTH_PERCENTAGE),
Constraint::Percentage((100 - POPUP_WIDTH_PERCENTAGE) / 2),
])
.flex(ratatui::layout::Flex::Legacy)
.split(vertical_layout[1])[1];
// Create the paragraph with automatic text wrapping
let paragraph = Paragraph::new(Text::from(TEXT_CONTENT))
.block(
Block::bordered()
.title(Line::from("Popup Title").centered())
.borders(Borders::ALL)
.border_style(Style::new().fg(Color::Yellow))
.style(Style::new().bg(Color::Black).fg(Color::White)),
)
.alignment(Alignment::Left)
.wrap(Wrap { trim: true });
// Render the popup on top
frame.render_widget(paragraph, popup_area);
}
fn main() -> std::io::Result<()> {
let mut terminal = ratatui::init();
let mut should_quit = false;
while !should_quit {
terminal.draw(|frame| {
render_popup(frame);
})?;
// Check for quit input (Ctrl+C)
if event::poll(std::time::Duration::from_millis(100))? {
if let event::Event::Key(key) = event::read()? {
if key.kind == event::KeyEventKind::Press && key.code == event::KeyCode::Char('q') {
should_quit = true;
}
}
}
}
ratatui::restore();
Ok(())
}
Show:
