r3bl-org / r3bl-open-core Goto Github PK
View Code? Open in Web Editor NEWTUI framework and developer productivity apps in Rust ๐ฆ
Home Page: https://r3bl.com
License: Apache License 2.0
TUI framework and developer productivity apps in Rust ๐ฆ
Home Page: https://r3bl.com
License: Apache License 2.0
Similar to https://github.com/r3bl-org/r3bl_rs_utils/blob/main/docs/dd_editor_component.md create a new DD for render pipline and painting:
Currently the style!
proc macro only works w/ Color
enums or Rgb
values. The expression that is passed into the proc macro has to evaluate into a Color
. However, it does not support passing a variable that contains a Color
.
Here's how you can use it today:
use crossterm::style::*;
use r3bl_rs_utils::*;
fn make_a_style_declaratively(id: &str) -> Style {
style! {
id: style2
attrib: [dim, bold]
margin: 1
color_fg: Color::Red
color_bg: Color::Rgb { r: 0, g: 0, b: 0 }
}
}
We would like to be able to pass a variable that holds a Color
in addition to just passing an expression which evaluates to a Color
value.
use crossterm::style::*;
use r3bl_rs_utils::*;
fn make_a_style_declaratively(id: &str) -> Style {
let black = Color::Rgb { r: 0, g: 0, b: 0 };
let red = Color::Red;
style! {
id: style2
attrib: [dim, bold]
margin: 1
color_fg: red
color_bg: black
}
}
After the fix, we should be able to pass all 3 types into the color_fg
or color_bg
fields:
Color
enumRgb
valueCurrently r3bl-cmdr crate is just a set of demos / examples for r3bl_rs_utils.
More about rust examples http://xion.io/post/code/rust-examples.html
also update dl fonts to use victor mono. here are helpful links:
update src/tui/readme.md:
tui_core
module)r3bl_tui
crate is and how is it different from othersCurrently, auto clipping to display width bounds is not supported for ANSI formatted text:
This means that ANSI formatted text is not clipped automatically: TruncationPolicy.
Currently, the rendering engine uses unicode_width
crate to determine display width (in cols) of strings. UnicodeString
handles this in the platform.
However, there's a disparity in how different terminals and OSes paint various compound complex grapheme clusters.
Here's an example ๐๐ฝ๐โ
.
unicode_width
reports its width to be larger than the display width in kitty + linux or terminal + macos.unicode_width
reports.A solution is to make all terminals "break" in the same way. This has already been staged in a sense. SharedTWData
gets piped into the rendering_pipeline
and the terminal_backend
.
This solution is implemented by "jumping" ๐ฆ the cursor to the width (display cols) reported by unicode_width
. The UnicodeString
has all the information to make this work when combined with SharedTWData
and its tracking of the cursor (independent from crossterm!).
In the future, since there's z-ordering support in the framework, more awesome stuff like this will be possible! ๐
Currently it is a lot of work to describe what exit_keys look like. The code is something like:
let exit_keys: Vec<TWInputEvent> = vec![TWInputEvent::DisplayableKeypress('x')];
It would be nicer to replace the TWInputEvent::DisplayableKeypress('x')
w/ a macro. And even provide the ability to handle multiple TWInputEvents. This is what that DSL syntax might look like:
let exit_keys = tw_input_events! {
tw_input_event { Displayable x },
tw_input_event { Displayable X },
tw_input_event { NonDisplayable Ctrl X }
tw_input_event { NonDisplayable Ctrl Shift X }
};
Currently layouts in apps are done imperatively. Here's an example https://github.com/r3bl-org/r3bl-cmdr/blob/main/src/ex_app_with_layout/app_with_layout.rs.
This issue will be updated w/ more details over time. The idea would be to build something like React JSX, so that this layout code could be expressed declaratively.
Here's a diagram of the layout code for an application. It's a 2 column responsive layout. https://app.diagrams.net/
Here's a simple block of layout code that be a good first candidate to macro-fy the code from here.
tw_surface.box_start(TWBoxProps {
styles: tw_surface.stylesheet.find_styles_by_ids(vec!["style1"]),
id: COL_1_ID.into(),
dir: Direction::Vertical,
req_size: (50, 100).try_into()?,
})?;
// OPTIMIZE: macro?
if let Some(shared_component) = self.component_registry.get(COL_1_ID) {
let current_box = tw_surface.current_box()?;
let queue = shared_component
.write()
.await
.render(&self.has_focus, current_box, state, shared_state)
.await?;
tw_surface.render_buffer += queue;
}
tw_surface.box_end()?;
Here are some thoughts on the area of improvements in this block of text:
tw_surface
, the act of calling box_start
, render
, box_end
seems pretty repetitive, and might be good for a macro.Define what the demo for cmdr should contain (rs will have the examples / demo from cmdr).
๐ The latest version of this design doc is in the repo here
In r3bl-cmdr repo, when trying to change the exit key from Ctrl+q
to x
it was not possible.
This is because a logic error made a KeyEvent which has a modifier key
set, to be NonDisplayable.
Create a simple modal dialog that looks like the diagram below. The dialog should only be rendered if it has gained focus, resulting from the end user pressing a keyboard shortcut.
DialogComponent
+------------------------------+ <- DialogEngine renders this in ZOrder::Glass
| TITLE | (in DialogComponent struct)
| | xxxxx | | <- DialogBuffer (in State struct / from Store)
+------------------------------+ holds: buffer, title, is_focused_in_dialog
<- Need handlers for onYes, onNo
App
component_registry::populate()
DialogComponent
fileOnDialogPressFn
handler fn to the component
Action::SetDialog
if DialogResponse::Yes(String)
or No
component_registry
impl_app::app_handle_event()
InputEvent
matches keyboard shortcut Ctrl+l -> give the dialog focus ๐
DialogComponent
, before giving focus to the dialogSetDialog
(DialogBuffer
: title, buffer) -> will trigger a render ๐๏ธConsumed
(don't return ConsumedRerender
as this will trigger a render ๐๏ธ)Redux state and actions
DialogBuffer
: InitDialog
HasDialogBuffer
(defined in component) for State
DialogBuffer
buffer: EditorBuffer
title: String
DialogComponent
DialogEngine
id: String
on_dialog_press_handler: OnDialogPressFn<S,A>
Component
handle_event()
<- ๐ must already have focus for this to be called
apply_event(input_event)
, deal with ApplyResponse<DialogResponse>
Applied(DialogResponse)
on_dialog_press_handler
fn (which will deal w/ Yes(DialogBuffer)
or No
)component_registry.has_focus.set_id($id)
where $id
is theDialogComponent
EventPropagation::ConsumedRerender
EventPropagation::Propagate
render()
DialogEngine
to renderDialogResponse
enum (define in component)
Yes(String)
, No
OnDialogPressFn<S,A>
(define in component)
fn(&SharedStore<S,A>, DialogResponse, ComponentRegistry, String /* prev focus id */)
HasDialogBuffer
trait (define in component): get_dialog_buffer()
DialogEngine
EditorEngine
prev_focus_id: String
: last component that had focus prior to dialog being shownDialogEngineApi
apply_event()
: return ApplyResponse<DialogResponse>
ApplyResponse::Applied<DialogResponse::Yes<String>>
ApplyResponse::Applied<DialogResponse::No>
ApplyResponse::NotApplied
render()
: return RenderPipeline
DialogBuffer
holds buffer and is_focused_in_dialog
enumZOrder::Glass
layerEditorEngingeRenderApi
(buffer comes from DialogBuffer
)
ZOrder::Normal
RenderOps
to Glass
in the RenderPipline
that's returnedCurrently it is left up to the application or component to clip to the bounds of the displayable area (in the terminal window / surface).
This should really be enforced by TWCommandQueue when it executes the stream of TWCommands.
There may be a need to use unsafe in order to mutate a static that holds the size of the terminal window itself in order to implement these checks.
Source code entry points:
Here's an example of code that can be simplified w/ the framework handling bounds enforcement: https://github.com/r3bl-org/r3bl_rs_utils/blob/main/src/tui/ed/editor_engine.rs#L111
Here's an example of code that uses unsafe global mutable static variable: https://github.com/r3bl-org/r3bl_rs_utils/blob/main/src/utils/file_logging.rs#L96
Here is the method that sets the size of the terminal window (good hook to update global state variable containing the size): https://github.com/r3bl-org/r3bl_rs_utils/blob/main/src/tui/terminal_window/main_event_loop.rs#L44
See: #29
Assume that the 1st arg to PrintWithAttributes is plain text ... make this clear in the docs ... calculate the available width using TWUtils.get_current_position() and the length of the text to figure out where to clip it.
Added 2 sets of paint text commands in TWCommand
to handle this:
Variant | Auto clipping support |
---|---|
PrintPlainTextWithAttributes(String, Option<Style>) |
YES |
PrintANSITextWithAttributes(String, Option<Style>) |
NO |
Also added terminal_window_static_data
that holds the following (atomics, lock-free):
Remove the use of static and unsafe code (https://gist.github.com/nazmulidris/78d7f64164db6cfb33eed5efd2b339c9) and replace it w/ TWData that contains the size & position.
TWCommand
& Keypress
compatible w/ both.Note - termion is created by popos swes who work for system76
r3bl_rs_utils
.Repro steps:
Initial thoughts:
From<Vec<Style, String>> for StyledTexts
syntax_set
and theme
to EditorEngine
(make it part of the construction)EditorEngineConfigOptions
to provide control over syntax highlightingEditorBuffer
to hold meta data about what file extension ("md"
, "rs"
) etc that will be used to get a SyntaxReference
(from the SyntaxSet
in the engine)EditorEngineRenderApi::render_content()
(make note of enable/disable syntax highlighting in EditorEngineConfigOptions
.test_styled_text.rs
)Plan some work to be done
Create a set of small issues that Shawn can work on. These issues must fit the following template:
Here's the list so far
Install miri and create fish scripts to run the binary & tests using it.
https://github.com/rust-lang/miri
Also install this on all dev machines & update
~/local-backup-restore/1-install.fish
See: #28
write developerlife.com article on how to reason about tuis & create tui apps
@font-face {
font-family: Mabry Pro;
src: url(MabryPro.woff2)
}
@font-face {
font-family: Mabry Pro;
src: url(MabryProItalic.woff2);
font-style: italic
}
@font-face {
font-family: Mabry Pro;
src: url(MabryProItalic.woff2);
font-weight: 700
}
@font-face {
font-family: Mabry Pro;
src: url(MabryProBoldItalic.woff2);
font-weight: 700;
font-style: italic
}
Related to #20
Once that is fixed, cut a new breaking release of:
In crossterm, the following KeyEvent
never happens.
let never = KeyEvent {
code: KeyCode::Char('x'),
modifiers: KeyModifiers::SHIFT,
}
When "X" is pressed, the following KeyEvent
is sent:
let never = KeyEvent {
code: KeyCode::Char('X'),
modifiers: KeyModifiers::SHIFT,
}
And not:
let never = KeyEvent {
code: KeyCode::Char('x'),
modifiers: KeyModifiers::SHIFT,
}
Assignees: @nazmulidris @shayes12111
Currently, TWCommandQueue is a simple queue. The ordering in which commands are executed to paint is first in, first out. This can lead to some unintended consequences when painting UI since things might get clobbered unintentionally. Also, it is important to have some idea of priority when painting since a lot of UI gets rendered in layers. Eg: in an editor you have the following layers:
Currently there is no easy way to manage this. I propose that a z-index number be added to TWCommandQueue which shards the single queue into a bunch of different queue which map to the z-index number. So this is what the data structure might look like.
command queue
+ z-index: 0 => queue of commands
+ z-index: 1 => queue of commands
+ z-index: 2 => queue of commands
.. etc
enum ZOrder {
Normal, // 0
High, // 1
Caret, // 2
Glass // 3
}
This can be accomplished by simply adding a number argument to TWCommand itself. And then the queue can behave like a priority queue when performing its actual painting (taking the commands produced by the render phase and actually executing them against stdout).
a
โ
bcd
Observed behavior: If you place the care where โ
is, then press Right arrow. Nothing happens.
Desired behavior: It should move down to the next line.
<span>
tag in HTML and create a struct that can support vecs of styled text (String, Style)
.UnicodeStringExt
.This bug surfaced when creating a simple layout which contains just 1 box in a surface. This box ignores the computed style margin. In Surface the add_root_box()
is not taking computed style into account in the same was as add_box()
.
Even if a surface has a single box which is the root, the styling should be applied.
Let us know about features you really want to see in r3bl_rs_utils library.
If the feature you are interested in exists in other CLI or TUI apps, please share links to documentation or screenshots to easily communicate the desired behavior!
Currently if you run r3bl-cmdr and make the terminal window very small then it looks bad. Non deterministic even.
It also caused this panic fixed by this commit bbecf32
Desired behavior should be
Here is a non exhaustive list of things that require some min size:
Currently the Stylesheet
struct has to be build imperatively. Along the lines of introducing style!
DSL for Style
struct, we need a stylesheet!
macro for Stylesheet
struct.
Here's the imperative style of code.
use crossterm::event::*;
use r3bl_rs_utils::*;
fn create_stylesheet(&mut self) -> CommonResult<Stylesheet> {
// Turquoise: Color::Rgb { r: 51, g: 255, b: 255 }
// Pink: Color::Rgb { r: 252, g: 157, b: 248 }
// Blue: Color::Rgb { r: 55, g: 55, b: 248 }
// Faded blue: Color::Rgb { r: 85, g: 85, b: 255 }
throws_with_return!({
let mut stylesheet = Stylesheet::new();
stylesheet.add_styles(vec![
style! {
id: style1
margin: 1
color_bg: Color::Rgb { r: 55, g: 55, b: 248 }
},
style! {
id: style2
margin: 1
color_bg: Color::Rgb { r: 85, g: 85, b: 255 }
},
])?;
stylesheet
})
}
So the declarative version might look like.
use crossterm::event::*;
use r3bl_rs_utils::*;
fn create_stylesheet(&mut self) -> CommonResult<Stylesheet> {
// Turquoise: Color::Rgb { r: 51, g: 255, b: 255 }
// Pink: Color::Rgb { r: 252, g: 157, b: 248 }
// Blue: Color::Rgb { r: 55, g: 55, b: 248 }
// Faded blue: Color::Rgb { r: 85, g: 85, b: 255 }
throws_with_return!({
stylesheet!(
style! {
id: style1
margin: 1
color_bg: Color::Rgb { r: 55, g: 55, b: 248 }
},
style! {
id: style2
margin: 1
color_bg: Color::Rgb { r: 85, g: 85, b: 255 }
}
)
})
}
Related:
RenderOps
and RenderPipline
end up generating crossterm commands which are transmitted to stdout
ZOrder
can't be handled efficiently.ZOrder
followed by a lower ZOrder
and so on.stdout
for each ZOrder
that has to be rendered.RenderPipeline
and translates it into an intermediate representation (IR) which is quite simply a 2d array of PixelChar
PixelChar
is a struct that is defined as:
GraphemeSegment
There are as many PixelChars
as there are window_size.col * window_size.row.
So the RenderPipeline
has to be rasterized into a grid of PixelChars
. Then this grid can be painted using whatever output device & library:
One other thing that changes between these different outputs is the way input is captured. However, all that input can be normalized to a generic
InputEvent
type that currently exists today.
Might also need to use a diffing library like imara-diff to calculate the diffs between a sequence of
PixelChar
grids.
Here's another rasterizer built atop termion
Add tests for render_pipeline.rs (in test_render_pipeline.rs):
Currently the crate unicode-width is used to calculate the displayed width of unicode characters.
These calculations are mostly correct for:
Here's where it gets interesting:
Here are some approaches to fix this:
Source files to work on:
The semantics of style! margin is really padding. Best to rename all usages to the new field name & update style! & stylesheet! macros.
As a developer, I was trying to understand the project and found myself wondering and confused by how the utils
and cmdr
repo were named and what their responsibilities are. After cloning the project and taking a closer look it seemed like r3bl_utils
contained the core code of this awesome library and the cmdr
repo contained examples.
Something like:
r3bl-org (organisation)
- r3bl (workspace)
- r3bl-core (crate)
- r3bl-macro (crate)
- r3bl-cmdr (crate)
I have seen many projects keeping the related code bases inside a single workspace. It is very common in the rust ecosystem to keep code related to a single project close to each other and within the same workspace. This provides many benefits such as:
cmdr
project refers to a dependency to utils
by local path but one needs to assure it exists first)The following steps would have to be taken
r3bl_rs_utils
to r3bl
, this is the project name and hence a natural fit for this repo. This project already has 22 stars so would be nice to reuse this repo and just rename.r3bl_rs_utils
to a new repository
r3bl_rs_utils
crate that shows it is deprecated.cmdr
into the r3bl
repo and create a workspacecmdr
repository (or delete it as it likely doesnt have many users yet)Cargo.toml
and rename the main project to r3bl
r3bl
.You would end up having to deprecate r3bl_rs_utils
but since it isn't used that much yet it should not be a problem. Those kinds of changes is better early than too late. Just publish a new version with a README saying it's deprecated and link to the new project, this repo, under its new name.
What do you think?
Currently, Component is not reusable. Make it so that it is, and make EditorComponent reusable as well.
Related to #23
RenderPipeline
It is a collection of atomic paint operations (aka RenderOps
at various ZOrder
s); each RenderOps
is made up of a vec of RenderOp
. It contains Map<ZOrder, Vec<RenderOps>>
, eg:
ZOrder::Normal
=> vec![ResetColor
, MoveCursorPositionAbs(..)
, PrintTextWithAttributes(..)
]ZOrder::Glass
=> vec![ResetColor
, MoveCursorPositionAbs(..)
, PrintTextWithAttributes(..)
]RenderOp::MoveCursorPositionAbs
RenderOp::MoveCursorPositionRelTo
In order to ensure that things on the terminal screen aren't being needlessly drawn (when they
have already been drawn before and are on screen), it is important to keep track of the position
and bounds of each [RenderOps]. This allows [optimized_paint::clear_flex_box] to perform its
magic by clearing out the space for a [RenderOps] before it is painted. This is needed for
managing cursor movement (cursors are painted when moved, but the old cursor doesn't get
cleared).
RenderPipeline
to hold a Vec<RenderOps>
for each ZOrder
TWData
to make it explicit when the cursor_position
is safe to use.RenderOps
TWData
A more comprehensive fix to this will arrive when this issue is fixed #46
Provide a constructor where the following is specified
Provide a method to manipulate the seed easily
In the dialog editor make sure to have a lolcat init'd w/ the same seed so that it doesn't change every time the app is run
dialog module:
jsx module:
Replace the use of the following structs w/ the following macros (which are more readable & easier for developers to use, ie, more ergonomic):
Struct | Macro |
---|---|
Keypress |
keypress! |
Size |
size! |
Position |
position! |
Pair |
pair! |
Remove crossterm deps from TWInputEvent:
TWInputEvent
enum has a variant that uses KeyEvent
. Remove this dependency & replace it w/ something along the lines of DisplayableKeypress
variant.Clean up API:
does_input_event_match_exit_keys
function into a method for TWInputEvent
& update usages.Color
w/ TWColor
.KeyEvent
& KeyCode
w/ KeypressEvent
.From: https://discord.com/channels/273534239310479360/943315667430563862/1019937035462647840
C++ impl of a pseudo terminal to allow terminals to be embedded in other apps:
https://github.com/FredrikAleksander/HexeTerminal
Currently EditorBuffer::lines
represents the contents of the editor as a Vec<String>
. While this might accurately represent a buffer when it is saved to disk, this might not be the best data model. The editor really treats content as a Vec<UnicodeString>
and it has to convert back and forth between display_col and vec index. This is not really ergonomic and the logic suffers due to this.
A better approach might just to be rewrite the EditorBuffer
using Vec<UnicodeString>
rather than Vec<String>
.
Currently this r3bl_rs_utils
crate is made up of the following crates (deps):
r3bl_macro
(depends on r3bl_core
)r3bl_core
The idea is to break up this crate into even smaller ones:
r3bl_redux
(depends on r3bl_macro
, r3bl_core
)
r3bl_tui
(depends on r3bl_macro
, r3bl_core
, r3bl_redux
)
Once these are moved out, whatever functionality remains will be r3bl_rs_utils
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.