Skip to content

Commit

Permalink
Merge pull request #56 from DioxusLabs/jk/add-tui
Browse files Browse the repository at this point in the history
Move native-core and tui out of dioxuslabs/dioxus and into this crate
  • Loading branch information
jkelleyrtp authored Mar 15, 2024
2 parents 45b44a0 + e213444 commit 2657cd7
Show file tree
Hide file tree
Showing 84 changed files with 16,772 additions and 1 deletion.
58 changes: 57 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,58 @@
[workspace]
members = ["packages/blitz", "packages/blitz-core"]
members = [
"packages/blitz",
"packages/blitz-core",
"packages/dioxus-tui",
"packages/native-core",
"packages/native-core-macro",
"packages/plasmo",
]

[workspace.package]
version = "0.5.0-alpha.0"

[workspace.dependencies]
blitz = { path = "packages/blitz", version = "0.5.0-alpha.0" }
blitz-core = { path = "packages/blitz-core", version = "0.5.0-alpha.0" }
dioxus-tui = { path = "packages/dioxus-tui", version = "0.5.0-alpha.0" }
dioxus-native-core = { path = "packages/native-core", version = "0.5.0-alpha.0" }
dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.5.0-alpha.0" }
plasmo = { path = "packages/plasmo", version = "0.5.0-alpha.0" }

dioxus = { version = "0.5.0-alpha.0" }
dioxus-core = { version = "0.5.0-alpha.0" }
dioxus-hot-reload = { version = "0.5.0-alpha.0" }
dioxus-html = { version = "0.5.0-alpha.0" }

tracing = "0.1.37"
tracing-futures = "0.2.5"
toml = "0.8"
tokio = "1.28"
slab = "0.4.2"
futures-channel = "0.3.21"
futures-util = { version = "0.3", default-features = false }
rustc-hash = "1.1.0"
wasm-bindgen = "0.2.92"
html_parser = "0.7.0"
thiserror = "1.0.40"
prettyplease = { package = "prettier-please", version = "0.2", features = [
"verbatim",
] }
manganis-cli-support = { version = "0.2.1", features = ["html"] }
manganis = { version = "0.2.1" }

lru = "0.12.2"
async-trait = "0.1.77"
axum = "0.7.0"
axum-server = { version = "0.6.0", default-features = false }
tower = "0.4.13"
http = "1.0.0"
tower-http = "0.5.1"
hyper = "1.0.0"
hyper-rustls = "0.26.0"
serde_json = "1.0.61"
serde = "1.0.61"
axum_session = "0.12.1"
axum_session_auth = "0.12.1"
axum-extra = "0.9.2"
reqwest = "0.11.24"
2 changes: 2 additions & 0 deletions packages/dioxus-tui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
Cargo.lock
2 changes: 2 additions & 0 deletions packages/dioxus-tui/.vscode/spellright.dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
esque
Tui
36 changes: 36 additions & 0 deletions packages/dioxus-tui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "dioxus-tui"
version = { workspace = true }
authors = ["Jonathan Kelley, Evan Almloff"]
edition = "2021"
description = "TUI-based renderer for Dioxus"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com/learn/0.4/getting_started/tui"
keywords = ["dom", "ui", "gui", "react", "terminal"]
license = "MIT OR Apache-2.0"

[dependencies]
dioxus-core = { workspace = true, features = ["serialize"] }
dioxus-html = { workspace = true }
dioxus-native-core = { workspace = true, features = ["dioxus"] }
dioxus-native-core-macro = { workspace = true }
dioxus-hot-reload = { workspace = true, optional = true }
plasmo = { workspace = true }

crossterm = "0.26.0"
tokio = { workspace = true, features = ["full"] }
futures = "0.3.19"
taffy = "0.3.12"

[dev-dependencies]
dioxus = { workspace = true }
tokio = { version = "1" }
criterion = "0.3.5"

[[bench]]
name = "update"
harness = false

[features]
default = ["hot-reload"]
hot-reload = ["dioxus-hot-reload"]
95 changes: 95 additions & 0 deletions packages/dioxus-tui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<div align="center">
<h1>Dioxus TUI</h1>
<p>
<strong>Beautiful terminal user interfaces in Rust with <a href="https://dioxuslabs.com/">Dioxus </a>.</strong>
</p>
</div>

<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>

<!--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>

<br/>

Leverage React-like patterns, CSS, HTML, and Rust to build beautiful, portable, terminal user interfaces with Dioxus.

