separate mod text; perf: don't branch with Tui

This commit is contained in:
facile pop culture reference 2026-09-01 18:28:43 +03:00
parent b318d498e5
commit c9a89eeb1d
9 changed files with 271 additions and 267 deletions

View file

@ -454,5 +454,5 @@ impl<'a, S: Screen, T: Draw<S>> LayoutPull<'a, S> for T {}
impl<'a, S: Screen, T: Draw<S>> LayoutPad<'a, S> for T {}
pub fn check_layout <'a> (t: impl Draw<Tui>) -> Drawn<u16> {
Tui::Layout(XYWH(1u16, 1, 80, 25)).size(None, t)
Tui(XYWH(1u16, 1, 80, 25), None).size(None, t)
}

View file

@ -226,7 +226,10 @@ pub fn bar_areas <'a, S: Screen> (
to.size(XYWH(x0 + w0 - n, y0, n, h0), a)?,
to.size(XYWH(x0, y0, w0 - n, h0), b)?,
),
_ => todo!()
Split::Above | Split::Below => (
to.size(XYWH(x0, y0, w0, h0), a)?,
to.size(XYWH(x0, y0, w0, h0), b)?,
),
})
}
@ -238,7 +241,7 @@ pub struct Pair<A, B>(Split, A, B);
/// use ::tengri::*;
/// use Split::*;
/// fn size_of <'a, A: Draw<Tui>, B: Draw<Tui>> (stack: &Pair<Tui, A, B>) -> Drawn<S::Unit> {
/// Tui::Layout(XYWH(0, 0, 80, 25)).size(None, stack)
/// Tui(XYWH(0, 0, 80, 25), None).size(None, stack)
/// }
/// assert_eq!(size_of(&split(East, "foo", "bar"))?, Some(XYWH(0, 0, 6, 1)));
/// assert_eq!(size_of(&split(South, "foo", "bar"))?, Some(XYWH(0, 0, 3, 2)));
@ -293,11 +296,11 @@ fn draw_stacks <'a, S: Screen> (
/// ```
/// # use ::tengri::*; fn test_stack_areas () -> Usually<()> {
/// let area = XYWH(0u16, 0, 80, 25);
/// assert_eq!(stack_areas(&Split::East, &mut Tui::Layout(area), &"foo", &"bar")?, (
/// assert_eq!(stack_areas(&Split::East, &mut Tui(area, None), &"foo", &"bar")?, (
/// Some(XYWH(0u16, 0, 3, 1)),
/// Some(XYWH(3u16, 0, 3, 1)),
/// ));
/// assert_eq!(stack_areas(&Split::South, &mut Tui::Layout(area), &"foo", &"bar")?, (
/// assert_eq!(stack_areas(&Split::South, &mut Tui(area, None), &"foo", &"bar")?, (
/// Some(XYWH(0u16, 0, 3, 1)),
/// Some(XYWH(0u16, 1, 3, 1)),
/// ));
@ -522,7 +525,7 @@ pub const fn below <'a, S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl
}
#[cfg(test)] #[test] fn test_align () -> Usually<()> {
let mut screen = Tui::Layout(XYWH(0, 0, 80, 25));
let mut screen = Tui(XYWH(0, 0, 80, 25), None);
assert_eq!("FOOBAR\nKILROY".draw(&mut screen)?, Some(XYWH(0, 0, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_nw().draw(&mut screen)?, Some(XYWH(0, 0, 6, 2)));
assert_eq!("FOOBAR\nKILROY".align_n().draw(&mut screen)?, Some(XYWH(37, 0, 6, 2)));

View file

@ -1792,67 +1792,7 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
}
#[cfg(feature = "text")] pub use self::text::*;
#[cfg(feature = "text")] mod text {
#![allow(unused)]
pub(crate) use ::unicode_width::*;
/// Displays an owned [str]-like with fixed maximum width.
///
/// Width is computed using [unicode_width].
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
impl<T: AsRef<str>> AsRef<str> for TrimString<T> {
fn as_ref (&self) -> &str {
self.1.as_ref()
}
}
impl<'a, T: AsRef<str>> TrimString<T> {
fn to_ref (&self) -> TrimStr<'_, T> {
TrimStr(self.0, &self.1)
}
}
/// Displays a borrowed [str]-like with fixed maximum width
///
/// Width is computed using [unicode_width].
pub struct TrimStr<'a, T: AsRef<str>>(pub u16, pub &'a T);
impl<T: AsRef<str>> AsRef<str> for TrimStr<'_, T> {
fn as_ref (&self) -> &str {
self.1.as_ref()
}
}
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
let mut width: u16 = 0;
let mut chars = text.as_ref().chars();
while let Some(c) = chars.next() {
width += c.width().unwrap_or(0) as u16;
if width >= max {
break
}
}
return width
}
/// Trim string with [unicode_width].
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
let input = input.as_ref();
let mut output = Vec::with_capacity(input.len());
let mut width: usize = 1;
let mut chars = input.chars();
while let Some(c) = chars.next() {
if width > max_width {
break
}
output.push(c);
width += c.width().unwrap_or(0);
}
return output.into_iter().collect()
}
}
#[cfg(feature = "text")] mod text;
#[cfg(feature = "term")] pub use self::term::*;
#[cfg(feature = "term")] mod term;

