use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
use crate::event::Event;
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::render::RenderCx;
use crate::style::{Style, TextWrap};
use crate::text::Text;
pub struct Label {
text: Text,
style: Style,
id: Option<String>,
class: Option<String>,
}
impl Label {
pub fn new(text: impl Into<Text>) -> Self {
Self { text: text.into(), style: Style::default(), id: None, class: None }
}
pub fn style(mut self, style: Style) -> Self { self.style = style; self }
pub fn id(mut self, id: impl Into<String>) -> Self { self.id = Some(id.into()); self }
pub fn class(mut self, class: impl Into<String>) -> Self { self.class = Some(class.into()); self }
}
impl Component for Label {
fn render(&self, cx: &mut RenderCx) {
for line in &self.text.lines {
if cx.wrap != TextWrap::None {
let full: String = line.spans.iter().map(|s| s.text.as_str()).collect();
let s = crate::style_parser::merge_styles(cx.style.clone(), &self.style);
cx.set_style(s);
cx.line(&full);
} else {
let total_w: u16 = line.spans.iter().map(|s| s.width()).sum();
cx.cursor.x = cx.cursor.x.saturating_add(cx.align_offset(total_w));
for span in &line.spans {
let base = crate::style_parser::merge_styles(cx.style.clone(), &self.style);
let s = crate::style_parser::merge_styles(base, &span.style);
cx.set_style(s);
cx.text(&span.text);
}
cx.cursor.y = cx.cursor.y.saturating_add(1);
cx.cursor.x = cx.rect.x;
}
}
}
fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
let natural_w = self.text.max_width();
let natural_h = self.text.height() as u16;
if constraint.max.width > 0 && natural_w > constraint.max.width {
let mut lines: u16 = 0;
for line in &self.text.lines {
let line_w: u16 = line.spans.iter().map(|s| s.width()).sum();
if line_w == 0 { lines += 1; continue; }
lines += (line_w + constraint.max.width - 1) / constraint.max.width;
}
Size { width: constraint.max.width.min(natural_w), height: lines }
} else {
Size { width: natural_w, height: natural_h }
}
}
fn layout(&mut self, _rect: Rect, _cx: &mut LayoutCx) {}
fn style(&self) -> Style { self.style.clone() }
fn id(&self) -> Option<&str> { self.id.as_deref() }
fn class(&self) -> Option<&str> { self.class.as_deref() }
fn focusable(&self) -> bool { false }
fn event(&mut self, _event: &Event, _cx: &mut EventCx) {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Color;
use crate::testbuffer::TestBuffer;
#[test]
fn test_basic_render() {
let mut tb = TestBuffer::new(20, 1);
tb.render(&Label::new("hello"));
tb.assert_line(0, "hello");
}
#[test]
fn test_multiline() {
let mut tb = TestBuffer::new(20, 3);
tb.render(&Label::new("line1\nline2\nline3"));
tb.assert_text(0, 0, "l");
tb.assert_text(0, 1, "l");
tb.assert_text(0, 2, "l");
}
#[test]
fn test_empty() {
let mut tb = TestBuffer::new(20, 1);
tb.render(&Label::new(""));
assert!(tb.buffer.cells[0].symbol == " ");
}
#[test]
fn test_styled_label() {
let mut tb = TestBuffer::new(20, 1);
tb.render(&Label::new("warn").style(Style::default().fg(Color::Red)));
assert_eq!(tb.cell_fg(0, 0), Some(Color::Red));
}
}