This commit is contained in:
2025-01-22 15:41:51 +01:00
parent 261ebce4dc
commit 42db070f48
7 changed files with 567 additions and 21 deletions
+13
View File
@@ -0,0 +1,13 @@
use crate::Element;
impl Element for &str {
fn render(&self) -> String {
self.to_string()
}
}
impl Element for String {
fn render(&self) -> String {
self.to_owned()
}
}
+98
View File
@@ -0,0 +1,98 @@
pub mod elements;
pub mod utils;
pub use std::io::Result;
use std::sync::Arc;
pub mod prelude {
pub use crate::{utils::init, Props, Result, Ui};
}
// Traits
pub trait Framing {
fn draw(&mut self, element: &dyn Element, props: &Props);
}
pub trait Element {
fn render(&self) -> String;
}
// Types
pub type Ui = Arc<dyn Fn(&mut dyn Framing)>;
pub enum Position {
Num(u16),
Center,
}
pub struct Props(Position, Position);
/// Represents the console state, containing a frame for rendering and a mouse capture flag.
pub struct Console {
mouse: bool,
width: u16,
height: u16,
}
impl Position {
pub fn get(&self, written: u16, frame: u16) -> u16 {
match self {
Self::Center => (frame - written) / 2,
Self::Num(n) => *n,
}
}
}
impl Props {
pub fn new(x: u16, y: u16) -> Self {
Self(Position::Num(x), Position::Num(y))
}
pub fn center() -> Self {
Self(Position::Center, Position::Center)
}
pub fn center_horizontal() -> Self {
Self(Position::Center, Position::Num(0))
}
pub fn center_vertical() -> Self {
Self(Position::Num(0), Position::Center)
}
}
impl Framing for Console {
fn draw(&mut self, element: &dyn Element, props: &Props) {
let written = element.render();
let (written_width, written_height) = utils::str_size(&written);
let (x, y) = (
props.0.get(written_width, self.width),
props.1.get(written_height, self.height),
);
for (i, line) in written.lines().enumerate() {
println!("\x1b[{};{}H{}", y as usize + i + 1, x + 1, line);
}
}
}
impl Console {
pub fn render(&mut self, ui: Ui) -> Result<()> {
utils::clear()?;
(self.width, self.height) = crossterm::terminal::size()?;
ui(self);
Ok(())
}
pub fn close(self) -> Result<()> {
if self.mouse {
crossterm::execute!(std::io::stdout(), crossterm::event::DisableMouseCapture)?;
}
crossterm::terminal::disable_raw_mode()?;
utils::show_cursor()?;
utils::clear()
}
}
+19
View File
@@ -0,0 +1,19 @@
use std::sync::Arc;
use osmium_engine::prelude::*;
fn main() -> Result<()> {
let mut console = init(false)?;
console.render(app())?;
crossterm::event::read()?;
console.close()
}
pub fn app() -> Ui {
Arc::new(|frame| {
frame.draw(&"Hello, World", &Props::center());
})
}
+116
View File
@@ -0,0 +1,116 @@
//! The `utils` module provides utility functions for terminal manipulation
//! and string operations, designed to enhance terminal-based applications.
use std::io::Write;
/// Clears the terminal screen and moves the cursor to the top-left corner.
///
/// # Returns
/// A `crate::Result<()>` indicating whether the operation succeeded.
///
/// # Example
/// ```
/// utils::clear().unwrap();
/// ```
pub fn clear() -> crate::Result<()> {
print!("\x1B[2J\x1B[H");
std::io::stdout().flush()
}
/// Hides the terminal cursor.
///
/// # Returns
/// A `crate::Result<()>` indicating whether the operation succeeded.
///
/// # Example
/// ```
/// utils::hide_cursor().unwrap();
/// ```
pub fn hide_cursor() -> crate::Result<()> {
print!("\x1b[?25l");
std::io::stdout().flush()
}
/// Shows the terminal cursor.
///
/// # Returns
/// A `crate::Result<()>` indicating whether the operation succeeded.
///
/// # Example
/// ```
/// utils::show_cursor().unwrap();
/// ```
pub fn show_cursor() -> crate::Result<()> {
print!("\x1B[?25h");
std::io::stdout().flush()
}
/// Flushes the terminal's stdout buffer.
///
/// # Returns
/// A `crate::Result<()>` indicating whether the operation succeeded.
///
/// # Example
/// ```
/// utils::flush().unwrap();
/// ```
pub fn flush() -> crate::Result<()> {
std::io::stdout().flush()
}
/// Calculates the width and height of a string when rendered in a terminal.
///
/// # Arguments
/// - `s`: The input string.
///
/// # Returns
/// A tuple `(u16, u16)` where:
/// - The first value is the maximum width of the string in characters.
/// - The second value is the height of the string in lines.
///
/// # Example
/// ```
/// let (width, height) = utils::str_size("Hello\nWorld!");
/// assert_eq!((5, 2), (width, height));
/// ```
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)
}
/// Initializes the console with raw mode enabled and optionally mouse capture.
///
/// # Arguments
/// * `mouse` - A boolean flag indicating whether to enable mouse capture.
///
/// # Returns
/// A `Console` instance wrapped in a `Result`.
pub fn init(mouse: bool) -> crate::Result<crate::Console> {
crossterm::terminal::enable_raw_mode()?;
clear()?;
hide_cursor()?;
if mouse {
crossterm::execute!(std::io::stdout(), crossterm::event::EnableMouseCapture)?;
}
let (width, height) = crossterm::terminal::size()?;
Ok(crate::Console {
width,
height,
mouse,
})
}