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.
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.10
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 Control support
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 0.2.10](#-glimmer-dsl-for-libui-0210)
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 Controls](#supported-controls)
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
- - **Control**: [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/Properties/Listeners Block**: Any keyword may be optionally followed by a Ruby curly-brace multi-line-block containing nested controls (content) and/or properties (attributes) (e.g. `window('hello world', 300, 200, true) {button('greet')}`). It 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 `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.10'
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 Controls
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
- Control(Args) | Properties | Listeners
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 Controls](#supported-controls)):
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 Controls](#supported-controls))
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` control can have the following properties:
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` can have the following properties:
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 test\n\n"
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 `field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
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 field(model, property)
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
- field(address, :street)
927
- field(address, :p_o_box)
928
- field(address, :city)
929
- field(address, :state)
930
- field(address, :zip_code)
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 Windows and Linux (not Mac) due to a current limitation in [libui](https://github.com/andlabs/ui/issues/357).
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 [Platybus](https://github.com/sveinbjornt/Platypus) (builds a native Mac app from a Ruby script)
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
- result = `ruby -r #{glimmer_dsl_libui_file} #{meta_example_file} 2>&1`
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
- system "ruby -r #{glimmer_dsl_libui_file} #{file_path_for(selected_example)}"
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 Name') {
2591
+ button('Display Info') {
2592
+ stretchy false
2593
+
2524
2594
  on_clicked do
2525
- msg_box('Name', "#{@first_name_entry.text} #{@last_name_entry.text}")
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
- CIRCLE_MIN_RADIUS = 15
5111
- CIRCLE_MAX_RADIUS = 50
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
- if @circles_data.empty?
5146
- # start with 3 circles to make more challenging
5147
- add_circle until @circles_data.size > 3
5148
- else
5149
- add_circle
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
- circle_x_center = rand * (WINDOW_WIDTH - MARGIN_WIDTH - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
5159
- circle_y_center = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
5160
- circle_radius = rand * (CIRCLE_MAX_RADIUS - CIRCLE_MIN_RADIUS) + CIRCLE_MIN_RADIUS
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: [circle_x_center, circle_y_center, circle_radius],
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
- vertical_box {
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
- @area = area {
5272
- on_draw do |area_draw_params|
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
- rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
5275
-
5276
- fill :white
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
- on_mouse_down do |area_mouse_event|
5290
- clicked_circle_data = @circles_data.find do |circle_data|
5291
- circle_data[:fill].nil? && circle_data[:circle].include?(area_mouse_event[:x], area_mouse_event[:y])
5292
- end
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 field(model, property)
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
- field(address, :street)
5856
- field(address, :p_o_box)
5857
- field(address, :city)
5858
- field(address, :state)
5859
- field(address, :zip_code)
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