This commit is contained in:
Alexander Heldt
2025-11-30 21:29:43 +01:00
parent 59429d7721
commit 3589d94c29
7 changed files with 294 additions and 199 deletions

View File

@@ -0,0 +1,42 @@
import gleam/list
import gleam/string
import gleam/string_tree
import musicplayer/ui/internal
pub fn text(chars: String, x: Int, y: Int) {
internal.chars_at(chars, x, y)
}
pub fn box(x: Int, y: Int, width: Int, height: Int) -> String {
let box_chars = #("", "", "", "", "", "")
let #(tl, tr, bl, br, h, v) = box_chars
// Add top of box
let tree =
string_tree.new()
|> string_tree.append(internal.chars_at(
tl <> string.repeat(h, width - 2) <> tr,
x,
y,
))
// Add sides of box
let tree_with_sides =
list.range(1, height - 2)
|> list.map(fn(row) {
tree
|> string_tree.append(internal.chars_at(v, x, y + row))
|> string_tree.append(internal.chars_at(v, x + width - 1, y + row))
})
|> string_tree.concat
// Add bottom of box
tree_with_sides
|> string_tree.append(internal.chars_at(
bl <> string.repeat(h, width - 2) <> br,
x,
y + height - 1,
))
|> string_tree.to_string
}

View File

