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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cf56c70a10adabf89009041d21ca90c692fd6a6c81e1fb4c69ba3e4a47a24dbd
4
+ data.tar.gz: d287ee12c0382bf83b2e26d747875e74b4307b4a346638b2f5b412d97f978984
5
+ SHA512:
6
+ metadata.gz: 47c7e60f0d3667ec1631ac89a10e0ab4d39643e9d6249d03f943e5c134398bd942449c89ecd1430a23c2a58ef2d9e89c611d477e658bf0e2ea1b62fef0dff48c
7
+ data.tar.gz: 8bee3cb065fbe8b7039bc8d8abf7d3bc7eacd31ea406f68b065ebe5f004c40b27302d83d1b8519747f1caebf77a77b8f7a159e3e8da3922a7003ac9d48093b55
@@ -0,0 +1,46 @@
1
+ module Sirens
2
+ class Component
3
+ include ComponentBehaviour
4
+
5
+
6
+ # Initializing
7
+
8
+ ##
9
+ # Initializes this component
10
+ #
11
+ def initialize(props = Hash[])
12
+ super(props)
13
+
14
+ build
15
+ end
16
+
17
+ ##
18
+ # Configures the widget with its model, styles and child widgets but does not apply the styles yet.
19
+ # This method is called when opening a widget with ::open and after calling ::initialize_handles.
20
+ # The building of the widget includes defining its model, its style props and its event blocks,
21
+ # but does no apply those styles yet. The wiring and synchronization of the component to the
22
+ # widget is done in the ::post_build method.
23
+ #
24
+ def build()
25
+ set_model( props.key?(:model) ? props[:model] : default_model )
26
+
27
+ renderWith(LayoutBuilder.new(main_component: self))
28
+
29
+ self
30
+ end
31
+
32
+ ##
33
+ # Hook method to allow each Component subclass to define its default styles and compose its child components.
34
+ # Subclasses are expected to implement this method.
35
+ def renderWith(layout)
36
+ raise RuntimeError.new("Class #{self.class.name} must implement a ::renderWith(layout) method.")
37
+ end
38
+
39
+ ##
40
+ # Returns the top most view of this component.
41
+ #
42
+ def main_view()
43
+ main_component.main_view
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,160 @@
1
+ module Sirens
2
+ module ComponentBehaviour
3
+ module ClassMethods
4
+ # Opening
5
+
6
+ ##
7
+ # Todo: Opens a new window with this component. Currently only works for actual Windows.
8
+ #
9
+ def open(props = Hash[])
10
+ self.new(props)
11
+ .open
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ # Initializing
20
+
21
+ ##
22
+ # Initializes this component.
23
+ #
24
+ def initialize(props = Hash[])
25
+ super()
26
+
27
+ @props = Hash[]
28
+ @components = []
29
+
30
+ set_props(props)
31
+ end
32
+
33
+ # Accessing
34
+
35
+ ##
36
+ # Returns this component props.
37
+ #
38
+ def props()
39
+ @props
40
+ end
41
+
42
+ ##
43
+ # Applies the given props to the current component.props.
44
+ # The component.props that are not included in given props remain untouched.
45
+ #
46
+ def set_props(props)
47
+ @props.merge!(props)
48
+ end
49
+
50
+ ##
51
+ # Returns the child components of this component.
52
+ #
53
+ def components()
54
+ @components
55
+ end
56
+
57
+ ##
58
+ # Returns the main child component.
59
+ #
60
+ def main_component()
61
+ @components.first
62
+ end
63
+
64
+ ##
65
+ # Returns a default model if none is given during the initialization of this component.
66
+ #
67
+ def default_model()
68
+ nil
69
+ end
70
+
71
+ ##
72
+ # Returns the current model of this component.
73
+ #
74
+ def model()
75
+ @props[:model]
76
+ end
77
+
78
+ ##
79
+ # Sets the current model of this component.
80
+ #
81
+ def set_model(new_model)
82
+ old_model = model
83
+
84
+ @props[:model] = new_model
85
+
86
+ on_model_changed(new_model: new_model, old_model: old_model)
87
+ end
88
+
89
+ def on_model_changed(new_model:, old_model:)
90
+ end
91
+
92
+ # Child components
93
+
94
+ ##
95
+ # Adds the child_component to this component.
96
+ #
97
+ def add_component(child_component)
98
+ components << child_component
99
+
100
+ on_component_added(child_component)
101
+
102
+ child_component
103
+ end
104
+
105
+ ##
106
+ # Adds the child_component to this component.
107
+ #
108
+ def add_all_components(components)
109
+ components.each do |child_component|
110
+ add_component(child_component)
111
+ end
112
+
113
+ self
114
+ end
115
+
116
+ ##
117
+ # Removes the last child component and returns it.
118
+ #
119
+ def remove_last_component()
120
+ remove_component_at(index: components.size - 1)
121
+ end
122
+
123
+ ##
124
+ # Removes the component at the index-th position.
125
+ #
126
+ def remove_component_at(index:)
127
+ component = components.delete_at(index)
128
+
129
+ main_view.remove_view(component.main_view)
130
+
131
+ component
132
+ end
133
+
134
+ ##
135
+ # This method is called right after adding a child component.
136
+ # Subclasses may use it to perform further configuration on this component or its children.
137
+ #
138
+ def on_component_added(child_component)
139
+ end
140
+
141
+ ##
142
+ # Returns the top most view of this component.
143
+ # This method is required to assemble parent and child views.
144
+ #
145
+ def main_view()
146
+ raise RuntimeError.new("Class #{self.class.name} must implement a ::main_view() method.")
147
+ end
148
+
149
+ ##
150
+ # Todo: Opens this component in a Window.
151
+ # At this moment only works if the main_component is a window.
152
+ #
153
+ def open()
154
+ main_component.show
155
+
156
+ self
157
+ end
158
+
159
+ end
160
+ end
@@ -0,0 +1,98 @@
1
+ module Sirens
2
+ ##
3
+ # Component that wraps a StackView.
4
+ #
5
+ class Splitter < PrimitiveComponent
6
+
7
+ # Class methods
8
+
9
+ class << self
10
+ def horizontal(props = Hash[])
11
+ props[:orientation] = :horizontal
12
+
13
+ self.new(props)
14
+ end
15
+
16
+ def vertical(props = Hash[])
17
+ props[:orientation] = :vertical
18
+
19
+ self.new(props)
20
+ end
21
+ end
22
+
23
+ ##
24
+ # Returns a PanedView.
25
+ #
26
+ def create_view()
27
+ PanedView.new(
28
+ orientation: props.fetch(:orientation),
29
+ on_size_allocation: proc{ |width:, height:|
30
+ on_size_allocation(width: width, height: height)
31
+ }
32
+ )
33
+ end
34
+
35
+ ##
36
+ # Adds the child_component to this component.
37
+ #
38
+ def add_component(child_component)
39
+ if components.size < 2
40
+ components << child_component
41
+
42
+ on_component_added(child_component)
43
+
44
+ return
45
+ end
46
+
47
+ last_child = remove_last_component
48
+
49
+ new_splitter_proportion = 1.0 - components.first.props[:splitter_proportion]
50
+
51
+ new_splitter = self.class.new(orientation: orientation)
52
+
53
+ last_child.props[:splitter_proportion] = last_child.props[:splitter_proportion] / new_splitter_proportion
54
+ new_splitter.add_component(last_child)
55
+
56
+ child_component.props[:splitter_proportion] = child_component.props[:splitter_proportion] / new_splitter_proportion
57
+ new_splitter.add_component(child_component)
58
+
59
+ components << new_splitter
60
+ on_component_added(new_splitter)
61
+ end
62
+
63
+ # Asking
64
+
65
+ def orientation()
66
+ props[:orientation]
67
+ end
68
+
69
+ # Events
70
+
71
+ def on_size_allocation(width:, height:)
72
+ components.each do |child_component|
73
+ size_proportion = child_component.props[:splitter_proportion]
74
+
75
+ next if size_proportion.nil?
76
+
77
+ if orientation == :vertical
78
+ proportional_height = height * size_proportion
79
+
80
+ if child_component.props.key?(:height)
81
+ proportional_height = [proportional_height, child_component.props[:height]].max
82
+ end
83
+
84
+ child_component.main_view.height = proportional_height
85
+
86
+ else
87
+ proportional_width = width * size_proportion
88
+
89
+ if child_component.props.key?(:width)
90
+ proportional_width = [proportional_width, child_component.props[:width]].max
91
+ end
92
+
93
+ child_component.main_view.width = proportional_width
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,42 @@
1
+ module Sirens
2
+ ##
3
+ # Component that wraps a StackView.
4
+ #
5
+ class Stack < PrimitiveComponent
6
+
7
+ # Class methods
8
+
9
+ class << self
10
+ def horizontal(props = Hash[])
11
+ props[:orientation] = :horizontal
12
+
13
+ self.new(props)
14
+ end
15
+
16
+ def vertical(props = Hash[])
17
+ props[:orientation] = :vertical
18
+
19
+ self.new(props)
20
+ end
21
+ end
22
+
23
+ ##
24
+ # Returns a StackView.
25
+ #
26
+ def create_view()
27
+ StackView.new(props.fetch(:orientation))
28
+ end
29
+
30
+ ##
31
+ # Adds the child_component to this component.
32
+ #
33
+ def on_component_added(child_component)
34
+ view.add_view(
35
+ child_component.main_view,
36
+ expand: child_component.props.fetch(:stack_expand, true),
37
+ fill: child_component.props.fetch(:stack_fill, true),
38
+ padding: child_component.props.fetch(:stack_padding, 0)
39
+ )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ module Sirens
2
+ ##
3
+ # Component that wraps a NotebookView.
4
+ #
5
+ class Tabs < PrimitiveComponent
6
+ ##
7
+ # Returns a StackView.
8
+ #
9
+ def create_view()
10
+ NotebookView.new
11
+ end
12
+
13
+ ##
14
+ # Adds the child_component to this component.
15
+ #
16
+ def on_component_added(child_component)
17
+ super(child_component)
18
+
19
+ tab_label_text = child_component.props[:tab_label]
20
+
21
+ view.set_tab_label_at(index: components.size - 1, text: tab_label_text)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module Sirens
2
+ ##
3
+ # Component that wraps a WindowView.
4
+ #
5
+ class Window < PrimitiveComponent
6
+ ##
7
+ # Returns a WindowView.
8
+ #
9
+ def create_view()
10
+ WindowView.new
11
+ end
12
+
13
+ ##
14
+ # Makes this component visible.
15
+ #
16
+ def show()
17
+ view.show
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,155 @@
1
+ module Sirens
2
+ ##
3
+ # PrimitiveComponent is a component wrapping a PrimitiveView.
4
+ # A PrimitiveView implements the actual GUI binding to a Widget (a Gtk widget, for instance).
5
+ # Besides acting as a regular Component, PrimitiveComponent also takes care of handling the PrimitiveView.
6
+ #
7
+ class PrimitiveComponent
8
+ include ComponentBehaviour
9
+
10
+ # Initializing
11
+
12
+ ##
13
+ # Initializes this component
14
+ #
15
+ def initialize(props = Hash[])
16
+ super(props)
17
+
18
+ @view = create_view
19
+
20
+ @updating_view = false
21
+
22
+ apply_props
23
+
24
+ sync_ui_from_model
25
+ end
26
+
27
+ ##
28
+ # Applies the props to the view.
29
+ #
30
+ def apply_props()
31
+ if ! view.nil?
32
+ @view.apply_props(props)
33
+
34
+ @view.populate_popup_menu_block = proc { |menu:| populate_popup_menu(menu: menu) }
35
+ end
36
+
37
+ set_model( props.key?(:model) ? props[:model] : default_model )
38
+ end
39
+
40
+ ##
41
+ # Creates the PrimitiveView that this component wraps.
42
+ # This method must be implemented by each subclass.
43
+ #
44
+ def create_view()
45
+ raise RuntimeError.new("Class #{self.class.name} must implement a ::create_view() method.")
46
+ end
47
+
48
+ # Accessing
49
+
50
+ ##
51
+ # Applies the given props to the current component.props.
52
+ # The component.props that are not included in given props remain untouched.
53
+ #
54
+ def set_props(props)
55
+ super(props)
56
+
57
+ apply_props
58
+
59
+ sync_ui_from_model if props.key?(:model)
60
+ end
61
+
62
+ ##
63
+ # Returns this component View.
64
+ #
65
+ def view()
66
+ @view
67
+ end
68
+
69
+ ##
70
+ # Returns the top most view of this component.
71
+ #
72
+ def main_view()
73
+ view
74
+ end
75
+
76
+ # Child components
77
+
78
+ ##
79
+ # Adds the child_component to this component.
80
+ #
81
+ def on_component_added(child_component)
82
+ view.add_view(child_component.main_view)
83
+ end
84
+
85
+ # Events
86
+
87
+ ##
88
+ # Method called when this component model changes.
89
+ # Unsubscribes this component from the current model events, subscribes this component to the
90
+ # new model events and syncs the widget with the new model.
91
+ #
92
+ def on_model_changed(old_model:, new_model:)
93
+ old_model.delete_observer(self) if old_model.respond_to?(:delete_observer)
94
+
95
+ subscribe_to_model_events
96
+
97
+ sync_ui_from_model
98
+ end
99
+
100
+ ##
101
+ # Subscribes this component to the model events
102
+ #
103
+ def subscribe_to_model_events()
104
+ model.add_observer(self, :on_value_changed) if model.respond_to?(:add_observer)
105
+ end
106
+
107
+ ##
108
+ # Hook method called when the model value changes.
109
+ # Note that the model remains the same, what changed is the value the model holds.
110
+ # Subclasses may use this method to update other aspects of its model or perform actions.
111
+ #
112
+ def on_value_changed(announcement)
113
+ sync_ui_from_model
114
+ end
115
+
116
+ ##
117
+ # Populate the popup menu.
118
+ #
119
+ def populate_popup_menu(menu:)
120
+ return if props[:popup_menu].nil?
121
+
122
+ props[:popup_menu].call(menu: menu, menu_owner: self)
123
+ end
124
+
125
+ # Actions
126
+
127
+ ##
128
+ # Synchronizes the view to the models current state.
129
+ #
130
+ def sync_ui_from_model()
131
+ end
132
+
133
+ ##
134
+ # Returns true if this component is currently updating the view.
135
+ #
136
+ def is_updating_view?()
137
+ @updating_view
138
+ end
139
+
140
+ ##
141
+ # Flags that this component is updating the view during the evaluation of the given &block.
142
+ #
143
+ def while_updating_view(&block)
144
+ updating = is_updating_view?
145
+
146
+ @updating_view = true
147
+
148
+ begin
149
+ block.call
150
+ ensure
151
+ @updating_view = updating
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,16 @@
1
+ module Sirens
2
+ class Button < PrimitiveComponent
3
+ ##
4
+ # Returns a ButtonView.
5
+ #
6
+ def create_view()
7
+ ButtonView.new
8
+ end
9
+
10
+ # Actions
11
+
12
+ def click()
13
+ view.click
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ module Sirens
2
+ class Checkbox < PrimitiveComponent
3
+ ##
4
+ # Returns a CheckButtonView.
5
+ #
6
+ def create_view()
7
+ CheckButtonView.new(on_toggled: proc{ |state:| on_toggled(state: state) })
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(false)
15
+ end
16
+
17
+ # Actions
18
+
19
+ def click()
20
+ view.click
21
+ end
22
+
23
+ def sync_ui_from_model()
24
+ view.set_value(model.value) unless view.nil?
25
+ end
26
+
27
+ # Events
28
+
29
+ def on_value_changed(announcement)
30
+ view.set_value(announcement.new_value)
31
+ end
32
+
33
+ def on_toggled(state:)
34
+ return if model.nil?
35
+
36
+ model.set_value(state)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ module Sirens
2
+ class ColumnProps
3
+ def initialize(props = Hash[])
4
+ @props = props
5
+ end
6
+
7
+ attr_reader :props
8
+
9
+ def fetch(key, absent_value)
10
+ @props.fetch(key, absent_value)
11
+ end
12
+
13
+ def [](key)
14
+ @props[key]
15
+ end
16
+
17
+ def display_text_of(item)
18
+ return item.to_s if ! @props.key?(:get_text_block)
19
+
20
+ @props[:get_text_block].call(item)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module Sirens
2
+ class InputText < PrimitiveComponent
3
+ ##
4
+ # Returns a TextView.
5
+ #
6
+ def create_view()
7
+ EntryView.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
+ # Events
22
+
23
+ def on_value_changed(announcement)
24
+ view.set_text(announcement.new_value)
25
+ end
26
+ end
27
+ end