glimmer-dsl-swt 4.18.4.3 → 4.18.4.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/README.md +81 -5089
  4. data/VERSION +1 -1
  5. data/docs/reference/GLIMMER_COMMAND.md +591 -0
  6. data/docs/reference/GLIMMER_CONFIGURATION.md +183 -0
  7. data/docs/reference/GLIMMER_GIRB.md +30 -0
  8. data/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md +3366 -0
  9. data/docs/reference/GLIMMER_PACKAGING_AND_DISTRIBUTION.md +202 -0
  10. data/docs/reference/GLIMMER_SAMPLES.md +676 -0
  11. data/docs/reference/GLIMMER_STYLE_GUIDE.md +14 -0
  12. data/glimmer-dsl-swt.gemspec +16 -8
  13. data/lib/glimmer/dsl/swt/custom_widget_expression.rb +3 -3
  14. data/lib/glimmer/dsl/swt/exec_expression.rb +1 -1
  15. data/lib/glimmer/dsl/swt/image_expression.rb +3 -0
  16. data/lib/glimmer/dsl/swt/observe_expression.rb +8 -5
  17. data/lib/glimmer/dsl/swt/pixel_expression.rb +38 -0
  18. data/lib/glimmer/dsl/swt/timer_exec_expression.rb +35 -0
  19. data/lib/glimmer/dsl/swt/widget_expression.rb +1 -0
  20. data/lib/glimmer/swt/color_proxy.rb +4 -3
  21. data/lib/glimmer/swt/custom/animation.rb +10 -8
  22. data/lib/glimmer/swt/custom/code_text.rb +24 -20
  23. data/lib/glimmer/swt/custom/drawable.rb +54 -6
  24. data/lib/glimmer/swt/custom/shape.rb +91 -60
  25. data/lib/glimmer/swt/display_proxy.rb +11 -10
  26. data/lib/glimmer/swt/image_proxy.rb +17 -6
  27. data/lib/glimmer/swt/properties.rb +35 -10
  28. data/lib/glimmer/swt/widget_proxy.rb +17 -3
  29. data/lib/glimmer/ui/custom_widget.rb +17 -0
  30. data/samples/elaborate/mandelbrot_fractal.rb +103 -0
  31. data/samples/elaborate/meta_sample.rb +1 -1
  32. data/samples/elaborate/tetris.rb +5 -18
  33. data/samples/elaborate/tetris/view/block.rb +1 -1
  34. data/samples/elaborate/tetris/view/playfield.rb +1 -1
  35. data/samples/hello/hello_canvas_animation.rb +1 -1
  36. data/samples/hello/hello_canvas_transform.rb +1 -1
  37. data/samples/hello/hello_table.rb +1 -1
  38. data/samples/hello/hello_table/baseball_park.png +0 -0
  39. metadata +14 -6
  40. data/samples/elaborate/meta_sample/meta_sample_logo.png +0 -0
  41. data/samples/hello/hello_canvas_transform/hello_canvas_transform_image.png +0 -0
@@ -0,0 +1,183 @@
1
+ ## Glimmer Configuration
2
+
3
+ Glimmer configuration may be done via the `Glimmer::Config` module.
4
+
5
+ ### logger
6
+
7
+ The Glimmer DSL engine supports logging via a standard `STDOUT` Ruby `Logger` configured in the `Glimmer::Config.logger` config option.
8
+ It is set to level Logger::ERROR by default.
9
+ Log level may be adjusted via `Glimmer::Config.logger.level` just like any other Ruby Logger.
10
+
11
+ Example:
12
+
13
+ ```ruby
14
+ Glimmer::Config.logger.level = :debug
15
+ ```
16
+ This results in more verbose debug loggging to `STDOUT`, which is very helpful in troubleshooting Glimmer DSL syntax when needed.
17
+
18
+ Example log:
19
+ ```
20
+ D, [2017-07-21T19:23:12.587870 #35707] DEBUG -- : method: shell and args: []
21
+ D, [2017-07-21T19:23:12.594405 #35707] DEBUG -- : ShellCommandHandler will handle command: shell with arguments []
22
+ D, [2017-07-21T19:23:12.844775 #35707] DEBUG -- : method: composite and args: []
23
+ D, [2017-07-21T19:23:12.845388 #35707] DEBUG -- : parent is a widget: true
24
+ D, [2017-07-21T19:23:12.845833 #35707] DEBUG -- : on listener?: false
25
+ D, [2017-07-21T19:23:12.864395 #35707] DEBUG -- : WidgetCommandHandler will handle command: composite with arguments []
26
+ D, [2017-07-21T19:23:12.864893 #35707] DEBUG -- : widget styles are: []
27
+ D, [2017-07-21T19:23:12.874296 #35707] DEBUG -- : method: list and args: [:multi]
28
+ D, [2017-07-21T19:23:12.874969 #35707] DEBUG -- : parent is a widget: true
29
+ D, [2017-07-21T19:23:12.875452 #35707] DEBUG -- : on listener?: false
30
+ D, [2017-07-21T19:23:12.878434 #35707] DEBUG -- : WidgetCommandHandler will handle command: list with arguments [:multi]
31
+ D, [2017-07-21T19:23:12.878798 #35707] DEBUG -- : widget styles are: [:multi]
32
+ ```
33
+
34
+ The `logger` instance may be replaced with a custom logger via `Glimmer::Config.logger = custom_logger`
35
+
36
+ To reset `logger` to the default instance, you may call `Glimmer::Config.reset_logger!`
37
+
38
+ All logging is done lazily via blocks (e.g. `logger.debug {message}`) to avoid affecting app performance with logging when below the configured logging level threshold.
39
+
40
+ [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) enhances Glimmer default logging support via the Ruby [`logging`](https://github.com/TwP/logging) gem, enabling buffered asynchronous logging in a separate thread, thus completely unhindering normal desktop app performance.
41
+
42
+ Other config options related to the [`logging`](https://github.com/TwP/logging) gem are mentioned below.
43
+
44
+ #### logging_devices
45
+
46
+ This is an array of these possible values: `:stdout` (default), `:stderr`, `:file`, `:syslog` (default), `:stringio`
47
+
48
+ It defaults to `[:stdout, :syslog]`
49
+
50
+ When `:file` is included, Glimmer creates a 'log' directory directly below the Glimmer app local directory.
51
+ It may also be customized further via the `logging_device_file_options` option.
52
+ This is useful on Windows as an alternative to `syslog`, which is not available on Windows by default.
53
+
54
+ #### logging_device_file_options
55
+
56
+ This is a hash of [`logging`](https://github.com/TwP/logging) gem options for the `:file` logging device.
57
+
58
+ Default: `{size: 1_000_000, age: 'daily', roll_by: 'number'}`
59
+
60
+ That ensures splitting log files at the 1MB size and daily, rolling them by unique number.
61
+
62
+ #### logging_appender_options
63
+
64
+ Appender options is a hash passed as options to every appender (logging device) used in the [`logging`](https://github.com/TwP/logging) gem.
65
+
66
+ Default: `{async: true, auto_flushing: 500, write_size: 500, flush_period: 60, immediate_at: [:error, :fatal], layout: logging_layout}`
67
+
68
+ That ensures asynchronous buffered logging that is flushed every 500 messages and 60 seconds, or immediately at error and fatal log levels
69
+
70
+ #### logging_layout
71
+
72
+ This is a [`logging`](https://github.com/TwP/logging) gem layout that formats the logging output.
73
+
74
+ Default:
75
+
76
+ ```
77
+ Logging.layouts.pattern(
78
+ pattern: '[%d] %-5l %c: %m\n',
79
+ date_pattern: '%Y-%m-%d %H:%M:%S'
80
+ )
81
+ ```
82
+
83
+ ### import_swt_packages
84
+
85
+ Glimmer automatically imports all SWT Java packages upon adding `include Glimmer`, `include Glimmer::UI::CustomWidget`, or `include Glimmer::UI::CustomShell` to a class or module. It relies on JRuby's `include_package` for lazy-importing upon first reference of a Java class.
86
+
87
+ As a result, you may call SWT Java classes from Glimmer Ruby code without mentioning Java package references explicitly.
88
+
89
+ For example, `org.eclipse.swt.graphics.Color` can be referenced as just `Color`
90
+
91
+ The Java packages imported come from the [`Glimmer::Config.import_swt_packages`](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/lib/ext/glimmer/config.rb) config option, which defaults to `Glimmer::Config::DEFAULT_IMPORT_SWT_PACKAGES`, importing the following Java packages:
92
+ ```
93
+ org.eclipse.swt.*
94
+ org.eclipse.swt.widgets.*
95
+ org.eclipse.swt.layout.*
96
+ org.eclipse.swt.graphics.*
97
+ org.eclipse.swt.browser.*
98
+ org.eclipse.swt.custom.*
99
+ org.eclipse.swt.dnd.*
100
+ ```
101
+
102
+ If you need to import additional Java packages as extra Glimmer widgets, you may add more packages to [`Glimmer::Config.import_swt_packages`](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/lib/ext/glimmer/config.rb) by using the `+=` operator (or alternatively limit to certain packages via `=` operator).
103
+
104
+ Example:
105
+
106
+ ```ruby
107
+ Glimmer::Config.import_swt_packages += [
108
+ 'org.eclipse.nebula.widgets.ganttchart'
109
+ ]
110
+ ```
111
+
112
+ Another alternative is to simply add a `java_import` call to your code (e.g. `java_import 'org.eclipse.nebula.widgets.ganttchart.GanttChart'`). Glimmer will automatically take advantage of it (e.g. when invoking `gantt_chart` keyword)
113
+
114
+ Nonetheless, you can disable automatic Java package import if needed via this Glimmer configuration option:
115
+
116
+ ```ruby
117
+ Glimmer::Config.import_swt_packages = false
118
+ ```
119
+
120
+ Once disabled, to import SWT Java packages manually, you may simply:
121
+
122
+ 1. `include Glimmer::SWT::Packages`: lazily imports all SWT Java packages to your class, lazy-loading SWT Java class constants on first reference.
123
+
124
+ 2. `java_import swt_package_class_string`: immediately imports a specific Java class where `swt_package_class_string` is the Java full package reference of a Java class (e.g. `java_import 'org.eclipse.swt.SWT'`)
125
+
126
+ Note: Glimmer relies on [`nested_imported_jruby_include_package`](https://github.com/AndyObtiva/nested_inherited_jruby_include_package), which automatically brings packages to nested-modules/nested-classes and sub-modules/sub-classes.
127
+
128
+ You can learn more about importing Java packages into Ruby code at this JRuby WIKI page:
129
+
130
+ https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby
131
+
132
+ ### loop_max_count
133
+
134
+ Glimmer has infinite loop detection support.
135
+ It can detect when an infinite loop is about to occur in method_missing and stops it.
136
+ It detects potential infinite loops when the same keyword and args repeat more than 100 times, which is unusual in a GUI app.
137
+
138
+ The max limit can be changed via the `Glimmer::Config::loop_max_count=(count)` config option.
139
+
140
+ Infinite loop detection may be disabled altogether if needed by setting `Glimmer::Config::loop_max_count` to `-1`
141
+
142
+ ### excluded_keyword_checkers
143
+
144
+ Glimmer permits consumers to exclude keywords from DSL processing by its engine via the `excluded_keyword_checkers` config option.
145
+
146
+ To do so, add a proc to it that returns a boolean indicating if a keyword is excluded or not.
147
+
148
+ Note that this proc runs within the context of the Glimmer object (as in the object mixing in the Glimmer module), so checker can can pretend to run there with its `self` object assumption.
149
+
150
+ Example of keywords excluded by [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt):
151
+
152
+ ```ruby
153
+ Glimmer::Config.excluded_keyword_checkers << lambda do |method_symbol, *args|
154
+ method = method_symbol.to_s
155
+ result = false
156
+ result ||= method.start_with?('on_swt_') && is_a?(Glimmer::UI::CustomWidget) && respond_to?(method)
157
+ result ||= method == 'dispose' && is_a?(Glimmer::UI::CustomWidget) && respond_to?(method)
158
+ result ||= ['drag_source_proxy', 'drop_target_proxy'].include?(method) && is_a?(Glimmer::UI::CustomWidget)
159
+ result ||= method == 'post_initialize_child'
160
+ result ||= method.end_with?('=')
161
+ result ||= ['finish_edit!', 'search', 'all_tree_items', 'depth_first_search'].include?(method) && is_a?(Glimmer::UI::CustomWidget) && body_root.respond_to?(method)
162
+ end
163
+ ```
164
+
165
+ ### log_excluded_keywords
166
+
167
+ (default = false)
168
+
169
+ This just tells Glimmer whether to log excluded keywords or not (at the debug level). It is off by default.
170
+
171
+ ### auto_sync_exec
172
+
173
+ (default = false)
174
+
175
+ This automatically uses sync_exec on GUI calls from threads other than the main GUI thread instead of requiring users to manually use sync_exec. Default value to false.
176
+
177
+ Keep in mind the caveat that it would force redraws on every minor changein the models instead of applying large scope changes all together, thus causing too much drawing/stutter in the GUI. As such, this is a good fit for simpler GUIs, not ones used with highly sophisticated 2D graphics. It may be mitigated in the future by introducing the idea of large-scale observation events that wrap around smaller events. Until then, keep the caveat in mind or just use sync_exec manually as usually done with Java SWT apps.
178
+
179
+ ## License
180
+
181
+ [MIT](LICENSE.txt)
182
+
183
+ Copyright (c) 2007-2021 - Andy Maleh.
@@ -0,0 +1,30 @@
1
+ ## Girb (Glimmer irb) Command
2
+
3
+ With `glimmer-dsl-swt` installed, you may want to run `girb` instead of standard `irb` to have SWT preloaded and the Glimmer library required and included for quick Glimmer coding/testing.
4
+
5
+ ```
6
+ girb
7
+ ```
8
+
9
+ ![GIRB](/images/glimmer-girb.png)
10
+
11
+ If you cloned [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt) project locally, you may run `bin/girb` instead.
12
+
13
+ ```
14
+ bin/girb
15
+ ```
16
+
17
+ Watch out for hands-on examples in this README indicated by "you may copy/paste in [`girb`](#girb-glimmer-irb-command)"
18
+
19
+ Keep in mind that all samples live under [https://github.com/AndyObtiva/glimmer-dsl-swt/samples](https://github.com/AndyObtiva/glimmer-dsl-swt/samples)
20
+
21
+ If you need a more GUI interactive option to experiement with Glimmer GUI DSL Syntax, you may try:
22
+ - [Glimmer Meta-Sample (The Sample of Samples)](#samples): allows launching Glimmer samples and viewing/editing code to learn/experiment too.
23
+ - ["Ugliest Editor Ever"](https://github.com/AndyObtiva/glimmer-cs-gladiator)
24
+ - Just build your own GUI editor using the [Glimmer DSL for SWT Ruby Gem](https://rubygems.org/gems/glimmer-dsl-swt).
25
+
26
+ ## License
27
+
28
+ [MIT](LICENSE.txt)
29
+
30
+ Copyright (c) 2007-2021 - Andy Maleh.
@@ -0,0 +1,3366 @@
1
+ ## Glimmer GUI DSL Syntax
2
+
3
+ This guide should help you get started with Glimmer DSL for SWT. For more advanced SWT details, please refer to the [SWT Reference](/README.md#swt-reference).
4
+
5
+ Glimmer's core is a GUI DSL with a lightweight visual syntax that makes it easy to visualize the nesting of widgets in the GUI hierarchy tree.
6
+
7
+ It is available through mixing in the `Glimmer` module, which makes [Glimmer GUI DSL Keywords](#glimmer-gui-dsl-keywords) available to both the instance scope and class scope:
8
+
9
+ ```ruby
10
+ include Glimmer
11
+ ```
12
+
13
+ For example, here is the basic "Hello, World!" sample code (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
14
+
15
+ ```ruby
16
+ include Glimmer
17
+
18
+ shell {
19
+ text "Glimmer"
20
+ label {
21
+ text "Hello, World!"
22
+ }
23
+ }.open
24
+ ```
25
+
26
+ The `include Glimmer` declaration on top mixed the `Glimmer` module into the Ruby global main object making the Glimmer GUI DSL available at the top-level global scope.
27
+
28
+ While this works well enough for mini-samples, it is better to isolate Glimmer in a class or module during production application development to create a clean separation between view code (GUI) and model code (business domain). Here is the "Hello, World!" sample re-written in a class to illustrate how mixing in the `Glimmer` module (via `include Glimmer`) makes the Glimmer GUI DSL available in both the instance scope and class scope. That is clearly demonstrated by pre-initializing a color constant in the class scope and building the GUI in the `#open` instance method (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
29
+
30
+ ```ruby
31
+ class HelloWorld
32
+ include Glimmer # makes the GUI DSL available in both the class scope and instance scope
33
+
34
+ COLOR_FOREGROUND_DEFAULT = rgb(255, 0, 0) # rgb is a GUI DSL keyword used in the class scope
35
+
36
+ def open
37
+ # the following are GUI DSL keywords (shell, text, and label) used in the instance scope
38
+ shell {
39
+ text "Glimmer"
40
+ label {
41
+ text "Hello, World!"
42
+ foreground COLOR_FOREGROUND_DEFAULT
43
+ }
44
+ }.open
45
+ end
46
+ end
47
+
48
+ HelloWorld.new.open
49
+ ```
50
+
51
+ This renders "Hello, World!" with a red foreground color:
52
+
53
+ ![Hello World Red Foreground Color](/images/glimmer-hello-world-red-foreground-color.png)
54
+
55
+ The GUI DSL intentionally avoids overly verbose syntax, requiring as little declarative code as possible to describe what GUI to render, how to style it, and what properties to data-bind to the Models.
56
+
57
+ As such, it breaks off from Ruby's convention of using `do end` for multi-line blocks, opting instead for the lightweight and visual `{ }` curly brace blocks everywhere inside the GUI DSL. More details about Glimmer's syntax conventions may be found in the [Glimmer Style Guide](GLIMMER_STYLE_GUIDE.md)
58
+
59
+ Glimmer DSL syntax consists mainly of:
60
+ - keywords (e.g. `table` for a table widget)
61
+ - style/args (e.g. :multi as in `table(:multi)` for a multi-line selection table widget)
62
+ - content (e.g. `{ table_column { text 'Name'} }` as in `table(:multi) { table_column { text 'name'} }` for a multi-line selection table widget with a table column having header text property `'Name'` as content)
63
+
64
+ ### DSL Auto-Expansion
65
+
66
+ Glimmer supports a new and radical Ruby DSL concept called DSL Auto-Expansion. To explain, let's first mention the two types of Glimmer GUI DSL keywords: static and dynamic.
67
+
68
+ Static keywords are pre-identified keywords in the Glimmer DSL, such as `shell`, `display`, `message_box`, `async_exec`, `sync_exec`, and `bind`.
69
+
70
+ Dynamic keywords are dynamically figured out from currently imported (aka required/loaded) SWT widgets and custom widgets. Examples are: `label`, `combo`, and `list` for SWT widgets and `c_date_time`, `video`, and `gantt_chart` for custom widgets.
71
+
72
+ The only reason to distinguish between the two is to realize that importing new Glimmer [custom widgets](#custom-widgets) and Java SWT custom widget libraries automatically expands Glimmer's DSL vocabulary with new dynamic keywords.
73
+
74
+ For example, if a project adds this custom Java SWT library from the [Nebula Project](https://www.eclipse.org/nebula/):
75
+
76
+ https://www.eclipse.org/nebula/widgets/gallery/gallery.php
77
+
78
+ Glimmer will automatically support using the keyword `gallery`
79
+
80
+ This is what DSL Auto-Expansion is.
81
+
82
+ You will learn more about widgets next.
83
+
84
+ ### Widgets
85
+
86
+ Glimmer GUIs (user interfaces) are modeled with widgets, which are wrappers around the SWT library widgets found here:
87
+
88
+ https://www.eclipse.org/swt/widgets/
89
+
90
+ This screenshot taken from the link above should give a glimpse of how SWT widgets look and feel:
91
+
92
+ [![SWT Widgets](/images/glimmer-swt-widgets.png)](https://www.eclipse.org/swt/widgets/)
93
+
94
+ In Glimmer DSL, widgets are declared with lowercase underscored names mirroring their SWT names minus the package name. For example, here are some Glimmer widgets and their SWT counterparts:
95
+
96
+ - `shell` instantiates `org.eclipse.swt.widgets.Shell`
97
+ - `text` instantiates `org.eclipse.swt.widgets.Text`
98
+ - `button` instantiates `org.eclipse.swt.widgets.Button`
99
+ - `label` instantiates `org.eclipse.swt.widgets.Label`
100
+ - `composite` instantiates `org.eclipse.swt.widgets.Composite`
101
+ - `tab_folder` instantiates `org.eclipse.swt.widgets.TabFolder`
102
+ - `tab_item` instantiates `org.eclipse.swt.widgets.TabItem`
103
+ - `table` instantiates `org.eclipse.swt.widgets.Table`
104
+ - `table_column` instantiates `org.eclipse.swt.widgets.TableColumn`
105
+ - `tree` instantiates `org.eclipse.swt.widgets.Tree`
106
+ - `combo` instantiates `org.eclipse.swt.widgets.Combo`
107
+ - `list` instantiates `org.eclipse.swt.widgets.List`
108
+
109
+ Every **widget** is sufficiently declared by name, but may optionally be accompanied with:
110
+ - SWT **style**/***arguments*** wrapped by parenthesis according to [Glimmer Style Guide](GLIMMER_STYLE_GUIDE.md) (see [next section](#widget-styles) for details).
111
+ - Ruby block containing **content**, which may be **properties** (e.g. `enabled false`) or nested **widgets** (e.g. `table_column` nested inside `table`)
112
+
113
+ For example, if we were to revisit `samples/hello/hello_world.rb` above (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
114
+
115
+ ```ruby
116
+ shell {
117
+ text "Glimmer"
118
+ label {
119
+ text "Hello, World!"
120
+ }
121
+ }.open
122
+ ```
123
+
124
+ Note that `shell` instantiates the outer shell **widget**, in other words, the window that houses all of the desktop graphical user interface.
125
+
126
+ `shell` is then followed by a ***block*** that contains
127
+
128
+ ```ruby
129
+ # ...
130
+ text "Glimmer" # text property of shell
131
+ label { # label widget declaration as content of shell
132
+ text "Hello, World!" # text property of label
133
+ }
134
+ # ...
135
+ ```
136
+
137
+ The first line declares a **property** called `text`, which sets the title of the shell (window) to `"Glimmer"`. **Properties** always have ***arguments*** (not wrapped by parenthesis according to [Glimmer Style Guide](GLIMMER_STYLE_GUIDE.md)), such as the text `"Glimmer"` in this case, and do **NOT** have a ***block*** (this distinguishes them from **widget** declarations).
138
+
139
+ The second line declares the `label` **widget**, which is followed by a Ruby **content** ***block*** that contains its `text` **property** with value `"Hello, World!"`
140
+
141
+ The **widget** ***block*** may optionally receive an argument representing the widget proxy object that the block content is for. This is useful in rare cases when the content code needs to refer to parent widget during declaration. You may leave that argument out most of the time and only add when absolutely needed.
142
+
143
+ Example:
144
+
145
+ ```ruby
146
+ shell {|shell_proxy|
147
+ #...
148
+ }
149
+ ```
150
+
151
+ Remember that The `shell` widget is always the outermost widget containing all others in a Glimmer desktop windowed application.
152
+
153
+ After it is declared, a `shell` must be opened with the `#open` method, which can be called on the block directly as in the example above, or by capturing `shell` in a `@shell` variable (shown in example below), and calling `#open` on it independently (recommended in actual apps)
154
+
155
+ ```ruby
156
+ @shell = shell {
157
+ # properties and content
158
+ # ...
159
+ }
160
+ @shell.open
161
+ ```
162
+
163
+ It is centered upon initial display and has a minimum width of 130 (can be re-centered when needed with `@shell.center` method after capturing `shell` in a `@shell` variable as per samples)
164
+
165
+ Check out the [samples](samples) directory for more examples.
166
+
167
+ Example from [hello_tab.rb](samples/hello/hello_tab.rb) sample (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
168
+
169
+ ![Hello Tab English](/images/glimmer-hello-tab-english.png)
170
+
171
+ ![Hello Tab French](/images/glimmer-hello-tab-french.png)
172
+
173
+ ```ruby
174
+ shell {
175
+ text "Hello, Tab!"
176
+ tab_folder {
177
+ tab_item {
178
+ text "English"
179
+ label {
180
+ text "Hello, World!"
181
+ }
182
+ }
183
+ tab_item {
184
+ text "French"
185
+ label {
186
+ text "Bonjour Univers!"
187
+ }
188
+ }
189
+ }
190
+ }.open
191
+ ```
192
+
193
+ If you are new to Glimmer, you have learned enough to start running some [samples](#samples) directly or by reading through [Glimmer GUI DSL Keywords](#glimmer-gui-dsl-keywords) (which list each keyword's samples). Go ahead and run all Glimmer [samples](#samples), and come back to read the rest in any order you like since this material is more organized like a reference.
194
+
195
+ If you are advanced and need more widgets, check out the [Nebula Project](https://www.eclipse.org/nebula/) for an extensive list of high quality custom widgets:
196
+
197
+ https://www.eclipse.org/nebula/
198
+
199
+ #### Glimmer GUI DSL Keywords
200
+
201
+ This is not an exaustive list, but should give you a good start in learning Glimmer GUI DSL keywords, keeping in mind that the full list can be derived from the [SWT documentation](https://www.eclipse.org/swt/widgets/). More will be explained in the following sections.
202
+
203
+ **Widgets:**
204
+ - `button`: featured in [Hello, Checkbox!](#hello-checkbox) / [Hello, Button!](#hello-button) / [Hello, Table!](#hello-table) / [Hello, Radio Group!](#hello-radio-group) / [Hello, Radio!](#hello-radio) / [Hello, Message Box!](#hello-message-box) / [Hello, List Single Selection!](#hello-list-single-selection) / [Hello, List Multi Selection!](#hello-list-multi-selection) / [Hello, Group!](#hello-group) / [Hello, Combo!](#hello-combo) / [Hello, Checkbox Group!](#hello-checkbox-group) / [Contact Manager](#contact-manager) / [Tic Tac Toe](#tic-tac-toe) / [Login](#login)
205
+ - `browser`: featured in [Hello, Browser!](#hello-browser)
206
+ - `calendar`: featured in [Hello, Date Time!](#hello-date-time)
207
+ - `checkbox`: featured in [Hello, Checkbox Group!](#hello-checkbox-group) / [Hello, Checkbox!](#hello-checkbox)
208
+ - `checkbox_group`: featured in [Hello, Checkbox Group!](#hello-checkbox-group)
209
+ - `combo`: featured in [Hello, Table!](#hello-table) / [Hello, Combo!](#hello-combo)
210
+ - `composite`: featured in [Hello, Radio!](#hello-radio) / [Hello, Computed!](#hello-computed) / [Hello, Checkbox!](#hello-checkbox) / [Tic Tac Toe](#tic-tac-toe) / [Login](#login) / [Contact Manager](#contact-manager)
211
+ - `date`: featured in [Hello, Table!](#hello-table) / [Hello, Date Time!](#hello-date-time) / [Hello, Custom Shell!](#hello-custom-shell) / [Tic Tac Toe](#tic-tac-toe)
212
+ - `date_drop_down`: featured in [Hello, Table!](#hello-table) / [Hello, Date Time!](#hello-date-time)
213
+ - `group`: featured in [Hello, Group!](#hello-group) / [Contact Manager](#contact-manager)
214
+ - `label`: featured in [Hello, Computed!](#hello-computed) / [Hello, Checkbox Group!](#hello-checkbox-group) / [Hello, Checkbox!](#hello-checkbox) / [Hello, World!](#hello-world) / [Hello, Table!](#hello-table) / [Hello, Tab!](#hello-tab) / [Hello, Radio Group!](#hello-radio-group) / [Hello, Radio!](#hello-radio) / [Hello, Pop Up Context Menu!](#hello-pop-up-context-menu) / [Hello, Menu Bar!](#hello-menu-bar) / [Hello, Date Time!](#hello-date-time) / [Hello, Custom Widget!](#hello-custom-widget) / [Hello, Custom Shell!](#hello-custom-shell) / [Contact Manager](#contact-manager) / [Login](#login)
215
+ - `list` (w/ optional `:multi` SWT style): featured in [Hello, List Single Selection!](#hello-list-single-selection) / [Hello, List Multi Selection!](#hello-list-multi-selection) / [Contact Manager](#contact-manager)
216
+ - `menu`: featured in [Hello, Menu Bar!](#hello-menu-bar) / [Hello, Pop Up Context Menu!](#hello-pop-up-context-menu) / [Hello, Table!](#hello-table)
217
+ - `menu_bar`: featured in [Hello, Menu Bar!](#hello-menu-bar)
218
+ - `menu_item`: featured in [Hello, Table!](#hello-table) / [Hello, Pop Up Context Menu!](#hello-pop-up-context-menu) / [Hello, Menu Bar!](#hello-menu-bar)
219
+ - `message_box`: featured in [Hello, Table!](#hello-table) / [Hello, Pop Up Context Menu!](#hello-pop-up-context-menu) / [Hello, Message Box!](#hello-message-box) / [Hello, Menu Bar!](#hello-menu-bar)
220
+ - `radio`: featured in [Hello, Radio!](#hello-radio) / [Hello, Group!](#hello-group)
221
+ - `radio_group`: featured in [Hello, Radio Group!](#hello-radio-group)
222
+ - `scrolled_composite`
223
+ - `shell`: featured in [Hello, Checkbox!](#hello-checkbox) / [Hello, Button!](#hello-button) / [Hello, Table!](#hello-table) / [Hello, Tab!](#hello-tab) / [Hello, Radio Group!](#hello-radio-group) / [Hello, Radio!](#hello-radio) / [Hello, List Single Selection!](#hello-list-single-selection) / [Hello, List Multi Selection!](#hello-list-multi-selection) / [Hello, Group!](#hello-group) / [Hello, Date Time!](#hello-date-time) / [Hello, Custom Shell!](#hello-custom-shell) / [Hello, Computed!](#hello-computed) / [Hello, Combo!](#hello-combo) / [Hello, Checkbox Group!](#hello-checkbox-group) / [Contact Manager](#contact-manager) / [Tic Tac Toe](#tic-tac-toe) / [Login](#login)
224
+ - `tab_folder`: featured in [Hello, Tab!](#hello-tab)
225
+ - `tab_item`: featured in [Hello, Tab!](#hello-tab)
226
+ - `table`: featured in [Hello, Custom Shell!](#hello-custom-shell) / [Hello, Table!](#hello-table) / [Contact Manager](#contact-manager)
227
+ - `table_column`: featured in [Hello, Table!](#hello-table) / [Hello, Custom Shell!](#hello-custom-shell) / [Contact Manager](#contact-manager)
228
+ - `text`: featured in [Hello, Computed!](#hello-computed) / [Login](#login) / [Contact Manager](#contact-manager)
229
+ - `time`: featured in [Hello, Table!](#hello-table) / [Hello, Date Time!](#hello-date-time)
230
+ - Glimmer::UI::CustomWidget: ability to define any keyword as a custom widget - featured in [Hello, Custom Widget!](#hello-custom-widget)
231
+ - Glimmer::UI::CustomShell: ability to define any keyword as a custom shell (aka custom window) that opens in a new browser window (tab) automatically unless there is no shell open in the current browser window (tab) - featured in [Hello, Custom Shell!](#hello-custom-shell)
232
+
233
+ **Layouts:**
234
+ - `grid_layout`: featured in [Hello, Custom Shell!](#hello-custom-shell) / [Hello, Computed!](#hello-computed) / [Hello, Table!](#hello-table) / [Hello, Pop Up Context Menu!](#hello-pop-up-context-menu) / [Hello, Menu Bar!](#hello-menu-bar) / [Hello, List Single Selection!](#hello-list-single-selection) / [Hello, List Multi Selection!](#hello-list-multi-selection) / [Contact Manager](#contact-manager) / [Login](#login) / [Tic Tac Toe](#tic-tac-toe)
235
+ - `row_layout`: featured in [Hello, Radio Group!](#hello-radio-group) / [Hello, Radio!](#hello-radio) / [Hello, Group!](#hello-group) / [Hello, Date Time!](#hello-date-time) / [Hello, Combo!](#hello-combo) / [Hello, Checkbox Group!](#hello-checkbox-group) / [Hello, Checkbox!](#hello-checkbox) / [Contact Manager](#contact-manager)
236
+ - `fill_layout`: featured in [Hello, Custom Widget!](#hello-custom-widget)
237
+ - `layout_data`: featured in [Hello, Table!](#hello-table) / [Hello, Custom Shell!](#hello-custom-shell) / [Hello, Computed!](#hello-computed) / [Tic Tac Toe](#tic-tac-toe) / [Contact Manager](#contact-manager)
238
+
239
+ **Graphics/Style:**
240
+ - `color`: featured in [Hello, Custom Widget!](#hello-custom-widget) / [Hello, Menu Bar!](#hello-menu-bar)
241
+ - `font`: featured in [Hello, Checkbox Group!](#hello-checkbox-group) / [Hello, Checkbox!](#hello-checkbox) / [Hello, Table!](#hello-table) / [Hello, Radio Group!](#hello-radio-group) / [Hello, Radio!](#hello-radio) / [Hello, Pop Up Context Menu!](#hello-pop-up-context-menu) / [Hello, Menu Bar!](#hello-menu-bar) / [Hello, Group!](#hello-group) / [Hello, Date Time!](#hello-date-time) / [Hello, Custom Widget!](#hello-custom-widget) / [Hello, Custom Shell!](#hello-custom-shell) / [Contact Manager](#contact-manager) / [Tic Tac Toe](#tic-tac-toe)
242
+ - `Point` class used in setting location on widgets
243
+ - `swt` and `SWT` class to set SWT styles on widgets - featured in [Hello, Custom Shell!](#hello-custom-shell) / [Login](#login) / [Contact Manager](#contact-manager)
244
+
245
+ **Data-Binding/Observers:**
246
+ - `bind`: featured in [Hello, Computed!](#hello-computed) / [Hello, Combo!](#hello-combo) / [Hello, Checkbox Group!](#hello-checkbox-group) / [Hello, Checkbox!](#hello-checkbox) / [Hello, Button!](#hello-button) / [Hello, Table!](#hello-table) / [Hello, Radio Group!](#hello-radio-group) / [Hello, Radio!](#hello-radio) / [Hello, List Single Selection!](#hello-list-single-selection) / [Hello, List Multi Selection!](#hello-list-multi-selection) / [Hello, Group!](#hello-group) / [Hello, Date Time!](#hello-date-time) / [Hello, Custom Widget!](#hello-custom-widget) / [Hello, Custom Shell!](#hello-custom-shell) / [Login](#login) / [Contact Manager](#contact-manager) / [Tic Tac Toe](#tic-tac-toe)
247
+ - `observe`: featured in [Hello, Table!](#hello-table) / [Tic Tac Toe](#tic-tac-toe)
248
+ - `on_widget_selected`: featured in [Hello, Combo!](#hello-combo) / [Hello, Checkbox Group!](#hello-checkbox-group) / [Hello, Checkbox!](#hello-checkbox) / [Hello, Button!](#hello-button) / [Hello, Table!](#hello-table) / [Hello, Radio Group!](#hello-radio-group) / [Hello, Radio!](#hello-radio) / [Hello, Pop Up Context Menu!](#hello-pop-up-context-menu) / [Hello, Message Box!](#hello-message-box) / [Hello, Menu Bar!](#hello-menu-bar) / [Hello, List Single Selection!](#hello-list-single-selection) / [Hello, List Multi Selection!](#hello-list-multi-selection) / [Hello, Group!](#hello-group) / [Contact Manager](#contact-manager) / [Login](#login) / [Tic Tac Toe](#tic-tac-toe)
249
+ - `on_modify_text`
250
+ - `on_key_pressed` (and SWT alias `on_swt_keydown`) - featured in [Login](#login) / [Contact Manager](#contact-manager)
251
+ - `on_key_released` (and SWT alias `on_swt_keyup`)
252
+ - `on_mouse_down` (and SWT alias `on_swt_mousedown`)
253
+ - `on_mouse_up` (and SWT alias `on_swt_mouseup`) - featured in [Hello, Custom Shell!](#hello-custom-shell) / [Contact Manager](#contact-manager)
254
+
255
+ **Event loop:**
256
+ - `display`: featured in [Tic Tac Toe](#tic-tac-toe)
257
+ - `async_exec`: featured in [Hello, Custom Widget!](#hello-custom-widget) / [Hello, Custom Shell!](#hello-custom-shell)
258
+ - `sync_exec`: executes a block on the event loop synchronously (usually from another thread)
259
+ - `timer_exec`: executes a block after a delay of time has passed
260
+
261
+ #### SWT Proxies
262
+
263
+ Glimmer follows Proxy Design Pattern by having Ruby proxy wrappers for all SWT objects:
264
+ - `Glimmer::SWT:WidgetProxy` wraps all descendants of `org.eclipse.swt.widgets.Widget` except the ones that have their own wrappers.
265
+ - `Glimmer::SWT::ShellProxy` wraps `org.eclipse.swt.widgets.Shell`
266
+ - `Glimmer::SWT:TabItemProxy` wraps `org.eclipse.swt.widget.TabItem` (also adds a composite to enable adding content under tab items directly in Glimmer)
267
+ - `Glimmer::SWT:LayoutProxy` wraps all descendants of `org.eclipse.swt.widget.Layout`
268
+ - `Glimmer::SWT:LayoutDataProxy` wraps all layout data objects
269
+ - `Glimmer::SWT:DisplayProxy` wraps `org.eclipse.swt.widget.Display` (manages displaying GUI)
270
+ - `Glimmer::SWT:ColorProxy` wraps `org.eclipse.swt.graphics.Color`
271
+ - `Glimmer::SWT:FontProxy` wraps `org.eclipse.swt.graphics.Font`
272
+ - `Glimmer::SWT::WidgetListenerProxy` wraps all widget listeners
273
+
274
+ These proxy objects have an API and provide some convenience methods, some of which are mentioned below.
275
+
276
+ ##### swt_widget
277
+
278
+ Glimmer SWT proxies come with the instance method `#swt_widget`, which returns the actual SWT `Widget` object wrapped by the Glimmer widget proxy. It is useful in cases you'd like to do some custom SWT programming outside of Glimmer.
279
+
280
+ ##### Shell widget proxy methods
281
+
282
+ Shell widget proxy has extra methods specific to SWT Shell:
283
+ - `#open`: Opens the shell, making it visible and active, and starting the SWT Event Loop (you may learn more about it here: https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Display.html). If shell was already open, but hidden, it makes the shell visible.
284
+ - `#show`: Alias for `#open`
285
+ - `#hide`: Hides a shell setting "visible" property to false
286
+ - `#close`: Closes the shell
287
+ - `#center`: Centers the shell within monitor it is in
288
+ - `#start_event_loop`: (happens as part of `#open`) Starts SWT Event Loop (you may learn more about it here: https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Display.html). This method is not needed except in rare circumstances where there is a need to start the SWT Event Loop before opening the shell.
289
+ - `#visible?`: Returns whether a shell is visible
290
+ - `#opened_before?`: Returns whether a shell has been opened at least once before (additionally implying the SWT Event Loop has been started already)
291
+ - `#visible=`: Setting to true opens/shows shell. Setting to false hides the shell.
292
+ - `#pack`: Packs contained widgets using SWT's `Shell#pack` method
293
+ - `#pack_same_size`: Packs contained widgets without changing shell's size when widget sizes change
294
+
295
+ ##### Widget Content Block
296
+
297
+ Glimmer allows re-opening any widget and adding properties or extra content after it has been constructed already by using the `#content` method.
298
+
299
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
300
+
301
+ ```ruby
302
+ @shell = shell {
303
+ text "Application"
304
+ row_layout
305
+ @label1 = label {
306
+ text "Hello,"
307
+ }
308
+ }
309
+ @shell.content {
310
+ minimum_size 130, 130
311
+ label {
312
+ text "World!"
313
+ }
314
+ }
315
+ @label1.content {
316
+ foreground :red
317
+ }
318
+ @shell.open
319
+ ```
320
+
321
+ ##### Shell Icon
322
+
323
+ To set the shell icon, simply set the `image` property under the `shell` widget. This shows up in the operating system toolbar and app-switcher (e.g. CMD+TAB) (and application window top-left corner in Windows)
324
+
325
+ Example:
326
+
327
+ ```ruby
328
+ shell {
329
+ # ...
330
+ image 'path/to/image.png'
331
+ # ...
332
+ }
333
+ ```
334
+
335
+ ###### Shell Icon Tip for Packaging on Windows
336
+
337
+ When setting shell icon for a [packaged](#packaging--distribution) app, which has a JAR file at its core, you can reference the `ico` file that ships with the app by going one level up (e.g. `'../AppName.ico'`)
338
+
339
+ #### Dialog
340
+
341
+ Dialog is a variation on Shell. It is basically a shell that is modal (blocks what's behind it) and belongs to another shell. It only has a close button.
342
+
343
+ Glimmer facilitates building dialogs by using the `dialog` keyword, which automatically adds the SWT.DIALOG_TRIM and SWT.APPLICATION_MODAL [widget styles](#widget-styles) needed for a dialog.
344
+
345
+ Check out [Hello, Dialog!](#hello-dialog) sample to learn more.
346
+
347
+ ##### message_box
348
+
349
+ The Glimmer DSL `message_box` keyword is similar to `shell` and `dialog`, but renders a modal dialog with a title `text` property, main body `message` property, and dismissal button(s) only (OK button by default or [more options](https://www.eclipse.org/swt/javadoc.php)). It may also be opened via the `#open` method.
350
+
351
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
352
+
353
+ ```ruby
354
+ include Glimmer
355
+
356
+ @shell = shell {
357
+ text 'Hello, Message Box!'
358
+ button {
359
+ text 'Please Click To Win a Surprise'
360
+ on_widget_selected {
361
+ message_box(@shell) {
362
+ text 'Surprise'
363
+ message "Congratulations!\n\nYou have won $1,000,000!"
364
+ }.open
365
+ }
366
+ }
367
+ }
368
+ @shell.open
369
+ ```
370
+
371
+ ![Hello Message Box Dialog](/images/glimmer-hello-message-box-dialog.png)
372
+
373
+ It is also possible to use `message_box` even before instantiating the first `shell` ([Glimmer](https://rubygems.org/gems/glimmer) builds a throwaway `shell` parent automatically for it):
374
+
375
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
376
+
377
+ ```ruby
378
+ include Glimmer
379
+
380
+ message_box {
381
+ text 'Greeting'
382
+ message "Hello, World!"
383
+ }.open
384
+ ```
385
+
386
+ #### Display
387
+
388
+ The SWT `Display` class is a singleton in Glimmer. It is used in SWT to represent your display device, allowing you to manage GUI globally
389
+ and access available monitors. Additionally, it is responsible for the SWT event loop, which runs on the first thread the Glimmer application starts on. In multi-threaded programming, `Display` provides the methods `async_exec` and `sync_exec` to enable enqueuing GUI changes asynchronously or synchronously from threads other than the main (first) thread since direct GUI changes are forbidden from other threads by design.
390
+
391
+ `Display` is automatically instantiated upon first instantiation of a `shell` widget.
392
+
393
+ Alternatively, for advanced use cases, a `Display` can be created explicitly with the Glimmer `display` keyword. When a `shell` is later declared, it
394
+ automatically uses the `display` created earlier without having to explicitly hook it.
395
+
396
+ ```ruby
397
+ @display = display {
398
+ cursor_location 300, 300
399
+ on_swt_keydown {
400
+ # ...
401
+ }
402
+ # ...
403
+ }
404
+ @shell = shell { # uses display created above
405
+ }
406
+ ```
407
+ The benefit of instantiating an SWT Display explicitly is to set [Properties](#widget-properties) or [Observers](#observer).
408
+ Although SWT Display is not technically a widget, it has similar APIs and DSL support.
409
+
410
+ #### Multi-Threading
411
+
412
+ [JRuby](https://www.jruby.org/) supports [truly parallel multi-threading](https://github.com/jruby/jruby/wiki/Concurrency-in-jruby) since it relies on the JVM (Java Virtual Machine). As such, it enables development of highly-interactive desktop applications that can do background work while the user is interacting with the GUI. However, any code that interacts with the GUI from a thread other than the main (first) GUI thread must do so only through sync_exec (if it is standard synchronous code) or async_exec.
413
+
414
+ ##### async_exec
415
+
416
+ `async_exec {}` is a Glimmer DSL keyword in addition to being a method on `display`. It accepts a block and when invoked, adds the block to the end of a queue of GUI events scheduled to run on the SWT event loop, executing asynchronously.
417
+
418
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
419
+
420
+ ```
421
+ @shell = shell {
422
+ text 'Glimmer'
423
+ @label = label {
424
+ text 'Hello, World!'
425
+ }
426
+ }
427
+
428
+ Thread.new {
429
+ [:red, :dark_green, :blue].cycle { |color|
430
+ async_exec {
431
+ @label.content {
432
+ foreground color if @shell.visible?
433
+ }
434
+ }
435
+ sleep(1)
436
+ }
437
+ }
438
+
439
+ @shell.open
440
+ ```
441
+
442
+ ##### sync_exec
443
+
444
+ `sync_exec {}` works just like `async_exec` except it executes the block synchronously at the earliest opportunity possible, waiting for the block to be finished.
445
+
446
+ ##### timer_exec
447
+
448
+ `timer_exec(delay_in_milliseconds) {}` works just like `async_exec` except it executes the block after a delay has elapsed.
449
+
450
+ #### Menus
451
+
452
+ Glimmer DSL provides support for SWT Menu and MenuItem widgets.
453
+
454
+ There are 2 main types of menus in SWT:
455
+ - Menu Bar (shows up on top)
456
+ - Pop Up Context Menu (shows up when right-clicking a widget)
457
+
458
+ Underneath both types, there can be a 3rd menu type called Drop Down.
459
+
460
+ Glimmer provides special support for Drop Down menus as it automatically instantiates associated Cascade menu items and wires together with proper parenting, swt styles, and calling setMenu.
461
+
462
+ The ampersand symbol indicates the keyboard shortcut key for the menu item (e.g. '&Help' can be triggered on Windows by hitting ALT+H)
463
+
464
+ Example of a Menu Bar (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
465
+
466
+ ```ruby
467
+ include Glimmer
468
+
469
+ COLORS = [:white, :red, :yellow, :green, :blue, :magenta, :gray, :black]
470
+
471
+ shell {
472
+ grid_layout {
473
+ margin_width 0
474
+ margin_height 0
475
+ }
476
+
477
+ text 'Hello, Menu Bar!'
478
+
479
+ @label = label(:center) {
480
+ font height: 50
481
+ text 'Check Out The Menu Bar Above!'
482
+ }
483
+
484
+ menu_bar {
485
+ menu {
486
+ text '&File'
487
+ menu_item {
488
+ text '&New'
489
+ accelerator :command, :N
490
+
491
+ on_widget_selected {
492
+ message_box {
493
+ text 'New'
494
+ message 'New file created.'
495
+ }.open
496
+ }
497
+ }
498
+ menu_item {
499
+ text '&Open...'
500
+ accelerator :command, :O
501
+
502
+ on_widget_selected {
503
+ message_box {
504
+ text 'Open'
505
+ message 'Opening File...'
506
+ }.open
507
+ }
508
+ }
509
+ menu {
510
+ text 'Open &Recent'
511
+ menu_item {
512
+ text 'File 1'
513
+ on_widget_selected {
514
+ message_box {
515
+ text 'File 1'
516
+ message 'File 1 Contents'
517
+ }.open
518
+ }
519
+ }
520
+ menu_item {
521
+ text 'File 2'
522
+ on_widget_selected {
523
+ message_box {
524
+ text 'File 2'
525
+ message 'File 2 Contents'
526
+ }.open
527
+ }
528
+ }
529
+ }
530
+ menu_item(:separator)
531
+ menu_item {
532
+ text 'E&xit'
533
+
534
+ on_widget_selected {
535
+ exit(0)
536
+ }
537
+ }
538
+ }
539
+ menu {
540
+ text '&Edit'
541
+ menu_item {
542
+ text 'Cut'
543
+ accelerator :command, :X
544
+ }
545
+ menu_item {
546
+ text 'Copy'
547
+ accelerator :command, :C
548
+ }
549
+ menu_item {
550
+ text 'Paste'
551
+ accelerator :command, :V
552
+ }
553
+ }
554
+ menu {
555
+ text '&Options'
556
+
557
+ menu_item(:radio) {
558
+ text '&Enabled'
559
+
560
+ on_widget_selected {
561
+ @select_one_menu.enabled = true
562
+ @select_multiple_menu.enabled = true
563
+ }
564
+ }
565
+ @select_one_menu = menu {
566
+ text '&Select One'
567
+ enabled false
568
+
569
+ menu_item(:radio) {
570
+ text 'Option 1'
571
+ }
572
+ menu_item(:radio) {
573
+ text 'Option 2'
574
+ }
575
+ menu_item(:radio) {
576
+ text 'Option 3'
577
+ }
578
+ }
579
+ @select_multiple_menu = menu {
580
+ text '&Select Multiple'
581
+ enabled false
582
+
583
+ menu_item(:check) {
584
+ text 'Option 4'
585
+ }
586
+ menu_item(:check) {
587
+ text 'Option 5'
588
+ }
589
+ menu_item(:check) {
590
+ text 'Option 6'
591
+ }
592
+ }
593
+ }
594
+ menu {
595
+ text '&Format'
596
+ menu {
597
+ text '&Background Color'
598
+ COLORS.each { |color_style|
599
+ menu_item(:radio) {
600
+ text color_style.to_s.split('_').map(&:capitalize).join(' ')
601
+
602
+ on_widget_selected {
603
+ @label.background = color_style
604
+ }
605
+ }
606
+ }
607
+ }
608
+ menu {
609
+ text 'Foreground &Color'
610
+ COLORS.each { |color_style|
611
+ menu_item(:radio) {
612
+ text color_style.to_s.split('_').map(&:capitalize).join(' ')
613
+
614
+ on_widget_selected {
615
+ @label.foreground = color_style
616
+ }
617
+ }
618
+ }
619
+ }
620
+ }
621
+ menu {
622
+ text '&View'
623
+ menu_item(:radio) {
624
+ text 'Small'
625
+
626
+ on_widget_selected {
627
+ @label.font = {height: 25}
628
+ @label.parent.pack
629
+ }
630
+ }
631
+ menu_item(:radio) {
632
+ text 'Medium'
633
+ selection true
634
+
635
+ on_widget_selected {
636
+ @label.font = {height: 50}
637
+ @label.parent.pack
638
+ }
639
+ }
640
+ menu_item(:radio) {
641
+ text 'Large'
642
+
643
+ on_widget_selected {
644
+ @label.font = {height: 75}
645
+ @label.parent.pack
646
+ }
647
+ }
648
+ }
649
+ menu {
650
+ text '&Help'
651
+ menu_item {
652
+ text '&Manual'
653
+ accelerator :command, :shift, :M
654
+
655
+ on_widget_selected {
656
+ message_box {
657
+ text 'Manual'
658
+ message 'Manual Contents'
659
+ }.open
660
+ }
661
+ }
662
+ menu_item {
663
+ text '&Tutorial'
664
+ accelerator :command, :shift, :T
665
+
666
+ on_widget_selected {
667
+ message_box {
668
+ text 'Tutorial'
669
+ message 'Tutorial Contents'
670
+ }.open
671
+ }
672
+ }
673
+ menu_item(:separator)
674
+ menu_item {
675
+ text '&Report an Issue...'
676
+
677
+ on_widget_selected {
678
+ message_box {
679
+ text 'Report an Issue'
680
+ message 'Reporting an issue...'
681
+ }.open
682
+ }
683
+ }
684
+ }
685
+ }
686
+ }.open
687
+ ```
688
+
689
+ Example of a Pop Up Context Menu (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
690
+
691
+ ```ruby
692
+ include Glimmer
693
+
694
+ shell {
695
+ grid_layout {
696
+ margin_width 0
697
+ margin_height 0
698
+ }
699
+
700
+ text 'Hello, Pop Up Context Menu!'
701
+
702
+ label {
703
+ text "Right-Click on the Text to\nPop Up a Context Menu"
704
+ font height: 50
705
+
706
+ menu {
707
+ menu {
708
+ text '&History'
709
+ menu {
710
+ text '&Recent'
711
+ menu_item {
712
+ text 'File 1'
713
+ on_widget_selected {
714
+ message_box {
715
+ text 'File 1'
716
+ message 'File 1 Contents'
717
+ }.open
718
+ }
719
+ }
720
+ menu_item {
721
+ text 'File 2'
722
+ on_widget_selected {
723
+ message_box {
724
+ text 'File 2'
725
+ message 'File 2 Contents'
726
+ }.open
727
+ }
728
+ }
729
+ }
730
+ menu {
731
+ text '&Archived'
732
+ menu_item {
733
+ text 'File 3'
734
+ on_widget_selected {
735
+ message_box {
736
+ text 'File 3'
737
+ message 'File 3 Contents'
738
+ }.open
739
+ }
740
+ }
741
+ menu_item {
742
+ text 'File 4'
743
+ on_widget_selected {
744
+ message_box {
745
+ text 'File 4'
746
+ message 'File 4 Contents'
747
+ }.open
748
+ }
749
+ }
750
+ }
751
+ }
752
+ }
753
+ }
754
+ }.open
755
+ ```
756
+
757
+ #### ScrolledComposite
758
+
759
+ Glimmer provides smart defaults for the `scrolled_composite` widget by:
760
+ - Automatically setting the nested widget as its content (meaning use can just like a plain old `composite` to add scrolling)
761
+ - Automatically setting the :h_scroll and :v_scroll SWT styles (can be set manually if only one of either :h_scroll or :v_scroll is desired )
762
+ - Automatically setting the expand horizontal and expand vertical SWT properties to `true`
763
+
764
+ #### Sash Form Widget
765
+
766
+ `sash_form` is an SWT built-in custom widget that provides a resizable sash that splits a window area into two or more panes.
767
+
768
+ It can be customized with the `weights` attribute by setting initial weights to size the panes at first display.
769
+
770
+ One noteworthy thing about the Glimmer implementation is that, unlike behavior in SWT, it allows declaring `weights` before the content of the `sash_form`, thus providing more natural and convenient syntax (Glimmer automatically takes care of sending that declaration to SWT at the end of declaring `sash_form` content as per the SWT requirements)
771
+
772
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
773
+
774
+ ```ruby
775
+ shell {
776
+ text 'Sash Form Example'
777
+ sash_form {
778
+ label {
779
+ text '(resize >>)'
780
+ background :dark_green
781
+ foreground :white
782
+ font height: 20
783
+ }
784
+ label {
785
+ text '(<< resize)'
786
+ background :red
787
+ foreground :white
788
+ font height: 20
789
+ }
790
+ weights 1, 2
791
+ }
792
+ }.open
793
+ ```
794
+
795
+ You may check out a more full-fledged example in [Hello, Sash Form!](#hello-sash-form)
796
+
797
+ ![Hello Sash Form](/images/glimmer-hello-sash-form.png)
798
+
799
+ #### Browser Widget
800
+
801
+ ![Hello Browser](/images/glimmer-hello-browser.png)
802
+
803
+ Glimmer supports the SWT Browser widget, which can load URLs or render HTML. It can even be instrumented with JavaScript when needed (though highly discouraged since it defeats the purpose of using Ruby except in very rare cases like leveraging a pre-existing web codebase in a desktop app).
804
+
805
+ Example loading a URL (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
806
+
807
+ ```ruby
808
+ shell {
809
+ minimum_size 1024, 860
810
+ browser {
811
+ url 'http://brightonresort.com/about'
812
+ }
813
+ }.open
814
+ ```
815
+
816
+ Example rendering HTML with JavaScript on document ready (you may copy/paste in [`girb`](GLIMMER_GIRB.md) provided you install and require [glimmer-dsl-xml gem](https://github.com/AndyObtiva/glimmer-dsl-xml)):
817
+
818
+ ```ruby
819
+ shell {
820
+ minimum_size 130, 130
821
+ @browser = browser {
822
+ text html {
823
+ head {
824
+ meta(name: "viewport", content: "width=device-width, initial-scale=2.0")
825
+ }
826
+ body {
827
+ h1 { "Hello, World!" }
828
+ }
829
+ }
830
+ on_completed { # on load of the page execute this JavaScript
831
+ @browser.swt_widget.execute("alert('Hello, World!');")
832
+ }
833
+ }
834
+ }.open
835
+ ```
836
+
837
+ ### Widget Styles
838
+
839
+ SWT widgets receive `SWT` styles in their constructor as per this guide:
840
+
841
+ https://wiki.eclipse.org/SWT_Widget_Style_Bits
842
+
843
+ Glimmer DSL facilitates that by passing symbols representing `SWT` constants as widget method arguments (i.e. inside widget `()` parentheses according to [Glimmer Style Guide](GLIMMER_STYLE_GUIDE.md). See example below) in lower case version (e.g. `SWT::MULTI` becomes `:multi`).
844
+
845
+ These styles customize widget look, feel, and behavior.
846
+
847
+ Example:
848
+
849
+ ```ruby
850
+ # ...
851
+ list(:multi) { # SWT styles go inside ()
852
+ # ...
853
+ }
854
+ # ...
855
+ ```
856
+ Passing `:multi` to `list` widget enables list element multi-selection.
857
+
858
+ ```ruby
859
+ # ...
860
+ composite(:border) { # SWT styles go inside ()
861
+ # ...
862
+ }
863
+ # ...
864
+ ```
865
+ Passing `:border` to `composite` widget ensures it has a border.
866
+
867
+ When you need to pass in **multiple SWT styles**, simply separate by commas.
868
+
869
+ Example:
870
+
871
+ ```ruby
872
+ # ...
873
+ text(:center, :border) { # Multiple SWT styles separated by comma
874
+ # ...
875
+ }
876
+ # ...
877
+ ```
878
+
879
+ Glimmer ships with SWT style **smart defaults** so you wouldn't have to set them yourself most of the time (albeit you can always override them):
880
+
881
+ - `text(:border)`
882
+ - `table(:border, :virtual, :full_selection)`
883
+ - `tree(:border, :virtual, :v_scroll, :h_scroll)`
884
+ - `spinner(:border)`
885
+ - `list(:border, :v_scroll)`
886
+ - `button(:push)`
887
+
888
+ You may check out all available `SWT` styles here:
889
+
890
+ https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/SWT.html
891
+
892
+ #### Explicit SWT Style Bit
893
+
894
+ When building a widget-related SWT object manually (e.g. `GridData.new(...)`), you are expected to use `SWT::CONSTANT` directly or BIT-OR a few SWT constants together like `SWT::BORDER | SWT::V_SCROLL`.
895
+
896
+ Glimmer facilitates that with `swt` keyword by allowing you to pass multiple styles as an argument array of symbols instead of dealing with BIT-OR.
897
+ Example:
898
+
899
+ ```ruby
900
+ style = swt(:border, :v_scroll)
901
+ ```
902
+
903
+ #### Negative SWT Style Bits
904
+
905
+ In rare occasions, you might need to apply & with a negative (not) style bit to negate it from another style bit that includes it.
906
+ Glimmer facilitates that by declaring the negative style bit via postfixing a symbol with `!`.
907
+
908
+ Example:
909
+
910
+ ```ruby
911
+ style = swt(:shell_trim, :max!) # creates a shell trim style without the maximize button (negated)
912
+ ```
913
+
914
+ #### Extra SWT Styles
915
+
916
+ ##### Non-resizable Window
917
+
918
+ SWT Shell widget by default is resizable. To make it non-resizable, one must pass a complicated style bit concoction like `swt(:shell_trim, :resize!, :max!)`.
919
+
920
+ Glimmer makes this easier by alternatively ing a `:no_resize` extra SWT style, added for convenience.
921
+ This makes declaring a non-resizable window as easy as:
922
+
923
+ ```ruby
924
+ shell(:no_resize) {
925
+ # ...
926
+ }
927
+ ```
928
+
929
+ ### Widget Properties
930
+
931
+ Widget properties such as text value, enablement, visibility, and layout details are set within the widget block using methods matching SWT widget property names in lower snakecase. You may refer to SWT widget guide for details on available widget properties:
932
+
933
+ https://help.eclipse.org/2019-12/topic/org.eclipse.platform.doc.isv/guide/swt_widgets_controls.htm?cp=2_0_7_0_0
934
+
935
+
936
+ Code examples:
937
+
938
+ ```ruby
939
+ # ...
940
+ label {
941
+ text "Hello, World!" # SWT properties go inside {} block
942
+ }
943
+ # ...
944
+ ```
945
+
946
+ In the above example, the `label` widget `text` property was set to "Hello, World!".
947
+
948
+ ```ruby
949
+ # ...
950
+ button {
951
+ enabled bind(@tic_tac_toe_board.box(row, column), :empty)
952
+ }
953
+ # ...
954
+ ```
955
+
956
+ In the above example, the `text` widget `enabled` property was data-bound to `#empty` method on `@tic_tac_toe_board.box(row, column)` (learn more about data-binding below)
957
+
958
+ #### Color
959
+
960
+ Colors make up a subset of widget properties. SWT accepts color objects created with RGB (Red Green Blue) or RGBA (Red Green Blue Alpha). Glimmer supports constructing color objects using the `rgb` and `rgba` DSL keywords.
961
+
962
+ Example:
963
+
964
+ ```ruby
965
+ # ...
966
+ label {
967
+ background rgb(144, 240, 244)
968
+ foreground rgba(38, 92, 232, 255)
969
+ }
970
+ # ...
971
+ ```
972
+
973
+ SWT also supports standard colors available as constants under the `SWT` namespace with the `COLOR_` prefix (e.g. `SWT::COLOR_BLUE`)
974
+
975
+ Glimmer supports constructing colors for these constants as lowercase Ruby symbols (with or without `color_` prefix) passed to `color` DSL keyword
976
+
977
+ Example:
978
+
979
+ ```ruby
980
+ # ...
981
+ label {
982
+ background color(:black)
983
+ foreground color(:yellow)
984
+ }
985
+ label {
986
+ background color(:color_white)
987
+ foreground color(:color_red)
988
+ }
989
+ # ...
990
+ ```
991
+
992
+ You may check out all available standard colors in `SWT` over here (having `COLOR_` prefix):
993
+
994
+ https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/SWT.html
995
+
996
+
997
+ ##### `#swt_color`
998
+
999
+ Glimmer color objects come with an instance method `#swt_color` that returns the actual SWT `Color` object wrapped by the Glimmer color object. It is useful in cases you'd like to do some custom SWT programming outside of Glimmer.
1000
+
1001
+ Example:
1002
+
1003
+ ```ruby
1004
+ color(:black).swt_color # returns SWT Color object
1005
+ ```
1006
+
1007
+ #### Font
1008
+
1009
+ Fonts are represented in Glimmer as a hash of name, height, and style keys.
1010
+
1011
+ The style can be one (or more) of 3 values: `:normal`, `:bold`, and `:italic`
1012
+
1013
+ Example:
1014
+
1015
+ ```ruby
1016
+ # ...
1017
+ label {
1018
+ font name: 'Arial', height: 36, style: :normal
1019
+ }
1020
+ # ...
1021
+ ```
1022
+
1023
+ Keys are optional, so some of them may be left off.
1024
+ When passing multiple styles, they are included in an array.
1025
+
1026
+ Example:
1027
+
1028
+ ```ruby
1029
+ # ...
1030
+ label {
1031
+ font style: [:bold, :italic]
1032
+ }
1033
+ # ...
1034
+ ```
1035
+
1036
+ You may simply use the standalone `font` keyword without nesting in a parent if there is a need to build a Font object to use in manual SWT programming outside of widget font property setting.
1037
+
1038
+ Example:
1039
+
1040
+ ```ruby
1041
+ @font = font(name: 'Arial', height: 36, style: :normal)
1042
+ ```
1043
+
1044
+ ### Image
1045
+
1046
+ The `image` keyword creates an instance of [org.eclipse.swt.graphics.Image](https://help.eclipse.org/2020-09/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/Image.html).
1047
+
1048
+ It is a graphics `Image` object (not a widget), but is used used in setting the `image` property on `label` and `background_image` on `composite` (and subclasses)
1049
+
1050
+ Glimmer recently included **EXPERIMENTAL** gif animation support for the `background_image` property on `composite' since SWT does not support animation by default. On Windows, it only works inside composites nested under standard shells, not ones that have the SWT styles :on_top or :no_trim
1051
+
1052
+ Learn more about images in general at this SWT Image guide: https://www.eclipse.org/articles/Article-SWT-images/graphics-resources.html
1053
+
1054
+ #### Image Options
1055
+
1056
+ Options may be passed in a hash at the end of `image` arguments:
1057
+
1058
+ - `width`: width of image
1059
+ - `height`: height of image
1060
+
1061
+ If only the width or height alone are specified, the other is calculated while maintaining the image aspect ratio.
1062
+
1063
+ Example:
1064
+
1065
+ ```
1066
+ label {
1067
+ image 'someimage.png', width: 400, height: 300
1068
+ }
1069
+ ```
1070
+
1071
+ ### Cursor
1072
+
1073
+ SWT widget `cursor` property represents the mouse cursor you see on the screen when you hover over that widget.
1074
+
1075
+ The `Display` class provides a way to obtain standard system cursors matching of the SWT style constants starting with prefix `CURSOR_` (e.g. `SWT::CURSOR_HELP` shows a question mark mouse cursor)
1076
+
1077
+ Glimmer provides an easier way to obtain and set `cursor` property on a widget by simply mentioning the SWT style constant as an abbreviated symbol excluding the "CURSOR_" suffix.
1078
+
1079
+ Example:
1080
+
1081
+ ```ruby
1082
+ shell {
1083
+ minimum_size 128, 128
1084
+ cursor :appstarting
1085
+ }
1086
+ ```
1087
+
1088
+ This sets the shell `cursor` to that of `SWT::CURSOR_APPSTARTING`
1089
+
1090
+ ### Layouts
1091
+
1092
+ Glimmer lays widgets out visually using SWT layouts, which can only be set on composite widget and subclasses.
1093
+
1094
+ The most common SWT layouts are:
1095
+ - `FillLayout`: lays widgets out in equal proportion horizontally or vertically with spacing/margin options. This is the ***default*** layout for ***shell*** (with `:horizontal` option) in Glimmer.
1096
+ - `RowLayout`: lays widgets out horizontally or vertically in varying proportions with advanced spacing/margin/justify options
1097
+ - `GridLayout`: lays widgets out in a grid with advanced spacing/margin/alignment/indentation options. This is the **default** layout for **composite** in Glimmer. It is important to master.
1098
+
1099
+ In Glimmer DSL, just like widgets, layouts can be specified with lowercase underscored names followed by a block containing properties, also lowercase underscored names (e.g. `RowLayout` is `row_layout`).
1100
+
1101
+ Example:
1102
+
1103
+ ```ruby
1104
+ # ...
1105
+ composite {
1106
+ row_layout {
1107
+ wrap true
1108
+ pack false
1109
+ justify true
1110
+ type :vertical
1111
+ margin_left 1
1112
+ margin_top 2
1113
+ margin_right 3
1114
+ margin_bottom 4
1115
+ spacing 5
1116
+ }
1117
+ # ... widgets follow
1118
+ }
1119
+ # ...
1120
+ ```
1121
+
1122
+ If you data-bind any layout properties, when they change, the shell containing their widget re-packs its children (calls `#pack` method automatically) to ensure proper relayout of all widgets.
1123
+
1124
+ Alternatively, a layout may be constructed by following the SWT API for the layout object. For example, a `RowLayout` can be constructed by passing it an SWT style constant (Glimmer automatically accepts symbols (e.g. `:horizontal`) for SWT style arguments like `SWT::HORIZONTAL`.)
1125
+
1126
+ ```ruby
1127
+ # ...
1128
+ composite {
1129
+ row_layout :horizontal
1130
+ # ... widgets follow
1131
+ }
1132
+ # ...
1133
+ ```
1134
+
1135
+ Here is a more sophisticated example taken from [hello_computed.rb](samples/hello/hello_computed.rb) sample:
1136
+
1137
+ ![Hello Computed](/images/glimmer-hello-computed.png)
1138
+
1139
+ ```ruby
1140
+ shell {
1141
+ text 'Hello, Computed!'
1142
+ composite {
1143
+ grid_layout {
1144
+ num_columns 2
1145
+ make_columns_equal_width true
1146
+ horizontal_spacing 20
1147
+ vertical_spacing 10
1148
+ }
1149
+ label {text 'First &Name: '}
1150
+ text {
1151
+ text bind(@contact, :first_name)
1152
+ layout_data {
1153
+ horizontal_alignment :fill
1154
+ grab_excess_horizontal_space true
1155
+ }
1156
+ }
1157
+ label {text '&Last Name: '}
1158
+ text {
1159
+ text bind(@contact, :last_name)
1160
+ layout_data {
1161
+ horizontal_alignment :fill
1162
+ grab_excess_horizontal_space true
1163
+ }
1164
+ }
1165
+ label {text '&Year of Birth: '}
1166
+ text {
1167
+ text bind(@contact, :year_of_birth)
1168
+ layout_data {
1169
+ horizontal_alignment :fill
1170
+ grab_excess_horizontal_space true
1171
+ }
1172
+ }
1173
+ label {text 'Name: '}
1174
+ label {
1175
+ text bind(@contact, :name, computed_by: [:first_name, :last_name])
1176
+ layout_data {
1177
+ horizontal_alignment :fill
1178
+ grab_excess_horizontal_space true
1179
+ }
1180
+ }
1181
+ label {text 'Age: '}
1182
+ label {
1183
+ text bind(@contact, :age, on_write: :to_i, computed_by: [:year_of_birth])
1184
+ layout_data {
1185
+ horizontal_alignment :fill
1186
+ grab_excess_horizontal_space true
1187
+ }
1188
+ }
1189
+ }
1190
+ }.open
1191
+ ```
1192
+
1193
+ Check out the samples directory for more advanced examples of layouts in Glimmer.
1194
+
1195
+ **Defaults**:
1196
+
1197
+ Glimmer composites always come with `grid_layout` by default, but you can still specify explicitly if you'd like to set specific properties on it.
1198
+
1199
+ Glimmer shell always comes with `fill_layout` having `:horizontal` type.
1200
+
1201
+ This is a great guide for learning more about SWT layouts:
1202
+
1203
+ https://www.eclipse.org/articles/Article-Understanding-Layouts/Understanding-Layouts.htm
1204
+
1205
+ Also, for a reference, check the SWT API:
1206
+
1207
+ https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/index.html
1208
+
1209
+ ### Layout Data
1210
+
1211
+ Layouts organize widgets following common rules for all widgets directly under a composite. But, what if a specific widget needs its own rules. That's where layout data comes into play.
1212
+
1213
+ By convention, SWT layouts expect widgets to set layout data with a class matching their class name with the word "Data" replacing "Layout":
1214
+ - `GridLayout` on a composite demands `GridData` on contained widgets
1215
+ - `RowLayout` on a composite demands `RowData` on contained widgets
1216
+
1217
+ Not all layouts support layout data to further customize widget layouts. For example, `FillLayout` supports no layout data.
1218
+
1219
+ Unlike widgets and layouts in Glimmer DSL, layout data is simply specified with `layout_data` keyword nested inside a widget block body, and followed by arguments and/or a block of its own properties (lowercase underscored names).
1220
+
1221
+ Glimmer automatically deduces layout data class name by convention as per rule above, with the assumption that the layout data class lives under the same exact Java package as the layout (one can set custom layout data that breaks convention if needed in rare cases. See code below for an example)
1222
+
1223
+ Glimmer also automatically accepts symbols (e.g. `:fill`) for SWT style arguments like `SWT::FILL`.
1224
+
1225
+ Examples:
1226
+
1227
+ ```ruby
1228
+ # ...
1229
+ composite {
1230
+ row_layout :horizontal
1231
+ label {
1232
+ layout_data { # followed by properties
1233
+ width 50
1234
+ height 30
1235
+ }
1236
+ }
1237
+ # ... more widgets follow
1238
+ }
1239
+ # ...
1240
+ ```
1241
+
1242
+ ```ruby
1243
+ # ...
1244
+ composite {
1245
+ grid_layout 3, false # grid layout with 3 columns not of equal width
1246
+ label {
1247
+ # layout data followed by arguments passed to SWT GridData constructor
1248
+ layout_data :fill, :end, true, false
1249
+ }
1250
+ }
1251
+ # ...
1252
+ ```
1253
+
1254
+ ```ruby
1255
+ # ...
1256
+ composite {
1257
+ grid_layout 3, false # grid layout with 3 columns not of equal width
1258
+ label {
1259
+ # layout data set explicitly via an object (helps in rare cases that break convention)
1260
+ layout_data GridData.new(swt(:fill), swt(:end), true, false)
1261
+ }
1262
+ }
1263
+ # ...
1264
+ ```
1265
+
1266
+ If you data-bind any layout data properties, when they change, the shell containing their widget re-packs its children (calls `#pack` method automatically) to ensure proper relayout of all widgets.
1267
+
1268
+ **NOTE**: Layout data must never be reused between widgets. Always specify or clone again for every widget.
1269
+
1270
+ This is a great guide for learning more about SWT layouts:
1271
+
1272
+ https://www.eclipse.org/articles/Article-Understanding-Layouts/Understanding-Layouts.htm
1273
+
1274
+ Also, for a reference, check the SWT API:
1275
+
1276
+ https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/index.html
1277
+
1278
+
1279
+ ### Canvas Shape DSL
1280
+
1281
+ **(ALPHA FEATURE)**
1282
+
1283
+ Glimmer supports drawing graphics directly on a `canvas` widget via SWT (or any widget for that matter though `canvas` is recommended for drawing).
1284
+
1285
+ `canvas` has the `:double_buffered` SWT style by default to ensure flicker-free rendering. If you need to disable it for whatever reason, just pass the `:none` SWT style instead (e.g. `canvas(:none)`)
1286
+
1287
+ This is accomplished via the Shape DSL a sub-DSL of the Glimmer GUI DSL, which makes it possible to draw graphics declaratively with very understandable and maintainable syntax.
1288
+
1289
+ Shape keywords and their args (including defaults) are listed below (they basically match method names and arguments on [org.eclipse.swt.graphics.GC](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html) minus the `draw` or `fill` prefix in downcase):
1290
+ - `arc(x, y, width, height, startAngle, arcAngle, fill: false)` arc is part of a circle within an oval area, denoted by start angle (degrees) and end angle (degrees)
1291
+ - `focus(x, y, width, height)` this is just like rectangle but its foreground color is always that of the OS widget focus color (useful when capturing user interaction via a shape)
1292
+ - `image(image, x = 0, y = 0)` sets [image](#image), which could be an [org.eclipse.swt.graphics.Image](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/Image.html) object or just a file path string
1293
+ - `line(x1, y1, x2, y2)` line
1294
+ - `oval(x, y, width, height, fill: false)` oval if width does not match heigh and circle if width matches height. Can be optionally filled.
1295
+ - `point(x, y)` point
1296
+ - `polygon(pointArray, fill: false)` polygon consisting of points, which close automatically to form a shape that can be optionally filled (when points only form a line, it does not show up as filled)
1297
+ - `polyline(pointArray)` polyline is just like a polygon, but it does not close up to form a shape, remaining open (unless the points close themselves by having the last point or an intermediate point match the first)
1298
+ - `rectangle(x, y, width, height, fill: false)` standard rectangle, which can be optionally filled
1299
+ - `rectangle(x, y, width, height, arcWidth = 60, arcHeight = 60, fill: false, round: true)` round rectangle, which can be optionally filled, and takes optional extra round angle arguments
1300
+ - `rectangle(x, y, width, height, vertical = true, fill: true, gradient: true)` gradient rectangle, which is always filled, and takes an optional extra argument to specify true for vertical gradient (default) and false for horizontal gradient
1301
+ - `text(string, x, y, flags = nil)` text with optional flags (flag format is `swt(comma_separated_flags)` where flags can be :draw_delimiter (i.e. new lines), :draw_tab, :draw_mnemonic, and :draw_transparent as explained in [GC API](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html))
1302
+
1303
+ Shape keywords that can be filled with color can take an keyword argument `fill: true`. Defaults to false when not specified unless background is set with no foreground (or foreground is set with no background), in which case a smart default is applied.
1304
+ Smart defaults can be applied to automatically infer `gradient: true` (rectangle with both foreground and background) and `round: true` (rectangle with more than 4 args, the extra args are numeric) as well.
1305
+
1306
+ Optionally, a shape keyword takes a block that can set any attributes from [org.eclipse.swt.graphics.GC](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html) (methods starting with `set`), which enable setting the `background` for filling and `foreground` for drawing.
1307
+
1308
+ Here is a list of supported attributes nestable within a block under shapes:
1309
+ - `advanced` enables advanced graphics subsystem (boolean value). Typically gets enabled automatically when setting alpha, antialias, patterns, interpolation, clipping. Rendering sometimes differs between advanced and non-advanced mode for basic graphics too, so you could enable manually if you prefer its look even for basic graphics.
1310
+ - `alpha` sets transparency (integer between `0` and `255`)
1311
+ - `antialias` enables antialiasing (SWT style value of `:default`, `:off`, `:on` whereby `:default` applies OS default, which varies per OS)
1312
+ - `background` sets fill color for fillable shapes (standard color symbol (e.g. `:red`), `rgb(red_integer, green_integer, blue_integer)` color, or Color/ColorProxy object directly)
1313
+ - `background_pattern` sets fill gradient/image pattern for fillable shape background (takes the same arguments as the SWT [Pattern](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/Pattern.html) class [e.g. `background_pattern 2.3, 4.2, 5.4, 7.2, :red, :blue`] / note: this feature isn't extensively tested yet)
1314
+ - `clipping` clips area of painting (numeric values for `(x, y, width, height)`)
1315
+ - `fill_rule` sets filling rule (SWT style value of `:fill_even_odd` or `:fill_winding`)
1316
+ - `font` sets font (Hash of `:name`, `:height`, and `:style` just like standard widget font property, or Font/FontProxy object directly)
1317
+ - `foreground` sets draw color for drawable shapes (standard color symbol (e.g. `:red`), `rgb(red_integer, green_integer, blue_integer)` color, or Color/ColorProxy object directly)
1318
+ - `foreground_pattern` sets foreground gradient/image pattern for drawable shape lines (takes the same arguments as the SWT [Pattern](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/Pattern.html) class [e.g. `foreground_pattern 2.3, 4.2, 5.4, 7.2, :red, :blue`] / note: this feature isn't extensively tested yet)
1319
+ - `interpolation` sets the interpolation value (SWT style value of `:default`, `:none`, `:low`, `:high`)
1320
+ - `line_cap` sets line cap (SWT style value of `:cap_flat`, `:cap_round`, or `:cap_square`)
1321
+ - `line_dash` line dash float values (automatically sets `line_style` to SWT style value of `:line_custom`)
1322
+ - `line_join` line join style (SWT style value of `:join_miter`, `:join_round`, or `:join_bevel`)
1323
+ - `line_style` line join style (SWT style value of `:line_solid`, `:line_dash`, `:line_dot`, `:line_dashdot`, or `:line_dashdotdot`)
1324
+ - `line_width` line width in integer (used in draw operations)
1325
+ - `text_anti_alias` enables text antialiasing (SWT style value of `:default`, `:off`, `:on` whereby `:default` applies OS default, which varies per OS)
1326
+ - `transform` sets transform object using [Canvas Transform DSL](#canvas-transform-dsl) syntax
1327
+
1328
+ Keep in mind that ordering of shapes matters as it is followed in painting. For example, it is recommended you paint filled shapes first and then drawn ones.
1329
+
1330
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1331
+
1332
+ ```ruby
1333
+ include Glimmer
1334
+
1335
+ # image object has to be declared outside the canvas and shell to avoid confusing with canvas image property
1336
+ image_object = image(File.expand_path('./icons/scaffold_app.png'), width: 100)
1337
+
1338
+ shell {
1339
+ text 'Canvas Example'
1340
+ minimum_size 320, 400
1341
+
1342
+ canvas {
1343
+ background :dark_yellow
1344
+ rectangle(0, 0, 220, 400) {
1345
+ background :dark_red
1346
+ }
1347
+ rectangle(50, 20, 300, 150, 30, 50, round: true) {
1348
+ background :yellow
1349
+ }
1350
+ rectangle(150, 200, 100, 70, true, gradient: true) {
1351
+ background :dark_red
1352
+ foreground :yellow
1353
+ }
1354
+ text('Glimmer', 208, 83) {
1355
+ font height: 25, style: :bold
1356
+ }
1357
+ rectangle(200, 80, 108, 36) {
1358
+ foreground :black
1359
+ line_width 3
1360
+ }
1361
+ image(image_object, 70, 50)
1362
+ }
1363
+ }.open
1364
+ ```
1365
+
1366
+ Screenshot:
1367
+
1368
+ ![Canvas Animation Example](/images/glimmer-example-canvas.png)
1369
+
1370
+ Learn more at the [Hello, Canvas! Sample](#hello-canvas).
1371
+
1372
+ If you ever have special needs or optimizations, you could always default to direct SWT painting via [org.eclipse.swt.graphics.GC](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html) instead. Learn more at the [SWT Graphics Guide](https://www.eclipse.org/articles/Article-SWT-graphics/SWT_graphics.html) and [SWT Image Guide](https://www.eclipse.org/articles/Article-SWT-images/graphics-resources.html#Saving%20Images).
1373
+
1374
+ Example of manually doing the same things as in the previous example without relying on the declarative Glimmer Shape DSL (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1375
+
1376
+ ```ruby
1377
+ include Glimmer
1378
+
1379
+ image_object = image(File.expand_path('./icons/scaffold_app.png'), width: 100)
1380
+
1381
+ shell {
1382
+ text 'Canvas Manual Example'
1383
+ minimum_size 320, 400
1384
+
1385
+ canvas {
1386
+ background :yellow
1387
+
1388
+ on_paint_control { |paint_event|
1389
+ gc = paint_event.gc
1390
+ gc.background = color(:red).swt_color
1391
+ gc.fill_rectangle(0, 0, 220, 400)
1392
+
1393
+ gc.background = color(:magenta).swt_color
1394
+ gc.fill_roundRectangle(50, 20, 300, 150, 30, 50)
1395
+
1396
+ gc.background = color(:dark_magenta).swt_color
1397
+ gc.fill_gradientRectangle(150, 200, 100, 70, true)
1398
+
1399
+ gc.foreground = color(:dark_blue).swt_color
1400
+ gc.draw_rectangle(200, 80, 108, 36)
1401
+
1402
+ gc.foreground = color(:black).swt_color
1403
+ gc.line_width = 3
1404
+ gc.draw_rectangle(200, 80, 108, 36)
1405
+
1406
+ gc.draw_image(image_object.swt_image, 70, 50)
1407
+ }
1408
+ }
1409
+ }.open
1410
+ ```
1411
+
1412
+ #### Pixel Graphics
1413
+
1414
+ **(Early Alpha Feature)**
1415
+
1416
+ If you need to paint pixel graphics, use the optimized `pixel` keyword alternative to `point`, which takes foreground as a hash argument and bypasses the [Glimmer DSL Engine chain of responsibility](https://github.com/AndyObtiva/glimmer#dsl-engine), thus rendering faster when having very large pixel counts.
1417
+
1418
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1419
+
1420
+ ```ruby
1421
+ include Glimmer
1422
+
1423
+ shell {
1424
+ minimum_size 250, 265
1425
+ text 'Pixel Graphics Example'
1426
+
1427
+ canvas {
1428
+ 250.times {|y|
1429
+ 250.times {|x|
1430
+ pixel(x, y, foreground: [y%255, x%255, (x+y)%255])
1431
+ }
1432
+ }
1433
+ }
1434
+ }.open
1435
+ ```
1436
+
1437
+ Result:
1438
+
1439
+ ![glimmer example pixel graphics](/images/glimmer-example-pixel-graphics.png)
1440
+
1441
+ Remember that you could always default to direct SWT painting via [org.eclipse.swt.graphics.GC](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html) too for even faster performance when needed in rare circumstances. Learn more at the [SWT Graphics Guide](https://www.eclipse.org/articles/Article-SWT-graphics/SWT_graphics.html) and [SWT Image Guide](https://www.eclipse.org/articles/Article-SWT-images/graphics-resources.html#Saving%20Images).
1442
+
1443
+ Example of manually doing the same things as in the previous example without relying on the declarative Glimmer Shape DSL (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1444
+
1445
+ ```ruby
1446
+ include Glimmer
1447
+
1448
+ shell {
1449
+ minimum_size 250, 265
1450
+ text 'Pixel Graphics Example'
1451
+
1452
+ canvas {
1453
+ on_paint_control { |paint_event|
1454
+ gc = paint_event.gc
1455
+ 250.times {|y|
1456
+ 250.times {|x|
1457
+ gc.foreground = Color.new(y%255, x%255, (x+y)%255)
1458
+ gc.draw_point(x, y)
1459
+ }
1460
+ }
1461
+ }
1462
+ }
1463
+ }.open
1464
+ ```
1465
+
1466
+ The only downside with the approach above is that it repaints all pixels on repaints to the window (e.g. during window resize). To get around that, we can rely on a technique called **Image Double-Buffering**. That is to buffer the graphics on an Image first and then set it on the Canvas so that resizes of the shell dont cause a repaint of all the pixels. Additionally, this gives us the added benefit of being able to use the image as a Shell icon via its `image` property.
1467
+
1468
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1469
+
1470
+ ```ruby
1471
+ include Glimmer
1472
+
1473
+ @the_image = image(250, 250)
1474
+ gc = org.eclipse.swt.graphics.GC.new(@the_image)
1475
+ 250.times {|y|
1476
+ 250.times {|x|
1477
+ gc.foreground = Color.new(y%255, x%255, (x+y)%255)
1478
+ gc.draw_point(x, y)
1479
+ }
1480
+ }
1481
+
1482
+ shell {
1483
+ minimum_size 250, 265
1484
+ text 'Pixel Graphics Example'
1485
+ image @the_image
1486
+
1487
+ canvas {
1488
+ image @the_image
1489
+ }
1490
+ }.open
1491
+ ```
1492
+
1493
+ If you need a transparent background for the image, replace the image construction line with the following:
1494
+
1495
+ ```ruby
1496
+ @the_image = image(250, 250)
1497
+ @the_image.image_data.alpha = 0
1498
+ @the_image = image(@the_image.image_data)
1499
+ ```
1500
+
1501
+ That way, wherever you don't draw a point, you get transparency (seeing what is behind the image).
1502
+
1503
+ If you don't need a `shell` image icon and `pixel` performance is enough, you can automatically apply **Image Double-Buffering** with the `:image_double_buffered` SWT style (custom Glimmer style not available in SWT itself)
1504
+
1505
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1506
+
1507
+ ```ruby
1508
+ include Glimmer
1509
+
1510
+ shell {
1511
+ minimum_size 250, 265
1512
+ text 'Pixel Graphics Example'
1513
+
1514
+ canvas(:image_double_buffered) {
1515
+ 250.times {|y|
1516
+ 250.times {|x|
1517
+ pixel(x, y, foreground: [y%255, x%255, (x+y)%255])
1518
+ }
1519
+ }
1520
+ }
1521
+ }.open
1522
+ ```
1523
+
1524
+ #### Shapes inside a Widget
1525
+
1526
+ Keep in mind that the Shape DSL can be used inside any widget, not just `canvas`. Unlike shapes on a `canvas`, which are standalone graphics, when included in a widget, which already has its own look and feel, shapes are used as a decorative add-on that complements its look by getting painted on top of it. For example, shapes were used to decorate `composite` blocks in the [Tetris](#tetris) sample to have a more bevel look. In summary, Shapes can be used in a hybrid approach (shapes inside a widget), not just standalone in a `canvas`.
1527
+
1528
+ #### Shapes inside an Image
1529
+
1530
+ You can build an image using the Canvas Shape DSL (including setting the icon of the application).
1531
+
1532
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1533
+
1534
+ ```
1535
+ include Glimmer
1536
+
1537
+ shell {
1538
+ text 'Image Shape DSL Example'
1539
+ label {
1540
+ bevel_constant = 20
1541
+ icon_block_size = 64
1542
+ icon_bevel_size = icon_block_size.to_f / 25.to_f
1543
+ icon_bevel_pixel_size = 0.16*icon_block_size.to_f
1544
+ icon_size = 8
1545
+ icon_pixel_size = icon_block_size * icon_size
1546
+ image(icon_pixel_size, icon_pixel_size) {
1547
+ icon_size.times { |row|
1548
+ icon_size.times { |column|
1549
+ colored = row >= 1 && column.between?(1, 6)
1550
+ color = colored ? color([:white, :red, :blue, :green, :yellow, :magenta, :cyan, :dark_blue].sample) : color(:white)
1551
+ x = column * icon_block_size
1552
+ y = row * icon_block_size
1553
+ rectangle(x, y, icon_block_size, icon_block_size) {
1554
+ background color
1555
+ }
1556
+ polygon(x, y, x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size) {
1557
+ background rgb(color.red + 4*bevel_constant, color.green + 4*bevel_constant, color.blue + 4*bevel_constant)
1558
+ }
1559
+ polygon(x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size, y + icon_block_size) {
1560
+ background rgb(color.red - bevel_constant, color.green - bevel_constant, color.blue - bevel_constant)
1561
+ }
1562
+ polygon(x + icon_block_size, y + icon_block_size, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size) {
1563
+ background rgb(color.red - 2*bevel_constant, color.green - 2*bevel_constant, color.blue - 2*bevel_constant)
1564
+ }
1565
+ polygon(x, y, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size) {
1566
+ background rgb(color.red - bevel_constant, color.green - bevel_constant, color.blue - bevel_constant)
1567
+ }
1568
+ }
1569
+ }
1570
+ }
1571
+ }
1572
+ }.open
1573
+ ```
1574
+
1575
+ ![Image Shape DSL](/images/glimmer-example-image-shape-dsl.png)
1576
+
1577
+ Example setting the icon of the application (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1578
+
1579
+ ```
1580
+ include Glimmer
1581
+
1582
+ shell {
1583
+ text 'Image Shape DSL Example'
1584
+ label {
1585
+ text 'Image Shape DSL Example'
1586
+ font height: 30
1587
+ }
1588
+ bevel_constant = 20
1589
+ icon_block_size = 64
1590
+ icon_bevel_size = icon_block_size.to_f / 25.to_f
1591
+ icon_bevel_pixel_size = 0.16*icon_block_size.to_f
1592
+ icon_size = 8
1593
+ icon_pixel_size = icon_block_size * icon_size
1594
+ image(icon_pixel_size, icon_pixel_size) {
1595
+ icon_size.times { |row|
1596
+ icon_size.times { |column|
1597
+ colored = row >= 1 && column.between?(1, 6)
1598
+ color = colored ? color([:white, :red, :blue, :green, :yellow, :magenta, :cyan, :dark_blue].sample) : color(:white)
1599
+ x = column * icon_block_size
1600
+ y = row * icon_block_size
1601
+ rectangle(x, y, icon_block_size, icon_block_size) {
1602
+ background color
1603
+ }
1604
+ polygon(x, y, x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size) {
1605
+ background rgb(color.red + 4*bevel_constant, color.green + 4*bevel_constant, color.blue + 4*bevel_constant)
1606
+ }
1607
+ polygon(x + icon_block_size, y, x + icon_block_size - icon_bevel_pixel_size, y + icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size, y + icon_block_size) {
1608
+ background rgb(color.red - bevel_constant, color.green - bevel_constant, color.blue - bevel_constant)
1609
+ }
1610
+ polygon(x + icon_block_size, y + icon_block_size, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_block_size - icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size) {
1611
+ background rgb(color.red - 2*bevel_constant, color.green - 2*bevel_constant, color.blue - 2*bevel_constant)
1612
+ }
1613
+ polygon(x, y, x, y + icon_block_size, x + icon_bevel_pixel_size, y + icon_block_size - icon_bevel_pixel_size, x + icon_bevel_pixel_size, y + icon_bevel_pixel_size) {
1614
+ background rgb(color.red - bevel_constant, color.green - bevel_constant, color.blue - bevel_constant)
1615
+ }
1616
+ }
1617
+ }
1618
+ }
1619
+ }.open
1620
+ ```
1621
+
1622
+ ![Image Shape DSL](/images/glimmer-example-image-shape-dsl-app-switcher-icon.png)
1623
+
1624
+
1625
+ ### Canvas Transform DSL
1626
+
1627
+ **(ALPHA FEATURE)**
1628
+
1629
+ The transform DSL builds [org.eclipse.swt.graphics.Transform](https://help.eclipse.org/2020-12/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/Transform.html) objects with a nice declarative syntax.
1630
+
1631
+ `transform` keyword builds a `Transform` object. It optionally takes the transformation matrix elements: (m11, m12, m21, m22, dx, dy)
1632
+
1633
+ The first 2 values represent the 1st row, the second 2 values represent the 2nd row, and the last 2 values represent translation on the x and y axes
1634
+
1635
+ Additionally, Transform operation keywords may be nested within the `transform` keyword to set its properties:
1636
+ - `identity` resets transform to identity (no transformation)
1637
+ - `invert` inverts a transform
1638
+ - `multiply(&block)` multiplies by another transform (takes a block representing properties of another transform, no need for using the word transform again)
1639
+ - `rotate(angle)` rotates by angle degrees
1640
+ - `scale(x, y)` scales a shape (stretch)
1641
+ - `shear(x, y)` shear effect
1642
+ - `translate(x, y)` translate x and y coordinates (move)
1643
+ - `elements(m11, m12, m21, m22, dx, dy)` resets all values of the transform matrix (first 2 values represent the 1st row, second 2 values represent the 2nd row, the last 2 values represent translation on x and y axes)
1644
+
1645
+ Also, setting `transform` to `nil` after a previous `transform` has been set is like calling `identity`. It resets the transform.
1646
+
1647
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1648
+
1649
+ ```ruby
1650
+ include Glimmer
1651
+
1652
+ shell {
1653
+ text 'Canvas Transform Example'
1654
+ minimum_size 330, 352
1655
+
1656
+ canvas { |canvas_proxy|
1657
+ background :white
1658
+
1659
+ text('glimmer', 0, 0) {
1660
+ foreground :red
1661
+ transform {
1662
+ translate 220, 100
1663
+ scale 2.5, 2.5
1664
+ rotate 90
1665
+ }
1666
+ }
1667
+ text('glimmer', 0, 0) {
1668
+ foreground :dark_green
1669
+ transform {
1670
+ translate 0, 0
1671
+ shear 2, 3
1672
+ scale 2, 2
1673
+ }
1674
+ }
1675
+ text('glimmer', 0, 0) {
1676
+ foreground :blue
1677
+ transform {
1678
+ translate 0, 220
1679
+ scale 3, 3
1680
+ }
1681
+ }
1682
+ }
1683
+ }.open
1684
+ ```
1685
+
1686
+ ![Canvas Transform Example](/images/glimmer-example-canvas-transform.png)
1687
+
1688
+ #### Top-Level Transform Fluent Interface
1689
+
1690
+ When using a transform at the top-level (outside of shell), you get a fluent interface to faciliate manual construction and use.
1691
+
1692
+ Example:
1693
+
1694
+ ```ruby
1695
+ include Glimmer # make sure it is included in your class/module before using the fluent interface
1696
+
1697
+ transform(1, 1, 4, 2, 2, 4).
1698
+ multiply(1, 2, 3, 4,3,4).
1699
+ scale(1, 2, 3, 4, 5, 6).
1700
+ rotate(45).
1701
+ scale(2, 4).
1702
+ invert.
1703
+ shear(2, 4).
1704
+ translate(3, 7)
1705
+ ```
1706
+
1707
+ Learn more at the [Hello, Canvas Transform! Sample](#hello-canvas-transform).
1708
+
1709
+ ### Canvas Animation DSL
1710
+
1711
+ **(ALPHA FEATURE)**
1712
+
1713
+ (note: this is a very new feature of Glimmer. It may change a bit while getting battle tested. As always, you could default to basic SWT usage if needed.)
1714
+
1715
+ Glimmer additionally provides built-in support for animations via a declarative Animation DSL, another sub-DSL of the Glimmer GUI DSL.
1716
+
1717
+ Animations take advantage of multi-threading, automatically running each animation in its own independent thread of execution while updating the GUI asynchronously.
1718
+
1719
+ Multiple simultaneous animations are supported by declaring an animation per `canvas` (or widget) parent.
1720
+
1721
+ `canvas` has the `:double_buffered` SWT style by default to ensure flicker-free rendering. If you need to disable it for whatever reason, just pass the `:none` SWT style instead (e.g. `canvas(:none)`)
1722
+
1723
+ This example says it all (it moves a tiny red square across a blue background) (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1724
+
1725
+ ```ruby
1726
+ include Glimmer
1727
+
1728
+ shell {
1729
+ text 'Canvas Animation Example'
1730
+ minimum_size 400, 400
1731
+
1732
+ canvas {
1733
+ animation {
1734
+ every 0.1
1735
+
1736
+ frame { |index|
1737
+ background rgb(index%100, index%100 + 100, index%55 + 200)
1738
+ rectangle(index, index, 20, 20) {
1739
+ background :red
1740
+ }
1741
+ }
1742
+ }
1743
+ }
1744
+ }.open
1745
+ ```
1746
+
1747
+ Screenshot:
1748
+
1749
+ ![Canvas Animation Example](/images/glimmer-example-canvas-animation.png)
1750
+
1751
+ Keywords:
1752
+ - `animation` declares an animation under a canvas, which renders frames using a frame block indefinitely or finitely depending on (cycle_count/frame_count) properties
1753
+ - `every` specifies delay in seconds between every two frame renders
1754
+ - `frame` a block that can contain Shape DSL syntax that is rendered dynamically with variables calculated on the fly
1755
+ - `cycle` a property that takes an array to cycle into a second variable for the `frame` block
1756
+ - `cycle_count` an optional cycle count limit after which the animation stops
1757
+ - `frame_count` an optional frame count limit after which the animation stops
1758
+ - `started` a boolean indicating if the animation is started right away or stopped waiting for manual startup via `#start` method
1759
+
1760
+ API of Animation Object (returned from `animation` keyword):
1761
+ - `#start` starts an animation that is indefinite or has never been started before (i.e. having `started: false` option). Otherwise, resumes a stopped animation that has not been completed.
1762
+ - `#stop` stops animation. Maintains progress when `frame_count`, `cycle_count`, or `duration_limit` are set and haven't finished. That way, if `#start` is called, animation resumes from where it stopped exactly to completion.
1763
+ - `#restart` restarts animation, restarting progress of `frame_count`, `cycle_count`, and `duration_limit` if set.
1764
+ - `#started?` returns whether animation started
1765
+ - `#stopped?` returns whether animation stopped
1766
+ - `#indefinite?` (alias `infinite?`) returns true if animation does not have `frame_count`, `cycle_count`, or `duration_limit`
1767
+ - `#finite?` returns true if animation has `frame_count`, `cycle_count` (with `cycle`), or `duration_limit`
1768
+ - `#frame_count_limited?` returns true if `frame_count` is specified
1769
+ - `#cycle_enabled?` returns true if `cycle` is specified
1770
+ - `#cycle_limited?` returns true if `cycle_count` is specified
1771
+ - `#duration_limited?` returns true if `duration_limit` is specified
1772
+
1773
+ Learn more at the [Hello, Canvas Animation! Sample](#hello-canvas-animation).
1774
+
1775
+ If there is anything missing you would like added to the Glimmer Animation DSL that you saw available in the SWT APIs, you may [report an issue](https://github.com/AndyObtiva/glimmer-dsl-swt/issues) or implement yourself and [contribute](#contributing) via a Pull Request.
1776
+
1777
+ #### Animation via Data-Binding
1778
+
1779
+ Animation could be alternatively implemented without the `animation` keyword through a loop that invokes model methods inside `sync_exec {}` (or `async_exec {}`), which indirectly cause updates to the GUI via data-binding.
1780
+
1781
+ The [Glimmer Tetris](#glimmer-tetris) sample provides a good example of that.
1782
+
1783
+ ### Data-Binding
1784
+
1785
+ Data-binding is done with `bind` command following widget property to bind and taking model and bindable attribute as arguments.
1786
+
1787
+ #### General Examples
1788
+
1789
+ `text bind(contact, :first_name)`
1790
+
1791
+ This example binds the text property of a widget like `label` to the first name of a contact model.
1792
+
1793
+ `text bind(contact, 'address.street')`
1794
+
1795
+ This example binds the text property of a widget like `label` to the nested street of
1796
+ the address of a contact. This is called nested property data binding.
1797
+
1798
+ `text bind(contact, 'address.street', on_read: :upcase, on_write: :downcase)`
1799
+
1800
+ This example adds on the one above it by specifying converters on read and write of the model property, like in the case of a `text` widget. The text widget will then displays the street upper case and the model will store it lower case. When specifying converters, read and write operations must be symmetric (to avoid an infinite update loop between the widget and the model since the widget checks first if value changed before updating)
1801
+
1802
+ `text bind(contact, 'address.street', on_read: lambda { |s| s[0..10] })`
1803
+
1804
+ This example also specifies a converter on read of the model property, but via a lambda, which truncates the street to 10 characters only. Note that the read and write operations are assymetric. This is fine in the case of formatting data for a read-only widget like `label`
1805
+
1806
+ `text bind(contact, 'address.street') { |s| s[0..10] }`
1807
+
1808
+ This is a block shortcut version of the syntax above it. It facilitates formatting model data for read-only widgets since it's a very common view concern. It also saves the developer from having to create a separate formatter/presenter for the model when the view can be an active view that handles common simple formatting operations directly.
1809
+
1810
+ `text bind(contact, 'address.street', read_only: true)
1811
+
1812
+ This is read-ohly data-binding. It doesn't update contact.address.street when widget text property is changed.
1813
+
1814
+ `text bind(contact, 'addresses[1].street')`
1815
+
1816
+ This example binds the text property of a widget like `label` to the nested indexed address street of a contact. This is called nested indexed property data binding.
1817
+
1818
+ `text bind(contact, :age, computed_by: :date_of_birth)`
1819
+
1820
+ This example demonstrates computed value data binding whereby the value of `age` depends on changes to `date_of_birth`.
1821
+
1822
+ `text bind(contact, :name, computed_by: [:first_name, :last_name])`
1823
+
1824
+ This example demonstrates computed value data binding whereby the value of `name` depends on changes to both `first_name` and `last_name`.
1825
+
1826
+ `text bind(contact, 'profiles[0].name', computed_by: ['profiles[0].first_name', 'profiles[0].last_name'])`
1827
+
1828
+ This example demonstrates nested indexed computed value data binding whereby the value of `profiles[0].name` depends on changes to both nested `profiles[0].first_name` and `profiles[0].last_name`.
1829
+
1830
+ Example from [samples/hello/hello_combo.rb](samples/hello_combo.rb) sample (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1831
+
1832
+ #### Combo
1833
+
1834
+ The `combo` widget provides a dropdown of options. By default, it also allows typing in a new option. To disable that behavior, you may use with the `:read_only` SWT style.
1835
+
1836
+ When data-binding a `combo` widget, Glimmer can automatically deduce available options from data-bound model by convention: `{attribute_name}_options` method.
1837
+
1838
+ ![Hello Combo](/images/glimmer-hello-combo.png)
1839
+
1840
+ ![Hello Combo](/images/glimmer-hello-combo-expanded.png)
1841
+
1842
+ ```ruby
1843
+ class Person
1844
+ attr_accessor :country, :country_options
1845
+
1846
+ def initialize
1847
+ self.country_options=["", "Canada", "US", "Mexico"]
1848
+ self.country = "Canada"
1849
+ end
1850
+
1851
+ def reset_country
1852
+ self.country = "Canada"
1853
+ end
1854
+ end
1855
+
1856
+ class HelloCombo
1857
+ include Glimmer
1858
+ def launch
1859
+ person = Person.new
1860
+ shell {
1861
+ composite {
1862
+ combo(:read_only) {
1863
+ selection bind(person, :country)
1864
+ }
1865
+ button {
1866
+ text "Reset"
1867
+ on_widget_selected do
1868
+ person.reset_country
1869
+ end
1870
+ }
1871
+ }
1872
+ }.open
1873
+ end
1874
+ end
1875
+
1876
+ HelloCombo.new.launch
1877
+ ```
1878
+
1879
+ `combo` widget is data-bound to the country of a person. Note that it expects the `person` object to have the `:country` attribute and `:country_options` attribute containing all available countries (aka options). Glimmer reads these attributes by convention.
1880
+
1881
+ #### List
1882
+
1883
+ Example from [samples/hello/hello_list_single_selection.rb](samples/hello_list_single_selection.rb) sample:
1884
+
1885
+ ![Hello List Single Selection](/images/glimmer-hello-list-single-selection.png)
1886
+
1887
+ ```ruby
1888
+ shell {
1889
+ composite {
1890
+ list {
1891
+ selection bind(person, :country)
1892
+ }
1893
+ button {
1894
+ text "Reset"
1895
+ on_widget_selected do
1896
+ person.reset_country
1897
+ end
1898
+ }
1899
+ }
1900
+ }.open
1901
+ ```
1902
+
1903
+ `list` widget is also data-bound to the country of a person similarly to the combo widget. Not much difference here (the rest of the code not shown is the same).
1904
+
1905
+ Nonetheless, in the next example, a multi-selection list is declared instead allowing data-binding of multiple selection values to the bindable attribute on the model.
1906
+
1907
+ Example from [samples/hello/hello_list_multi_selection.rb](samples/hello_list_multi_selection.rb) sample (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
1908
+
1909
+ ![Hello List Multi Selection](/images/glimmer-hello-list-multi-selection.png)
1910
+
1911
+ ```ruby
1912
+ class Person
1913
+ attr_accessor :provinces, :provinces_options
1914
+
1915
+ def initialize
1916
+ self.provinces_options=[
1917
+ "",
1918
+ "Quebec",
1919
+ "Ontario",
1920
+ "Manitoba",
1921
+ "Saskatchewan",
1922
+ "Alberta",
1923
+ "British Columbia",
1924
+ "Nova Skotia",
1925
+ "Newfoundland"
1926
+ ]
1927
+ self.provinces = ["Quebec", "Manitoba", "Alberta"]
1928
+ end
1929
+
1930
+ def reset_provinces
1931
+ self.provinces = ["Quebec", "Manitoba", "Alberta"]
1932
+ end
1933
+ end
1934
+
1935
+ class HelloListMultiSelection
1936
+ include Glimmer
1937
+ def launch
1938
+ person = Person.new
1939
+ shell {
1940
+ composite {
1941
+ list(:multi) {
1942
+ selection bind(person, :provinces)
1943
+ }
1944
+ button {
1945
+ text "Reset"
1946
+ on_widget_selected do
1947
+ person.reset_provinces
1948
+ end
1949
+ }
1950
+ }
1951
+ }.open
1952
+ end
1953
+ end
1954
+
1955
+ HelloListMultiSelection.new.launch
1956
+ ```
1957
+
1958
+ The Glimmer code is not much different from above except for passing the `:multi` style to the `list` widget. However, the model code behind the scenes is quite different as it is a `provinces` array bindable to the selection of multiple values on a `list` widget. `provinces_options` contains all available province values just as expected by a single selection `list` and `combo`.
1959
+
1960
+ Note that in all the data-binding examples above, there was also an observer attached to the `button` widget to trigger an action on the model, which in turn triggers a data-binding update on the `list` or `combo`. Observers will be discussed in more details in the [next section](#observer).
1961
+
1962
+ You may learn more about Glimmer's data-binding syntax by reading the code under the [samples](samples) directory.
1963
+
1964
+ #### Table
1965
+
1966
+ The SWT Tree widget renders a multi-column data table, such as a contact listing or a sales report.
1967
+
1968
+ To data-bind a Table, you need the main model, the collection property, and the text display attribute for each table column.
1969
+
1970
+ This involves using the `bind` keyword mentioned above in addition to a special `column_properties` keyword that takes the table column text attribute methods.
1971
+
1972
+ It assumes you have defined the table columns via `table_column` widget.
1973
+
1974
+ Example:
1975
+
1976
+ ```ruby
1977
+ shell {
1978
+ @table = table {
1979
+ table_column {
1980
+ text "Name"
1981
+ width 120
1982
+ }
1983
+ table_column {
1984
+ text "Age"
1985
+ width 120
1986
+ }
1987
+ table_column {
1988
+ text "Adult"
1989
+ width 120
1990
+ }
1991
+ items bind(group, :people), column_properties(:name, :age, :adult)
1992
+ selection bind(group, :selected_person)
1993
+ on_mouse_up { |event|
1994
+ @table.edit_table_item(event.table_item, event.column_index)
1995
+ }
1996
+ }
1997
+ }
1998
+ ```
1999
+
2000
+ The code above includes two data-bindings:
2001
+ - Table `items`, which first bind to the model collection property (group.people), and then maps each column property (name, age, adult) for displaying each table item column.
2002
+ - Table `selection`, which binds the single table item selected by the user to the attribute denoted by the `bind` keyword (or binds multiple table items selected for a table with `:multi` SWT style)
2003
+ - The `on_mouse_up` event handler invokes `@table.edit_table_item(event.table_item, event.column_index)` to start edit mode on the clicked table item cell, and then saves or cancel depending on whether the user hits ENTER or ESC once done editing (or focus-out after either making a change or not making any changes.)
2004
+
2005
+ Additionally, Table `items` data-binding automatically stores each node model unto the SWT TableItem object via `setData` method. This enables things like searchability.
2006
+
2007
+ The table widget in Glimmer is represented by a subclass of `WidgetProxy` called `TableProxy`.
2008
+ TableProxy includes a `search` method that takes a block to look for a table item.
2009
+
2010
+ Example:
2011
+
2012
+ ```ruby
2013
+ found_array = @table.search { |table_item| table_item.getData == company.owner }
2014
+ ```
2015
+
2016
+ This finds a person. The array is a Java array. This enables easy passing of it to SWT `Table#setSelection` method, which expects a Java array of `TableItem` objects.
2017
+
2018
+ To edit a table, you must invoke `TableProxy#edit_selected_table_item(column_index, before_write: nil, after_write: nil, after_cancel: nil)` or `TableProxy#edit_table_item(table_item, column_index, before_write: nil, after_write: nil, after_cancel: nil)`.
2019
+ This automatically leverages the SWT TableEditor custom class behind the scenes, displaying a text widget to the user to change the selected or
2020
+ passed table item text into something else.
2021
+ It automatically persists the change to `items` data-bound model on ENTER/FOCUS-OUT or cancels on ESC/NO-CHANGE.
2022
+
2023
+ ##### Table Selection
2024
+
2025
+ Table Selection data-binding is simply done via the `selection` property.
2026
+
2027
+ ```ruby
2028
+ selection bind(group, :selected_person)
2029
+ ```
2030
+
2031
+ If it's a multi-selection table (`table(:multi)`), then the data-bound model property oughta be a collection.
2032
+
2033
+ ```ruby
2034
+ selection bind(group, :selected_people)
2035
+ ```
2036
+
2037
+ ##### Table Editing
2038
+
2039
+ Glimmer provides a custom SWT style for table editing called `:editable` to obviate the need for an `on_mouse_up` listener.
2040
+
2041
+ For example, the code above could be simplified as:
2042
+
2043
+ ```ruby
2044
+ shell {
2045
+ @table = table(:editable) {
2046
+ table_column {
2047
+ text "Name"
2048
+ width 120
2049
+ }
2050
+ table_column {
2051
+ text "Age"
2052
+ width 120
2053
+ }
2054
+ table_column {
2055
+ text "Adult"
2056
+ width 120
2057
+ }
2058
+ items bind(group, :people), column_properties(:name, :age, :adult)
2059
+ selection bind(group, :selected_person)
2060
+ }
2061
+ }
2062
+ ```
2063
+
2064
+ Additionally, Glimmer supports the idea of custom editors or no editor per column.
2065
+
2066
+ Example:
2067
+
2068
+ ```ruby
2069
+ shell {
2070
+ @table = table(:editable) {
2071
+ table_column {
2072
+ text "Name"
2073
+ width 120
2074
+ }
2075
+ table_column {
2076
+ text "Age"
2077
+ width 120
2078
+ editor :spinner
2079
+ }
2080
+ table_column {
2081
+ text "Adult"
2082
+ width 120
2083
+ editor :checkbox
2084
+ }
2085
+ items bind(group, :people), column_properties(:name, :age, :adult)
2086
+ selection bind(group, :selected_person)
2087
+ }
2088
+ }
2089
+ ```
2090
+
2091
+ The example above uses a `spinner` widget editor for the age column since it's an `Integer` and
2092
+ a `checkbox` widget (`button(:check)`) editor for the adult column since it's a `Boolean`
2093
+
2094
+ Here are all the supported types of table editors:
2095
+ - `text`: expects a `String` property
2096
+ - `combo`: expects a `String` property accompanied by a matching `property_options` property by convention to provide items to present in the `combo`
2097
+ - `checkbox`: expects a `Boolean` property
2098
+ - `radio`: expects a `Boolean` property
2099
+ - `spinner`: expects an `Integer` property
2100
+ - `date`: expects a `DateTime` property
2101
+ - `date_drop_down`: expects a `DateTime` property
2102
+ - `time`: expects a `DateTime` property
2103
+
2104
+ An editor may also take additional arguments (SWT styles such as :long for the date field) that are passed to the editor widget, as well as hash options to
2105
+ customize the property being used for editing (e.g. property: :raw_name for a :formatted_name field) in case it differs from the property used to display
2106
+ the data in the table.
2107
+
2108
+ Example:
2109
+
2110
+ ```ruby
2111
+ shell {
2112
+ @table = table(:editable) {
2113
+ table_column {
2114
+ text "Date of Birth"
2115
+ width 120
2116
+ editor :date_drop_down, property: :date_time
2117
+ }
2118
+ table_column {
2119
+ text "Industry"
2120
+ width 120
2121
+ # assume there is a `Person#industry_options` property method on the model to provide items to the `combo`
2122
+ editor :combo, :read_only # passes :ready_only SWT style to `combo` widget
2123
+ }
2124
+ items bind(group, :people), column_properties(:formatted_date, :industry)
2125
+ selection bind(group, :selected_person)
2126
+ }
2127
+ }
2128
+ ```
2129
+
2130
+ Check out [Hello, Table!](#hello-table) for an actual example including table editors.
2131
+
2132
+ [Are We There Yet?](#are-we-there-yet) is an actual production Glimmer application that takes full advantage of table capabilities, storing model data in a database via ActiveRecord. As such, it's an excellent demonstration of how to use Glimmer DSL for SWT with a database.
2133
+
2134
+ ##### Table Sorting
2135
+
2136
+ Glimmer automatically adds sorting support to the SWT `Table` widget.
2137
+
2138
+ Check out the [Contact Manager](#contact-manager) sample for an example.
2139
+ You may click on any column and it will sort by ascending order first and descending if you click again.
2140
+
2141
+ Glimmer automatic table sorting supports `String`, `Integer`, and `Float` columns out of the box as well as any column data that is comparable.
2142
+
2143
+ In cases where data is nil, depending on the data-type, it is automatically converted to `Float` with `to_f`, `Integer` with `to_i`, or `String` with `to_s`.
2144
+
2145
+ Should you have a special data type that could not be compared automatically, Glimmer s the following 3 alternatives for custom sorting:
2146
+ - `sort_property`: this may be set to an alternative property to the one data-bound to the table column. For example, a table column called 'adult', which returns `true` or `false` may be sorted with `sort_property :dob` instead. This also support multi-property (aka multi-column) sorting (e.g. `sort_property :dob, :name`).
2147
+ - `sort_by(&block)`: this works just like Ruby `Enumerable` `sort_by`. The block receives the table column data as argument.
2148
+ - `sort(&comparator)`: this works just like Ruby `Enumerable` `sort`. The comparator block receives two objects from the table column data.
2149
+
2150
+ These alternatives could be used inside `table_column` for column-clicked sorting or in the `table` body directly to set the initial default sort.
2151
+
2152
+ You may also set `additional_sort_properties` on the parent `table` widget to have secondary sorting applied. For example, if you set `additional_sort_properties :name, :project_name`, then whenever you sort by `:name`, it additionally sorts by `:project_name` afterwards, and vice versa. This only works for columns that either have no custom sort set or have a `sort_property` with one property only (but no sort or sort_by block)
2153
+
2154
+ Example:
2155
+
2156
+ ```ruby
2157
+ # ...
2158
+ table {
2159
+ table_column {
2160
+ text 'Task'
2161
+ width 120
2162
+ }
2163
+ table_column {
2164
+ text 'Project'
2165
+ width 120
2166
+ }
2167
+ table_column {
2168
+ text 'Duration (hours)'
2169
+ width 120
2170
+ sort_property :duration_in_hours
2171
+ }
2172
+ table_column {
2173
+ text 'Priority'
2174
+ width 120
2175
+ sort_by { |value| ['High', 'Medium', 'Low'].index(value) }
2176
+ }
2177
+ table_column {
2178
+ text 'Start Date'
2179
+ width 120
2180
+ sort { |d1, d2| d1.to_date <=> d2.to_date }
2181
+ }
2182
+ additional_sort_properties :project_name, :duration_in_hours, :name
2183
+ items bind(Task, :list), column_properties(:name, :project_name, :duration, :priority, :start_date)
2184
+ # ...
2185
+ }
2186
+ # ...
2187
+ ```
2188
+
2189
+ Here is an explanation of the example above:
2190
+ - Task and Project table columns are data-bound to the `:name` and `:project_name` properties and sorted through them automatically
2191
+ - Task Duration table column is data-bound to the `:duration` property, but sorted via the `:duration_in_hours` property instead
2192
+ - Task Priority table column has a custom sort_by block
2193
+ - Task Start Date table column has a custom sort comparator block
2194
+ - Additional (secondary) sort properties are applied when sorting by Task, Project, or Duration in the order specified
2195
+
2196
+ `bind(model, :property, read_only_sort: true)` could be used with items to make sorting not propagate sorting changes to model.
2197
+
2198
+ #### Tree
2199
+
2200
+ The SWT Tree widget visualizes a tree data-structure, such as an employment or composition hierarchy.
2201
+
2202
+ To data-bind a Tree, you need the root model, the children querying method, and the text display attribute on each child.
2203
+
2204
+ This involves using the `bind` keyword mentioned above in addition to a special `tree_properties` keyword that takes the children and text attribute methods.
2205
+
2206
+ Example:
2207
+
2208
+ ```ruby
2209
+ shell {
2210
+ @tree = tree {
2211
+ items bind(company, :owner), tree_properties(children: :coworkers, text: :name)
2212
+ selection bind(company, :selected_coworker)
2213
+ }
2214
+ }
2215
+ ```
2216
+
2217
+ The code above includes two data-bindings:
2218
+ - Tree `items`, which first bind to the root node (company.owner), and then dig down via `coworkers` `children` method, using the `name` `text` attribute for displaying each tree item.
2219
+ - Tree `selection`, which binds the single tree item selected by the user to the attribute denoted by the `bind` keyword
2220
+
2221
+ Additionally, Tree `items` data-binding automatically stores each node model unto the SWT TreeItem object via `setData` method. This enables things like searchability.
2222
+
2223
+ The tree widget in Glimmer is represented by a subclass of `WidgetProxy` called `TreeProxy`.
2224
+ TreeProxy includes a `depth_first_search` method that takes a block to look for a tree item.
2225
+
2226
+ Example:
2227
+
2228
+ ```ruby
2229
+ found_array = @tree.depth_first_search { |tree_item| tree_item.getData == company.owner }
2230
+ ```
2231
+
2232
+ This finds the root node. The array is a Java array. This enables easy passing of it to SWT `Tree#setSelection` method, which expects a Java array of `TreeItem` objects.
2233
+
2234
+ To edit a tree, you must invoke `TreeProxy#edit_selected_tree_item` or `TreeProxy#edit_tree_item`. This automatically leverages the SWT TreeEditor custom class behind the scenes, displaying
2235
+ a text widget to the user to change the selected or passed tree item text into something else. It automatically persists the change to `items` data-bound model on ENTER/FOCUS-OUT or cancels on ESC/NO-CHANGE.
2236
+
2237
+ #### DateTime
2238
+
2239
+ `date_time` represents the SWT DateTime widget.
2240
+
2241
+ Glimmer s the following alias keywords for it for convenience:
2242
+ - `date`: `date_time(:date)`
2243
+ - `date_drop_down`: `date_time(:date, :drop_down)`
2244
+ - `time`: `date_time(:time)`
2245
+ - `calendar`: `date_time(:calendar)`
2246
+
2247
+ You can data-bind any of these properties:
2248
+ - `date_time bind(model, :property)`: produces a Ruby DateTime object
2249
+ - `date bind(model, :property)`: produces a Ruby Date object
2250
+ - `time bind(model, :property)`: produces a Ruby Time object
2251
+ - `year bind(model, :property)`: produces an integer
2252
+ - `month bind(model, :property)`: produces an integer
2253
+ - `day bind(model, :property)`: produces an integer
2254
+ - `hours bind(model, :property)`: produces an integer
2255
+ - `minutes bind(model, :property)`: produces an integer
2256
+ - `seconds bind(model, :property)`: produces an integer
2257
+
2258
+ Learn more at the [Hello, Date Time!](#hello-date-time) sample.
2259
+
2260
+ If you need a better widget with the ability to customize the date format pattern, check out the [Nebula CDateTime Glimmer Custom Widget](https://github.com/AndyObtiva/glimmer-cw-cdatetime-nebula)
2261
+
2262
+ ### Observer
2263
+
2264
+ Glimmer comes with the `Observer` mixin module, which is used internally for data-binding, but can also be used externally for custom use of the Observer Pattern. It is hidden when observing widgets, and used explicitly when observing models. In bidirectional data-binding, `Observer` is automatically unregistered from models once a widget is disposed to avoid memory leaks and worrying about managing them yourself.
2265
+
2266
+ #### Observing Widgets
2267
+
2268
+ Glimmer supports observing widgets with two main types of events:
2269
+ 1. `on_{swt-listener-method-name}`: where {swt-listener-method-name} is replaced with the lowercase underscored event method name on an SWT listener class (e.g. `on_verify_text` for `org.eclipse.swt.events.VerifyListener#verifyText`).
2270
+ 2. `on_swt_{swt-event-constant}`: where {swt-event-constant} is replaced with an [`org.eclipse.swt.SWT`](https://help.eclipse.org/2020-06/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/SWT.html) event constant (e.g. `on_swt_show` for `SWT.Show` to observe when widget becomes visible)
2271
+
2272
+ Additionally, there are two more types of events:
2273
+ - SWT `display` supports global listeners called filters that run on any widget. They are hooked via `on_swt_{swt-event-constant}`
2274
+ - SWT `display` supports Mac application menu item observers (`on_about` and `on_preferences`), which you can read about under [Miscellaneous](#miscellaneous).
2275
+
2276
+ Number 1 is more commonly used in SWT applications, so make it your starting point. Number 2 covers events not found in number 1, so look into it if you don't find an SWT listener you need in number 1.
2277
+
2278
+ **Regarding number 1**, to figure out what the available events for an SWT widget are, check out all of its `add***Listener` API methods, and then open the listener class argument to check its "event methods".
2279
+
2280
+ For example, if you look at the `Button` SWT API:
2281
+ https://help.eclipse.org/2019-12/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fswt%2Fbrowser%2FBrowser.html
2282
+
2283
+ It has `addSelectionListener`. Additionally, under its `Control` super class, it has `addControlListener`, `addDragDetectListener`, `addFocusListener`, `addGestureListener`, `addHelpListener`, `addKeyListener`, `addMenuDetectListener`, `addMouseListener`, `addMouseMoveListener`, `addMouseTrackListener`, `addMouseWheelListener`, `addPaintListener`, `addTouchListener`, and `addTraverseListener`
2284
+
2285
+ Suppose, we select `addSelectionListener`, which is responsible for what happens when a user selects a button (clicks it). Then, open its argument `SelectionListener` SWT API, and you find the event (instance) methods: `widgetDefaultSelected` and `widgetSelected`. Let's select the second one, which is what gets invoked when a button is clicked.
2286
+
2287
+ Now, Glimmer simplifies the process of hooking into that listener (observer) by neither requiring you to call the `addSelectionListener` method nor requiring you to implement/extend the `SelectionListener` API.
2288
+
2289
+ Instead, simply add a `on_widget_selected` followed by a Ruby block containing the logic to perform. Glimmer figures out the rest.
2290
+
2291
+ Let's revisit the Tic Tac Toe example shown near the beginning of the page:
2292
+
2293
+ ```ruby
2294
+ shell {
2295
+ text "Tic-Tac-Toe"
2296
+ minimum_size 150, 178
2297
+ composite {
2298
+ grid_layout 3, true
2299
+ (1..3).each { |row|
2300
+ (1..3).each { |column|
2301
+ button {
2302
+ layout_data :fill, :fill, true, true
2303
+ text bind(@tic_tac_toe_board[row, column], :sign)
2304
+ enabled bind(@tic_tac_toe_board[row, column], :empty)
2305
+ on_widget_selected {
2306
+ @tic_tac_toe_board.mark(row, column)
2307
+ }
2308
+ }
2309
+ }
2310
+ }
2311
+ }
2312
+ }
2313
+ ```
2314
+
2315
+ Note that every Tic Tac Toe grid cell has its `text` and `enabled` properties data-bound to the `sign` and `empty` attributes on the `TicTacToe::Board` model respectively.
2316
+
2317
+ Next however, each of these Tic Tac Toe grid cells, which are clickable buttons, have an `on_widget_selected` observer, which once triggered, marks the cell on the `TicTacToe::Board` to make a move.
2318
+
2319
+ **Regarding number 2**, you can figure out all available events by looking at the [`org.eclipse.swt.SWT`](https://help.eclipse.org/2020-06/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/SWT.html) API:
2320
+
2321
+ https://help.eclipse.org/2019-12/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/SWT.html
2322
+
2323
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
2324
+
2325
+ `SWT.Show` - hooks a listener for showing a widget (using `on_swt_show` in Glimmer)
2326
+ `SWT.Hide` - hooks a listener for hiding a widget (using `on_swt_hide` in Glimmer)
2327
+
2328
+ ```ruby
2329
+ shell {
2330
+ @button1 = button {
2331
+ text "Show 2nd Button"
2332
+ visible true
2333
+ on_swt_show {
2334
+ @button2.swt_widget.setVisible(false)
2335
+ }
2336
+ on_widget_selected {
2337
+ @button2.swt_widget.setVisible(true)
2338
+ }
2339
+ }
2340
+ @button2 = button {
2341
+ text "Show 1st Button"
2342
+ visible false
2343
+ on_swt_show {
2344
+ @button1.swt_widget.setVisible(false)
2345
+ }
2346
+ on_widget_selected {
2347
+ @button1.swt_widget.setVisible(true)
2348
+ }
2349
+ }
2350
+ }.open
2351
+ ```
2352
+
2353
+ **Gotcha:** SWT.Resize event needs to be hooked using **`on_swt_Resize`** because [`org.eclipse.swt.SWT`](https://help.eclipse.org/2020-06/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/SWT.html) has 2 constants for resize: `RESIZE` and `Resize`, so it cannot infer the right one automatically from the underscored version `on_swt_resize`
2354
+
2355
+ ##### Alternative Syntax
2356
+
2357
+ Instead of declaring a widget observer using `on_***` syntax inside a widget content block, you may also do so after the widget declaration by invoking directly on the widget object.
2358
+
2359
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
2360
+
2361
+ ```
2362
+ @shell = shell {
2363
+ label {
2364
+ text "Hello, World!"
2365
+ }
2366
+ }
2367
+ @shell.on_shell_iconified {
2368
+ @shell.close
2369
+ }
2370
+ @shell.open
2371
+ ```
2372
+
2373
+ The shell declared above has been modified so that the minimize button works just like the close button. Once you minimize the shell (iconify it), it closes.
2374
+
2375
+ The alternative syntax can be helpful if you prefer to separate Glimmer observer declarations from Glimmer GUI declarations, or would like to add observers dynamically based on some logic later on.
2376
+
2377
+ #### Observing Models
2378
+
2379
+ Glimmer DSL includes an `observe` keyword used to register an observer by passing in the observable and the property(ies) to observe, and then specifying in a block what happens on notification.
2380
+
2381
+ ```ruby
2382
+ class TicTacToe
2383
+ include Glimmer
2384
+
2385
+ def initialize
2386
+ # ...
2387
+ observe(@tic_tac_toe_board, :game_status) { |game_status|
2388
+ display_win_message if game_status == Board::WIN
2389
+ display_draw_message if game_status == Board::DRAW
2390
+ }
2391
+ end
2392
+ # ...
2393
+ end
2394
+ ```
2395
+
2396
+ Observers can be a good mechanism for displaying dialog messages in Glimmer (using SWT's [`MessageBox`](https://help.eclipse.org/2020-06/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/MessageBox.html) class).
2397
+
2398
+ Look at [`samples/elaborate/tictactoe/tic_tac_toe.rb`](samples/tictactoe/tic_tac_toe.rb) for more details starting with the code included below.
2399
+
2400
+ ```ruby
2401
+ class TicTacToe
2402
+ include Glimmer
2403
+ include Observer
2404
+
2405
+ def initialize
2406
+ # ...
2407
+ observe(@tic_tac_toe_board, :game_status) { |game_status|
2408
+ display_win_message if game_status == Board::WIN
2409
+ display_draw_message if game_status == Board::DRAW
2410
+ }
2411
+ end
2412
+
2413
+ def display_win_message
2414
+ display_game_over_message("Player #{@tic_tac_toe_board.winning_sign} has won!")
2415
+ end
2416
+
2417
+ def display_draw_message
2418
+ display_game_over_message("Draw!")
2419
+ end
2420
+
2421
+ def display_game_over_message(message)
2422
+ message_box(@shell) {
2423
+ text 'Game Over'
2424
+ message message_text
2425
+ }.open
2426
+ @tic_tac_toe_board.reset
2427
+ end
2428
+ # ...
2429
+ end
2430
+ ```
2431
+
2432
+ ### Custom Widgets
2433
+
2434
+ Custom widgets are brand new Glimmer DSL keywords that represent aggregates of existing widgets (e.g. `address_form`), customized existing widgets (e.g. `greeting_label`), or brand new widgets (e.g. `oscilloscope`)
2435
+
2436
+ You can find out about [published Glimmer Custom Widgets](https://github.com/AndyObtiva/glimmer-dsl-swt#gem-listing) by running the `glimmer list:gems:customwidget` command
2437
+
2438
+ Glimmer supports three ways of creating custom widgets with minimal code:
2439
+ 1. Method-based Custom Widgets (for single-view-internal reuse): Extract a method containing Glimmer DSL widget syntax. Useful for quickly eliminating redundant code within a single view.
2440
+ 2. Class-based Custom Widgets (for multiple-view-external reuse): Create a class that includes the `Glimmer::UI::CustomWidget` module and Glimmer DSL widget syntax in a `body {}` block. This will automatically extend Glimmer's DSL syntax with an underscored lowercase keyword matching the class name by convention. Useful in making a custom widget available in many views.
2441
+
2442
+ Approach #1 is a casual Ruby-based approach. Approach #2 is the official Glimmer approach. Typically, when referring to Custom Widgets, we are talking about Class-based Custom Widgets.
2443
+
2444
+ A developer might start with approach #1 to eliminate duplication in a view and later upgrade it to approach #2 when needing to export a custom widget to make it available in many views.
2445
+
2446
+ Class-based Custom Widgets a number of benefits over method-based custom widgets, such as built-in support for passing SWT style, nested block of extra widgets and properties, and `before_body`/`after_body` hooks.
2447
+
2448
+ #### Simple Example
2449
+
2450
+ ##### Method-Based Custom Widget Example
2451
+
2452
+ (you may copy/paste in [`girb`](GLIMMER_GIRB.md))
2453
+
2454
+ Definition and usage in the same file:
2455
+ ```ruby
2456
+ def red_label(label_text)
2457
+ label {
2458
+ text label_text
2459
+ background :red
2460
+ }
2461
+ end
2462
+
2463
+ shell {
2464
+ red_label('Red Label')
2465
+ }.open
2466
+ ```
2467
+
2468
+ ##### Class-Based Custom Widget Example
2469
+
2470
+ Simply create a new class that includes `Glimmer::UI::CustomWidget` and put Glimmer DSL code in its `#body` block (its return value is stored in `#body_root` attribute). Glimmer will then automatically recognize this class by convention when it encounters a keyword matching the class name converted to underscored lowercase (and namespace double-colons `::` replaced with double-underscores `__`)
2471
+
2472
+ (you may copy/paste in [`girb`](GLIMMER_GIRB.md))
2473
+
2474
+ Definition:
2475
+ ```ruby
2476
+ class RedLabel
2477
+ include Glimmer::UI::CustomWidget
2478
+
2479
+ body {
2480
+ label(swt_style) {
2481
+ background :red
2482
+ }
2483
+ }
2484
+ end
2485
+ ```
2486
+
2487
+ Usage:
2488
+ ```ruby
2489
+ shell {
2490
+ red_label(:center) {
2491
+ text 'Red Label'
2492
+ foreground :green
2493
+ }
2494
+ }.open
2495
+ ```
2496
+
2497
+ As you can see, `RedLabel` became the Glimmer DSL keyword `red_label` and worked just like a standard label by taking in SWT style and nested properties. As such, it is a first-class citizen of the Glimmer GUI DSL.
2498
+
2499
+ #### Custom Widget Lifecycle Hooks
2500
+
2501
+ You may execute code before or after evaluating the body with these lifecycle hooks:
2502
+ - `before_body`: takes a block that executes in the custom widget instance scope before calling `body`. Useful for initializing variables to later use in `body`
2503
+ - `after_body`: takes a block that executes in the custom widget instance scope after calling `body`. Useful for setting up observers on widgets built in `body` (set in instance variables) and linking to other shells.
2504
+
2505
+ #### Lifecycle Hooks Example
2506
+
2507
+ (you may copy/paste in [`girb`](GLIMMER_GIRB.md))
2508
+
2509
+ Definition:
2510
+ ```ruby
2511
+ module Red
2512
+ class Composite
2513
+ include Glimmer::UI::CustomWidget
2514
+
2515
+ before_body {
2516
+ @color = :red
2517
+ }
2518
+
2519
+ body {
2520
+ composite(swt_style) {
2521
+ background @color
2522
+ }
2523
+ }
2524
+ end
2525
+ end
2526
+ ```
2527
+
2528
+ Usage:
2529
+ ```ruby
2530
+ shell {
2531
+ red__composite {
2532
+ label {
2533
+ foreground :white
2534
+ text 'This is showing inside a Red Composite'
2535
+ }
2536
+ }
2537
+ }.open
2538
+ ```
2539
+
2540
+ Notice how `Red::Composite` became `red__composite` with double-underscore, which is how Glimmer Custom Widgets signify namespaces by convention. Additionally, the `before_body` lifecycle hook was utilized to set a `@color` variable and use inside the `body`.
2541
+
2542
+ Keep in mind that namespaces are not needed to be specified if the Custom Widget class has a unique name, not clashing with a basic SWT widget or another custom widget name.
2543
+
2544
+ #### Custom Widget API
2545
+
2546
+ Custom Widgets have the following attributes available to call from inside the `#body` method:
2547
+ - `#parent`: Glimmer object parenting custom widget
2548
+ - `#swt_style`: SWT style integer. Can be useful if you want to allow consumers to customize a widget inside the custom widget body
2549
+ - `#options`: a hash of options passed in parentheses when declaring a custom widget (useful for passing in model data) (e.g. `calendar(events: events)`). Custom widget class can declare option names (array) with `::options` class method as shown below, which generates attribute accessors for every option (not to be confused with `#options` instance method for retrieving options hash containing names & values)
2550
+ - `#content`: nested block underneath custom widget. It will be automatically called at the end of processing the custom widget body. Alternatively, the custom widget body may call `content.call` at the place where the content is needed to show up as shown in the following example.
2551
+ - `#body_root`: top-most (root) widget returned from `#body` method.
2552
+ - `#swt_widget`: actual SWT widget for `body_root`
2553
+
2554
+ Additionally, custom widgets can call the following class methods:
2555
+ - `::options(*option_names)`: declares a list of options by taking an option name array (symbols/strings). This generates option attribute accessors (e.g. `options :orientation, :bg_color` generates `#orientation`, `#orientation=(v)`, `#bg_color`, and `#bg_color=(v)` attribute accessors)
2556
+ - `::option(option_name, default: nil)`: declares a single option taking option name and default value as arguments (also generates attribute accessors just like `::options`)
2557
+
2558
+ #### Content/Options Example
2559
+
2560
+ (you may copy/paste in [`girb`](GLIMMER_GIRB.md))
2561
+
2562
+ Definition:
2563
+ ```ruby
2564
+ class Sandwich
2565
+ include Glimmer::UI::CustomWidget
2566
+
2567
+ options :orientation, :bg_color
2568
+ option :fg_color, default: :black
2569
+
2570
+ body {
2571
+ composite(swt_style) { # gets custom widget style
2572
+ fill_layout orientation # using orientation option
2573
+ background bg_color # using container_background option
2574
+ label {
2575
+ text 'SANDWICH TOP'
2576
+ }
2577
+ content.call # this is where content block is called
2578
+ label {
2579
+ text 'SANDWICH BOTTOM'
2580
+ }
2581
+ }
2582
+ }
2583
+ end
2584
+ ```
2585
+
2586
+ Usage:
2587
+ ```ruby
2588
+ shell {
2589
+ sandwich(:no_focus, orientation: :vertical, bg_color: :red) {
2590
+ label {
2591
+ background :green
2592
+ text 'SANDWICH CONTENT'
2593
+ }
2594
+ }
2595
+ }.open
2596
+ ```
2597
+
2598
+ Notice how `:no_focus` was the `swt_style` value, followed by the `options` hash `{orientation: :horizontal, bg_color: :white}`, and finally the `content` block containing the label with `'SANDWICH CONTENT'`
2599
+
2600
+ #### Custom Widget Gotchas
2601
+
2602
+ Beware of defining a custom attribute that is a common SWT widget property name.
2603
+ For example, if you define `text=` and `text` methods to accept a custom text and then later you write this body:
2604
+
2605
+ ```ruby
2606
+ # ...
2607
+ def text
2608
+ # ...
2609
+ end
2610
+
2611
+ def text=(value)
2612
+ # ...
2613
+ end
2614
+
2615
+ body {
2616
+ composite {
2617
+ label {
2618
+ text "Hello"
2619
+ }
2620
+ label {
2621
+ text "World"
2622
+ }
2623
+ }
2624
+ }
2625
+ # ...
2626
+ ```
2627
+
2628
+ The `text` method invoked in the custom widget body will call the one you defined above it. To avoid this gotcha, simply name the text property above something else, like `custom_text`.
2629
+
2630
+ #### Built-In Custom Widgets
2631
+
2632
+ ##### Checkbox Group Custom Widget
2633
+
2634
+ `checkbox_group` (or alias `check_group`) is a Glimmer built-in custom widget that displays a list of `checkbox` buttons (`button(:check)`) based on its `items` property.
2635
+
2636
+ `checkbox_group` consists of a root `composite` (with `grid_layout 1, false` by default) that holds nested `checkbox` (`button(:check)`) widgets.
2637
+
2638
+ The `selection` property determines which `checkbox` buttons are checked. It expects an `Array` of `String` objects
2639
+ The `selection_indices` property determines which `checkbox` button indices are checked. It expects an `Array` of index `Integer` objects that are zero-based.
2640
+ The `checkboxes` property returns the list of nested `checkbox` widgets.
2641
+
2642
+ When data-binding `selection`, the model property should have a matching property with `_options` suffix (e.g. `activities_options` for `activities`) to provide an `Array` of `String` objects for `checkbox` buttons.
2643
+
2644
+ You may see an example at the [Hello, Checkbox Group!](#hello-checkbox-group) sample.
2645
+
2646
+ ![Hello Checkbox Group](/images/glimmer-hello-checkbox-group.png)
2647
+
2648
+ ##### Radio Group Custom Widget
2649
+
2650
+ `radio_group` is a Glimmer built-in custom widget that displays a list of `radio` buttons (`button(:radio)`) based on its `items` property, which expects an `Array` of `String` objects.
2651
+
2652
+ `radio_group` consists of a root `composite` (with `grid_layout 1, false` by default) that holds nested `radio` widgets.
2653
+
2654
+ The `selection` property determines which `radio` button is selected. It expects a `String`
2655
+ The `selection_index` property determines which `radio` button index is selected. It expects an index integer that is zero-based.
2656
+ The `radios` property returns the list of nested `radio` widgets.
2657
+
2658
+ When data-binding `selection`, the model property should have a matching property with `_options` suffix (e.g. `country_options` for `country`) to provide text for `radio` buttons.
2659
+
2660
+ This custom widget is used in the [Glimmer Meta-Sample (The Sample of Samples)](#samples):
2661
+
2662
+ ![Glimmer Meta-Sample](/images/glimmer-meta-sample.png)
2663
+
2664
+ Glimmer Meta-Sample Code Example:
2665
+
2666
+ ```ruby
2667
+ # ...
2668
+ radio_group { |radio_group_proxy|
2669
+ row_layout(:vertical) {
2670
+ fill true
2671
+ }
2672
+ selection bind(sample_directory, :selected_sample_name)
2673
+ font height: 24
2674
+ }
2675
+
2676
+ # ...
2677
+ ```
2678
+
2679
+ You may see another example at the [Hello, Radio Group!](#hello-radio-group) sample.
2680
+
2681
+ ##### Code Text Custom Widget
2682
+
2683
+ **(BETA FEATURE)**
2684
+
2685
+ `code_text` is a Glimmer built-in custom widget that displays syntax highlighted Ruby code in a customized SWT [StyledText](https://help.eclipse.org/2020-09/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/custom/StyledText.html) widget.
2686
+
2687
+ It is used in the [Glimmer Meta-Sample (The Sample of Samples)](#samples):
2688
+
2689
+ ![Glimmer Meta-Sample](/images/glimmer-meta-sample.png)
2690
+
2691
+ Glimmer Meta-Sample Code Example:
2692
+
2693
+ ```ruby
2694
+ # ...
2695
+ @code_text = code_text {
2696
+ text bind(SampleDirectory, 'selected_sample.code', read_only: true)
2697
+ editable bind(SampleDirectory, 'selected_sample.editable')
2698
+ }
2699
+ # ...
2700
+ ```
2701
+
2702
+ To use, simply use `code_text` in place of the `text` or `styled_text` widget. If you set its `text` value to Ruby code, it automatically styles it with syntax highlighting.
2703
+
2704
+ ###### Options
2705
+
2706
+ **lines**
2707
+ (default: `false`)
2708
+
2709
+ Shows line numbers when set to true.
2710
+
2711
+ If set to a hash like `{width: 4}`, it sets the initial width of the line numbers lane in character count (default: 4)
2712
+
2713
+ Keep in mind that if the text grows and required a wider line numbers area, it grows automatically regardless of initial width.
2714
+
2715
+ **theme**
2716
+ (default: `'glimmer'`)
2717
+
2718
+ Changes syntax color highlighting theme. Can be one of the following:
2719
+ - glimmer
2720
+ - github
2721
+ - pastie
2722
+
2723
+ **language**
2724
+ (default: `'ruby'`)
2725
+
2726
+ Sets the code language, which can be one of the following [rouge gem](#https://rubygems.org/gems/rouge) supported languages:
2727
+ - abap
2728
+ - actionscript
2729
+ - ada
2730
+ - apache
2731
+ - apex
2732
+ - apiblueprint
2733
+ - apple_script
2734
+ - armasm
2735
+ - augeas
2736
+ - awk
2737
+ - batchfile
2738
+ - bbcbasic
2739
+ - bibtex
2740
+ - biml
2741
+ - bpf
2742
+ - brainfuck
2743
+ - brightscript
2744
+ - bsl
2745
+ - c
2746
+ - ceylon
2747
+ - cfscript
2748
+ - clean
2749
+ - clojure
2750
+ - cmake
2751
+ - cmhg
2752
+ - coffeescript
2753
+ - common_lisp
2754
+ - conf
2755
+ - console
2756
+ - coq
2757
+ - cpp
2758
+ - crystal
2759
+ - csharp
2760
+ - css
2761
+ - csvs
2762
+ - cuda
2763
+ - cypher
2764
+ - cython
2765
+ - d
2766
+ - dart
2767
+ - datastudio
2768
+ - diff
2769
+ - digdag
2770
+ - docker
2771
+ - dot
2772
+ - ecl
2773
+ - eex
2774
+ - eiffel
2775
+ - elixir
2776
+ - elm
2777
+ - email
2778
+ - epp
2779
+ - erb
2780
+ - erlang
2781
+ - escape
2782
+ - factor
2783
+ - fortran
2784
+ - freefem
2785
+ - fsharp
2786
+ - gdscript
2787
+ - ghc_cmm
2788
+ - ghc_core
2789
+ - gherkin
2790
+ - glsl
2791
+ - go
2792
+ - gradle
2793
+ - graphql
2794
+ - groovy
2795
+ - hack
2796
+ - haml
2797
+ - handlebars
2798
+ - haskell
2799
+ - haxe
2800
+ - hcl
2801
+ - hlsl
2802
+ - hocon
2803
+ - hql
2804
+ - html
2805
+ - http
2806
+ - hylang
2807
+ - idlang
2808
+ - igorpro
2809
+ - ini
2810
+ - io
2811
+ - irb
2812
+ - isbl
2813
+ - j
2814
+ - janet
2815
+ - java
2816
+ - javascript
2817
+ - jinja
2818
+ - jsl
2819
+ - json
2820
+ - json_doc
2821
+ - jsonnet
2822
+ - jsp
2823
+ - jsx
2824
+ - julia
2825
+ - kotlin
2826
+ - lasso
2827
+ - liquid
2828
+ - literate_coffeescript
2829
+ - literate_haskell
2830
+ - livescript
2831
+ - llvm
2832
+ - lua
2833
+ - lustre
2834
+ - lutin
2835
+ - m68k
2836
+ - magik
2837
+ - make
2838
+ - markdown
2839
+ - mason
2840
+ - mathematica
2841
+ - matlab
2842
+ - minizinc
2843
+ - moonscript
2844
+ - mosel
2845
+ - msgtrans
2846
+ - mxml
2847
+ - nasm
2848
+ - nesasm
2849
+ - nginx
2850
+ - nim
2851
+ - nix
2852
+ - objective_c
2853
+ - objective_cpp
2854
+ - ocaml
2855
+ - ocl
2856
+ - openedge
2857
+ - opentype_feature_file
2858
+ - pascal
2859
+ - perl
2860
+ - php
2861
+ - plain_text
2862
+ - plist
2863
+ - pony
2864
+ - postscript
2865
+ - powershell
2866
+ - praat
2867
+ - prolog
2868
+ - prometheus
2869
+ - properties
2870
+ - protobuf
2871
+ - puppet
2872
+ - python
2873
+ - q
2874
+ - qml
2875
+ - r
2876
+ - racket
2877
+ - reasonml
2878
+ - rego
2879
+ - rescript
2880
+ - robot_framework
2881
+ - ruby
2882
+ - rust
2883
+ - sas
2884
+ - sass
2885
+ - scala
2886
+ - scheme
2887
+ - scss
2888
+ - sed
2889
+ - shell
2890
+ - sieve
2891
+ - slice
2892
+ - slim
2893
+ - smalltalk
2894
+ - smarty
2895
+ - sml
2896
+ - solidity
2897
+ - sparql
2898
+ - sqf
2899
+ - sql
2900
+ - ssh
2901
+ - supercollider
2902
+ - swift
2903
+ - systemd
2904
+ - tap
2905
+ - tcl
2906
+ - terraform
2907
+ - tex
2908
+ - toml
2909
+ - tsx
2910
+ - ttcn3
2911
+ - tulip
2912
+ - turtle
2913
+ - twig
2914
+ - typescript
2915
+ - vala
2916
+ - varnish
2917
+ - vb
2918
+ - velocity
2919
+ - verilog
2920
+ - vhdl
2921
+ - viml
2922
+ - vue
2923
+ - wollok
2924
+ - xml
2925
+ - xojo
2926
+ - xpath
2927
+ - xquery
2928
+ - yaml
2929
+ - yang
2930
+ - zig
2931
+
2932
+ **default_behavior**
2933
+ (default: true)
2934
+
2935
+ This adds some default keyboard shortcuts:
2936
+ - CMD+A (CTRL+A on Windows/Linux) to select all
2937
+ - CTRL+A on Mac to jump to beginning of line
2938
+ - CTRL+E on Mac to jump to end of line
2939
+
2940
+ If you prefer it to be vanilla with no default key event listeners, then pass the `default_behavior: false` option.
2941
+
2942
+ Learn more at [Hello, Code Text!](#hello-code-text)
2943
+
2944
+ ##### Video Custom Custom Widget
2945
+
2946
+ [![Video Widget](/images/glimmer-video-widget.png)](https://github.com/AndyObtiva/glimmer-cw-video)
2947
+
2948
+ Glimmer supports a [video custom widget](https://github.com/AndyObtiva/glimmer-cw-video) not in SWT, which was originally a Glimmer built-in custom widget, but has been later extracted into its own [Ruby gem](https://rubygems.org/gems/glimmer-cw-video).
2949
+
2950
+ Simply install the [glimmer-cw-video](https://rubygems.org/gems/glimmer-cw-video) gem.
2951
+
2952
+ #### Custom Widget Final Notes
2953
+
2954
+ This [Eclipse guide](https://www.eclipse.org/articles/Article-Writing%20Your%20Own%20Widget/Writing%20Your%20Own%20Widget.htm) for how to write custom SWT widgets is also applicable to Glimmer Custom Widgets written in Ruby. I recommend reading it:
2955
+ [https://www.eclipse.org/articles/Article-Writing%20Your%20Own%20Widget/Writing%20Your%20Own%20Widget.htm](https://www.eclipse.org/articles/Article-Writing%20Your%20Own%20Widget/Writing%20Your%20Own%20Widget.htm)
2956
+
2957
+ Also, you may check out [Hello, Custom Widget!](#hello-custom-widget) for another example.
2958
+
2959
+ ### Custom Shells
2960
+
2961
+ Custom shells are a kind of custom widgets that have shells only as the body root. They can be self-contained applications that may be opened and hidden/closed independently of the main app.
2962
+
2963
+ They may also be chained in a wizard fashion.
2964
+
2965
+ You can find out about [published Glimmer Custom Shells](https://github.com/AndyObtiva/glimmer-dsl-swt#gem-listing) by running the `glimmer list:gems:customshell` command
2966
+
2967
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
2968
+
2969
+ ```ruby
2970
+ class WizardStep
2971
+ include Glimmer::UI::CustomShell
2972
+
2973
+ options :number, :step_count
2974
+
2975
+ before_body {
2976
+ @title = "Step #{number}"
2977
+ }
2978
+
2979
+ body {
2980
+ shell {
2981
+ text "Wizard - #{@title}"
2982
+ minimum_size 200, 100
2983
+ fill_layout :vertical
2984
+ label(:center) {
2985
+ text @title
2986
+ font height: 30
2987
+ }
2988
+ if number < step_count
2989
+ button {
2990
+ text "Go To Next Step"
2991
+ on_widget_selected {
2992
+ body_root.hide
2993
+ }
2994
+ }
2995
+ end
2996
+ }
2997
+ }
2998
+ end
2999
+
3000
+ shell { |app_shell|
3001
+ text "Wizard"
3002
+ minimum_size 200, 100
3003
+ @current_step_number = 1
3004
+ @wizard_steps = 5.times.map { |n|
3005
+ wizard_step(number: n+1, step_count: 5) {
3006
+ on_swt_hide {
3007
+ if @current_step_number < 5
3008
+ @current_step_number += 1
3009
+ app_shell.hide
3010
+ @wizard_steps[@current_step_number - 1].open
3011
+ end
3012
+ }
3013
+ }
3014
+ }
3015
+ button {
3016
+ text "Start"
3017
+ font height: 40
3018
+ on_widget_selected {
3019
+ app_shell.hide
3020
+ @wizard_steps[@current_step_number - 1].open
3021
+ }
3022
+ }
3023
+ }.open
3024
+ ```
3025
+
3026
+ If you use a Custom Shell as the top-level app shell, you may invoke the class method `::launch` instead to avoid building an app class yourself or including Glimmer into the top-level namespace (e.g. `Tetris.launch` instead of `include Glimmer; tetris.open`)
3027
+
3028
+ You may check out [Hello, Custom Shell!](#hello-custom-shell) for another example.
3029
+
3030
+ ### Drag and Drop
3031
+
3032
+ Glimmer s Drag and Drop support, thanks to [SWT](https://www.eclipse.org/swt/) and Glimmer's lightweight [DSL syntax](#glimmer-dsl-syntax).
3033
+
3034
+ You may learn more about SWT Drag and Drop support over here: [https://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html](https://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html)
3035
+
3036
+ To get started, simply follow these steps:
3037
+ 1. On the drag source widget, add `on_drag_set_data` [DragSourceListener](https://help.eclipse.org/2020-03/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/dnd/DragSourceListener.html) event handler block at minimum (you may also add `on_drag_start` and `on_drag_finished` if needed)
3038
+ 1. Set `event.data` to transfer via drag and drop inside the `on_drag_set_data` event handler block (defaults to `transfer` type of `:text`, as in a Ruby String)
3039
+ 1. On the drop target widget, add `on_drop` [DropTargetListener](https://help.eclipse.org/2020-03/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/dnd/DropTargetListener.html) event handler block at minimum (you may also add `on_drag_enter` [must set [`event.detail`](https://help.eclipse.org/2020-06/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/dnd/DropTargetEvent.html#detail) if added], `on_drag_over`, `on_drag_leave`, `on_drag_operation_changed` and `on_drop_accept` if needed)
3040
+ 1. Read `event.data` and consume it (e.g. change widget text) inside the `on_drop` event handler block.
3041
+
3042
+ Example (taken from [samples/hello/hello_drag_and_drop.rb](#hello-drag-and-drop) / you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3043
+
3044
+ ```ruby
3045
+ class Location
3046
+ attr_accessor :country
3047
+
3048
+ def country_options
3049
+ %w[USA Canada Mexico Columbia UK Australia Germany Italy Spain]
3050
+ end
3051
+ end
3052
+
3053
+ @location = Location.new
3054
+
3055
+ include Glimmer
3056
+
3057
+ shell {
3058
+ text 'Hello, Drag and Drop!'
3059
+ list {
3060
+ selection bind(@location, :country)
3061
+ on_drag_set_data { |event|
3062
+ list = event.widget.getControl
3063
+ event.data = list.getSelection.first
3064
+ }
3065
+ }
3066
+ label(:center) {
3067
+ text 'Drag a country here!'
3068
+ font height: 20
3069
+ on_drop { |event|
3070
+ event.widget.getControl.setText(event.data)
3071
+ }
3072
+ }
3073
+ }.open
3074
+ ```
3075
+
3076
+ ![Hello Drag and Drop](/images/glimmer-hello-drag-and-drop.gif)
3077
+
3078
+ Optional steps:
3079
+ - Set a `transfer` property (defaults to `:text`). Values may be: :text (default), :html :image, :rtf, :url, and :file, or an array of multiple values. The `transfer` property will automatically convert your option into a [Transfer](https://help.eclipse.org/2020-03/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/dnd/Transfer.html) object as per the SWT API.
3080
+ - Specify `drag_source_style` operation (may be: :drop_copy (default), :drop_link, :drop_move, :drop_none, or an array of multiple operations)
3081
+ - Specify `drag_source_effect` (Check [DragSourceEffect](https://help.eclipse.org/2020-06/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/dnd/DragSourceEffect.html) SWT API for details)
3082
+ - Specify `drop_target_style` operation (may be: :drop_copy (default), :drop_link, :drop_move, :drop_none, or an array of multiple operations)
3083
+ - Specify `drop_target_effect` (Check [DropTargetEffect](https://help.eclipse.org/2020-06/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/dnd/DropTargetEffect.html) SWT API for details)
3084
+ - Set drag operation in `event.detail` (e.g. DND::DROP_COPY) inside `on_drag_enter`
3085
+
3086
+ ### Miscellaneous
3087
+
3088
+ #### Multi-DSL Support
3089
+
3090
+ Glimmer is a DSL engine that supports multiple DSLs (Domain Specific Languages):
3091
+ - [SWT](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (Desktop GUI)
3092
+ - [Opal](https://github.com/AndyObtiva/glimmer-dsl-opal): Glimmer DSL for Opal (Web GUI Adapter for Desktop Apps)
3093
+ - [XML](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML (& HTML) - Useful with [SWT Browser Widget](#browser-widget)
3094
+ - [CSS](https://github.com/AndyObtiva/glimmer-dsl-css): Glimmer DSL for CSS (Cascading Style Sheets) - Useful with [SWT Browser Widget](#browser-widget)
3095
+
3096
+ Glimmer automatically recognizes top-level keywords in each DSL and activates DSL accordingly. Glimmer allows mixing DSLs, which comes in handy when doing things like using the SWT Browser widget with XML and CSS. Once done processing a nested DSL top-level keyword, Glimmer switches back to the prior DSL automatically.
3097
+
3098
+ ##### SWT
3099
+
3100
+ The SWT DSL was already covered in detail. However, for the sake of mixing DSLs, you need to know that the SWT DSL has the following top-level keywords:
3101
+ - `shell`
3102
+ - `display`
3103
+ - `color`
3104
+ - `observe`
3105
+ - `async_exec`
3106
+ - `sync_exec`
3107
+
3108
+ ##### Opal
3109
+
3110
+ Full instructions are found in the [Opal](https://github.com/AndyObtiva/glimmer-dsl-opal) DSL page.
3111
+
3112
+ The [Opal](https://github.com/AndyObtiva/glimmer-dsl-opal) DSL is simply a web GUI adapter for desktop apps written in Glimmer. As such, it supports all the DSL keywords of the SWT DSL and shares the same top-level keywords.
3113
+
3114
+ ##### XML
3115
+
3116
+ Simply start with `html` keyword and add HTML inside its block using Glimmer DSL syntax.
3117
+ Once done, you may call `to_s`, `to_xml`, or `to_html` to get the formatted HTML output.
3118
+
3119
+ Here are all the Glimmer XML DSL top-level keywords:
3120
+ - `html`
3121
+ - `tag`: enables custom tag creation for exceptional cases by passing tag name as '_name' attribute
3122
+ - `name_space`: enables namespacing html tags
3123
+
3124
+ Element properties are typically passed as a key/value hash (e.g. `section(id: 'main', class: 'accordion')`) . However, for properties like "selected" or "checked", you must leave value `nil` or otherwise pass in front of the hash (e.g. `input(:checked, type: 'checkbox')` )
3125
+
3126
+ Example (basic HTML / you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3127
+
3128
+ ```ruby
3129
+ @xml = html {
3130
+ head {
3131
+ meta(name: "viewport", content: "width=device-width, initial-scale=2.0")
3132
+ }
3133
+ body {
3134
+ h1 { "Hello, World!" }
3135
+ }
3136
+ }
3137
+ puts @xml
3138
+ ```
3139
+
3140
+ Output:
3141
+
3142
+ ```
3143
+ <html><head><meta name="viewport" content="width=device-width, initial-scale=2.0" /></head><body><h1>Hello, World!</h1></body></html>
3144
+ ```
3145
+
3146
+ Example (explicit XML tag / you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3147
+
3148
+ ```ruby
3149
+ puts tag(:_name => "DOCUMENT")
3150
+ ```
3151
+
3152
+ Output:
3153
+
3154
+ ```
3155
+ <DOCUMENT/>
3156
+ ```
3157
+
3158
+ Example (XML namespaces using `name_space` keyword / you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3159
+
3160
+ ```ruby
3161
+ @xml = name_space(:w3c) {
3162
+ html(:id => "thesis", :class => "document") {
3163
+ body(:id => "main") {
3164
+ }
3165
+ }
3166
+ }
3167
+ puts @xml
3168
+ ```
3169
+
3170
+ Output:
3171
+
3172
+ ```
3173
+ <w3c:html id="thesis" class="document"><w3c:body id="main"></w3c:body></w3c:html>
3174
+ ```
3175
+
3176
+ Example (XML namespaces using dot operator / you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3177
+
3178
+ ```ruby
3179
+ @xml = tag(:_name => "DOCUMENT") {
3180
+ document.body(document.id => "main") {
3181
+ }
3182
+ }
3183
+ puts @xml
3184
+ ```
3185
+
3186
+ Output:
3187
+
3188
+ ```
3189
+ <DOCUMENT><document:body document:id="main"></document:body></DOCUMENT>
3190
+ ```
3191
+
3192
+ ##### CSS
3193
+
3194
+ Simply start with `css` keyword and add stylesheet rule sets inside its block using Glimmer DSL syntax.
3195
+ Once done, you may call `to_s` or `to_css` to get the formatted CSS output.
3196
+
3197
+ `css` is the only top-level keyword in the Glimmer CSS DSL
3198
+
3199
+ Selectors may be specified by `s` keyword or HTML element keyword directly (e.g. `body`)
3200
+ Rule property values may be specified by `pv` keyword or underscored property name directly (e.g. `font_size`)
3201
+
3202
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3203
+
3204
+ ```ruby
3205
+ @css = css {
3206
+ body {
3207
+ font_size '1.1em'
3208
+ pv 'background', 'white'
3209
+ }
3210
+
3211
+ s('body > h1') {
3212
+ background_color :red
3213
+ pv 'font-size', '2em'
3214
+ }
3215
+ }
3216
+ puts @css
3217
+ ```
3218
+
3219
+ ##### Listing / Enabling / Disabling DSLs
3220
+
3221
+ Glimmer provides a number of methods on Glimmer::DSL::Engine to configure DSL support or inquire about it:
3222
+ - `Glimmer::DSL::Engine.dsls`: Lists available Glimmer DSLs
3223
+ - `Glimmer::DSL::Engine.disable_dsl(dsl_name)`: Disables a specific DSL. Useful when there is no need for certain DSLs in a certain application.
3224
+ - `Glimmer::DSL::Engine.disabled_dsls': Lists disabled DSLs
3225
+ - `Glimmer::DSL::Engine.enable_dsl(dsl_name)`: Re-enables disabled DSL
3226
+ - `Glimmer::DSL::Engine.enabled_dsls=(dsl_names)`: Disables all DSLs except the ones specified.
3227
+
3228
+ #### Application Menu Items (About/Preferences)
3229
+
3230
+ Mac applications always have About and Preferences menu items. Glimmer provides widget observer hooks for them on the `display`:
3231
+ - `on_about`: executes code when user selects App Name -> About
3232
+ - `on_preferences`: executes code when user selects App Name -> Preferences or hits 'CMD+,' on the Mac
3233
+
3234
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3235
+
3236
+ ```ruby
3237
+ class Example
3238
+ def initialize
3239
+ display {
3240
+ on_about {
3241
+ message_box(@shell_proxy) {
3242
+ text 'About'
3243
+ message 'About Application'
3244
+ }.open
3245
+ }
3246
+ on_preferences {
3247
+ preferences_dialog = dialog {
3248
+ text 'Preferences'
3249
+ row_layout {
3250
+ type :vertical
3251
+ margin_left 15
3252
+ margin_top 15
3253
+ margin_right 15
3254
+ margin_bottom 15
3255
+ }
3256
+ label {
3257
+ text 'Check one of these options:'
3258
+ }
3259
+ button(:radio) {
3260
+ text 'Option 1'
3261
+ }
3262
+ button(:radio) {
3263
+ text 'Option 2'
3264
+ }
3265
+ }
3266
+ preferences_dialog.open
3267
+ }
3268
+ }
3269
+ @shell_proxy = shell {
3270
+ text 'Application Menu Items'
3271
+ fill_layout {
3272
+ margin_width 15
3273
+ margin_height 15
3274
+ }
3275
+ label {
3276
+ text 'Application Menu Items'
3277
+ font height: 30
3278
+ }
3279
+ }
3280
+ @shell_proxy.open
3281
+ end
3282
+ end
3283
+
3284
+ Example.new
3285
+ ```
3286
+
3287
+ #### App Name and Version
3288
+
3289
+ Application name (shows up on the Mac in top menu bar) and version may be specified upon [packaging](#packaging--distribution) by specifying "-Bmac.CFBundleName" and "-Bmac.CFBundleVersion" options.
3290
+
3291
+ Still, if you would like proper application name to show up on the Mac top menu bar during development, you may do so by invoking the SWT `Display.app_name=` method before any Display object has been instantiated (i.e. before any Glimmer widget like shell has been declared).
3292
+
3293
+ Example (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3294
+
3295
+ ```ruby
3296
+ Display.app_name = 'Glimmer Demo'
3297
+
3298
+ shell(:no_resize) {
3299
+ text "Glimmer"
3300
+ label {
3301
+ text "Hello, World!"
3302
+ }
3303
+ }.open
3304
+ ```
3305
+
3306
+ Also, you may invoke `Display.app_version = '1.0.0'` if needed for OS app version identification reasons during development, replacing `'1.0.0'` with your application version.
3307
+
3308
+ #### Performance Profiling
3309
+
3310
+ JRuby comes with built-in support for performance profiling via the `--profile` option (with some code shown below), which can be accepted by the `glimmer` command too:
3311
+
3312
+ `glimmer --profile path_to_glimmer_app.rb`
3313
+
3314
+ Additionally, add this code to monitor Glimmer app performance around its launch method:
3315
+
3316
+ ```ruby
3317
+ require 'jruby/profiler'
3318
+ profile_data = JRuby::Profiler.profile do
3319
+ SomeGlimmerApp.launch
3320
+ end
3321
+
3322
+ profile_printer = JRuby::Profiler::HtmlProfilePrinter.new(profile_data)
3323
+ ps = java.io.PrintStream.new(STDOUT.to_outputstream)
3324
+ ```
3325
+
3326
+ When monitoring app startup time performance, make sure to add a hook to the top-level `shell` `on_swt_show` event that exits the app as soon as the shell shows up to end performance profiling and get the results.
3327
+
3328
+ Example:
3329
+
3330
+ ```ruby
3331
+ shell {
3332
+ # some code
3333
+ on_swt_show {
3334
+ exit(0)
3335
+ }
3336
+ }
3337
+ ```
3338
+
3339
+ You may run `glimmer` with the `--profile.graph` instead for a more detailed output.
3340
+
3341
+ Learn more at the [JRuby Performance Profile WIKI page](https://github.com/jruby/jruby/wiki/Profiling-JRuby).
3342
+
3343
+ ##### SWT Browser Style Options
3344
+
3345
+ The `browser` widget can use a particular desktop browser by setting the SWT Style to:
3346
+ - `:webkit`: use the Webkit browser engine
3347
+ - `:chromium`: use the Chromium browser engine
3348
+
3349
+ Example using the Chromium browser (you may copy/paste in [`girb`](GLIMMER_GIRB.md)):
3350
+
3351
+ ```ruby
3352
+ shell {
3353
+ minimum_size 1024, 860
3354
+ browser(:chromium) {
3355
+ url 'http://brightonresort.com/about'
3356
+ }
3357
+ }.open
3358
+ ```
3359
+
3360
+ This relies on Glimmer's [Multi-DSL Support](#multi-dsl-support) for building the HTML text using [Glimmer XML DSL](https://github.com/AndyObtiva/glimmer-dsl-xml).
3361
+
3362
+ ## License
3363
+
3364
+ [MIT](LICENSE.txt)
3365
+
3366
+ Copyright (c) 2007-2021 - Andy Maleh.