charming 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/charming/application.rb +22 -5
  3. data/lib/charming/cli.rb +3 -3
  4. data/lib/charming/controller/class_methods.rb +2 -2
  5. data/lib/charming/controller/command_palette.rb +2 -2
  6. data/lib/charming/controller/component_dispatching.rb +47 -3
  7. data/lib/charming/controller/focus.rb +123 -0
  8. data/lib/charming/controller/focus_management.rb +1 -1
  9. data/lib/charming/controller/rendering.rb +6 -17
  10. data/lib/charming/controller/session_state.rb +12 -1
  11. data/lib/charming/controller.rb +11 -2
  12. data/lib/charming/database/commands.rb +106 -0
  13. data/lib/charming/generators/component_generator.rb +1 -1
  14. data/lib/charming/generators/database_installer.rb +154 -0
  15. data/lib/charming/generators/model_generator.rb +2 -10
  16. data/lib/charming/generators/name.rb +1 -1
  17. data/lib/charming/generators/templates/app/application.template +1 -1
  18. data/lib/charming/generators/templates/app/layout.template +3 -6
  19. data/lib/charming/generators/templates/app/view.template +1 -1
  20. data/lib/charming/generators/templates/component/component.rb.template +1 -1
  21. data/lib/charming/generators/templates/screen/view.rb.template +1 -1
  22. data/lib/charming/generators/templates/view/view.rb.template +1 -1
  23. data/lib/charming/generators/view_generator.rb +1 -1
  24. data/lib/charming/internal/renderer/differential.rb +13 -5
  25. data/lib/charming/internal/terminal/tty_backend.rb +22 -2
  26. data/lib/charming/presentation/component.rb +3 -5
  27. data/lib/charming/presentation/components/activity_indicator.rb +173 -134
  28. data/lib/charming/presentation/components/command_palette.rb +94 -96
  29. data/lib/charming/presentation/components/command_palette_modal.rb +33 -0
  30. data/lib/charming/presentation/components/empty_state.rb +47 -49
  31. data/lib/charming/presentation/components/form/builder.rb +52 -54
  32. data/lib/charming/presentation/components/form/confirm.rb +49 -51
  33. data/lib/charming/presentation/components/form/field.rb +94 -96
  34. data/lib/charming/presentation/components/form/input.rb +53 -55
  35. data/lib/charming/presentation/components/form/note.rb +27 -29
  36. data/lib/charming/presentation/components/form/select.rb +84 -86
  37. data/lib/charming/presentation/components/form/textarea.rb +67 -69
  38. data/lib/charming/presentation/components/form.rb +120 -122
  39. data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
  40. data/lib/charming/presentation/components/list.rb +123 -125
  41. data/lib/charming/presentation/components/markdown.rb +21 -23
  42. data/lib/charming/presentation/components/modal.rb +46 -48
  43. data/lib/charming/presentation/components/progressbar.rb +51 -53
  44. data/lib/charming/presentation/components/spinner.rb +40 -42
  45. data/lib/charming/presentation/components/table.rb +109 -111
  46. data/lib/charming/presentation/components/text_area.rb +219 -221
  47. data/lib/charming/presentation/components/text_input.rb +120 -122
  48. data/lib/charming/presentation/components/viewport.rb +218 -220
  49. data/lib/charming/presentation/layout/builder.rb +64 -66
  50. data/lib/charming/presentation/layout/overlay.rb +48 -50
  51. data/lib/charming/presentation/layout/pane.rb +129 -118
  52. data/lib/charming/presentation/layout/rect.rb +19 -16
  53. data/lib/charming/presentation/layout/screen_layout.rb +47 -42
  54. data/lib/charming/presentation/layout/split.rb +107 -102
  55. data/lib/charming/presentation/layout.rb +28 -30
  56. data/lib/charming/presentation/markdown/render_context.rb +30 -14
  57. data/lib/charming/presentation/markdown/renderer.rb +302 -79
  58. data/lib/charming/presentation/markdown/style_config.rb +215 -0
  59. data/lib/charming/presentation/markdown/syntax_highlighter.rb +58 -59
  60. data/lib/charming/presentation/markdown.rb +4 -6
  61. data/lib/charming/presentation/template_view.rb +22 -24
  62. data/lib/charming/presentation/templates/erb_handler.rb +4 -6
  63. data/lib/charming/presentation/templates.rb +47 -49
  64. data/lib/charming/presentation/ui/ansi_codes.rb +66 -68
  65. data/lib/charming/presentation/ui/ansi_slicer.rb +67 -69
  66. data/lib/charming/presentation/ui/border.rb +24 -26
  67. data/lib/charming/presentation/ui/border_painter.rb +37 -39
  68. data/lib/charming/presentation/ui/canvas.rb +59 -61
  69. data/lib/charming/presentation/ui/style.rb +173 -175
  70. data/lib/charming/presentation/ui/theme.rb +133 -135
  71. data/lib/charming/presentation/ui/width.rb +12 -14
  72. data/lib/charming/presentation/ui.rb +69 -71
  73. data/lib/charming/presentation/view.rb +110 -105
  74. data/lib/charming/router.rb +3 -8
  75. data/lib/charming/runtime.rb +25 -10
  76. data/lib/charming/version.rb +1 -1
  77. data/lib/charming.rb +5 -4
  78. metadata +43 -9
  79. data/lib/charming/database_commands.rb +0 -103
  80. data/lib/charming/database_installer.rb +0 -152
  81. data/lib/charming/focus.rb +0 -121
  82. data/lib/charming/presentation/markdown/block_renderers.rb +0 -120
  83. data/lib/charming/presentation/markdown/inline_renderers.rb +0 -68
