Add Border widget
This commit is contained in:
parent
6a0c0474ec
commit
964f3bf011
3 changed files with 208 additions and 3 deletions
|
|
@ -2,18 +2,20 @@ use std::convert::Infallible;
|
||||||
|
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use crossterm::style::{ContentStyle, Stylize};
|
use crossterm::style::{ContentStyle, Stylize};
|
||||||
use toss::widgets::Text;
|
use toss::widgets::{Border, BorderLook, Text};
|
||||||
use toss::{Styled, Terminal, Widget};
|
use toss::{Styled, Terminal, Widget};
|
||||||
|
|
||||||
fn widget() -> impl Widget<Infallible> {
|
fn widget() -> impl Widget<Infallible> {
|
||||||
Text::new(
|
Border::new(Text::new(
|
||||||
Styled::new("Hello world!", ContentStyle::default().green())
|
Styled::new("Hello world!", ContentStyle::default().green())
|
||||||
.then_plain("\n")
|
.then_plain("\n")
|
||||||
.then(
|
.then(
|
||||||
"Press any key to exit",
|
"Press any key to exit",
|
||||||
ContentStyle::default().on_dark_blue(),
|
ContentStyle::default().on_dark_blue(),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
|
.look(BorderLook::LINE_DOUBLE)
|
||||||
|
.style(ContentStyle::default().dark_red())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_frame(term: &mut Terminal) {
|
fn render_frame(term: &mut Terminal) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
mod border;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
|
pub use border::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|
|
||||||
201
src/widgets/border.rs
Normal file
201
src/widgets/border.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use crossterm::style::ContentStyle;
|
||||||
|
|
||||||
|
use crate::{AsyncWidget, Frame, Pos, Size, Widget};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BorderLook {
|
||||||
|
pub top_left: &'static str,
|
||||||
|
pub top_right: &'static str,
|
||||||
|
pub bottom_left: &'static str,
|
||||||
|
pub bottom_right: &'static str,
|
||||||
|
pub top: &'static str,
|
||||||
|
pub bottom: &'static str,
|
||||||
|
pub left: &'static str,
|
||||||
|
pub right: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BorderLook {
|
||||||
|
/// ```
|
||||||
|
/// +-------+
|
||||||
|
/// | Hello |
|
||||||
|
/// +-------+
|
||||||
|
/// ```
|
||||||
|
pub const ASCII: Self = Self {
|
||||||
|
top_left: "+",
|
||||||
|
top_right: "+",
|
||||||
|
bottom_left: "+",
|
||||||
|
bottom_right: "+",
|
||||||
|
top: "-",
|
||||||
|
bottom: "-",
|
||||||
|
left: "|",
|
||||||
|
right: "|",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// ┌───────┐
|
||||||
|
/// │ Hello │
|
||||||
|
/// └───────┘
|
||||||
|
/// ```
|
||||||
|
pub const LINE: Self = Self {
|
||||||
|
top_left: "┌",
|
||||||
|
top_right: "┐",
|
||||||
|
bottom_left: "└",
|
||||||
|
bottom_right: "┘",
|
||||||
|
top: "─",
|
||||||
|
bottom: "─",
|
||||||
|
left: "│",
|
||||||
|
right: "│",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// ┏━━━━━━━┓
|
||||||
|
/// ┃ Hello ┃
|
||||||
|
/// ┗━━━━━━━┛
|
||||||
|
/// ```
|
||||||
|
pub const LINE_HEAVY: Self = Self {
|
||||||
|
top_left: "┏",
|
||||||
|
top_right: "┓",
|
||||||
|
bottom_left: "┗",
|
||||||
|
bottom_right: "┛",
|
||||||
|
top: "━",
|
||||||
|
bottom: "━",
|
||||||
|
left: "┃",
|
||||||
|
right: "┃",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// ╔═══════╗
|
||||||
|
/// ║ Hello ║
|
||||||
|
/// ╚═══════╝
|
||||||
|
/// ```
|
||||||
|
pub const LINE_DOUBLE: Self = Self {
|
||||||
|
top_left: "╔",
|
||||||
|
top_right: "╗",
|
||||||
|
bottom_left: "╚",
|
||||||
|
bottom_right: "╝",
|
||||||
|
top: "═",
|
||||||
|
bottom: "═",
|
||||||
|
left: "║",
|
||||||
|
right: "║",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BorderLook {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::LINE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Border<I> {
|
||||||
|
inner: I,
|
||||||
|
look: BorderLook,
|
||||||
|
style: ContentStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Border<I> {
|
||||||
|
pub fn new(inner: I) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
look: BorderLook::default(),
|
||||||
|
style: ContentStyle::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn look(mut self, look: BorderLook) -> Self {
|
||||||
|
self.look = look;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn style(mut self, style: ContentStyle) -> Self {
|
||||||
|
self.style = style;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_border(&self, frame: &mut Frame) {
|
||||||
|
let size = frame.size();
|
||||||
|
let right = size.width.saturating_sub(1).into();
|
||||||
|
let bottom = size.height.saturating_sub(1).into();
|
||||||
|
|
||||||
|
for y in 1..bottom {
|
||||||
|
frame.write(Pos::new(right, y), (self.look.right, self.style));
|
||||||
|
frame.write(Pos::new(0, y), (self.look.left, self.style));
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in 1..right {
|
||||||
|
frame.write(Pos::new(x, bottom), (self.look.bottom, self.style));
|
||||||
|
frame.write(Pos::new(x, 0), (self.look.top, self.style));
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.write(
|
||||||
|
Pos::new(right, bottom),
|
||||||
|
(self.look.bottom_right, self.style),
|
||||||
|
);
|
||||||
|
frame.write(Pos::new(0, bottom), (self.look.bottom_left, self.style));
|
||||||
|
frame.write(Pos::new(right, 0), (self.look.top_right, self.style));
|
||||||
|
frame.write(Pos::new(0, 0), (self.look.top_left, self.style));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_inner(&self, frame: &mut Frame) {
|
||||||
|
let mut size = frame.size();
|
||||||
|
size.width = size.width.saturating_sub(2);
|
||||||
|
size.height = size.height.saturating_sub(2);
|
||||||
|
|
||||||
|
frame.push(Pos::new(1, 1), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E, I> Widget<E> for Border<I>
|
||||||
|
where
|
||||||
|
I: Widget<E>,
|
||||||
|
{
|
||||||
|
fn size(
|
||||||
|
&self,
|
||||||
|
frame: &mut Frame,
|
||||||
|
max_width: Option<u16>,
|
||||||
|
max_height: Option<u16>,
|
||||||
|
) -> Result<Size, E> {
|
||||||
|
let max_width = max_width.map(|w| w.saturating_sub(2));
|
||||||
|
let max_height = max_height.map(|h| h.saturating_sub(2));
|
||||||
|
let size = self.inner.size(frame, max_width, max_height)?;
|
||||||
|
Ok(size + Size::new(2, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||||
|
self.draw_border(frame);
|
||||||
|
|
||||||
|
self.push_inner(frame);
|
||||||
|
self.inner.draw(frame)?;
|
||||||
|
frame.pop();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<E, I> AsyncWidget<E> for Border<I>
|
||||||
|
where
|
||||||
|
I: AsyncWidget<E> + Send + Sync,
|
||||||
|
{
|
||||||
|
async fn size(
|
||||||
|
&self,
|
||||||
|
frame: &mut Frame,
|
||||||
|
max_width: Option<u16>,
|
||||||
|
max_height: Option<u16>,
|
||||||
|
) -> Result<Size, E> {
|
||||||
|
let max_width = max_width.map(|w| w.saturating_sub(2));
|
||||||
|
let max_height = max_height.map(|h| h.saturating_sub(2));
|
||||||
|
let size = self.inner.size(frame, max_width, max_height).await?;
|
||||||
|
Ok(size + Size::new(2, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn draw(self, frame: &mut Frame) -> Result<(), E> {
|
||||||
|
self.draw_border(frame);
|
||||||
|
|
||||||
|
self.push_inner(frame);
|
||||||
|
self.inner.draw(frame).await?;
|
||||||
|
frame.pop();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue