This commit is contained in:
2025-01-16 23:47:34 +01:00
parent 6878777140
commit eeefd1f002
5 changed files with 183 additions and 77 deletions
+3
View File
@@ -8,6 +8,9 @@ repository = "https://github.com/osui-rs/osui"
homepage = "https://osui.netlify.app" homepage = "https://osui.netlify.app"
documentation = "https://osui.netlify.app/docs/" documentation = "https://osui.netlify.app/docs/"
[features]
rsx = []
[lib] [lib]
name = "osui" name = "osui"
path = "src/lib.rs" path = "src/lib.rs"
+104 -61
View File
@@ -1,86 +1,129 @@
pub mod utils; pub mod utils;
pub mod widgets;
type Result<T> = std::io::Result<T>; pub use std::io::Result;
pub trait Widget { pub trait Widget {
fn draw(&self, w: &mut Writer); fn render(&self) -> String;
} }
pub struct Writer(String); #[derive(Debug, Clone, Copy)]
pub struct Area {
impl Writer { width: u16,
pub fn write(&mut self, s: &str) { height: u16,
self.0 += s; x: u16,
} y: u16,
center_x: bool,
center_y: bool,
width_auto: bool,
height_auto: bool,
} }
pub type Element = Box<dyn Widget>; #[derive(Debug, Default, Clone, Copy)]
/////////////////////////////////////////
pub struct Frame { pub struct Frame {
pub area: (u16, u16), pub area: Area,
pub x: u16,
pub y: u16,
parent: Option<*const Frame>,
} }
impl Frame { impl Frame {
pub fn new(area: (u16, u16), parent: &Frame) -> Frame { pub fn draw<W>(&self, w: &W, mut props: Area) -> Result<()>
Frame {
area,
x: 0,
y: 0,
parent: Some(parent),
}
}
pub fn new_root(area: (u16, u16)) -> Frame {
Frame {
area,
x: 0,
y: 0,
parent: None,
}
}
pub fn render<E>(&self, elem: E)
where where
E: Widget, W: Widget,
{ {
let mut w = Writer(String::new()); let written = w.render();
elem.draw(&mut w);
if let Some(_f) = self.parent {
} else {
println!("{}", w.0);
}
}
}
pub struct Terminal(Frame); let (ww, wh) = utils::str_size(&written);
impl Terminal { if props.width_auto {
pub fn new() -> Result<Terminal> { props.width = ww;
Ok(Terminal(Frame::new_root(crossterm::terminal::size()?))) }
if props.height_auto {
props.height = wh;
}
if props.width > self.area.width {
props.width -= props.width - self.area.width;
}
if props.width > self.area.width {
props.width -= props.width - self.area.width;
}
if props.center_x {
props.x = (self.area.width - ww) / 2;
}
if props.center_y {
props.y = (self.area.height - wh) / 2;
}
for (i, line) in written.lines().enumerate() {
if i as u16 > props.height {
break;
}
println!(
"\x1b[{};{}H{}",
props.x + (i as u16) + 1,
props.y + 1,
line.chars().take(props.width as usize).collect::<String>()
);
} }
pub fn draw<F>(&mut self, f: F) -> Result<()>
where
F: Fn(&mut Frame),
{
f(&mut self.0);
Ok(()) Ok(())
} }
}
impl Widget for &str { pub fn new(width: u16, height: u16) -> Self {
fn draw(&self, w: &mut Writer) { let mut f = Self::default();
w.write(self); f.area.width(width);
f.area.height(height);
f
} }
} }
pub fn init() -> Result<Terminal> { impl Area {
utils::clear()?; pub fn new() -> Self {
utils::hide_cursor()?; Self::default()
Terminal::new() }
pub fn center_x(&mut self) -> Self {
self.center_x = true;
*self
}
pub fn center_y(&mut self) -> Self {
self.center_y = true;
*self
}
pub fn center(&mut self) -> Self {
self.center_x = true;
self.center_y = true;
*self
}
pub fn width(&mut self, w: u16) -> Self {
self.width = w;
self.width_auto = false;
*self
}
pub fn height(&mut self, h: u16) -> Self {
self.height = h;
self.height_auto = false;
*self
}
}
impl Default for Area {
fn default() -> Self {
Self {
width: 0,
height: 0,
x: 0,
y: 0,
center_x: false,
center_y: false,
width_auto: true,
height_auto: true,
}
}
} }
+11 -8
View File
@@ -1,12 +1,15 @@
use osui::*; use osui::*;
fn main() { fn main() -> osui::Result<()> {
let mut term = init().unwrap(); utils::init()?;
loop {
term.draw(app).unwrap();
}
}
fn app(frame: &mut Frame) { let (width, height) = crossterm::terminal::size()?;
frame.render("hi");
let frame = Frame::new(width, height);
frame.draw(&"Hello?", Area::new())?;
utils::read()?;
utils::end()
} }
+58 -8
View File
@@ -2,23 +2,73 @@ use std::io::Write;
pub fn clear() -> crate::Result<()> { pub fn clear() -> crate::Result<()> {
print!("\x1B[2J\x1B[H"); print!("\x1B[2J\x1B[H");
std::io::stdout().flush()?; std::io::stdout().flush()
Ok(())
} }
pub fn hide_cursor() -> crate::Result<()> { pub fn hide_cursor() -> crate::Result<()> {
print!("\x1b[?25l"); print!("\x1b[?25l");
std::io::stdout().flush()?; std::io::stdout().flush()
Ok(())
} }
pub fn show_cursor() -> crate::Result<()> { pub fn show_cursor() -> crate::Result<()> {
print!("\x1B[?25h"); print!("\x1B[?25h");
std::io::stdout().flush()?; std::io::stdout().flush()
Ok(())
} }
pub fn flush() -> crate::Result<()> { pub fn flush() -> crate::Result<()> {
std::io::stdout().flush()?; std::io::stdout().flush()
Ok(()) }
pub fn init() -> crate::Result<()> {
crossterm::terminal::enable_raw_mode()?;
clear()?;
hide_cursor()
}
pub fn end() -> crate::Result<()> {
crossterm::terminal::disable_raw_mode()?;
clear()?;
show_cursor()
}
pub fn str_size(s: &str) -> (u16, u16) {
let mut height = 1;
let mut max_width = 0;
let mut current_width = 0;
for b in s.bytes() {
if b == b'\n' {
height += 1;
max_width = max_width.max(current_width);
current_width = 0;
} else {
current_width += 1;
}
}
max_width = max_width.max(current_width);
(max_width, height)
}
pub enum Event {
Key(crossterm::event::KeyEvent),
Resize(u16, u16),
Mouse(crossterm::event::MouseEvent),
Paste(String),
FocusGained,
FocusLost,
}
pub fn read() -> crate::Result<Event> {
let event = crossterm::event::read()?;
Ok(match event {
crossterm::event::Event::Key(k) => Event::Key(k),
crossterm::event::Event::Resize(w, h) => Event::Resize(w, h),
crossterm::event::Event::FocusGained => Event::FocusGained,
crossterm::event::Event::FocusLost => Event::FocusLost,
crossterm::event::Event::Mouse(m) => Event::Mouse(m),
crossterm::event::Event::Paste(p) => Event::Paste(p),
})
} }
+7
View File
@@ -0,0 +1,7 @@
use crate::Widget;
impl Widget for &str {
fn render(&self) -> String {
self.to_string()
}
}