Merge pull request #14 from Demonthos/master

add more attributes and colors
This commit is contained in:
Jonathan Kelley 2022-02-27 23:59:16 -05:00 committed by GitHub
commit 18e8092df3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1420 additions and 137 deletions

View file

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

27
examples/border.rs Normal file
View file

@ -0,0 +1,27 @@
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()),
border_style: "solid none solid double",
border_width: "thick",
border_radius: "{radius}px",
border_color: "#0000FF #FF00FF #FF0000 #00FF00",
"{radius}"
}
})
}

48
examples/color_test.rs Normal file
View file

@ -0,0 +1,48 @@
use dioxus::prelude::*;
fn main() {
// rink::launch(app);
rink::launch_cfg(
app,
rink::Config {
rendering_mode: rink::RenderingMode::Ansi,
},
);
}
fn app(cx: Scope) -> Element {
let steps = 50;
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "column",
(0..=steps).map(|x|
{
let hue = x as f32*360.0/steps as f32;
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "row",
(0..=steps).map(|y|
{
let alpha = y as f32*100.0/steps as f32;
cx.render(rsx! {
div {
left: "{x}px",
top: "{y}px",
width: "10%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
}
})
}
)
}
})
}
)
}
})
}

View file

@ -2,9 +2,17 @@ use dioxus::prelude::*;
fn main() {
rink::launch(app);
// rink::launch_cfg(
// app,
// rink::Config {
// rendering_mode: rink::RenderingMode::Ansi,
// },
// )
}
fn app(cx: Scope) -> Element {
let (alpha, set_alpha) = use_state(&cx, || 100);
cx.render(rsx! {
div {
width: "100%",
@ -13,7 +21,9 @@ fn app(cx: Scope) -> Element {
// justify_content: "center",
// align_items: "center",
// flex_direction: "row",
// background_color: "red",
onwheel: move |evt| {
set_alpha((alpha + evt.data.delta_y as i64).min(100).max(0));
},
p {
background_color: "black",
@ -21,6 +31,7 @@ fn app(cx: Scope) -> Element {
justify_content: "center",
align_items: "center",
// height: "10%",
color: "green",
"hi"
"hi"
"hi"
@ -53,13 +64,6 @@ fn app(cx: Scope) -> Element {
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
"zib"
}
p {
background_color: "yellow",
@ -77,6 +81,47 @@ 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: "rgba(255, 255, 255)",
"underline"
}
p {
text_decoration: "line-through",
color: "hsla(10, 100%, 70%)",
"line-through"
}
div{
position: "absolute",
top: "1px",
background_color: "rgba(255, 0, 0, 50%)",
width: "100%",
p {
color: "rgba(255, 255, 255, {alpha}%)",
background_color: "rgba(100, 100, 100, {alpha}%)",
"rgba(255, 255, 255, {alpha}%)"
}
p {
color: "rgba(255, 255, 255, 100%)",
"rgba(255, 255, 255, 100%)"
}
}
}
})
}

View file

@ -30,15 +30,97 @@
*/
use stretch2::{prelude::*, style::PositionType, style::Style};
use tui::style::Style as TuiStyle;
use crate::style::{RinkColor, RinkStyle};
pub struct StyleModifer {
pub style: Style,
pub tui_style: TuiStyle,
pub tui_style: RinkStyle,
pub tui_modifier: TuiModifier,
}
enum TuiModifier {
Text,
#[derive(Default)]
pub struct TuiModifier {
pub borders: Borders,
}
#[derive(Default)]
pub struct Borders {
pub top: BorderEdge,
pub right: BorderEdge,
pub bottom: BorderEdge,
pub left: BorderEdge,
}
impl Borders {
fn slice(&mut self) -> [&mut BorderEdge; 4] {
[
&mut self.top,
&mut self.right,
&mut self.bottom,
&mut self.left,
]
}
}
pub struct BorderEdge {
pub color: Option<RinkColor>,
pub style: BorderStyle,
pub width: UnitSystem,
pub radius: UnitSystem,
}
impl Default for BorderEdge {
fn default() -> Self {
Self {
color: None,
style: BorderStyle::NONE,
width: UnitSystem::Point(0.0),
radius: UnitSystem::Point(0.0),
}
}
}
#[derive(Clone, Copy)]
pub enum BorderStyle {
DOTTED,
DASHED,
SOLID,
DOUBLE,
GROOVE,
RIDGE,
INSET,
OUTSET,
HIDDEN,
NONE,
}
impl BorderStyle {
pub fn symbol_set(&self) -> Option<tui::symbols::line::Set> {
use tui::symbols::line::*;
const DASHED: Set = Set {
horizontal: "",
vertical: "",
..NORMAL
};
const DOTTED: Set = Set {
horizontal: "",
vertical: "",
..NORMAL
};
match self {
BorderStyle::DOTTED => Some(DOTTED),
BorderStyle::DASHED => Some(DASHED),
BorderStyle::SOLID => Some(NORMAL),
BorderStyle::DOUBLE => Some(DOUBLE),
BorderStyle::GROOVE => Some(NORMAL),
BorderStyle::RIDGE => Some(NORMAL),
BorderStyle::INSET => Some(NORMAL),
BorderStyle::OUTSET => Some(NORMAL),
BorderStyle::HIDDEN => None,
BorderStyle::NONE => None,
}
}
}
/// applies the entire html namespace defined in dioxus-html
@ -117,7 +199,9 @@ pub fn apply_attributes(
"clip" => {}
"color" => {
// text color
if let Ok(c) = value.parse() {
style.tui_style.fg.replace(c);
}
}
"column-count"
@ -170,14 +254,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::<f32>() {
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::<f32>() {
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 +367,11 @@ pub fn apply_attributes(
"visibility" => {}
"white-space" => {}
"width" => {
if value.ends_with("%") {
if let Ok(pct) = value.trim_end_matches("%").parse::<f32>() {
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::<f32>() {
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 +382,8 @@ pub fn apply_attributes(
}
}
enum UnitSystem {
#[derive(Clone, Copy)]
pub enum UnitSystem {
Percent(f32),
Point(f32),
}
@ -316,8 +395,8 @@ fn parse_value(value: &str) -> Option<UnitSystem> {
} else {
None
}
} else if value.ends_with("%") {
if let Ok(pct) = value.trim_end_matches("%").parse::<f32>() {
} else if value.ends_with('%') {
if let Ok(pct) = value.trim_end_matches('%').parse::<f32>() {
Some(UnitSystem::Percent(pct))
} else {
None
@ -344,7 +423,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 +454,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 Ok(c) = value.parse() {
style.tui_style.bg.replace(c);
}
}
"background" => {}
"background-attachment" => {}
@ -423,17 +470,71 @@ 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_style(v: &str) -> BorderStyle {
match v {
"dotted" => BorderStyle::DOTTED,
"dashed" => BorderStyle::DASHED,
"solid" => BorderStyle::SOLID,
"double" => BorderStyle::DOUBLE,
"groove" => BorderStyle::GROOVE,
"ridge" => BorderStyle::RIDGE,
"inset" => BorderStyle::INSET,
"outset" => BorderStyle::OUTSET,
"none" => BorderStyle::NONE,
"hidden" => BorderStyle::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 Ok(c) = value.parse() {
style.tui_modifier.borders.bottom.color = Some(c);
}
}
"border-bottom-left-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.left.radius = v;
}
}
"border-bottom-right-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.right.radius = v;
}
}
"border-bottom-style" => {
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;
}
}
"border-collapse" => {}
"border-color" => {}
"border-color" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Ok(c) = values[0].parse() {
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.color = Some(c));
}
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
if let Ok(c) = v.parse() {
b.color = Some(c);
}
}
}
}
"border-image" => {}
"border-image-outset" => {}
"border-image-repeat" => {}
@ -441,28 +542,116 @@ 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::<f32>() {
// tuistyle = px;
"border-left-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.left.color = Some(c);
}
}
_ => {}
"border-left-style" => 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;
}
}
"border-radius" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(r) = parse_value(values[0]) {
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.radius = r);
}
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
if let Some(r) = parse_value(v) {
b.radius = r;
}
}
}
}
"border-right" => {}
"border-right-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.right.color = Some(c);
}
}
"border-right-style" => 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;
}
}
"border-spacing" => {}
"border-style" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
let border_style = parse_border_style(values[0]);
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.style = border_style);
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
b.style = parse_border_style(v);
}
}
}
"border-top" => {}
"border-top-color" => {
if let Ok(c) = value.parse() {
style.tui_modifier.borders.top.color = Some(c);
}
}
"border-top-left-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.left.radius = v;
}
}
"border-top-right-radius" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.right.radius = v;
}
}
"border-top-style" => style.tui_modifier.borders.top.style = parse_border_style(value),
"border-top-width" => {
if let Some(v) = parse_value(value) {
style.tui_modifier.borders.top.width = v;
}
}
"border-width" => {
let values: Vec<_> = value.split(' ').collect();
if values.len() == 1 {
if let Some(w) = parse_value(values[0]) {
style
.tui_modifier
.borders
.slice()
.iter_mut()
.for_each(|b| b.width = w);
}
} else {
for (v, b) in values
.into_iter()
.zip(style.tui_modifier.borders.slice().iter_mut())
{
if let Some(w) = parse_value(v) {
b.width = w;
}
}
}
}
_ => (),
}
}
@ -516,14 +705,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::<f32>() {
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::<f32>() {
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 +736,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" => (),
"font-family" => (),
"font-size" => (),
"font-size-adjust" => (),
"font-stretch" => (),
"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 +792,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) {

20
src/config.rs Normal file
View file

@ -0,0 +1,20 @@
#[derive(Default, Clone, Copy)]
pub struct Config {
pub rendering_mode: RenderingMode,
}
#[derive(Clone, Copy)]
pub enum RenderingMode {
/// only 16 colors by accessed by name, no alpha support
BaseColors,
/// 8 bit colors, will be downsampled from rgb colors
Ansi,
/// 24 bit colors, most terminals support this
Rgb,
}
impl Default for RenderingMode {
fn default() -> Self {
RenderingMode::Rgb
}
}

View file

@ -1,10 +1,10 @@
use dioxus::core::*;
use std::collections::HashMap;
use tui::style::Style as TuiStyle;
use crate::{
attributes::{apply_attributes, StyleModifer},
TuiNode,
style::RinkStyle,
TuiModifier, TuiNode,
};
/*
@ -41,7 +41,8 @@ pub fn collect_layout<'a>(
id,
TuiNode {
node,
block_style: tui::style::Style::default(),
block_style: RinkStyle::default(),
tui_modifier: TuiModifier::default(),
layout: layout.new_node(style, &[]).unwrap(),
},
);
@ -50,7 +51,8 @@ pub fn collect_layout<'a>(
// gather up all the styles from the attribute list
let mut modifier = StyleModifer {
style: Style::default(),
tui_style: TuiStyle::default(),
tui_style: RinkStyle::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(),
},
);

View file

@ -13,19 +13,28 @@ use std::{
time::{Duration, Instant},
};
use stretch2::{prelude::Size, Stretch};
use tui::{backend::CrosstermBackend, style::Style as TuiStyle, Terminal};
use style::RinkStyle;
use tui::{backend::CrosstermBackend, Terminal};
mod attributes;
mod config;
mod hooks;
mod layout;
mod render;
mod style;
mod widget;
pub use attributes::*;
pub use config::*;
pub use hooks::*;
pub use layout::*;
pub use render::*;
pub fn launch(app: Component<()>) {
launch_cfg(app, Config::default())
}
pub fn launch_cfg(app: Component<()>, cfg: Config) {
let mut dom = VirtualDom::new(app);
let (tx, rx) = unbounded();
@ -37,12 +46,13 @@ pub fn launch(app: Component<()>) {
dom.rebuild();
render_vdom(&mut dom, tx, handler).unwrap();
render_vdom(&mut dom, tx, handler, cfg).unwrap();
}
pub struct TuiNode<'a> {
pub layout: stretch2::node::Node,
pub block_style: TuiStyle,
pub block_style: RinkStyle,
pub tui_modifier: TuiModifier,
pub node: &'a VNode<'a>,
}
@ -50,6 +60,7 @@ pub fn render_vdom(
vdom: &mut VirtualDom,
ctx: UnboundedSender<TermEvent>,
handler: RinkInputHandler,
cfg: Config,
) -> Result<()> {
// Setup input handling
let (tx, mut rx) = unbounded();
@ -132,7 +143,15 @@ 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,
&RinkStyle::default(),
cfg,
);
assert!(nodes.is_empty());
})?;

View file

@ -5,15 +5,15 @@ use stretch2::{
prelude::{Layout, Size},
Stretch,
};
use tui::{
backend::CrosstermBackend,
buffer::Buffer,
layout::Rect,
style::Style as TuiStyle,
widgets::{Block, Widget},
use tui::{backend::CrosstermBackend, layout::Rect};
use crate::{
style::{RinkColor, RinkStyle},
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
BorderEdge, BorderStyle, Config, TuiNode, UnitSystem,
};
use crate::TuiNode;
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
pub fn render_vnode<'a>(
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
@ -21,11 +21,14 @@ pub fn render_vnode<'a>(
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
vdom: &'a VirtualDom,
node: &'a VNode<'a>,
// this holds the accumulated syle state for styled text rendering
style: &RinkStyle,
cfg: Config,
) {
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, cfg);
}
return;
}
@ -33,7 +36,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, cfg);
return;
}
@ -43,7 +46,7 @@ pub fn render_vnode<'a>(
}
let id = node.try_mounted_id().unwrap();
let node = layouts.remove(&id).unwrap();
let mut node = layouts.remove(&id).unwrap();
let Layout { location, size, .. } = layout.layout(node.layout).unwrap();
@ -55,37 +58,46 @@ pub fn render_vnode<'a>(
#[derive(Default)]
struct Label<'a> {
text: &'a str,
style: RinkStyle,
}
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());
impl<'a> RinkWidget for Label<'a> {
fn render(self, area: Rect, mut buf: RinkBuffer) {
for (i, c) in self.text.char_indices() {
let mut new_cell = RinkCell::default();
new_cell.set_style(self.style);
new_cell.symbol = c.to_string();
buf.set(area.left() + i as u16, area.top(), &new_cell);
}
}
}
// let s = Span::raw(t.text);
// 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
if area.width > 0 && area.height > 0 {
frame.render_widget(label, area);
frame.render_widget(WidgetWithContext::new(label, cfg), area);
}
}
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 mut new_style = node.block_style.merge(*style);
node.block_style = new_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(WidgetWithContext::new(node, cfg), area);
}
// do not pass background color to children
new_style.bg = None;
for el in el.children {
render_vnode(frame, layout, layouts, vdom, el);
render_vnode(frame, layout, layouts, vdom, el, &new_style, cfg);
}
}
VNode::Fragment(_) => todo!(),
@ -93,3 +105,338 @@ pub fn render_vnode<'a>(
VNode::Placeholder(_) => todo!(),
}
}
impl<'a> RinkWidget for TuiNode<'a> {
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
use tui::symbols::line::*;
enum Direction {
Left,
Right,
Up,
Down,
}
fn draw(
buf: &mut RinkBuffer,
points_history: [[i32; 2]; 3],
symbols: &Set,
pos: [u16; 2],
color: &Option<RinkColor>,
) {
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 mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = *c;
}
new_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();
buf.set(
(current[0] + pos[0] as i32) as u16,
(current[1] + pos[1] as i32) as u16,
&new_cell,
);
}
fn draw_arc(
pos: [u16; 2],
starting_angle: f32,
arc_angle: f32,
radius: f32,
symbols: &Set,
buf: &mut RinkBuffer,
color: &Option<RinkColor>,
) {
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,
];
// keep track of the last 3 point to allow filling diagonals
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);
}
fn get_radius(border: &BorderEdge, area: Rect) -> f32 {
match border.style {
BorderStyle::HIDDEN => 0.0,
BorderStyle::NONE => 0.0,
_ => match border.radius {
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),
}
}
if area.area() == 0 {
return;
}
// todo: only render inside borders
for x in area.left()..area.right() {
for y in area.top()..area.bottom() {
let mut new_cell = RinkCell::default();
if let Some(c) = self.block_style.bg {
new_cell.bg = c;
}
buf.set(x, y, &new_cell);
}
}
let borders = self.tui_modifier.borders;
let last_edge = &borders.left;
let current_edge = &borders.top;
if let Some(symbols) = current_edge.style.symbol_set() {
// the radius for the curve between this line and the next
let r = get_radius(current_edge, area);
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_r = get_radius(last_edge, area);
let last_radius = [
(last_r * RADIUS_MULTIPLIER[0]) as u16,
(last_r * RADIUS_MULTIPLIER[1]) as u16,
];
let color = current_edge.color.or(self.block_style.fg);
let mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = c;
}
for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) {
new_cell.symbol = symbols.horizontal.to_string();
buf.set(x, area.top(), &new_cell);
}
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,
&mut buf,
&color,
);
}
let last_edge = &borders.top;
let current_edge = &borders.right;
if let Some(symbols) = current_edge.style.symbol_set() {
// the radius for the curve between this line and the next
let r = get_radius(current_edge, area);
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_r = get_radius(last_edge, area);
let last_radius = [
(last_r * RADIUS_MULTIPLIER[0]) as u16,
(last_r * RADIUS_MULTIPLIER[1]) as u16,
];
let color = current_edge.color.or(self.block_style.fg);
let mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = c;
}
for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) {
new_cell.symbol = symbols.vertical.to_string();
buf.set(area.right() - 1, y, &new_cell);
}
draw_arc(
[area.right() - radius[0] - 1, area.bottom() - radius[1] - 1],
0.0,
std::f32::consts::FRAC_PI_2,
r,
&symbols,
&mut buf,
&color,
);
}
let last_edge = &borders.right;
let current_edge = &borders.bottom;
if let Some(symbols) = current_edge.style.symbol_set() {
// the radius for the curve between this line and the next
let r = get_radius(current_edge, area);
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_r = get_radius(last_edge, area);
let last_radius = [
(last_r * RADIUS_MULTIPLIER[0]) as u16,
(last_r * RADIUS_MULTIPLIER[1]) as u16,
];
let color = current_edge.color.or(self.block_style.fg);
let mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = c;
}
for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) {
new_cell.symbol = symbols.horizontal.to_string();
buf.set(x, area.bottom() - 1, &new_cell);
}
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,
&mut buf,
&color,
);
}
let last_edge = &borders.bottom;
let current_edge = &borders.left;
if let Some(symbols) = current_edge.style.symbol_set() {
// the radius for the curve between this line and the next
let r = get_radius(current_edge, area);
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_r = get_radius(last_edge, area);
let last_radius = [
(last_r * RADIUS_MULTIPLIER[0]) as u16,
(last_r * RADIUS_MULTIPLIER[1]) as u16,
];
let color = current_edge.color.or(self.block_style.fg);
let mut new_cell = RinkCell::default();
if let Some(c) = color {
new_cell.fg = c;
}
for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) {
new_cell.symbol = symbols.vertical.to_string();
buf.set(area.left(), y, &new_cell);
}
draw_arc(
[area.left() + radius[0], area.top() + radius[1]],
std::f32::consts::PI,
std::f32::consts::FRAC_PI_2,
r,
&symbols,
&mut buf,
&color,
);
}
}
}

447
src/style.rs Normal file
View file

@ -0,0 +1,447 @@
use std::{num::ParseFloatError, str::FromStr};
use tui::style::{Color, Modifier, Style};
use crate::RenderingMode;
#[derive(Clone, Copy, Debug)]
pub struct RinkColor {
pub color: Color,
pub alpha: f32,
}
impl Default for RinkColor {
fn default() -> Self {
Self {
color: Color::Black,
alpha: 0.0,
}
}
}
impl RinkColor {
pub fn blend(self, other: Color) -> Color {
if self.color == Color::Reset {
Color::Reset
} else if self.alpha == 0.0 {
other
} else {
let [sr, sg, sb] = to_rgb(self.color);
let [or, og, ob] = to_rgb(other);
let (sr, sg, sb, sa) = (
sr as f32 / 255.0,
sg as f32 / 255.0,
sb as f32 / 255.0,
self.alpha,
);
let (or, og, ob) = (or as f32 / 255.0, og as f32 / 255.0, ob as f32 / 255.0);
Color::Rgb(
(255.0 * (sr * sa + or * (1.0 - sa))) as u8,
(255.0 * (sg * sa + og * (1.0 - sa))) as u8,
(255.0 * (sb * sa + ob * (1.0 - sa))) as u8,
)
}
}
}
fn parse_value(
v: &str,
current_max_output: f32,
required_max_output: f32,
) -> Result<f32, ParseFloatError> {
if let Some(stripped) = v.strip_suffix('%') {
Ok((stripped.trim().parse::<f32>()? / 100.0) * required_max_output)
} else {
Ok((v.trim().parse::<f32>()? / current_max_output) * required_max_output)
}
}
pub struct ParseColorError;
fn parse_hex(color: &str) -> Result<Color, ParseColorError> {
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 {
Ok(Color::Rgb(values[0], values[1], values[2]))
} else {
Err(ParseColorError)
}
}
fn parse_rgb(color: &str) -> Result<Color, ParseColorError> {
let mut values = [0, 0, 0];
let mut color_ok = true;
for (v, i) in color.split(',').zip(0..values.len()) {
if let Ok(v) = parse_value(v.trim(), 255.0, 255.0) {
values[i] = v as u8;
} else {
color_ok = false;
}
}
if color_ok {
Ok(Color::Rgb(values[0], values[1], values[2]))
} else {
Err(ParseColorError)
}
}
fn parse_hsl(color: &str) -> Result<Color, ParseColorError> {
let mut values = [0.0, 0.0, 0.0];
let mut color_ok = true;
for (v, i) in color.split(',').zip(0..values.len()) {
if let Ok(v) = parse_value(v.trim(), if i == 0 { 360.0 } else { 100.0 }, 1.0) {
values[i] = v;
} else {
color_ok = false;
}
}
if color_ok {
let [h, s, l] = values;
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,
]
};
Ok(Color::Rgb(rgb[0], rgb[1], rgb[2]))
} else {
Err(ParseColorError)
}
}
impl FromStr for RinkColor {
type Err = ParseColorError;
fn from_str(color: &str) -> Result<Self, Self::Err> {
match color {
"red" => Ok(RinkColor {
color: Color::Red,
alpha: 1.0,
}),
"black" => Ok(RinkColor {
color: Color::Black,
alpha: 1.0,
}),
"green" => Ok(RinkColor {
color: Color::Green,
alpha: 1.0,
}),
"yellow" => Ok(RinkColor {
color: Color::Yellow,
alpha: 1.0,
}),
"blue" => Ok(RinkColor {
color: Color::Blue,
alpha: 1.0,
}),
"magenta" => Ok(RinkColor {
color: Color::Magenta,
alpha: 1.0,
}),
"cyan" => Ok(RinkColor {
color: Color::Cyan,
alpha: 1.0,
}),
"gray" => Ok(RinkColor {
color: Color::Gray,
alpha: 1.0,
}),
"darkgray" => Ok(RinkColor {
color: Color::DarkGray,
alpha: 1.0,
}),
// light red does not exist
"orangered" => Ok(RinkColor {
color: Color::LightRed,
alpha: 1.0,
}),
"lightgreen" => Ok(RinkColor {
color: Color::LightGreen,
alpha: 1.0,
}),
"lightyellow" => Ok(RinkColor {
color: Color::LightYellow,
alpha: 1.0,
}),
"lightblue" => Ok(RinkColor {
color: Color::LightBlue,
alpha: 1.0,
}),
// light magenta does not exist
"orchid" => Ok(RinkColor {
color: Color::LightMagenta,
alpha: 1.0,
}),
"lightcyan" => Ok(RinkColor {
color: Color::LightCyan,
alpha: 1.0,
}),
"white" => Ok(RinkColor {
color: Color::White,
alpha: 1.0,
}),
_ => {
if color.len() == 7 && color.starts_with('#') {
parse_hex(color).map(|c| RinkColor {
color: c,
alpha: 1.0,
})
} else if let Some(stripped) = color.strip_prefix("rgb(") {
let color_values = stripped.trim_end_matches(')');
if color.matches(',').count() == 3 {
let (alpha, rgb_values) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = alpha.parse() {
parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: a })
} else {
Err(ParseColorError)
}
} else {
parse_rgb(color_values).map(|c| RinkColor {
color: c,
alpha: 1.0,
})
}
} else if let Some(stripped) = color.strip_prefix("rgba(") {
let color_values = stripped.trim_end_matches(')');
if color.matches(',').count() == 3 {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: a })
} else {
Err(ParseColorError)
}
} else {
parse_rgb(color_values).map(|c| RinkColor {
color: c,
alpha: 1.0,
})
}
} else if let Some(stripped) = color.strip_prefix("hsl(") {
let color_values = stripped.trim_end_matches(')');
if color.matches(',').count() == 3 {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a })
} else {
Err(ParseColorError)
}
} else {
parse_hsl(color_values).map(|c| RinkColor {
color: c,
alpha: 1.0,
})
}
} else if let Some(stripped) = color.strip_prefix("hsla(") {
let color_values = stripped.trim_end_matches(')');
if color.matches(',').count() == 3 {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_hsl(rgb_values).map(|c| RinkColor { color: c, alpha: a })
} else {
Err(ParseColorError)
}
} else {
parse_hsl(color_values).map(|c| RinkColor {
color: c,
alpha: 1.0,
})
}
} else {
Err(ParseColorError)
}
}
}
}
}
fn to_rgb(c: Color) -> [u8; 3] {
match c {
Color::Black => [0, 0, 0],
Color::Red => [255, 0, 0],
Color::Green => [0, 128, 0],
Color::Yellow => [255, 255, 0],
Color::Blue => [0, 0, 255],
Color::Magenta => [255, 0, 255],
Color::Cyan => [0, 255, 255],
Color::Gray => [128, 128, 128],
Color::DarkGray => [169, 169, 169],
Color::LightRed => [255, 69, 0],
Color::LightGreen => [144, 238, 144],
Color::LightYellow => [255, 255, 224],
Color::LightBlue => [173, 216, 230],
Color::LightMagenta => [218, 112, 214],
Color::LightCyan => [224, 255, 255],
Color::White => [255, 255, 255],
Color::Rgb(r, g, b) => [r, g, b],
Color::Indexed(idx) => match idx {
16..=231 => {
let v = idx - 16;
// add 3 to round up
let r = ((v as u16 / 36) * 255 + 3) / 5;
let g = (((v as u16 % 36) / 6) * 255 + 3) / 5;
let b = ((v as u16 % 6) * 255 + 3) / 5;
[r as u8, g as u8, b as u8]
}
232..=255 => {
let l = (idx - 232) / 24;
[l; 3]
}
// rink will never generate these colors, but they might be on the screen from another program
_ => [0, 0, 0],
},
Color::Reset => [0, 0, 0],
}
}
pub fn convert(mode: RenderingMode, c: Color) -> Color {
if let Color::Reset = c {
c
} else {
match mode {
crate::RenderingMode::BaseColors => match c {
Color::Rgb(_, _, _) => panic!("cannot convert rgb color to base color"),
Color::Indexed(_) => panic!("cannot convert Ansi color to base color"),
_ => c,
},
crate::RenderingMode::Rgb => {
let rgb = to_rgb(c);
Color::Rgb(rgb[0], rgb[1], rgb[2])
}
crate::RenderingMode::Ansi => match c {
Color::Indexed(_) => c,
_ => {
let rgb = to_rgb(c);
// 16-231: 6 × 6 × 6 color cube
// 232-255: 23 step grayscale
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
let idx = 232 + (rgb[0] as u16 * 23 / 255) as u8;
Color::Indexed(idx)
} else {
let r = (rgb[0] as u16 * 5) / 255;
let g = (rgb[1] as u16 * 5) / 255;
let b = (rgb[2] as u16 * 5) / 255;
let idx = 16 + r * 36 + g * 6 + b;
Color::Indexed(idx as u8)
}
}
},
}
}
}
#[test]
fn rgb_to_ansi() {
for idx in 17..=231 {
let idxed = Color::Indexed(idx);
let rgb = to_rgb(idxed);
// gray scale colors have two equivelent repersentations
let color = Color::Rgb(rgb[0], rgb[1], rgb[2]);
let converted = convert(RenderingMode::Ansi, color);
if let Color::Indexed(i) = converted {
if rgb[0] != rgb[1] || rgb[1] != rgb[2] {
assert_eq!(idxed, converted);
} else {
assert!(i >= 232);
}
} else {
panic!("color is not indexed")
}
}
for idx in 232..=255 {
let idxed = Color::Indexed(idx);
let rgb = to_rgb(idxed);
assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]);
}
}
#[derive(Clone, Copy)]
pub struct RinkStyle {
pub fg: Option<RinkColor>,
pub bg: Option<RinkColor>,
pub add_modifier: Modifier,
pub sub_modifier: Modifier,
}
impl Default for RinkStyle {
fn default() -> Self {
Self {
fg: Some(RinkColor {
color: Color::White,
alpha: 1.0,
}),
bg: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
}
}
}
impl RinkStyle {
pub fn add_modifier(mut self, m: Modifier) -> Self {
self.sub_modifier.remove(m);
self.add_modifier.insert(m);
self
}
pub fn remove_modifier(mut self, m: Modifier) -> Self {
self.add_modifier.remove(m);
self.sub_modifier.insert(m);
self
}
pub fn merge(mut self, other: RinkStyle) -> Self {
self.fg = self.fg.or(other.fg);
self.add_modifier(other.add_modifier)
.remove_modifier(other.sub_modifier)
}
}
impl Into<Style> for RinkStyle {
fn into(self) -> Style {
Style {
fg: self.fg.map(|c| c.color),
bg: self.bg.map(|c| c.color),
add_modifier: self.add_modifier,
sub_modifier: self.sub_modifier,
}
}
}

96
src/widget.rs Normal file
View file

@ -0,0 +1,96 @@
use tui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier},
widgets::Widget,
};
use crate::{
style::{convert, RinkColor, RinkStyle},
Config,
};
pub struct RinkBuffer<'a> {
buf: &'a mut Buffer,
cfg: Config,
}
impl<'a> RinkBuffer<'a> {
fn new(buf: &'a mut Buffer, cfg: Config) -> RinkBuffer<'a> {
Self { buf, cfg }
}
pub fn set(&mut self, x: u16, y: u16, new: &RinkCell) {
let mut cell = self.buf.get_mut(x, y);
cell.bg = convert(self.cfg.rendering_mode, new.bg.blend(cell.bg));
if new.symbol.is_empty() {
if !cell.symbol.is_empty() {
// allows text to "shine through" transparent backgrounds
cell.fg = convert(self.cfg.rendering_mode, new.bg.blend(cell.fg));
}
} else {
cell.modifier = new.modifier;
cell.symbol = new.symbol.clone();
cell.fg = convert(self.cfg.rendering_mode, new.fg.blend(cell.bg));
}
}
}
pub trait RinkWidget {
fn render(self, area: Rect, buf: RinkBuffer);
}
pub struct WidgetWithContext<T: RinkWidget> {
widget: T,
config: Config,
}
impl<T: RinkWidget> WidgetWithContext<T> {
pub fn new(widget: T, config: Config) -> WidgetWithContext<T> {
WidgetWithContext { widget, config }
}
}
impl<T: RinkWidget> Widget for WidgetWithContext<T> {
fn render(self, area: Rect, buf: &mut Buffer) {
self.widget.render(area, RinkBuffer::new(buf, self.config))
}
}
#[derive(Clone)]
pub struct RinkCell {
pub symbol: String,
pub bg: RinkColor,
pub fg: RinkColor,
pub modifier: Modifier,
}
impl Default for RinkCell {
fn default() -> Self {
Self {
symbol: "".to_string(),
fg: RinkColor {
color: Color::Rgb(0, 0, 0),
alpha: 0.0,
},
bg: RinkColor {
color: Color::Rgb(0, 0, 0),
alpha: 0.0,
},
modifier: Modifier::empty(),
}
}
}
impl RinkCell {
pub fn set_style(&mut self, style: RinkStyle) {
if let Some(c) = style.fg {
self.fg = c;
}
if let Some(c) = style.bg {
self.bg = c;
}
self.modifier = style.add_modifier;
self.modifier.remove(style.sub_modifier);
}
}