@@ -1,56 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Layout
6
- # Overlay is a compositing node used by ScreenLayout for floating elements (modals,
7
- # dialogs, command palettes). It positions its content at *top*/*left* (each may be
8
- # `:center` or an absolute cell offset) and optionally sizes it via *width*/*height*
9
- # with an outer *style*.
10
- class Overlay
11
- # The vertical and horizontal offset (cell count or `:center`) of the overlay
12
- # within the parent canvas.
13
- attr_reader :top, :left
14
-
15
- # *content* (or a *block*) provides the body. *top*/*left* default to :center.
16
- # *width*/*height* fix the overlay's dimensions; when unset, the content's natural
17
- # size is used. *style* wraps the rendered content in a UI::Style.
18
- def initialize(content: nil, block: nil, view: nil, top: :center, left: :center, width: nil, height: nil, style: nil)
19
- @content = content
20
- @block = block
21
- @view = view
22
- @top = top
23
- @left = left
24
- @width = width
25
- @height = height
26
- @style = style
27
- end
28
-
29
- # Renders the overlay's content; when *width* or *height* is set, places the rendered
30
- # content into a sized canvas before returning.
31
- def render
32
- return styled_content unless width || height
33
-
34
- UI.place(styled_content, width: width || UI.block_width(styled_content.lines(chomp: true)), height: height || styled_content.lines.count)
35
- end
36
-
37
- private
38
-
39
- # The raw content, body block, view, and sizing/style options.
40
- attr_reader :content, :block, :view, :width, :height, :style
41
-
42
- # Returns the rendered content wrapped in the configured *style* (when present).
43
- def styled_content
44
- return rendered_content unless style
45
-
46
- style.render(rendered_content)
47
- end
48
-
49
- # Evaluates the content (block or constant) and returns its rendered string.
50
- def rendered_content
51
- value = block ? view.instance_exec(&block) : content
52
- value.respond_to?(:render) ? value.render.to_s : value.to_s
53
- end
4
+ module Layout
5
+ # Overlay is a compositing node used by ScreenLayout for floating elements (modals,
6
+ # dialogs, command palettes). It positions its content at *top*/*left* (each may be
7
+ # `:center` or an absolute cell offset) and optionally sizes it via *width*/*height*
8
+ # with an outer *style*.
9
+ class Overlay
10
+ # The vertical and horizontal offset (cell count or `:center`) of the overlay
11
+ # within the parent canvas.
12
+ attr_reader :top, :left
13
+
14
+ # *content* (or a *block*) provides the body. *top*/*left* default to :center.
15
+ # *width*/*height* fix the overlay's dimensions; when unset, the content's natural
16
+ # size is used. *style* wraps the rendered content in a UI::Style.
17
+ def initialize(content: nil, block: nil, view: nil, top: :center, left: :center, width: nil, height: nil, style: nil)
18
+ @content = content
19
+ @block = block
20
+ @view = view
21
+ @top = top
22
+ @left = left
23
+ @width = width
24
+ @height = height
25
+ @style = style
26
+ end
27
+
28
+ # Renders the overlay's content; when *width* or *height* is set, places the rendered
29
+ # content into a sized canvas before returning.
30
+ def render
31
+ return styled_content unless width || height
32
+
33
+ UI.place(styled_content, width: width || UI.block_width(styled_content.lines(chomp: true)), height: height || styled_content.lines.count)
34
+ end
35
+
36
+ private
37
+
38
+ # The raw content, body block, view, and sizing/style options.
39
+ attr_reader :content, :block, :view, :width, :height, :style
40
+
41
+ # Returns the rendered content wrapped in the configured *style* (when present).
42
+ def styled_content
43
+ return rendered_content unless style
44
+
45
+ style.render(rendered_content)
46
+ end
47
+
48
+ # Evaluates the content (block or constant) and returns its rendered string.
49
+ def rendered_content
50
+ value = block ? view.instance_exec(&block) : content
51
+ value.respond_to?(:render) ? value.render.to_s : value.to_s
54
52
  end
