glimmer-dsl-libui 0.2.10 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/README.md +209 -100
- data/VERSION +1 -1
- data/bin/girb +0 -0
- data/bin/girb_runner.rb +1 -1
- data/examples/area_gallery.rb +1 -3
- data/examples/area_gallery3.rb +1 -3
- data/examples/basic_draw_text2.rb +0 -1
- data/examples/basic_table_button.rb +2 -1
- data/examples/basic_table_checkbox.rb +1 -1
- data/examples/basic_table_image.rb +1 -0
- data/examples/basic_table_image_text.rb +1 -0
- data/examples/color_the_circles.rb +13 -13
- data/examples/control_gallery.rb +6 -0
- data/examples/editable_table.rb +2 -0
- data/examples/meta_example.rb +20 -10
- data/examples/method_based_custom_keyword.rb +16 -6
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/control_expression.rb +2 -0
- data/lib/glimmer/dsl/libui/string_expression.rb +9 -2
- data/lib/glimmer/libui/attributed_string.rb +32 -12
- data/lib/glimmer/libui/control_proxy/area_proxy.rb +37 -4
- data/lib/glimmer/libui/control_proxy/table_proxy.rb +30 -10
- data/lib/glimmer/libui/control_proxy.rb +17 -2
- data/lib/glimmer/libui/shape/arc.rb +5 -1
- data/lib/glimmer/libui/shape/circle.rb +5 -1
- metadata +13 -10
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.2.
|
1
|
+
# [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=85 />](https://github.com/AndyObtiva/glimmer) Glimmer DSL for LibUI 0.2.14
|
2
2
|
## Prerequisite-Free Ruby Desktop Development GUI Library
|
3
3
|
[](http://badge.fury.io/rb/glimmer-dsl-libui)
|
4
|
-
[](https://codeclimate.com/github/AndyObtiva/glimmer-dsl-libui/maintainability)
|
5
4
|
[](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
6
5
|
|
7
6
|
[Glimmer](https://github.com/AndyObtiva/glimmer) DSL for [LibUI](https://github.com/kojix2/LibUI) is a prerequisite-free Ruby desktop development GUI library. No need to pre-install any prerequisites. Just install the gem and have platform-independent native GUI that just works!
|
@@ -14,7 +13,7 @@ The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/gl
|
|
14
13
|
- Declarative DSL syntax that visually maps to the GUI control hierarchy
|
15
14
|
- Convention over configuration via smart defaults and automation of low-level details
|
16
15
|
- Requiring the least amount of syntax possible to build GUI
|
17
|
-
- Custom
|
16
|
+
- Custom Keyword support
|
18
17
|
- [Far Future Plan] Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models
|
19
18
|
- [Far Future Plan] Scaffolding for new custom controls, apps, and gems
|
20
19
|
- [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
|
@@ -132,11 +131,9 @@ window('Area Gallery', 400, 400) {
|
|
132
131
|
stroke r: 0, g: 0, b: 0, thickness: 2
|
133
132
|
}
|
134
133
|
text(160, 40, 100) { # x, y, width
|
135
|
-
string {
|
134
|
+
string('Area Gallery') {
|
136
135
|
font family: 'Times', size: 14
|
137
136
|
color :black
|
138
|
-
|
139
|
-
'Area Gallery'
|
140
137
|
}
|
141
138
|
}
|
142
139
|
|
@@ -207,12 +204,12 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
|
|
207
204
|
|
208
205
|
## Table of Contents
|
209
206
|
|
210
|
-
- [Glimmer DSL for LibUI
|
207
|
+
- [Glimmer DSL for LibUI](#)
|
211
208
|
- [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts)
|
212
209
|
- [Usage](#usage)
|
213
210
|
- [Girb (Glimmer IRB)](#girb-glimmer-irb)
|
214
211
|
- [API](#api)
|
215
|
-
- [Supported
|
212
|
+
- [Supported Keywords](#supported-keywords)
|
216
213
|
- [Common Control Properties](#common-control-properties)
|
217
214
|
- [Common Control Operations](#common-control-operations)
|
218
215
|
- [LibUI Operations](#libui-operations)
|
@@ -260,6 +257,9 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
|
|
260
257
|
- [Basic Draw Text](#basic-draw-text)
|
261
258
|
- [Custom Draw Text](#custom-draw-text)
|
262
259
|
- [Method-Based Custom Keyword](#method-based-custom-keyword)
|
260
|
+
- [Applications](#applications)
|
261
|
+
- [Manga2PDF](#manga2pdf)
|
262
|
+
- [Befunge98 GUI](#befunge98-gui)
|
263
263
|
- [Contributing to glimmer-dsl-libui](#contributing-to-glimmer-dsl-libui)
|
264
264
|
- [Help](#help)
|
265
265
|
- [Issues](#issues)
|
@@ -280,10 +280,10 @@ The Glimmer GUI DSL provides object-oriented declarative hierarchical syntax for
|
|
280
280
|
- Requires the minimum amount of syntax needed to describe an app's GUI
|
281
281
|
|
282
282
|
The Glimmer GUI DSL follows these simple concepts in mapping from [LibUI](https://github.com/kojix2/LibUI) syntax:
|
283
|
-
- **
|
284
|
-
- **Content
|
285
|
-
- **Property**: Control properties may be declared inside keyword blocks with lower-case underscored name followed by property value args (e.g. `title "hello world"` inside `group`). Behind the scenes, properties correspond to `control_set_property` methods.
|
286
|
-
- **Listener**: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with `on_` and receiving required block handler (e.g. `on_clicked {puts 'clicked'}` inside `button`). Behind the scenes, listeners correspond to `control_on_event` methods.
|
283
|
+
- **Keyword(args)**: [LibUI](https://github.com/kojix2/LibUI) controls may be declared by lower-case underscored name (aka keyword) (e.g. `window` or `button`). Behind the scenes, they are represented by keyword methods that map to corresponding `LibUI.new_keyword` methods receiving args (e.g. `window('hello world', 300, 200, true)`).
|
284
|
+
- **Content Block** (Properties/Listeners/Controls): Any keyword may be optionally followed by a Ruby curly-brace multi-line content block containing properties (attributes), listeners, and/or nested controls (e.g. `window {title 'hello world'; on_closing {puts 'Bye'}; button('greet')}`). Content block optionally receives one arg representing the control (e.g. `button('greet') {|b| on_clicked { puts b.text}}`)
|
285
|
+
- **Property**: Control properties may be declared inside keyword blocks with lower-case underscored name followed by property value args (e.g. `title "hello world"` inside `group`). Behind the scenes, properties correspond to `LibUI.control_set_property` methods.
|
286
|
+
- **Listener**: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with `on_` and receiving required block handler (e.g. `on_clicked {puts 'clicked'}` inside `button`). Optionally, the listener block can receive an arg representing the control (e.g. `on_clicked {|btn| puts btn.text}`). Behind the scenes, listeners correspond to `LibUI.control_on_event` methods.
|
287
287
|
|
288
288
|
Example of an app written in [LibUI](https://github.com/kojix2/LibUI)'s procedural imperative syntax:
|
289
289
|
|
@@ -347,7 +347,7 @@ gem install glimmer-dsl-libui
|
|
347
347
|
Or install via Bundler `Gemfile`:
|
348
348
|
|
349
349
|
```ruby
|
350
|
-
gem 'glimmer-dsl-libui', '~> 0.2.
|
350
|
+
gem 'glimmer-dsl-libui', '~> 0.2.14'
|
351
351
|
```
|
352
352
|
|
353
353
|
Add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
|
@@ -407,7 +407,7 @@ w.set_title 'aloha'
|
|
407
407
|
puts w.title # => aloha
|
408
408
|
```
|
409
409
|
|
410
|
-
Controls are wrapped as Ruby proxy objects, having a `#libui` method to obtain the wrapped Fiddle pointer object. Ruby proxy objects rely on composition (via [Proxy Design Pattern](https://en.wikipedia.org/wiki/Proxy_pattern)) instead of inheritance to shield consumers from having to deal with lower-level details unless absolutely needed.
|
410
|
+
Controls are wrapped as Ruby proxy objects, having a `#libui` method to obtain the wrapped [LibUI](https://github.com/kojix2/LibUI) Fiddle pointer object. Ruby proxy objects rely on composition (via [Proxy Design Pattern](https://en.wikipedia.org/wiki/Proxy_pattern)) instead of inheritance to shield consumers from having to deal with lower-level details unless absolutely needed.
|
411
411
|
|
412
412
|
Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
413
413
|
|
@@ -416,9 +416,11 @@ w = window('hello world') # => #<Glimmer::LibUI::WindowProxy:0x00007fde4ea39fb0
|
|
416
416
|
w.libui # => #<Fiddle::Pointer:0x00007fde53997980 ptr=0x00007fde51352a60 size=0 free=0x0000000000000000>
|
417
417
|
```
|
418
418
|
|
419
|
-
### Supported
|
419
|
+
### Supported Keywords
|
420
|
+
|
421
|
+
These are all the supported keywords. Note that some keywords do not represent controls, but produce objects that are used as the property values of controls (e.g. `image` builds objects to use in `cell_rows` for a `table` with an image column)
|
420
422
|
|
421
|
-
|
423
|
+
Keyword(Args) | Properties | Listeners
|
422
424
|
------------- | ---------- | ---------
|
423
425
|
`about_menu_item` | None | `on_clicked`
|
424
426
|
`area` | None | `on_draw(area_draw_params)`, `on_mouse_event(area_mouse_event)`, `on_mouse_down(area_mouse_event)`, `on_mouse_up(area_mouse_event)`, `on_mouse_drag_started(area_mouse_event)`, `on_mouse_dragged(area_mouse_event)`, `on_mouse_dropped(area_mouse_event)`, `on_mouse_entered`, `on_mouse_exited`, `on_key_event(area_key_event)`, `on_key_down(area_key_event)`, `on_key_up(area_key_event)`
|
@@ -454,6 +456,8 @@ Control(Args) | Properties | Listeners
|
|
454
456
|
`matrix(m11 = nil as Numeric, m12 = nil as Numeric, m21 = nil as Numeric, m22 = nil as Numeric, m31 = nil as Numeric, m32 = nil as Numeric)` | `m11` (`Numeric`), `m12` (`Numeric`), `m21` (`Numeric`), `m22` (`Numeric`), `m31` (`Numeric`), `m32` (`Numeric`) | None
|
455
457
|
`menu(text as String)` | None | None
|
456
458
|
`menu_item(text as String)` | `checked` (Boolean) | `on_clicked`
|
459
|
+
`message_box` (alias for `msg_box`; see for arguments) | None | None
|
460
|
+
`message_box_error` (alias for `msg_box_error`; see for arguments) | None | None
|
457
461
|
`multiline_entry` | `read_only` (Boolean), `text` (`String`) | `on_changed`
|
458
462
|
`msg_box(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String)` | None | None
|
459
463
|
`msg_box_error(window = main_window as Glimmer::LibUI::WindowProxy, title as String, description as String)` | None | None
|
@@ -470,7 +474,7 @@ Control(Args) | Properties | Listeners
|
|
470
474
|
`slider(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
|
471
475
|
`spinbox(min as Numeric, max as Numeric)` | `value` (`Numeric`) | `on_changed`
|
472
476
|
`square(x as Numeric, y as Numeric, length as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `length` (`Numeric`) | None
|
473
|
-
`string` | `font`, `color` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `background` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `underline`, `underline_color` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `open_type_features` | None
|
477
|
+
`string(string = '')` | `font`, `color` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `background` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `underline`, `underline_color` (`Hash` of `:r` as `0`-`255`, `:g` as `0`-`255`, `:b` as `0`-`255`, `:a` as `0.0`-`1.0`, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color), `open_type_features`, `string` (`String`) | None
|
474
478
|
`tab` | `margined` (Boolean), `num_pages` (`Integer`) | None
|
475
479
|
`tab_item(name as String)` | `index` [read-only] (`Integer`), `margined` (Boolean), `name` [read-only] (`String`) | None
|
476
480
|
`table` | `cell_rows` (`Array` (rows) of `Arrays` (row columns) of cell values (e.g. `String` values for `text_column` cells or `Array` of `image`/`String` for `image_text_column`)), `editable` as Boolean | `on_changed {|row, type, row_data| ...}`, `on_edited {|row, row_data| ...}`
|
@@ -515,8 +519,8 @@ All operations that could normally be called on `LibUI` can also be called on `G
|
|
515
519
|
|
516
520
|
### Extra Dialogs
|
517
521
|
|
518
|
-
- `open_file(window as Glimmer::LibUI::WindowProxy)`: returns selected file (`String`) or `nil` if cancelled
|
519
|
-
- `save_file(window as Glimmer::LibUI::WindowProxy)`: returns selected file (`String`) or `nil` if cancelled
|
522
|
+
- `open_file(window as Glimmer::LibUI::WindowProxy = ControlProxy::main_window_proxy)`: returns selected file (`String`) or `nil` if cancelled
|
523
|
+
- `save_file(window as Glimmer::LibUI::WindowProxy = ControlProxy::main_window_proxy)`: returns selected file (`String`) or `nil` if cancelled
|
520
524
|
|
521
525
|
### Extra Operations
|
522
526
|
|
@@ -529,13 +533,13 @@ All operations that could normally be called on `LibUI` can also be called on `G
|
|
529
533
|
|
530
534
|
### Table API
|
531
535
|
|
532
|
-
The `table` control must first declare its columns via one of these column keywords (mentioned in [Supported
|
536
|
+
The `table` control must first declare its columns via one of these column keywords (mentioned in [Supported Keywords](#supported-keywords)):
|
533
537
|
- `background_color_column`: expects color cell values
|
534
538
|
- `button_column`: expects `String` cell values
|
535
539
|
- `checkbox_column`: expects Boolean cell values
|
536
540
|
- `checkbox_text_column`: expects dual-element `Array` of Boolean and `String` cell values
|
537
541
|
- `checkbox_text_color_column`: expects triple-element `Array` of Boolean, `String`, and color cell values
|
538
|
-
- `image_column`: expects `image` cell values (produced by `image` and `image_part` keywords as per [Supported
|
542
|
+
- `image_column`: expects `image` cell values (produced by `image` and `image_part` keywords as per [Supported Keywords](#supported-keywords))
|
539
543
|
- `image_text_column`: expects dual-element `Array` of `image` and `String` cell values
|
540
544
|
- `image_text_color_column`: expects triple-element `Array` of `image`, `String`, and color cell values
|
541
545
|
- `text_column`: expects `String` cell values
|
@@ -597,6 +601,7 @@ window('Contacts', 600, 600) { |w|
|
|
597
601
|
msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
|
598
602
|
else
|
599
603
|
data << new_row # automatically inserts a row into the table due to implicit data-binding
|
604
|
+
@unfiltered_data = data.dup
|
600
605
|
@name_entry.text = ''
|
601
606
|
@email_entry.text = ''
|
602
607
|
@phone_entry.text = ''
|
@@ -606,6 +611,25 @@ window('Contacts', 600, 600) { |w|
|
|
606
611
|
end
|
607
612
|
}
|
608
613
|
|
614
|
+
search_entry { |se|
|
615
|
+
stretchy false
|
616
|
+
|
617
|
+
on_changed do
|
618
|
+
filter_value = se.text
|
619
|
+
@unfiltered_data ||= data.dup
|
620
|
+
# Unfilter first to remove any previous filters
|
621
|
+
data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
|
622
|
+
# Now, apply filter if entered
|
623
|
+
unless filter_value.empty?
|
624
|
+
data.filter! do |row_data| # affects table indirectly through implicit data-binding
|
625
|
+
row_data.any? do |cell|
|
626
|
+
cell.to_s.downcase.include?(filter_value.downcase)
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
}
|
632
|
+
|
609
633
|
table {
|
610
634
|
text_column('Name')
|
611
635
|
text_column('Email')
|
@@ -614,6 +638,10 @@ window('Contacts', 600, 600) { |w|
|
|
614
638
|
text_column('State')
|
615
639
|
|
616
640
|
cell_rows data # implicit data-binding
|
641
|
+
|
642
|
+
on_changed do |row, type, row_data|
|
643
|
+
puts "Row #{row} #{type}: #{row_data}"
|
644
|
+
end
|
617
645
|
}
|
618
646
|
}
|
619
647
|
}.show
|
@@ -626,8 +654,8 @@ Learn more by checking out [examples](#examples).
|
|
626
654
|
### Area API
|
627
655
|
|
628
656
|
The `area` control is a canvas-like control for drawing paths that can be used in one of two ways:
|
629
|
-
- Declaratively via stable paths: useful for stable paths that will not change later on. Simply nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be few stable paths (mostly for decorative reasons).
|
630
|
-
- Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change. Open an `on_draw` listener block that receives a `area_draw_params` argument and nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are destroyed (thrown-away) at the end of drawing, thus having less memory overhead for drawing thousands of dynamic paths.
|
657
|
+
- Declaratively via stable paths: useful for stable paths that will not change often later on. Simply nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are preserved across redraws assuming there would be relatively few stable paths (mostly for decorative reasons).
|
658
|
+
- Semi-declaratively via on_draw listener dynamic paths: useful for more dynamic paths that will definitely change very often. Open an `on_draw` listener block that receives a `area_draw_params` argument and nest `path` and figures like `rectangle` and all drawing logic is generated automatically. Path proxy objects are destroyed (thrown-away) at the end of drawing, thus having less memory overhead for drawing thousands of dynamic paths.
|
631
659
|
|
632
660
|
Here is an example of a declarative `area` with a stable path (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
633
661
|
|
@@ -814,19 +842,23 @@ Check [Basic Transform](#basic-transform) example for use of [X11](https://en.wi
|
|
814
842
|
|
815
843
|
Check [Histogram](#histogram) example for use of hex colors.
|
816
844
|
|
817
|
-
To draw `text` in an `area`, you simply nest a `text(x, y, width)` control directly under `area` or inside a `on_draw` listener, and then nest attributed `string {string_value}` controls underneath it returning an actual `String` (think of them as the `<span>` element in html, which contains a string of text).
|
845
|
+
To draw `text` in an `area`, you simply nest a `text(x, y, width)` control directly under `area` or inside a `on_draw` listener, and then nest attributed `string {[attributes]; string_value}` controls underneath it returning an actual `String` (think of them as the `<span>` or `<p>` element in html, which contains a string of text). Alternatively, you can nest attributed `string(string_value) {[attributes]}` if `string_value` is a short single-line string. An attributed `string` value can be changed dynamically via its `string` property.
|
818
846
|
|
819
|
-
`text`
|
847
|
+
`text` has the following properties:
|
820
848
|
- `default_font`:
|
821
849
|
- `align`: `:left` (default), `:center`, or `:right` (`align` currently seems not to work on the Mac)
|
850
|
+
- `x`: x coordinate in relation to parent `area` top-left corner
|
851
|
+
- `y`: y coordinate in relation to parent `area` top-left corner
|
852
|
+
- `width` (default: area width - x*2): width of text to display
|
822
853
|
|
823
|
-
`string`
|
854
|
+
`string` has the following properties:
|
824
855
|
- `font`: font descriptor hash consisting of `:family`, `:size`, `:weight` (`[:minimum, :thin, :ultra_light, :light, :book, :normal, :medium, :semi_bold, :bold, :ultra_bold, :heavy, :ultra_heavy, :maximum]`), `:italic` (`[:normal, :oblique, :italic]`), and `:stretch` (`[:ultra_condensed, :extra_condensed, :condensed, :semi_condensed, :normal, :semi_expanded, :expanded, :extra_expanded, :ultra_expanded]`) key values
|
825
856
|
- `color`: rgba, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color
|
826
857
|
- `background`: rgba, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color
|
827
858
|
- `underline`: one of `:none`, `:single`, `:double`, `:suggestion`, `:color_custom`, `:color_spelling`, `:color_grammar`, `:color_auxiliary`
|
828
859
|
- `underline_color`: one of `:spelling`, `:grammar`, `:auxiliary`, rgba, hex, or [X11](https://en.wikipedia.org/wiki/X11_color_names) color
|
829
860
|
- `open_type_features`: Open Type Features (https://www.microsoft.com/typography/otspec/featuretags.htm) consist of `open_type_tag`s nested in content block, which accept (`a`, `b`, `c`, `d`, `Integer`) arguments.
|
861
|
+
- `string`: string value (`String`)
|
830
862
|
|
831
863
|
Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
832
864
|
|
@@ -847,12 +879,13 @@ window('area text drawing') {
|
|
847
879
|
open_type_tag 'l', 'i', 'g', 'a', 1
|
848
880
|
}
|
849
881
|
|
850
|
-
"This is a
|
882
|
+
"This is a demonstration\n" \
|
883
|
+
"of a very long\n" \
|
884
|
+
"attributed string\n" \
|
885
|
+
"spanning multiple lines\n\n"
|
851
886
|
}
|
852
887
|
|
853
|
-
string
|
854
|
-
'This is another test'
|
855
|
-
}
|
888
|
+
string('This is a short unattributed string')
|
856
889
|
}
|
857
890
|
}
|
858
891
|
}.show
|
@@ -886,9 +919,11 @@ window('area text drawing') {
|
|
886
919
|
- Table `cell_rows` data has implicit data-binding to table cell values for deletion, insertion, and change (done by diffing `cell_rows` value before and after change and auto-informing `table` of deletions [`LibUI.table_model_row_deleted`], insertions [`LibUI.table_model_row_deleted`], and changes [`LibUI.table_model_row_changed`]). When deleting data rows from `cell_rows` array, then actual rows from the `table` are automatically deleted. When inserting data rows into `cell_rows` array, then actual `table` rows are automatically inserted. When updating data rows in `cell_rows` array, then actual `table` rows are automatically updated.
|
887
920
|
- `image` instances are automatically freed from memory after `window` is destroyed.
|
888
921
|
- `image` `width` and `height` can be left off if it has one `image_part` only as they default to the same `width` and `height` of the `image_part`
|
922
|
+
- Automatically provide shifted `:key` characters in `area_key_event` provided in `area` key listeners `on_key_event`, `on_key_down`, and `on_key_up`
|
889
923
|
- `area` paths are specified declaratively with figures underneath (e.g. `rectangle`) and `area` draw listener is automatically generated
|
890
924
|
- Observe figure properties (e.g. `rectangle` `width`) for changes and automatically redraw containing area accordingly
|
891
925
|
- Observe `path` `fill` and `stroke` hashes for changes and automatically redraw containing area accordingly
|
926
|
+
- Observe `text` and `string` properties for changes and automatically redraw containing area accordingly
|
892
927
|
- All controls are protected from garbage collection until no longer needed (explicitly destroyed), so there is no need to worry about surprises.
|
893
928
|
- All resources are freed automatically once no longer needed or left to garbage collection.
|
894
929
|
- When nesting an `area` directly underneath `window` (without a layout control like `vertical_box`), it is automatically reparented with `vertical_box` in between the `window` and `area` since it would not show up on Linux otherwise.
|
@@ -899,7 +934,7 @@ window('area text drawing') {
|
|
899
934
|
|
900
935
|
To define custom keywords, simply define a method representing the custom control you want. To make reusable, you can define in modules and simply include the modules in the view classes that need them.
|
901
936
|
|
902
|
-
Example that defines `
|
937
|
+
Example that defines `form_field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
|
903
938
|
|
904
939
|
```ruby
|
905
940
|
require 'glimmer-dsl-libui'
|
@@ -909,7 +944,7 @@ include Glimmer
|
|
909
944
|
|
910
945
|
Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
|
911
946
|
|
912
|
-
def
|
947
|
+
def form_field(model, property)
|
913
948
|
property = property.to_s
|
914
949
|
entry { |e|
|
915
950
|
label property.underscore.split('_').map(&:capitalize).join(' ')
|
@@ -923,11 +958,11 @@ end
|
|
923
958
|
|
924
959
|
def address_form(address)
|
925
960
|
form {
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
961
|
+
form_field(address, :street)
|
962
|
+
form_field(address, :p_o_box)
|
963
|
+
form_field(address, :city)
|
964
|
+
form_field(address, :state)
|
965
|
+
form_field(address, :zip_code)
|
931
966
|
}
|
932
967
|
end
|
933
968
|
|
@@ -962,29 +997,39 @@ window('Method-Based Custom Keyword') {
|
|
962
997
|
label('Address 1') {
|
963
998
|
stretchy false
|
964
999
|
}
|
1000
|
+
|
965
1001
|
address_form(address1)
|
1002
|
+
|
966
1003
|
horizontal_separator {
|
967
1004
|
stretchy false
|
968
1005
|
}
|
1006
|
+
|
969
1007
|
label('Address 1 (Saved)') {
|
970
1008
|
stretchy false
|
971
1009
|
}
|
1010
|
+
|
972
1011
|
address(address1)
|
973
1012
|
}
|
1013
|
+
|
974
1014
|
vertical_separator {
|
975
1015
|
stretchy false
|
976
1016
|
}
|
1017
|
+
|
977
1018
|
vertical_box {
|
978
1019
|
label('Address 2') {
|
979
1020
|
stretchy false
|
980
1021
|
}
|
1022
|
+
|
981
1023
|
address_form(address2)
|
1024
|
+
|
982
1025
|
horizontal_separator {
|
983
1026
|
stretchy false
|
984
1027
|
}
|
1028
|
+
|
985
1029
|
label('Address 2 (Saved)') {
|
986
1030
|
stretchy false
|
987
1031
|
}
|
1032
|
+
|
988
1033
|
address(address2)
|
989
1034
|
}
|
990
1035
|
}
|
@@ -996,8 +1041,13 @@ window('Method-Based Custom Keyword') {
|
|
996
1041
|
### API Gotchas
|
997
1042
|
|
998
1043
|
- There is no proper way to destroy `grid` children due to [libui](https://github.com/andlabs/libui) not offering any API for deleting them from `grid` (no `grid_delete` similar to `box_delete` for `horizontal_box` and `vertical_box`).
|
999
|
-
- `table` `checkbox_column` and `checkbox_text_column` checkbox editing only works on
|
1044
|
+
- `table` `checkbox_column` and `checkbox_text_column` checkbox editing only works on Linux and Windows (not Mac) due to a current limitation in [libui](https://github.com/andlabs/ui/issues/357).
|
1000
1045
|
- `text` `align` property seems not to work on the Mac ([libui](https://github.com/andlabs/libui) has an [issue](https://github.com/andlabs/libui/pull/407) about it)
|
1046
|
+
- `text` `string` `background` does not work on Windows due to an [issue in libui](https://github.com/andlabs/libui/issues/347).
|
1047
|
+
- `arc` shape does not work on Windows unless a figure is started due to implementation of [libui](https://github.com/andlabs/libui).
|
1048
|
+
- `table` controls on Windows intentionally get an extra empty row at the end because if any row were to be deleted for the first time, double-deletion happens due to an issue in [libui](https://github.com/andlabs/libui) on Windows.
|
1049
|
+
- `table` `progress_bar` column on Windows cannot be updated with a positive value if it started initially with `-1` (it ignores update to avoid crashing due to an issue in [libui](https://github.com/andlabs/libui) on Windows.
|
1050
|
+
- It seems that [libui](https://github.com/andlabs/libui) does not support nesting multiple `area` controls under a `grid` as only the first one shows up in that scenario. To workaround that limitation, use a `vertical_box` with nested `horizontal_box`s instead to include multiple `area`s in a GUI.
|
1001
1051
|
|
1002
1052
|
### Original API
|
1003
1053
|
|
@@ -1012,7 +1062,7 @@ I am documenting options for packaging, which I have not tried myself, but figur
|
|
1012
1062
|
|
1013
1063
|
For Windows, the [LibUI](https://github.com/kojix2/LibUI) project recommends [OCRA](https://github.com/larsch/ocra) (One-Click Ruby Application), which builds Windows executables from Ruby source.
|
1014
1064
|
|
1015
|
-
For Mac, consider [
|
1065
|
+
For Mac, consider [Platypus](https://github.com/sveinbjornt/Platypus) (builds a native Mac app from a Ruby script)
|
1016
1066
|
|
1017
1067
|
For Linux, simply package your app as a [Ruby Gem](https://guides.rubygems.org/what-is-a-gem/) and [build rpm package from Ruby Gem](https://www.redpill-linpro.com/sysadvent/2015/12/07/building-rpms-from-gems.html) or [build deb package from Ruby Gem](https://openpreservation.org/blogs/building-debian-package-ruby-program/).
|
1018
1068
|
|
@@ -1056,8 +1106,6 @@ Linux
|
|
1056
1106
|
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
1057
1107
|
|
1058
1108
|
```ruby
|
1059
|
-
# frozen_string_literal: true
|
1060
|
-
|
1061
1109
|
require 'glimmer-dsl-libui'
|
1062
1110
|
require 'facets'
|
1063
1111
|
|
@@ -1100,6 +1148,18 @@ class MetaExample
|
|
1100
1148
|
examples[@selected_example_index]
|
1101
1149
|
end
|
1102
1150
|
|
1151
|
+
def run_example(example)
|
1152
|
+
command = "ruby -r #{glimmer_dsl_libui_file} #{example} 2>&1"
|
1153
|
+
result = ''
|
1154
|
+
IO.popen(command) do |f|
|
1155
|
+
f.each_line do |line|
|
1156
|
+
result << line
|
1157
|
+
puts line
|
1158
|
+
end
|
1159
|
+
end
|
1160
|
+
msg_box('Error Running Example', result) if result.downcase.include?('error')
|
1161
|
+
end
|
1162
|
+
|
1103
1163
|
def launch
|
1104
1164
|
window('Meta-Example', 700, 500) {
|
1105
1165
|
margined true
|
@@ -1150,11 +1210,11 @@ class MetaExample
|
|
1150
1210
|
begin
|
1151
1211
|
meta_example_file = File.join(Dir.home, '.meta_example.rb')
|
1152
1212
|
File.write(meta_example_file, @code_entry.text)
|
1153
|
-
|
1154
|
-
msg_box('Error Running Example', result) if result.include?('error')
|
1213
|
+
run_example(meta_example_file)
|
1155
1214
|
rescue => e
|
1215
|
+
puts e.full_message
|
1156
1216
|
puts 'Unable to write code changes! Running original example...'
|
1157
|
-
|
1217
|
+
run_example(file_path_for(selected_example))
|
1158
1218
|
end
|
1159
1219
|
end
|
1160
1220
|
}
|
@@ -2518,11 +2578,21 @@ window('Form') {
|
|
2518
2578
|
@last_name_entry = entry {
|
2519
2579
|
label 'Last Name' # label property is available when control is nested under form
|
2520
2580
|
}
|
2581
|
+
|
2582
|
+
@phone_entry = entry {
|
2583
|
+
label 'Phone' # label property is available when control is nested under form
|
2584
|
+
}
|
2585
|
+
|
2586
|
+
@email_entry = entry {
|
2587
|
+
label 'Email' # label property is available when control is nested under form
|
2588
|
+
}
|
2521
2589
|
}
|
2522
2590
|
|
2523
|
-
button('Display
|
2591
|
+
button('Display Info') {
|
2592
|
+
stretchy false
|
2593
|
+
|
2524
2594
|
on_clicked do
|
2525
|
-
msg_box('
|
2595
|
+
msg_box('Info', "#{@first_name_entry.text} #{@last_name_entry.text} has phone #{@phone_entry.text} and email #{@email_entry.text}")
|
2526
2596
|
end
|
2527
2597
|
}
|
2528
2598
|
}
|
@@ -3343,8 +3413,6 @@ Linux
|
|
3343
3413
|
New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
|
3344
3414
|
|
3345
3415
|
```ruby
|
3346
|
-
# frozen_string_literal: true
|
3347
|
-
|
3348
3416
|
require 'glimmer-dsl-libui'
|
3349
3417
|
|
3350
3418
|
include Glimmer
|
@@ -3874,11 +3942,9 @@ window('Area Gallery', 400, 400) {
|
|
3874
3942
|
stroke r: 0, g: 0, b: 0, thickness: 2
|
3875
3943
|
}
|
3876
3944
|
text(160, 40, 100) { # x, y, width
|
3877
|
-
string {
|
3945
|
+
string('Area Gallery') {
|
3878
3946
|
font family: 'Times', size: 14
|
3879
3947
|
color :black
|
3880
|
-
|
3881
|
-
'Area Gallery'
|
3882
3948
|
}
|
3883
3949
|
}
|
3884
3950
|
|
@@ -4182,11 +4248,9 @@ window('Area Gallery', 400, 400) {
|
|
4182
4248
|
stroke r: 0, g: 0, b: 0, thickness: 2
|
4183
4249
|
}
|
4184
4250
|
text(160, 40, 100) { # x, y, width
|
4185
|
-
string {
|
4251
|
+
string('Area Gallery') {
|
4186
4252
|
font family: 'Times', size: 14
|
4187
4253
|
color :black
|
4188
|
-
|
4189
|
-
'Area Gallery'
|
4190
4254
|
}
|
4191
4255
|
}
|
4192
4256
|
end
|
@@ -5107,8 +5171,8 @@ class ColorTheCircles
|
|
5107
5171
|
|
5108
5172
|
WINDOW_WIDTH = 800
|
5109
5173
|
WINDOW_HEIGHT = 600
|
5110
|
-
|
5111
|
-
|
5174
|
+
SHAPE_MIN_SIZE = 15
|
5175
|
+
SHAPE_MAX_SIZE = 75
|
5112
5176
|
MARGIN_WIDTH = 55
|
5113
5177
|
MARGIN_HEIGHT = 155
|
5114
5178
|
TIME_MAX_EASY = 4
|
@@ -5122,6 +5186,7 @@ class ColorTheCircles
|
|
5122
5186
|
@circles_data = []
|
5123
5187
|
@score = 0
|
5124
5188
|
@time_max = TIME_MAX_HARD
|
5189
|
+
@game_over = false
|
5125
5190
|
register_observers
|
5126
5191
|
setup_circle_factory
|
5127
5192
|
end
|
@@ -5130,9 +5195,11 @@ class ColorTheCircles
|
|
5130
5195
|
observer = Glimmer::DataBinding::Observer.proc do |new_score|
|
5131
5196
|
@score_label.text = new_score.to_s
|
5132
5197
|
if new_score == -20
|
5198
|
+
@game_over = true
|
5133
5199
|
msg_box('You Lost!', 'Sorry! Your score reached -20')
|
5134
5200
|
restart_game
|
5135
5201
|
elsif new_score == 0
|
5202
|
+
@game_over = true
|
5136
5203
|
msg_box('You Won!', 'Congratulations! Your score reached 0')
|
5137
5204
|
restart_game
|
5138
5205
|
end
|
@@ -5142,11 +5209,13 @@ class ColorTheCircles
|
|
5142
5209
|
|
5143
5210
|
def setup_circle_factory
|
5144
5211
|
consumer = Proc.new do
|
5145
|
-
|
5146
|
-
|
5147
|
-
|
5148
|
-
|
5149
|
-
|
5212
|
+
unless @game_over
|
5213
|
+
if @circles_data.empty?
|
5214
|
+
# start with 3 circles to make more challenging
|
5215
|
+
add_circle until @circles_data.size > 3
|
5216
|
+
else
|
5217
|
+
add_circle
|
5218
|
+
end
|
5150
5219
|
end
|
5151
5220
|
delay = rand * @time_max
|
5152
5221
|
Glimmer::LibUI.timer(delay, repeat: false, &consumer)
|
@@ -5155,12 +5224,12 @@ class ColorTheCircles
|
|
5155
5224
|
end
|
5156
5225
|
|
5157
5226
|
def add_circle
|
5158
|
-
|
5159
|
-
|
5160
|
-
|
5227
|
+
circle_x = rand * (WINDOW_WIDTH - MARGIN_WIDTH - SHAPE_MAX_SIZE) + SHAPE_MAX_SIZE
|
5228
|
+
circle_y = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - SHAPE_MAX_SIZE) + SHAPE_MAX_SIZE
|
5229
|
+
circle_size = rand * (SHAPE_MAX_SIZE - SHAPE_MIN_SIZE) + SHAPE_MIN_SIZE
|
5161
5230
|
stroke_color = Glimmer::LibUI.x11_colors.sample
|
5162
5231
|
@circles_data << {
|
5163
|
-
args: [
|
5232
|
+
args: [circle_x, circle_y, circle_size],
|
5164
5233
|
fill: nil,
|
5165
5234
|
stroke: stroke_color
|
5166
5235
|
}
|
@@ -5171,8 +5240,28 @@ class ColorTheCircles
|
|
5171
5240
|
def restart_game
|
5172
5241
|
@score = 0 # update variable directly to avoid notifying observers
|
5173
5242
|
@circles_data.clear
|
5243
|
+
@game_over = false
|
5174
5244
|
end
|
5175
5245
|
|
5246
|
+
def color_circle(x, y)
|
5247
|
+
clicked_circle_data = @circles_data.find do |circle_data|
|
5248
|
+
circle_data[:fill].nil? && circle_data[:circle]&.include?(x, y)
|
5249
|
+
end
|
5250
|
+
if clicked_circle_data
|
5251
|
+
clicked_circle_data[:fill] = clicked_circle_data[:stroke]
|
5252
|
+
push_colored_circle_behind_uncolored_circles(clicked_circle_data)
|
5253
|
+
@area.queue_redraw_all
|
5254
|
+
self.score += 1 # notifies score observers automatically of change
|
5255
|
+
end
|
5256
|
+
end
|
5257
|
+
|
5258
|
+
def push_colored_circle_behind_uncolored_circles(colored_circle_data)
|
5259
|
+
removed_colored_circle_data = @circles_data.delete(colored_circle_data)
|
5260
|
+
last_colored_circle_data = @circles_data.select {|cd| cd[:fill]}.last
|
5261
|
+
last_colored_circle_data_index = @circles_data.index(last_colored_circle_data) || -1
|
5262
|
+
@circles_data.insert(last_colored_circle_data_index + 1, removed_colored_circle_data)
|
5263
|
+
end
|
5264
|
+
|
5176
5265
|
def launch
|
5177
5266
|
menu('Actions') {
|
5178
5267
|
menu_item('Restart') {
|
@@ -5260,45 +5349,35 @@ class ColorTheCircles
|
|
5260
5349
|
}
|
5261
5350
|
}
|
5262
5351
|
|
5263
|
-
|
5352
|
+
@area = area {
|
5264
5353
|
left 0
|
5265
5354
|
top 4
|
5266
5355
|
hexpand true
|
5267
5356
|
vexpand true
|
5268
5357
|
halign :fill
|
5269
5358
|
valign :fill
|
5270
|
-
|
5271
|
-
|
5272
|
-
|
5359
|
+
|
5360
|
+
on_draw do |area_draw_params|
|
5361
|
+
path {
|
5362
|
+
rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
|
5363
|
+
|
5364
|
+
fill :white
|
5365
|
+
}
|
5366
|
+
|
5367
|
+
@circles_data.each do |circle_data|
|
5273
5368
|
path {
|
5274
|
-
|
5275
|
-
|
5276
|
-
fill :
|
5369
|
+
circle_data[:circle] = circle(*circle_data[:args])
|
5370
|
+
|
5371
|
+
fill circle_data[:fill]
|
5372
|
+
stroke circle_data[:stroke]
|
5277
5373
|
}
|
5278
|
-
|
5279
|
-
@circles_data.each do |circle_data|
|
5280
|
-
path {
|
5281
|
-
circle_data[:circle] = circle(*circle_data[:args])
|
5282
|
-
|
5283
|
-
fill circle_data[:fill]
|
5284
|
-
stroke circle_data[:stroke]
|
5285
|
-
}
|
5286
|
-
end
|
5287
5374
|
end
|
5288
|
-
|
5289
|
-
|
5290
|
-
|
5291
|
-
|
5292
|
-
|
5293
|
-
if clicked_circle_data
|
5294
|
-
clicked_circle_data[:fill] = clicked_circle_data[:stroke]
|
5295
|
-
@area.queue_redraw_all
|
5296
|
-
self.score += 1 # notifies score observers automatically of change
|
5297
|
-
end
|
5298
|
-
end
|
5299
|
-
}
|
5375
|
+
end
|
5376
|
+
|
5377
|
+
on_mouse_down do |area_mouse_event|
|
5378
|
+
color_circle(area_mouse_event[:x], area_mouse_event[:y])
|
5379
|
+
end
|
5300
5380
|
}
|
5301
|
-
|
5302
5381
|
}
|
5303
5382
|
}.show
|
5304
5383
|
end
|
@@ -5838,7 +5917,7 @@ include Glimmer
|
|
5838
5917
|
|
5839
5918
|
Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
|
5840
5919
|
|
5841
|
-
def
|
5920
|
+
def form_field(model, property)
|
5842
5921
|
property = property.to_s
|
5843
5922
|
entry { |e|
|
5844
5923
|
label property.underscore.split('_').map(&:capitalize).join(' ')
|
@@ -5852,11 +5931,11 @@ end
|
|
5852
5931
|
|
5853
5932
|
def address_form(address)
|
5854
5933
|
form {
|
5855
|
-
|
5856
|
-
|
5857
|
-
|
5858
|
-
|
5859
|
-
|
5934
|
+
form_field(address, :street)
|
5935
|
+
form_field(address, :p_o_box)
|
5936
|
+
form_field(address, :city)
|
5937
|
+
form_field(address, :state)
|
5938
|
+
form_field(address, :zip_code)
|
5860
5939
|
}
|
5861
5940
|
end
|
5862
5941
|
|
@@ -5891,35 +5970,65 @@ window('Method-Based Custom Keyword') {
|
|
5891
5970
|
label('Address 1') {
|
5892
5971
|
stretchy false
|
5893
5972
|
}
|
5973
|
+
|
5894
5974
|
address_form(address1)
|
5975
|
+
|
5895
5976
|
horizontal_separator {
|
5896
5977
|
stretchy false
|
5897
5978
|
}
|
5979
|
+
|
5898
5980
|
label('Address 1 (Saved)') {
|
5899
5981
|
stretchy false
|
5900
5982
|
}
|
5983
|
+
|
5901
5984
|
address(address1)
|
5902
5985
|
}
|
5986
|
+
|
5903
5987
|
vertical_separator {
|
5904
5988
|
stretchy false
|
5905
5989
|
}
|
5990
|
+
|
5906
5991
|
vertical_box {
|
5907
5992
|
label('Address 2') {
|
5908
5993
|
stretchy false
|
5909
5994
|
}
|
5995
|
+
|
5910
5996
|
address_form(address2)
|
5997
|
+
|
5911
5998
|
horizontal_separator {
|
5912
5999
|
stretchy false
|
5913
6000
|
}
|
6001
|
+
|
5914
6002
|
label('Address 2 (Saved)') {
|
5915
6003
|
stretchy false
|
5916
6004
|
}
|
6005
|
+
|
5917
6006
|
address(address2)
|
5918
6007
|
}
|
5919
6008
|
}
|
5920
6009
|
}.show
|
5921
6010
|
```
|
5922
6011
|
|
6012
|
+
## Applications
|
6013
|
+
|
6014
|
+
Here are some applications built with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui)
|
6015
|
+
|
6016
|
+
### Manga2PDF
|
6017
|
+
|
6018
|
+
Download and merge manga images into a single pdf file.
|
6019
|
+
|
6020
|
+
https://github.com/PinGunter/manga2pdf
|
6021
|
+
|
6022
|
+

|
6023
|
+
|
6024
|
+
### Befunge98 GUI
|
6025
|
+
|
6026
|
+
Ruby implementation of the Befunge-98 programmming language.
|
6027
|
+
|
6028
|
+
https://github.com/AndyObtiva/befunge98/tree/gui
|
6029
|
+
|
6030
|
+

|
6031
|
+
|
5923
6032
|
## Contributing to glimmer-dsl-libui
|
5924
6033
|
|
5925
6034
|
- Check out the latest master to make sure the feature hasn't been
|