mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-09-18 05:16:44 +02:00
Compare commits
2 commits
b318d498e5
...
48aaa3933a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48aaa3933a | ||
|
|
c9a89eeb1d |
10 changed files with 300 additions and 279 deletions
2
dizzle
2
dizzle
|
|
@ -1 +1 @@
|
|||
Subproject commit b6c1bb5f9400c28de1709df78c18e0d4d5618a15
|
||||
Subproject commit 3d04622e4266eac65021da6cd3d378e84a843b87
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,22 +211,25 @@ pub fn bar_areas <'a, S: Screen> (
|
|||
let XYWH(x0, y0, w0, h0) = to.area();
|
||||
Ok(match split {
|
||||
Split::South => (
|
||||
to.size(XYWH(x0, y0, w0, n), a)?,
|
||||
to.size(XYWH(x0, y0 + n, w0, h0 - n), b)?,
|
||||
Some(XYWH(x0, y0, w0, n)),
|
||||
Some(XYWH(x0, y0 + n, w0, h0 - n)),
|
||||
),
|
||||
Split::East => (
|
||||
to.size(XYWH(x0, y0, n, h0), a)?,
|
||||
to.size(XYWH(x0 + n, y0, w0 - n, h0), b)?,
|
||||
Some(XYWH(x0, y0, n, h0)),
|
||||
Some(XYWH(x0 + n, y0, w0 - n, h0)),
|
||||
),
|
||||
Split::North => (
|
||||
to.size(XYWH(x0, y0 + h0 - n, w0, n), a)?,
|
||||
to.size(XYWH(x0, y0, w0, h0 - n), b)?,
|
||||
Some(XYWH(x0, y0 + h0 - n, w0, n)),
|
||||
Some(XYWH(x0, y0, w0, h0 - n)),
|
||||
),
|
||||
Split::West => (
|
||||
to.size(XYWH(x0 + w0 - n, y0, n, h0), a)?,
|
||||
to.size(XYWH(x0, y0, w0 - n, h0), b)?,
|
||||
Some(XYWH(x0 + w0 - n, y0, n, h0)),
|
||||
Some(XYWH(x0, y0, w0 - n, h0)),
|
||||
),
|
||||
Split::Above | Split::Below => (
|
||||
Some(XYWH(x0, y0, w0, h0)),
|
||||
Some(XYWH(x0, y0, w0, h0)),
|
||||
),
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -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)));
|
||||
|
|
|
|||
62
src/lib.rs
62
src/lib.rs
|
|
@ -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;
|
||||
|
|
|
|||
231
src/term.rs
231
src/term.rs
|
|
@ -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,
|
||||
/// Current draw area
|
||||
XYWH<u16>
|
||||
),
|
||||
/// Discard mode: only computes sizes
|
||||
Layout (
|
||||
/// Draw area; no buffer
|
||||
XYWH<u16>
|
||||
)
|
||||
pub struct Tui(
|
||||
/// Current draw area
|
||||
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>
|
||||
|
|
@ -111,10 +126,12 @@ impl Screen for Tui {
|
|||
let prev = self.area();
|
||||
if let Some(area) = area.into() {
|
||||
*self.area_mut() = area.into();
|
||||
let result = draw(self);
|
||||
*self.area_mut() = prev;
|
||||
result
|
||||
} else {
|
||||
draw(self)
|
||||
}
|
||||
let result = draw(self);
|
||||
*self.area_mut() = prev;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,24 +194,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 +225,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 +241,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 +250,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 +324,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 +342,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 +360,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 +398,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(())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
let modify = if on {
|
||||
Modifier::insert
|
||||
} else {
|
||||
Modifier::remove
|
||||
};
|
||||
draw(move|to: &mut Tui|Ok(Some({
|
||||
if on {
|
||||
to.update(&|cell,_,_|cell.modifier.insert(modifier))
|
||||
} else {
|
||||
to.update(&|cell,_,_|cell.modifier.remove(modifier))
|
||||
}
|
||||
to.update(&|cell,_,_|modify(&mut cell.modifier, modifier))
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
for x in x..x+w {
|
||||
if let Tui::Draw(buf, ..) = to {
|
||||
to.buffer().map(|buf|{
|
||||
for x in x..x+w {
|
||||
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();
|
||||
for y in y..y+h {
|
||||
if let Tui::Draw(buf, ..) = to {
|
||||
to.buffer().map(|buf|{
|
||||
for y in y..y+h {
|
||||
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();
|
||||
for (_v, y) in (y..y+h).enumerate() {
|
||||
for (u, x) in (x..x+w).enumerate() {
|
||||
if let Tui::Draw(buf, ..) = to {
|
||||
to.buffer().map(|buf|{
|
||||
for (_v, y) in (y..y+h).enumerate() {
|
||||
for (u, x) in (x..x+w).enumerate() {
|
||||
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)))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
1
src/term/text.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
use crate::*;
|
||||
213
src/text.rs
Normal file
213
src/text.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
use crate::*;
|
||||
pub(crate) use ::unicode_width::*;
|
||||
|
||||
/// Displays a multi-line text string.
|
||||
pub struct Text<T: AsRef<str>>(T);
|
||||
|
||||
/// 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);
|
||||
|
||||
/// 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 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)
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if h > 0 {
|
||||
to.buffer().map(|buf|buf.set_stringn(x, y, self, w as usize, Style::default()));
|
||||
Ok(Some(XYWH(x, y, w.min(self.len() as u16), 1)))
|
||||
} else {
|
||||
Ok(Some(XYWH(x, y, 0, 0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> Draw<Tui> for Text<T> {
|
||||
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.0.as_ref().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 line in self.0.as_ref().split("\n") {
|
||||
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(())
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue