///|
/// Render crater Layout to CharBuffer
///|
/// Rendering options for a node
pub(all) struct RenderStyle {
fg : Color
bg : Color
bold : Bool
underline : Bool
border : BorderChars?
border_fg : Color
}
///|
pub fn RenderStyle::default() -> RenderStyle {
{
fg: Color::white(),
bg: Color::transparent(),
bold: false,
underline: false,
border: None,
border_fg: Color::white(),
}
}
///|
/// Map from node ID to render style
pub struct RenderStyleMap(Map[String, RenderStyle])
///|
pub fn RenderStyleMap::new() -> RenderStyleMap {
RenderStyleMap(Map::new())
}
///|
pub fn RenderStyleMap::set(
self : RenderStyleMap,
id : String,
style : RenderStyle,
) -> Unit {
self.0.set(id, style)
}
///|
pub fn RenderStyleMap::get(self : RenderStyleMap, id : String) -> RenderStyle {
match self.0.get(id) {
Some(s) => s
None => RenderStyle::default()
}
}
///|
/// Map from node ID to text content
pub struct TextContentMap(Map[String, String])
///|
pub fn TextContentMap::new() -> TextContentMap {
TextContentMap(Map::new())
}
///|
pub fn TextContentMap::set(
self : TextContentMap,
id : String,
text : String,
) -> Unit {
self.0.set(id, text)
}
///|
pub fn TextContentMap::get(self : TextContentMap, id : String) -> String? {
self.0.get(id)
}
///|
/// Render Layout to CharBuffer
pub fn render_layout(
buf : CharBuffer,
layout : @node.Layout,
styles : RenderStyleMap,
texts : TextContentMap,
offset_x : Int,
offset_y : Int,
) -> Unit {
let x = offset_x + layout.x.to_int()
let y = offset_y + layout.y.to_int()
let w = layout.width.to_int()
let h = layout.height.to_int()
let style = styles.get(layout.id)
// Fill background if not transparent
if not(style.bg.is_transparent()) {
buf.fill_bg(x, y, w, h, style.bg)
}
// Draw border if specified
match style.border {
Some(border) => draw_border(buf, x, y, w, h, border, style.border_fg)
None => ()
}
// Draw text content if present
match texts.get(layout.id) {
Some(text) => {
let has_border = style.border is Some(_)
let content_x = x +
(if has_border { 1 } else { 0 }) +
layout.padding.left.to_int()
let content_y = y +
(if has_border { 1 } else { 0 }) +
layout.padding.top.to_int()
let content_w = w -
(if has_border { 2 } else { 0 }) -
layout.padding.left.to_int() -
layout.padding.right.to_int()
let content_h = h -
(if has_border { 2 } else { 0 }) -
layout.padding.top.to_int() -
layout.padding.bottom.to_int()
if content_w > 0 && content_h > 0 {
let text_style : TextStyle = {
fg: style.fg,
bg: Color::transparent(),
bold: style.bold,
underline: style.underline,
}
let _ = buf.write_text_wrapped(
content_x, content_y, text, text_style, content_w, content_h,
)
}
}
None => ()
}
// Render children
for child in layout.children {
render_layout(buf, child, styles, texts, x, y)
}
}
///|
fn draw_border(
buf : CharBuffer,
x : Int,
y : Int,
w : Int,
h : Int,
border : BorderChars,
fg : Color,
) -> Unit {
if w < 2 || h < 2 {
return
}
// Top-left corner
buf.set_cell(x, y, { ..CharCell::default(), char: border.top_left, fg })
// Top-right corner
buf.set_cell(x + w - 1, y, {
..CharCell::default(),
char: border.top_right,
fg,
})
// Bottom-left corner
buf.set_cell(x, y + h - 1, {
..CharCell::default(),
char: border.bottom_left,
fg,
})
// Bottom-right corner
buf.set_cell(x + w - 1, y + h - 1, {
..CharCell::default(),
char: border.bottom_right,
fg,
})
// Top and bottom edges
for col = x + 1; col < x + w - 1; col = col + 1 {
buf.set_cell(col, y, { ..CharCell::default(), char: border.horizontal, fg })
buf.set_cell(col, y + h - 1, {
..CharCell::default(),
char: border.horizontal,
fg,
})
}
// Left and right edges
for row = y + 1; row < y + h - 1; row = row + 1 {
buf.set_cell(x, row, { ..CharCell::default(), char: border.vertical, fg })
buf.set_cell(x + w - 1, row, {
..CharCell::default(),
char: border.vertical,
fg,
})
}
}
///|
/// Quick render function: Layout -> ANSI string
pub fn render_to_ansi(
layout : @node.Layout,
styles : RenderStyleMap,
texts : TextContentMap,
width : Int,
height : Int,
) -> String {
let buf = CharBuffer::new(width, height)
render_layout(buf, layout, styles, texts, 0, 0)
buffer_to_ansi(buf)
}