glimmer-dsl-libui 0.4.0 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
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.4.0
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.4.4
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
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)
@@ -14,11 +14,11 @@ Mac | Windows | Linux
14
14
  The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) as opposed to [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) or [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk) is the fact that [SWT](https://www.eclipse.org/swt/) and [Tk](https://www.tcl.tk/) are more mature than mid-alpha [libui](https://github.com/andlabs/libui) as GUI toolkits. Still, if there is only a need to build a small simple application, [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) could be a good convenient choice due to having zero prerequisites beyond the dependencies included in the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui). Also, just like [Glimmer DSL for Tk](https://github.com/AndyObtiva/glimmer-dsl-tk), its apps start instantly and have a small memory footprint. [LibUI](https://github.com/kojix2/LibUI) is a promising new GUI toolkit that might prove quite worthy in the future.
15
15
 
16
16
  [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) aims to provide a DSL similar to the [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) to enable more productive desktop development in Ruby with:
17
- - Declarative DSL syntax that visually maps to the GUI control hierarchy
18
- - Convention over configuration via smart defaults and automation of low-level details
19
- - Requiring the least amount of syntax possible to build GUI
20
- - Custom Keyword support
21
- - Bidirectional Data-Binding to declaratively wire and automatically synchronize GUI with Business Models
17
+ - [Declarative DSL syntax](#glimmer-gui-dsl-concepts) that visually maps to the GUI control hierarchy
18
+ - [Convention over configuration](#smart-defaults-and-conventions) via smart defaults and automation of low-level details
19
+ - Requiring the [least amount of syntax](#glimmer-gui-dsl-concepts) possible to build GUI
20
+ - [Custom Keyword](#custom-keywords) support
21
+ - [Bidirectional/Unidirectional Data-Binding](#data-binding) to declaratively wire and automatically synchronize GUI Views with Models
22
22
  - [Far Future Plan] Scaffolding for new custom controls, apps, and gems
23
23
  - [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
24
24
 
@@ -233,6 +233,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
233
233
  - [Area Transform Matrix](#area-transform-matrix)
234
234
  - [Smart Defaults and Conventions](#smart-defaults-and-conventions)
235
235
  - [Custom Keywords](#custom-keywords)
236
+ - [Observer Pattern](#observer-pattern)
236
237
  - [Data-Binding](#data-binding)
237
238
  - [API Gotchas](#api-gotchas)
238
239
  - [Original API](#original-api)
@@ -263,6 +264,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
263
264
  - [Basic Draw Text](#basic-draw-text)
264
265
  - [Advanced Examples](#advanced-examples)
265
266
  - [Area Gallery](#area-gallery)
267
+ - [Button Counter](#button-counter)
266
268
  - [Color The Circles](#color-the-circles)
267
269
  - [Control Gallery](#control-gallery)
268
270
  - [Custom Draw Text](#custom-draw-text)
@@ -371,7 +373,7 @@ gem install glimmer-dsl-libui
371
373
  Or install via Bundler `Gemfile`:
372
374
 
373
375
  ```ruby
374
- gem 'glimmer-dsl-libui', '~> 0.4.0'
376
+ gem 'glimmer-dsl-libui', '~> 0.4.4'
375
377
  ```
376
378
 
377
379
  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.
@@ -777,9 +779,9 @@ Check [examples/dynamic_area.rb](#dynamic-area) for a more detailed semi-declara
777
779
  - `scroll_to(x as Numeric, y as Numeric, width as Numeric = main_window.width, height as Numeric = main_window.height)`: scrolls to `x`/`y` location with `width` and `height` viewport size.
778
780
  - `set_size(width as Numeric, height as Numeric)`: set size of scrolling area, which must must exceed that of visible viewport in order for scrolling to be enabled.
779
781
 
780
- Mac |
781
- ----|
782
- ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png)
782
+ Mac |Linux
783
+ ----|-----
784
+ ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
783
785
 
784
786
  Check [examples/basic_scrolling_area.rb](#basic-scrolling-area) for a more detailed example.
785
787
 
@@ -875,7 +877,7 @@ Given that it is very new and is not a [libui](https://github.com/andlabs/libui)
875
877
  - Including an `image` inside an `area` `on_draw` listener improves performance due to not retaining pixel/line data in memory.
876
878
  - Supplying `width` and `height` (2nd and 3rd arguments) greatly improves performance when shrinking image.
877
879
 
878
- Currently, it is recommended to use `image` with very small `width` and `height` values only.
880
+ Currently, it is recommended to use `image` with very small `width` and `height` values only (e.g. 24x24).
879
881
 
880
882
  Setting a [`transform` `matrix`](#area-transform-matrix) is supported under `image` just like it is under `path` and `text` inside `area`.
881
883
 
@@ -1031,6 +1033,8 @@ window('Basic Image', 96, 96) {
1031
1033
 
1032
1034
  One final note is that in Linux, table images grow and shrink with the image size unlike on the Mac where table row heights are constant regardless of image sizes. As such, you may be able to repurpose a table with a single image column and a single row as an image control with more native libui rendering if you are only targeting Linux with your app.
1033
1035
 
1036
+ ![linux table image](images/glimmer-dsl-libui-linux-basic-table-image.png)
1037
+
1034
1038
  Check out [examples/basic_image.rb](#basic-image) (all versions) for examples of using `image` Glimmer custom control.
1035
1039
 
1036
1040
  #### Colors
@@ -1321,23 +1325,72 @@ window('Method-Based Custom Keyword') {
1321
1325
 
1322
1326
  ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
1323
1327
 
1324
- ### Data-Binding
1328
+ ### Observer Pattern
1325
1329
 
1326
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports unidirectional (one-way) data-binding of any control/shape/attributed-string property via the `<=` symbol (indicating data is moving from the right side, which is the model, to the left side, which is the GUI view object).
1330
+ The [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) (a.k.a. Observer Pattern) is fundamental to building GUIs (Graphical User Interfaces) following the [MVC (Model View Controller) Architectural Pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) or any of its variations like [MVP (Model View Presenter)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter). In the original Smalltalk-MVC, the View observes the Model for changes and updates itself accordingly.
1327
1331
 
1328
- This is also known as the [Glimmer Shine](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#shine) syntax for data-binding, a [Glimmer](https://github.com/AndyObtiva/glimmer)-only unique innovation.
1332
+ ![MVC - Model View Controller](https://www.researchgate.net/profile/Danny-Weyns/publication/269303611/figure/fig2/AS:858133056462866@1581606272800/Smalltalk80-MVC-pattern-View-and-Controller-work-as-a-pair-allowing-the-user-to-interact.ppm)
1333
+
1334
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports the [Observer Design Pattern](https://en.wikipedia.org/wiki/Observer_pattern) via the `observe(model, attribute_or_key=nil)` keyword, which can observe `Object` models with attributes, `Hash`es with keys, and `Array`s. It automatically enhances objects as needed to support automatically notifying observers of changes via `observable#notify_observers(attribute_or_key = nil)` method:
1335
+ - `Object` becomes `Glimmer::DataBinding::ObservableModel`, which supports observing specified `Object` model attributes.
1336
+ - `Hash` becomes `Glimmer::DataBinding::ObservableHash`, which supports observing all `Hash` keys or a specific `Hash` key
1337
+ - `Array` becomes `Glimmer::DataBinding::ObservableArray`, which supports observing `Array` changes like those done with `push`, `<<`, `delete`, and `map!` methods (all mutation methods).
1329
1338
 
1330
1339
  Example:
1331
1340
 
1332
1341
  ```ruby
1333
- window {
1334
- title <= [@game, :score, on_read: -> (score) {"Glimmer Snake (Score: #{@game.score})"}]
1342
+ observe(person, :name) do |new_name|
1343
+ @name_label.text = new_name
1344
+ end
1345
+ ```
1346
+
1347
+ That observes a person's name attribute for changes and updates the name `label` `text` property accordingly.
1348
+
1349
+ [Learn about Glimmer's Observer Pattern capabilities and options in more detail at the Glimmer project page.](https://github.com/AndyObtiva/glimmer#data-binding-library)
1350
+
1351
+ See examples of the `observe` keyword at [Color The Circles](#color-the-circles), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake), and [Tetris](#tetris).
1352
+
1353
+ ### Data-Binding
1354
+
1355
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports both bidirectional (two-way) data-binding and unidirectional (one-way) data-binding.
1356
+
1357
+ Data-binding enables writing very expressive, terse, and declarative code to synchronize View properties with Model attributes without writing many lines or pages of imperative code doing the same thing, increasing productivity immensely.
1358
+
1359
+ Data-binding automatically takes advantage of the [Observer Pattern](#observer-pattern) behind the scenes and is very well suited to declaring View property data sources piecemeal. On the other hand, explicit use of the [Observer Pattern](#observer-pattern) is sometimes more suitable when needing to make multiple View updates upon a single Model attribute change.
1360
+
1361
+ Data-binding supports utilizing the [MVP (Model View Presenter)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) flavor of [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) by observing both the View and a Presenter for changes and updating the opposite side upon encountering them. This enables writing more decoupled cleaner code that keeps View code and Model code disentangled and highly maintainable. For example, check out the Snake game presenters for [Grid](/examples/snake/presenter/grid.rb) and [Cell](/examples/snake/presenter/cell.rb), which act as proxies for the actual Snake game models [Snake](/examples/snake/model/snake.rb) and [Apple](/examples/snake/model/apple.rb), mediating synchronization of data between them and the [Snake View GUI](/examples/snake.rb).
1362
+
1363
+ ![MVP](https://www.researchgate.net/profile/Gilles-Perrouin/publication/320249584/figure/fig8/AS:668260987068418@1536337243385/Model-view-presenter-architecture.png)
1364
+
1365
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports bidirectional (two-way) data-binding of the following controls/properties via the `<=>` operator (indicating data is moving in both directions between View and Model):
1366
+ - `entry` `text` property
1367
+ - `multiline_entry` `text` property
1368
+ - `non_wrapping_multiline_entry` `text` property
1369
+ - `search_entry` `text` property
1370
+
1371
+ Example of bidirectional data-binding:
1372
+
1373
+ ```ruby
1374
+ entry {
1375
+ text <=> [contract, :legal_text]
1335
1376
  }
1336
1377
  ```
1337
1378
 
1338
- That is data-binding the `window` `title` property to the `score` attribute of a `@game`, but converting on read from the Model to a `String`.
1379
+ That is data-binding a contract's legal text to an `entry` `text` property.
1380
+
1381
+ Another example of bidirectional data-binding with an option:
1382
+
1383
+ ```ruby
1384
+ entry {
1385
+ text <=> [self, :entered_text, after_write: ->(text) {puts text}]
1386
+ }
1387
+ ```
1388
+
1389
+ That is data-binding `entered_text` attribute on `self` to `entry` `text` property and printing text after write to the model.
1390
+
1391
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) supports unidirectional (one-way) data-binding of any control/shape/attributed-string property via the `<=` operator (indicating data is moving from the right side, which is the Model, to the left side, which is the GUI View object).
1339
1392
 
1340
- Another Example:
1393
+ Example of unidirectional data-binding:
1341
1394
 
1342
1395
  ```ruby
1343
1396
  square(0, 0, CELL_SIZE) {
@@ -1347,9 +1400,44 @@ square(0, 0, CELL_SIZE) {
1347
1400
 
1348
1401
  That is data-binding a grid cell color to a `square` shape's `fill` property. That means if the `color` attribute of the grid cell is updated, the `fill` property of the `square` shape is automatically updated accordingly.
1349
1402
 
1350
- Data-binding enables writing very expressive, terse, and declarative code to synchronize View properties with Model attributes instead of pages of imperative code doing the same thing.
1403
+ Another Example of unidirectional data-binding with an option:
1351
1404
 
1352
- Learn more from data-binding usage in [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
1405
+ ```ruby
1406
+ window {
1407
+ title <= [@game, :score, on_read: -> (score) {"Glimmer Snake (Score: #{@game.score})"}]
1408
+ }
1409
+ ```
1410
+
1411
+ That is data-binding the `window` `title` property to the `score` attribute of a `@game`, but converting on read from the Model to a `String`.
1412
+
1413
+ To summarize the data-binding API:
1414
+ - `view_property <=> [model, attribute, *read_or_write_options]`: Bidirectional (two-way) data-binding to Model attribute accessor
1415
+ - `view_property <= [model, attribute, *read_only_options]`: Unidirectional (one-way) data-binding to Model attribute reader
1416
+
1417
+ This is also known as the [Glimmer Shine](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#shine) syntax for data-binding, a [Glimmer](https://github.com/AndyObtiva/glimmer)-only unique innovation that takes advantage of [Ruby](https://www.ruby-lang.org/en/)'s highly expressive syntax and malleable DSL support.
1418
+
1419
+ Data-binding options include:
1420
+ - `before_read {|value| ...}`: performs an operation before reading data from Model to update the View.
1421
+ - `on_read {|value| ...}`: converts value read from Model to update the View.
1422
+ - `after_read {|converted_value| ...}`: performs an operation after read from Model and updating the View.
1423
+ - `before_write {|value| ...}`: performs an operation before writing data to Model from View.
1424
+ - `on_write {|value| ...}`: converts value read from View to update the Model.
1425
+ - `after_write {|converted_value| ...}`: performs an operation after writing to Model from View.
1426
+ - `computed_by attribute` or `computed_by [attribute1, attribute2, ...]`: indicates model attribute is computed from specified attribute(s), thus updated when they are updated (see in [Login example version 2](/examples/login2.rb))
1427
+
1428
+ Note that with both `on_read` and `on_write` converters, you could pass a `Symbol` representing the name of a method on the value object to invoke.
1429
+
1430
+ Example:
1431
+
1432
+ ```ruby
1433
+ entry {
1434
+ text <=> [product, :price, on_read: :to_s, on_write: :to_i]
1435
+ }
1436
+ ```
1437
+
1438
+ Gotcha: never data-bind a control property to an attribute on the same view object with the same exact name (e.g. binding `entry` `text` property to `self` `text` attribute) as it would conflict with it. Instead, data-bind view property to an attribute with a different name on the view object or with the same name, but on a presenter or model object (e.g. data-bind `entry` `text` to `self` `legal_text` attribute or to `contract` model `text` attribute)
1439
+
1440
+ Learn more from data-binding usage in [Login](#login) (4 data-binding versions), [Basic Entry](#basic-entry), [Form](#form), [Form Table](#form-table), [Method-Based Custom Keyword](#method-based-custom-keyword), [Snake](#snake) and [Tic Tac Toe](#tic_tac_toe) examples.
1353
1441
 
1354
1442
  ### API Gotchas
1355
1443
 
@@ -1365,6 +1453,270 @@ Learn more from data-binding usage in [Snake](#snake) and [Tic Tac Toe](#tic_tac
1365
1453
 
1366
1454
  ### Original API
1367
1455
 
1456
+ Here are all the lower-level [LibUI](https://github.com/kojix2/LibUI) API methods utilized by [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui):
1457
+ - `alloc_control`
1458
+ - `area_begin_user_window_move`
1459
+ - `area_begin_user_window_resize`
1460
+ - `area_queue_redraw_all`
1461
+ - `area_scroll_to`
1462
+ - `area_set_size`
1463
+ - `attribute_color`
1464
+ - `attribute_family`
1465
+ - `attribute_features`
1466
+ - `attribute_get_type`
1467
+ - `attribute_italic`
1468
+ - `attribute_size`
1469
+ - `attribute_stretch`
1470
+ - `attribute_underline`
1471
+ - `attribute_underline_color`
1472
+ - `attribute_weight`
1473
+ - `attributed_string_append_unattributed`
1474
+ - `attributed_string_byte_index_to_grapheme`
1475
+ - `attributed_string_delete`
1476
+ - `attributed_string_for_each_attribute`
1477
+ - `attributed_string_grapheme_to_byte_index`
1478
+ - `attributed_string_insert_at_unattributed`
1479
+ - `attributed_string_len`
1480
+ - `attributed_string_num_graphemes`
1481
+ - `attributed_string_set_attribute`
1482
+ - `attributed_string_string`
1483
+ - `box_append`
1484
+ - `box_delete`
1485
+ - `box_padded`
1486
+ - `box_set_padded`
1487
+ - `button_on_clicked`
1488
+ - `button_set_text`
1489
+ - `button_text`
1490
+ - `checkbox_checked`
1491
+ - `checkbox_on_toggled`
1492
+ - `checkbox_set_checked`
1493
+ - `checkbox_set_text`
1494
+ - `checkbox_text`
1495
+ - `color_button_color`
1496
+ - `color_button_on_changed`
1497
+ - `color_button_set_color`
1498
+ - `combobox_append`
1499
+ - `combobox_on_selected`
1500
+ - `combobox_selected`
1501
+ - `combobox_set_selected`
1502
+ - `control_destroy`
1503
+ - `control_disable`
1504
+ - `control_enable`
1505
+ - `control_enabled`
1506
+ - `control_enabled_to_user`
1507
+ - `control_handle`
1508
+ - `control_hide`
1509
+ - `control_parent`
1510
+ - `control_set_parent`
1511
+ - `control_show`
1512
+ - `control_toplevel`
1513
+ - `control_verify_set_parent`
1514
+ - `control_visible`
1515
+ - `date_time_picker_on_changed`
1516
+ - `date_time_picker_set_time`
1517
+ - `date_time_picker_time`
1518
+ - `draw_clip`
1519
+ - `draw_fill`
1520
+ - `draw_free_path`
1521
+ - `draw_free_text_layout`
1522
+ - `draw_matrix_invert`
1523
+ - `draw_matrix_invertible`
1524
+ - `draw_matrix_multiply`
1525
+ - `draw_matrix_rotate`
1526
+ - `draw_matrix_scale`
1527
+ - `draw_matrix_set_identity`
1528
+ - `draw_matrix_skew`
1529
+ - `draw_matrix_transform_point`
1530
+ - `draw_matrix_transform_size`
1531
+ - `draw_matrix_translate`
1532
+ - `draw_new_path`
1533
+ - `draw_new_text_layout`
1534
+ - `draw_path_add_rectangle`
1535
+ - `draw_path_arc_to`
1536
+ - `draw_path_bezier_to`
1537
+ - `draw_path_close_figure`
1538
+ - `draw_path_end`
1539
+ - `draw_path_line_to`
1540
+ - `draw_path_new_figure`
1541
+ - `draw_path_new_figure_with_arc`
1542
+ - `draw_restore`
1543
+ - `draw_save`
1544
+ - `draw_stroke`
1545
+ - `draw_text`
1546
+ - `draw_text_layout_extents`
1547
+ - `draw_transform`
1548
+ - `editable_combobox_append`
1549
+ - `editable_combobox_on_changed`
1550
+ - `editable_combobox_set_text`
1551
+ - `editable_combobox_text`
1552
+ - `entry_on_changed`
1553
+ - `entry_read_only`
1554
+ - `entry_set_read_only`
1555
+ - `entry_set_text`
1556
+ - `entry_text`
1557
+ - `ffi_lib`
1558
+ - `ffi_lib=`
1559
+ - `font_button_font`
1560
+ - `font_button_on_changed`
1561
+ - `form_append`
1562
+ - `form_delete`
1563
+ - `form_padded`
1564
+ - `form_set_padded`
1565
+ - `free_attribute`
1566
+ - `free_attributed_string`
1567
+ - `free_control`
1568
+ - `free_font_button_font`
1569
+ - `free_image`
1570
+ - `free_init_error`
1571
+ - `free_open_type_features`
1572
+ - `free_table_model`
1573
+ - `free_table_value`
1574
+ - `free_text`
1575
+ - `grid_append`
1576
+ - `grid_insert_at`
1577
+ - `grid_padded`
1578
+ - `grid_set_padded`
1579
+ - `group_margined`
1580
+ - `group_set_child`
1581
+ - `group_set_margined`
1582
+ - `group_set_title`
1583
+ - `group_title`
1584
+ - `image_append`
1585
+ - `init`
1586
+ - `label_set_text`
1587
+ - `label_text`
1588
+ - `main`
1589
+ - `main_step`
1590
+ - `main_steps`
1591
+ - `menu_append_about_item`
1592
+ - `menu_append_check_item`
1593
+ - `menu_append_item`
1594
+ - `menu_append_preferences_item`
1595
+ - `menu_append_quit_item`
1596
+ - `menu_append_separator`
1597
+ - `menu_item_checked`
1598
+ - `menu_item_disable`
1599
+ - `menu_item_enable`
1600
+ - `menu_item_on_clicked`
1601
+ - `menu_item_set_checked`
1602
+ - `msg_box`
1603
+ - `msg_box_error`
1604
+ - `multiline_entry_append`
1605
+ - `multiline_entry_on_changed`
1606
+ - `multiline_entry_read_only`
1607
+ - `multiline_entry_set_read_only`
1608
+ - `multiline_entry_set_text`
1609
+ - `multiline_entry_text`
1610
+ - `new_area`
1611
+ - `new_attributed_string`
1612
+ - `new_background_attribute`
1613
+ - `new_button`
1614
+ - `new_checkbox`
1615
+ - `new_color_attribute`
1616
+ - `new_color_button`
1617
+ - `new_combobox`
1618
+ - `new_date_picker`
1619
+ - `new_date_time_picker`
1620
+ - `new_editable_combobox`
1621
+ - `new_entry`
1622
+ - `new_family_attribute`
1623
+ - `new_features_attribute`
1624
+ - `new_font_button`
1625
+ - `new_form`
1626
+ - `new_grid`
1627
+ - `new_group`
1628
+ - `new_horizontal_box`
1629
+ - `new_horizontal_separator`
1630
+ - `new_image`
1631
+ - `new_italic_attribute`
1632
+ - `new_label`
1633
+ - `new_menu`
1634
+ - `new_multiline_entry`
1635
+ - `new_non_wrapping_multiline_entry`
1636
+ - `new_open_type_features`
1637
+ - `new_password_entry`
1638
+ - `new_progress_bar`
1639
+ - `new_radio_buttons`
1640
+ - `new_scrolling_area`
1641
+ - `new_search_entry`
1642
+ - `new_size_attribute`
1643
+ - `new_slider`
1644
+ - `new_spinbox`
1645
+ - `new_stretch_attribute`
1646
+ - `new_tab`
1647
+ - `new_table`
1648
+ - `new_table_model`
1649
+ - `new_table_value_color`
1650
+ - `new_table_value_image`
1651
+ - `new_table_value_int`
1652
+ - `new_table_value_string`
1653
+ - `new_time_picker`
1654
+ - `new_underline_attribute`
1655
+ - `new_underline_color_attribute`
1656
+ - `new_vertical_box`
1657
+ - `new_vertical_separator`
1658
+ - `new_weight_attribute`
1659
+ - `new_window`
1660
+ - `on_should_quit`
1661
+ - `open_file`
1662
+ - `open_type_features_add`
1663
+ - `open_type_features_clone`
1664
+ - `open_type_features_for_each`
1665
+ - `open_type_features_get`
1666
+ - `open_type_features_remove`
1667
+ - `progress_bar_set_value`
1668
+ - `progress_bar_value`
1669
+ - `queue_main`
1670
+ - `quit`
1671
+ - `radio_buttons_append`
1672
+ - `radio_buttons_on_selected`
1673
+ - `radio_buttons_selected`
1674
+ - `radio_buttons_set_selected`
1675
+ - `save_file`
1676
+ - `slider_on_changed`
1677
+ - `slider_set_value`
1678
+ - `slider_value`
1679
+ - `spinbox_on_changed`
1680
+ - `spinbox_set_value`
1681
+ - `spinbox_value`
1682
+ - `tab_append`
1683
+ - `tab_delete`
1684
+ - `tab_insert_at`
1685
+ - `tab_margined`
1686
+ - `tab_num_pages`
1687
+ - `tab_set_margined`
1688
+ - `table_append_button_column`
1689
+ - `table_append_checkbox_column`
1690
+ - `table_append_checkbox_text_column`
1691
+ - `table_append_image_column`
1692
+ - `table_append_image_text_column`
1693
+ - `table_append_progress_bar_column`
1694
+ - `table_append_text_column`
1695
+ - `table_model_row_changed`
1696
+ - `table_model_row_deleted`
1697
+ - `table_model_row_inserted`
1698
+ - `table_value_color`
1699
+ - `table_value_get_type`
1700
+ - `table_value_image`
1701
+ - `table_value_int`
1702
+ - `table_value_string`
1703
+ - `timer`
1704
+ - `uninit`
1705
+ - `user_bug_cannot_set_parent_on_toplevel`
1706
+ - `window_borderless`
1707
+ - `window_content_size`
1708
+ - `window_fullscreen`
1709
+ - `window_margined`
1710
+ - `window_on_closing`
1711
+ - `window_on_content_size_changed`
1712
+ - `window_set_borderless`
1713
+ - `window_set_child`
1714
+ - `window_set_content_size`
1715
+ - `window_set_fullscreen`
1716
+ - `window_set_margined`
1717
+ - `window_set_title`
1718
+ - `window_title`
1719
+
1368
1720
  To learn more about the [LibUI](https://github.com/kojix2/LibUI) API exposed through [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui):
1369
1721
  - Check out [LibUI ffi.rb](https://github.com/kojix2/LibUI/blob/main/lib/libui/ffi.rb)
1370
1722
  - Check out the [libui C Headers](https://github.com/andlabs/libui/blob/master/ui.h)
@@ -1871,7 +2223,44 @@ UI.main
1871
2223
  UI.quit
1872
2224
  ```
1873
2225
 
1874
- [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2226
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2227
+
2228
+ ```ruby
2229
+ require 'glimmer-dsl-libui'
2230
+
2231
+ class BasicEntry
2232
+ include Glimmer
2233
+
2234
+ attr_accessor :entry_text
2235
+
2236
+ def launch
2237
+ window('Basic Entry', 300, 50) {
2238
+ horizontal_box {
2239
+ entry {
2240
+ # stretchy true # Smart default option for appending to horizontal_box
2241
+ text <=> [self, :entry_text, after_write: ->(text) {puts text; $stdout.flush}] # bidirectional data-binding between text property and entry_text attribute, printing after write to model.
2242
+ }
2243
+
2244
+ button('Button') {
2245
+ stretchy false # stretchy property is available when control is nested under horizontal_box
2246
+
2247
+ on_clicked do
2248
+ msg_box('You entered', entry_text)
2249
+ end
2250
+ }
2251
+ }
2252
+
2253
+ on_closing do
2254
+ puts 'Bye Bye'
2255
+ end
2256
+ }.show
2257
+ end
2258
+ end
2259
+
2260
+ BasicEntry.new.launch
2261
+ ```
2262
+
2263
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
1875
2264
 
1876
2265
  ```ruby
1877
2266
  require 'glimmer-dsl-libui'
@@ -2195,7 +2584,59 @@ Mac | Windows | Linux
2195
2584
  ----|---------|------
2196
2585
  ![glimmer-dsl-libui-mac-form.png](images/glimmer-dsl-libui-mac-form.png) ![glimmer-dsl-libui-mac-form-msg-box.png](images/glimmer-dsl-libui-mac-form-msg-box.png) | ![glimmer-dsl-libui-windows-form.png](images/glimmer-dsl-libui-windows-form.png) ![glimmer-dsl-libui-windows-form-msg-box.png](images/glimmer-dsl-libui-windows-form-msg-box.png) | ![glimmer-dsl-libui-linux-form.png](images/glimmer-dsl-libui-linux-form.png) ![glimmer-dsl-libui-linux-form-msg-box.png](images/glimmer-dsl-libui-linux-form-msg-box.png)
2197
2586
 
2198
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2587
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
2588
+
2589
+ ```ruby
2590
+ require 'glimmer-dsl-libui'
2591
+
2592
+ class Form
2593
+ include Glimmer
2594
+
2595
+ attr_accessor :first_name, :last_name, :phone, :email
2596
+
2597
+ def launch
2598
+ window('Form') {
2599
+ margined true
2600
+
2601
+ vertical_box {
2602
+ form {
2603
+ entry {
2604
+ label 'First Name' # label property is available when control is nested under form
2605
+ text <=> [self, :first_name] # bidirectional data-binding of entry text property to self first_name attribute
2606
+ }
2607
+
2608
+ entry {
2609
+ label 'Last Name' # label property is available when control is nested under form
2610
+ text <=> [self, :last_name]
2611
+ }
2612
+
2613
+ entry {
2614
+ label 'Phone' # label property is available when control is nested under form
2615
+ text <=> [self, :phone]
2616
+ }
2617
+
2618
+ entry {
2619
+ label 'Email' # label property is available when control is nested under form
2620
+ text <=> [self, :email]
2621
+ }
2622
+ }
2623
+
2624
+ button('Display Info') {
2625
+ stretchy false
2626
+
2627
+ on_clicked do
2628
+ msg_box('Info', "#{first_name} #{last_name} has phone #{phone} and email #{email}")
2629
+ end
2630
+ }
2631
+ }
2632
+ }.show
2633
+ end
2634
+ end
2635
+
2636
+ Form.new.launch
2637
+ ```
2638
+
2639
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
2199
2640
 
2200
2641
  ```ruby
2201
2642
  require 'glimmer-dsl-libui'
@@ -3077,9 +3518,9 @@ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/
3077
3518
  ruby -r glimmer-dsl-libui -e "require 'examples/basic_scrolling_area'"
3078
3519
  ```
3079
3520
 
3080
- Mac |
3081
- ----|
3082
- ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png)
3521
+ Mac | Linux
3522
+ ----|------
3523
+ ![glimmer-dsl-libui-mac-dynamic-area.png](images/glimmer-dsl-libui-mac-basic-scrolling-area.png) ![glimmer-dsl-libui-mac-dynamic-area-updated.png](images/glimmer-dsl-libui-mac-basic-scrolling-area-scrolled.png) | ![glimmer-dsl-libui-linux-dynamic-area.png](images/glimmer-dsl-libui-linux-basic-scrolling-area.png) ![glimmer-dsl-libui-linux-dynamic-area-updated.png](images/glimmer-dsl-libui-linux-basic-scrolling-area-scrolled.png)
3083
3524
 
3084
3525
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
3085
3526
 
@@ -4400,6 +4841,56 @@ window('Area Gallery', 400, 400) {
4400
4841
  }.show
4401
4842
  ```
4402
4843
 
4844
+ #### Button Counter
4845
+
4846
+ [examples/button_counter.rb](examples/button_counter.rb)
4847
+
4848
+ Run with this command from the root of the project if you cloned the project:
4849
+
4850
+ ```
4851
+ ruby -r './lib/glimmer-dsl-libui' examples/button_counter.rb
4852
+ ```
4853
+
4854
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4855
+
4856
+ ```
4857
+ ruby -r glimmer-dsl-libui -e "require 'examples/button_counter'"
4858
+ ```
4859
+
4860
+ Mac | Linux
4861
+ ----|------
4862
+ ![glimmer-dsl-libui-mac-button-counter.png](images/glimmer-dsl-libui-mac-button-counter.png) | ![glimmer-dsl-libui-linux-button-counter.png](images/glimmer-dsl-libui-linux-button-counter.png)
4863
+
4864
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4865
+
4866
+ ```ruby
4867
+ require 'glimmer-dsl-libui'
4868
+
4869
+ class ButtonCounter
4870
+ include Glimmer
4871
+
4872
+ attr_accessor :count
4873
+
4874
+ def initialize
4875
+ @count = 0
4876
+ end
4877
+
4878
+ def launch
4879
+ window('Hello, Button!') {
4880
+ button {
4881
+ text <= [self, :count, on_read: ->(count) {"Count: #{count}"}] # data-bind button text to self count, converting to string on read.
4882
+
4883
+ on_clicked do
4884
+ self.count += 1
4885
+ end
4886
+ }
4887
+ }.show
4888
+ end
4889
+ end
4890
+
4891
+ ButtonCounter.new.launch
4892
+ ```
4893
+
4403
4894
  #### Color The Circles
4404
4895
 
4405
4896
  [examples/color_the_circles.rb](examples/color_the_circles.rb)
@@ -4451,7 +4942,8 @@ class ColorTheCircles
4451
4942
  end
4452
4943
 
4453
4944
  def register_observers
4454
- observer = Glimmer::DataBinding::Observer.proc do |new_score|
4945
+ # observe automatically enhances self to become Glimmer::DataBinding::ObservableModel and notify observer block of score attribute changes
4946
+ observe(self, :score) do |new_score|
4455
4947
  Glimmer::LibUI.queue_main do
4456
4948
  @score_label.text = new_score.to_s
4457
4949
  if new_score == -20
@@ -4465,7 +4957,6 @@ class ColorTheCircles
4465
4957
  end
4466
4958
  end
4467
4959
  end
4468
- observer.observe(self, :score) # automatically enhances self to become Glimmer::DataBinding::ObservableModel and notify observer on score attribute changes
4469
4960
  end
4470
4961
 
4471
4962
  def setup_circle_factory
@@ -5596,7 +6087,120 @@ Mac | Windows | Linux
5596
6087
  ----|---------|------
5597
6088
  ![glimmer-dsl-libui-mac-form-table.png](images/glimmer-dsl-libui-mac-form-table.png) ![glimmer-dsl-libui-mac-form-table-contact-entered.png](images/glimmer-dsl-libui-mac-form-table-contact-entered.png) ![glimmer-dsl-libui-mac-form-table-filtered.png](images/glimmer-dsl-libui-mac-form-table-filtered.png) | ![glimmer-dsl-libui-windows-form-table.png](images/glimmer-dsl-libui-windows-form-table.png) ![glimmer-dsl-libui-windows-form-table-contact-entered.png](images/glimmer-dsl-libui-windows-form-table-contact-entered.png) ![glimmer-dsl-libui-windows-form-table-filtered.png](images/glimmer-dsl-libui-windows-form-table-filtered.png) | ![glimmer-dsl-libui-linux-form-table.png](images/glimmer-dsl-libui-linux-form-table.png) ![glimmer-dsl-libui-linux-form-table-contact-entered.png](images/glimmer-dsl-libui-linux-form-table-contact-entered.png) ![glimmer-dsl-libui-linux-form-table-filtered.png](images/glimmer-dsl-libui-linux-form-table-filtered.png)
5598
6089
 
5599
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6090
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6091
+
6092
+ ```ruby
6093
+ require 'glimmer-dsl-libui'
6094
+
6095
+ class FormTable
6096
+ include Glimmer
6097
+
6098
+ attr_accessor :name, :email, :phone, :city, :state, :filter_value
6099
+
6100
+ def initialize
6101
+ @data = [
6102
+ ['Lisa Sky', 'lisa@sky.com', '720-523-4329', 'Denver', 'CO', '80014'],
6103
+ ['Jordan Biggins', 'jordan@biggins.com', '617-528-5399', 'Boston', 'MA', '02101'],
6104
+ ['Mary Glass', 'mary@glass.com', '847-589-8788', 'Elk Grove Village', 'IL', '60007'],
6105
+ ['Darren McGrath', 'darren@mcgrath.com', '206-539-9283', 'Seattle', 'WA', '98101'],
6106
+ ['Melody Hanheimer', 'melody@hanheimer.com', '213-493-8274', 'Los Angeles', 'CA', '90001'],
6107
+ ]
6108
+ end
6109
+
6110
+ def launch
6111
+ window('Contacts', 600, 600) { |w|
6112
+ margined true
6113
+
6114
+ vertical_box {
6115
+ form {
6116
+ stretchy false
6117
+
6118
+ entry {
6119
+ label 'Name'
6120
+ text <=> [self, :name]
6121
+ }
6122
+
6123
+ entry {
6124
+ label 'Email'
6125
+ text <=> [self, :email]
6126
+ }
6127
+
6128
+ entry {
6129
+ label 'Phone'
6130
+ text <=> [self, :phone]
6131
+ }
6132
+
6133
+ entry {
6134
+ label 'City'
6135
+ text <=> [self, :city]
6136
+ }
6137
+
6138
+ entry {
6139
+ label 'State'
6140
+ text <=> [self, :state]
6141
+ }
6142
+ }
6143
+
6144
+ button('Save Contact') {
6145
+ stretchy false
6146
+
6147
+ on_clicked do
6148
+ new_row = [name, email, phone, city, state]
6149
+ if new_row.include?('')
6150
+ msg_box_error(w, 'Validation Error!', 'All fields are required! Please make sure to enter a value for all fields.')
6151
+ else
6152
+ @data << new_row # automatically inserts a row into the table due to implicit data-binding
6153
+ @unfiltered_data = @data.dup
6154
+ self.name = '' # automatically clears name entry through explicit data-binding
6155
+ self.email = ''
6156
+ self.phone = ''
6157
+ self.city = ''
6158
+ self.state = ''
6159
+ end
6160
+ end
6161
+ }
6162
+
6163
+ search_entry {
6164
+ stretchy false
6165
+ text <=> [self, :filter_value, # bidirectional data-binding of text to self.filter_value with after_write option
6166
+ after_write: ->(filter_value) { # execute after write to self.filter_value
6167
+ @unfiltered_data ||= @data.dup
6168
+ # Unfilter first to remove any previous filters
6169
+ @data.replace(@unfiltered_data) # affects table indirectly through implicit data-binding
6170
+ # Now, apply filter if entered
6171
+ unless filter_value.empty?
6172
+ @data.filter! do |row_data| # affects table indirectly through implicit data-binding
6173
+ row_data.any? do |cell|
6174
+ cell.to_s.downcase.include?(filter_value.downcase)
6175
+ end
6176
+ end
6177
+ end
6178
+ }
6179
+ ]
6180
+ }
6181
+
6182
+ table {
6183
+ text_column('Name')
6184
+ text_column('Email')
6185
+ text_column('Phone')
6186
+ text_column('City')
6187
+ text_column('State')
6188
+
6189
+ cell_rows @data # implicit data-binding
6190
+
6191
+ on_changed do |row, type, row_data|
6192
+ puts "Row #{row} #{type}: #{row_data}"
6193
+ end
6194
+ }
6195
+ }
6196
+ }.show
6197
+ end
6198
+ end
6199
+
6200
+ FormTable.new.launch
6201
+ ```
6202
+
6203
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
5600
6204
 
5601
6205
  ```ruby
5602
6206
  require 'glimmer-dsl-libui'
@@ -6175,7 +6779,259 @@ Mac | Windows | Linux
6175
6779
  ----|---------|------
6176
6780
  ![glimmer-dsl-libui-mac-login.png](images/glimmer-dsl-libui-mac-login.png) ![glimmer-dsl-libui-mac-login-logged-in.png](images/glimmer-dsl-libui-mac-login-logged-in.png) | ![glimmer-dsl-libui-windows-login.png](images/glimmer-dsl-libui-windows-login.png) ![glimmer-dsl-libui-windows-login-logged-in.png](images/glimmer-dsl-libui-windows-login-logged-in.png) | ![glimmer-dsl-libui-linux-login.png](images/glimmer-dsl-libui-linux-login.png) ![glimmer-dsl-libui-linux-login-logged-in.png](images/glimmer-dsl-libui-linux-login-logged-in.png)
6177
6781
 
6178
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
6782
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
6783
+
6784
+ ```ruby
6785
+ require 'glimmer-dsl-libui'
6786
+
6787
+ class Login
6788
+ include Glimmer
6789
+
6790
+ attr_accessor :username, :password, :logged_in
6791
+
6792
+ def launch
6793
+ window('Login') {
6794
+ margined true
6795
+
6796
+ vertical_box {
6797
+ form {
6798
+ entry {
6799
+ label 'Username:'
6800
+ text <=> [self, :username]
6801
+ enabled <= [self, :logged_in, on_read: :!]
6802
+ }
6803
+
6804
+ password_entry {
6805
+ label 'Password:'
6806
+ text <=> [self, :password]
6807
+ enabled <= [self, :logged_in, on_read: :!]
6808
+ }
6809
+ }
6810
+
6811
+ horizontal_box {
6812
+ button('Login') {
6813
+ enabled <= [self, :logged_in, on_read: :!]
6814
+
6815
+ on_clicked do
6816
+ self.logged_in = true
6817
+ end
6818
+ }
6819
+
6820
+ button('Logout') {
6821
+ enabled <= [self, :logged_in]
6822
+
6823
+ on_clicked do
6824
+ self.logged_in = false
6825
+ self.username = ''
6826
+ self.password = ''
6827
+ end
6828
+ }
6829
+ }
6830
+ }
6831
+ }.show
6832
+ end
6833
+ end
6834
+
6835
+ Login.new.launch
6836
+ ```
6837
+
6838
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (with [data-binding](#data-binding)):
6839
+
6840
+ ```ruby
6841
+ require 'glimmer-dsl-libui'
6842
+
6843
+ class Login
6844
+ include Glimmer
6845
+
6846
+ attr_accessor :username, :password, :logged_in
6847
+
6848
+ def logged_out
6849
+ !logged_in
6850
+ end
6851
+
6852
+ def launch
6853
+ window('Login') {
6854
+ margined true
6855
+
6856
+ vertical_box {
6857
+ form {
6858
+ entry {
6859
+ label 'Username:'
6860
+ text <=> [self, :username]
6861
+ enabled <= [self, :logged_out, computed_by: :logged_in] # computed_by option ensures being notified of changes to logged_in
6862
+ }
6863
+
6864
+ password_entry {
6865
+ label 'Password:'
6866
+ text <=> [self, :password]
6867
+ enabled <= [self, :logged_out, computed_by: :logged_in]
6868
+ }
6869
+ }
6870
+
6871
+ horizontal_box {
6872
+ button('Login') {
6873
+ enabled <= [self, :logged_out, computed_by: :logged_in]
6874
+
6875
+ on_clicked do
6876
+ self.logged_in = true
6877
+ end
6878
+ }
6879
+
6880
+ button('Logout') {
6881
+ enabled <= [self, :logged_in]
6882
+
6883
+ on_clicked do
6884
+ self.logged_in = false
6885
+ self.username = ''
6886
+ self.password = ''
6887
+ end
6888
+ }
6889
+ }
6890
+ }
6891
+ }.show
6892
+ end
6893
+ end
6894
+
6895
+ Login.new.launch
6896
+ ```
6897
+
6898
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 3 (with [data-binding](#data-binding)):
6899
+
6900
+ ```ruby
6901
+ require 'glimmer-dsl-libui'
6902
+
6903
+ class Login
6904
+ include Glimmer
6905
+
6906
+ attr_accessor :username, :password
6907
+ attr_reader :logged_in
6908
+
6909
+ def logged_in=(value)
6910
+ @logged_in = value
6911
+ self.logged_out = !value # calling logged_out= method notifies logged_out observers
6912
+ end
6913
+
6914
+ def logged_out=(value)
6915
+ self.logged_in = !value unless logged_in == !value
6916
+ end
6917
+
6918
+ def logged_out
6919
+ !logged_in
6920
+ end
6921
+
6922
+ def launch
6923
+ window('Login') {
6924
+ margined true
6925
+
6926
+ vertical_box {
6927
+ form {
6928
+ entry {
6929
+ label 'Username:'
6930
+ text <=> [self, :username]
6931
+ enabled <= [self, :logged_out]
6932
+ }
6933
+
6934
+ password_entry {
6935
+ label 'Password:'
6936
+ text <=> [self, :password]
6937
+ enabled <= [self, :logged_out]
6938
+ }
6939
+ }
6940
+
6941
+ horizontal_box {
6942
+ button('Login') {
6943
+ enabled <= [self, :logged_out]
6944
+
6945
+ on_clicked do
6946
+ self.logged_in = true
6947
+ end
6948
+ }
6949
+
6950
+ button('Logout') {
6951
+ enabled <= [self, :logged_in]
6952
+
6953
+ on_clicked do
6954
+ self.logged_in = false
6955
+ self.username = ''
6956
+ self.password = ''
6957
+ end
6958
+ }
6959
+ }
6960
+ }
6961
+ }.show
6962
+ end
6963
+ end
6964
+
6965
+ Login.new.launch
6966
+ ```
6967
+
6968
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 4 (with [data-binding](#data-binding)):
6969
+
6970
+ ```ruby
6971
+ require 'glimmer-dsl-libui'
6972
+
6973
+ class Login
6974
+ include Glimmer
6975
+
6976
+ attr_accessor :username, :password
6977
+ attr_reader :logged_in
6978
+
6979
+ def logged_in=(value)
6980
+ @logged_in = value
6981
+ notify_observers(:logged_out) # manually notify observers of logged_out upon logged_in changes; this method comes automatically from enhancement as Glimmer::DataBinding::ObservableModel via data-binding
6982
+ end
6983
+
6984
+ def logged_out
6985
+ !logged_in
6986
+ end
6987
+
6988
+ def launch
6989
+ window('Login') {
6990
+ margined true
6991
+
6992
+ vertical_box {
6993
+ form {
6994
+ entry {
6995
+ label 'Username:'
6996
+ text <=> [self, :username]
6997
+ enabled <= [self, :logged_out]
6998
+ }
6999
+
7000
+ password_entry {
7001
+ label 'Password:'
7002
+ text <=> [self, :password]
7003
+ enabled <= [self, :logged_out]
7004
+ }
7005
+ }
7006
+
7007
+ horizontal_box {
7008
+ button('Login') {
7009
+ enabled <= [self, :logged_out]
7010
+
7011
+ on_clicked do
7012
+ self.logged_in = true
7013
+ end
7014
+ }
7015
+
7016
+ button('Logout') {
7017
+ enabled <= [self, :logged_in]
7018
+
7019
+ on_clicked do
7020
+ self.logged_in = false
7021
+ self.username = ''
7022
+ self.password = ''
7023
+ end
7024
+ }
7025
+ }
7026
+ }
7027
+ }.show
7028
+ end
7029
+ end
7030
+
7031
+ Login.new.launch
7032
+ ```
7033
+
7034
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 5 (without [data-binding](#data-binding)):
6179
7035
 
6180
7036
  ```ruby
6181
7037
  require 'glimmer-dsl-libui'
@@ -6243,7 +7099,102 @@ Mac | Windows | Linux
6243
7099
  ----|---------|------
6244
7100
  ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png) | ![glimmer-dsl-libui-windows-method-based-custom-keyword.png](images/glimmer-dsl-libui-windows-method-based-custom-keyword.png) | ![glimmer-dsl-libui-linux-method-based-custom-keyword.png](images/glimmer-dsl-libui-linux-method-based-custom-keyword.png)
6245
7101
 
6246
- New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
7102
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
7103
+
7104
+ ```ruby
7105
+ require 'glimmer-dsl-libui'
7106
+ require 'facets'
7107
+
7108
+ include Glimmer
7109
+
7110
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
7111
+
7112
+ def form_field(model, attribute)
7113
+ attribute = attribute.to_s
7114
+ entry { |e|
7115
+ label attribute.underscore.split('_').map(&:capitalize).join(' ')
7116
+ text <=> [model, attribute]
7117
+ }
7118
+ end
7119
+
7120
+ def address_form(address)
7121
+ form {
7122
+ form_field(address, :street)
7123
+ form_field(address, :p_o_box)
7124
+ form_field(address, :city)
7125
+ form_field(address, :state)
7126
+ form_field(address, :zip_code)
7127
+ }
7128
+ end
7129
+
7130
+ def label_pair(model, attribute, value)
7131
+ horizontal_box {
7132
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
7133
+ label(value.to_s) {
7134
+ text <= [model, attribute]
7135
+ }
7136
+ }
7137
+ end
7138
+
7139
+ def address(address)
7140
+ vertical_box {
7141
+ address.each_pair do |attribute, value|
7142
+ label_pair(address, attribute, value)
7143
+ end
7144
+ }
7145
+ end
7146
+
7147
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
7148
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
7149
+
7150
+ window('Method-Based Custom Keyword') {
7151
+ margined true
7152
+
7153
+ horizontal_box {
7154
+ vertical_box {
7155
+ label('Address 1') {
7156
+ stretchy false
7157
+ }
7158
+
7159
+ address_form(address1)
7160
+
7161
+ horizontal_separator {
7162
+ stretchy false
7163
+ }
7164
+
7165
+ label('Address 1 (Saved)') {
7166
+ stretchy false
7167
+ }
7168
+
7169
+ address(address1)
7170
+ }
7171
+
7172
+ vertical_separator {
7173
+ stretchy false
7174
+ }
7175
+
7176
+ vertical_box {
7177
+ label('Address 2') {
7178
+ stretchy false
7179
+ }
7180
+
7181
+ address_form(address2)
7182
+
7183
+ horizontal_separator {
7184
+ stretchy false
7185
+ }
7186
+
7187
+ label('Address 2 (Saved)') {
7188
+ stretchy false
7189
+ }
7190
+
7191
+ address(address2)
7192
+ }
7193
+ }
7194
+ }.show
7195
+ ```
7196
+
7197
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2 (without [data-binding](#data-binding)):
6247
7198
 
6248
7199
  ```ruby
6249
7200
  require 'glimmer-dsl-libui'
@@ -6282,9 +7233,9 @@ def label_pair(model, attribute, value)
6282
7233
  name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
6283
7234
  value_label = label(value.to_s)
6284
7235
  }
6285
- Glimmer::DataBinding::Observer.proc do
7236
+ observe(model, attribute) do
6286
7237
  value_label.text = model.send(attribute)
6287
- end.observe(model, attribute)
7238
+ end
6288
7239
  end
6289
7240
 
6290
7241
  def address(address)
@@ -6591,14 +7542,14 @@ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version
6591
7542
 
6592
7543
  ```ruby
6593
7544
  require 'glimmer-dsl-libui'
6594
- require 'glimmer/data_binding/observer'
6595
7545
 
6596
7546
  require_relative 'snake/presenter/grid'
6597
7547
 
6598
7548
  class Snake
7549
+ include Glimmer
7550
+
6599
7551
  CELL_SIZE = 15
6600
7552
  SNAKE_MOVE_DELAY = 0.1
6601
- include Glimmer
6602
7553
 
6603
7554
  def initialize
6604
7555
  @game = Model::Game.new
@@ -6644,7 +7595,7 @@ class Snake
6644
7595
  @game.width.times do |column|
6645
7596
  area {
6646
7597
  square(0, 0, CELL_SIZE) {
6647
- fill <= [@grid.cells[row][column], :color]
7598
+ fill <= [@grid.cells[row][column], :color] # data-bind square fill to grid cell color
6648
7599
  }
6649
7600
 
6650
7601
  on_key_up do |area_key_event|
@@ -6739,7 +7690,7 @@ class Tetris
6739
7690
  end
6740
7691
 
6741
7692
  def register_observers
6742
- Glimmer::DataBinding::Observer.proc do |game_over|
7693
+ observe(@game, :game_over) do |game_over|
6743
7694
  if game_over
6744
7695
  @pause_menu_item.enabled = false
6745
7696
  show_game_over_dialog
@@ -6747,11 +7698,11 @@ class Tetris
6747
7698
  @pause_menu_item.enabled = true
6748
7699
  start_moving_tetrominos_down
6749
7700
  end
6750
- end.observe(@game, :game_over)
7701
+ end
6751
7702
 
6752
7703
  Model::Game::PLAYFIELD_HEIGHT.times do |row|
6753
7704
  Model::Game::PLAYFIELD_WIDTH.times do |column|
6754
- Glimmer::DataBinding::Observer.proc do |new_color|
7705
+ observe(@game.playfield[row][column], :color) do |new_color|
6755
7706
  Glimmer::LibUI.queue_main do
6756
7707
  color = Glimmer::LibUI.interpret_color(new_color)
6757
7708
  block = @playfield_blocks[row][column]
@@ -6762,13 +7713,13 @@ class Tetris
6762
7713
  block[:left_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
6763
7714
  block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
6764
7715
  end
6765
- end.observe(@game.playfield[row][column], :color)
7716
+ end
6766
7717
  end
6767
7718
  end
6768
7719
 
6769
7720
  Model::Game::PREVIEW_PLAYFIELD_HEIGHT.times do |row|
6770
7721
  Model::Game::PREVIEW_PLAYFIELD_WIDTH.times do |column|
6771
- Glimmer::DataBinding::Observer.proc do |new_color|
7722
+ observe(@game.preview_playfield[row][column], :color) do |new_color|
6772
7723
  Glimmer::LibUI.queue_main do
6773
7724
  color = Glimmer::LibUI.interpret_color(new_color)
6774
7725
  block = @preview_playfield_blocks[row][column]
@@ -6779,27 +7730,27 @@ class Tetris
6779
7730
  block[:left_bevel_edge].fill = {r: color[:r] - BEVEL_CONSTANT, g: color[:g] - BEVEL_CONSTANT, b: color[:b] - BEVEL_CONSTANT}
6780
7731
  block[:border_square].stroke = new_color == Model::Block::COLOR_CLEAR ? COLOR_GRAY : color
6781
7732
  end
6782
- end.observe(@game.preview_playfield[row][column], :color)
7733
+ end
6783
7734
  end
6784
7735
  end
6785
7736
 
6786
- Glimmer::DataBinding::Observer.proc do |new_score|
7737
+ observe(@game, :score) do |new_score|
6787
7738
  Glimmer::LibUI.queue_main do
6788
7739
  @score_label.text = new_score.to_s
6789
7740
  end
6790
- end.observe(@game, :score)
7741
+ end
6791
7742
 
6792
- Glimmer::DataBinding::Observer.proc do |new_lines|
7743
+ observe(@game, :lines) do |new_lines|
6793
7744
  Glimmer::LibUI.queue_main do
6794
7745
  @lines_label.text = new_lines.to_s
6795
7746
  end
6796
- end.observe(@game, :lines)
7747
+ end
6797
7748
 
6798
- Glimmer::DataBinding::Observer.proc do |new_level|
7749
+ observe(@game, :level) do |new_level|
6799
7750
  Glimmer::LibUI.queue_main do
6800
7751
  @level_label.text = new_level.to_s
6801
7752
  end
6802
- end.observe(@game, :level)
7753
+ end
6803
7754
  end
6804
7755
 
6805
7756
  def menu_bar