diff --git a/Cargo.toml b/Cargo.toml index 8b8b96616..1837ad5ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tui = { version = "0.16.0", features = ["crossterm"], default-features = false } +tui = "0.17.0" crossterm = "0.22.1" anyhow = "1.0.42" thiserror = "1.0.24" diff --git a/examples/border.rs b/examples/border.rs new file mode 100644 index 000000000..2e267b271 --- /dev/null +++ b/examples/border.rs @@ -0,0 +1,30 @@ +use dioxus::prelude::*; + +fn main() { + rink::launch(app); +} + +fn app(cx: Scope) -> Element { + let (radius, set_radius) = use_state(&cx, || 0); + + cx.render(rsx! { + div { + width: "100%", + height: "100%", + justify_content: "center", + align_items: "center", + background_color: "hsl(248, 53%, 58%)", + onwheel: move |w| set_radius((radius + w.delta_y as i8).abs()), + + // the border can either be solid, double, thick, OR rounded + // if multable are set only the last style is appiled + // to skip a side set the style to none + border_style: "solid none solid double", + border_width: "thick", + border_radius: "{radius}px", + border_color: "#0000FF #FF00FF #FF0000 #00FF00", + + "{radius}" + } + }) +} diff --git a/examples/text.rs b/examples/text.rs index db49cd733..21a7654ad 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -53,13 +53,6 @@ fn app(cx: Scope) -> Element { "zib" "zib" "zib" - "zib" - "zib" - "zib" - "zib" - "zib" - "zib" - "zib" } p { background_color: "yellow", @@ -77,6 +70,32 @@ fn app(cx: Scope) -> Element { background_color: "cyan", "asd" } + div { + font_weight: "bold", + color: "#666666", + p{ + "bold" + } + p { + font_weight: "normal", + " normal" + } + } + p { + font_style: "italic", + color: "red", + "italic" + } + p { + text_decoration: "underline", + color: "rgb(50, 100, 255)", + "underline" + } + p { + text_decoration: "line-through", + color: "hsl(10, 100%, 70%)", + "line-through" + } } }) } diff --git a/src/attributes.rs b/src/attributes.rs index d84779406..b4a5691f3 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -30,15 +30,155 @@ */ use stretch2::{prelude::*, style::PositionType, style::Style}; -use tui::style::Style as TuiStyle; +use tui::style::{Color, Style as TuiStyle}; pub struct StyleModifer { pub style: Style, pub tui_style: TuiStyle, + pub tui_modifier: TuiModifier, } -enum TuiModifier { - Text, +pub struct TuiModifier { + // border arrays start at the top and proceed clockwise + pub border_colors: [Option; 4], + pub border_types: [BorderType; 4], + pub border_widths: [UnitSystem; 4], + pub border_radi: [UnitSystem; 4], +} + +#[derive(Clone, Copy)] +pub enum BorderType { + DOTTED, + DASHED, + SOLID, + DOUBLE, + GROOVE, + RIDGE, + INSET, + OUTSET, + HIDDEN, + NONE, +} + +impl Default for TuiModifier { + fn default() -> Self { + Self { + border_colors: [None; 4], + border_types: [BorderType::NONE; 4], + border_widths: [UnitSystem::Point(0.0); 4], + border_radi: [UnitSystem::Point(0.0); 4], + } + } +} + +fn parse_color(color: &str) -> Option { + match color { + "red" => Some(Color::Red), + "green" => Some(Color::Green), + "blue" => Some(Color::Blue), + "yellow" => Some(Color::Yellow), + "cyan" => Some(Color::Cyan), + "magenta" => Some(Color::Magenta), + "white" => Some(Color::White), + "black" => Some(Color::Black), + _ => { + if color.len() == 7 && color.starts_with('#') { + let mut values = [0, 0, 0]; + let mut color_ok = true; + for i in 0..values.len() { + if let Ok(v) = u8::from_str_radix(&color[(1 + 2 * i)..(1 + 2 * (i + 1))], 16) { + values[i] = v; + } else { + color_ok = false; + } + } + if color_ok { + Some(Color::Rgb(values[0], values[1], values[2])) + } else { + None + } + } else if color.starts_with("rgb(") { + let mut values = [0, 0, 0]; + let mut color_ok = true; + for (v, i) in color[4..] + .trim_end_matches(')') + .split(',') + .zip(0..values.len()) + { + if let Ok(v) = v.trim().parse() { + values[i] = v; + } else { + color_ok = false; + } + } + if color_ok { + Some(Color::Rgb(values[0], values[1], values[2])) + } else { + None + } + } else if color.starts_with("hsl(") { + let mut values = [0, 0, 0]; + let mut color_ok = true; + for (v, i) in color[4..] + .trim_end_matches(')') + .split(',') + .zip(0..values.len()) + { + if let Ok(v) = v.trim_end_matches('%').trim().parse() { + values[i] = v; + } else { + color_ok = false; + } + } + if color_ok { + let [h, s, l] = [ + values[0] as f32 / 360.0, + values[1] as f32 / 100.0, + values[2] as f32 / 100.0, + ]; + let rgb = if s == 0.0 { + [l as u8; 3] + } else { + fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 { + if t < 0.0 { + t += 1.0; + } + if t > 1.0 { + t -= 1.0; + } + if t < 1.0 / 6.0 { + p + (q - p) * 6.0 * t + } else if t < 1.0 / 2.0 { + q + } else if t < 2.0 / 3.0 { + p + (q - p) * (2.0 / 3.0 - t) * 6.0 + } else { + p + } + } + + let q = if l < 0.5 { + l * (1.0 + s) + } else { + l + s - l * s + }; + let p = 2.0 * l - q; + [ + (hue_to_rgb(p, q, h + 1.0 / 3.0) * 255.0) as u8, + (hue_to_rgb(p, q, h) * 255.0) as u8, + (hue_to_rgb(p, q, h - 1.0 / 3.0) * 255.0) as u8, + ] + }; + + Some(Color::Rgb(rgb[0], rgb[1], rgb[2])) + } else { + None + } + } else { + None + } + } + } } /// applies the entire html namespace defined in dioxus-html @@ -117,7 +257,9 @@ pub fn apply_attributes( "clip" => {} "color" => { - // text color + if let Some(c) = parse_color(value) { + style.tui_style.fg.replace(c); + } } "column-count" @@ -170,14 +312,11 @@ pub fn apply_attributes( | "font-weight" => apply_font(name, value, style), "height" => { - if value.ends_with("%") { - if let Ok(pct) = value.trim_end_matches("%").parse::() { - style.style.size.height = Dimension::Percent(pct / 100.0); - } - } else if value.ends_with("px") { - if let Ok(px) = value.trim_end_matches("px").parse::() { - style.style.size.height = Dimension::Points(px); - } + if let Some(v) = parse_value(value){ + style.style.size.height = match v { + UnitSystem::Percent(v)=> Dimension::Percent(v/100.0), + UnitSystem::Point(v)=> Dimension::Points(v), + }; } } "justify-content" => { @@ -286,14 +425,11 @@ pub fn apply_attributes( "visibility" => {} "white-space" => {} "width" => { - if value.ends_with("%") { - if let Ok(pct) = value.trim_end_matches("%").parse::() { - style.style.size.width = Dimension::Percent(pct / 100.0); - } - } else if value.ends_with("px") { - if let Ok(px) = value.trim_end_matches("px").parse::() { - style.style.size.width = Dimension::Points(px); - } + if let Some(v) = parse_value(value){ + style.style.size.width = match v { + UnitSystem::Percent(v)=> Dimension::Percent(v/100.0), + UnitSystem::Point(v)=> Dimension::Points(v), + }; } } "word-break" => {} @@ -304,7 +440,8 @@ pub fn apply_attributes( } } -enum UnitSystem { +#[derive(Clone, Copy)] +pub enum UnitSystem { Percent(f32), Point(f32), } @@ -344,7 +481,6 @@ fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) { } fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) { - use stretch2::style::Display; style.style.display = match value { "flex" => Display::Flex, "block" => Display::None, @@ -376,40 +512,9 @@ fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) { fn apply_background(name: &str, value: &str, style: &mut StyleModifer) { match name { "background-color" => { - use tui::style::Color; - match value { - "red" => style.tui_style.bg.replace(Color::Red), - "green" => style.tui_style.bg.replace(Color::Green), - "blue" => style.tui_style.bg.replace(Color::Blue), - "yellow" => style.tui_style.bg.replace(Color::Yellow), - "cyan" => style.tui_style.bg.replace(Color::Cyan), - "magenta" => style.tui_style.bg.replace(Color::Magenta), - "white" => style.tui_style.bg.replace(Color::White), - "black" => style.tui_style.bg.replace(Color::Black), - _ => { - if value.len() == 7 { - let mut values = [0, 0, 0]; - let mut color_ok = true; - for i in 0..values.len() { - if let Ok(v) = - u8::from_str_radix(&value[(1 + 2 * i)..(1 + 2 * (i + 1))], 16) - { - values[i] = v; - } else { - color_ok = false; - } - } - if color_ok { - let color = Color::Rgb(values[0], values[1], values[2]); - style.tui_style.bg.replace(color) - } else { - None - } - } else { - None - } - } - }; + if let Some(c) = parse_color(value) { + style.tui_style.bg.replace(c); + } } "background" => {} "background-attachment" => {} @@ -423,17 +528,63 @@ fn apply_background(name: &str, value: &str, style: &mut StyleModifer) { } } -fn apply_border(name: &str, value: &str, _style: &mut StyleModifer) { +fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { + fn parse_border_type(v: &str) -> BorderType { + match v { + "dotted" => BorderType::DOTTED, + "dashed" => BorderType::DASHED, + "solid" => BorderType::SOLID, + "double" => BorderType::DOUBLE, + "groove" => BorderType::GROOVE, + "ridge" => BorderType::RIDGE, + "inset" => BorderType::INSET, + "outset" => BorderType::OUTSET, + "none" => BorderType::NONE, + "hidden" => BorderType::HIDDEN, + _ => todo!(), + } + } match name { "border" => {} "border-bottom" => {} - "border-bottom-color" => {} - "border-bottom-left-radius" => {} - "border-bottom-right-radius" => {} - "border-bottom-style" => {} - "border-bottom-width" => {} + "border-bottom-color" => { + if let Some(c) = parse_color(value) { + style.tui_modifier.border_colors[2] = Some(c); + } + } + "border-bottom-left-radius" => { + if let Some(v) = parse_value(value) { + style.tui_modifier.border_radi[2] = v; + } + } + "border-bottom-right-radius" => { + if let Some(v) = parse_value(value) { + style.tui_modifier.border_radi[1] = v; + } + } + "border-bottom-style" => style.tui_modifier.border_types[2] = parse_border_type(value), + "border-bottom-width" => { + if let Some(v) = parse_value(value) { + style.tui_modifier.border_widths[2] = v; + } + } "border-collapse" => {} - "border-color" => {} + "border-color" => { + let values: Vec<_> = value.split(' ').collect(); + if values.len() == 1 { + if let Some(c) = parse_color(values[0]) { + for i in 0..4 { + style.tui_modifier.border_colors[i] = Some(c); + } + } + } else { + for (i, v) in values.into_iter().enumerate() { + if let Some(c) = parse_color(v) { + style.tui_modifier.border_colors[i] = Some(c); + } + } + } + } "border-image" => {} "border-image-outset" => {} "border-image-repeat" => {} @@ -441,28 +592,89 @@ fn apply_border(name: &str, value: &str, _style: &mut StyleModifer) { "border-image-source" => {} "border-image-width" => {} "border-left" => {} - "border-left-color" => {} - "border-left-style" => {} - "border-left-width" => {} - "border-radius" => {} - "border-right" => {} - "border-right-color" => {} - "border-right-style" => {} - "border-right-width" => {} - "border-spacing" => {} - "border-style" => {} - "border-top" => {} - "border-top-color" => {} - "border-top-left-radius" => {} - "border-top-right-radius" => {} - "border-top-style" => {} - "border-top-width" => {} - "border-width" => { - if let Ok(_px) = value.trim_end_matches("px").parse::() { - // tuistyle = px; + "border-left-color" => { + if let Some(c) = parse_color(value) { + style.tui_modifier.border_colors[3] = Some(c); } } - _ => {} + "border-left-style" => style.tui_modifier.border_types[3] = parse_border_type(value), + "border-left-width" => { + if let Some(v) = parse_value(value) { + style.tui_modifier.border_widths[3] = v; + } + } + "border-radius" => { + let values: Vec<_> = value.split(' ').collect(); + if values.len() == 1 { + if let Some(r) = parse_value(values[0]) { + for i in 0..4 { + style.tui_modifier.border_radi[i] = r; + } + } + } else { + for (i, v) in values.into_iter().enumerate() { + if let Some(r) = parse_value(v) { + style.tui_modifier.border_radi[i] = r; + } + } + } + } + "border-right" => {} + "border-right-color" => { + if let Some(c) = parse_color(value) { + style.tui_modifier.border_colors[1] = Some(c); + } + } + "border-right-style" => style.tui_modifier.border_types[1] = parse_border_type(value), + "border-right-width" => { + if let Some(v) = parse_value(value) { + style.tui_modifier.border_widths[1] = v; + } + } + "border-spacing" => {} + "border-style" => { + let values: Vec<_> = value.split(' ').collect(); + if values.len() == 1 { + let border = parse_border_type(values[0]); + for i in 0..4 { + style.tui_modifier.border_types[i] = border; + } + } else { + for (i, v) in values.into_iter().enumerate() { + style.tui_modifier.border_types[i] = parse_border_type(v); + } + } + } + "border-top" => {} + "border-top-color" => { + if let Some(c) = parse_color(value) { + style.tui_modifier.border_colors[0] = Some(c); + } + } + "border-top-left-radius" => { + if let Some(v) = parse_value(value) { + style.tui_modifier.border_radi[3] = v; + } + } + "border-top-right-radius" => { + if let Some(v) = parse_value(value) { + style.tui_modifier.border_radi[0] = v; + } + } + "border-top-style" => style.tui_modifier.border_types[0] = parse_border_type(value), + "border-top-width" => { + if let Some(v) = parse_value(value) { + style.tui_modifier.border_widths[0] = v; + } + } + "border-width" => { + if let Some(v) = parse_value(value) { + for i in 0..4 { + style.tui_modifier.border_widths[i] = v; + } + } + } + _ => (), } } @@ -516,14 +728,11 @@ fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) { }; } "flex-basis" => { - if value.ends_with("%") { - if let Ok(pct) = value.trim_end_matches("%").parse::() { - style.style.flex_basis = Dimension::Percent(pct / 100.0); - } - } else if value.ends_with("px") { - if let Ok(px) = value.trim_end_matches("px").parse::() { - style.style.flex_basis = Dimension::Points(px); - } + if let Some(v) = parse_value(value) { + style.style.flex_basis = match v { + UnitSystem::Percent(v) => Dimension::Percent(v / 100.0), + UnitSystem::Point(v) => Dimension::Points(v), + }; } } "flex-flow" => {} @@ -550,8 +759,27 @@ fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) { } } -fn apply_font(_name: &str, _value: &str, _style: &mut StyleModifer) { - todo!() +fn apply_font(name: &str, value: &str, style: &mut StyleModifer) { + use tui::style::Modifier; + match name { + "font" => todo!(), + "font-family" => todo!(), + "font-size" => todo!(), + "font-size-adjust" => todo!(), + "font-stretch" => todo!(), + "font-style" => match value { + "italic" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC), + "oblique" => style.tui_style = style.tui_style.add_modifier(Modifier::ITALIC), + _ => (), + }, + "font-variant" => todo!(), + "font-weight" => match value { + "bold" => style.tui_style = style.tui_style.add_modifier(Modifier::BOLD), + "normal" => style.tui_style = style.tui_style.remove_modifier(Modifier::BOLD), + _ => (), + }, + _ => (), + } } fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) { @@ -587,8 +815,34 @@ fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) { } } -fn apply_text(_name: &str, _value: &str, _style: &mut StyleModifer) { - todo!() +fn apply_text(name: &str, value: &str, style: &mut StyleModifer) { + use tui::style::Modifier; + + match name { + "text-align" => todo!(), + "text-align-last" => todo!(), + "text-decoration" | "text-decoration-line" => { + for v in value.split(' ') { + match v { + "line-through" => { + style.tui_style = style.tui_style.add_modifier(Modifier::CROSSED_OUT) + } + "underline" => { + style.tui_style = style.tui_style.add_modifier(Modifier::UNDERLINED) + } + _ => (), + } + } + } + "text-decoration-color" => todo!(), + "text-decoration-style" => todo!(), + "text-indent" => todo!(), + "text-justify" => todo!(), + "text-overflow" => todo!(), + "text-shadow" => todo!(), + "text-transform" => todo!(), + _ => todo!(), + } } fn apply_transform(_name: &str, _value: &str, _style: &mut StyleModifer) { diff --git a/src/layout.rs b/src/layout.rs index 94f750990..d9cf6ad21 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -4,7 +4,7 @@ use tui::style::Style as TuiStyle; use crate::{ attributes::{apply_attributes, StyleModifer}, - TuiNode, + TuiModifier, TuiNode, }; /* @@ -42,6 +42,7 @@ pub fn collect_layout<'a>( TuiNode { node, block_style: tui::style::Style::default(), + tui_modifier: TuiModifier::default(), layout: layout.new_node(style, &[]).unwrap(), }, ); @@ -51,6 +52,7 @@ pub fn collect_layout<'a>( let mut modifier = StyleModifer { style: Style::default(), tui_style: TuiStyle::default(), + tui_modifier: TuiModifier::default(), }; for &Attribute { name, value, .. } in el.attributes { @@ -86,6 +88,7 @@ pub fn collect_layout<'a>( TuiNode { node, block_style: modifier.tui_style, + tui_modifier: modifier.tui_modifier, layout: layout.new_node(modifier.style, &child_layout).unwrap(), }, ); diff --git a/src/lib.rs b/src/lib.rs index 145e59d1b..c3cba6970 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,7 @@ pub fn launch(app: Component<()>) { pub struct TuiNode<'a> { pub layout: stretch2::node::Node, pub block_style: TuiStyle, + pub tui_modifier: TuiModifier, pub node: &'a VNode<'a>, } @@ -132,7 +133,14 @@ pub fn render_vdom( // resolve events before rendering events = handler.get_events(vdom, &layout, &mut nodes, root_node); - render::render_vnode(frame, &layout, &mut nodes, vdom, root_node); + render::render_vnode( + frame, + &layout, + &mut nodes, + vdom, + root_node, + &TuiStyle::default(), + ); assert!(nodes.is_empty()); })?; diff --git a/src/render.rs b/src/render.rs index c997d5302..83b30046c 100644 --- a/src/render.rs +++ b/src/render.rs @@ -9,11 +9,320 @@ use tui::{ backend::CrosstermBackend, buffer::Buffer, layout::Rect, - style::Style as TuiStyle, - widgets::{Block, Widget}, + style::{Color, Style as TuiStyle}, + widgets::Widget, }; -use crate::TuiNode; +use crate::{BorderType, TuiNode, UnitSystem}; + +const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; + +impl<'a> Widget for TuiNode<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + use tui::symbols::line::*; + + enum Direction { + Left, + Right, + Up, + Down, + } + + fn draw( + buf: &mut Buffer, + points_history: [[i32; 2]; 3], + symbols: &Set, + pos: [u16; 2], + color: &Option, + ) { + let [before, current, after] = points_history; + let start_dir = match [before[0] - current[0], before[1] - current[1]] { + [1, 0] => Direction::Right, + [-1, 0] => Direction::Left, + [0, 1] => Direction::Down, + [0, -1] => Direction::Up, + [a, b] => { + panic!( + "draw({:?} {:?} {:?}) {}, {} no cell adjacent", + before, current, after, a, b + ) + } + }; + let end_dir = match [after[0] - current[0], after[1] - current[1]] { + [1, 0] => Direction::Right, + [-1, 0] => Direction::Left, + [0, 1] => Direction::Down, + [0, -1] => Direction::Up, + [a, b] => { + panic!( + "draw({:?} {:?} {:?}) {}, {} no cell adjacent", + before, current, after, a, b + ) + } + }; + + let cell = buf.get_mut( + (current[0] + pos[0] as i32) as u16, + (current[1] + pos[1] as i32) as u16, + ); + if let Some(c) = color { + cell.fg = *c; + } + cell.symbol = match [start_dir, end_dir] { + [Direction::Down, Direction::Up] => symbols.vertical, + [Direction::Down, Direction::Right] => symbols.top_left, + [Direction::Down, Direction::Left] => symbols.top_right, + [Direction::Up, Direction::Down] => symbols.vertical, + [Direction::Up, Direction::Right] => symbols.bottom_left, + [Direction::Up, Direction::Left] => symbols.bottom_right, + [Direction::Right, Direction::Left] => symbols.horizontal, + [Direction::Right, Direction::Up] => symbols.bottom_left, + [Direction::Right, Direction::Down] => symbols.top_left, + [Direction::Left, Direction::Up] => symbols.bottom_right, + [Direction::Left, Direction::Right] => symbols.horizontal, + [Direction::Left, Direction::Down] => symbols.top_right, + _ => panic!( + "{:?} {:?} {:?} cannont connect cell to itself", + before, current, after + ), + } + .to_string(); + } + + fn draw_arc( + pos: [u16; 2], + starting_angle: f32, + arc_angle: f32, + radius: f32, + symbols: &Set, + buf: &mut Buffer, + color: &Option, + ) { + if radius < 0.0 { + return; + } + + let num_points = (radius * arc_angle) as i32; + let starting_point = [ + (starting_angle.cos() * (radius * RADIUS_MULTIPLIER[0])) as i32, + (starting_angle.sin() * (radius * RADIUS_MULTIPLIER[1])) as i32, + ]; + let mut points_history = [ + [0, 0], + { + // change the x or y value based on which one is changing quicker + let ddx = -starting_angle.sin(); + let ddy = starting_angle.cos(); + if ddx.abs() > ddy.abs() { + [starting_point[0] - ddx.signum() as i32, starting_point[1]] + } else { + [starting_point[0], starting_point[1] - ddy.signum() as i32] + } + }, + starting_point, + ]; + + for i in 1..=num_points { + let angle = (i as f32 / num_points as f32) * arc_angle + starting_angle; + let x = angle.cos() * radius * RADIUS_MULTIPLIER[0]; + let y = angle.sin() * radius * RADIUS_MULTIPLIER[1]; + let new = [x as i32, y as i32]; + + if new != points_history[2] { + points_history = [points_history[1], points_history[2], new]; + + let dx = points_history[2][0] - points_history[1][0]; + let dy = points_history[2][1] - points_history[1][1]; + // fill diagonals + if dx != 0 && dy != 0 { + let connecting_point = match [dx, dy] { + [1, 1] => [points_history[1][0] + 1, points_history[1][1]], + [1, -1] => [points_history[1][0], points_history[1][1] - 1], + [-1, 1] => [points_history[1][0], points_history[1][1] + 1], + [-1, -1] => [points_history[1][0] - 1, points_history[1][1]], + _ => todo!(), + }; + draw( + buf, + [points_history[0], points_history[1], connecting_point], + &symbols, + pos, + color, + ); + points_history = [points_history[1], connecting_point, points_history[2]]; + } + + draw(buf, points_history, &symbols, pos, color); + } + } + + points_history = [points_history[1], points_history[2], { + // change the x or y value based on which one is changing quicker + let ddx = -(starting_angle + arc_angle).sin(); + let ddy = (starting_angle + arc_angle).cos(); + if ddx.abs() > ddy.abs() { + [ + points_history[2][0] + ddx.signum() as i32, + points_history[2][1], + ] + } else { + [ + points_history[2][0], + points_history[2][1] + ddy.signum() as i32, + ] + } + }]; + + draw(buf, points_history, &symbols, pos, color); + } + + if area.area() == 0 { + return; + } + + for i in 0..4 { + // the radius for the curve between this line and the next + let r = match self.tui_modifier.border_types[(i + 1) % 4] { + BorderType::HIDDEN => 0.0, + BorderType::NONE => 0.0, + _ => match self.tui_modifier.border_radi[i] { + UnitSystem::Percent(p) => p * area.width as f32 / 100.0, + UnitSystem::Point(p) => p, + } + .abs() + .min((area.width as f32 / RADIUS_MULTIPLIER[0]) / 2.0) + .min((area.height as f32 / RADIUS_MULTIPLIER[1]) / 2.0), + }; + let radius = [ + (r * RADIUS_MULTIPLIER[0]) as u16, + (r * RADIUS_MULTIPLIER[1]) as u16, + ]; + + // the radius for the curve between this line and the last + let last_idx = if i == 0 { 3 } else { i - 1 }; + let last_r = match self.tui_modifier.border_types[last_idx] { + BorderType::HIDDEN => 0.0, + BorderType::NONE => 0.0, + _ => match self.tui_modifier.border_radi[last_idx] { + UnitSystem::Percent(p) => p * area.width as f32 / 100.0, + UnitSystem::Point(p) => p, + } + .abs() + .min((area.width as f32 / RADIUS_MULTIPLIER[0]) / 2.0) + .min((area.height as f32 / RADIUS_MULTIPLIER[1]) / 2.0), + }; + let last_radius = [ + (last_r * RADIUS_MULTIPLIER[0]) as u16, + (last_r * RADIUS_MULTIPLIER[1]) as u16, + ]; + + let symbols = match self.tui_modifier.border_types[i] { + BorderType::DOTTED => NORMAL, + BorderType::DASHED => NORMAL, + BorderType::SOLID => NORMAL, + BorderType::DOUBLE => DOUBLE, + BorderType::GROOVE => NORMAL, + BorderType::RIDGE => NORMAL, + BorderType::INSET => NORMAL, + BorderType::OUTSET => NORMAL, + BorderType::HIDDEN => continue, + BorderType::NONE => continue, + }; + + let color = self.tui_modifier.border_colors[i].or(self.block_style.fg); + + match i { + 0 => { + for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) { + let cell = buf.get_mut(x, area.top()); + if let Some(c) = color { + cell.fg = c; + } + cell.symbol = symbols.horizontal.to_string(); + } + } + 1 => { + for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) { + let cell = buf.get_mut(area.right() - 1, y); + if let Some(c) = color { + cell.fg = c; + } + cell.symbol = symbols.vertical.to_string(); + } + } + 2 => { + for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) { + let cell = buf.get_mut(x, area.bottom() - 1); + if let Some(c) = color { + cell.fg = c; + } + cell.symbol = symbols.horizontal.to_string(); + } + } + 3 => { + for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) { + let cell = buf.get_mut(area.left(), y); + if let Some(c) = color { + cell.fg = c; + } + cell.symbol = symbols.vertical.to_string(); + } + } + _ => (), + } + + match i { + 0 => draw_arc( + [area.right() - radius[0] - 1, area.top() + radius[1]], + std::f32::consts::FRAC_PI_2 * 3.0, + std::f32::consts::FRAC_PI_2, + r, + &symbols, + buf, + &color, + ), + 1 => draw_arc( + [area.right() - radius[0] - 1, area.bottom() - radius[1] - 1], + 0.0, + std::f32::consts::FRAC_PI_2, + r, + &symbols, + buf, + &color, + ), + 2 => draw_arc( + [area.left() + radius[0], area.bottom() - radius[1] - 1], + std::f32::consts::FRAC_PI_2, + std::f32::consts::FRAC_PI_2, + r, + &symbols, + buf, + &color, + ), + 3 => draw_arc( + [area.left() + radius[0], area.top() + radius[1]], + std::f32::consts::PI, + std::f32::consts::FRAC_PI_2, + r, + &symbols, + buf, + &color, + ), + _ => panic!("more than 4 sides?"), + } + } + + // todo: only render inside borders + for x in area.left()..area.right() { + for y in area.top()..area.bottom() { + let cell = buf.get_mut(x, y); + if let Some(c) = self.block_style.bg { + cell.bg = c; + } + } + } + } +} pub fn render_vnode<'a>( frame: &mut tui::Frame>, @@ -21,11 +330,13 @@ pub fn render_vnode<'a>( layouts: &mut HashMap>, vdom: &'a VirtualDom, node: &'a VNode<'a>, + // this holds the parents syle state for styled text rendering and potentially transparentcy + style: &TuiStyle, ) { match node { VNode::Fragment(f) => { for child in f.children { - render_vnode(frame, layout, layouts, vdom, child); + render_vnode(frame, layout, layouts, vdom, child, style); } return; } @@ -33,7 +344,7 @@ pub fn render_vnode<'a>( VNode::Component(vcomp) => { let idx = vcomp.scope.get().unwrap(); let new_node = vdom.get_scope(idx).unwrap().root_node(); - render_vnode(frame, layout, layouts, vdom, new_node); + render_vnode(frame, layout, layouts, vdom, new_node, style); return; } @@ -55,11 +366,12 @@ pub fn render_vnode<'a>( #[derive(Default)] struct Label<'a> { text: &'a str, + style: TuiStyle, } impl<'a> Widget for Label<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - buf.set_string(area.left(), area.top(), self.text, TuiStyle::default()); + buf.set_string(area.left(), area.top(), self.text, self.style); } } @@ -67,7 +379,10 @@ pub fn render_vnode<'a>( // Block::default(). - let label = Label { text: t.text }; + let label = Label { + text: t.text, + style: *style, + }; let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); // the renderer will panic if a node is rendered out of range even if the size is zero @@ -76,16 +391,17 @@ pub fn render_vnode<'a>( } } VNode::Element(el) => { - let block = Block::default().style(node.block_style); let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16); + let new_style = style.patch(node.block_style); + // the renderer will panic if a node is rendered out of range even if the size is zero if area.width > 0 && area.height > 0 { - frame.render_widget(block, area); + frame.render_widget(node, area); } for el in el.children { - render_vnode(frame, layout, layouts, vdom, el); + render_vnode(frame, layout, layouts, vdom, el, &new_style); } } VNode::Fragment(_) => todo!(),