55
53
  end
56
54
  end
@@ -1,143 +1,154 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Layout
6
- # Pane is a leaf layout node: a single rectangle with optional border, padding, and
7
- # styling, containing a piece of content (a string, a View, or a block evaluated in the
8
- # view's context). Panes with a `name` and `focus: true` are registered as focusable
9
- # slots in the controller's focus ring.
10
- class Pane
11
- # The pane's focus slot name, fixed width, fixed height, and grow weight.
12
- attr_reader :name, :width, :height, :grow
13
-
14
- # *name* is the focus slot identifier (optional). *content* or *block* provides the body.
15
- # *width*/*height*/*grow* control sizing. *border* may be `true` (normal border) or a
16
- # border name symbol. *padding* may be 1, 2, or 4 values (CSS-style shorthand).
17
- # *style* sets the base style; *focused_style* overrides it when the pane is focused.
18
- # *focus: true* marks the pane as focusable. *scroll*/*clip*/*wrap* control how
19
- # overflow content is rendered (via the embedded Viewport).
20
- def initialize(name: nil, content: nil, block: nil, view: nil, width: nil, height: nil, grow: nil, border: nil, padding: nil, style: nil, focused_style: nil, focus: false, scroll: false, clip: true, wrap: false)
21
- @name = name
22
- @content = content
23
- @block = block
24
- @view = view
25
- @width = width
26
- @height = height
27
- @grow = grow
28
- @border = border
29
- @padding = padding
30
- @style = style
31
- @focused_style = focused_style
32
- @focus = focus
33
- @scroll = scroll
34
- @clip = clip
35
- @wrap = wrap
36
- end
4
+ module Layout
5
+ # Pane is a leaf layout node: a single rectangle with optional border, padding, and
6
+ # styling, containing a piece of content (a string, a View, or a block evaluated in the
7
+ # view's context). Panes with a `name` and `focus: true` are registered as focusable
8
+ # slots in the controller's focus ring.
9
+ class Pane
10
+ # The pane's focus slot name, fixed width, fixed height, and grow weight.
11
+ attr_reader :name, :width, :height, :grow
12
+
13
+ # *name* is the focus slot identifier (optional). *content* or *block* provides the body.
14
+ # *width*/*height*/*grow* control sizing. *border* may be `true` (normal border) or a
15
+ # border name symbol. *padding* may be 1, 2, or 4 values (CSS-style shorthand).
16
+ # *style* sets the base style; *focused_style* overrides it when the pane is focused.
17
+ # *focus: true* marks the pane as focusable. *scroll*/*clip*/*wrap* control how
18
+ # overflow content is rendered (via the embedded Viewport).
19
+ def initialize(name: nil, content: nil, block: nil, view: nil, width: nil, height: nil, grow: nil, border: nil, padding: nil, style: nil, focused_style: nil, focus: false, scroll: false, clip: true, wrap: false)
20
+ @name = name
21
+ @content = content
22
+ @block = block
23
+ @view = view
24
+ @width = width
25
+ @height = height
26
+ @grow = grow
27
+ @border = border
28
+ @padding = padding
29
+ @style = style
30
+ @focused_style = focused_style
31
+ @focus = focus
32
+ @scroll = scroll
33
+ @clip = clip
34
+ @wrap = wrap
35
+ end
37
36
 
