sirens 0.0.1

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/lib/components/component.rb +46 -0
  3. data/lib/components/component_behaviour.rb +160 -0
  4. data/lib/components/containers/splitter.rb +98 -0
  5. data/lib/components/containers/stack.rb +42 -0
  6. data/lib/components/containers/tabs.rb +24 -0
  7. data/lib/components/containers/window.rb +20 -0
  8. data/lib/components/primitive_component.rb +155 -0
  9. data/lib/components/widgets/button.rb +16 -0
  10. data/lib/components/widgets/checkbox.rb +39 -0
  11. data/lib/components/widgets/column_props.rb +23 -0
  12. data/lib/components/widgets/input_text.rb +27 -0
  13. data/lib/components/widgets/list.rb +68 -0
  14. data/lib/components/widgets/list_choice.rb +89 -0
  15. data/lib/components/widgets/radio_button.rb +33 -0
  16. data/lib/components/widgets/text.rb +31 -0
  17. data/lib/components/widgets/tree_choice.rb +82 -0
  18. data/lib/layouts/columns_builder.rb +63 -0
  19. data/lib/layouts/layout_builder.rb +203 -0
  20. data/lib/layouts/radio_button_group_builder.rb +28 -0
  21. data/lib/models/choice_model.rb +41 -0
  22. data/lib/models/list_model.rb +173 -0
  23. data/lib/models/tree_choice_model.rb +102 -0
  24. data/lib/models/value_model.rb +56 -0
  25. data/lib/models/virtual_tree_model.rb +150 -0
  26. data/lib/sirens/browsers/module_browser.rb +67 -0
  27. data/lib/sirens/browsers/object_browser.rb +103 -0
  28. data/lib/sirens/components/ancestors_list.rb +22 -0
  29. data/lib/sirens/components/class_browser.rb +38 -0
  30. data/lib/sirens/components/constants_list.rb +22 -0
  31. data/lib/sirens/components/method_source_code.rb +22 -0
  32. data/lib/sirens/components/methods_list.rb +63 -0
  33. data/lib/sirens/components/modules_list.rb +23 -0
  34. data/lib/sirens/components/namespaces_list.rb +21 -0
  35. data/lib/sirens/models/constant_model.rb +21 -0
  36. data/lib/sirens/models/method_model.rb +111 -0
  37. data/lib/sirens/models/module_browser_model.rb +225 -0
  38. data/lib/sirens/models/object_browser_model.rb +125 -0
  39. data/lib/sirens.rb +90 -0
  40. data/lib/views/button_view.rb +56 -0
  41. data/lib/views/check_button_view.rb +65 -0
  42. data/lib/views/entry_view.rb +25 -0
  43. data/lib/views/list_view.rb +230 -0
  44. data/lib/views/menu_view.rb +46 -0
  45. data/lib/views/notebook_view.rb +27 -0
  46. data/lib/views/paned_view.rb +26 -0
  47. data/lib/views/radio_button_view.rb +67 -0
  48. data/lib/views/stack_view.rb +26 -0
  49. data/lib/views/text_view.rb +112 -0
  50. data/lib/views/tree_view.rb +314 -0
  51. data/lib/views/view.rb +206 -0
  52. data/lib/views/window_view.rb +51 -0
  53. metadata +110 -0