```rust
fn app() -> Element {
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 inline styles, tree hierarchy, components, and more in your [`text-based user interface (TUI)`](https://en.wikipedia.org/wiki/Text-based_user_interface) application.

Dioxus TUI is essentially a port of [Ink](https://github.com/vadimdemedes/ink) but for [`Rust`](https://www.rust-lang.org/) and [`Dioxus`](https://dioxuslabs.com/). Dioxus TUI doesn't depend on Node.js or any other JavaScript runtime, so your binaries are portable and beautiful.

## Limitations

- **Subset of Html**
Terminals can only render a subset of HTML. We support as much as we can.
- **Particular frontend design**
Terminals and browsers are and look different. Therefore, the same design might not be the best to cover both renderers.

## Status

**WARNING: Dioxus TUI is currently under construction!**

Rendering a VirtualDom works fine, but the ecosystem of hooks is not yet ready. Additionally, some bugs in the flexbox implementation might be quirky at times.

## Features

Dioxus TUI features:

- [x] Flexbox-based layout system
- [ ] CSS selectors
- [x] inline CSS support
- [x] Built-in focusing system

* [x] Widgets<sup>1</sup>
* [ ] Support for events, hooks, and callbacks<sup>2</sup>
* [ ] Html tags<sup>3</sup>

<sup>1</sup> Currently only a subset of the input element is implemented as a component (not an element). The `Input` component supports sliders, text, numbers, passwords, buttons, and checkboxes.
<sup>2</sup> Basic keyboard, mouse, and focus events are implemented.
<sup>3</sup> Currently, most HTML tags don't translate into any meaning inside of Dioxus TUI. So an `input` _element_ won't mean anything nor does it have any additional functionality.
155 changes: 155 additions & 0 deletions packages/dioxus-tui/benches/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use dioxus::prelude::*;
use dioxus_tui::{Config, TuiContext};

criterion_group!(mbenches, tui_update);
criterion_main!(mbenches);

/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
fn tui_update(c: &mut Criterion) {
{
let mut group = c.benchmark_group("Update boxes");

for size in 1..=20usize {
let parameter_string = format!("{}", (size).pow(2));
group.bench_with_input(
BenchmarkId::new("size", parameter_string),
&size,
|b, size| {
b.iter(|| {
dioxus_tui::launch_cfg_with_props(
app,
GridProps {
size: *size,
update_count: 1,
},
Config::default().with_headless(),
)
})
},
);
}
}

{
let mut group = c.benchmark_group("Update many boxes");

for update_count in 1..=20usize {
let update_count = update_count * 20;
let parameter_string = update_count.to_string();
group.bench_with_input(
BenchmarkId::new("update count", parameter_string),
&update_count,
|b, update_count| {
b.iter(|| {
dioxus_tui::launch_cfg_with_props(
app,
GridProps {
size: 20,
update_count: *update_count,
},
Config::default().with_headless(),
)
})
},
);
}
}
}

#[derive(Props, PartialEq, Clone)]
struct BoxProps {
x: usize,
y: usize,
hue: f32,
alpha: f32,
}
#[allow(non_snake_case)]
fn Box(props: BoxProps) -> Element {
let count = use_signal(|| 0);

let x = props.x * 2;
let y = props.y * 2;
let hue = props.hue;
let display_hue = props.hue as u32 / 10;
let count = count();
let alpha = props.alpha + (count % 100) as f32;

rsx! {
div {
left: "{x}%",
top: "{y}%",
width: "100%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
align_items: "center",
p{"{display_hue:03}"}
}
}
}

#[derive(Props, PartialEq, Clone)]
struct GridProps {
size: usize,
update_count: usize,
}
#[allow(non_snake_case)]
fn Grid(props: GridProps) -> Element {
let size = props.size;
let mut count = use_signal(|| 0);
let mut counts = use_signal(|| vec![0; size * size]);

let ctx: TuiContext = consume_context();
if count() + props.update_count >= (size * size) {
ctx.quit();
} else {
for _ in 0..props.update_count {
counts.with_mut(|c| {
let i = count();
c[i] += 1;
c[i] %= 360;
});
count.with_mut(|i| {
*i += 1;
*i %= size * size;
});
}
}

rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "column",
for x in 0..size {
div {
width: "100%",
height: "100%",
flex_direction: "row",
for y in 0..size {
Box {
key: "{x}-{y}",
x: x,
y: y,
alpha: 100.0,
hue: y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32,
}
}
}
}
}
}
}

fn app(props: GridProps) -> Element {
rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: props.size,
update_count: props.update_count,
}
}
}
}
Loading

0 comments on commit 2657cd7

Please sign in to comment.