38
- # Raises ArgumentError — panes are leaves and cannot contain layout children.
39
- def add_child(_node)
40
- raise ArgumentError, "pane cannot contain layout children"
41
- end
37
+ # Raises ArgumentError — panes are leaves and cannot contain layout children.
38
+ def add_child(_node)
39
+ raise ArgumentError, "pane cannot contain layout children"
40
+ end
42
41
 
43
- # Returns [name] when the pane is marked focusable and has a name, otherwise [].
44
- def focusable_names
45
- (focus && name) ? [name] : []
46
- end
42
+ # Returns [name] when the pane is marked focusable and has a name, otherwise [].
43
+ def focusable_names
44
+ (focus && name) ? [name] : []
45
+ end
47
46
 
48
- # Renders the pane into *rect*, applying the configured style, border, and padding
49
- # around the evaluated content.
50
- def render(rect)
51
- outer_style(rect).render(rendered_content(rect))
52
- end
47
+ # Returns the mouse target represented by this pane, if it has a name.
48
+ def mouse_targets(rect)
49
+ return [] unless name
53
50
 
54
- private
51
+ [{name: name, rect: rect, inner_rect: inner_rect(rect)}]
52
+ end
55
53
 
56
- # The raw content, the body block, the view used for instance_exec, and styling options.
57
- attr_reader :content, :block, :view, :border, :padding, :style, :focused_style, :focus, :scroll, :clip, :wrap
54
+ # Renders the pane into *rect*, applying the configured style, border, and padding
55
+ # around the evaluated content.
56
+ def render(rect)
57
+ outer_style(rect).render(rendered_content(rect))
58
+ end
58
59
 
59
- # Returns the content string for *rect*, optionally clipped/scrolled by an embedded Viewport.
60
- def rendered_content(rect)
61
- value = evaluate_content
62
- return value unless clip || scroll
60
+ private
63
61
 
64
- Components::Viewport.new(content: value, width: inner_rect(rect).width, height: inner_rect(rect).height, wrap: wrap).render
65
- end
62
+ # The raw content, the body block, the view used for instance_exec, and styling options.
63
+ attr_reader :content, :block, :view, :border, :padding, :style, :focused_style, :focus, :scroll, :clip, :wrap
66
64
 
67
- # Evaluates the configured content (block or constant) and renders it to a string.
68
- def evaluate_content
69
- value = block ? view.instance_exec(&block) : content
70
- value.respond_to?(:render) ? value.render.to_s : value.to_s
71
- end
65
+ # Returns the content string for *rect*, optionally clipped/scrolled by an embedded Viewport.
66
+ def rendered_content(rect)
67
+ content_rect = inner_rect(rect)
68
+ value = evaluate_content(content_rect)
69
+ return value unless clip || scroll
72
70
 
