mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 06:30:20 +00:00
added alpha channel
This commit is contained in:
parent
f6ef9981dd
commit
7a1d8f0532
10 changed files with 821 additions and 277 deletions
|
@ -16,9 +16,6 @@ fn app(cx: Scope) -> Element {
|
|||
background_color: "hsl(248, 53%, 58%)",
|
||||
onwheel: move |w| set_radius((radius + w.delta_y as i8).abs()),
|
||||
|
||||
// the border can either be solid, double, thick, OR rounded
|
||||
// if multable are set only the last style is appiled
|
||||
// to skip a side set the style to none
|
||||
border_style: "solid none solid double",
|
||||
border_width: "thick",
|
||||
border_radius: "{radius}px",
|
||||
|
|
48
examples/color_test.rs
Normal file
48
examples/color_test.rs
Normal 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}%)",
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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"
|
||||
|
@ -73,7 +84,7 @@ fn app(cx: Scope) -> Element {
|
|||
div {
|
||||
font_weight: "bold",
|
||||
color: "#666666",
|
||||
p{
|
||||
p {
|
||||
"bold"
|
||||
}
|
||||
p {
|
||||
|
@ -88,14 +99,29 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
p {
|
||||
text_decoration: "underline",
|
||||
color: "rgb(50, 100, 255)",
|
||||
color: "rgba(255, 255, 255)",
|
||||
"underline"
|
||||
}
|
||||
p {
|
||||
text_decoration: "line-through",
|
||||
color: "hsl(10, 100%, 70%)",
|
||||
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%)"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -32,15 +32,17 @@
|
|||
use stretch2::{prelude::*, style::PositionType, style::Style};
|
||||
use tui::style::{Color, 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,
|
||||
}
|
||||
|
||||
pub struct TuiModifier {
|
||||
// border arrays start at the top and proceed clockwise
|
||||
pub border_colors: [Option<Color>; 4],
|
||||
pub border_colors: [Option<RinkColor>; 4],
|
||||
pub border_types: [BorderType; 4],
|
||||
pub border_widths: [UnitSystem; 4],
|
||||
pub border_radi: [UnitSystem; 4],
|
||||
|
@ -71,116 +73,6 @@ impl Default for TuiModifier {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_color(color: &str) -> Option<tui::style::Color> {
|
||||
match color {
|
||||
"red" => Some(Color::Red),
|
||||
"green" => Some(Color::Green),
|
||||
"blue" => Some(Color::Blue),
|
||||
"yellow" => Some(Color::Yellow),
|
||||
"cyan" => Some(Color::Cyan),
|
||||
"magenta" => Some(Color::Magenta),
|
||||
"white" => Some(Color::White),
|
||||
"black" => Some(Color::Black),
|
||||
_ => {
|
||||
if color.len() == 7 && color.starts_with('#') {
|
||||
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 {
|
||||
Some(Color::Rgb(values[0], values[1], values[2]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if color.starts_with("rgb(") {
|
||||
let mut values = [0, 0, 0];
|
||||
let mut color_ok = true;
|
||||
for (v, i) in color[4..]
|
||||
.trim_end_matches(')')
|
||||
.split(',')
|
||||
.zip(0..values.len())
|
||||
{
|
||||
if let Ok(v) = v.trim().parse() {
|
||||
values[i] = v;
|
||||
} else {
|
||||
color_ok = false;
|
||||
}
|
||||
}
|
||||
if color_ok {
|
||||
Some(Color::Rgb(values[0], values[1], values[2]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if color.starts_with("hsl(") {
|
||||
let mut values = [0, 0, 0];
|
||||
let mut color_ok = true;
|
||||
for (v, i) in color[4..]
|
||||
.trim_end_matches(')')
|
||||
.split(',')
|
||||
.zip(0..values.len())
|
||||
{
|
||||
if let Ok(v) = v.trim_end_matches('%').trim().parse() {
|
||||
values[i] = v;
|
||||
} else {
|
||||
color_ok = false;
|
||||
}
|
||||
}
|
||||
if color_ok {
|
||||
let [h, s, l] = [
|
||||
values[0] as f32 / 360.0,
|
||||
values[1] as f32 / 100.0,
|
||||
values[2] as f32 / 100.0,
|
||||
];
|
||||
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,
|
||||
]
|
||||
};
|
||||
|
||||
Some(Color::Rgb(rgb[0], rgb[1], rgb[2]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// applies the entire html namespace defined in dioxus-html
|
||||
pub fn apply_attributes(
|
||||
//
|
||||
|
@ -257,7 +149,7 @@ pub fn apply_attributes(
|
|||
"clip" => {}
|
||||
|
||||
"color" => {
|
||||
if let Some(c) = parse_color(value) {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_style.fg.replace(c);
|
||||
}
|
||||
}
|
||||
|
@ -512,7 +404,7 @@ fn apply_display(_name: &str, value: &str, style: &mut StyleModifer) {
|
|||
fn apply_background(name: &str, value: &str, style: &mut StyleModifer) {
|
||||
match name {
|
||||
"background-color" => {
|
||||
if let Some(c) = parse_color(value) {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_style.bg.replace(c);
|
||||
}
|
||||
}
|
||||
|
@ -548,7 +440,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
|
|||
"border" => {}
|
||||
"border-bottom" => {}
|
||||
"border-bottom-color" => {
|
||||
if let Some(c) = parse_color(value) {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_modifier.border_colors[2] = Some(c);
|
||||
}
|
||||
}
|
||||
|
@ -572,14 +464,14 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
|
|||
"border-color" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Some(c) = parse_color(values[0]) {
|
||||
if let Ok(c) = values[0].parse() {
|
||||
for i in 0..4 {
|
||||
style.tui_modifier.border_colors[i] = Some(c);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i, v) in values.into_iter().enumerate() {
|
||||
if let Some(c) = parse_color(v) {
|
||||
if let Ok(c) = v.parse() {
|
||||
style.tui_modifier.border_colors[i] = Some(c);
|
||||
}
|
||||
}
|
||||
|
@ -593,7 +485,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
|
|||
"border-image-width" => {}
|
||||
"border-left" => {}
|
||||
"border-left-color" => {
|
||||
if let Some(c) = parse_color(value) {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_modifier.border_colors[3] = Some(c);
|
||||
}
|
||||
}
|
||||
|
@ -621,7 +513,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
|
|||
}
|
||||
"border-right" => {}
|
||||
"border-right-color" => {
|
||||
if let Some(c) = parse_color(value) {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_modifier.border_colors[1] = Some(c);
|
||||
}
|
||||
}
|
||||
|
@ -647,7 +539,7 @@ fn apply_border(name: &str, value: &str, style: &mut StyleModifer) {
|
|||
}
|
||||
"border-top" => {}
|
||||
"border-top-color" => {
|
||||
if let Some(c) = parse_color(value) {
|
||||
if let Ok(c) = value.parse() {
|
||||
style.tui_modifier.border_colors[0] = Some(c);
|
||||
}
|
||||
}
|
||||
|
|
20
src/config.rs
Normal file
20
src/config.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ use tui::style::Style as TuiStyle;
|
|||
|
||||
use crate::{
|
||||
attributes::{apply_attributes, StyleModifer},
|
||||
style::RinkStyle,
|
||||
TuiModifier, TuiNode,
|
||||
};
|
||||
|
||||
|
@ -41,7 +42,7 @@ 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(),
|
||||
},
|
||||
|
@ -51,7 +52,7 @@ 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(),
|
||||
};
|
||||
|
||||
|
@ -87,7 +88,7 @@ pub fn collect_layout<'a>(
|
|||
node.mounted_id(),
|
||||
TuiNode {
|
||||
node,
|
||||
block_style: modifier.tui_style,
|
||||
block_style: modifier.tui_style.into(),
|
||||
tui_modifier: modifier.tui_modifier,
|
||||
layout: layout.new_node(modifier.style, &child_layout).unwrap(),
|
||||
},
|
||||
|
|
19
src/lib.rs
19
src/lib.rs
|
@ -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,12 @@ 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>,
|
||||
}
|
||||
|
@ -51,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();
|
||||
|
@ -139,7 +149,8 @@ pub fn render_vdom(
|
|||
&mut nodes,
|
||||
vdom,
|
||||
root_node,
|
||||
&TuiStyle::default(),
|
||||
&RinkStyle::default(),
|
||||
cfg,
|
||||
);
|
||||
assert!(nodes.is_empty());
|
||||
})?;
|
||||
|
|
286
src/render.rs
286
src/render.rs
|
@ -5,20 +5,111 @@ use stretch2::{
|
|||
prelude::{Layout, Size},
|
||||
Stretch,
|
||||
};
|
||||
use tui::{
|
||||
backend::CrosstermBackend,
|
||||
buffer::Buffer,
|
||||
layout::Rect,
|
||||
style::{Color, Style as TuiStyle},
|
||||
widgets::Widget,
|
||||
};
|
||||
use tui::{backend::CrosstermBackend, layout::Rect};
|
||||
|
||||
use crate::{BorderType, TuiNode, UnitSystem};
|
||||
use crate::{
|
||||
style::{RinkColor, RinkStyle},
|
||||
widget::{RinkBuffer, RinkCell, RinkWidget, WidgetWithContext},
|
||||
BorderType, Config, TuiNode, UnitSystem,
|
||||
};
|
||||
|
||||
const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
||||
|
||||
impl<'a> Widget for TuiNode<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
pub fn render_vnode<'a>(
|
||||
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
||||
layout: &Stretch,
|
||||
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, style, cfg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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, style, cfg);
|
||||
return;
|
||||
}
|
||||
|
||||
VNode::Placeholder(_) => return,
|
||||
|
||||
VNode::Element(_) | VNode::Text(_) => {}
|
||||
}
|
||||
|
||||
let id = node.try_mounted_id().unwrap();
|
||||
let mut node = layouts.remove(&id).unwrap();
|
||||
|
||||
let Layout { location, size, .. } = layout.layout(node.layout).unwrap();
|
||||
|
||||
let Point { x, y } = location;
|
||||
let Size { width, height } = size;
|
||||
|
||||
match node.node {
|
||||
VNode::Text(t) => {
|
||||
#[derive(Default)]
|
||||
struct Label<'a> {
|
||||
text: &'a str,
|
||||
style: RinkStyle,
|
||||
}
|
||||
|
||||
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,
|
||||
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(WidgetWithContext::new(label, cfg), area);
|
||||
}
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
|
||||
|
||||
let 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(WidgetWithContext::new(node, cfg), area);
|
||||
}
|
||||
|
||||
for el in el.children {
|
||||
render_vnode(frame, layout, layouts, vdom, el, &new_style, cfg);
|
||||
}
|
||||
}
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
VNode::Placeholder(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RinkWidget for TuiNode<'a> {
|
||||
fn render(self, area: Rect, mut buf: RinkBuffer<'_>) {
|
||||
use tui::symbols::line::*;
|
||||
|
||||
enum Direction {
|
||||
|
@ -29,11 +120,11 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
}
|
||||
|
||||
fn draw(
|
||||
buf: &mut Buffer,
|
||||
buf: &mut RinkBuffer,
|
||||
points_history: [[i32; 2]; 3],
|
||||
symbols: &Set,
|
||||
pos: [u16; 2],
|
||||
color: &Option<Color>,
|
||||
color: &Option<RinkColor>,
|
||||
) {
|
||||
let [before, current, after] = points_history;
|
||||
let start_dir = match [before[0] - current[0], before[1] - current[1]] {
|
||||
|
@ -61,14 +152,11 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
}
|
||||
};
|
||||
|
||||
let cell = buf.get_mut(
|
||||
(current[0] + pos[0] as i32) as u16,
|
||||
(current[1] + pos[1] as i32) as u16,
|
||||
);
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
cell.fg = *c;
|
||||
new_cell.fg = *c;
|
||||
}
|
||||
cell.symbol = match [start_dir, end_dir] {
|
||||
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,
|
||||
|
@ -87,6 +175,11 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
),
|
||||
}
|
||||
.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(
|
||||
|
@ -95,8 +188,8 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
arc_angle: f32,
|
||||
radius: f32,
|
||||
symbols: &Set,
|
||||
buf: &mut Buffer,
|
||||
color: &Option<Color>,
|
||||
mut buf: &mut RinkBuffer,
|
||||
color: &Option<RinkColor>,
|
||||
) {
|
||||
if radius < 0.0 {
|
||||
return;
|
||||
|
@ -143,7 +236,7 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
_ => todo!(),
|
||||
};
|
||||
draw(
|
||||
buf,
|
||||
&mut buf,
|
||||
[points_history[0], points_history[1], connecting_point],
|
||||
&symbols,
|
||||
pos,
|
||||
|
@ -152,7 +245,7 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
points_history = [points_history[1], connecting_point, points_history[2]];
|
||||
}
|
||||
|
||||
draw(buf, points_history, &symbols, pos, color);
|
||||
draw(&mut buf, points_history, &symbols, pos, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,13 +266,24 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
}
|
||||
}];
|
||||
|
||||
draw(buf, points_history, &symbols, pos, color);
|
||||
draw(&mut buf, points_history, &symbols, pos, color);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..4 {
|
||||
// the radius for the curve between this line and the next
|
||||
let r = match self.tui_modifier.border_types[(i + 1) % 4] {
|
||||
|
@ -231,41 +335,33 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
|
||||
let color = self.tui_modifier.border_colors[i].or(self.block_style.fg);
|
||||
|
||||
let mut new_cell = RinkCell::default();
|
||||
if let Some(c) = color {
|
||||
new_cell.fg = c;
|
||||
}
|
||||
match i {
|
||||
0 => {
|
||||
for x in (area.left() + last_radius[0] + 1)..(area.right() - radius[0]) {
|
||||
let cell = buf.get_mut(x, area.top());
|
||||
if let Some(c) = color {
|
||||
cell.fg = c;
|
||||
}
|
||||
cell.symbol = symbols.horizontal.to_string();
|
||||
new_cell.symbol = symbols.horizontal.to_string();
|
||||
buf.set(x, area.top(), &new_cell);
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
for y in (area.top() + last_radius[1] + 1)..(area.bottom() - radius[1]) {
|
||||
let cell = buf.get_mut(area.right() - 1, y);
|
||||
if let Some(c) = color {
|
||||
cell.fg = c;
|
||||
}
|
||||
cell.symbol = symbols.vertical.to_string();
|
||||
new_cell.symbol = symbols.vertical.to_string();
|
||||
buf.set(area.right() - 1, y, &new_cell);
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
for x in (area.left() + radius[0])..(area.right() - last_radius[0] - 1) {
|
||||
let cell = buf.get_mut(x, area.bottom() - 1);
|
||||
if let Some(c) = color {
|
||||
cell.fg = c;
|
||||
}
|
||||
cell.symbol = symbols.horizontal.to_string();
|
||||
new_cell.symbol = symbols.horizontal.to_string();
|
||||
buf.set(x, area.bottom() - 1, &new_cell);
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
for y in (area.top() + radius[1])..(area.bottom() - last_radius[1] - 1) {
|
||||
let cell = buf.get_mut(area.left(), y);
|
||||
if let Some(c) = color {
|
||||
cell.fg = c;
|
||||
}
|
||||
cell.symbol = symbols.vertical.to_string();
|
||||
new_cell.symbol = symbols.vertical.to_string();
|
||||
buf.set(area.left(), y, &new_cell);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
@ -278,7 +374,7 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
std::f32::consts::FRAC_PI_2,
|
||||
r,
|
||||
&symbols,
|
||||
buf,
|
||||
&mut buf,
|
||||
&color,
|
||||
),
|
||||
1 => draw_arc(
|
||||
|
@ -287,7 +383,7 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
std::f32::consts::FRAC_PI_2,
|
||||
r,
|
||||
&symbols,
|
||||
buf,
|
||||
&mut buf,
|
||||
&color,
|
||||
),
|
||||
2 => draw_arc(
|
||||
|
@ -296,7 +392,7 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
std::f32::consts::FRAC_PI_2,
|
||||
r,
|
||||
&symbols,
|
||||
buf,
|
||||
&mut buf,
|
||||
&color,
|
||||
),
|
||||
3 => draw_arc(
|
||||
|
@ -305,107 +401,11 @@ impl<'a> Widget for TuiNode<'a> {
|
|||
std::f32::consts::FRAC_PI_2,
|
||||
r,
|
||||
&symbols,
|
||||
buf,
|
||||
&mut buf,
|
||||
&color,
|
||||
),
|
||||
_ => panic!("more than 4 sides?"),
|
||||
}
|
||||
}
|
||||
|
||||
// todo: only render inside borders
|
||||
for x in area.left()..area.right() {
|
||||
for y in area.top()..area.bottom() {
|
||||
let cell = buf.get_mut(x, y);
|
||||
if let Some(c) = self.block_style.bg {
|
||||
cell.bg = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_vnode<'a>(
|
||||
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
||||
layout: &Stretch,
|
||||
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
|
||||
vdom: &'a VirtualDom,
|
||||
node: &'a VNode<'a>,
|
||||
// this holds the parents syle state for styled text rendering and potentially transparentcy
|
||||
style: &TuiStyle,
|
||||
) {
|
||||
match node {
|
||||
VNode::Fragment(f) => {
|
||||
for child in f.children {
|
||||
render_vnode(frame, layout, layouts, vdom, child, style);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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, style);
|
||||
return;
|
||||
}
|
||||
|
||||
VNode::Placeholder(_) => return,
|
||||
|
||||
VNode::Element(_) | VNode::Text(_) => {}
|
||||
}
|
||||
|
||||
let id = node.try_mounted_id().unwrap();
|
||||
let node = layouts.remove(&id).unwrap();
|
||||
|
||||
let Layout { location, size, .. } = layout.layout(node.layout).unwrap();
|
||||
|
||||
let Point { x, y } = location;
|
||||
let Size { width, height } = size;
|
||||
|
||||
match node.node {
|
||||
VNode::Text(t) => {
|
||||
#[derive(Default)]
|
||||
struct Label<'a> {
|
||||
text: &'a str,
|
||||
style: TuiStyle,
|
||||
}
|
||||
|
||||
impl<'a> Widget for Label<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_string(area.left(), area.top(), self.text, self.style);
|
||||
}
|
||||
}
|
||||
|
||||
// let s = Span::raw(t.text);
|
||||
|
||||
// Block::default().
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
|
||||
|
||||
let new_style = style.patch(node.block_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(node, area);
|
||||
}
|
||||
|
||||
for el in el.children {
|
||||
render_vnode(frame, layout, layouts, vdom, el, &new_style);
|
||||
}
|
||||
}
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
VNode::Placeholder(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
|
453
src/style.rs
Normal file
453
src/style.rs
Normal file
|
@ -0,0 +1,453 @@
|
|||
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 if other == Color::Reset {
|
||||
self.color
|
||||
} 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);
|
||||
let c = 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,
|
||||
);
|
||||
c
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value(
|
||||
v: &str,
|
||||
current_max_output: f32,
|
||||
required_max_output: f32,
|
||||
) -> Result<f32, ParseFloatError> {
|
||||
if v.ends_with('%') {
|
||||
Ok((v[..v.len() - 1].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 color.starts_with("rgb(") {
|
||||
let color_values = color[4..].trim_end_matches(')');
|
||||
if color.matches(',').count() == 4 {
|
||||
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 color.starts_with("rgba(") {
|
||||
let color_values = color[5..].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 color.starts_with("hsl(") {
|
||||
let color_values = color[4..].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 color.starts_with("hsla(") {
|
||||
let color_values = color[5..].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) / 6;
|
||||
let g = (((v as u16 % 36) / 6) * 255 + 3) / 6;
|
||||
let b = ((v as u16 % 6) * 255 + 3) / 6;
|
||||
let vals = [v / 36, (v % 36) / 6, v % 6];
|
||||
[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],
|
||||
},
|
||||
_ => todo!("{c:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
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: 24 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 * 6) / 255;
|
||||
let g = (rgb[1] as u16 * 6) / 255;
|
||||
let b = (rgb[2] as u16 * 6) / 255;
|
||||
let idx = 16 + r * 36 + g * 6 + b;
|
||||
Color::Indexed(idx as u8)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rgb_to_ansi() {
|
||||
for idx in 16..=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 && i <= 255);
|
||||
}
|
||||
} 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
96
src/widget.rs
Normal 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 == "" {
|
||||
if &cell.symbol != "" {
|
||||
// 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue