diff --git a/packages/tui/Cargo.toml b/packages/tui/Cargo.toml index 9945b04ae..398da95c7 100644 --- a/packages/tui/Cargo.toml +++ b/packages/tui/Cargo.toml @@ -2,7 +2,7 @@ name = "dioxus-tui" version = "0.2.1" authors = ["Jonathan Kelley, @dementhos"] -edition = "2018" +edition = "2021" description = "TUI-based renderer for Dioxus" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" @@ -19,9 +19,6 @@ dioxus-html = { path = "../html", version = "^0.2.0" } tui = "0.17.0" crossterm = "0.23.0" anyhow = "1.0.42" -thiserror = "1.0.24" -ctrlc = "3.2.1" -bumpalo = { version = "3.8.0", features = ["boxed"] } tokio = { version = "1.15.0", features = ["full"] } futures = "0.3.19" stretch2 = "0.4.1" diff --git a/packages/tui/src/attributes.rs b/packages/tui/src/attributes.rs index a1ffcb5ea..7533652f1 100644 --- a/packages/tui/src/attributes.rs +++ b/packages/tui/src/attributes.rs @@ -388,6 +388,15 @@ pub enum UnitSystem { Point(f32), } +impl Into for UnitSystem { + fn into(self) -> Dimension { + match self { + Self::Percent(v) => Dimension::Percent(v), + Self::Point(v) => Dimension::Points(v), + } + } +} + fn parse_value(value: &str) -> Option { if value.ends_with("px") { if let Ok(px) = value.trim_end_matches("px").parse::() { @@ -505,11 +514,16 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { } } "border-bottom-style" => { + if style.style.border.bottom == Dimension::default() { + let v = Dimension::Points(1.0); + style.style.border.bottom = v; + } style.tui_modifier.borders.bottom.style = parse_border_style(value) } "border-bottom-width" => { if let Some(v) = parse_value(value) { style.tui_modifier.borders.bottom.width = v; + style.style.border.bottom = v.into(); } } "border-collapse" => {} @@ -547,10 +561,17 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { style.tui_modifier.borders.left.color = Some(c); } } - "border-left-style" => style.tui_modifier.borders.left.style = parse_border_style(value), + "border-left-style" => { + if style.style.border.start == Dimension::default() { + let v = Dimension::Points(1.0); + style.style.border.start = v; + } + style.tui_modifier.borders.left.style = parse_border_style(value) + } "border-left-width" => { if let Some(v) = parse_value(value) { style.tui_modifier.borders.left.width = v; + style.style.border.start = v.into(); } } "border-radius" => { @@ -581,7 +602,11 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { style.tui_modifier.borders.right.color = Some(c); } } - "border-right-style" => style.tui_modifier.borders.right.style = parse_border_style(value), + "border-right-style" => { + let v = Dimension::Points(1.0); + style.style.border.end = v; + style.tui_modifier.borders.right.style = parse_border_style(value) + } "border-right-width" => { if let Some(v) = parse_value(value) { style.tui_modifier.borders.right.width = v; @@ -590,6 +615,22 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { "border-spacing" => {} "border-style" => { let values: Vec<_> = value.split(' ').collect(); + if style.style.border.top == Dimension::default() { + let v = Dimension::Points(1.0); + style.style.border.top = v; + } + if style.style.border.bottom == Dimension::default() { + let v = Dimension::Points(1.0); + style.style.border.bottom = v; + } + if style.style.border.start == Dimension::default() { + let v = Dimension::Points(1.0); + style.style.border.start = v; + } + if style.style.border.end == Dimension::default() { + let v = Dimension::Points(1.0); + style.style.border.end = v; + } if values.len() == 1 { let border_style = parse_border_style(values[0]); style @@ -623,9 +664,16 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { style.tui_modifier.borders.right.radius = v; } } - "border-top-style" => style.tui_modifier.borders.top.style = parse_border_style(value), + "border-top-style" => { + if style.style.border.top == Dimension::default() { + let v = Dimension::Points(1.0); + style.style.border.top = v; + } + style.tui_modifier.borders.top.style = parse_border_style(value) + } "border-top-width" => { if let Some(v) = parse_value(value) { + style.style.border.top = v.into(); style.tui_modifier.borders.top.width = v; } } @@ -633,6 +681,10 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { let values: Vec<_> = value.split(' ').collect(); if values.len() == 1 { if let Some(w) = parse_value(values[0]) { + style.style.border.top = w.into(); + style.style.border.bottom = w.into(); + style.style.border.start = w.into(); + style.style.border.end = w.into(); style .tui_modifier .borders @@ -641,11 +693,19 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) { .for_each(|b| b.width = w); } } else { - for (v, b) in values + let border_widths = [ + &mut style.style.border.top, + &mut style.style.border.bottom, + &mut style.style.border.start, + &mut style.style.border.end, + ]; + for ((v, b), width) in values .into_iter() .zip(style.tui_modifier.borders.slice().iter_mut()) + .zip(border_widths) { if let Some(w) = parse_value(v) { + *width = w.into(); b.width = w; } } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 963fc277b..2224edb16 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -460,26 +460,7 @@ impl RinkInputHandler { // translate crossterm events into dioxus events fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { let (name, data): (&str, EventData) = match evt { - TermEvent::Key(k) => { - let key = translate_key_code(k.code)?; - ( - "keydown", - // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent - EventData::Keyboard(KeyboardData { - char_code: key.raw_code(), - key: format!("{key:?}"), - key_code: key, - alt_key: k.modifiers.contains(KeyModifiers::ALT), - ctrl_key: k.modifiers.contains(KeyModifiers::CONTROL), - meta_key: false, - shift_key: k.modifiers.contains(KeyModifiers::SHIFT), - locale: Default::default(), - location: 0x00, - repeat: Default::default(), - which: Default::default(), - }), - ) - } + TermEvent::Key(k) => ("keydown", translate_key_event(k)?), TermEvent::Mouse(m) => { let (x, y) = (m.column.into(), m.row.into()); let alt = m.modifiers.contains(KeyModifiers::ALT); @@ -542,69 +523,145 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { Some((name, data)) } -fn translate_key_code(c: TermKeyCode) -> Option { - match c { - TermKeyCode::Backspace => Some(KeyCode::Backspace), - TermKeyCode::Enter => Some(KeyCode::Enter), - TermKeyCode::Left => Some(KeyCode::LeftArrow), - TermKeyCode::Right => Some(KeyCode::RightArrow), - TermKeyCode::Up => Some(KeyCode::UpArrow), - TermKeyCode::Down => Some(KeyCode::DownArrow), - TermKeyCode::Home => Some(KeyCode::Home), - TermKeyCode::End => Some(KeyCode::End), - TermKeyCode::PageUp => Some(KeyCode::PageUp), - TermKeyCode::PageDown => Some(KeyCode::PageDown), - TermKeyCode::Tab => Some(KeyCode::Tab), - TermKeyCode::BackTab => None, - TermKeyCode::Delete => Some(KeyCode::Delete), - TermKeyCode::Insert => Some(KeyCode::Insert), - TermKeyCode::F(fn_num) => match fn_num { - 1 => Some(KeyCode::F1), - 2 => Some(KeyCode::F2), - 3 => Some(KeyCode::F3), - 4 => Some(KeyCode::F4), - 5 => Some(KeyCode::F5), - 6 => Some(KeyCode::F6), - 7 => Some(KeyCode::F7), - 8 => Some(KeyCode::F8), - 9 => Some(KeyCode::F9), - 10 => Some(KeyCode::F10), - 11 => Some(KeyCode::F11), - 12 => Some(KeyCode::F12), - _ => None, - }, - TermKeyCode::Char(c) => match c.to_uppercase().next().unwrap() { - 'A' => Some(KeyCode::A), - 'B' => Some(KeyCode::B), - 'C' => Some(KeyCode::C), - 'D' => Some(KeyCode::D), - 'E' => Some(KeyCode::E), - 'F' => Some(KeyCode::F), - 'G' => Some(KeyCode::G), - 'H' => Some(KeyCode::H), - 'I' => Some(KeyCode::I), - 'J' => Some(KeyCode::J), - 'K' => Some(KeyCode::K), - 'L' => Some(KeyCode::L), - 'M' => Some(KeyCode::M), - 'N' => Some(KeyCode::N), - 'O' => Some(KeyCode::O), - 'P' => Some(KeyCode::P), - 'Q' => Some(KeyCode::Q), - 'R' => Some(KeyCode::R), - 'S' => Some(KeyCode::S), - 'T' => Some(KeyCode::T), - 'U' => Some(KeyCode::U), - 'V' => Some(KeyCode::V), - 'W' => Some(KeyCode::W), - 'X' => Some(KeyCode::X), - 'Y' => Some(KeyCode::Y), - 'Z' => Some(KeyCode::Z), - _ => None, - }, - TermKeyCode::Null => None, - TermKeyCode::Esc => Some(KeyCode::Escape), - } +fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { + let (code, key_str); + if let TermKeyCode::Char(c) = event.code { + code = match c { + 'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() { + 'A' => KeyCode::A, + 'B' => KeyCode::B, + 'C' => KeyCode::C, + 'D' => KeyCode::D, + 'E' => KeyCode::E, + 'F' => KeyCode::F, + 'G' => KeyCode::G, + 'H' => KeyCode::H, + 'I' => KeyCode::I, + 'J' => KeyCode::J, + 'K' => KeyCode::K, + 'L' => KeyCode::L, + 'M' => KeyCode::M, + 'N' => KeyCode::N, + 'O' => KeyCode::O, + 'P' => KeyCode::P, + 'Q' => KeyCode::Q, + 'R' => KeyCode::R, + 'S' => KeyCode::S, + 'T' => KeyCode::T, + 'U' => KeyCode::U, + 'V' => KeyCode::V, + 'W' => KeyCode::W, + 'X' => KeyCode::X, + 'Y' => KeyCode::Y, + 'Z' => KeyCode::Z, + _ => return None, + }, + ' ' => KeyCode::Space, + '[' => KeyCode::OpenBracket, + '{' => KeyCode::OpenBracket, + ']' => KeyCode::CloseBraket, + '}' => KeyCode::CloseBraket, + ';' => KeyCode::Semicolon, + ':' => KeyCode::Semicolon, + ',' => KeyCode::Comma, + '<' => KeyCode::Comma, + '.' => KeyCode::Period, + '>' => KeyCode::Period, + '1' => KeyCode::Num1, + '2' => KeyCode::Num2, + '3' => KeyCode::Num3, + '4' => KeyCode::Num4, + '5' => KeyCode::Num5, + '6' => KeyCode::Num6, + '7' => KeyCode::Num7, + '8' => KeyCode::Num8, + '9' => KeyCode::Num9, + '0' => KeyCode::Num0, + '!' => KeyCode::Num1, + '@' => KeyCode::Num2, + '#' => KeyCode::Num3, + '$' => KeyCode::Num4, + '%' => KeyCode::Num5, + '^' => KeyCode::Num6, + '&' => KeyCode::Num7, + '*' => KeyCode::Num8, + '(' => KeyCode::Num9, + ')' => KeyCode::Num0, + // numpad charicter are ambiguous to tui + // '*' => KeyCode::Multiply, + // '/' => KeyCode::Divide, + // '-' => KeyCode::Subtract, + // '+' => KeyCode::Add, + '+' => KeyCode::EqualSign, + '-' => KeyCode::Dash, + '_' => KeyCode::Dash, + '\'' => KeyCode::SingleQuote, + '"' => KeyCode::SingleQuote, + '\\' => KeyCode::BackSlash, + '|' => KeyCode::BackSlash, + '/' => KeyCode::ForwardSlash, + '?' => KeyCode::ForwardSlash, + '=' => KeyCode::EqualSign, + '`' => KeyCode::GraveAccent, + '~' => KeyCode::GraveAccent, + _ => return None, + }; + key_str = c.to_string(); + } else { + code = match event.code { + TermKeyCode::Esc => KeyCode::Escape, + TermKeyCode::Backspace => KeyCode::Backspace, + TermKeyCode::Enter => KeyCode::Enter, + TermKeyCode::Left => KeyCode::LeftArrow, + TermKeyCode::Right => KeyCode::RightArrow, + TermKeyCode::Up => KeyCode::UpArrow, + TermKeyCode::Down => KeyCode::DownArrow, + TermKeyCode::Home => KeyCode::Home, + TermKeyCode::End => KeyCode::End, + TermKeyCode::PageUp => KeyCode::PageUp, + TermKeyCode::PageDown => KeyCode::PageDown, + TermKeyCode::Tab => KeyCode::Tab, + TermKeyCode::Delete => KeyCode::Delete, + TermKeyCode::Insert => KeyCode::Insert, + TermKeyCode::F(fn_num) => match fn_num { + 1 => KeyCode::F1, + 2 => KeyCode::F2, + 3 => KeyCode::F3, + 4 => KeyCode::F4, + 5 => KeyCode::F5, + 6 => KeyCode::F6, + 7 => KeyCode::F7, + 8 => KeyCode::F8, + 9 => KeyCode::F9, + 10 => KeyCode::F10, + 11 => KeyCode::F11, + 12 => KeyCode::F12, + _ => return None, + }, + TermKeyCode::BackTab => return None, + TermKeyCode::Null => return None, + _ => return None, + }; + key_str = if let KeyCode::BackSlash = code { + "\\".to_string() + } else { + format!("{code:?}") + } + }; + // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent + Some(EventData::Keyboard(KeyboardData { + char_code: code.raw_code(), + key: key_str.to_string(), + key_code: code, + alt_key: event.modifiers.contains(KeyModifiers::ALT), + ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL), + meta_key: false, + shift_key: event.modifiers.contains(KeyModifiers::SHIFT), + locale: Default::default(), + location: 0x00, + repeat: Default::default(), + which: Default::default(), + })) } fn clone_mouse_data(m: &MouseData) -> MouseData { diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index 6a8744efb..0d615d1a5 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -55,6 +55,25 @@ pub fn collect_layout<'a>( tui_modifier: TuiModifier::default(), }; + // handle text modifier elements + if el.namespace.is_none() { + match el.tag { + "b" => apply_attributes("font-weight", "bold", &mut modifier), + "strong" => apply_attributes("font-weight", "bold", &mut modifier), + "u" => apply_attributes("text-decoration", "underline", &mut modifier), + "ins" => apply_attributes("text-decoration", "underline", &mut modifier), + "del" => apply_attributes("text-decoration", "line-through", &mut modifier), + "i" => apply_attributes("font-style", "italic", &mut modifier), + "em" => apply_attributes("font-style", "italic", &mut modifier), + "mark" => apply_attributes( + "background-color", + "rgba(241, 231, 64, 50%)", + &mut modifier, + ), + _ => (), + } + } + for &Attribute { name, value, .. } in el.attributes { apply_attributes(name, value, &mut modifier); } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 91ff7db4d..f90967802 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -172,7 +172,7 @@ pub fn render_vdom( match evt.as_ref().unwrap() { InputEvent::UserInput(event) => match event { TermEvent::Key(key) => { - if matches!(key.code, KeyCode::Char('c')) + if matches!(key.code, KeyCode::Char('C' | 'c')) && key.modifiers.contains(KeyModifiers::CONTROL) { break;