From de5c2367fa4463b78272549243d5141fa8da487f Mon Sep 17 00:00:00 2001 From: Alexander Heldt Date: Sat, 6 Dec 2025 22:46:56 +0100 Subject: [PATCH] wip --- src/musicplayer/ui/control.gleam | 2 +- src/musicplayer/ui/layout.gleam | 83 ++++++++--------- .../ui/layout_examples/layout_examples.gleam | 88 ++++++++---------- src/musicplayer/ui/ui.gleam | 39 ++++++-- test/musicplayer/ui/layout_test.gleam | 91 ++++++++----------- test/musicplayer/ui/virtual_ansi.gleam | 6 +- 6 files changed, 152 insertions(+), 157 deletions(-) diff --git a/src/musicplayer/ui/control.gleam b/src/musicplayer/ui/control.gleam index b86282e..bb9a501 100644 --- a/src/musicplayer/ui/control.gleam +++ b/src/musicplayer/ui/control.gleam @@ -3,7 +3,7 @@ import gleam/erlang/process.{type Subject} import musicplayer/ui/layout.{type Section} pub type Control { - UpdateDimensions(width: Int, height: Int) + UpdateDimensions(columns: Int, rows: Int) UpdateState(section: Section, content: String) Exit(reply_to: Subject(Nil)) diff --git a/src/musicplayer/ui/layout.gleam b/src/musicplayer/ui/layout.gleam index c7b9993..0f7d0d8 100644 --- a/src/musicplayer/ui/layout.gleam +++ b/src/musicplayer/ui/layout.gleam @@ -2,6 +2,8 @@ import gleam/dict import gleam/float import gleam/int import gleam/list +import gleam/pair +import gleam/set import gleam/string import gleam/string_tree @@ -9,10 +11,11 @@ import musicplayer/logging/logging import musicplayer/ui/ansi import musicplayer/ui/internal +pub const root_section = "reserved_root_section" + pub type Section { Section(String) - Root Header Search PlaybackTime @@ -33,48 +36,40 @@ pub type Node { Cell(content: String, style: Style) } -/// The root node must use `Root` section pub type Layout { - Layout(width: Int, height: Int, nodes: dict.Dict(Section, Node)) + Layout(columns: Int, rows: Int, nodes: dict.Dict(Section, Node)) } -pub fn new() -> Layout { +pub fn new(columns: Int, rows: Int, nodes: List(#(Section, Node))) -> Layout { + let children = + nodes + |> list.flat_map(fn(node) { + case pair.second(node) { + Row(children: c, ..) -> c + Cell(..) -> [] + } + }) + |> set.from_list + + // All sections that are not children of other nodes will be added as + // children to the root + let orphans = + nodes + |> list.map(pair.first) + |> list.filter(fn(node) { !set.contains(children, node) }) + let nodes = - dict.from_list([ - #( - Root, - Row( - content: "Music Player", - style: Style(dimensions: Percent(width: 100, height: 100)), - children: [Header, Search, PlaybackTime], - ), + dict.from_list(nodes) + |> dict.insert( + Section(root_section), + Row( + content: "", + style: Style(dimensions: Percent(width: 100, height: 100)), + children: orphans, ), - #( - Header, - Row( - content: "Foo (1) | Bar (2) | Baz (3)", - style: Style(dimensions: Percent(width: 100, height: 33)), - children: [], - ), - ), - #( - Search, - Row( - content: "", - style: Style(dimensions: Percent(width: 100, height: 33)), - children: [], - ), - ), - #( - PlaybackTime, - Row( - content: "00:00", - style: Style(dimensions: Percent(width: 100, height: 33)), - children: [], - ), - ), - ]) - Layout(width: 0, height: 0, nodes: nodes) + ) + + Layout(columns:, rows:, nodes:) } pub fn update_section( @@ -95,21 +90,21 @@ pub fn update_section( } } -pub fn update_dimensions(layout: Layout, width: Int, height: Int) -> Layout { - Layout(..layout, width:, height:) +pub fn update_dimensions(layout: Layout, columns: Int, rows: Int) -> Layout { + Layout(..layout, columns:, rows:) } pub fn render(layout: Layout) -> Nil { internal.clear_screen() - [layout.width, layout.height] + [layout.columns, layout.rows] |> list.map(int.to_string) |> string.join(" ") |> string.append("layout - render: ", _) |> logging.log - let container_width = int.to_float(layout.width) - let container_height = int.to_float(layout.height) + let container_width = int.to_float(layout.columns) + let container_height = int.to_float(layout.rows) let container_top_left_x = 1 let container_top_left_y = 1 @@ -131,7 +126,7 @@ pub fn render(layout: Layout) -> Nil { container_top_left_x, container_top_left_y, 0, - Root, + Section(root_section), _, ansi_renders, ) diff --git a/src/musicplayer/ui/layout_examples/layout_examples.gleam b/src/musicplayer/ui/layout_examples/layout_examples.gleam index d231a79..2c1b39c 100644 --- a/src/musicplayer/ui/layout_examples/layout_examples.gleam +++ b/src/musicplayer/ui/layout_examples/layout_examples.gleam @@ -1,14 +1,12 @@ -import gleam/dict - import musicplayer/ui/internal -import musicplayer/ui/layout.{Layout, Percent, Section, Style} +import musicplayer/ui/layout.{Percent, Section, Style} 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() + let assert Ok(columns) = internal.io_get_columns() + let assert Ok(rows) = internal.io_get_rows() - two_rows_with_cells(width, height) + two_rows_with_cells(columns, rows) |> layout.render wait_for_input() @@ -17,54 +15,42 @@ pub fn main() { /// 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([ - #( - layout.Root, - layout.Row( - content: "container", - style: Style(dimensions: Percent(width: 100, height: 100)), - children: [ - Section("Row1"), - Section("Row2"), - ], - ), +fn two_rows_with_cells(columns: Int, rows: Int) -> layout.Layout { + let nodes = [ + #( + Section("Row1"), + layout.Row( + content: "row 1", + style: Style(dimensions: Percent(width: 100, height: 50)), + children: [ + Section("A"), + Section("B"), + ], ), - #( - Section("Row1"), - layout.Row( - content: "row 1", - style: Style(dimensions: Percent(width: 100, height: 50)), - children: [ - Section("A"), - Section("A"), - ], - ), + ), + #( + Section("A"), + layout.Cell( + content: "cell 1", + style: Style(dimensions: Percent(width: 50, height: 50)), ), - #( - Section("A"), - layout.Cell( - content: "cell 1", - style: Style(dimensions: Percent(width: 50, height: 50)), - ), + ), + #( + Section("B"), + layout.Cell( + content: "cell 2", + style: Style(dimensions: Percent(width: 50, height: 50)), ), - #( - Section("B"), - layout.Cell( - content: "cell 2", - style: Style(dimensions: Percent(width: 50, height: 50)), - ), + ), + #( + Section("Row2"), + layout.Row( + content: "row 2", + style: Style(dimensions: Percent(width: 50, height: 50)), + children: [], ), - #( - Section("Row2"), - layout.Row( - content: "row 2", - style: Style(dimensions: Percent(width: 50, height: 50)), - children: [], - ), - ), - ]) + ), + ] - Layout(width:, height:, nodes: nodes) + layout.new(columns, rows, nodes) } diff --git a/src/musicplayer/ui/ui.gleam b/src/musicplayer/ui/ui.gleam index 1dd2c49..84910ec 100644 --- a/src/musicplayer/ui/ui.gleam +++ b/src/musicplayer/ui/ui.gleam @@ -17,7 +17,34 @@ pub fn new() -> Result(Subject(Control), String) { let redraw_name = process.new_name("redraw") let redraw: Subject(Layout) = process.named_subject(redraw_name) - let layout = layout.new() + let layout = + [ + #( + layout.Header, + layout.Row( + content: "Foo (1) | Bar (2) | Baz (3)", + style: layout.Style(dimensions: layout.Percent(width: 100, height: 33)), + children: [], + ), + ), + #( + layout.Search, + layout.Row( + content: "", + style: layout.Style(dimensions: layout.Percent(width: 100, height: 33)), + children: [], + ), + ), + #( + layout.PlaybackTime, + layout.Row( + content: "00:00", + style: layout.Style(dimensions: layout.Percent(width: 100, height: 33)), + children: [], + ), + ), + ] + |> layout.new(0, 0, _) case actor.new(State(redraw, layout)) @@ -51,19 +78,19 @@ fn handle_message( control: Control, ) -> actor.Next(State(redraw, layout), Control) { case control { - control.UpdateDimensions(width, height) -> { - let current_dimensions = #(state.layout.width, state.layout.height) + control.UpdateDimensions(columns, rows) -> { + let current_dimensions = #(state.layout.columns, state.layout.rows) - case #(width, height) == current_dimensions { + case #(columns, rows) == current_dimensions { True -> actor.continue(state) False -> { - [width, height] + [columns, rows] |> list.map(int.to_string) |> string.join(" ") |> string.append("ui - updating dimensions: ", _) |> logging.log - let layout = layout.update_dimensions(state.layout, width, height) + let layout = layout.update_dimensions(state.layout, columns, rows) process.send(state.redraw, layout) actor.continue(State(..state, layout:)) diff --git a/test/musicplayer/ui/layout_test.gleam b/test/musicplayer/ui/layout_test.gleam index 15c8472..882871e 100644 --- a/test/musicplayer/ui/layout_test.gleam +++ b/test/musicplayer/ui/layout_test.gleam @@ -1,72 +1,59 @@ -import gleam/dict import gleam/io import gleam/string import gleeunit import gleeunit/should import musicplayer/ui/virtual_ansi -import musicplayer/ui/layout.{Layout, Percent, Section, Style} +import musicplayer/ui/layout.{Percent, Section, Style} pub fn main() -> Nil { gleeunit.main() } pub fn percent_layout_test() { - let layout = - Layout( - width: 80, - height: 20, - nodes: dict.from_list([ - #( - layout.Root, - layout.Row( - content: "container", - style: Style(dimensions: Percent(width: 100, height: 100)), - children: [ - Section("Row1"), - Section("Row2"), - ], - ), - ), - #( - Section("Row1"), - layout.Row( - content: "row 1", - style: Style(dimensions: Percent(width: 100, height: 50)), - children: [ - Section("A"), - Section("B"), - ], - ), - ), - #( + let nodes = [ + #( + Section("Row1"), + layout.Row( + content: "row 1", + style: Style(dimensions: Percent(width: 100, height: 50)), + children: [ Section("A"), - layout.Cell( - content: "cell 1", - style: Style(dimensions: Percent(width: 50, height: 100)), - ), - ), - #( Section("B"), - layout.Cell( - content: "cell 2", - style: Style(dimensions: Percent(width: 50, height: 100)), - ), - ), - #( - Section("Row2"), - layout.Row( - content: "row 1", - style: Style(dimensions: Percent(width: 100, height: 50)), - children: [], - ), - ), - ]), - ) + ], + ), + ), + #( + Section("A"), + layout.Cell( + content: "cell 1", + style: Style(dimensions: Percent(width: 50, height: 100)), + ), + ), + #( + Section("B"), + layout.Cell( + content: "cell 2", + style: Style(dimensions: Percent(width: 50, height: 100)), + ), + ), + #( + Section("Row2"), + layout.Row( + content: "row 1", + style: Style(dimensions: Percent(width: 100, height: 50)), + children: [], + ), + ), + ] + + let columns = 80 + let rows = 20 + let layout = layout.new(columns, rows, nodes) let expected = " -container──────────────────────────────────────────────────────────────────────┐ +┌──────────────────────────────────────────────────────────────────────────────┐ │row 1────────────────────────────────────────────────────────────────────────┐│ ││cell 1───────────────────────────────┐cell 2───────────────────────────────┐││ │││ ││ │││ diff --git a/test/musicplayer/ui/virtual_ansi.gleam b/test/musicplayer/ui/virtual_ansi.gleam index 525ee2a..88562c6 100644 --- a/test/musicplayer/ui/virtual_ansi.gleam +++ b/test/musicplayer/ui/virtual_ansi.gleam @@ -18,12 +18,12 @@ pub fn render(layout: Layout) -> String { let screen = layout.render_generic( layout, - int.to_float(layout.width), - int.to_float(layout.height), + int.to_float(layout.columns), + int.to_float(layout.rows), 1, 1, 0, - layout.Root, + layout.Section(layout.root_section), dict.new(), test_renders, )