This commit is contained in:
2024-12-09 13:54:51 +01:00
parent 15d0cd22ee
commit 1876f4d640
6 changed files with 169 additions and 293 deletions
+42 -154
View File
@@ -3,181 +3,40 @@ macro_rules! __css {
($_style:expr,)=>{}; ($_style:expr,)=>{};
// percentage (n%) // percentage (n%)
( (
$_style:expr, $name:ident: $value:literal% $(, $($other:tt)*)? $_style:expr, $name:ident: $value:literal% $(, $($rest:tt)*)?
) => {{ ) => {{
$_style.$name = Number::Pe($value); $_style.$name = Number::Pe($value);
$crate::__css!($_style, $($($other)*)?); $crate::__css!($_style, $($($rest)*)?);
}};
(
$_style:expr, $name:literal: $value:literal% $(, $($other:tt)*)?
) => {{
$_style.other.insert($name.to_string(), Box::new(
Number::Pe($value)
));
$crate::__css!($_style, $($($other)*)?);
}}; }};
// Number // Number
( (
$_style:expr, $name:ident: $value:literal px $(, $($other:tt)*)? $_style:expr, $name:ident: $value:literal px $(, $($rest:tt)*)?
) => {{ ) => {{
$_style.$name = Number::Px($value); $_style.$name = Number::Px($value);
$crate::__css!($_style, $($($other)*)?); $crate::__css!($_style, $($($rest)*)?);
}}; }};
// Vector // Vector
( (
$_style:expr, $name:ident: [$($inner:tt),*] $(, $($other:tt)*)? $_style:expr, $name:ident: [$($inner:tt),*] $(, $($rest:tt)*)?
) => {{ ) => {{
$_style.$name = vec![$($inner),*]; $_style.$name = vec![$($inner),*];
$crate::__css!($_style, $($($other)*)?); $crate::__css!($_style, $($($rest)*)?);
}}; }};
// Normal // Normal
( (
$_style:expr, $name:ident: $value:expr $(, $($other:tt)*)? $_style:expr, $name:ident: $value:expr $(, $($rest:tt)*)?
) => {{ ) => {{
$_style.$name = $value; $_style.$name = $value;
$crate::__css!($_style, $($($other)*)?); $crate::__css!($_style, $($($rest)*)?);
}};
(
$_style:expr, $name:literal: $value:expr $(, $($other:tt)*)?
) => {{
$_style.other.insert($name.to_string(), Box::new($value));
$crate::__css!($_style, $($($other)*)?);
}}; }};
} }
#[macro_export] #[macro_export]
macro_rules! _css { macro_rules! _css {
($_style:expr,) => {}; ($_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)*);
}};
( (
$hm:expr, $name:literal { $($inner:tt)* } $($rest:tt)* $hm:expr, $name:literal { $($inner:tt)* } $($rest:tt)*
) => {{ ) => {{
@@ -197,19 +56,19 @@ macro_rules! _css {
$crate::_css!($hm, $($rest)*); $crate::_css!($hm, $($rest)*);
}}; }};
( (
$hm:expr, $name:literal:$kind:ident { $($inner:tt)* } $($rest:tt)* $hm:expr, $name:literal: $kind:literal { $($inner:tt)* } $($rest:tt)*
) => {{ ) => {{
let name = $crate::ui::StyleName::Class(String::from($name)); let name = $crate::ui::StyleName::Class(String::from($name));
if let Some(_style) = $hm.get_mut(&name) { if let Some(_style) = $hm.get_mut(&name) {
let mut style_elem = $crate::ui::StyleElement::default(); let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*); $crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem); _style.1.insert($kind.to_string(), style_elem);
} else { } else {
let mut _style = $crate::ui::Style::default(); let mut _style = $crate::ui::Style::default();
let mut style_elem = $crate::ui::StyleElement::default(); let mut style_elem = $crate::ui::StyleElement::default();
$crate::__css!(style_elem, $($inner)*); $crate::__css!(style_elem, $($inner)*);
_style.1.insert(stringify!($kind).to_string(), style_elem); _style.1.insert($kind.to_string(), style_elem);
$hm.insert( $hm.insert(
name, name,
@@ -224,8 +83,11 @@ macro_rules! _css {
/// ///
/// # Examples /// # Examples
/// ``` /// ```
/// .btn: clicked { /// "btn" {
/// color: Blue /// color: Red,
/// }
/// "btn": "clicked" {
/// color: Blue,
/// } /// }
/// ``` /// ```
/// ///
@@ -245,6 +107,32 @@ macro_rules! css {
#[macro_export] #[macro_export]
macro_rules! style { macro_rules! style {
(
$key:ident: $value:literal% $(, $($rest:tt)*)?
) => {{
let mut _style = $crate::ui::Style::default();
$crate::__css!(_style.0, $key: $crate::ui::Number::Pe($value));
$crate::style!{ _style; $($($rest)*)? }
}};
(
$key:ident: $value:literal px $(, $($rest:tt)*)?
) => {{
let mut _style = $crate::ui::Style::default();
$crate::__css!(_style.0, $key: $crate::ui::Number::Px($value));
$crate::style!{ _style; $($($rest)*)? }
}};
(
$style:expr; $key:ident: $value:literal% $(, $($rest:tt)*)?
) => {{
$crate::__css!($style.0, $key: $crate::ui::Number::Pe($value));
$crate::style!{ $style; $($($rest)*)? }
}};
(
$style:expr; $key:ident: $value:literal px $(, $($rest:tt)*)?
) => {{
$crate::__css!($style.0, $key: $crate::ui::Number::Px($value));
$crate::style!{ $style; $($($rest)*)? }
}};
( (
$key:ident: $value:expr $(, $($rest:tt)*)? $key:ident: $value:expr $(, $($rest:tt)*)?
) => {{ ) => {{
+13 -18
View File
@@ -24,33 +24,28 @@ pub fn todo_app() -> Element {
styling: Some(styling()), styling: Some(styling()),
class: "root", class: "root",
div { div {
class: "first", class: "tab",
text { class: "title", "not started" } text { class: "title", "not started" }
} }
div { // div {
class: "second", // class: "tab",
text { class: "title", "completed" } // text { class: "title", "completed" }
} // }
} }
} }
pub fn styling() -> Css { pub fn styling() -> Css {
css! { css! {
"first" { "tab" {
x: Auto, // x: 0 px,
y: 0 px, // y: Auto,
width: 50%, // height: 50%,
} outline: true,
"second" {
x: Auto,
y: 0 px,
width: 50%,
}
"root" {
width: 100%,
} }
"title" { "title" {
x: Center // x: Center,
// color: Red,
// width: 15 px,
} }
} }
} }
+12 -8
View File
@@ -82,9 +82,10 @@ pub struct Document {
running: *mut Option<bool>, running: *mut Option<bool>,
} }
#[derive(Debug)]
pub struct Writer { pub struct Writer {
text: String, text: String,
style: Style, style: ui::StyleElement,
focused: bool, focused: bool,
size: (u16, u16), size: (u16, u16),
} }
@@ -145,7 +146,7 @@ impl Document {
let mut frame = utils::Frame::new(width, height); let mut frame = utils::Frame::new(width, height);
frame.render(true, unsafe { &*self.element }); frame.render(true, unsafe { &*self.element });
utils::clear(); utils::clear();
print!("{}", frame.output_nnl()); print!("{}", frame.output_nnl().replace("\n", ""));
utils::flush(); utils::flush();
} }
} }
@@ -227,7 +228,7 @@ impl<T> Handler<T> {
} }
impl Writer { impl Writer {
pub fn new(focused: bool, style: ui::Style, size: (u16, u16)) -> Writer { pub fn new(focused: bool, style: ui::StyleElement, size: (u16, u16)) -> Writer {
Writer { Writer {
text: String::new(), text: String::new(),
style, style,
@@ -237,16 +238,19 @@ impl Writer {
} }
pub fn write(&mut self, s: &str) { pub fn write(&mut self, s: &str) {
self.text += self.style.get(self.focused).write(s).as_str(); self.text += self.style.write(s).as_str();
}
pub fn get_size(&self) -> (u16, u16) {
self.size
} }
pub fn get_focused(&self) -> bool { pub fn get_focused(&self) -> bool {
self.focused self.focused
} }
pub fn get_size(&self) -> (u16, u16) {
(
self.style.width.as_size(self.size.0, self.style.outline),
self.style.height.as_size(self.size.1, self.style.outline),
)
}
} }
impl<T> Default for Handler<T> { impl<T> Default for Handler<T> {
+9
View File
@@ -3,3 +3,12 @@ use osui::{examples::todo_app, prelude::*};
fn main() { fn main() {
launch!(todo_app()) launch!(todo_app())
} }
// use osui::utils::overlay;
// fn main() {
// let layer0 = " ".repeat(crossterm::terminal::size().unwrap().0 as usize);
// let layer1 = "\0\x1b[32m\0ABC";
// let overlayed = overlay(&layer0, &layer1, 0).replace('\0', "");
// println!("{overlayed}END");
// }
+14 -11
View File
@@ -83,7 +83,6 @@ pub struct StyleElement {
pub height: Number, pub height: Number,
pub visible: bool, pub visible: bool,
pub outline: bool, pub outline: bool,
pub other: HashMap<String, Box<dyn StyleCore>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -129,7 +128,7 @@ impl StyleElement {
.into_iter() .into_iter()
.map(|p| { .map(|p| {
format!( format!(
"{}{}{}{p}\x1b[0m", "\0{}{}{}\0{p}\0\x1b[0m\0",
self.color.ansi(), self.color.ansi(),
self.background.ansi_bg(), self.background.ansi_bg(),
self.font.ansi() self.font.ansi()
@@ -148,12 +147,11 @@ impl Default for StyleElement {
font: Vec::new(), font: Vec::new(),
outline_color: Color::NoColor, outline_color: Color::NoColor,
x: Number::Default, x: Number::Default,
y: Number::Default, y: Number::Px(0),
width: Number::Default, width: Number::Default,
height: Number::Default, height: Number::Default,
visible: true, visible: true,
outline: false, outline: false,
other: HashMap::new(),
} }
} }
} }
@@ -305,13 +303,18 @@ impl StyleCore for Vec<Font> {
} }
impl Number { impl Number {
pub fn as_size(&self, text_size: u16, frame_size: u16) -> u16 { pub fn as_size_raw(&self, frame_size: u16) -> u16 {
match self { match self {
Number::Px(s) => *s, Number::Px(s) => *s,
Number::Pe(pe) => (frame_size * pe) / 100, Number::Pe(pe) => (frame_size * pe) / 100,
Number::Default => frame_size, _ => frame_size,
crate::ui::Number::Auto => text_size, }
_ => 0, }
pub fn as_size(&self, frame_size: u16, outline: bool) -> u16 {
if outline {
self.as_size_raw(frame_size) - 2
} else {
self.as_size_raw(frame_size)
} }
} }
pub fn as_position_y(&self, used: &Vec<u16>, content_size: u16, frame_size: u16) -> u16 { pub fn as_position_y(&self, used: &Vec<u16>, content_size: u16, frame_size: u16) -> u16 {
@@ -320,14 +323,14 @@ impl Number {
crate::ui::Number::Pe(pe) => (frame_size * pe) / 100, crate::ui::Number::Pe(pe) => (frame_size * pe) / 100,
crate::ui::Number::Center => (frame_size - content_size) / 2, crate::ui::Number::Center => (frame_size - content_size) / 2,
crate::ui::Number::Auto | crate::ui::Number::Default => { crate::ui::Number::Auto | crate::ui::Number::Default => {
let mut x = 0; let mut y = 0;
for (i, n) in used.iter().enumerate() { for (i, n) in used.iter().enumerate() {
if *n == 0 { if *n == 0 {
x = i as u16; y = i as u16;
break; break;
} }
} }
x y
} }
} }
} }
+77 -100
View File
@@ -1,86 +1,36 @@
/// The `utils` module contains helper functions and utilities that assist in rendering and use std::io::{stdout, Write};
/// managing UI state. These functions are used by UI elements like `Div` and `Text` for common
/// tasks such as frame creation, event handling, and component selection.
///
/// # Example
/// ```rust
/// use crate::ui::utils::{create_frame, render_to_frame};
///
/// let frame = create_frame(50, 10);
/// render_to_frame(crate::State::Normal, 50, &mut frame, &text_element);
/// ```
use lazy_static::lazy_static;
use regex::Regex;
use std::{
collections::HashMap,
io::{stdout, Write},
};
lazy_static! { pub fn overlay(layer0: &str, layer1: &str, mut index: usize) -> String {
pub static ref ANSI: Regex = Regex::new(r"(\x1b\[([0-9;]*)[a-zA-Z])+").unwrap(); let layer0: Vec<char> = layer0.chars().collect();
let layer1: Vec<char> = layer1.chars().collect();
let mut layer1_index = 0;
let mut result = String::new();
let mut is_ansi = false;
let mut ansi = String::new();
for (i, &c) in layer0.iter().enumerate() {
if c == '\0' {
if is_ansi {
result.push_str(&ansi);
ansi.clear();
} }
is_ansi = !is_ansi;
/// Compress a string by a regex pattern index += 1;
pub fn compress_string(input: &str, re: &Regex) -> (String, HashMap<usize, String>) { } else if is_ansi {
let mut matches_map = HashMap::new(); ansi.push(c);
let mut res = String::new(); index += 1;
let input_chars: Vec<char> = input.chars().collect(); } else if i >= index && layer1_index < layer1.len() {
if layer1[layer1_index] == '\0' {
let mut i = 0; result.push(c);
while i < input_chars.len() { }
let remaining_input: String = input_chars[i..].iter().collect(); result.push(layer1[layer1_index]);
if let Some(loc) = re.find(&remaining_input) { layer1_index += 1;
if loc.start() == 0 {
let matched_chars: String = input_chars[i..i + loc.end()].iter().collect();
matches_map.insert(res.len(), matched_chars.clone());
i += loc.end();
} else { } else {
res.push(input_chars[i]); result.push(c);
i += 1;
}
} else {
res.push(input_chars[i]);
i += 1;
} }
} }
(res, matches_map) result
}
/// Merges a frame withe a line by x
fn merge_line(frame_: &str, (mut line_, lm): (String, HashMap<usize, String>), x: usize) -> String {
let (frame_, fm) = compress_string(frame_, &ANSI);
if let Some(_) = lm.get(&line_.len()) {
line_.push('\n');
}
let mut res = String::new();
let frame: Vec<char> = frame_.chars().collect();
let line: Vec<char> = (line_).chars().collect();
let flen = frame.len();
let llen = line.len();
for i in 0..flen {
if let Some(v) = fm.get(&i) {
res.push_str(v);
}
if i >= x && i - x < llen && line[i - x] != '\t' {
if let Some(v) = lm.get(&(i - x)) {
res.push_str(v);
}
if line[i - x] == '\n' {
res.push(frame[i]);
} else {
res.push(line[i - x]);
}
} else {
res.push(frame[i]);
}
}
res
} }
pub struct Frame { pub struct Frame {
@@ -98,31 +48,49 @@ impl Frame {
} }
} }
pub fn render(&mut self, focused: bool, element: &crate::Element) { pub fn render(&mut self, focused: bool, element: &crate::Element) {
let style = element.get_style().get(focused);
let mut writer = crate::Writer::new( let mut writer = crate::Writer::new(
focused, focused,
element.get_style().clone(), style.clone(),
(self.width, self.frame.len() as u16), (self.width, self.frame.len() as u16),
); );
let style = writer.style.get(focused);
element.render(&mut writer);
if style.visible { if style.visible {
let frame_height = self.frame.len() as u16; let frame_height = self.frame.len() as u16;
let width = style.width.as_size(writer.text.len() as u16, self.width); let (width, height) = writer.get_size();
let height = style.height.as_size(0, frame_height);
let y = style.y.as_position_y(&self.used, height, frame_height) as usize; let y = style.y.as_position_y(&self.used, height, frame_height) as usize;
let x = style if let Some(y_) = self.used.get(y) {
.x let x = style.x.as_position_x(y_, width, self.width) as usize;
.as_position_x(self.used.get(y).unwrap(), width, self.width) if style.outline {
as usize; // {
// let frame_line = self.frame.get_mut(y).unwrap();
// *frame_line =
// overlay(&frame_line, &"_".repeat(width as usize - 2), x as usize);
// // *self.used.get_mut(y - 1).unwrap() += (x as u16 + width + 1) as u16;
// }
// {
// if let Some(frame_line) = self.frame.get_mut(y + height as usize) {
// *frame_line = overlay(
// &frame_line,
// &"⎻".repeat(width as usize - 2),
// x as usize + 1,
// );
// // *self.used.get_mut(y + height as usize).unwrap() +=
// // (x as u16 + width + 1) as u16;
// }
// }
// y += 1;
}
element.render(&mut writer);
for (i, line) in writer.text.lines().enumerate() { for (i, line) in writer.text.lines().enumerate() {
if frame_height > (y + i) as u16 { if frame_height > (y + i) as u16 {
let frame_line = self.frame.get_mut(y + i).unwrap(); let frame_line = self.frame.get_mut(y + i).unwrap();
let (line_, lm) = compress_string(line, &ANSI); if style.outline {
let formatted = format_string(&line_, width as usize); *frame_line = overlay(frame_line, &format!("a{line}b"), x as usize);
*frame_line = } else {
merge_line(&frame_line, (formatted.clone(), lm.clone()), x as usize); *frame_line = overlay(frame_line, line, x as usize);
*self.used.get_mut(y + i).unwrap() += (x + formatted.len()) as u16; }
// *self.used.get_mut(y + i).unwrap() += (x + formatted.len()) as u16;
}
} }
} }
} }
@@ -136,16 +104,25 @@ impl Frame {
pub fn output_nnl(&self) -> String { pub fn output_nnl(&self) -> String {
self.frame.join("") self.frame.join("")
} }
pub fn output_array(&self) -> &Vec<String> {
&self.frame
}
} }
fn format_string(input: &str, width: usize) -> String { // fn format_string(input: &str, width: usize, outline: bool) -> String {
let truncated = if input.len() > width { // let truncated = if input.len() > width {
&input[..width] // let input_chars: Vec<char> = input.chars().collect();
} else { // input_chars[..width].iter().collect::<String>()
input // } else {
}; // input.to_string()
format!("{:<width$}", truncated, width = width) // };
} // if outline {
// format!("a{:<width$}b", truncated, width = width)
// } else {
// format!("{:<width$}", truncated, width = width)
// }
// }
pub fn clear() { pub fn clear() {
print!("\x1B[2J\x1B[H"); print!("\x1B[2J\x1B[H");