This commit is contained in:
Evan Almloff committed 2022-02-04 20:19:19 -06:00
commit dd60c9ce3c
8 files changed
+103 -108

No files matched your search

+61 -27
View File
@@ -1,12 +1,67 @@
# Rink: Like "Ink" but for Rust and Dioxus <div align="center">
<h1>Rink</h1>
<p>
<strong>Beautiful terminal user interfaces in Rust with <a href="https://dioxuslabs.com/">Dioxus </a>.</strong>
</p>
</div>
The fastest portable TUIs in the west <div align="center">
🔫🤠🔫 <!-- Crates version -->
🐎🔥🔥🔥 <a href="https://crates.io/crates/dioxus">
<img src="https://img.shields.io/crates/v/dioxus.svg?style=flat-square"
alt="Crates.io version" />
</a>
<!-- Downloads -->
<a href="https://crates.io/crates/dioxus">
<img src="https://img.shields.io/crates/d/dioxus.svg?style=flat-square"
alt="Download" />
</a>
<!-- docs -->
<a href="https://docs.rs/dioxus">
<img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
alt="docs.rs docs" />
</a>
<!-- CI -->
<a href="https://github.com/jkelleyrtp/dioxus/actions">
<img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
alt="CI status" />
</a>
Rink lets you build terminal user interfaces in Rust with [`Dioxus`](https://dioxuslabs.com/). <!--Awesome -->
<a href="https://github.com/dioxuslabs/awesome-dioxus">
<img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome Page" />
</a>
<!-- Discord -->
<a href="https://discord.gg/XgGxMSkvUM">
<img src="https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square" alt="Discord Link" />
</a>
</div>
You can use Html-like semantics with stylesheets, inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application.
<br/>
Leverage React-like patterns, CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces with Dioxus.
```rust
fn app(cx: Scope) -> Element {
cx.render(rsx!{
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
"Hello world!"
}
})
}
```
![demo app](examples/example.png)
## Background
You can use Html-like semantics with stylesheets, inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application.
Rink is basically a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful. Rink is basically a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Rink doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.
@@ -17,27 +72,6 @@ Terminals can only render a subset of HTML. We support as much as we can.
- **Particular frontend design** - **Particular frontend design**
Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers. Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers.
## Example
Let's print `Hello world!` in the center of the screen.
```rust
static App: FC<()> = |cx| {
cx.render(rsx!{
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
"Hello world!"
}
})
}
```
![demo app](examples/example.png)
## Status ## Status
+2 -2
View File
@@ -7,10 +7,10 @@ fn main() {
#[derive(Props, PartialEq)] #[derive(Props, PartialEq)]
struct QuadrentProps { struct QuadrentProps {
color: String, color: String,
text: String text: String,
} }
fn Quadrent(cx: Scope<QuadrentProps>) -> Element{ fn Quadrent(cx: Scope<QuadrentProps>) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { div {
border_width: "1px", border_width: "1px",
-3
View File
@@ -1,8 +1,5 @@
use std::cell::RefCell;
use crossterm::event::{KeyCode, KeyEvent, MouseEvent}; use crossterm::event::{KeyCode, KeyEvent, MouseEvent};
use dioxus::prelude::*; use dioxus::prelude::*;
use rink::InputHandler;
fn main() { fn main() {
rink::launch(app); rink::launch(app);
+2 -3
View File
@@ -1,7 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use dioxus::{core::ElementId, prelude::*}; use dioxus::prelude::*;
use rink::TuiNode;
fn main() { fn main() {
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
@@ -19,7 +18,7 @@ fn main() {
.compute_layout(node.layout, stretch2::geometry::Size::undefined()) .compute_layout(node.layout, stretch2::geometry::Size::undefined())
.unwrap(); .unwrap();
for (id, node) in nodes.drain() { for (_id, node) in nodes.drain() {
println!("{:?}", layout.layout(node.layout)); println!("{:?}", layout.layout(node.layout));
} }
} }
+20 -21
View File
@@ -343,7 +343,7 @@ fn apply_overflow(name: &str, value: &str, style: &mut StyleModifer) {
} }
} }
fn apply_display(name: &str, value: &str, style: &mut StyleModifer) { fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) {
use stretch2::style::Display; use stretch2::style::Display;
style.style.display = match value { style.style.display = match value {
"flex" => Display::Flex, "flex" => Display::Flex,
@@ -387,29 +387,28 @@ fn apply_background(name: &str, value: &str, style: &mut StyleModifer) {
"white" => style.tui_style.bg.replace(Color::White), "white" => style.tui_style.bg.replace(Color::White),
"black" => style.tui_style.bg.replace(Color::Black), "black" => style.tui_style.bg.replace(Color::Black),
_ => { _ => {
if value.len() == 7{ if value.len() == 7 {
let mut values = [0, 0, 0]; let mut values = [0, 0, 0];
let mut color_ok = true; let mut color_ok = true;
for i in 0..values.len(){ for i in 0..values.len() {
if let Ok(v) = u8::from_str_radix(&value[(1+2*i)..(1+2*(i+1))], 16){ if let Ok(v) =
u8::from_str_radix(&value[(1 + 2 * i)..(1 + 2 * (i + 1))], 16)
{
values[i] = v; values[i] = v;
} } else {
else{
color_ok = false; color_ok = false;
} }
} }
if color_ok{ if color_ok {
let color = Color::Rgb(values[0], values[1], values[2]); let color = Color::Rgb(values[0], values[1], values[2]);
style.tui_style.bg.replace(color) style.tui_style.bg.replace(color)
} } else {
else{
None None
} }
} } else {
else{
None None
} }
}, }
}; };
} }
"background" => {} "background" => {}
@@ -424,7 +423,7 @@ 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) {
match name { match name {
"border" => {} "border" => {}
"border-bottom" => {} "border-bottom" => {}
@@ -459,7 +458,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
"border-top-style" => {} "border-top-style" => {}
"border-top-width" => {} "border-top-width" => {}
"border-width" => { "border-width" => {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() { if let Ok(_px) = value.trim_end_matches("px").parse::<f32>() {
// tuistyle = px; // tuistyle = px;
} }
} }
@@ -467,7 +466,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
} }
} }
fn apply_animation(name: &str, value: &str, style: &mut StyleModifer) { fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifer) {
match name { match name {
"animation" => {} "animation" => {}
"animation-delay" => {} "animation-delay" => {}
@@ -482,7 +481,7 @@ fn apply_animation(name: &str, value: &str, style: &mut StyleModifer) {
} }
} }
fn apply_column(name: &str, value: &str, style: &mut StyleModifer) { fn apply_column(name: &str, _value: &str, _style: &mut StyleModifer) {
match name { match name {
"column-count" => {} "column-count" => {}
"column-fill" => {} "column-fill" => {}
@@ -551,7 +550,7 @@ fn apply_flex(name: &str, value: &str, style: &mut StyleModifer) {
} }
} }
fn apply_font(name: &str, value: &str, style: &mut StyleModifer) { fn apply_font(_name: &str, _value: &str, _style: &mut StyleModifer) {
todo!() todo!()
} }
@@ -588,15 +587,15 @@ fn apply_padding(name: &str, value: &str, style: &mut StyleModifer) {
} }
} }
fn apply_text(name: &str, value: &str, style: &mut StyleModifer) { fn apply_text(_name: &str, _value: &str, _style: &mut StyleModifer) {
todo!() todo!()
} }
fn apply_transform(name: &str, value: &str, style: &mut StyleModifer) { fn apply_transform(_name: &str, _value: &str, _style: &mut StyleModifer) {
todo!() todo!()
} }
fn apply_transition(name: &str, value: &str, style: &mut StyleModifer) { fn apply_transition(_name: &str, _value: &str, _style: &mut StyleModifer) {
todo!() todo!()
} }
@@ -640,7 +639,7 @@ fn apply_align(name: &str, value: &str, style: &mut StyleModifer) {
} }
} }
pub fn apply_size(name: &str, value: &str, style: &mut StyleModifer) { pub fn apply_size(_name: &str, _value: &str, _style: &mut StyleModifer) {
// //
} }
+6 -18
View File
@@ -1,24 +1,12 @@
use anyhow::Result; use crossterm::event::{Event as TermEvent, KeyEvent, MouseEvent};
use crossterm::{ use dioxus::core::*;
event::{ use dioxus::prelude::Props;
DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyEvent, MouseEvent, use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use dioxus::{core::exports::futures_channel::mpsc::unbounded, prelude::Props};
use dioxus::{core::*, prelude::*};
use futures::{channel::mpsc::UnboundedReceiver, future::Either, pin_mut, StreamExt};
use std::{ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::HashMap, collections::HashMap,
io,
rc::Rc, rc::Rc,
time::{Duration, Instant},
}; };
use stretch2::{prelude::Size, Stretch};
use tokio::sync::broadcast::Receiver;
use tui::{backend::CrosstermBackend, style::Style as TuiStyle, Terminal};
pub struct RinkContext { pub struct RinkContext {
last_event: Rc<Cell<Option<TermEvent>>>, last_event: Rc<Cell<Option<TermEvent>>>,
@@ -44,8 +32,8 @@ impl RinkContext {
}); });
Self { Self {
last_event: last_event, last_event,
subscribers: subscribers, subscribers,
} }
} }
+10 -16
View File
@@ -1,21 +1,15 @@
use anyhow::Result; use anyhow::Result;
use crossterm::{ use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyEvent}, event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
execute, execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };
use dioxus::{core::exports::futures_channel::mpsc::unbounded, prelude::Props}; use dioxus::core::exports::futures_channel::mpsc::unbounded;
use dioxus::{core::*, prelude::*}; use dioxus::core::*;
use futures::{ use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
channel::mpsc::{UnboundedReceiver, UnboundedSender},
future::Either,
pin_mut, StreamExt,
};
use std::{ use std::{
cell::Cell,
collections::HashMap, collections::HashMap,
io, io,
rc::Rc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use stretch2::{prelude::Size, Stretch}; use stretch2::{prelude::Size, Stretch};
@@ -137,17 +131,17 @@ pub fn render_vdom(vdom: &mut VirtualDom, ctx: UnboundedSender<TermEvent>) -> Re
pin_mut!(wait); pin_mut!(wait);
match select(wait, rx.next()).await { match select(wait, rx.next()).await {
Either::Left((a, b)) => { Either::Left((_a, _b)) => {
// //
} }
Either::Right((evt, o)) => { Either::Right((evt, _o)) => {
//
match evt.as_ref().unwrap() { match evt.as_ref().unwrap() {
InputEvent::UserInput(event) => match event { InputEvent::UserInput(event) => match event {
TermEvent::Key(key) => { TermEvent::Key(key) => {
match key.code { if matches!(key.code, KeyCode::Char('c'))
KeyCode::Char('q') => break, && key.modifiers.contains(KeyModifiers::CONTROL)
_ => {} // handle event {
break;
} }
} }
TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {} TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {}
+2 -18
View File
@@ -1,22 +1,8 @@
use anyhow::Result;
use crossterm::{
event,
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyEvent},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use dioxus::core::*; use dioxus::core::*;
use std::{ use std::{collections::HashMap, io::Stdout};
collections::HashMap,
io::{self, Stdout},
sync::mpsc,
thread,
time::{Duration, Instant},
};
use stretch2::{ use stretch2::{
geometry::Point, geometry::Point,
prelude::{Layout, Size}, prelude::{Layout, Size},
style::Style as StretchStyle,
Stretch, Stretch,
}; };
use tui::{ use tui::{
@@ -24,9 +10,7 @@ use tui::{
buffer::Buffer, buffer::Buffer,
layout::Rect, layout::Rect,
style::Style as TuiStyle, style::Style as TuiStyle,
text::Span, widgets::{Block, Widget},
widgets::{canvas::Label, Block, BorderType, Borders, Widget},
Terminal,
}; };
use crate::TuiNode; use crate::TuiNode;