This commit is contained in:
2024-11-24 20:00:29 +01:00
parent e463ca1e7c
commit 33843688d8
8 changed files with 228 additions and 72 deletions
+4 -6
View File
@@ -4,9 +4,7 @@ use crossterm::{
};
use crate::prelude::*;
use crate::utils::{
clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, show_cursor,
};
use crate::utils::{clear, flush, get_term_size, hide_cursor, show_cursor, Frame};
use std::{ffi::c_void, ptr::null, sync::mpsc};
pub struct LArgs<'a> {
@@ -125,10 +123,10 @@ extern "C" fn cmd_loop(ptr: *mut c_void) -> *const c_void {
}
Command::Render => {
let (width, height) = get_term_size();
let mut frame: Vec<String> = create_frame(width, height);
render_to_frame(true, width, &mut frame, args.element);
let mut frame = Frame::new(width, height);
frame.render(true, args.element);
clear();
print!("{}", frame.join(""));
print!("{}", frame.output_nnl());
flush();
}
}
+25
View File
@@ -1,6 +1,31 @@
#[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)*)?);
}};
// Normal
(
$_style:expr, $name:ident: $value:expr $(, $($other:tt)*)?
) => {{
+16 -5
View File
@@ -2,10 +2,21 @@ use crate::prelude::*;
pub fn event_logger() -> Element {
rsx! {
id: "root",
text { on_event: Handler::new(|_, _, document| {
let root = document.get_element_by_id::<Div>("root").unwrap();
root.children.add_child(rsx_elem!( text { "Ok" } ));
}), id: "txt", "current event: None" }
styling: Some(styling()),
button { class: "txt", "Hello, World!" }
}
}
pub fn styling() -> Css {
css! {
.txt {
x: Center,
y: Center,
color: Red,
}
.txt: clicked {
color: Blue,
}
}
}
+7 -4
View File
@@ -43,6 +43,7 @@ pub use app::run;
/// the UI framework easier to use.
pub mod prelude {
pub use crate::ui::Color::*;
pub use crate::ui::Number::*;
pub use crate::{self as osui, css, rsx, rsx_elem, ui::*, Handler};
pub use crate::{style, Command, Document, Element, Value};
// useful for Element making
@@ -88,6 +89,11 @@ impl<T: Copy + PartialEq + Default> Default for Value<T> {
/// ElementCore Traits
//////////////////////////////////////////////////////////////////////////////////////////////////
/// ```
/// RenderResult(output, (x, y), (width, height))
/// ```
pub struct RenderResult(String, (ui::Number, ui::Number));
pub trait ElementCore: Send + Sync {
fn get_data(&self) -> (Value<usize>, usize, String);
fn update_data(&mut self, width: usize, height: usize);
@@ -97,10 +103,7 @@ pub trait ElementCore: Send + Sync {
}
pub trait ElementWidget: ElementCore + std::fmt::Debug {
fn render(&self, focused: bool) -> String {
_ = focused;
String::new()
}
fn render(&self, focused: bool) -> RenderResult;
fn event(&mut self, event: Event, document: &Document) {
_ = (event, document)
+29 -23
View File
@@ -5,7 +5,7 @@
pub mod styles;
pub use styles::*;
use crate::prelude::*;
use crate::{prelude::*, RenderResult};
use osui_element::{elem_fn, element};
/// The `Text` element represents a piece of static text in the terminal UI.
@@ -31,12 +31,17 @@ pub struct Text {
}
impl ElementWidget for Text<'_> {
fn render(&self, _: bool) -> String {
if let Children::Text(text) = &self.children {
text.clone()
} else {
String::new()
}
fn render(&self, _: bool) -> RenderResult {
self.style.result(
"",
self.style.write("", &{
if let Children::Text(text) = &self.children {
text.clone()
} else {
String::new()
}
}),
)
}
fn event(&mut self, event: Event, document: &Document) {
call!(self.on_event(event, document));
@@ -71,21 +76,16 @@ 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) -> RenderResult {
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);
}
}
frame.join("\n")
self.style.result("", frame.output())
}
fn event(&mut self, event: Event, document: &Document) {
@@ -149,14 +149,20 @@ pub struct Button {
}
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())
fn render(&self, focused: bool) -> RenderResult {
if self.state != "" {
self.style.result(
self.state,
self.style.write(self.state, &self.children.get_text()),
)
} else if focused {
self.style.result(
"hover",
self.style.write("hover", &self.children.get_text()),
)
} else {
self.style.write("", &self.children.get_text())
self.style
.result("", self.style.write("", &self.children.get_text()))
}
}
+72 -5
View File
@@ -6,9 +6,12 @@ use std::{collections::HashMap, fmt::Debug};
use dyn_clone::{clone_trait_object, DynClone};
use crate::RenderResult;
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);
@@ -45,6 +48,9 @@ impl StyleCore for Font {
fn ansi_bg(&self) -> String {
self.ansi()
}
fn is_null(&self) -> bool {
*self == Self::None
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -104,6 +110,9 @@ impl StyleCore for Color {
Color::White => "\x1b[47m",
})
}
fn is_null(&self) -> bool {
*self == Self::NoColor
}
}
impl Default for Color {
@@ -144,6 +153,15 @@ fn hex_to_rgb(hex: &str) -> (u8, u8, u8) {
(r, g, b)
}
#[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),
@@ -154,6 +172,12 @@ pub enum StyleName {
#[derive(Debug, Clone)]
pub struct StyleElement {
pub color: Color,
pub background: Color,
pub font: Font,
pub x: Number,
pub y: Number,
pub width: Number,
pub height: Number,
pub other: HashMap<String, Box<dyn StyleCore>>,
}
@@ -161,11 +185,44 @@ impl Default for StyleElement {
fn default() -> Self {
StyleElement {
color: Color::NoColor,
background: Color::NoColor,
font: Font::None,
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
}
}
#[derive(Debug, Clone)]
pub struct Style(pub StyleElement, pub HashMap<String, StyleElement>);
@@ -177,20 +234,30 @@ impl Default for Style {
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()
return self.0.write(s);
}
}
pub fn result(&self, state: &str, s: String) -> RenderResult {
if let Some(style_element) = self.1.get(state) {
let style_element = self.0.clone().prioritize(&style_element.clone());
RenderResult(s, (style_element.x.clone(), style_element.y.clone()))
} else {
RenderResult(s, (self.0.x.clone(), self.0.y.clone()))
}
}
}
impl StyleElement {
pub fn write(&self, s: &str) -> String {
format!("{}{s}\x1b[0m", self.color.ansi())
format!(
"{}{}{}{s}\x1b[0m",
self.color.ansi(),
self.background.ansi_bg(),
self.font.ansi()
)
}
}
+73 -27
View File
@@ -9,7 +9,6 @@
/// let frame = create_frame(50, 10);
/// render_to_frame(crate::State::Normal, 50, &mut frame, &text_element);
/// ```
use crate::{Element, Value};
use lazy_static::lazy_static;
use regex::Regex;
@@ -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,35 +81,86 @@ 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) {
// let data = element.get_data();
let output = element.render(focused);
let x;
let mut y = 0;
match output.1 .1 {
crate::ui::Number::Px(_y) => {
y = _y;
}
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;
}
}
_ => {
for (x, c) in frame_line.chars().rev().enumerate() {
if c != ' ' {
*frame_line = merge_line(&frame_line, line, width - x);
return;
}
}
crate::ui::Number::Default => {
for (i, n) in self.used.iter().enumerate() {
if *n == 0 {
y = i;
break;
}
*frame_line = merge_line(&frame_line, line, 0);
}
}
}
match output.1 .0 {
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("")
}
}
@@ -180,10 +230,6 @@ pub fn 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)
+1 -1
View File
@@ -6,4 +6,4 @@
`x`, `y`: `n`, `n%`, `center`, `auto`;
`display`: `row`, `column`, `overlap`;
<!-- `display`: `row`, `column`, `overlap`; -->