73
- # Builds the outer style object with optional border and padding, sized to the
74
- # inner rect of the pane.
75
- def outer_style(rect)
76
- styled = current_style
77
- styled = styled.border(border_style) if border
78
- styled = styled.padding(*padding_values) if padding
79
- styled.width(inner_rect(rect).width).height(inner_rect(rect).height)
71
+ Components::Viewport.new(content: value, width: content_rect.width, height: content_rect.height, wrap: wrap).render
72
+ end
73
+
74
+ # Evaluates the configured content (block or constant) and renders it to a string.
75
+ # Pane blocks may accept the inner content Rect when they need exact available dimensions.
76
+ def evaluate_content(content_rect)
77
+ value = if block
78
+ block.arity.zero? ? view.instance_exec(&block) : view.instance_exec(content_rect, &block)
79
+ else
80
+ content
80
81
  end
82
+ value.respond_to?(:render) ? value.render.to_s : value.to_s
83
+ end
81
84
 
82
- # Returns the active style: the focused variant when the pane is focused, otherwise
83
- # the configured style or a default UI::Style.
84
- def current_style
85
- return focused_pane_style if focused?
85
+ # Builds the outer style object with optional border and padding, sized to the
86
+ # inner rect of the pane.
87
+ def outer_style(rect)
88
+ styled = current_style
89
+ styled = styled.border(border_style) if border
90
+ styled = styled.padding(*padding_values) if padding
91
+ styled.width(inner_rect(rect).width).height(inner_rect(rect).height)
92
+ end
86
93
 
87
- style || UI.style
88
- end
94
+ # Returns the active style: the focused variant when the pane is focused, otherwise
95
+ # the configured style or a default UI::Style.
96
+ def current_style
97
+ return focused_pane_style if focused?
89
98
 
90
- # Returns the focused-pane style: the focused_style override, or the theme's title style.
91
- def focused_pane_style
92
- focused_style || view.__send__(:theme).title
93
- end
99
+ style || UI.style
100
+ end
94
101
 
95
- # True when the pane is configured for focus and the view reports it as currently focused.
96
- def focused?
97
- focus && name && view.focused?(name)
98
- end
102
+ # Returns the focused-pane style: the focused_style override, or the theme's title style.
103
+ def focused_pane_style
104
+ focused_style || view.__send__(:theme).title
105
+ end
99
106
 
100
- # Returns the inner Rect after border and padding insets are applied.
101
- def inner_rect(rect)
102
- rect.inset(
103
- top: border_top + padding_top,
104
- right: border_right + padding_right,
105
- bottom: border_bottom + padding_bottom,
106
- left: border_left + padding_left
107
- )
108
- end
107
+ # True when the pane is configured for focus and the view reports it as currently focused.
108
+ def focused?
109
+ focus && name && view.focused?(name)
110
+ end
109
111
 
110
- # Resolves the border style symbol: :normal when border is `true`, otherwise the configured value.
111
- def border_style
112
- (border == true) ? :normal : border
113
- end
112
+ # Returns the inner Rect after border and padding insets are applied.
113
+ def inner_rect(rect)
114
+ rect.inset(
115
+ top: border_top + padding_top,
116
+ right: border_right + padding_right,
117
+ bottom: border_bottom + padding_bottom,
118
+ left: border_left + padding_left
119
+ )
120
+ end
114
121
 
115
- # Border thickness on each side (1 when a border is configured, 0 otherwise).
116
- def border_top = border ? 1 : 0
117
- def border_right = border ? 1 : 0
118
- def border_bottom = border ? 1 : 0
119
- def border_left = border ? 1 : 0
122
+ # Resolves the border style symbol: :normal when border is `true`, otherwise the configured value.
123
+ def border_style
124
+ (border == true) ? :normal : border
125
+ end
120
126
 
