glimmer-dsl-libui 0.2.10 → 0.2.14
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
|
4
|
-
[![Maintainability](https://api.codeclimate.com/v1/badges/ce2853efdbecf6ebdc73/maintainability)](https://codeclimate.com/github/AndyObtiva/glimmer-dsl-libui/maintainability)
|
5
4
|
[![Join the chat at https://gitter.im/AndyObtiva/glimmer](https://badges.gitter.im/AndyObtiva/glimmer.svg)](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
|
+
![manga2pdf screenshot](https://raw.githubusercontent.com/PinGunter/manga2pdf/master/screenshots/manga2pdf-gui.png)
|
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
|
+
![befunge98 gui screenshot](https://raw.githubusercontent.com/AndyObtiva/befunge98/master/gui/glimmer-dsl-libui/befunge98_gui_glimmer_dsl_libui/screenshots/befunge98_gui_glimmer_dsl_libui_example.png)
|
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
|