From 95eaeb60f419fef051b03fbce1438d919fe79405 Mon Sep 17 00:00:00 2001 From: Alexander Heldt Date: Sun, 21 Dec 2025 13:21:14 +0100 Subject: [PATCH] Layout consist of multiple Views --- src/musicplayer/ui/layout.gleam | 106 +++++++++++++----- .../ui/layout_examples/layout_examples.gleam | 64 ++++++----- src/musicplayer/ui/ui.gleam | 53 +++++---- test/musicplayer/ui/layout_test.gleam | 70 ++++++------ 4 files changed, 177 insertions(+), 116 deletions(-) diff --git a/src/musicplayer/ui/layout.gleam b/src/musicplayer/ui/layout.gleam index 22fc82a..34978eb 100644 --- a/src/musicplayer/ui/layout.gleam +++ b/src/musicplayer/ui/layout.gleam @@ -33,13 +33,41 @@ pub type Node { Cell(content: String, style: Style) } +pub type ViewIdx = + Int + +pub type View = + dict.Dict(Section, Node) + +// Layout consists of a list Views, and only one View is rendered at a time pub type Layout { - Layout(columns: Int, rows: Int, nodes: dict.Dict(Section, Node)) + Layout( + columns: Int, + rows: Int, + current_view: ViewIdx, + views: dict.Dict(ViewIdx, View), + ) } -pub fn new(columns: Int, rows: Int, nodes: List(#(Section, Node))) -> Layout { +pub fn new( + columns: Int, + rows: Int, + views: List(List(#(Section, Node))), +) -> Layout { + let views = + list.index_map(views, fn(view_nodes, i) { #(i, view_nodes) }) + |> list.fold(dict.new(), fn(view_acc, iv) { + let #(i, view_nodes) = iv + + dict.insert(view_acc, i, view_loop(i, view_nodes)) + }) + + Layout(columns:, rows:, current_view: 0, views:) +} + +fn view_loop(i: ViewIdx, view_nodes: List(#(Section, Node))) -> View { let children = - nodes + view_nodes |> list.flat_map(fn(node) { case pair.second(node) { Row(children: c, ..) -> c @@ -51,22 +79,19 @@ pub fn new(columns: Int, rows: Int, nodes: List(#(Section, Node))) -> Layout { // All sections that are not children of other nodes will be added as // children to the root let orphans = - nodes + view_nodes |> list.map(pair.first) |> list.filter(fn(node) { !set.contains(children, node) }) - let nodes = - dict.from_list(nodes) - |> dict.insert( - Section(root_section), - Row( - content: "", - style: Style(dimensions: Percent(width: 100, height: 100)), - children: orphans, - ), - ) - - Layout(columns:, rows:, nodes:) + dict.from_list(view_nodes) + |> dict.insert( + Section(string.append("view_", string.inspect(i))), + Row( + content: "", + style: Style(dimensions: Percent(width: 100, height: 100)), + children: orphans, + ), + ) } pub fn update_section( @@ -74,16 +99,25 @@ pub fn update_section( section: Section, content: String, ) -> Layout { - case dict.get(layout.nodes, section) { + case dict.get(layout.views, layout.current_view) { Error(_) -> layout - Ok(node) -> { - let updated = case node { - Cell(..) -> Cell(..node, content: content) - Row(..) -> Row(..node, content: content) - } + Ok(view) -> + case dict.get(view, section) { + Error(_) -> layout + Ok(node) -> { + let updated_node = case node { + Cell(..) -> Cell(..node, content: content) + Row(..) -> Row(..node, content: content) + } - Layout(..layout, nodes: dict.insert(layout.nodes, section, updated)) - } + let updated_view = dict.insert(view, section, updated_node) + + Layout( + ..layout, + views: dict.insert(layout.views, layout.current_view, updated_view), + ) + } + } } } @@ -107,11 +141,21 @@ pub fn render(layout: Layout) -> Nil { position_index: 0, ) - let buffer: Buffer = dict.new() + case dict.get(layout.views, layout.current_view) { + Error(_) -> Nil + Ok(view) -> { + let buffer: Buffer = dict.new() - render_loop(layout, context, Section(root_section), buffer) - |> plot.flush_buffer(layout.columns, layout.rows) - |> internal.update + render_loop( + view, + context, + Section(string.append("view_", string.inspect(layout.current_view))), + buffer, + ) + |> plot.flush_buffer(layout.columns, layout.rows) + |> internal.update + } + } } pub type RenderContext { @@ -125,12 +169,12 @@ pub type RenderContext { } pub fn render_loop( - layout: Layout, + view: View, context: RenderContext, from: Section, buffer: Buffer, ) -> Buffer { - case dict.get(layout.nodes, from) { + case dict.get(view, from) { Error(_) -> buffer Ok(node) -> { // Margin between container and the node being rendered @@ -193,7 +237,7 @@ pub fn render_loop( position_index: i, ) - render_loop(layout, context, child, acc_buffer) + render_loop(view, context, child, acc_buffer) }) } } diff --git a/src/musicplayer/ui/layout_examples/layout_examples.gleam b/src/musicplayer/ui/layout_examples/layout_examples.gleam index 2c1b39c..55e127c 100644 --- a/src/musicplayer/ui/layout_examples/layout_examples.gleam +++ b/src/musicplayer/ui/layout_examples/layout_examples.gleam @@ -16,41 +16,43 @@ pub fn main() { /// First row has two cells /// Second row has no cells 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"), - ], + let views = [ + [ + #( + Section("Row1"), + layout.Row( + content: "row 1", + style: Style(dimensions: Percent(width: 100, height: 50)), + children: [ + Section("A"), + Section("B"), + ], + ), ), - ), - #( - 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.new(columns, rows, nodes) + layout.new(columns, rows, views) } diff --git a/src/musicplayer/ui/ui.gleam b/src/musicplayer/ui/ui.gleam index 84910ec..4d7cf2d 100644 --- a/src/musicplayer/ui/ui.gleam +++ b/src/musicplayer/ui/ui.gleam @@ -19,30 +19,41 @@ pub fn new() -> Result(Subject(Control), String) { 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.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.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.PlaybackTime, + layout.Row( + content: "00:00", + style: layout.Style(dimensions: layout.Percent( + width: 100, + height: 33, + )), + children: [], + ), ), - ), + ], ] |> layout.new(0, 0, _) diff --git a/test/musicplayer/ui/layout_test.gleam b/test/musicplayer/ui/layout_test.gleam index 1b6a3c6..871889e 100644 --- a/test/musicplayer/ui/layout_test.gleam +++ b/test/musicplayer/ui/layout_test.gleam @@ -12,45 +12,47 @@ pub fn main() -> Nil { } pub fn percent_layout_test() { - let nodes = [ - #( - Section("Row1"), - layout.Row( - content: "row 1", - style: Style(dimensions: Percent(width: 100, height: 50)), - children: [ - Section("A"), - Section("B"), - ], + let views = [ + [ + #( + Section("Row1"), + layout.Row( + content: "row 1", + style: Style(dimensions: Percent(width: 100, height: 50)), + children: [ + Section("A"), + Section("B"), + ], + ), ), - ), - #( - Section("A"), - layout.Cell( - content: "cell 1", - style: Style(dimensions: Percent(width: 50, height: 100)), + #( + 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("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("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 layout = layout.new(columns, rows, views) let expected = " @@ -87,11 +89,13 @@ pub fn percent_layout_test() { position_index: 0, ) + let assert Ok(view) = dict.get(layout.views, layout.current_view) + let flushed = layout.render_loop( - layout, + view, context, - Section(layout.root_section), + Section(string.append("view_", string.inspect(layout.current_view))), dict.new(), ) |> plot.flush_buffer(layout.columns, layout.rows)