121
- # The padding values normalized to [top, right, bottom, left] form.
122
- def padding_values
123
- @padding_values ||= expand_padding(Array(padding))
124
- end
127
+ # Border thickness on each side (1 when a border is configured, 0 otherwise).
128
+ def border_top = border ? 1 : 0
129
+ def border_right = border ? 1 : 0
130
+ def border_bottom = border ? 1 : 0
131
+ def border_left = border ? 1 : 0
132
+
133
+ # The padding values normalized to [top, right, bottom, left] form.
134
+ def padding_values
135
+ @padding_values ||= expand_padding(Array(padding))
136
+ end
125
137
 
126
- # Per-side padding values (0 when no padding is configured).
127
- def padding_top = padding ? padding_values[0] : 0
128
- def padding_right = padding ? padding_values[1] : 0
129
- def padding_bottom = padding ? padding_values[2] : 0
130
- def padding_left = padding ? padding_values[3] : 0
131
-
132
- # Normalizes 1/2/4 padding arguments to [top, right, bottom, left].
133
- def expand_padding(values)
134
- case values.length
135
- when 1 then [values[0], values[0], values[0], values[0]]
136
- when 2 then [values[0], values[1], values[0], values[1]]
137
- when 4 then values
138
- else
139
- raise ArgumentError, "padding expects 1, 2, or 4 values"
140
- end
138
+ # Per-side padding values (0 when no padding is configured).
139
+ def padding_top = padding ? padding_values[0] : 0
140
+ def padding_right = padding ? padding_values[1] : 0
141
+ def padding_bottom = padding ? padding_values[2] : 0
142
+ def padding_left = padding ? padding_values[3] : 0
143
+
144
+ # Normalizes 1/2/4 padding arguments to [top, right, bottom, left].
145
+ def expand_padding(values)
146
+ case values.length
147
+ when 1 then [values[0], values[0], values[0], values[0]]
148
+ when 2 then [values[0], values[1], values[0], values[1]]
149
+ when 4 then values
150
+ else
151
+ raise ArgumentError, "padding expects 1, 2, or 4 values"
141
152
  end
142
153
  end
143
154
  end
@@ -1,22 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Layout
6
- # Rect is an immutable rectangle with a top-left position (x, y) and dimensions
7
- # (width, height). Layout operations produce new Rect instances rather than mutating
8
- # existing ones.
9
- Rect = Data.define(:x, :y, :width, :height) do
10
- # Returns a new Rect inset by *top*/*right*/*bottom*/*left* cells. The result is
11
- # clamped to a minimum width/height of 0.
12
- def inset(top: 0, right: 0, bottom: 0, left: 0)
13
- Rect.new(
14
- x: x + left,
15
- y: y + top,
16
- width: [width - left - right, 0].max,
17
- height: [height - top - bottom, 0].max
18
- )
19
- end
4
+ module Layout
5
+ # Rect is an immutable rectangle with a top-left position (x, y) and dimensions
6
+ # (width, height). Layout operations produce new Rect instances rather than mutating
7
+ # existing ones.
8
+ Rect = Data.define(:x, :y, :width, :height) do
9
+ # Returns true when the zero-based cell coordinate falls within this rectangle.
10
+ def cover?(point_x, point_y)
11
+ point_x >= x && point_x < x + width && point_y >= y && point_y < y + height
12
+ end
13
+
14
+ # Returns a new Rect inset by *top*/*right*/*bottom*/*left* cells. The result is
15
+ # clamped to a minimum width/height of 0.
16
+ def inset(top: 0, right: 0, bottom: 0, left: 0)
17
+ Rect.new(
18
+ x: x + left,
19
+ y: y + top,
20
+ width: [width - left - right, 0].max,
21
+ height: [height - top - bottom, 0].max
22
+ )
20
23
  end
21
24
  end
22
25
  end
