styling
This commit is contained in:
+4
-6
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+74
-28
@@ -5,11 +5,10 @@
|
||||
/// # 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 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
@@ -6,4 +6,4 @@
|
||||
|
||||
`x`, `y`: `n`, `n%`, `center`, `auto`;
|
||||
|
||||
`display`: `row`, `column`, `overlap`;
|
||||
<!-- `display`: `row`, `column`, `overlap`; -->
|
||||
Reference in New Issue
Block a user