slightly improved structure and added a few more elements

This commit is contained in:
2025-06-27 12:10:25 -05:00
parent dfb17ab48e
commit 15b61d57c8
11 changed files with 242 additions and 14 deletions
+91
View File
@@ -0,0 +1,91 @@
use std::sync::{Arc, Mutex};
use crossterm::event::KeyEvent;
use crate::{
event, event_handler, events::EventManager, extensions::keypress::KeyPressEventHandler, Element,
};
pub struct Input(String, usize, Arc<Mutex<EventManager>>);
impl Input {
pub fn new() -> Input {
Input(String::new(), 0, Arc::new(Mutex::new(EventManager::new())))
}
pub fn get_event(&self) -> Arc<Mutex<EventManager>> {
self.2.clone()
}
}
impl Element for Input {
fn init(&mut self, events: &mut crate::events::EventManager) {
event_handler!(Self, self, events, on_keypress);
}
fn render(&mut self, scope: &mut crate::render_scope::RenderScope) {
let mut s = self.0.clone();
if self.1 < self.0.len() {
s.insert_str(self.1 + 1, "\x1b[0m");
s.insert_str(self.1, "\x1b[42m");
} else {
s += "\x1b[42m \x1b[0m";
}
scope.draw_text(&s);
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl KeyPressEventHandler for Input {
fn on_keypress(&mut self, event: Box<KeyEvent>) {
match event.code {
crossterm::event::KeyCode::Char(c) => {
self.0.insert(self.1, c);
self.1 += 1;
self.2
.lock()
.unwrap()
.dispatch(InputUpdateEvent(self.0.clone()));
}
crossterm::event::KeyCode::Backspace => {
if self.1 > 0 {
self.0.remove(self.1 - 1);
self.1 -= 1;
self.2
.lock()
.unwrap()
.dispatch(InputUpdateEvent(self.0.clone()));
}
}
crossterm::event::KeyCode::Left => {
if self.1 > 0 {
self.1 -= 1;
self.2
.lock()
.unwrap()
.dispatch(InputUpdateEvent(self.0.clone()));
}
}
crossterm::event::KeyCode::Right => {
if self.1 < self.0.len() {
self.1 += 1;
self.2
.lock()
.unwrap()
.dispatch(InputUpdateEvent(self.0.clone()));
}
}
_ => {}
}
}
}
event!(InputUpdateEvent(pub String));
+3
View File
@@ -0,0 +1,3 @@
pub mod rect;
pub mod text;
pub mod input;
+20
View File
@@ -0,0 +1,20 @@
use crate::Element;
pub struct Rect(pub u32);
impl Element for Rect {
fn render(&mut self, scope: &mut crate::render_scope::RenderScope) {
let (w, h) = scope.get_size();
if w > 0 && h > 0 {
scope.draw_rect(w, h, self.0);
}
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
+6 -2
View File
@@ -5,7 +5,11 @@ impl Element for String {
scope.draw_text(self);
}
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
Box::new(self)
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
+9 -6
View File
@@ -1,16 +1,19 @@
use crossterm::event::{KeyCode, KeyModifiers};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crate::{
event,
events::{Close, EventManager},
events::{Close, Event, EventManager},
extensions::Extension,
Screen,
};
event!(KeyPress(pub crossterm::event::KeyEvent));
impl Event for KeyEvent {
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
}
pub trait KeyPressEventHandler {
fn on_keypress(&mut self, event: KeyPress);
fn on_keypress(&mut self, event: Box<KeyEvent>);
}
pub struct KeyPressExtension;
@@ -36,7 +39,7 @@ impl Extension for KeyPressExtension {
if e.modifiers.contains(KeyModifiers::CONTROL) && e.code == KeyCode::Char('c') {
events.dispatch(Close);
} else {
events.dispatch(KeyPress(e));
events.dispatch(e);
}
}
_ => {}
+8 -2
View File
@@ -10,12 +10,12 @@ use crate::{
style::Transform,
};
pub mod element;
pub mod events;
pub mod extensions;
pub mod macros;
pub mod render_scope;
pub mod style;
pub mod text;
pub mod utils;
pub trait Element {
@@ -23,7 +23,8 @@ pub trait Element {
fn render(&mut self, scope: &mut RenderScope) {}
#[allow(unused)]
fn init(&mut self, events: &mut EventManager) {}
fn as_any(self: Box<Self>) -> Box<dyn Any>;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
pub trait Component {
@@ -94,7 +95,12 @@ impl Screen {
utils::clear().unwrap();
for elem in &mut self.elements {
scope.clear();
if let Some(t) = elem.get() {
scope.set_transform(t);
}
elem.0.render(&mut scope);
if let Some(t) = elem.get() {
scope.set_transform(t);
}
+37
View File
@@ -85,3 +85,40 @@ macro_rules! component {
}
};
}
/// Macro to create an event handler closure that calls a method on `self`
///
/// This macro automates the pattern of capturing `self` as a raw pointer,
/// then passing an event to a method on `self` inside an unsafe closure.
///
/// # Parameters
/// - `$self_ty`: The self type.
/// - `$self`: The instance variable (usually `self`) to call the method on.
/// - `$events`: The event source which has an `.on` method to register the handler.
/// - `$method`: The method name on `self` to call when an event occurs.
///
/// # Usage
/// ```rust
/// event_handler!(self, events, on_keypress);
/// ```
///
/// This expands roughly to:
/// ```rust
/// let self_ref = self as *mut Self;
/// events.on(move |event| unsafe { (*self_ref).on_keypress(event) });
/// ```
///
/// # Safety
/// This macro uses `unsafe` code because it dereferences a raw pointer inside the closure.
/// Ensure that the `self` reference lives at least as long as the closure to avoid undefined behavior.
///
/// # Why use raw pointers here?
/// Often, event handlers require `'static` closures, but `self` is a stack reference.
/// Capturing `self` directly is not possible, so this workaround uses a raw pointer.
#[macro_export]
macro_rules! event_handler {
($self_ty:ty, $self:ident, $events:ident, $method:ident) => {{
let self_ref = $self as *mut $self_ty;
$events.on(move |e| unsafe { (*self_ref).$method(e) });
}};
}
+18 -3
View File
@@ -1,4 +1,5 @@
use osui::{
element::input::{Input, InputUpdateEvent},
events::EventManager,
extensions::{keypress::KeyPressExtension, ExtensionManager},
style::Transform,
@@ -11,9 +12,23 @@ fn main() {
let mut events = EventManager::new();
extensions.add(KeyPressExtension);
screen
.draw("Hello, World".to_string())
.component(Transform::center());
let widget = screen.draw(Input::new()).component(Transform::center());
let input_event = widget
.0
.as_any()
.downcast_ref::<Input>()
.unwrap()
.get_event();
input_event
.lock()
.unwrap()
.on(|event: Box<InputUpdateEvent>| {
if event.0 == "quit" {
std::process::exit(0)
}
});
screen.run(&mut events, &mut extensions).unwrap();
}
+30 -1
View File
@@ -1,10 +1,11 @@
use crate::{
style::{RawTransform, Transform},
utils,
utils::{self, hex_ansi_bg},
};
enum RenderMethod {
Text(String),
Rectangle(u16, u16, u32),
}
pub struct RenderScope {
@@ -37,17 +38,45 @@ impl RenderScope {
self.transform.height = self.transform.height.max(h);
}
pub fn draw_rect(&mut self, width: u16, height: u16, color: u32) {
self.render_stack
.push(RenderMethod::Rectangle(width, height, color));
self.transform.width = self.transform.width.max(width);
self.transform.height = self.transform.height.max(height);
}
pub fn draw(&self) {
for m in &self.render_stack {
match m {
RenderMethod::Text(t) => {
println!("\x1b[{};{}H{t}", self.transform.y + 1, self.transform.x + 1)
}
RenderMethod::Rectangle(width, height, color) => {
for i in 0..(*height) {
println!(
"\x1b[{};{}H{}{}\x1b[0m",
self.transform.y + 1 + i,
self.transform.x + 1,
hex_ansi_bg(*color),
" ".repeat(*width as usize)
)
}
}
}
}
}
pub fn clear(&mut self) {
self.render_stack.clear();
self.transform.width = 0;
self.transform.height = 0;
}
pub fn get_size(&self) -> (u16, u16) {
(self.transform.width, self.transform.height)
}
pub fn get_parent_size(&self) -> (u16, u16) {
(self.parent_width, self.parent_height)
}
}
+6
View File
@@ -54,6 +54,12 @@ impl Transform {
}
}
pub fn dimensions(&mut self, width: u16, height: u16) -> Self {
self.width = Dimension::Const(width);
self.height = Dimension::Const(height);
self.clone()
}
pub fn use_dimensions(&self, raw: &mut RawTransform) {
self.width.use_dimension(&mut raw.width);
self.height.use_dimension(&mut raw.height);
+14
View File
@@ -92,3 +92,17 @@ pub fn str_size(s: &str) -> (u16, u16) {
(max_width, height)
}
pub fn hex_ansi(hex: u32) -> String {
let r = ((hex >> 16) & 0xFF) as u8;
let g = ((hex >> 8) & 0xFF) as u8;
let b = (hex & 0xFF) as u8;
format!("\x1b[38;2;{r};{g};{b}m")
}
pub fn hex_ansi_bg(hex: u32) -> String {
let r = ((hex >> 16) & 0xFF) as u8;
let g = ((hex >> 8) & 0xFF) as u8;
let b = (hex & 0xFF) as u8;
format!("\x1b[48;2;{r};{g};{b}m")
}