@@ -6,6 +6,7 @@ import gleam/string
import gleam/string_tree
import musicplayer/logging/logging
import musicplayer/ui/ansi
import musicplayer/ui/internal
pub type Section {
@@ -45,7 +46,7 @@ pub fn new() -> Layout {
Section("Root"),
Node(
t: Container,
content: "container",
content: "",
width_percent: 100,
height_percent: 100,
children: [
@@ -63,7 +64,7 @@ pub fn new() -> Layout {
height_percent: 50,
children: [
Section("A"),
Section("A"),
Section("B"),
],
),
),
@@ -91,7 +92,7 @@ pub fn new() -> Layout {
Section("Row2"),
Node(
t: Row,
content: "row 1",
content: "row 2",
width_percent: 100,
height_percent: 50,
children: [],
@@ -138,13 +139,13 @@ pub fn render(layout: Layout) -> Nil {
let container_top_left_x = 1
let container_top_left_y = 1
let ansi_ops =
RenderOps(
draw_box: fn(tree, x, y, w, h) {
string_tree.append(tree, draw_box(x, y, w, h))
let ansi_renders =
Renders(
box: fn(tree, x, y, w, h) {
string_tree.append(tree, ansi.box(x, y, w, h))
},
draw_text: fn(tree, text, x, y) {
string_tree.append(tree, internal.chars_at(text, x, y))
text: fn(tree, chars, x, y) {
string_tree.append(tree, ansi.text(chars, x, y))
},
)
@@ -158,16 +159,16 @@ pub fn render(layout: Layout) -> Nil {
0,
Section("Root"),
_,
ansi_ops,
ansi_renders,
)
|> string_tree.to_string
|> internal.print
}
pub type RenderOps(ctx) {
RenderOps(
draw_box: fn(ctx, Int, Int, Int, Int) -> ctx,
draw_text: fn(ctx, String, Int, Int) -> ctx,
pub type Renders(into) {
Renders(
text: fn(into, String, Int, Int) -> into,
box: fn(into, Int, Int, Int, Int) -> into,
)
}
@@ -181,15 +182,12 @@ pub fn render_generic(
// State
index: Int,
from: Section,
current_ctx: ctx,
// <--- Generic State
ops: RenderOps(ctx),
// <--- The Strategy
) -> ctx {
render_into: into,
renders: Renders(into),
) -> into {
case dict.get(layout.nodes, from) {
Error(_) -> current_ctx
Error(_) -> render_into
Ok(node) -> {
// --- 1. MATH (Shared Logic) ---
let margin = 2.0
let width =
@@ -208,16 +206,13 @@ pub fn render_generic(
Cell -> #(container_tl_x + { index * width }, container_tl_y)
}
// --- 2. RENDER PARENT (Using Generic Ops) ---
// We modify the context using the provided functions
let ctx_with_parent =
current_ctx
|> ops.draw_box(cx, cy, width, height)
|> ops.draw_text(node.content, cx, cy)
let parent =
render_into
|> renders.box(cx, cy, width, height)
|> renders.text(node.content, cx, cy)
// --- 3. RECURSE CHILDREN ---
list.index_map(node.children, fn(child, i) { #(i, child) })
|> list.fold(ctx_with_parent, fn(acc_ctx, ic) {
|> list.fold(parent, fn(acc_into, ic) {
let #(i, child) = ic
let cw =
@@ -243,43 +238,10 @@ pub fn render_generic(
child_origin_y,
i,
child,
acc_ctx,
ops,
acc_into,
renders,
)
})
}
}
}
fn draw_box(x: Int, y: Int, width: Int, height: Int) -> String {
let box_chars = #("", "", "", "", "", "")
let #(tl, tr, bl, br, h, v) = box_chars
// Add top of box
let tree =
string_tree.new()
|> string_tree.append(internal.chars_at(
tl <> string.repeat(h, width - 2) <> tr,
x,
y,
))
// Add sides of box
let tree_with_sides =
list.range(1, height - 2)
|> list.map(fn(row) {
tree
|> string_tree.append(internal.chars_at(v, x, y + row))
|> string_tree.append(internal.chars_at(v, x + width - 1, y + row))
})
|> string_tree.concat
// Add bottom of box
tree_with_sides
|> string_tree.append(internal.chars_at(
bl <> string.repeat(h, width - 2) <> br,
x,
y + height - 1,
))
|> string_tree.to_string
}

View File

@@ -0,0 +1,82 @@
import gleam/dict
import musicplayer/ui/internal
import musicplayer/ui/layout.{Container, Layout, Node, Section}
import musicplayer/ui/layout_examples/wait_for_input.{wait_for_input}
pub fn main() {
let assert Ok(width) = internal.io_get_columns()
let assert Ok(height) = internal.io_get_rows()
two_rows_with_cells(width, height)
|> layout.render
wait_for_input()
}
/// Two rows:
/// First row has two cells
/// Second row has no cells
fn two_rows_with_cells(width: Int, height: Int) -> layout.Layout {
let nodes =
dict.from_list([
#(
Section("Root"),
Node(
t: Container,
content: "container",
width_percent: 100,
height_percent: 100,
children: [
Section("Row1"),
Section("Row2"),
],
),
),
#(
Section("Row1"),
Node(
t: layout.Row,
content: "row 1",
width_percent: 100,
height_percent: 50,
children: [
Section("A"),
Section("A"),
],
),
),
#(
Section("A"),
Node(
t: layout.Cell,
content: "cell 1",
width_percent: 50,
height_percent: 100,
children: [],
),
),
#(
Section("B"),
Node(
t: layout.Cell,
content: "cell 2",
width_percent: 50,
height_percent: 100,
children: [],
),
),
#(
Section("Row2"),
Node(
t: layout.Row,
content: "row 2",
width_percent: 100,
height_percent: 50,
children: [],
),
),
])
Layout(width:, height:, nodes: nodes)
}

View File

@@ -1,3 +0,0 @@
pub fn main() {
echo "hello"
}

View File

@@ -0,0 +1,15 @@
import gleam/erlang/process.{type Name}
import musicplayer/input/input
import musicplayer/input/key.{type Key}
pub fn wait_for_input() {
let input_keys_name: Name(Key) = process.new_name("input_keys")
let assert Ok(_) = process.register(process.self(), input_keys_name)
input.new(input_keys_name)
process.new_selector()
|> process.select(process.named_subject(input_keys_name))
|> process.selector_receive_forever
}