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.
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