teek-ui 0.1.0

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 (127) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +452 -0
  4. data/lib/teek/ui/canvas_item.rb +270 -0
  5. data/lib/teek/ui/component_handle.rb +32 -0
  6. data/lib/teek/ui/document.rb +185 -0
  7. data/lib/teek/ui/errors.rb +41 -0
  8. data/lib/teek/ui/event_binding.rb +22 -0
  9. data/lib/teek/ui/event_bus.rb +53 -0
  10. data/lib/teek/ui/grid_validator.rb +89 -0
  11. data/lib/teek/ui/handle.rb +585 -0
  12. data/lib/teek/ui/image.rb +58 -0
  13. data/lib/teek/ui/keysyms.rb +72 -0
  14. data/lib/teek/ui/menu_builder.rb +130 -0
  15. data/lib/teek/ui/menu_entry_addressing.rb +62 -0
  16. data/lib/teek/ui/modal_stack.rb +99 -0
  17. data/lib/teek/ui/mouse_events.rb +31 -0
  18. data/lib/teek/ui/node.rb +141 -0
  19. data/lib/teek/ui/option_dump_parsing.rb +31 -0
  20. data/lib/teek/ui/overlay_anchors.rb +29 -0
  21. data/lib/teek/ui/overlay_validator.rb +33 -0
  22. data/lib/teek/ui/pane_validator.rb +30 -0
  23. data/lib/teek/ui/realized_node.rb +25 -0
  24. data/lib/teek/ui/realizer.rb +577 -0
  25. data/lib/teek/ui/scope.rb +40 -0
  26. data/lib/teek/ui/screens.rb +141 -0
  27. data/lib/teek/ui/session.rb +417 -0
  28. data/lib/teek/ui/tab_validator.rb +30 -0
  29. data/lib/teek/ui/text_content.rb +321 -0
  30. data/lib/teek/ui/tree_inspector.rb +97 -0
  31. data/lib/teek/ui/validator.rb +131 -0
  32. data/lib/teek/ui/var.rb +98 -0
  33. data/lib/teek/ui/version.rb +7 -0
  34. data/lib/teek/ui/widget_addressing.rb +47 -0
  35. data/lib/teek/ui/widget_dsl.rb +512 -0
  36. data/lib/teek/ui/widget_type.rb +242 -0
  37. data/lib/teek/ui/widget_types/button.rb +7 -0
  38. data/lib/teek/ui/widget_types/canvas.rb +15 -0
  39. data/lib/teek/ui/widget_types/checkbox.rb +7 -0
  40. data/lib/teek/ui/widget_types/column.rb +14 -0
  41. data/lib/teek/ui/widget_types/context_menu.rb +15 -0
  42. data/lib/teek/ui/widget_types/divider.rb +10 -0
  43. data/lib/teek/ui/widget_types/dropdown.rb +7 -0
  44. data/lib/teek/ui/widget_types/grid.rb +12 -0
  45. data/lib/teek/ui/widget_types/group.rb +7 -0
  46. data/lib/teek/ui/widget_types/label.rb +7 -0
  47. data/lib/teek/ui/widget_types/list.rb +7 -0
  48. data/lib/teek/ui/widget_types/menu_bar.rb +26 -0
  49. data/lib/teek/ui/widget_types/menu_checkbox.rb +14 -0
  50. data/lib/teek/ui/widget_types/menu_item.rb +20 -0
  51. data/lib/teek/ui/widget_types/menu_radio.rb +14 -0
  52. data/lib/teek/ui/widget_types/number_box.rb +7 -0
  53. data/lib/teek/ui/widget_types/pane.rb +37 -0
  54. data/lib/teek/ui/widget_types/panel.rb +7 -0
  55. data/lib/teek/ui/widget_types/progress.rb +7 -0
  56. data/lib/teek/ui/widget_types/radio.rb +7 -0
  57. data/lib/teek/ui/widget_types/row.rb +14 -0
  58. data/lib/teek/ui/widget_types/scrollable.rb +17 -0
  59. data/lib/teek/ui/widget_types/slider.rb +7 -0
  60. data/lib/teek/ui/widget_types/spacer.rb +14 -0
  61. data/lib/teek/ui/widget_types/split.rb +17 -0
  62. data/lib/teek/ui/widget_types/tab.rb +35 -0
  63. data/lib/teek/ui/widget_types/table.rb +9 -0
  64. data/lib/teek/ui/widget_types/tabs.rb +10 -0
  65. data/lib/teek/ui/widget_types/text_area.rb +7 -0
  66. data/lib/teek/ui/widget_types/text_box.rb +7 -0
  67. data/lib/teek/ui/widget_types/tree.rb +9 -0
  68. data/lib/teek/ui/widget_types/window.rb +50 -0
  69. data/lib/teek/ui/widget_types.rb +121 -0
  70. data/lib/teek/ui/widget_validators.rb +62 -0
  71. data/lib/teek/ui.rb +54 -0
  72. data/teek-ui.gemspec +24 -0
  73. data/test/support/fake_app.rb +57 -0
  74. data/test/test_busy.rb +82 -0
  75. data/test/test_canvas_items.rb +551 -0
  76. data/test/test_clipboard.rb +87 -0
  77. data/test/test_close_handling.rb +118 -0
  78. data/test/test_component.rb +151 -0
  79. data/test/test_component_realtk.rb +120 -0
  80. data/test/test_debug_info.rb +158 -0
  81. data/test/test_document.rb +317 -0
  82. data/test/test_event_bus.rb +163 -0
  83. data/test/test_events.rb +164 -0
  84. data/test/test_fake_app_contract.rb +62 -0
  85. data/test/test_grid.rb +97 -0
  86. data/test/test_handle.rb +386 -0
  87. data/test/test_handle_destroy_realtk.rb +344 -0
  88. data/test/test_helper.rb +20 -0
  89. data/test/test_image.rb +24 -0
  90. data/test/test_image_realtk.rb +146 -0
  91. data/test/test_incremental_realize.rb +203 -0
  92. data/test/test_keysyms.rb +76 -0
  93. data/test/test_layout.rb +185 -0
  94. data/test/test_lazy_realize.rb +128 -0
  95. data/test/test_managed_window.rb +304 -0
  96. data/test/test_menu_dsl.rb +230 -0
  97. data/test/test_menu_entry_addressing.rb +73 -0
  98. data/test/test_menu_realize.rb +296 -0
  99. data/test/test_modal_handle.rb +147 -0
  100. data/test/test_modal_stack.rb +219 -0
  101. data/test/test_modal_stack_realtk.rb +131 -0
  102. data/test/test_native_scrollable.rb +250 -0
  103. data/test/test_node.rb +193 -0
  104. data/test/test_overlay.rb +139 -0
  105. data/test/test_raw.rb +58 -0
  106. data/test/test_reactive_vars.rb +118 -0
  107. data/test/test_realizer.rb +264 -0
  108. data/test/test_scope.rb +38 -0
  109. data/test/test_screens.rb +260 -0
  110. data/test/test_screens_realtk.rb +314 -0
  111. data/test/test_scrollable.rb +185 -0
  112. data/test/test_scrollable_wheel.rb +172 -0
  113. data/test/test_scrollbar_auto_hide.rb +127 -0
  114. data/test/test_split.rb +145 -0
  115. data/test/test_tabs.rb +201 -0
  116. data/test/test_text_content.rb +664 -0
  117. data/test/test_toast.rb +126 -0
  118. data/test/test_tree_inspector.rb +183 -0
  119. data/test/test_ui.rb +428 -0
  120. data/test/test_validator.rb +209 -0
  121. data/test/test_var.rb +32 -0
  122. data/test/test_widget_dsl.rb +621 -0
  123. data/test/test_widget_introspection.rb +126 -0
  124. data/test/test_widget_types.rb +429 -0
  125. data/test/test_widget_types_realtk.rb +82 -0
  126. data/test/test_widget_validators.rb +82 -0
  127. metadata +207 -0
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'widget_addressing'
4
+
5
+ module Teek
6
+ module UI
7
+ # @api private
8
+ #
9
+ # A single widget/node type's own metadata: what it draws, how it's
10
+ # built and arranged, what it validates - self-contained enough that
11
+ # {WidgetDSL} (the builder), {Realizer}, and {Validator} can each treat
12
+ # a registered type as the sole source of truth for it, dispatched by
13
+ # node type via {WidgetTypes}.
14
+ #
15
+ # Leaf defaults cover the common case, so a real widget is a ~5-line
16
+ # descriptor: +WidgetType.new(type: :divider, tk_command:
17
+ # 'ttk::separator')+ is a complete, working leaf widget. A container,
18
+ # or a widget needing bespoke DSL methods/realize setup, overrides
19
+ # +dsl:+/+post_create:+.
20
+ class WidgetType
21
+ attr_reader :type, :tk_command, :bind_option, :validator, :flow, :addressing
22
+
23
+ # @param type [Symbol] the node type this describes, e.g. +:divider+
24
+ # @param tk_command [String] the Tk widget-creation command, e.g.
25
+ # +'ttk::separator'+ - documentary only for a type that also sets
26
+ # +custom_create:+, since that hook bypasses the generic
27
+ # widget-creation call this would otherwise drive
28
+ # @param leaf [Boolean] true for a childless widget (the default);
29
+ # false for a container that holds a DSL subtree
30
+ # @param natively_scrollable [Boolean] whether this widget already
31
+ # speaks Tk's -yscrollcommand/-xscrollcommand protocol - see
32
+ # {Realizer#auto_scrollable?}, which consults this for a registered type
33
+ # @param scroll_default [Symbol] which {Teek::UI} global default
34
+ # reader this type's own auto-scrollable wrapping falls back to
35
+ # when neither the widget's own +scroll:+ nor the session's
36
+ # app-wide override says otherwise - +:auto_scroll+ (the default)
37
+ # for most natively-scrollable types, +:auto_scroll_canvas+ for
38
+ # canvas specifically. Only meaningful alongside
39
+ # +natively_scrollable: true+ - see {Realizer#resolve_scroll}.
40
+ # @param arranged [Boolean] whether a geometry manager (pack/grid)
41
+ # should place this node inside its parent - true (the default) for
42
+ # almost everything; false for a type placed some other way
43
+ # entirely, e.g. a toplevel window (placed by the window manager,
44
+ # not its nominal parent) or a tab/pane (placed by its own
45
+ # container's `add` command) - see {Realizer#unarranged?}, which
46
+ # consults this for a registered type
47
+ # @param bind_option [Symbol, nil] the Tk option `bind:` plugs a
48
+ # {Var} into for this widget (+:textvariable+/+:variable+/...) -
49
+ # +nil+ (the default) means this widget doesn't support `bind:`
50
+ # @param flow [Hash, nil] flow-packing config for a `column`/`row`-
51
+ # style container (side/main_pad/cross_pad/main_fill/cross_fill/
52
+ # anchor) - +nil+ (the default) means this type isn't flow-arranged.
53
+ # A convenience over +arrange:+ for exactly this shape: giving
54
+ # +flow:+ computes an +arrange:+ that delegates to
55
+ # {Realizer#arrange_flow} with this data, so most flow containers
56
+ # never need to touch +arrange:+ directly.
57
+ # @param arrange [#call, nil] +->(realizer, node, children) { ... }+,
58
+ # replaces the realizer's generic "pack every child plainly"
59
+ # default arrangement for this type entirely - e.g. real Tk grid
60
+ # placement (row/col/span/stretch) for a `:grid`-shaped type. Since
61
+ # the logic usually needs realizer-private helpers (gap/align
62
+ # computation, error formatting, ...), the callable is typically a
63
+ # thin adapter delegating back via +realizer.send(:some_private_method, ...)+
64
+ # rather than reimplementing arrangement from scratch - see
65
+ # {Realizer#arrange_grid} for an example. +nil+ (the default,
66
+ # unless +flow:+ computed one) means the generic pack default applies.
67
+ # @param custom_children [#call, nil] +->(realizer, node, path) { ... }+,
68
+ # replaces the realizer's generic "create every child normally"
69
+ # step for this type, once its OWN widget has already been created
70
+ # the normal way - e.g. `:scrollable`'s children live inside an
71
+ # embedded canvas+viewport it builds itself, not directly under its
72
+ # own path. +nil+ (the default) means children realize normally.
73
+ # @param custom_create [#call, nil] +->(realizer, node, parent_path) { ... }+,
74
+ # replaces the realizer's ENTIRE per-node create/link handling for
75
+ # this type - no generic widget-creation call, no {#post_create},
76
+ # no {#arrange}/`custom_children`, no normal `link` processing
77
+ # (events/close-handler/child recursion) either. For a type whose
78
+ # realize model doesn't share ANY of that machinery at all - e.g.
79
+ # `:menu_bar`/`:context_menu`, realized via
80
+ # {Realizer#create_menu_tree}'s own bespoke traversal, using
81
+ # `Teek::App#menu` rather than a generic widget-creation command.
82
+ # +nil+ (the default) means this type goes through the normal
83
+ # create/link two-pass path.
84
+ # @param validator [#call, nil] a +(node, parent, document, errors)+
85
+ # callable checking this type's own contract - a REFERENCE to an
86
+ # already-written validator (e.g. an existing WidgetValidators-style
87
+ # module), not duplicated logic. Composed into {WidgetValidators}
88
+ # automatically at {WidgetTypes.register} time, so {Validator}
89
+ # needs no separate awareness of descriptors at all.
90
+ # @param dsl [Proc, nil] +->(mod) { ... }+, called once at
91
+ # {WidgetTypes.register} time (and replayed for any subscriber that
92
+ # joins later - see {WidgetTypes.on_register}) to define this
93
+ # type's `ui.<type>` method(s) on the builder module. Defaults to
94
+ # the leaf/container-appropriate +append_leaf+/+append_container+
95
+ # call - pass +->(mod) { }+ (a genuine no-op) for a type reachable
96
+ # only via a bespoke, hand-written top-level method with a
97
+ # different signature (e.g. `#tab`/`#pane`/`#split`, or
98
+ # `#menu_bar`/`#context_menu`), so the registry doesn't shadow it
99
+ # with a same-named generic method.
100
+ # @param post_create [#call, nil] +->(app, node, path, parent_path) { ... }+,
101
+ # run right after the generic widget-creation command at realize -
102
+ # see {WindowRealize}/{TabRealize}/{PaneRealize} for real examples.
103
+ # Defaults to a no-op. Not called at all for a type that sets
104
+ # +custom_create:+, since that hook bypasses this entire step.
105
+ # @param addressing [#new, nil] a class - +->(node) { ... }+-shaped
106
+ # via +.new+ - knowing how to read/write THIS type's live state
107
+ # (+#virtual_path+, +#configure+). {Handle} resolves this per node
108
+ # from the registry, with no type-specific knowledge of its own -
109
+ # a plugin/custom widget type gets correct addressing purely by
110
+ # registering its own +addressing:+ here. Defaults to
111
+ # {WidgetAddressing} (an ordinary Tk widget with its own path) -
112
+ # see {MenuEntryAddressing} for a type with none of its own.
113
+ def initialize(type:, tk_command:, leaf: true, natively_scrollable: false, arranged: true,
114
+ scroll_default: :auto_scroll, bind_option: nil, flow: nil, arrange: nil,
115
+ custom_children: nil, custom_create: nil, validator: nil, dsl: nil, post_create: nil,
116
+ addressing: WidgetAddressing)
117
+ @type = type.to_sym
118
+ @tk_command = tk_command
119
+ @leaf = leaf
120
+ @natively_scrollable = natively_scrollable
121
+ @arranged = arranged
122
+ @scroll_default = scroll_default
123
+ @bind_option = bind_option
124
+ @flow = flow
125
+ @arrange = arrange || (flow && ->(realizer, node, children) { realizer.send(:arrange_flow, node, children, flow) })
126
+ @custom_children = custom_children
127
+ @custom_create = custom_create
128
+ @validator = validator
129
+ @dsl = dsl || default_dsl
130
+ @addressing = addressing
131
+ @post_create = post_create
132
+ end
133
+
134
+ # @return [Boolean]
135
+ def leaf?
136
+ @leaf
137
+ end
138
+
139
+ # @return [Boolean]
140
+ def container?
141
+ !@leaf
142
+ end
143
+
144
+ # @return [Boolean]
145
+ def natively_scrollable?
146
+ @natively_scrollable
147
+ end
148
+
149
+ # @return [Boolean] the current value of this type's own
150
+ # {Teek::UI} global scroll-default reader (+scroll_default:+)
151
+ def global_scroll_default
152
+ Teek::UI.public_send(@scroll_default)
153
+ end
154
+
155
+ # @return [Boolean]
156
+ def arranged?
157
+ @arranged
158
+ end
159
+
160
+ # @return [Boolean] whether this type replaces the generic arrangement
161
+ def arrange?
162
+ !@arrange.nil?
163
+ end
164
+
165
+ # Runs this type's custom arrangement strategy.
166
+ # @param realizer [Realizer]
167
+ # @param node [Node]
168
+ # @param children [Array<Node>] this node's arrangeable children
169
+ # @return [void]
170
+ def arrange(realizer, node, children)
171
+ @arrange.call(realizer, node, children)
172
+ end
173
+
174
+ # @return [Boolean] whether this type replaces generic child creation
175
+ def custom_children?
176
+ !@custom_children.nil?
177
+ end
178
+
179
+ # Runs this type's custom child-creation strategy, once its own
180
+ # widget already exists at +path+.
181
+ # @param realizer [Realizer]
182
+ # @param node [Node]
183
+ # @param path [String]
184
+ # @return [void]
185
+ def custom_children(realizer, node, path)
186
+ @custom_children.call(realizer, node, path)
187
+ end
188
+
189
+ # @return [Boolean] whether this type replaces the entire create/link handling
190
+ def custom_create?
191
+ !@custom_create.nil?
192
+ end
193
+
194
+ # Runs this type's entire create/link replacement.
195
+ # @param realizer [Realizer]
196
+ # @param node [Node]
197
+ # @param parent_path [String]
198
+ # @return [void]
199
+ def custom_create(realizer, node, parent_path)
200
+ @custom_create.call(realizer, node, parent_path)
201
+ end
202
+
203
+ # Defines this type's `ui.<type>` method(s) on +mod+ (the {WidgetDSL}
204
+ # module) - see {WidgetTypes.on_register}, which drives this.
205
+ # @param mod [Module]
206
+ # @return [void]
207
+ def define_dsl_method!(mod)
208
+ @dsl.call(mod)
209
+ end
210
+
211
+ # Runs this type's post-creation realize setup, if any - a no-op
212
+ # unless +post_create:+ was given.
213
+ # @param app [Teek::App]
214
+ # @param node [Node]
215
+ # @param path [String]
216
+ # @param parent_path [String]
217
+ # @return [void]
218
+ def post_create(app, node, path, parent_path)
219
+ @post_create&.call(app, node, path, parent_path)
220
+ end
221
+
222
+ private
223
+
224
+ def default_dsl
225
+ widget_type = self
226
+ if leaf?
227
+ ->(mod) {
228
+ mod.send(:define_method, widget_type.type) { |name = nil, **opts|
229
+ append_leaf(widget_type.type, name, opts)
230
+ }
231
+ }
232
+ else
233
+ ->(mod) {
234
+ mod.send(:define_method, widget_type.type) { |name = nil, **opts, &block|
235
+ append_container(widget_type.type, name, opts, &block)
236
+ }
237
+ }
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :button, tk_command: 'ttk::button')
7
+ )
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # A canvas is as often fixed drawing as scrollable content, unlike the
6
+ # other natively-scrollable types - scroll_default: points its own
7
+ # auto-scrollable wrapping at Teek::UI.auto_scroll_canvas (false by
8
+ # default) instead of the shared Teek::UI.auto_scroll (true by default)
9
+ # every other natively-scrollable type falls back to.
10
+ Teek::UI::WidgetTypes.register(
11
+ Teek::UI::WidgetType.new(
12
+ type: :canvas, tk_command: 'canvas', leaf: false,
13
+ natively_scrollable: true, scroll_default: :auto_scroll_canvas
14
+ )
15
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :checkbox, tk_command: 'ttk::checkbutton', bind_option: :variable)
7
+ )
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(
7
+ type: :column, tk_command: 'ttk::frame', leaf: false,
8
+ flow: {
9
+ side: 'top', main_pad: :pady, cross_pad: :padx,
10
+ main_fill: 'y', cross_fill: 'x',
11
+ anchor: { start: 'w', center: 'center', end: 'e' },
12
+ }
13
+ )
14
+ )
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # See widget_types/menu_bar.rb for the shared reasoning - context_menu is
6
+ # the other entry point into the same Realizer#create_menu_tree traversal,
7
+ # just never attached to a -menu option automatically (popped up via
8
+ # Handle#on_right_click instead).
9
+ Teek::UI::WidgetTypes.register(
10
+ Teek::UI::WidgetType.new(
11
+ type: :context_menu, tk_command: 'menu', leaf: false, arranged: false,
12
+ custom_create: ->(realizer, node, parent_path) { realizer.send(:create_menu_tree, node, parent_path) },
13
+ dsl: ->(mod) { }
14
+ )
15
+ )
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # A plain leaf with no bind option, no scrolling, and no realize setup
6
+ # beyond the generic widget-creation command - every field but
7
+ # type/tk_command is a leaf default.
8
+ Teek::UI::WidgetTypes.register(
9
+ Teek::UI::WidgetType.new(type: :divider, tk_command: 'ttk::separator')
10
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :dropdown, tk_command: 'ttk::combobox', bind_option: :textvariable)
7
+ )
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+ require_relative '../grid_validator'
5
+
6
+ Teek::UI::WidgetTypes.register(
7
+ Teek::UI::WidgetType.new(
8
+ type: :grid, tk_command: 'ttk::frame', leaf: false,
9
+ arrange: ->(realizer, node, children) { realizer.send(:arrange_grid, node, children) },
10
+ validator: Teek::UI::GridValidator.method(:call)
11
+ )
12
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :group, tk_command: 'ttk::labelframe', leaf: false)
7
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :label, tk_command: 'ttk::label', bind_option: :textvariable)
7
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :list, tk_command: 'listbox', natively_scrollable: true)
7
+ )
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # menu_bar/context_menu are the two entry points into a menu subtree -
6
+ # everything under them (nested :menu cascades, :menu_item/:menu_separator/
7
+ # :menu_checkbox/:menu_radio entries) is built in one shot by
8
+ # Realizer#create_menu_tree, in menu-add order, rather than through the
9
+ # generic per-node create/link passes every other node type uses - menu
10
+ # entries have no Tk path or geometry-managed arrangement of their own to
11
+ # visit separately, so custom_create: hands the whole thing off (see
12
+ # create_menu_tree's own doc comment for why it also skips #link entirely).
13
+ #
14
+ # tk_command: is documentary only here - create_menu_tree calls
15
+ # Teek::App#menu directly, never the generic tk_command_for path
16
+ # custom_create: bypasses. No auto-generated `ui.menu_bar` method either -
17
+ # it's only ever reachable via the hand-written WidgetDSL#menu_bar, which
18
+ # validates its parent (see WidgetDSL::MENU_BAR_HOSTS); dsl: is a genuine
19
+ # no-op so the registry doesn't shadow that with a same-named generic method.
20
+ Teek::UI::WidgetTypes.register(
21
+ Teek::UI::WidgetType.new(
22
+ type: :menu_bar, tk_command: 'menu', leaf: false, arranged: false,
23
+ custom_create: ->(realizer, node, parent_path) { realizer.send(:create_menu_tree, node, parent_path) },
24
+ dsl: ->(mod) { }
25
+ )
26
+ )
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+ require_relative '../menu_entry_addressing'
5
+
6
+ # See widget_types/menu_item.rb for the shared reasoning - :menu_checkbox
7
+ # is a menu entry kind, addressed the same way (MenuEntryAddressing),
8
+ # reachable only via the hand-written MenuBuilder#checkbox.
9
+ Teek::UI::WidgetTypes.register(
10
+ Teek::UI::WidgetType.new(
11
+ type: :menu_checkbox, tk_command: 'menu', addressing: Teek::UI::MenuEntryAddressing,
12
+ dsl: ->(mod) { }
13
+ )
14
+ )
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+ require_relative '../menu_entry_addressing'
5
+
6
+ # :menu_item never flows through the generic Realizer#create path -
7
+ # Realizer#create_menu_tree issues its own `menu add command` call for
8
+ # every :menu_item child directly, so leaf:/arranged:/etc are inert here.
9
+ # Registered so its addressing: (how a named item's Handle reads/writes
10
+ # its live -state/-label/...) is discoverable from the registry, the same
11
+ # way every other type's is - see menu_entry_addressing.rb. No
12
+ # auto-generated `ui.menu_item` method either - it's only ever reachable
13
+ # via the hand-written MenuBuilder#item; dsl: is a genuine no-op so the
14
+ # registry doesn't shadow that with a same-named generic method.
15
+ Teek::UI::WidgetTypes.register(
16
+ Teek::UI::WidgetType.new(
17
+ type: :menu_item, tk_command: 'menu', addressing: Teek::UI::MenuEntryAddressing,
18
+ dsl: ->(mod) { }
19
+ )
20
+ )
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+ require_relative '../menu_entry_addressing'
5
+
6
+ # See widget_types/menu_item.rb for the shared reasoning - :menu_radio is
7
+ # a menu entry kind, addressed the same way (MenuEntryAddressing),
8
+ # reachable only via the hand-written MenuBuilder#radio.
9
+ Teek::UI::WidgetTypes.register(
10
+ Teek::UI::WidgetType.new(
11
+ type: :menu_radio, tk_command: 'menu', addressing: Teek::UI::MenuEntryAddressing,
12
+ dsl: ->(mod) { }
13
+ )
14
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :number_box, tk_command: 'ttk::spinbox', bind_option: :textvariable)
7
+ )
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+ require_relative '../pane_validator'
5
+
6
+ module Teek
7
+ module UI
8
+ # @api private
9
+ #
10
+ # Adds a freshly created :pane's own frame to the enclosing
11
+ # panedwindow, with whatever `#pane` stashed as pane_weight: (if any).
12
+ # `ttk::panedwindow add` is the pane's whole placement - unlike every
13
+ # other container, a pane's frame is never pack/grid-managed on its
14
+ # own (see :pane's own +arranged: false+ below). Registered as :pane's
15
+ # own `post_create:`.
16
+ module PaneRealize
17
+ def self.post_create(app, node, path, parent_path)
18
+ weight = node.opts[:pane_weight]
19
+ opts = weight.nil? ? {} : { weight: weight }
20
+ app.command(parent_path, :add, path, **opts)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # :pane has no auto-generated `ui.pane` method - it's only ever reachable
27
+ # via the hand-written WidgetDSL#pane, which validates it's declared
28
+ # directly inside ui.split. dsl: is a genuine no-op so the registry
29
+ # doesn't shadow that with a same-named generic method.
30
+ Teek::UI::WidgetTypes.register(
31
+ Teek::UI::WidgetType.new(
32
+ type: :pane, tk_command: 'ttk::frame', leaf: false, arranged: false,
33
+ post_create: Teek::UI::PaneRealize.method(:post_create),
34
+ validator: Teek::UI::PaneValidator.method(:call),
35
+ dsl: ->(mod) { }
36
+ )
37
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :panel, tk_command: 'ttk::frame', leaf: false)
7
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :progress, tk_command: 'ttk::progressbar', bind_option: :variable)
7
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :radio, tk_command: 'ttk::radiobutton')
7
+ )
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(
7
+ type: :row, tk_command: 'ttk::frame', leaf: false,
8
+ flow: {
9
+ side: 'left', main_pad: :padx, cross_pad: :pady,
10
+ main_fill: 'x', cross_fill: 'y',
11
+ anchor: { start: 'n', center: 'center', end: 's' },
12
+ }
13
+ )
14
+ )
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # There's no Tk protocol to hook a scrollbar into arbitrary widgets (unlike
6
+ # a natively-scrollable widget's own wrapping - see list.rb/table.rb/etc),
7
+ # so :scrollable's children are created inside an embedded canvas+viewport
8
+ # it builds itself (Realizer#create_scrollable) rather than directly under
9
+ # its own path - custom_children: takes over from the generic
10
+ # "create every child normally" step once its own frame already exists.
11
+ Teek::UI::WidgetTypes.register(
12
+ Teek::UI::WidgetType.new(
13
+ type: :scrollable, tk_command: 'ttk::frame', leaf: false,
14
+ custom_children: ->(realizer, node, path) { realizer.send(:create_scrollable, node, path) },
15
+ arrange: ->(realizer, node, children) { realizer.send(:arrange_scrollable_frame, node, children) }
16
+ )
17
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ Teek::UI::WidgetTypes.register(
6
+ Teek::UI::WidgetType.new(type: :slider, tk_command: 'ttk::scale', bind_option: :variable)
7
+ )
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # A flexible gap - the named replacement for the "invisible spring row"
6
+ # trick (an empty row/column given all the leftover weight). A leaf with
7
+ # `grow: true` baked in and no arguments at all, so it needs its own `dsl:`
8
+ # rather than the generic leaf default (`name = nil, **opts`).
9
+ Teek::UI::WidgetTypes.register(
10
+ Teek::UI::WidgetType.new(
11
+ type: :spacer, tk_command: 'ttk::frame',
12
+ dsl: ->(mod) { mod.send(:define_method, :spacer) { append_leaf(:spacer, nil, grow: true) } }
13
+ )
14
+ )
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # :split has no auto-generated `ui.split` method - it's only ever reachable
6
+ # via the hand-written WidgetDSL#split, which validates orientation: and
7
+ # translates it to the real -orient option before creating the node.
8
+ # dsl: is a genuine no-op so the registry doesn't shadow that with a
9
+ # same-named generic method. `orient:` itself is a real ttk::panedwindow
10
+ # option, so it needs no reserved-option handling here - it passes through
11
+ # the generic widget-creation call untouched.
12
+ Teek::UI::WidgetTypes.register(
13
+ Teek::UI::WidgetType.new(
14
+ type: :split, tk_command: 'ttk::panedwindow', leaf: false,
15
+ dsl: ->(mod) { }
16
+ )
17
+ )
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+ require_relative '../tab_validator'
5
+
6
+ module Teek
7
+ module UI
8
+ # @api private
9
+ #
10
+ # Adds a freshly created :tab's own frame to the enclosing notebook as
11
+ # a page, labeled with whatever `#tab` stashed as tab_label:.
12
+ # `ttk::notebook add` is the page's whole placement - unlike every
13
+ # other container, a tab's frame is never pack/grid-managed on its own
14
+ # (see :tab's own +arranged: false+ below). Registered as :tab's own
15
+ # `post_create:`.
16
+ module TabRealize
17
+ def self.post_create(app, node, path, parent_path)
18
+ app.command(parent_path, :add, path, text: node.opts[:tab_label])
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # :tab has no auto-generated `ui.tab` method - it's only ever reachable via
25
+ # the hand-written WidgetDSL#tab, which validates it's declared directly
26
+ # inside ui.tabs. dsl: is a genuine no-op so the registry doesn't shadow
27
+ # that with a same-named generic method.
28
+ Teek::UI::WidgetTypes.register(
29
+ Teek::UI::WidgetType.new(
30
+ type: :tab, tk_command: 'ttk::frame', leaf: false, arranged: false,
31
+ post_create: Teek::UI::TabRealize.method(:post_create),
32
+ validator: Teek::UI::TabValidator.method(:call),
33
+ dsl: ->(mod) { }
34
+ )
35
+ )
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # `table` and `tree` are two DSL names over the same Tk widget
6
+ # (ttk::treeview, used with/without -show tree) - see tree.rb.
7
+ Teek::UI::WidgetTypes.register(
8
+ Teek::UI::WidgetType.new(type: :table, tk_command: 'ttk::treeview', natively_scrollable: true)
9
+ )
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../widget_type'
4
+
5
+ # A plain container holding #tab-declared pages - each one placed entirely
6
+ # by `ttk::notebook add` (see tab.rb's own arranged: false), so :tabs
7
+ # itself never has any arrangeable children and needs no custom arrange:.
8
+ Teek::UI::WidgetTypes.register(
9
+ Teek::UI::WidgetType.new(type: :tabs, tk_command: 'ttk::notebook', leaf: false)
10
+ )