View file

@ -29,6 +29,7 @@ mod modify; pub use self::modify::*;
mod phat; pub use self::phat::*;
mod repeat; pub use self::repeat::*;
mod scroll; pub use self::scroll::*;
#[cfg(feature = "text")] mod text;
#[macro_export] macro_rules! tui_app {
($Struct:ident { $($fields:tt)* }) => {
@ -75,30 +76,44 @@ mod scroll; pub use self::scroll::*;
}
/// Terminal output.
pub enum Tui {
/// Draw mode: updates contained [Buffer].
Draw (
/// Ratatui buffer; area is screen size
Buffer,
pub struct Tui(
/// Current draw area
XYWH<u16>
),
/// Discard mode: only computes sizes
Layout (
/// Draw area; no buffer
XYWH<u16>
)
pub(crate) XYWH<u16>,
/// Ratatui buffer.
///
/// - Area is screen size.
/// - If present, we're in draw mode.
/// - If absent, we're in layout mode.
pub(crate) Option<Buffer>
);
impl AsRef<XYWH<u16>> for Tui {
fn as_ref (&self) -> &XYWH<u16> {
&self.0
}
}
impl From<&Tui> for XYWH<u16> {
fn from (screen: &Tui) -> XYWH<u16> {
screen.0
}
}
impl<'a: 'b, 'b> From<&'a mut Tui> for &'b mut XYWH<u16> {
fn from (screen: &'a mut Tui) -> &'b mut XYWH<u16> {
&mut screen.0
}
}
impl Screen for Tui {
type Unit = u16;
fn area (&self) -> XYWH<Self::Unit> {
self.into()
self.0
}
fn size <'a> (
&mut self, area: impl Into<Option<XYWH<u16>>>, draw: impl Draw<Self>,
) -> Drawn<Self::Unit> {
Self::Layout(self.area()).clip(area, &|to|draw.draw(to))
Self(self.area(), None).clip(area, &|to|draw.draw(to))
}
fn draw <'a> (
&mut self, area: impl Into<Option<XYWH<u16>>>, draw: impl Draw<Tui>
@ -177,24 +192,28 @@ impl Tui {
disable_raw_mode().map_err(Into::into)
}
pub fn new (width: u16, height: u16) -> Self {
Self::Draw(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
Self(
XYWH(0, 0, width, height),
Some(Buffer::empty(Rect { x: 0, y: 0, width, height }))
)
}
pub fn resize <W: Write> (&mut self, back: &mut CrosstermBackend<W>, width: u16, height: u16) {
let size = Rect { x: 0, y: 0, width, height };
if let Self::Draw(buffer, ..) = self {
self.buffer().map(|buffer|{
if buffer.area != size {
back.clear_region(ClearType::All).unwrap();
buffer.resize(size);
buffer.reset();
}
}
});
}
pub fn redraw <'b, W: Write> (
&'b mut self,
back: &mut CrosstermBackend<W>,
next: &'b mut Self
) {
if let Self::Draw(prev, ..) = self && let Self::Draw(next, ..) = next {
if let Some(prev) = self.buffer()
&& let Some(next) = next.buffer() {
let updates = prev.diff(&next);
back.draw(updates.into_iter()).expect("failed to render");
Backend::flush(back).expect("failed to flush output new");
@ -204,7 +223,7 @@ impl Tui {
}
pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH<u16> {
let XYWH(x0, y0, w, h) = self.area();
if let Self::Draw(buffer, ..) = self {
if let Some(buffer) = self.buffer() {
for row in 0..h {
let y = y0 + row;
for col in 0..w {
@ -220,7 +239,7 @@ impl Tui {
self.xywh()
}
pub fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
if let Self::Draw(buffer, ..) = self {
if let Some(buffer) = self.buffer() {
let text = text.as_ref();
let style = style.unwrap_or(Style::default());
if x < buffer.area.width && y < buffer.area.height {
@ -229,7 +248,7 @@ impl Tui {
}
}
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
if let Self::Draw(buffer, ..) = self {
if let Some(buffer) = self.buffer() {
for cell in buffer.content.iter_mut() {
cell.fg = fg;
cell.bg = bg;
@ -303,11 +322,7 @@ impl Tui {
}
pub fn buffer (&mut self) -> Option<&mut Buffer> {
if let Tui::Draw(buffer, _) = self {
Some(buffer)
} else {
None
}
self.1.as_mut()
}
pub fn set_char (&mut self, x: u16, y: u16, c: char) {
@ -325,14 +340,6 @@ impl Tui {
//impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }
//impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } }
//impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
impl AsRef<XYWH<u16>> for Tui {
fn as_ref (&self) -> &XYWH<u16> {
match self {
Self::Draw(_, area) => area,
Self::Layout(area) => area
}
}
}
impl Wide<u16> for Tui {
fn w (&self) -> u16 { self.as_ref().2 }
@ -351,24 +358,6 @@ impl HasOrigin for Tui {
fn origin (&self) -> Azimuth { Azimuth::NW }
}
impl From<&Tui> for XYWH<u16> {
fn from (screen: &Tui) -> XYWH<u16> {
match screen {
Tui::Draw(_, area) => *area,
Tui::Layout(area) => *area,
}
}
}
impl<'a: 'b, 'b> From<&'a mut Tui> for &'b mut XYWH<u16> {
fn from (screen: &'a mut Tui) -> &'b mut XYWH<u16> {
match screen {
Tui::Draw(_, area) => area,
Tui::Layout(area) => area,
}
}
}
pub const fn fill_char <'a> (c: char) -> impl Draw<Tui> {
draw(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{
cell.set_char(c);
@ -407,131 +396,3 @@ impl<'a, T: Draw<Tui>> Draw<Tui> for ShowSizeOf<T> {
None
})
});
//#[cfg(feature = "text")] pub use self::tui_text::*;
#[cfg(feature = "text")] mod tui_text {
use super::*;
impl Tui {
/// Write a line of text
///
/// TODO: do a paragraph (handle newlines)
pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16)
-> Perhaps<XYWH<u16>>
{
let text = text.as_ref();
let mut string_width: u16 = 0;
for character in text.chars() {
let x = x0 + string_width;
let character_width = character.width().unwrap_or(0) as u16;
string_width += character_width;
if string_width > max_width {
break
}
if let Self::Draw(buffer, ..) = self {
if let Some(cell) = buffer.cell_mut(ratatui::prelude::Position { x, y }) {
cell.set_char(character);
} else {
break
}
}
}
Ok(Some(XYWH(x0, y, string_width, 1)))
}
}
impl_draw!(|self: usize, to: Tui|{
let XYWH(x0, y0, w0, h0) = to.area();
let mut v = *self;
let w = (1 + if v > 0 { self.ilog10() } else { 0 }) as u16;
let h = 1u16;
Ok((w0 >= w && h0 >= h).then(||{
if let Tui::Draw(buffer, ..) = to {
let mut x = x0 + w - 1;
while x >= x0 {
if let Some(cell) = buffer.cell_mut(Position { x, y: y0 }) {
cell.set_char([
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
][(v % 10) as usize]);
}
x -= 1;
v = v / 10;
}
}
XYWH(x0, y0, w, h)
}))
});
impl_draw!(|self: String, to: Tui|{
self.as_str().draw(to)
});
impl_draw!(|self: std::sync::Arc<str>, to: Tui|{
self.as_ref().draw(to)
});
impl_draw!(<T: AsRef<str>,>|self: TrimString<T>, to: Tui|{
self.as_ref().draw(to)
});
impl<'a> Draw<Tui> for &str {
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
let XYWH(x, y, w, h) = to.area();
let mut max_w = 0u16;
let mut max_h = 0u16;
for (index, line) in self.split("\n").enumerate() {
let length = width_chars_max(w, line) as u16;
max_w = max_w.max(length);
max_h += 1;
if let Tui::Draw(..) = to {
let _ = to.text(&line, x, y + index as u16, length)?;
}
if max_h >= h {
break;
}
}
Ok(Some(XYWH(x, y, max_w, max_h)))
}
}
impl<'a, T: AsRef<str>> Draw<Tui> for TrimStr<'a, T> {
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
let text = self.1.as_ref();
let area = layout_text_u16(text, to.area())?.unwrap();
let XYWH(x, y, w, ..) = to.area();
let mut width: u16 = 1;
let mut chars = text.chars();
while let Some(c) = chars.next() {
if width > self.0 || width > w {
break
}
if let Tui::Draw(..) = to {
to.set_char(x + width - 1, y, c);
}
width += c.width().unwrap_or(0) as u16;
}
let XYWH(x, y, w, ..) = XYWH(
to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()
);
to.text(&self.as_ref(), x, y, w)
}
}
fn layout_text_u16 (text: &str, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
let XYWH(x, y, w, h) = area;
let mut max_w = 0u16;
let mut max_h = 0u16;
for line in text.split("\n") {
max_h += 1;
max_w = max_w.max(line.len() as u16);
}
Ok(Some(XYWH(x, y, w.min(max_w), h.min(max_h))))
}
#[cfg(test)] #[test] fn test_layout_text_u16 () -> Usually<()> {
assert_eq!(layout_text_u16("foo", XYWH(5, 6, 10, 10))?, Some(XYWH(5, 6, 3, 1)));
assert_eq!(layout_text_u16("foo\nbarz", XYWH(5, 6, 10, 10))?, Some(XYWH(5, 6, 4, 2)));
Ok(())
}
}

View file

@ -113,12 +113,13 @@ pub const fn modify <'a> (on: bool, modifier: Modifier, item: impl Draw<Tui>)
}
pub const fn fill_mod <'a> (on: bool, modifier: Modifier) -> impl Draw<Tui> {
draw(move|to: &mut Tui|Ok(Some({
if on {
to.update(&|cell,_,_|cell.modifier.insert(modifier))
let modify = if on {
Modifier::insert
} else {
to.update(&|cell,_,_|cell.modifier.remove(modifier))
}
Modifier::remove
};
draw(move|to: &mut Tui|Ok(Some({
to.update(&|cell,_,_|modify(&mut cell.modifier, modifier))
})))
}

View file

@ -4,13 +4,13 @@ use ratatui::{prelude::{Position}};
pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
draw(move|to: &mut Tui|{
let XYWH(x, y, w, _h) = to.xywh();
to.buffer().map(|buf|{
for x in x..x+w {
if let Tui::Draw(buf, ..) = to {
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
cell.set_symbol(&c);
}
}
}
});
Ok(Some(XYWH(x, y, w, 1)))
})
}
@ -18,13 +18,13 @@ pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
pub const fn y_repeat (c: &str) -> impl Draw<Tui> {
draw(move|to: &mut Tui|{
let XYWH(x, y, _w, h) = to.xywh();
to.buffer().map(|buf|{
for y in y..y+h {
if let Tui::Draw(buf, ..) = to {
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
cell.set_symbol(&c);
}
}
}
});
Ok(Some(XYWH(x, y, 1, h)))
})
}
@ -33,16 +33,16 @@ pub const fn xy_repeat (c: &str) -> impl Draw<Tui> {
draw(move|to: &mut Tui|{
let XYWH(x, y, w, h) = to.xywh();
let a = c.len();
to.buffer().map(|buf|{
for (_v, y) in (y..y+h).enumerate() {
for (u, x) in (x..x+w).enumerate() {
if let Tui::Draw(buf, ..) = to {
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
let u = u % a;
cell.set_symbol(&c[u..u+1]);
}
}
}
}
});
Ok(Some(XYWH(x, y, w, h)))
})
}

View file

@ -11,7 +11,7 @@ pub fn x_scroll <'a> () -> impl Draw<Tui> {
let XYWH(x1, y1, w, _h) = to.area();
let x2 = x1 + w;
for (i, x) in (x1..=x2).enumerate() {
if let Tui::Draw(buf, ..) = to {
to.buffer().map(|buf|{
if let Some(cell) = buf.cell_mut(Position::from((x, y1))) {
if i < (ICON_DEC_H.len()) {
cell.set_fg(Rgb(255, 255, 255));
@ -31,7 +31,7 @@ pub fn x_scroll <'a> () -> impl Draw<Tui> {
cell.set_char('╌');
}
}
}
});
}
Ok(Some(XYWH(x1, y1, w, 1)))
})
@ -42,7 +42,7 @@ pub fn y_scroll <'a> () -> impl Draw<Tui> {
let XYWH(x1, y1, _w, h) = to.area();
let y2 = y1 + h;
for (i, y) in (y1..=y2).enumerate() {
if let Tui::Draw(buf, ..) = to {
to.buffer().map(|buf|{
if let Some(cell) = buf.cell_mut(Position::from((x1, y))) {
if (i as usize) < (ICON_DEC_V.len()) {
cell.set_fg(Rgb(255, 255, 255));
@ -62,7 +62,7 @@ pub fn y_scroll <'a> () -> impl Draw<Tui> {
cell.set_char('╎'); // ━
}
}
}
});
}
Ok(Some(XYWH(x1, y1, 1, h)))
})

1
src/term/text.rs Normal file
View file

@ -0,0 +1 @@
use crate::*;

198
src/text.rs Normal file
View file

@ -0,0 +1,198 @@
use crate::*;
pub(crate) use ::unicode_width::*;
/// Displays an owned [str]-like with fixed maximum width.
///
/// Width is computed using [unicode_width].
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
impl<T: AsRef<str>> AsRef<str> for TrimString<T> {
fn as_ref (&self) -> &str {
self.1.as_ref()
}
}
impl<'a, T: AsRef<str>> TrimString<T> {
fn to_ref (&self) -> TrimStr<'_, T> {
TrimStr(self.0, &self.1)
}
}
/// Displays a borrowed [str]-like with fixed maximum width
///
/// Width is computed using [unicode_width].
pub struct TrimStr<'a, T: AsRef<str>>(pub u16, pub &'a T);
impl<T: AsRef<str>> AsRef<str> for TrimStr<'_, T> {
fn as_ref (&self) -> &str {
self.1.as_ref()
}
}
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
let mut width: u16 = 0;
let mut chars = text.as_ref().chars();
while let Some(c) = chars.next() {
width += c.width().unwrap_or(0) as u16;
if width >= max {
break
}
}
return width
}
/// Trim string with [unicode_width].
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
let input = input.as_ref();
let mut output = Vec::with_capacity(input.len());
let mut width: usize = 1;
let mut chars = input.chars();
while let Some(c) = chars.next() {
if width > max_width {
break
}
output.push(c);
width += c.width().unwrap_or(0);
}
return output.into_iter().collect()
}
#[cfg(feature = "term")]
mod text_term {
use crate::*;
impl Tui {
/// Write a line of text
///
/// TODO: do a paragraph (handle newlines)
pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16)
-> Perhaps<XYWH<u16>>
{
let text = text.as_ref();
let mut string_width: u16 = 0;
for character in text.chars() {
let x = x0 + string_width;
let character_width = character.width().unwrap_or(0) as u16;
string_width += character_width;
if string_width > max_width {
break
}
if Some(true) == self.buffer().map(|buffer|{
if let Some(cell) = buffer.cell_mut(ratatui::prelude::Position { x, y }) {
cell.set_char(character);
false
} else {
true
}
}) {
break
}
}
Ok(Some(XYWH(x0, y, string_width, 1)))
}
}
impl_draw!(|self: usize, to: Tui|{
let XYWH(x0, y0, w0, h0) = to.area();
let mut v = *self;
let w = (1 + if v > 0 { self.ilog10() } else { 0 }) as u16;
let h = 1u16;
Ok((w0 >= w && h0 >= h).then(||{
to.buffer().map(|buffer|{
let mut x = x0 + w - 1;
while x >= x0 {
if let Some(cell) = buffer.cell_mut(Position { x, y: y0 }) {
cell.set_char([
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
][(v % 10) as usize]);
}
x -= 1;
v = v / 10;
}
});
XYWH(x0, y0, w, h)
}))
});
impl_draw!(|self: String, to: Tui|{
self.as_str().draw(to)
});
impl_draw!(|self: std::sync::Arc<str>, to: Tui|{
self.as_ref().draw(to)
});
impl_draw!(<T: AsRef<str>,>|self: TrimString<T>, to: Tui|{
self.as_ref().draw(to)
});
impl<'a> Draw<Tui> for &str {
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
let XYWH(x, y, w, h) = to.area();
let mut max_w = 0u16;
let mut max_h = 0u16;
if to.buffer().is_some() {
for (index, line) in self.split("\n").enumerate() {
let length = width_chars_max(w, line) as u16;
max_w = max_w.max(length);
max_h += 1;
let _ = to.text(&line, x, y + index as u16, length)?;
if max_h >= h {
break;
}
}
} else {
for (index, line) in self.split("\n").enumerate() {
let length = width_chars_max(w, line) as u16;
max_w = max_w.max(length);
max_h += 1;
if max_h >= h {
break;
}
}
}
Ok(Some(XYWH(x, y, max_w, max_h)))
}
}
impl<'a, T: AsRef<str>> Draw<Tui> for TrimStr<'a, T> {
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
let text = self.1.as_ref();
//let area = layout_text_u16(text, to.area())?.unwrap();
let XYWH(x, y, w, ..) = to.area();
let mut width: u16 = 1;
let mut chars = text.chars();
while let Some(c) = chars.next() {
if width > self.0 || width > w {
break
}
if to.buffer().is_some() {
to.set_char(x + width - 1, y, c);
}
width += c.width().unwrap_or(0) as u16;
}
let XYWH(x, y, w, ..) = XYWH(
to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()
);
to.text(&self.as_ref(), x, y, w)
}
}
fn layout_text_u16 (text: &str, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
let XYWH(x, y, w, h) = area;
let mut max_w = 0u16;
let mut max_h = 0u16;
for line in text.split("\n") {
max_h += 1;
max_w = max_w.max(line.len() as u16);
}
Ok(Some(XYWH(x, y, w.min(max_w), h.min(max_h))))
}
#[cfg(test)] #[test] fn test_layout_text_u16 () -> Usually<()> {
assert_eq!(layout_text_u16("foo", XYWH(5, 6, 10, 10))?, Some(XYWH(5, 6, 3, 1)));
assert_eq!(layout_text_u16("foo\nbarz", XYWH(5, 6, 10, 10))?, Some(XYWH(5, 6, 4, 2)));
Ok(())
}
}