Merge pull request #4 from osui-rs/kleo

Kleo
This commit is contained in:
Leo Codes
2024-11-26 12:17:29 +01:00
committed by GitHub
20 changed files with 1096 additions and 803 deletions
Vendored
BIN
View File
Binary file not shown.
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "osui"
version = "0.0.5"
version = "0.0.6"
edition = "2021"
license = "Apache-2.0"
description = "A rsx TUI library"
@@ -22,5 +22,6 @@ crossterm = "0.28.1"
regex = "1.11.1"
lazy_static = "1.4"
paste = "1.0.15"
osui-element = "0.1.0"
dyn-clone = "1.0.17"
flume = "=0.10.12"
osui-element = "0.1.11"
+5 -6
View File
@@ -1,7 +1,9 @@
<p align="center">
<img src="github/OSUI-cropped.png" alt="OSUI" width="200px"/>
<img src="github/OSUIw.svg" alt="OSUI" width="200px"/>
</p>
<h1 align="center">osui</h1>
<p align="center">
<a href="https://crates.io/crates/osui">
<img alt="Crates.io Version" src="https://img.shields.io/crates/v/osui?style=flat">
@@ -28,11 +30,8 @@
## Getting Started
To use OSUI in your project, include it in your `Cargo.toml`:
```toml
[dependencies]
osui = "0.2" # Replace with the latest version
```bash
cargo add osui
```
## Example Usage
-4
View File
@@ -1,4 +0,0 @@
fn main() {
cc::Build::new()
.file("src/app.c").compile("app");
}
-4
View File
@@ -1,4 +0,0 @@
rm -rf ./lib/
mkdir lib
gcc -c -o lib/app.o src/app.c
ar rcs lib/libapp.a lib/app.o
+90
View File
@@ -0,0 +1,90 @@
<svg width="153" height="175" viewBox="0 0 153 175" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3_9)">
<path d="M5.02181 46.0207L76 5.04145L146.978 46.0207V127.979L76 168.959L5.02181 127.979V46.0207Z" stroke="#19FDE9" stroke-width="7"/>
<circle cx="76" cy="86" r="19" fill="#19FDE9"/>
<g filter="url(#filter0_d_3_9)">
<path d="M95.3551 111.773C88.353 117.244 80.8934 121.965 73.4125 125.669C65.9271 129.375 58.6068 131.97 51.8773 133.341C45.1345 134.715 39.2222 134.812 34.4274 133.77C29.6586 132.734 26.1648 130.615 23.8944 127.709C21.6241 124.803 20.4134 120.901 20.5616 116.023C20.7106 111.118 22.2351 105.405 25.1995 99.1951C28.1579 92.9973 32.4472 86.5222 37.854 80.1558C43.2575 73.7931 49.6429 67.6972 56.645 62.2266" stroke="#19FDE9" stroke-width="6"/>
</g>
<g filter="url(#filter1_d_3_9)">
<path d="M128.106 127.709C123.331 133.821 113.472 136.061 100.123 133.341C86.9733 130.662 71.4 123.301 56.6449 111.773C41.8898 100.245 30.9803 86.9154 25.1995 74.8049C19.3308 62.5103 19.1196 52.4022 23.8945 46.2907C28.6693 40.1792 38.5282 37.9387 51.8774 40.6587C65.0267 43.338 80.6 50.6987 95.3551 62.2266C110.11 73.7546 121.02 87.0846 126.801 99.1951C132.669 111.49 132.88 121.598 128.106 127.709Z" stroke="#19FDE9" stroke-width="6" shape-rendering="crispEdges"/>
</g>
<g filter="url(#filter2_d_3_9)">
<path d="M56.6449 62.2266C63.647 56.756 71.1066 52.035 78.5875 48.3313C86.0729 44.6253 93.3932 42.0299 100.123 40.6587C106.866 39.2848 112.778 39.1877 117.573 40.2297C122.341 41.2659 125.835 43.3848 128.106 46.2907C130.376 49.1966 131.587 53.0993 131.438 57.9771C131.289 62.8815 129.765 68.5947 126.801 74.8049C123.842 81.0027 119.553 87.4777 114.146 93.8442C108.743 100.207 102.357 106.303 95.3551 111.773" stroke="#19FDE9" stroke-width="6"/>
</g>
<path d="M51.0666 66.83C58.4441 60.4017 66.5619 54.7502 74.8433 50.2708C83.1297 45.7886 91.3332 42.6131 98.8706 40.9264C106.427 39.2355 112.996 39.1113 118.187 40.3708C123.331 41.6189 126.928 44.1667 129.052 47.661C131.198 51.1904 132.007 55.9597 131.058 61.8603C130.109 67.768 127.44 74.4991 123.104 81.5783C118.776 88.644 112.948 95.7948 106.005 102.513C99.0657 109.23 91.2119 115.321 82.9997 120.358" stroke="#19FDE9" stroke-width="6"/>
<g filter="url(#filter3_d_3_9)">
<circle cx="126" cy="44" r="6" fill="#19FDE9"/>
</g>
<g filter="url(#filter4_d_3_9)">
<circle cx="97" cy="64" r="6" fill="#19FDE9"/>
</g>
<g filter="url(#filter5_d_3_9)">
<circle cx="130" cy="122" r="6" fill="#19FDE9"/>
</g>
</g>
<defs>
<filter id="filter0_d_3_9" x="13.5495" y="59.8625" width="87.6526" height="85.61" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.42 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter1_d_3_9" x="14.2338" y="36.5211" width="123.532" height="108.258" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="1.65"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter2_d_3_9" x="50.7979" y="36.5275" width="87.6526" height="85.6099" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.53 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter3_d_3_9" x="116" y="38" width="20" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.34 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter4_d_3_9" x="87" y="58" width="20" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.34 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter5_d_3_9" x="120" y="116" width="20" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.34 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<clipPath id="clip0_3_9">
<rect width="153" height="175" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+90
View File
@@ -0,0 +1,90 @@
<svg width="153" height="175" viewBox="0 0 153 175" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3_9)">
<path d="M5.02181 46.0207L76 5.04145L146.978 46.0207V127.979L76 168.959L5.02181 127.979V46.0207Z" stroke="white" stroke-width="7"/>
<circle cx="76" cy="86" r="19" fill="#19FDE9"/>
<g filter="url(#filter0_d_3_9)">
<path d="M95.3551 111.773C88.353 117.244 80.8934 121.965 73.4125 125.669C65.9271 129.375 58.6068 131.97 51.8773 133.341C45.1345 134.715 39.2222 134.812 34.4274 133.77C29.6586 132.734 26.1648 130.615 23.8944 127.709C21.6241 124.803 20.4134 120.901 20.5616 116.023C20.7106 111.118 22.2351 105.405 25.1995 99.1951C28.1579 92.9973 32.4472 86.5222 37.854 80.1558C43.2575 73.7931 49.6429 67.6972 56.645 62.2266" stroke="#19FDE9" stroke-width="6"/>
</g>
<g filter="url(#filter1_d_3_9)">
<path d="M128.106 127.709C123.331 133.821 113.472 136.061 100.123 133.341C86.9733 130.662 71.4 123.301 56.6449 111.773C41.8898 100.245 30.9803 86.9154 25.1995 74.8049C19.3308 62.5103 19.1196 52.4022 23.8945 46.2907C28.6693 40.1792 38.5282 37.9387 51.8774 40.6587C65.0267 43.338 80.6 50.6987 95.3551 62.2266C110.11 73.7546 121.02 87.0846 126.801 99.1951C132.669 111.49 132.88 121.598 128.106 127.709Z" stroke="#19FDE9" stroke-width="6" shape-rendering="crispEdges"/>
</g>
<g filter="url(#filter2_d_3_9)">
<path d="M56.6449 62.2266C63.647 56.756 71.1066 52.035 78.5875 48.3313C86.0729 44.6253 93.3932 42.0299 100.123 40.6587C106.866 39.2848 112.778 39.1877 117.573 40.2297C122.341 41.2659 125.835 43.3848 128.106 46.2907C130.376 49.1966 131.587 53.0993 131.438 57.9771C131.289 62.8815 129.765 68.5947 126.801 74.8049C123.842 81.0027 119.553 87.4777 114.146 93.8442C108.743 100.207 102.357 106.303 95.3551 111.773" stroke="#19FDE9" stroke-width="6"/>
</g>
<path d="M51.0666 66.83C58.4441 60.4017 66.5619 54.7502 74.8433 50.2708C83.1297 45.7886 91.3332 42.6131 98.8706 40.9264C106.427 39.2355 112.996 39.1113 118.187 40.3708C123.331 41.6189 126.928 44.1667 129.052 47.661C131.198 51.1904 132.007 55.9597 131.058 61.8603C130.109 67.768 127.44 74.4991 123.104 81.5783C118.776 88.644 112.948 95.7948 106.005 102.513C99.0657 109.23 91.2119 115.321 82.9997 120.358" stroke="#19FDE9" stroke-width="6"/>
<g filter="url(#filter3_d_3_9)">
<circle cx="126" cy="44" r="6" fill="#19FDE9"/>
</g>
<g filter="url(#filter4_d_3_9)">
<circle cx="97" cy="64" r="6" fill="#19FDE9"/>
</g>
<g filter="url(#filter5_d_3_9)">
<circle cx="130" cy="122" r="6" fill="#19FDE9"/>
</g>
</g>
<defs>
<filter id="filter0_d_3_9" x="13.5495" y="59.8625" width="87.6526" height="85.61" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.42 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter1_d_3_9" x="14.2338" y="36.5211" width="123.532" height="108.258" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="1.65"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter2_d_3_9" x="50.7979" y="36.5275" width="87.6526" height="85.6099" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.53 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter3_d_3_9" x="116" y="38" width="20" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.34 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter4_d_3_9" x="87" y="58" width="20" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.34 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<filter id="filter5_d_3_9" x="120" y="116" width="20" height="20" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.34 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3_9"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3_9" result="shape"/>
</filter>
<clipPath id="clip0_3_9">
<rect width="153" height="175" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

-32
View File
@@ -1,32 +0,0 @@
#include <stdio.h>
#include <memory.h>
#include <libc.h>
#include <pthread.h>
#include <time.h>
extern boolean_t render(void* element);
extern void *event_loop(void* element);
void sleep_ms(long milliseconds)
{
struct timespec ts;
ts.tv_sec = milliseconds / 1000;
ts.tv_nsec = (milliseconds % 1000) * 1000000;
nanosleep(&ts, NULL);
}
boolean_t c_run(void* element)
{
pthread_t thread;
if (pthread_create(&thread, NULL, event_loop, element) != 0)
{
perror("Failed to create thread");
}
while (render(element))
{
sleep_ms(20);
}
return 0;
}
-68
View File
@@ -1,68 +0,0 @@
use crossterm::{
event::Event,
terminal::{disable_raw_mode, enable_raw_mode},
};
use crate::{
utils::{clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, show_cursor},
Element,
};
use std::{ffi::c_void, ptr::null};
extern "C" {
fn c_run(element: *mut c_void) -> bool;
}
#[no_mangle]
extern "C" fn render(ptr: *mut c_void) -> bool {
if ptr.is_null() {
return false;
}
let element = unsafe { &mut *(ptr as *mut Element) };
let (width, height) = get_term_size();
let mut frame: Vec<String> = create_frame(width, height);
render_to_frame(true, width, &mut frame, element);
clear();
print!("{}", frame.join(""));
flush();
true
}
#[no_mangle]
extern "C" fn event_loop(ptr: *mut c_void) -> *const c_void {
if ptr.is_null() {
return null() as *const c_void;
}
let element = unsafe { &mut *(ptr as *mut Element) };
let (width, height) = get_term_size();
element.update_data(width, height);
loop {
if ptr.is_null() {
break;
}
event(element, crossterm::event::read().unwrap())
}
null() as *const c_void
}
fn event(element: &mut Element, event: Event) {
if let crossterm::event::Event::Resize(width, height) = event {
element.update_data(width as usize, height as usize);
}
element.event(event);
}
pub fn run(element: &mut Element) -> bool {
hide_cursor();
enable_raw_mode().unwrap();
clear();
unsafe {
let c = c_run(element as *mut Element as *mut c_void);
if !c {
show_cursor();
disable_raw_mode().unwrap();
clear();
}
return c;
}
}
+261
View File
@@ -0,0 +1,261 @@
#[macro_export]
macro_rules! __css {
($_style:expr,)=>{};
// percentage (n%)
(
$_style:expr, $name:ident: $value:literal% $(, $($other:tt)*)?
) => {{
$_style.$name = Number::Pe($value);
$crate::__css!($_style, $($($other)*)?);
}};
(
$_style:expr, ($name:ident): $value:literal% $(, $($other:tt)*)?
) => {{
$_style.other.insert(stringify!($name).to_string(), Box::new(
Number::Pe($value)
));
$crate::__css!($_style, $($($other)*)?);
}};
// Number
(
$_style:expr, $name:ident: $value:literal $(, $($other:tt)*)?
) => {{
$_style.$name = Number::Px($value);
$crate::__css!($_style, $($($other)*)?);
}};
// Vector
(
$_style:expr, $name:ident: [$($inner:tt),*] $(, $($other:tt)*)?
) => {{
$_style.$name = vec![$($inner),*];
$crate::__css!($_style, $($($other)*)?);
}};
// Normal
(
$_style:expr, $name:ident: $value:expr $(, $($other:tt)*)?
) => {{
$_style.$name = $value;
$crate::__css!($_style, $($($other)*)?);
}};
(
$_style:expr, ($name:ident): $value:expr $(, $($other:tt)*)?
) => {{
$_style.other.insert(stringify!($name).to_string(), Box::new($value));
$crate::__css!($_style, $($($other)*)?);
}};
}
#[macro_export]
macro_rules! _css {
($_style:expr,) => {};
(
$hm:expr, .$name:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Class(stringify!($name).to_string());
if let Some(_style) = $hm.get_mut(&name) {
$crate::__css!(_style.0, $($inner)*);
} else {
let mut _style = $crate::ui::Style::default();
$crate::__css!(_style.0, $($inner)*);
$hm.insert(
name,
_style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, .$name:ident:$kind:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Class(stringify!($name).to_string());
if let Some(_style) = $hm.get_mut(&name) {
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem);
} else {
let mut _style = $crate::ui::Style::default();
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem);
$hm.insert(
name,
_style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, #$name:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Id(stringify!($name).to_string());
if let Some(_style) = $hm.get_mut(&name) {
$crate::__css!(_style.0, $($inner)*);
} else {
let mut _style = $crate::ui::Style::default();
$crate::__css!(_style.0, $($inner)*);
$hm.insert(
name,
_style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, #$name:ident:$kind:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Id(stringify!($name).to_string());
if let Some(_style) = $hm.get_mut(&name) {
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem);
} else {
let mut _style = $crate::ui::Style::default();
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem);
$hm.insert(
name,
_style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, $name:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Component(stringify!($name).to_string());
if let Some(_style) = $hm.get_mut(&name) {
$crate::__css!(_style.0, $($inner)*);
} else {
let mut _style = $crate::ui::Style::default();
$crate::__css!(_style.0, $($inner)*);
$hm.insert(
name,
_style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, $name:ident:$kind:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Component(stringify!($name).to_string());
if let Some(_style) = $hm.get_mut(&name) {
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem);
} else {
let mut _style = $crate::ui::Style::default();
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem);
$hm.insert(
name,
_style,
);
}
$crate::_css!($hm, $($rest)*);
}};
}
/// Creates a styling map `Css`
///
/// # Examples
/// ```
/// .btn: clicked {
/// color: Blue
/// }
/// ```
///
/// # Returns
/// `Css`
#[macro_export]
macro_rules! css {
(
$($inner:tt)*
) => {{
let mut hm: $crate::ui::Css = std::collections::HashMap::new();
$crate::_css!(hm, $($inner)*);
hm
}};
}
#[macro_export]
macro_rules! style {
(
$key:ident: $value:expr $(, $($rest:tt)*)?
) => {{
let mut _style = $crate::ui::Style::default();
$crate::__css!(_style.0, $key: $value);
$crate::style!{ _style; $($($rest)*)? }
}};
(
$style:expr; $key:ident: $value:expr $(, $($rest:tt)*)?
) => {{
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!($style.0, $key: $value);
$crate::style!{ $style; $($($rest)*)? }
}};
(
($key:ident): $value:expr $(, $($rest:tt)*)?
) => {{
let mut _style = $crate::ui::Style::default();
$crate::__css!(_style.0, ($key): $value);
$crate::style!{ _style; $($($rest)*)? }
}};
(
$style:expr; ($key:ident): $value:expr $(, $($rest:tt)*)?
) => {{
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!($style.0, ($key): $value);
$crate::style!{ $style; $($($rest)*)? }
}};
(
$kind:ident {$($inner:tt)*} $(, $($rest:tt)*)?
) => {{
let mut _style = $crate::ui::Style::default();
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem);
$crate::style!{ _style; $($($rest)*)? }
}};
(
$style:expr; $kind:ident {$($inner:tt)*} $(, $($rest:tt)*)?
) => {{
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
$style.1.insert(stringify!($kind).to_string(), style_elem);
$crate::style!{ $style; $($($rest)*)? }
}};
(
$style:expr;
) => {{
$style
}};
() => {{
$crate::ui::Style::default()
}};
}
+25
View File
@@ -0,0 +1,25 @@
use crate::prelude::*;
pub fn examples() -> Element {
rsx! {
styling: Some(styling()),
button { class: "btn", "Click me!" }
text { "Welcome!" }
}
}
pub fn styling() -> Css {
css! {
.btn {
x: Center,
y: Center,
color: Red,
}
.btn: clicked {
color: Blue,
}
}
}
+199 -85
View File
@@ -10,13 +10,12 @@
//! ```rust
//! use osui::prelude::*;
//!
//! run(&mut rsx! {
//! launch(rsx! {
//! text { "Hello, World!" }
//! });
//! ```
//!
//! ## Modules
//! - `app` - The main function for rendering.
//! - `ui` - Contains all user interface components, enabling rich CLI experiences.
//! - `utils` - Utility functions for common TUI tasks such as clearing the screen.
@@ -26,24 +25,20 @@ use std::{
sync::{Arc, Mutex},
};
use crossterm::event::Event;
pub mod app;
pub mod css;
pub mod examples;
pub mod macros;
pub mod rsx;
pub mod ui;
pub mod utils;
pub use app::run;
/// The `prelude` module provides commonly used imports and macros to simplify the development
/// of UI elements. It re-exports commonly used traits, structs, and utility functions to make
/// the UI framework easier to use.
pub mod prelude {
pub use crate::ui::Color::*;
pub use crate::{self as osui, css, rsx, rsx_elem, ui::*, Handler};
pub use crate::{Element, Value};
// useful for Element making
pub use crate::{run_handler, Children, ElementCore, ElementWidget};
pub use crate::ui::Number::*;
pub use crate::ui::Font::*;
pub use crate::{self as osui, css, ersx, launch, rsx, ui::*, Handler};
pub use crate::{call, Children, Element, ElementCore, ElementWidget};
pub use crate::{style, Document, RenderResult, RenderWriter};
pub use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
pub fn sleep(ms: u64) {
std::thread::sleep(std::time::Duration::from_millis(ms));
@@ -51,90 +46,53 @@ pub mod prelude {
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Value Enum
// Type aliases
//////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, Copy)]
pub enum Value<T: Copy + PartialEq> {
Default(T),
Custom(T),
}
impl<T: Copy + PartialEq> Value<T> {
pub fn get_value(&self) -> T {
match self {
Value::Custom(s) => *s,
Value::Default(s) => *s,
}
}
pub fn try_set_value(&mut self, value: T) {
if let Value::Default(_) = *self {
*self = Value::Default(value);
}
}
}
impl<T: Copy + PartialEq + Default> Default for Value<T> {
fn default() -> Self {
Self::Default(T::default())
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// ElementCore Traits
//////////////////////////////////////////////////////////////////////////////////////////////////
pub trait ElementCore: Send + Sync {
fn get_data(&self) -> (Value<usize>, usize, String);
fn update_data(&mut self, width: usize, height: usize);
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>;
fn get_child(&mut self) -> Option<&mut Element>;
fn set_styling(&mut self, styling: &HashMap<crate::ui::StyleName, crate::ui::Style>);
}
pub trait ElementWidget: ElementCore + std::fmt::Debug {
fn render(&self, focused: bool) -> String {
_ = focused;
String::new()
}
fn event(&mut self, event: Event) {
_ = event
}
}
pub type Element = Box<dyn ElementWidget>;
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Handler Struct
/// Traits
//////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Handler<T>(pub Arc<Mutex<dyn FnMut(&mut T, Event) + Send + Sync>>);
impl<T> Handler<T> {
pub fn new<F>(handler_fn: F) -> Handler<T>
where
F: FnMut(&mut T, Event) + 'static + Send + Sync,
{
Handler(Arc::new(Mutex::new(handler_fn)))
}
pub trait ElementCore {
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>;
fn set_styling(&mut self, styling: &HashMap<crate::ui::StyleName, crate::ui::Style>);
fn get_id(&self) -> String;
}
impl<T> Default for Handler<T> {
fn default() -> Self {
Handler(Arc::new(Mutex::new(|_: &mut T, _: Event| {})))
}
}
impl<T> std::fmt::Debug for Handler<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Handler()")
pub trait ElementWidget: ElementCore + std::fmt::Debug + Send + Sync {
fn render(&self, focused: bool) -> Option<RenderResult>;
fn event(&mut self, event: crossterm::event::Event, document: &Document) {
_ = (event, document)
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Children Enum
/// Structs
//////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Handler<T>(
pub Arc<Mutex<dyn FnMut(&mut T, crossterm::event::Event, &Document) + Send + Sync>>,
);
pub struct Document {
element: *mut Element,
running: *mut Option<bool>,
}
/// ```
/// RenderResult(output, (x, y))
/// ```
pub struct RenderResult(String, ui::StyleElement);
pub struct RenderWriter {
w: String,
style: ui::Style,
focused: bool,
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Enums
//////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
@@ -144,6 +102,57 @@ pub enum Children {
Children(Vec<Element>, usize),
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Implementations
//////////////////////////////////////////////////////////////////////////////////////////////////
impl Document {
pub fn exit(&self) {
if !self.running.is_null() {
unsafe {
*self.running = Some(false);
}
}
}
pub fn rebuild(&self) {
if !self.running.is_null() {
unsafe {
*self.running = Some(true);
}
}
}
pub fn get_element_by_id<T>(&self, id: &str) -> Option<&mut Box<T>> {
if let Some(e) = self.get_element_by_id_raw(id) {
Some(unsafe { &mut *(e as *mut Element as *mut Box<T>) })
} else {
None
}
}
pub fn get_element_by_id_raw(&self, id: &str) -> Option<&mut Element> {
if !self.element.is_null() {
unsafe {
if (*self.element).get_id() == id {
Some(&mut *self.element)
} else {
(*self.element).get_element_by_id(id)
}
}
} else {
None
}
}
pub fn render(&self) {
if !self.running.is_null() {
let (width, height) = utils::get_term_size();
let mut frame = utils::Frame::new(width, height);
frame.render(true, unsafe { &*self.element });
utils::clear();
print!("{}", frame.output_nnl());
utils::flush();
}
}
}
impl Default for Children {
fn default() -> Self {
Self::None
@@ -163,4 +172,109 @@ impl Children {
_ => String::new(),
}
}
pub fn get_text_mut(&mut self) -> Option<&mut String> {
match self {
Children::Text(text) => Some(text),
_ => None,
}
}
pub fn set_text(&mut self, text: &str) {
match self {
Children::Text(t) => {
*t = text.to_string();
}
_ => {}
}
}
pub fn set_text_force(&mut self, text: &str) {
match self {
Children::Text(t) => {
*t = text.to_string();
}
_ => {
*self = Children::Text(text.to_string());
}
}
}
pub fn add_child(&mut self, element: Element) {
if let Children::Children(children, _) = self {
children.push(element);
}
}
pub fn get_child(&mut self) -> Option<&mut Element> {
if let Children::Children(children, child) = self {
children.get_mut(*child)
} else {
None
}
}
}
impl<T> Handler<T> {
pub fn new<F>(handler_fn: F) -> Handler<T>
where
F: FnMut(&mut T, crossterm::event::Event, &Document) + 'static + Send + Sync,
{
Handler(Arc::new(Mutex::new(handler_fn)))
}
}
impl RenderWriter {
pub fn new(focused: bool, style: ui::Style) -> RenderWriter {
RenderWriter {
w: String::new(),
style,
focused,
}
}
pub fn write(&mut self, s: &str) {
self.w += self.style.get(self.focused).write(s).as_str();
}
pub fn result(&self) -> RenderResult {
RenderResult(self.w.clone(), self.style.get(self.focused).clone())
}
}
impl<T> Default for Handler<T> {
fn default() -> Self {
Handler(Arc::new(Mutex::new(
|_: &mut T, _: crossterm::event::Event, _: &Document| {},
)))
}
}
impl<T> std::fmt::Debug for Handler<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Handler()")
}
}
pub fn run(element: &mut Element) -> bool {
let document = Document {
element: element,
running: &mut None,
};
let (width, height) = crate::utils::get_term_size();
element.event(
crossterm::event::Event::Resize(width as u16, height as u16),
&document,
);
element.event(crossterm::event::Event::FocusGained, &document);
utils::hide_cursor();
crossterm::terminal::enable_raw_mode().unwrap();
utils::clear();
while unsafe { *document.running } == None {
document.render();
element.event(crossterm::event::read().unwrap(), &document);
}
utils::show_cursor();
crossterm::terminal::disable_raw_mode().unwrap();
utils::clear();
unsafe { (*document.running).unwrap() }
}
+5 -325
View File
@@ -1,171 +1,5 @@
/// **parse_rsx_param!** - A macro for parsing and setting parameters on elements, allowing for dynamic handling of element properties and children.
///
/// This macro enables flexible configuration of UI elements. It handles setting fields, adding children, invoking paths, and processing complex structures.
///
/// ## Example
/// ```rust
/// parse_rsx_param!(elem, title: "Hello", for (i in 0..5) { <child> }, text: "World");
/// ```
///
/// ## Syntax
///
/// **Set property or field of an element:**
/// ```rust
/// parse_rsx_param!($elem, $($k:ident).+: $v:expr);
/// ```
///
/// **Process multiple parameters:**
/// ```rust
/// parse_rsx_param!($elem, $($k:ident).+: $v:expr, $($rest:tt)*);
/// ```
///
/// **Handle path expression:**
/// ```rust
/// parse_rsx_param!($elem, $p:path);
/// ```
///
/// **Add nested children:**
/// ```rust
/// parse_rsx_param!($elem, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*);
/// ```
///
/// **Add child element directly:**
/// ```rust
/// parse_rsx_param!($elem, {$ielem:expr} $($rest:tt)*);
/// ```
///
/// **Set element text:**
/// ```rust
/// parse_rsx_param!($elem, $text:expr);
/// ```
///
/// **Handle empty case:**
/// ```rust
/// parse_rsx_param!($elem, );
/// ```
#[macro_export]
macro_rules! parse_rsx_param {
($elem:expr, $($k:ident).+: $v:expr) => {
$elem.$($k).+ = $v;
};
($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => {
$elem.$($k).+ = $v;
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $p:path) => {
$p;
};
($elem:expr, $($k:ident).+, $($rest:tt)*) => {
$elem.$($k).+;
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $($k:ident).+., $($rest:tt)*) => {
$elem.$($k).
+.;
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $($k:ident).+.: $v:expr) => {
$elem.$($k).+. = $v;
};
($elem:expr, $($k:ident).+.: $v:expr, $($rest:tt)*) => {
$elem.$($k).+. = $v;
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, for ($($for:tt)*) { $($inner:tt)* } $($rest:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
if let $crate::Children::Children(children, _) = &mut $elem.children {
for $($for)* {
children.push({$($inner)*})
}
}
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, {$ielem:expr} $($rest:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
if let $crate::Children::Children(children, _) = &mut $elem.children {
children.push({$ielem});
}
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
if let $crate::Children::Children(children, _) = &mut $elem.children {
children.push(osui::rsx_elem!($pelem { $($inner)* }));
}
osui::parse_rsx_param!($elem, $($rest)*)
};
($elem:expr, $text:expr) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text))}
};
($elem:expr, $text:expr, $($inner:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text, $($inner)*))}
};
($elem:expr, ) => {};
}
/// **rsx_elem!** - A macro to instantiate a UI element and apply parameters to it.
///
/// This macro simplifies creating an element and applying a set of properties or children to it, enabling a declarative style of UI building.
///
/// ## Example
/// ```rust
/// let element = rsx_elem!(ui::Button { text: "Click me" });
/// ```
///
/// ## Syntax
/// ```rust
/// rsx_elem!($elem:path { $($inner:tt)* })
/// ```
#[macro_export]
macro_rules! rsx_elem {
($elem:path { $($inner:tt)* }) => {{
#[allow(unused_mut)]
let mut elem = $elem();
$crate::parse_rsx_param!(elem, $($inner)*);
elem as $crate::Element
}};
}
/// **rsx!** - A shorthand macro to create a `div` element and apply nested content to it.
///
/// This macro is a simple wrapper for `rsx_elem!`, making it easy to create a `div` element and add children to it.
///
/// ## Example
/// ```rust
/// rsx! {
/// text { "Hello, World" }
/// div { button {"Click me!"} }
/// }
/// ```
///
/// ## Syntax
/// ```rust
/// rsx!($($inner:tt)*)
/// ```
#[macro_export]
macro_rules! rsx {
($($inner:tt)*) => {{
$crate::rsx_elem!( $crate::ui::div { $($inner)* } )
}};
}
#[macro_export]
macro_rules! val {
($a:expr) => {
$crate::Value::new($a)
};
}
#[macro_export]
macro_rules! run_handler {
macro_rules! call {
($self:ident.$handler:ident ($($inner:tt)*)) => {{
let a = std::sync::Arc::clone(&$self.$handler.0);
let mut o = a.lock().unwrap();
@@ -177,162 +11,8 @@ macro_rules! run_handler {
}
#[macro_export]
macro_rules! __css {
($style:expr,)=>{};
(
$style:expr, ($name:ident): $value:expr
) => {{
$style.other.insert(stringify!($name).to_string(), Box::new($value));
}};
(
$style:expr, ($name:ident): $value:expr, $($other:tt)*
) => {{
$style.other.insert(stringify!($name).to_string(), Box::new($value));
$crate::__css!($($other)*);
}};
(
$style:expr, $name:ident: $value:expr
) => {{
$style.$name = $value;
}};
}
#[macro_export]
macro_rules! _css {
($style:expr,) => {};
(
$hm:expr, .$name:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Class(stringify!($name).to_string());
if let Some(style) = $hm.get_mut(&name) {
$crate::__css!(style.0, $($inner)*);
} else {
let mut style = $crate::ui::Style::default();
$crate::__css!(style.0, $($inner)*);
$hm.insert(
name,
style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, .$name:ident::$kind:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Class(stringify!($name).to_string());
if let Some(style) = $hm.get_mut(&name) {
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
style.1.insert(stringify!($kind).to_string(), style_elem);
} else {
let mut style = $crate::ui::Style::default();
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
style.1.insert(stringify!($kind).to_string(), style_elem);
$hm.insert(
name,
style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, #$name:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Id(stringify!($name).to_string());
if let Some(style) = $hm.get_mut(&name) {
$crate::__css!(style.0, $($inner)*);
} else {
let mut style = $crate::ui::Style::default();
$crate::__css!(style.0, $($inner)*);
$hm.insert(
name,
style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, #$name:ident::$kind:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Id(stringify!($name).to_string());
if let Some(style) = $hm.get_mut(&name) {
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
style.1.insert(stringify!($kind).to_string(), style_elem);
} else {
let mut style = $crate::ui::Style::default();
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
style.1.insert(stringify!($kind).to_string(), style_elem);
$hm.insert(
name,
style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, $name:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Component(stringify!($name).to_string());
if let Some(style) = $hm.get_mut(&name) {
$crate::__css!(style.0, $($inner)*);
} else {
let mut style = $crate::ui::Style::default();
$crate::__css!(style.0, $($inner)*);
$hm.insert(
name,
style,
);
}
$crate::_css!($hm, $($rest)*);
}};
(
$hm:expr, $name:ident::$kind:ident { $($inner:tt)* } $($rest:tt)*
) => {{
let name = $crate::ui::StyleName::Component(stringify!($name).to_string());
if let Some(style) = $hm.get_mut(&name) {
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
style.1.insert(stringify!($kind).to_string(), style_elem);
} else {
let mut style = $crate::ui::Style::default();
let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*);
style.1.insert(stringify!($kind).to_string(), style_elem);
$hm.insert(
name,
style,
);
}
$crate::_css!($hm, $($rest)*);
}};
}
#[macro_export]
macro_rules! css {
(
$($inner:tt)*
) => {{
let mut hm: $crate::ui::Css = std::collections::HashMap::new();
$crate::_css!(hm, $($inner)*);
hm
}};
macro_rules! launch {
($expr:expr) => {
while $crate::run(&mut $expr) {}
};
}
+2 -15
View File
@@ -1,19 +1,6 @@
use osui::examples;
use osui::prelude::*;
fn main() {
while osui::run(&mut app()) {}
}
fn app() -> Element {
rsx! { styling: Some(styles()),
button { class: "btn", "This is a button!" }
}
}
fn styles() -> Css {
css! {
.btn::clicked {
color: Blue
}
}
launch!(examples::examples());
}
+95
View File
@@ -0,0 +1,95 @@
#[macro_export]
macro_rules! parse_rsx_param {
($elem:expr, for ($($for:tt)*) $code:block $($rest:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
if let $crate::Children::Children(children, _) = &mut $elem.children {
for $($for)* {
children.push($code)
}
}
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $($k:ident).+: $v:expr) => {
$elem.$($k).+ = $v;
};
($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => {
$elem.$($k).+ = $v;
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $p:path) => {
$p;
};
($elem:expr, $($k:ident).+, $($rest:tt)*) => {
$elem.$($k).+;
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $($k:ident).+., $($rest:tt)*) => {
$elem.$($k).
+.;
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $($k:ident).+.: $v:expr) => {
$elem.$($k).+. = $v;
};
($elem:expr, $($k:ident).+.: $v:expr, $($rest:tt)*) => {
$elem.$($k).+. = $v;
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $code:block $($rest:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
if let $crate::Children::Children(children, _) = &mut $elem.children {
children.push($code);
}
osui::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $elem_path:path { $($inner:tt)* } $($rest:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)}
if let $crate::Children::Children(children, _) = &mut $elem.children {
children.push(osui::ersx!($elem_path { $($inner)* }));
}
osui::parse_rsx_param!($elem, $($rest)*)
};
($elem:expr, $text:expr) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text))}
};
($elem:expr, $text:expr, $($inner:tt)*) => {
if $elem.children.is_none() {$elem.children = $crate::Children::Text(format!($text, $($inner)*))}
};
($elem:expr, ) => {};
}
#[macro_export]
macro_rules! ersx {
($elem:path { $($inner:tt)* }) => {{
#[allow(unused_mut)]
let mut elem = $elem();
$crate::parse_rsx_param!(elem, $($inner)*);
elem as $crate::Element
}};
}
/// Makes a div and puts elements into it
///
/// # Example
/// ```
/// rsx! {
/// button { class: "btn", "Click me!" }
/// }
/// ```
///
/// # Returns
/// A `osui::Element` - Which is a `Box<dyn osui::ElementWidget>`
#[macro_export]
macro_rules! rsx {
($($inner:tt)*) => {
$crate::ersx!{ $crate::ui::div { $($inner)* } }
};
}
+67 -112
View File
@@ -3,60 +3,37 @@
/// # Modules
/// - `styles`: A module for defining UI styles.
pub mod styles;
pub use styles::*;
use crate::prelude::*;
use osui_element::{elem_fn, element};
/// The `Text` element represents a piece of static text in the terminal UI.
///
/// It is used to render plain text within a UI component. The `Text` element does not interact
/// with events and only outputs the text when rendered.
///
/// # Example
/// ```rust
/// let text = Text::default();
/// let rendered_text = text.render(crate::State::Normal);
/// println!("{}", rendered_text);
/// ```
///
/// ## Methods
/// - `render(state: crate::State) -> String`: Renders the text to a string, depending on the state.
/// If the element contains text as a child, it returns that text. Otherwise, it returns an empty string.
#[element]
#[elem_fn]
#[derive(Default, Debug)]
pub struct Text {}
impl ElementWidget for Text<'_> {
fn render(&self, _: bool) -> String {
if let Children::Text(text) = &self.children {
text.clone()
} else {
String::new()
}
}
pub struct Text {
pub on_event: Handler<Text<'a>>,
}
/// The `Div` element represents a container for other UI elements. It can hold child components
/// and render them in a specific layout. The `Div` element also handles keyboard navigation through
/// its children.
///
/// The `Div` element supports rendering a list of children and allows interactive navigation using
/// arrow keys (Up, Down, Left, Right). It renders the selected child element differently based on the state.
///
/// # Example
/// ```rust
/// let div = Div::default();
/// let rendered_div = div.render(crate::State::Normal);
/// println!("{}", rendered_div);
/// ```
///
/// ## Methods
/// - `render(state: crate::State) -> String`: Renders the `Div` and its child elements to a string.
/// The child elements are rendered according to the current selection (`child` index).
/// - `event(event: Event, state: &crate::StateChanger)`: Handles keyboard events for
/// navigation through child elements. Arrow keys (Up, Down, Left, Right) change the currently selected child.
impl ElementWidget for Text<'_> {
fn render(&self, focused: bool) -> Option<RenderResult> {
let mut writer = RenderWriter::new(focused, self.style.clone());
writer.write(&{
if let Children::Text(text) = &self.children {
text.clone()
} else {
String::new()
}
});
Some(writer.result())
}
fn event(&mut self, event: Event, document: &Document) {
call!(self.on_event(event, document));
}
}
#[element]
#[elem_fn]
@@ -66,69 +43,28 @@ pub struct Div {
}
impl ElementWidget for Div<'_> {
fn render(&self, focused: bool) -> String {
let mut frame = crate::utils::create_frame(self.width.get_value(), self.height.get_value());
fn render(&self, focused: bool) -> Option<RenderResult> {
let mut writer = RenderWriter::new(focused, self.style.clone());
let mut frame = crate::utils::Frame::new(crate::utils::get_term_size().0, 12);
if let Children::Children(children, child) = &self.children {
for (i, elem) in children.iter().enumerate() {
crate::utils::render_to_frame(
focused && i == *child,
self.width.get_value(),
&mut frame,
elem,
);
frame.render(focused && i == *child, elem);
}
}
writer.write(&frame.output());
frame.join("\n")
Some(writer.result())
}
fn event(&mut self, event: Event) {
if let Some(styling) = self.styling.clone() {
self.set_styling(&styling);
fn event(&mut self, event: Event, document: &Document) {
if event == Event::FocusGained {
if let Some(styling) = self.styling.clone() {
self.set_styling(&styling);
}
}
match event {
Event::Key(key) => {
if let Children::Children(children, child) = &mut self.children {
*child = match key.code {
KeyCode::Up => crate::utils::closest_component(
children,
*child,
crate::utils::Direction::Up,
),
KeyCode::Down => crate::utils::closest_component(
children,
*child,
crate::utils::Direction::Down,
),
KeyCode::Left => crate::utils::closest_component(
children,
*child,
crate::utils::Direction::Left,
),
KeyCode::Right => crate::utils::closest_component(
children,
*child,
crate::utils::Direction::Right,
),
_ => {
if let Some(c) = children.get_mut(*child) {
c.event(event);
}
*child
}
}
}
}
_ => {
if let Some(child) = self.get_child() {
child.event(event);
}
}
if let Some(child) = self.children.get_child() {
child.event(event, document);
}
}
}
@@ -138,32 +74,51 @@ impl ElementWidget for Div<'_> {
#[derive(Default, Debug)]
pub struct Button {
pub on_click: Handler<Button<'a>>,
pub state: &'a str,
}
impl ElementWidget for Button<'_> {
fn render(&self, focused: bool) -> String {
if self.state == "clicked" {
return self.style.write("clicked", &self.children.get_text());
}
if focused {
self.style.write("hover", &self.children.get_text())
} else {
self.style.write("", &self.children.get_text())
}
fn render(&self, focused: bool) -> Option<RenderResult> {
let mut writer = RenderWriter::new(focused, self.style.clone());
writer.write(&self.children.get_text());
Some(writer.result())
}
fn event(&mut self, event: Event) {
fn event(&mut self, event: Event, document: &Document) {
match event {
Event::Key(key) => {
if key.code == KeyCode::Enter {
self.state = "clicked";
run_handler!(self.on_click(event));
document.render();
self.style.set_state("clicked");
call!(self.on_click(event, document));
document.render();
sleep(100);
self.state = "";
self.style.set_state("");
document.rebuild();
}
}
_ => {}
}
}
}
#[element]
#[derive(Debug)]
pub struct DataHolder<'a, T> {
pub data: T,
}
pub fn dataholder<'a, T: std::default::Default>() -> Box<DataHolder<'a, T>> {
Box::new(DataHolder {
style: Style::default(),
data: T::default(),
children: Children::None,
class: "",
id: "",
})
}
impl<'a, T: std::fmt::Debug + Send + Sync> ElementWidget for DataHolder<'a, T> {
fn render(&self, _: bool) -> Option<RenderResult> {
None
}
}
+171 -75
View File
@@ -6,45 +6,34 @@ use std::{collections::HashMap, fmt::Debug};
use dyn_clone::{clone_trait_object, DynClone};
//////////////////////////////////////////////////////////////////////////////////////////////////
// Type aliases
//////////////////////////////////////////////////////////////////////////////////////////////////
pub type Css = HashMap<StyleName, Style>;
//////////////////////////////////////////////////////////////////////////////////////////////////
// Traits
//////////////////////////////////////////////////////////////////////////////////////////////////
pub trait StyleCore: Debug + Send + Sync + DynClone {
fn ansi(&self) -> String;
fn ansi_bg(&self) -> String;
fn is_null(&self) -> bool;
}
clone_trait_object!(StyleCore);
//////////////////////////////////////////////////////////////////////////////////////////////////
// Enums
//////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Font {
None,
Bold,
Underline,
Italic,
Reverse,
Strike,
Mul(Vec<Font>),
}
impl StyleCore for Font {
fn ansi(&self) -> String {
String::from(match self {
Font::None => "",
Font::Bold => "\x1b[1m",
Font::Underline => "\x1b[4m",
Font::Italic => "\x1b[3m",
Font::Reverse => "\x1b[7m",
Font::Strike => "\x1b[9m",
Font::Mul(v) => {
let mut s = String::new();
for n in v {
s += n.ansi().as_str();
}
return s;
}
})
}
fn ansi_bg(&self) -> String {
self.ansi()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -62,6 +51,128 @@ pub enum Color {
White,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Number {
Px(usize),
Pe(usize),
Center,
Auto,
Default,
}
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum StyleName {
Class(String),
Id(String),
Component(String),
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Structs
//////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone)]
pub struct StyleElement {
pub color: Color,
pub background: Color,
pub font: Vec<Font>,
pub x: Number,
pub y: Number,
pub width: Number,
pub height: Number,
pub other: HashMap<String, Box<dyn StyleCore>>,
}
#[derive(Debug, Clone)]
pub struct Style(
pub StyleElement,
pub HashMap<String, StyleElement>,
pub String,
);
//////////////////////////////////////////////////////////////////////////////////////////////////
// Implementations
//////////////////////////////////////////////////////////////////////////////////////////////////
impl Default for Style {
fn default() -> Self {
Style(StyleElement::default(), HashMap::new(), String::new())
}
}
impl Style {
pub fn get(&self, hover: bool) -> StyleElement {
if let Some(style_element) = self.1.get(&self.2) {
self.0.clone().prioritize(style_element)
} else if hover {
if let Some(style_element) = self.1.get("hover") {
self.0.clone().prioritize(style_element)
} else {
self.0.clone()
}
} else {
self.0.clone()
}
}
pub fn set_state(&mut self, state: &str) {
self.2 = state.to_string();
}
}
impl StyleElement {
pub fn write(&self, s: &str) -> String {
format!(
"{}{}{}{s}\x1b[0m",
self.color.ansi(),
self.background.ansi_bg(),
self.font.ansi()
)
}
}
impl Default for StyleElement {
fn default() -> Self {
StyleElement {
color: Color::NoColor,
background: Color::NoColor,
font: Vec::new(),
x: Number::Default,
y: Number::Default,
width: Number::Default,
height: Number::Default,
other: HashMap::new(),
}
}
}
impl StyleElement {
pub fn prioritize(mut self, other: &Self) -> StyleElement {
if !other.color.is_null() {
self.color = other.color.clone();
}
if !other.background.is_null() {
self.background = other.background.clone();
}
if !other.font.is_null() {
self.font = other.font.clone();
}
if other.x != Number::Default {
self.x = other.x.clone();
}
if other.y != Number::Default {
self.y = other.y.clone();
}
if other.width != Number::Default {
self.width = other.width.clone();
}
if other.height != Number::Default {
self.height = other.height.clone();
}
self
}
}
impl StyleCore for Color {
fn ansi(&self) -> String {
String::from(match self {
@@ -104,6 +215,9 @@ impl StyleCore for Color {
Color::White => "\x1b[47m",
})
}
fn is_null(&self) -> bool {
*self == Self::NoColor
}
}
impl Default for Color {
@@ -112,12 +226,6 @@ impl Default for Color {
}
}
impl Default for Font {
fn default() -> Self {
Font::None
}
}
fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
let hex = hex.trim_start_matches('#');
@@ -144,54 +252,42 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
(r, g, b)
}
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
pub enum StyleName {
Class(String),
Id(String),
Component(String),
impl StyleCore for Font {
fn ansi(&self) -> String {
String::from(match self {
Font::Bold => "\x1b[1m",
Font::Underline => "\x1b[4m",
Font::Italic => "\x1b[3m",
Font::Reverse => "\x1b[7m",
Font::Strike => "\x1b[9m",
})
}
fn ansi_bg(&self) -> String {
self.ansi()
}
fn is_null(&self) -> bool {
false
}
}
#[derive(Debug, Clone)]
pub struct StyleElement {
pub color: Color,
pub other: HashMap<String, Box<dyn StyleCore>>,
}
impl Default for StyleElement {
fn default() -> Self {
StyleElement {
color: Color::NoColor,
other: HashMap::new(),
impl StyleCore for Vec<Font> {
fn ansi(&self) -> String {
let mut a = String::new();
for i in self {
a += i.ansi().as_str();
}
a
}
}
#[derive(Debug, Clone)]
pub struct Style(pub StyleElement, pub HashMap<String, StyleElement>);
impl Default for Style {
fn default() -> Self {
Style(StyleElement::default(), HashMap::new())
}
}
impl Style {
pub fn write(&self, state: &str, s: &str) -> String {
if state == "" {
return self.0.write(s);
}
if let Some(style_element) = self.1.get(state) {
style_element.write(s)
} else {
s.to_string()
fn ansi_bg(&self) -> String {
let mut a = String::new();
for i in self {
a += i.ansi_bg().as_str();
}
a
}
fn is_null(&self) -> bool {
self.len() == 0
}
}
impl StyleElement {
pub fn write(&self, s: &str) -> String {
format!("{}{s}\x1b[0m", self.color.ansi())
}
}
pub type Css = HashMap<StyleName, Style>;
+73 -74
View File
@@ -9,8 +9,7 @@
/// let frame = create_frame(50, 10);
/// render_to_frame(crate::State::Normal, 50, &mut frame, &text_element);
/// ```
use crate::{Element, Value};
use crate::Element;
use lazy_static::lazy_static;
use regex::Regex;
use std::{
@@ -49,7 +48,7 @@ pub fn compress_string(input: &str, re: &Regex) -> (String, HashMap<usize, Strin
}
/// Merges a frame withe a line by x
fn merge_line(frame_: &str, line_: &str, x: usize) -> String {
fn merge_line(frame_: &str, line_: &str, x: usize) -> (String, String) {
let (frame_, fm) = compress_string(frame_, &ANSI);
let (mut line_, lm) = compress_string(line_, &ANSI);
@@ -82,36 +81,87 @@ fn merge_line(frame_: &str, line_: &str, x: usize) -> String {
}
}
res
(res, line_)
}
/// Render to a frame
pub fn render_to_frame(
focused: bool,
pub struct Frame {
frame: Vec<String>,
width: usize,
frame: &mut Vec<String>,
element: &Element,
) {
let data = element.get_data();
for (i, line) in element.render(focused).split('\n').enumerate() {
if (data.1 + i) < frame.len() {
let frame_line = frame.get_mut(data.1 + i).unwrap();
match data.0 {
Value::Custom(x) => {
*frame_line = merge_line(&frame_line, line, x);
used: Vec<usize>,
}
impl Frame {
pub fn new(width: usize, height: usize) -> Self {
Self {
frame: vec![" ".repeat(width); height],
width,
used: vec![0; height],
}
}
pub fn render(&mut self, focused: bool, element: &Element) {
if let Some(output) = element.render(focused) {
let x;
let mut y = 0;
match output.1.y {
crate::ui::Number::Px(_y) => {
y = _y;
}
_ => {
for (x, c) in frame_line.chars().rev().enumerate() {
if c != ' ' {
*frame_line = merge_line(&frame_line, line, width - x);
return;
crate::ui::Number::Pe(p) => y = (self.frame.len() * p) / 100,
crate::ui::Number::Center => {
y = (self.frame.len() - output.0.matches('\n').count() + 1) / 2
}
crate::ui::Number::Auto => {
for (i, n) in self.used.iter().enumerate() {
if *n == 0 {
y = i;
break;
}
}
*frame_line = merge_line(&frame_line, line, 0);
}
crate::ui::Number::Default => {
for (i, n) in self.used.iter().enumerate() {
if *n == 0 {
y = i;
break;
}
}
}
}
match output.1.x {
crate::ui::Number::Px(_x) => {
x = _x;
}
crate::ui::Number::Pe(p) => x = (self.width * p) / 100,
crate::ui::Number::Center => {
x = (self.width
- ANSI
.replace_all(&output.0.lines().next().unwrap_or(""), "")
.len())
/ 2;
}
crate::ui::Number::Auto => {
x = *self.used.get(y).unwrap();
}
crate::ui::Number::Default => {
x = 0;
}
}
for (i, line) in output.0.split('\n').enumerate() {
if self.frame.len() > y + i {
let frame_line = self.frame.get_mut(y + i).unwrap();
let merged = merge_line(&frame_line, line, x);
*frame_line = merged.0;
*self.used.get_mut(y + i).unwrap() += merged.1.len() - 1;
}
}
}
}
pub fn output(&self) -> String {
self.frame.join("\n")
}
pub fn output_nnl(&self) -> String {
self.frame.join("")
}
}
pub fn clear() {
@@ -133,57 +183,6 @@ pub fn flush() {
stdout().flush().unwrap();
}
#[derive(Debug, Clone)]
pub enum Direction {
Left,
Right,
Up,
Down,
}
pub fn closest_component(
components: &[Element],
current_index: usize,
direction: Direction,
) -> usize {
let current = &components[current_index].get_data();
components
.iter()
.enumerate()
.filter(|(i, comp_)| {
let comp = comp_.get_data();
match direction {
Direction::Left => match comp.0 {
Value::Custom(_) => {
comp.0.get_value() < current.0.get_value() && comp.1 == current.1
}
Value::Default(_) => *i < current_index,
}, // Left
Direction::Right => match comp.0 {
Value::Custom(_) => {
comp.0.get_value() > current.0.get_value() && comp.1 == current.1
}
Value::Default(_) => *i > current_index,
}, // Right
Direction::Up => comp.1 < current.1 && comp.0.get_value() == current.0.get_value(), // Up
Direction::Down => {
comp.1 > current.1 && comp.0.get_value() == current.0.get_value()
} // Down
}
})
.min_by_key(|(_, comp_)| {
let comp = comp_.get_data();
current.0.get_value().abs_diff(comp.0.get_value()) + current.1.abs_diff(comp.1)
}) // Find the closest component
.map(|(index, _)| index) // Return the index of the closest component
.unwrap_or(current_index) // If no component is found, return the current index
}
pub fn create_frame(width: usize, height: usize) -> Vec<String> {
vec![" ".repeat(width); height]
}
pub fn get_term_size() -> (usize, usize) {
let (width, height) = crossterm::terminal::size().unwrap();
(width as usize, height as usize)
+9
View File
@@ -0,0 +1,9 @@
`color`, `background`: `$color{}\x1b[0m`;
`font`: `$font{}\x1b[0m`;
`width`, `height`: `n`, `n%`, `auto`;
`x`, `y`: `n`, `n%`, `center`, `auto`;
<!-- `display`: `row`, `column`, `overlap`; -->