@@ -0,0 +1,68 @@
1
+ module Sirens
2
+ class List < PrimitiveComponent
3
+ ##
4
+ # Returns a ListView.
5
+ #
6
+ def create_view()
7
+ ListView.new
8
+ .on_selection_changed_block { |selection_items:, selection_indices:|
9
+ on_selection_changed(selection_items: selection_items, selection_indices: selection_indices)
10
+ }
11
+ .get_item_block { |index| model.item_at(index: index) }
12
+ end
13
+
14
+ ##
15
+ # Defines the columns in the list with the given columns_props.
16
+ # column_props is an Array of props Hashes, one Hash for each column to define.
17
+ #
18
+ def define_columns(columns_props_array)
19
+ view.define_columns(columns_props_array)
20
+
21
+ # Sync again after adding the columns.
22
+ sync_ui_from_model
23
+ end
24
+
25
+ ##
26
+ # Returns a default model if none is given during the initialization of this component.
27
+ #
28
+ def default_model()
29
+ ListModel.new
30
+ end
31
+
32
+ ##
33
+ # Hook method called when the model value changes.
34
+ #
35
+ def on_value_changed(announcement)
36
+ if announcement.kind_of?(ListChanged)
37
+ sync_ui_from_model
38
+ elsif announcement.kind_of?(ItemsAdded)
39
+ view.add_items(items: announcement.items, index: announcement.index)
40
+ elsif announcement.kind_of?(ItemsUpdated)
41
+ view.update_items(items: announcement.items, indices: announcement.indices)
42
+ elsif announcement.kind_of?(ItemsRemoved)
43
+ view.remove_items(items: announcement.items, indices: announcement.indices)
44
+ else
45
+ raise RuntimeError.new("Unknown announcement #{announcement.class.name}.")
46
+ end
47
+ end
48
+
49
+ def sync_ui_from_model()
50
+ return if view.nil?
51
+
52
+ view.clear_items
53
+ view.add_items(items: model.list, index: 0)
54
+ end
55
+
56
+ # Events
57
+
58
+ def on_selection_changed(selection_items:, selection_indices:)
59
+ return if props[:on_selection_changed].nil?
60
+
61
+ props[:on_selection_changed].call(
62
+ selection: selection_items,
63
+ indices: selection_indices,
64
+ list: self
65
+ )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,89 @@
1
+ module Sirens
2
+ class ListChoice < PrimitiveComponent
3
+ ##
4
+ # Returns a WindowView.
5
+ #
6
+ def create_view()
7
+ ListView.new
8
+ .on_selection_changed_block { |selection_items:, selection_indices:|
9
+ on_selection_changed(selection_items: selection_items, selection_indices: selection_indices)
10
+ }
11
+ .get_item_block { |index| model.item_at(index: index) }
12
+ end
13
+
14
+ ##
15
+ # Defines the columns in the list with the given columns_props.
16
+ # column_props is an Array of props Hashes, one Hash for each column to define.
17
+ #
18
+ def define_columns(columns_props_array)
19
+ view.define_columns(columns_props_array)
20
+
21
+ # Sync again after adding the columns.
22
+ sync_ui_from_model
23
+ end
24
+
25
+ ##
26
+ # Returns a default model if none is given during the initialization of this component.
27
+ #
28
+ def default_model()
29
+ ChoiceModel.new
30
+ end
31
+
32
+ ##
33
+ # Returns the choices list
34
+ #
35
+ def choices()
36
+ model.choices
37
+ end
38
+
39
+ ##
40
+ # Subscribes this component to the model events
41
+ #
42
+ def subscribe_to_model_events()
43
+ model.choices.add_observer(self, :on_choices_changed)
44
+ model.selection.add_observer(self, :on_selected_value_changed)
45
+ end
46
+
47
+ ##
48
+ # Method called when the choices list changes in the model.
49
+ #
50
+ def on_choices_changed(announcement)
51
+ if announcement.kind_of?(ListChanged)
52
+ sync_ui_from_model
53
+ elsif announcement.kind_of?(ItemsAdded)
54
+ view.add_items(items: announcement.items, index: announcement.index)
55
+ elsif announcement.kind_of?(ItemsUpdated)
56
+ view.update_items(items: announcement.items, indices: announcement.indices)
57
+ elsif announcement.kind_of?(ItemsRemoved)
58
+ view.remove_items(items: announcement.items, indices: announcement.indices)
59
+ else
60
+ raise RuntimeError.new("Unknown announcement #{announcement.class.name}.")
61
+ end
62
+ end
63
+
64
+ ##
65
+ # Method called when the selected choice changes in the model.
66
+ #
67
+ def on_selected_value_changed(announcement)
68
+ selected_value = announcement.new_value
69
+
70
+ selection = choices.list.index(selected_value)
71
+
72
+ selection.nil? ?
73
+ view.set_selection_indices([]) : view.set_selection_indices([selection])
74
+ end
75
+
76
+ def sync_ui_from_model()
77
+ return if view.nil?
78
+
79
+ view.clear_items
80
+ view.add_items(items: choices.list, index: 0)
81
+ end
82
+
83
+ # Events
84
+
85
+ def on_selection_changed(selection_items:, selection_indices:)
86
+ model.set_selection(selection_items.first) unless model.nil?
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,33 @@
1
+ module Sirens
2
+ class RadioButton < PrimitiveComponent
3
+ ##
4
+ # Returns a ButtonView.
5
+ #
6
+ def create_view()
7
+ previous_button = props[:previous_button].nil? ? nil : props[:previous_button].view
8
+
9
+ RadioButtonView.new(
10
+ previous_button: previous_button,
11
+ on_toggled: proc{ |state:| on_toggled(state: state) }
12
+ )
13
+ end
14
+
15
+ # Actions
16
+
17
+ def click()
18
+ view.click
19
+ end
20
+
21
+ # Events
22
+
23
+ def on_value_changed(announcement)
24
+ view.set_value(announcement.new_value)
25
+ end
26
+
27
+ def on_toggled(state:)
28
+ return if model.nil?
29
+
30
+ model.set_selection(props[:id]) if state == true
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ module Sirens
2
+ class Text < PrimitiveComponent
3
+ ##
4
+ # Returns a TextView.
5
+ #
6
+ def create_view()
7
+ TextView.new
8
+ end
9
+
10
+ ##
11
+ # Returns a default model if none is given during the initialization of this component.
12
+ #
13
+ def default_model()
14
+ ValueModel.on('')
15
+ end
16
+
17
+ def sync_ui_from_model()
18
+ view.set_text(model.value) unless view.nil?
19
+ end
20
+
21
+ def selected_text()
22
+ view.selected_text
23
+ end
24
+
25
+ # Events
26
+
27
+ def on_value_changed(announcement)
28
+ view.set_text(announcement.new_value)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,82 @@
1
+ module Sirens
2
+ class TreeChoice < PrimitiveComponent
3
+ ##
4
+ # Returns a WindowView.
5
+ #
6
+ def create_view()
7
+ TreeView.new
8
+ .on_selection_changed_block { |selection_items:, selection_paths:|
9
+ on_selection_changed(selection_items: selection_items, selection_paths: selection_paths)
10
+ }
11
+ .get_item_block { |path:| model.item_at(path: path) }
12
+ .get_children_block { |path:| model.children_at(path: path) }
13
+ end
14
+
15
+ ##
16
+ # Defines the columns in the list with the given columns_props.
17
+ # column_props is an Array of props Hashes, one Hash for each column to define.
18
+ #
19
+ def define_columns(columns_props_array)
20
+ raise RuntimeError.new("The #{self.class.name} must have at least one column.") if columns_props_array.empty?
21
+
22
+ view.define_columns(columns_props_array)
23
+
24
+ # Sync again after adding the columns.
25
+ sync_ui_from_model
26
+ end
27
+
28
+ ##
29
+ # Returns a default model if none is given during the initialization of this component.
30
+ #
31
+ def default_model()
32
+ TreeChoiceModel.new(roots: [], get_children_block: nil)
33
+ end
34
+
35
+ ##
36
+ # Returns the root items of the tree.
37
+ #
38
+ def root_items()
39
+ model.tree.roots
40
+ end
41
+
42
+ ##
43
+ # Syncs the ui from the model.
44
+ #
45
+ def sync_ui_from_model()
46
+ return if view.nil?
47
+
48
+ view.clear_items
49
+ view.add_items(parent_iter: nil, items: model.tree.roots, index: 0)
50
+ end
51
+
52
+ # Events
53
+
54
+ ##
55
+ # Subscribes this component to the model events
56
+ #
57
+ def subscribe_to_model_events()
58
+ model.tree.add_observer(self, :on_tree_changed)
59
+ model.selection.add_observer(self, :on_selected_value_changed)
60
+ end
61
+
62
+ def on_tree_changed(announcement)
63
+ sync_ui_from_model
64
+ end
65
+
66
+ def on_selected_value_changed(announcement)
67
+ selection_hierarchy = announcement.new_value
68
+
69
+ indices_path = model.path_of(selection_hierarchy)
70
+
71
+ while_updating_view do
72
+ view.set_selection_indices(indices_path)
73
+ end
74
+ end
75
+
76
+ def on_selection_changed(selection_items:, selection_paths:)
77
+ return if is_updating_view?
78
+
79
+ model.set_selection_from_path(path: selection_paths.first) unless model.nil?
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,63 @@
1
+ module Sirens
2
+ class ColumnsBuilder
3
+ # Initializing
4
+
5
+ def initialize()
6
+ @props = Hash[]
7
+ @columns = []
8
+ @popup_menu = nil
9
+ end
10
+
11
+ def columns()
12
+ @columns
13
+ end
14
+
15
+ def get_popup_menu()
16
+ @popup_menu
17
+ end
18
+
19
+ # Evaluating
20
+
21
+ def render(props, &block)
22
+ @props = props
23
+ @columns = []
24
+ @popup_menu = nil
25
+
26
+ instance_exec(self, &block)
27
+
28
+ self
29
+ end
30
+
31
+ # Current component model
32
+
33
+ def model(object)
34
+ @props[:model] = object
35
+ end
36
+
37
+ # Current component props
38
+
39
+ def styles(props)
40
+ @props.merge!(props)
41
+ end
42
+
43
+ def props(props)
44
+ @props.merge!(props)
45
+ end
46
+
47
+ def handlers(props)
48
+ @props.merge!(props)
49
+ end
50
+
51
+ # List columns
52
+
53
+ def column(props = Hash[])
54
+ @columns << ColumnProps.new(props)
55
+ end
56
+
57
+ # Popup menu
58
+
59
+ def popup_menu(&block)
60
+ @popup_menu = block
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,203 @@
1
+ module Sirens
2
+ class LayoutBuilder
3
+
4
+ # Initializing
5
+
6
+ def initialize(main_component:)
7
+ @main_component = main_component
8
+
9
+ @props = Hash[]
10
+ @components = []
11
+ end
12
+
13
+ # Evaluating
14
+
15
+ def render(props = Hash[], &build_block)
16
+ instance_exec(@main_component, &build_block)
17
+
18
+ @main_component.add_all_components(@components)
19
+ end
20
+
21
+ # Accessing
22
+
23
+ def merge_to_current_props(props)
24
+ @props.merge!(props)
25
+ end
26
+
27
+ # Current component model
28
+
29
+ def model(object)
30
+ merge_to_current_props(model: object)
31
+ end
32
+
33
+ # Current component props
34
+
35
+ def styles(props)
36
+ merge_to_current_props(props)
37
+ end
38
+
39
+ def props(props)
40
+ merge_to_current_props(props)
41
+ end
42
+
43
+ def handlers(props)
44
+ merge_to_current_props(props)
45
+ end
46
+
47
+ # Components
48
+
49
+ def component(component, &build_block)
50
+ build_component_from(Hash[], build_block) { |props, child_components|
51
+ component.set_props(props)
52
+
53
+ component.add_all_components(child_components)
54
+ }
55
+ end
56
+
57
+ # Containers
58
+
59
+ def window(props = Hash[], &build_block)
60
+ build_component_from(props, build_block) { |props, child_components|
61
+ Window.new(props)
62
+ .add_all_components(child_components)
63
+ }
64
+ end
65
+
66
+ def horizontal_stack(props = Hash[], &build_block)
67
+ build_component_from(props, build_block) { |props, child_components|
68
+ Stack.horizontal(props)
69
+ .add_all_components(child_components)
70
+ }
71
+ end
72
+
73
+ def vertical_stack(props = Hash[], &build_block)
74
+ build_component_from(props, build_block) { |props, child_components|
75
+ Stack.vertical(props)
76
+ .add_all_components(child_components)
77
+ }
78
+ end
79
+
80
+ def horizontal_splitter(props = Hash[], &build_block)
81
+ build_component_from(props, build_block) { |props, child_components|
82
+ Splitter.horizontal(props)
83
+ .add_all_components(child_components)
84
+ }
85
+ end
86
+
87
+ def vertical_splitter(props = Hash[], &build_block)
88
+ build_component_from(props, build_block) { |props, child_components|
89
+ Splitter.vertical(props)
90
+ .add_all_components(child_components)
91
+ }
92
+ end
93
+
94
+ def tabs(props = Hash[], &build_block)
95
+ build_component_from(props, build_block) { |props, child_components|
96
+ Tabs.new(props)
97
+ .add_all_components(child_components)
98
+ }
99
+ end
100
+
101
+ # Widgets
102
+
103
+ def button(props = Hash[], &build_block)
104
+ build_component_from(props, build_block) { |props, child_components|
105
+ Button.new(props)
106
+ }
107
+ end
108
+
109
+ def checkbox(props = Hash[], &build_block)
110
+ build_component_from(props, build_block) { |props, child_components|
111
+ Checkbox.new(props)
112
+ }
113
+ end
114
+
115
+ def radio_button(props = Hash[], &build_block)
116
+ build_component_from(props, build_block) { |props, child_components|
117
+ RadioButton.new(props)
118
+ }
119
+ end
120
+
121
+ def radio_buttons_group(props = Hash[], &build_block)
122
+ buttons = RadioButtonGroupBuilder.new.render(&build_block)
123
+
124
+ @components.concat(buttons)
125
+ end
126
+
127
+ def list(props = Hash[], &build_block)
128
+ columns_builder = ColumnsBuilder.new.render(props, &build_block)
129
+
130
+ props[:popup_menu] = columns_builder.popup_menu unless columns_builder.popup_menu.nil?
131
+
132
+ List.new(props).tap { |list|
133
+ list.define_columns(columns_builder.columns)
134
+
135
+ @components << list
136
+ }
137
+ end
138
+
139
+ def choices_list(props = Hash[], &build_block)
140
+ columns_builder = ColumnsBuilder.new.render(props, &build_block)
141
+
142
+ props[:popup_menu] = columns_builder.popup_menu unless columns_builder.popup_menu.nil?
143
+
144
+ ListChoice.new(props).tap { |list|
145
+ list.define_columns(columns_builder.columns)
146
+
147
+ @components << list
148
+ }
149
+ end
150
+
151
+ def input_text(props = Hash[], &build_block)
152
+ build_component_from(props, build_block) { |props, child_components|
153
+ InputText.new(props)
154
+ }
155
+ end
156
+
157
+ def text(props = Hash[], &build_block)
158
+ build_component_from(props, build_block) { |props, child_components|
159
+ Text.new(props)
160
+ }
161
+ end
162
+
163
+ def choices_tree(props = Hash[], &build_block)
164
+ columns_builder = ColumnsBuilder.new.render(props, &build_block)
165
+
166
+ props[:popup_menu] = columns_builder.get_popup_menu unless columns_builder.get_popup_menu.nil?
167
+
168
+ TreeChoice.new(props).tap { |tree|
169
+ tree.define_columns(columns_builder.columns)
170
+
171
+ @components << tree
172
+ }
173
+ end
174
+
175
+ def popup_menu(&block)
176
+ merge_to_current_props(popup_menu: block)
177
+ end
178
+
179
+ # Utility methods
180
+
181
+ def build_component_from(props = Hash[], build_block, &after_build_block)
182
+ current_props = @props
183
+ current_components = @components
184
+
185
+ built_component = nil
186
+
187
+ begin
188
+ @props = props.clone
189
+ @components = []
190
+
191
+ build_block.call unless build_block.nil?
192
+
193
+ built_component = after_build_block.call(@props, @components)
194
+ ensure
195
+ @props = current_props
196
+ @components = current_components
197
+ end
198
+
199
+ @components << built_component unless built_component.nil?
200
+ end
201
+
202
+ end
203
+ end
@@ -0,0 +1,28 @@
1
+ module Sirens
2
+ class RadioButtonGroupBuilder
3
+
4
+ # Initializing
5
+
6
+ def initialize()
7
+ @buttons = []
8
+ end
9
+
10
+ # Evaluating
11
+
12
+ def render(&block)
13
+ @buttons = []
14
+
15
+ instance_exec(self, &block)
16
+
17
+ @buttons
18
+ end
19
+
20
+ # List columns
21
+
22
+ def radio_button(props = Hash[])
23
+ props[:previous_button] = @buttons.last
24
+
25
+ @buttons << RadioButton.new(props)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ module Sirens
2
+ class ChoiceModel
3
+
4
+ # Initializing
5
+
6
+ def initialize(selection: nil, choices: [])
7
+ super()
8
+
9
+ @selection = ValueModel.on(selection)
10
+ @choices = ListModel.on(choices)
11
+ end
12
+
13
+ # Accessing
14
+
15
+ def selection()
16
+ @selection
17
+ end
18
+
19
+ def set_selection(new_value)
20
+ @selection.set_value(new_value)
21
+ end
22
+
23
+ def choices()
24
+ @choices
25
+ end
26
+
27
+ def set_choices(list)
28
+ @choices.set_list(list)
29
+ end
30
+
31
+ def item_at(index:)
32
+ @choices[index]
33
+ end
34
+
35
+ # Asking
36
+
37
+ def has_selection()
38
+ ! @selection.value.nil?
39
+ end
40
+ end
41
+ end