From 7212df3abbbaf99209a8ee471159d33e088005ae Mon Sep 17 00:00:00 2001 From: Alexander Heldt Date: Sun, 30 Nov 2025 16:27:31 +0100 Subject: [PATCH] wip-before-change-of-order --- src/musicplayer/ui/layout.gleam | 35 +-- src/musicplayer/ui/ui.gleam | 26 -- test/musicplayer/ui/layout_test.gleam | 330 +++++++++++++++++++------- 3 files changed, 268 insertions(+), 123 deletions(-) diff --git a/src/musicplayer/ui/layout.gleam b/src/musicplayer/ui/layout.gleam index f5c8cf1..c4db104 100644 --- a/src/musicplayer/ui/layout.gleam +++ b/src/musicplayer/ui/layout.gleam @@ -249,31 +249,34 @@ pub fn render_loop( } fn draw_box(x: Int, y: Int, width: Int, height: Int) -> String { - let box_tree = string_tree.new() let box_chars = #("┌", "┐", "└", "┘", "─", "│") let #(tl, tr, bl, br, h, v) = box_chars - let box_tree = - string_tree.append( - box_tree, - internal.chars_at(tl <> string.repeat(h, width - 2) <> tr, x, y), - ) + // Add top of box + let tree = + string_tree.new() + |> string_tree.append(internal.chars_at( + tl <> string.repeat(h, width - 2) <> tr, + x, + y, + )) - let box_trees = + // Add sides of box + let tree_with_sides = list.range(1, height - 2) |> list.map(fn(row) { - box_tree + 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 - string_tree.append( - string_tree.concat(box_trees), - internal.chars_at( - bl <> string.repeat(h, width - 2) <> br, - x, - y + height - 1, - ), - ) + // 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 } diff --git a/src/musicplayer/ui/ui.gleam b/src/musicplayer/ui/ui.gleam index 6210c70..1dd2c49 100644 --- a/src/musicplayer/ui/ui.gleam +++ b/src/musicplayer/ui/ui.gleam @@ -3,7 +3,6 @@ import gleam/int import gleam/list import gleam/otp/actor import gleam/string -import gleam/string_tree.{type StringTree} import musicplayer/logging/logging import musicplayer/ui/control.{type Control} @@ -104,28 +103,3 @@ fn update_dimensions_on_interval(ui: Subject(Control), interval_ms: Int) { process.sleep(interval_ms) update_dimensions_on_interval(ui, interval_ms) } -// fn render_layout(layout: Layout, from: Section) -> Nil { -// string_tree.new() -// |> render_layout_loop(layout, from, _) -// |> string_tree.to_string -// |> ui_internal.print -// } - -// fn render_layout_loop( -// layout: Layout, -// from: Section, -// tree: StringTree, -// ) -> StringTree { -// case dict.get(layout.nodes, from) { -// Error(_) -> tree -// Ok(node) -> { -// let acc_after_children = -// list.fold(node.children, tree, fn(current_acc, child_id) { -// render_layout_loop(layout, child_id, current_acc) -// }) - -// acc_after_children -// |> string_tree.append(ui_internal.chars_at(node.content, node.x, node.y)) -// } -// } -// } diff --git a/test/musicplayer/ui/layout_test.gleam b/test/musicplayer/ui/layout_test.gleam index a9f9cc0..8f767d2 100644 --- a/test/musicplayer/ui/layout_test.gleam +++ b/test/musicplayer/ui/layout_test.gleam @@ -1,87 +1,255 @@ -// import gleam/dict -// import gleam/int -// import gleeunit +import gleam/dict +import gleam/float +import gleam/int +import gleam/list +import gleam/string +import gleeunit -// import musicplayer/ui/layout.{type Node, Layout, Section} +import musicplayer/ui/layout.{type Layout, type Section, Layout, Node, Section} -// pub fn main() -> Nil { -// gleeunit.main() -// } +pub fn main() -> Nil { + gleeunit.main() +} -// pub fn foo_test() { -// let expected = "" +pub fn foo_test() { + let layout = + Layout( + width: 80, + height: 20, + nodes: dict.from_list([ + #( + Section("Root"), + Node( + t: layout.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("B"), + ], + ), + ), + #( + 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 1", + width_percent: 100, + height_percent: 50, + children: [], + ), + ), + ]), + ) -// let layout = -// Layout( -// width: 80, -// height: 20, -// 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: Row, -// content: "row 1", -// width_percent: 100, -// height_percent: 50, -// children: [ -// Section("A"), -// Section("B"), -// ], -// ), -// ), -// #( -// Section("A"), -// Node( -// t: Cell, -// content: "cell 1", -// width_percent: 50, -// height_percent: 100, -// children: [], -// ), -// ), -// #( -// B, -// Node( -// t: Cell, -// content: "cell 2", -// width_percent: 50, -// height_percent: 100, -// children: [], -// ), -// ), -// #( -// Section("Row2"), -// Node( -// t: Row, -// content: "row 1", -// width_percent: 100, -// height_percent: 50, -// children: [], -// ), -// ), -// ]), -// ) + let visual = render_to_visual(layout, Section("Root"), 80, 20) + assert visual == "" +} -// let container_width = int.to_float(layout.width) -// let container_height = int.to_float(layout.height) -// let container_top_left_x = 1 -// let container_top_left_y = 1 +/// The visual grid: (x, y) -> Character +pub type Screen = + dict.Dict(#(Int, Int), String) -// let assert Ok(data) = -// layout.render_loop(layout, container_width, container_height) -// assert data == 123.456789 -// } +pub fn render_to_visual( + layout: Layout, + root: Section, + width: Int, + height: Int, +) -> String { + let screen = dict.new() + + // Initial container settings (matching your render function) + let container_width = int.to_float(width) + let container_height = int.to_float(height) + let container_top_left_x = 1 + let container_top_left_y = 1 + + let final_screen = + render_visual_loop( + layout, + container_width, + container_height, + container_top_left_x, + container_top_left_y, + 0, + // root index + root, + screen, + ) + + screen_to_string(final_screen) +} + +fn render_visual_loop( + layout: Layout, + c_width: Float, + c_height: Float, + c_x: Int, + c_y: Int, + index: Int, + from: Section, + screen: Screen, +) -> Screen { + case dict.get(layout.nodes, from) { + Error(_) -> screen + Ok(node) -> { + let margin = 2.0 + + // 1. RECURSE CHILDREN + // We process children first so the parent draws ON TOP of them later + // (This matches your string_tree.append logic order) + let screen_after_children = + list.index_map(node.children, fn(c, i) { #(i, c) }) + |> list.fold(screen, fn(acc_screen, ic) { + let #(i, child) = ic + + // Logic from your code: + let cw = + c_width *. { int.to_float(node.width_percent) /. 100.0 } -. margin + |> float.floor + let ch = + c_height *. { int.to_float(node.height_percent) /. 100.0 } -. margin + |> float.floor + let cx = c_x + 1 + let cy = c_y + 1 + + render_visual_loop(layout, cw, ch, cx, cy, i, child, acc_screen) + }) + + // 2. CALCULATE CURRENT NODE DIMENSIONS (Logic from your code) + let width = + c_width *. { int.to_float(node.width_percent) /. 100.0 } + |> float.floor + |> float.truncate + let height = + c_height *. { int.to_float(node.height_percent) /. 100.0 } + |> float.floor + |> float.truncate + + // 3. CALCULATE COORDINATES (Logic from your code) + let #(cx, cy) = case node.t { + layout.Container -> #(c_x, c_y) + layout.Row -> #(c_x, c_y + { index * height }) + layout.Cell -> #(c_x + { index * width }, c_y) + } + + // 4. DRAW BOX AND CONTENT + screen_after_children + |> plot_box(cx, cy, width, height) + |> plot_text(node.content, cx, cy) + } + } +} + +// --- Drawing Primitives --- + +fn plot_text(screen: Screen, text: String, start_x: Int, y: Int) -> Screen { + text + |> string.to_graphemes + |> list.index_fold(screen, fn(acc, char, i) { + dict.insert(acc, #(start_x + i, y), char) + }) +} + +fn plot_box(screen: Screen, x: Int, y: Int, w: Int, h: Int) -> Screen { + let box_chars = #("┌", "┐", "└", "┘", "─", "│") + let #(tl, tr, bl, br, hor, ver) = box_chars + + // If box is too small to render, return screen as is + case w < 2 || h < 2 { + True -> screen + False -> { + screen + // Corners + |> dict.insert(#(x, y), tl) + |> dict.insert(#(x + w - 1, y), tr) + |> dict.insert(#(x, y + h - 1), bl) + |> dict.insert(#(x + w - 1, y + h - 1), br) + // Top and Bottom edges + |> plot_line_hor(x + 1, y, w - 2, hor) + |> plot_line_hor(x + 1, y + h - 1, w - 2, hor) + // Side edges + |> plot_line_ver(x, y + 1, h - 2, ver) + |> plot_line_ver(x + w - 1, y + 1, h - 2, ver) + } + } +} + +fn plot_line_hor( + screen: Screen, + x: Int, + y: Int, + len: Int, + char: String, +) -> Screen { + list.range(0, len - 1) + |> list.fold(screen, fn(acc, i) { dict.insert(acc, #(x + i, y), char) }) +} + +fn plot_line_ver( + screen: Screen, + x: Int, + y: Int, + len: Int, + char: String, +) -> Screen { + list.range(0, len - 1) + |> list.fold(screen, fn(acc, i) { dict.insert(acc, #(x, y + i), char) }) +} + +// --- Output Formatting --- + +fn screen_to_string(screen: Screen) -> String { + let keys = dict.keys(screen) + let max_x = list.fold(keys, 0, fn(m, k) { int.max(m, k.0) }) + let max_y = list.fold(keys, 0, fn(m, k) { int.max(m, k.1) }) + let min_y = list.fold(keys, 1000, fn(m, k) { int.min(m, k.1) }) + + // We add +1 to max_x to account for the last character width + list.range(min_y, max_y) + |> list.map(fn(y) { + list.range(1, max_x) + |> list.map(fn(x) { + case dict.get(screen, #(x, y)) { + Ok(char) -> char + Error(_) -> " " + } + }) + |> string.join("") + }) + |> string.join("\n") +}