From 42db070f48d6fa8a736763571312659dc8aebfec Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 22 Jan 2025 15:41:51 +0100 Subject: [PATCH] engine --- .gitignore | 22 +--- Cargo.lock | 300 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 20 ++++ src/elements.rs | 13 +++ src/lib.rs | 98 ++++++++++++++++ src/main.rs | 19 +++ src/utils.rs | 116 +++++++++++++++++++ 7 files changed, 567 insertions(+), 21 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/elements.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/utils.rs diff --git a/.gitignore b/.gitignore index d01bd1a..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b56168b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,300 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "osmium-engine" +version = "0.0.1-alpha.0" +dependencies = [ + "crossterm", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a8685f3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "osmium-engine" +version = "0.0.1-alpha.0" +edition = "2021" +license = "Apache-2.0" +description = "A TUI library for advanced uis" +repository = "https://github.com/osui-rs/engine" +homepage = "https://osui.netlify.app" +documentation = "https://osui.netlify.app/docs/" + +[lib] +name = "osmium_engine" +path = "src/lib.rs" + +[[bin]] +name = "osmium_engine" +path = "src/main.rs" + +[dependencies] +crossterm = "0.28.1" \ No newline at end of file diff --git a/src/elements.rs b/src/elements.rs new file mode 100644 index 0000000..6cbc310 --- /dev/null +++ b/src/elements.rs @@ -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() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2696da9 --- /dev/null +++ b/src/lib.rs @@ -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; + +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() + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..441f923 --- /dev/null +++ b/src/main.rs @@ -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()); + }) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..2b2aa5f --- /dev/null +++ b/src/utils.rs @@ -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 { + 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, + }) +}