@@ -1,59 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Charming
4
- module Presentation
5
- module Layout
6
- # ScreenLayout is the root of a layout tree. It owns a single child (typically a Split
7
- # or Pane) rendered into the full terminal screen, and an ordered list of Overlays
8
- # composited on top of the rendered body.
9
- class ScreenLayout
10
- # *screen* is the Charming::Screen whose dimensions define the layout area.
11
- # *background* (optional) is a UI::Style applied to the empty canvas behind the body.
12
- def initialize(screen:, background: nil)
13
- @screen = screen
14
- @background = background
15
- @child = nil
16
- @overlays = []
17
- end
4
+ module Layout
5
+ # ScreenLayout is the root of a layout tree. It owns a single child (typically a Split
6
+ # or Pane) rendered into the full terminal screen, and an ordered list of Overlays
7
+ # composited on top of the rendered body.
8
+ class ScreenLayout
9
+ # *screen* is the Charming::Screen whose dimensions define the layout area.
10
+ # *background* (optional) is a UI::Style applied to the empty canvas behind the body.
11
+ def initialize(screen:, background: nil)
12
+ @screen = screen
13
+ @background = background
14
+ @child = nil
15
+ @overlays = []
16
+ end
18
17
 
19
- # Sets the single root child. Raises ArgumentError when a child is already present.
20
- def add_child(node)
21
- raise ArgumentError, "screen_layout accepts one root layout node" if child
18
+ # Sets the single root child. Raises ArgumentError when a child is already present.
19
+ def add_child(node)
20
+ raise ArgumentError, "screen_layout accepts one root layout node" if child
22
21
 
23
- @child = node
24
- end
22
+ @child = node
23
+ end
25
24
 
26
- # Appends an overlay to be composited on top of the body, in registration order.
27
- def add_overlay(node)
28
- overlays << node
29
- end
25
+ # Appends an overlay to be composited on top of the body, in registration order.
26
+ def add_overlay(node)
27
+ overlays << node
28
+ end
30
29
 
31
- # Returns the focusable names from the child, or [] when no child has been added.
32
- def focusable_names
33
- child ? child.focusable_names : []
34
- end
30
+ # Returns the focusable names from the child, or [] when no child has been added.
31
+ def focusable_names
32
+ child ? child.focusable_names : []
33
+ end
34
+
35
+ # Returns all named pane mouse targets for the full screen layout.
36
+ def mouse_targets
37
+ return [] unless child
38
+
39
+ child.mouse_targets(Rect.new(x: 0, y: 0, width: screen.width, height: screen.height))
40
+ end
35
41
 
36
- # Renders the child into the full-screen rect, then overlays each registered overlay
37
- # on top in order.
38
- def render
39
- body = UI.place(render_child, width: screen.width, height: screen.height, background: background)
42
+ # Renders the child into the full-screen rect, then overlays each registered overlay
43
+ # on top in order.
44
+ def render
45
+ body = UI.place(render_child, width: screen.width, height: screen.height, background: background)
40
46
 
41
- overlays.reduce(body) do |current, overlay|
42
- UI.overlay(current, overlay.render, top: overlay.top, left: overlay.left)
43
- end
47
+ overlays.reduce(body) do |current, overlay|
48
+ UI.overlay(current, overlay.render, top: overlay.top, left: overlay.left)
44
49
  end
50
+ end
45
51
 
46
- private
52
+ private
47
53
 
48
- # The screen, background style, the single child, and the list of overlays.
49
- attr_reader :screen, :background, :child, :overlays
54
+ # The screen, background style, the single child, and the list of overlays.
55
+ attr_reader :screen, :background, :child, :overlays
50
56
 
51
- # Renders the child into a full-screen Rect, or returns an empty string when no child.
52
- def render_child
53
- return "" unless child
57
+ # Renders the child into a full-screen Rect, or returns an empty string when no child.
58
+ def render_child
59
+ return "" unless child
54
60
 
55
- child.render(Rect.new(x: 0, y: 0, width: screen.width, height: screen.height))
56
- end
61
+ child.render(Rect.new(x: 0, y: 0, width: screen.width, height: screen.height))
57
62
  end
58
63
  end
59
64
  end