glimmer-dsl-libui 0.5.4 → 0.5.7

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.5.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.5.7
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)
@@ -19,13 +19,11 @@ The main trade-off in using [Glimmer DSL for LibUI](https://rubygems.org/gems/gl
19
19
  - [Declarative DSL syntax](#glimmer-gui-dsl-concepts) that visually maps to the GUI control hierarchy
20
20
  - [Convention over configuration](#smart-defaults-and-conventions) via smart defaults and automation of low-level details
21
21
  - Requiring the [least amount of syntax](#glimmer-gui-dsl-concepts) possible to build GUI
22
- - [Custom Keyword](#custom-keywords) support
22
+ - [Custom Control](#custom-keywords) support
23
23
  - [Bidirectional/Unidirectional Data-Binding](#data-binding) to declaratively wire and automatically synchronize GUI Views with Models
24
24
  - [Far Future Plan] Scaffolding for new custom controls, apps, and gems
25
25
  - [Far Future Plan] Native-Executable packaging on Mac, Windows, and Linux.
26
26
 
27
- Note that currently, [LibUI](https://github.com/kojix2/LibUI) only includes x86_64 binaries out of the box, but there are plans to include ARM64/AARCH64 binaries too in the future.
28
-
29
27
  Hello, World!
30
28
 
31
29
  ```ruby
@@ -338,7 +336,7 @@ DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
338
336
  [Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)](https://github.com/AndyObtiva/glimmer-dsl-swt) | Mac / Windows / Linux | Yes | Yes (Canvas Shape DSL) | Very Mature / Scaffolding / Native Executable Packaging / Custom Widgets | Slow JRuby Startup Time / Heavy Memory Footprint | Java / JRuby
339
337
  [Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)](https://github.com/AndyObtiva/glimmer-dsl-opal) | All Web Browsers | No | Yes (Canvas Shape DSL) | Simpler than All JavaScript Technologies / Auto-Webify Desktop Apps | Setup Process / Only Rails 5 Support for Now | Rails
340
338
  [Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-libui) | Mac / Windows / Linux | Yes | Yes (Area API) | Fast Startup Time / Light Memory Footprint | LibUI is an Incomplete Mid-Alpha Only | None Other Than MRI Ruby
341
- [Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-tk) | Mac / Windows / Linux | Some Native-Themed Widgets (Not Truly Native) | Yes (Canvas) | Fast Startup Time / Light Memory Footprint | Widgets Do Not Look Truly Native, Espcially on Linux | ActiveTcl / MRI Ruby
339
+ [Glimmer DSL for Tk (Ruby Tk Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-tk) | Mac / Windows / Linux | Some Native-Themed Widgets (Not Truly Native) | Yes (Canvas) | Fast Startup Time / Light Memory Footprint | Complicated Setup / Widgets Do Not Look Truly Native, Espcially on Linux | ActiveTcl / MRI Ruby
342
340
  [Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-gtk) | Mac / Windows / Linux | Only on Linux | Yes (Cairo) | Complete Access to GNOME Features on Linux (Forte) | Not Native on Mac and Windows | None Other Than MRI Ruby on Linux / Brew Packages on Mac / MSYS & MING Toolchains on Windows / MRI Ruby
343
341
  [Glimmer DSL for FX (FOX Toolkit Ruby Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-fx) | Mac (requires XQuartz) / Windows / Linux | No | Yes (Canvas) | No Prerequisites on Windows (Forte Since Binaries Are Included Out of The Box) | Widgets Do Not Look Native / Mac Usage Obtrusively Starts XQuartz | None Other Than MRI Ruby on Windows / XQuarts on Mac / MRI Ruby
344
342
  [Glimmer DSL for JFX (JRuby JavaFX Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-jfx) | Mac / Windows / Linux | No | Yes (javafx.scene.shape and javafx.scene.canvas) | Rich in Custom Widgets | Slow JRuby Startup Time / Heavy Memory Footprint / Widgets Do Not Look Native | Java / JRuby / JavaFX SDK
@@ -370,6 +368,7 @@ DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
370
368
  - [Area Listeners](#area-listeners)
371
369
  - [Area Methods/Attributes](#area-methods-attributes)
372
370
  - [Area Transform Matrix](#area-transform-matrix)
371
+ - [Area Animation](#area-animation)
373
372
  - [Smart Defaults and Conventions](#smart-defaults-and-conventions)
374
373
  - [Custom Keywords](#custom-keywords)
375
374
  - [Observer Pattern](#observer-pattern)
@@ -420,7 +419,8 @@ DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
420
419
  - [Grid](#grid)
421
420
  - [Histogram](#histogram)
422
421
  - [Login](#login)
423
- - [Method-Based Custom Keyword](#method-based-custom-keyword)
422
+ - [Method-Based Custom Controls](#method-based-custom-controls)
423
+ - [Class-Based Custom Controls](#class-based-custom-controls)
424
424
  - [Area-Based Custom Controls](#area-based-custom-controls)
425
425
  - [Midi Player](#midi-player)
426
426
  - [Snake](#snake)
@@ -452,10 +452,68 @@ The Glimmer GUI DSL provides object-oriented declarative hierarchical syntax for
452
452
  - Requires the minimum amount of syntax needed to describe an app's GUI
453
453
 
454
454
  The Glimmer GUI DSL follows these simple concepts in mapping from [LibUI](https://github.com/kojix2/LibUI) syntax:
455
- - **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)`).
456
- - **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}}`)
457
- - **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.
458
- - **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.
455
+
456
+ **Keyword(args)**: [LibUI](https://github.com/kojix2/LibUI) controls may be declared by lower-case underscored name (aka keyword from list of [supported keywords](#supported-keywords)) (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)`).
457
+
458
+ **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.
459
+
460
+ Example:
461
+
462
+ ```ruby
463
+ window {
464
+ title 'hello world' # property
465
+
466
+ on_closing do # listener (always has a do; end block to signify logic)
467
+ puts 'Bye'
468
+ end
469
+
470
+ button('greet') { # nested control
471
+ on_clicked do
472
+ puts 'hello world'
473
+ end
474
+ }
475
+ }
476
+ ```
477
+
478
+ Content block optionally receives one arg representing the controll
479
+
480
+ Example:
481
+
482
+ ```ruby
483
+ button('greet') { |b|
484
+ on_clicked do
485
+ puts b.text
486
+ end
487
+ }
488
+ ```
489
+
490
+ **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.
491
+
492
+ **Listener**: Control listeners may be declared inside keyword blocks with listener lower-case underscored name beginning with `on_` and receiving required block handler (always followed by a `do; end` style block to signify logic).
493
+
494
+ Example:
495
+
496
+ ```ruby
497
+ button('click') {
498
+ on_clicked do
499
+ puts 'clicked'
500
+ end
501
+ }
502
+ ```
503
+
504
+ Optionally, the listener block can receive an arg representing the control.
505
+
506
+ ```ruby
507
+ button('click') {
508
+ on_clicked do |btn|
509
+ puts btn.text
510
+ end
511
+ }
512
+ ```
513
+
514
+ Behind the scenes, listeners correspond to `LibUI.control_on_event` methods.
515
+
516
+ **Method**: Controls have methods that invoke certain operations on them. For example, `window` has a `#show` method that shows the window GUI. More methods are mentioned under [API](#api)
459
517
 
460
518
  Example of an app written in [LibUI](https://github.com/kojix2/LibUI)'s procedural imperative syntax:
461
519
 
@@ -510,7 +568,7 @@ window('hello world', 300, 200) {
510
568
 
511
569
  ## Usage
512
570
 
513
- Install [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) gem directly:
571
+ Install [glimmer-dsl-libui](https://rubygems.org/gems/glimmer-dsl-libui) gem directly into a [maintained Ruby version](https://www.ruby-lang.org/en/downloads/):
514
572
 
515
573
  ```
516
574
  gem install glimmer-dsl-libui
@@ -519,7 +577,7 @@ gem install glimmer-dsl-libui
519
577
  Or install via Bundler `Gemfile`:
520
578
 
521
579
  ```ruby
522
- gem 'glimmer-dsl-libui', '~> 0.5.4'
580
+ gem 'glimmer-dsl-libui', '~> 0.5.7'
523
581
  ```
524
582
 
525
583
  Test that installation worked by running the [Meta-Example](#examples):
@@ -1404,6 +1462,92 @@ You can set a `matrix`/`transform` on `area` directly to conveniently apply to a
1404
1462
 
1405
1463
  Note that `area`, `path`, and nested shapes are all truly declarative, meaning they do not care about the ordering of calls to `fill`, `stroke`, and `transform`. Furthermore, any transform that is applied is reversed at the end of the block, so you never have to worry about the ordering of `transform` calls among different paths. You simply set a transform on the `path`s that need it and it is guaranteed to be called before all its content is drawn, and then undone afterwards to avoid affecting later paths. Matrix `transform` can be set on an entire `area` too, applying to all nested `path`s.
1406
1464
 
1465
+ #### Area Animation
1466
+
1467
+ If you need to animate `area` vector graphics, you just have to use the [`Glimmer::LibUI::timer`](#libui-operations) method along with making changes to shape attributes.
1468
+
1469
+ Spinner example that has a fully customizable method-based custom control called `spinner`, which is destroyed if you click on it (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1470
+
1471
+ ```ruby
1472
+ require 'glimmer-dsl-libui'
1473
+
1474
+ class SpinnerExample
1475
+ include Glimmer
1476
+
1477
+ SIZE = 120
1478
+
1479
+ def initialize
1480
+ create_gui
1481
+ end
1482
+
1483
+ def launch
1484
+ @main_window.show
1485
+ end
1486
+
1487
+ def create_gui
1488
+ @main_window = window {
1489
+ title 'Spinner'
1490
+ content_size SIZE*2, SIZE*2
1491
+
1492
+ horizontal_box {
1493
+ padded false
1494
+
1495
+ vertical_box {
1496
+ padded false
1497
+
1498
+ spinner(size: SIZE)
1499
+ spinner(size: SIZE, fill_color: [42, 153, 214])
1500
+ }
1501
+
1502
+ vertical_box {
1503
+ padded false
1504
+
1505
+ spinner(size: SIZE/2.0, fill_color: :orange)
1506
+ spinner(size: SIZE/2.0, fill_color: {x0: 0, y0: 0, x1: SIZE/2.0, y1: SIZE/2.0, stops: [{pos: 0.25, r: 204, g: 102, b: 204}, {pos: 1, r: 2, g: 2, b: 254}]})
1507
+ spinner(size: SIZE/2.0, fill_color: :green, unfilled_color: :yellow)
1508
+ spinner(size: SIZE/2.0, fill_color: :white, unfilled_color: :gray, background_color: :black)
1509
+ }
1510
+ }
1511
+ }
1512
+ end
1513
+
1514
+ def spinner(size: 40.0, fill_color: :gray, background_color: :white, unfilled_color: {r: 243, g: 243, b: 243}, donut_percentage: 0.25)
1515
+ arc1 = arc2 = nil
1516
+ area { |the_area|
1517
+ rectangle(0, 0, size, size) {
1518
+ fill background_color
1519
+ }
1520
+ circle(size/2.0, size/2.0, size/2.0) {
1521
+ fill fill_color
1522
+ }
1523
+ arc1 = arc(size/2.0, size/2.0, size/2.0, 0, 180) {
1524
+ fill unfilled_color
1525
+ }
1526
+ arc2 = arc(size/2.0, size/2.0, size/2.0, 90, 180) {
1527
+ fill unfilled_color
1528
+ }
1529
+ circle(size/2.0, size/2.0, (size/2.0)*(1.0 - donut_percentage)) {
1530
+ fill background_color
1531
+ }
1532
+
1533
+ on_mouse_up do
1534
+ the_area.destroy
1535
+ end
1536
+ }.tap do
1537
+ Glimmer::LibUI.timer(0.05) do
1538
+ delta = 10
1539
+ arc1.start_angle += delta
1540
+ arc2.start_angle += delta
1541
+ end
1542
+ end
1543
+ end
1544
+ end
1545
+
1546
+ SpinnerExample.new.launch
1547
+ ```
1548
+
1549
+ ![mac spinner](/images/glimmer-dsl-libui-mac-spinner.gif)
1550
+
1407
1551
  ### Smart Defaults and Conventions
1408
1552
 
1409
1553
  - `horizontal_box`, `vertical_box`, `grid`, and `form` controls have `padded` as `true` upon instantiation to ensure more user-friendly GUI by default
@@ -1455,15 +1599,15 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
1455
1599
 
1456
1600
  Custom keywords can be defined to represent custom controls (components) that provide new features or act as composites of [existing controls](#supported-keywords) that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
1457
1601
 
1458
- For example, you can define a custom `address` control as an aggregate of multiple `label` controls to reuse multiple times as a standard address View, displaying street, city, state, and zip code.
1602
+ For example, you can define a custom `address_view` control as an aggregate of multiple `label` controls to reuse multiple times as a standard address View, displaying street, city, state, and zip code.
1459
1603
 
1460
- To define custom keywords, simply define a method representing the custom control you want (e.g. `address`) with any arguments needed (e.g. `address(address_model)`).
1604
+ There are two ways to define custom keywords:
1605
+ - Method-Based: simply define a method representing the custom control you want (e.g. `address_view`) with any arguments needed (e.g. `address(address_model)`).
1606
+ - Class-Based: define a class matching the camelcased name of the custom control by convention (e.g. the `address_view` custom control keyword would have a class called `AddressView`) and `include Glimmer::LibUI::CustomControl`. Classes add the benefit of being able to distribute the custom controls into separate files and reuse externally from multiple places or share via Ruby gems.
1461
1607
 
1462
- To make custom keywords externally reusable, you can define in modules and simply include the modules in the view classes that need them.
1608
+ It is OK to use the terms "custom keyword" and "custom control" synonymously though "custom keyword" is a broader term that covers things other than controls too like custom shapes (e.g. `cylinder`), custom attributed strings (e.g. `alternating_color_string`), and custom transforms (`isometric_transform`).
1463
1609
 
1464
- It is OK to use terms "custom keyword" and "custom control" synonymously though "custom keyword" is a broader term that covers things other than controls too like custom shapes (e.g. `cylinder`), custom attributed strings (e.g. `alternating_color_string`), and custom transforms (`isometric_transform`).
1465
-
1466
- Example that defines `form_field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1610
+ Example that defines `form_field`, `address_form`, `label_pair`, and `address_view` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1467
1611
 
1468
1612
  ```ruby
1469
1613
  require 'glimmer-dsl-libui'
@@ -1500,7 +1644,7 @@ def label_pair(model, attribute, value)
1500
1644
  }
1501
1645
  end
1502
1646
 
1503
- def address(address_model)
1647
+ def address_view(address_model)
1504
1648
  vertical_box {
1505
1649
  address_model.each_pair do |attribute, value|
1506
1650
  label_pair(address_model, attribute, value)
@@ -1530,7 +1674,7 @@ window('Method-Based Custom Keyword') {
1530
1674
  stretchy false
1531
1675
  }
1532
1676
 
1533
- address(address1)
1677
+ address_view(address1)
1534
1678
  }
1535
1679
 
1536
1680
  vertical_separator {
@@ -1552,7 +1696,7 @@ window('Method-Based Custom Keyword') {
1552
1696
  stretchy false
1553
1697
  }
1554
1698
 
1555
- address(address2)
1699
+ address_view(address2)
1556
1700
  }
1557
1701
  }
1558
1702
  }.show
@@ -1560,6 +1704,136 @@ window('Method-Based Custom Keyword') {
1560
1704
 
1561
1705
  ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
1562
1706
 
1707
+ You can also define Custom Window keywords, that is custom controls with `window` being the body root. These are also known as Applications. To define a Custom Window, you `include Glimmer::LibUI::CustomWindow` or `include Glimmer:LibUI::Application` and then you can invoke the `::launch` method on the class.
1708
+
1709
+ Example (you may copy/paste in [`girb`](#girb-glimmer-irb)):
1710
+
1711
+ ```ruby
1712
+ require 'glimmer-dsl-libui'
1713
+ require 'facets'
1714
+
1715
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
1716
+
1717
+ class FormField
1718
+ include Glimmer::LibUI::CustomControl
1719
+
1720
+ options :model, :attribute
1721
+
1722
+ body {
1723
+ entry { |e|
1724
+ label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
1725
+ text <=> [model, attribute]
1726
+ }
1727
+ }
1728
+ end
1729
+
1730
+ class AddressForm
1731
+ include Glimmer::LibUI::CustomControl
1732
+
1733
+ options :address
1734
+
1735
+ body {
1736
+ form {
1737
+ form_field(model: address, attribute: :street)
1738
+ form_field(model: address, attribute: :p_o_box)
1739
+ form_field(model: address, attribute: :city)
1740
+ form_field(model: address, attribute: :state)
1741
+ form_field(model: address, attribute: :zip_code)
1742
+ }
1743
+ }
1744
+ end
1745
+
1746
+ class LabelPair
1747
+ include Glimmer::LibUI::CustomControl
1748
+
1749
+ options :model, :attribute, :value
1750
+
1751
+ body {
1752
+ horizontal_box {
1753
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
1754
+ label(value.to_s) {
1755
+ text <= [model, attribute]
1756
+ }
1757
+ }
1758
+ }
1759
+ end
1760
+
1761
+ class AddressView
1762
+ include Glimmer::LibUI::CustomControl
1763
+
1764
+ options :address
1765
+
1766
+ body {
1767
+ vertical_box {
1768
+ address.each_pair do |attribute, value|
1769
+ label_pair(model: address, attribute: attribute, value: value)
1770
+ end
1771
+ }
1772
+ }
1773
+ end
1774
+
1775
+ class ClassBasedCustomControls
1776
+ include Glimmer::LibUI::Application # alias: Glimmer::LibUI::CustomWindow
1777
+
1778
+ before_body do
1779
+ @address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
1780
+ @address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
1781
+ end
1782
+
1783
+ body {
1784
+ window('Class-Based Custom Keyword') {
1785
+ margined true
1786
+
1787
+ horizontal_box {
1788
+ vertical_box {
1789
+ label('Address 1') {
1790
+ stretchy false
1791
+ }
1792
+
1793
+ address_form(address: @address1)
1794
+
1795
+ horizontal_separator {
1796
+ stretchy false
1797
+ }
1798
+
1799
+ label('Address 1 (Saved)') {
1800
+ stretchy false
1801
+ }
1802
+
1803
+ address_view(address: @address1)
1804
+ }
1805
+
1806
+ vertical_separator {
1807
+ stretchy false
1808
+ }
1809
+
1810
+ vertical_box {
1811
+ label('Address 2') {
1812
+ stretchy false
1813
+ }
1814
+
1815
+ address_form(address: @address2)
1816
+
1817
+ horizontal_separator {
1818
+ stretchy false
1819
+ }
1820
+
1821
+ label('Address 2 (Saved)') {
1822
+ stretchy false
1823
+ }
1824
+
1825
+ address_view(address: @address2)
1826
+ }
1827
+ }
1828
+ }
1829
+ }
1830
+ end
1831
+
1832
+ ClassBasedCustomControls.launch
1833
+ ```
1834
+
1835
+ ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
1836
+
1563
1837
  The [`area`](#area-api) control can be utilized to build non-native custom controls from scratch by leveraging vector graphics, formattable text, keyboard events, and mouse events. This is demonstrated in the [Area-Based Custom Controls](#area-based-custom-controls) example.
1564
1838
 
1565
1839
  Defining custom keywords enables unlimited extension of the [Glimmer GUI DSL](#glimmer-gui-dsl). The sky is the limit on what can be done with custom keywords as a result. You can compose new visual vocabulary to build applications in any domain from higher concepts rather than [mere standard controls](#supported-keywords). For example, in a traffic signaling app, you could define `street`, `light_signal`, `traffic_sign`, and `car` as custom keywords and build your application from these concepts directly, saving enormous time and achieving much higher productivity.
@@ -1835,6 +2109,7 @@ Data-bound model attribute can be:
1835
2109
  - **Direct:** `Symbol` representing attribute reader/writer (e.g. `[person, :name`])
1836
2110
  - **Nested:** `String` representing nested attribute path (e.g. `[company, 'address.street']`). That results in "nested data-binding"
1837
2111
  - **Indexed:** `String` containing array attribute index (e.g. `[customer, 'addresses[0].street']`). That results in "indexed data-binding"
2112
+ - **Keyed:** `String` containing hash attribute key (e.g. `[customer, 'addresses[:main].street']`). That results in "keyed data-binding"
1838
2113
 
1839
2114
  Data-binding options include:
1840
2115
  - `before_read {|value| ...}`: performs an operation before reading data from Model to update View.
@@ -1868,6 +2143,7 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions),
1868
2143
  - 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`).
1869
2144
  - `table` `checkbox_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).
1870
2145
  - `table` `checkbox_text_column` checkbox editing only works on Linux (not Mac or Windows) due to a current limitation in [libui](https://github.com/andlabs/ui/issues/357).
2146
+ - `checkbox` only supports obtaining the `checked` property, but not updating it due to a current limitation in [libui](https://github.com/andlabs/libui).
1871
2147
  - `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)
1872
2148
  - `text` `string` `background` does not work on Windows due to an [issue in libui](https://github.com/andlabs/libui/issues/347).
1873
2149
  - `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.
@@ -1875,6 +2151,7 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions),
1875
2151
  - 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.
1876
2152
  - As per the code of [examples/basic_transform.rb](#basic-transform), Windows requires different ordering of transforms than Mac and Linux.
1877
2153
  - `scrolling_area#scroll_to` does not seem to work on Windows and Linux, but works fine on Mac
2154
+ - When creating/showing a window other than the main window and then closing the secondary window, the entire app closes. This is a current limitation to the windowing system that should be fixed with [child window support](https://github.com/andlabs/libui/issues/137) in [libui](https://github.com/andlabs/libui)
1878
2155
 
1879
2156
  ### Original API
1880
2157
 
@@ -8435,26 +8712,26 @@ window('Login') {
8435
8712
  }.show
8436
8713
  ```
8437
8714
 
8438
- #### Method-Based Custom Keyword
8715
+ #### Method-Based Custom Controls
8439
8716
 
8440
8717
  [Custom keywords](#custom-keywords) can be defined to represent custom controls (components) that provide new features or act as composites of existing controls that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
8441
8718
 
8442
- This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom control keywords.
8719
+ This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom controls (keywords).
8443
8720
 
8444
8721
  The custom keywords are defined via methods (thus are "method-based").
8445
8722
 
8446
- [examples/method_based_custom_keyword.rb](examples/method_based_custom_keyword.rb)
8723
+ [examples/method_based_custom_controls.rb](examples/method_based_custom_controls.rb)
8447
8724
 
8448
8725
  Run with this command from the root of the project if you cloned the project:
8449
8726
 
8450
8727
  ```
8451
- ruby -r './lib/glimmer-dsl-libui' examples/method_based_custom_keyword.rb
8728
+ ruby -r './lib/glimmer-dsl-libui' examples/method_based_custom_controls.rb
8452
8729
  ```
8453
8730
 
8454
8731
  Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
8455
8732
 
8456
8733
  ```
8457
- ruby -r glimmer-dsl-libui -e "require 'examples/method_based_custom_keyword'"
8734
+ ruby -r glimmer-dsl-libui -e "require 'examples/method_based_custom_controls'"
8458
8735
  ```
8459
8736
 
8460
8737
  Mac | Windows | Linux
@@ -8479,13 +8756,13 @@ def form_field(model, attribute)
8479
8756
  }
8480
8757
  end
8481
8758
 
8482
- def address_form(address)
8759
+ def address_form(address_model)
8483
8760
  form {
8484
- form_field(address, :street)
8485
- form_field(address, :p_o_box)
8486
- form_field(address, :city)
8487
- form_field(address, :state)
8488
- form_field(address, :zip_code)
8761
+ form_field(address_model, :street)
8762
+ form_field(address_model, :p_o_box)
8763
+ form_field(address_model, :city)
8764
+ form_field(address_model, :state)
8765
+ form_field(address_model, :zip_code)
8489
8766
  }
8490
8767
  end
8491
8768
 
@@ -8498,10 +8775,10 @@ def label_pair(model, attribute, value)
8498
8775
  }
8499
8776
  end
8500
8777
 
8501
- def address(address)
8778
+ def address_view(address_model)
8502
8779
  vertical_box {
8503
- address.each_pair do |attribute, value|
8504
- label_pair(address, attribute, value)
8780
+ address_model.each_pair do |attribute, value|
8781
+ label_pair(address_model, attribute, value)
8505
8782
  end
8506
8783
  }
8507
8784
  end
@@ -8509,7 +8786,7 @@ end
8509
8786
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
8510
8787
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
8511
8788
 
8512
- window('Method-Based Custom Keyword') {
8789
+ window('Method-Based Custom Controls') {
8513
8790
  margined true
8514
8791
 
8515
8792
  horizontal_box {
@@ -8528,7 +8805,7 @@ window('Method-Based Custom Keyword') {
8528
8805
  stretchy false
8529
8806
  }
8530
8807
 
8531
- address(address1)
8808
+ address_view(address1)
8532
8809
  }
8533
8810
 
8534
8811
  vertical_separator {
@@ -8550,7 +8827,7 @@ window('Method-Based Custom Keyword') {
8550
8827
  stretchy false
8551
8828
  }
8552
8829
 
8553
- address(address2)
8830
+ address_view(address2)
8554
8831
  }
8555
8832
  }
8556
8833
  }.show
@@ -8578,13 +8855,13 @@ def form_field(model, property)
8578
8855
  }
8579
8856
  end
8580
8857
 
8581
- def address_form(address)
8858
+ def address_form(address_model)
8582
8859
  form {
8583
- form_field(address, :street)
8584
- form_field(address, :p_o_box)
8585
- form_field(address, :city)
8586
- form_field(address, :state)
8587
- form_field(address, :zip_code)
8860
+ form_field(address_model, :street)
8861
+ form_field(address_model, :p_o_box)
8862
+ form_field(address_model, :city)
8863
+ form_field(address_model, :state)
8864
+ form_field(address_model, :zip_code)
8588
8865
  }
8589
8866
  end
8590
8867
 
@@ -8600,10 +8877,10 @@ def label_pair(model, attribute, value)
8600
8877
  end
8601
8878
  end
8602
8879
 
8603
- def address(address)
8880
+ def address_view(address_model)
8604
8881
  vertical_box {
8605
- address.each_pair do |attribute, value|
8606
- label_pair(address, attribute, value)
8882
+ address_model.each_pair do |attribute, value|
8883
+ label_pair(address_model, attribute, value)
8607
8884
  end
8608
8885
  }
8609
8886
  end
@@ -8611,7 +8888,7 @@ end
8611
8888
  address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
8612
8889
  address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
8613
8890
 
8614
- window('Method-Based Custom Keyword') {
8891
+ window('Method-Based Custom Controls') {
8615
8892
  margined true
8616
8893
 
8617
8894
  horizontal_box {
@@ -8630,7 +8907,7 @@ window('Method-Based Custom Keyword') {
8630
8907
  stretchy false
8631
8908
  }
8632
8909
 
8633
- address(address1)
8910
+ address_view(address1)
8634
8911
  }
8635
8912
 
8636
8913
  vertical_separator {
@@ -8652,7 +8929,151 @@ window('Method-Based Custom Keyword') {
8652
8929
  stretchy false
8653
8930
  }
8654
8931
 
8655
- address(address2)
8932
+ address_view(address2)
8933
+ }
8934
+ }
8935
+ }.show
8936
+ ```
8937
+
8938
+ #### Class-Based Custom Controls
8939
+
8940
+ [Custom keywords](#custom-keywords) can be defined to represent custom controls (components) that provide new features or act as composites of existing controls that need to be reused multiple times in an application or across multiple applications. Custom keywords save a lot of development time, improving productivity and maintainability immensely.
8941
+
8942
+ This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom controls (keywords).
8943
+
8944
+ The custom keywords are defined via classes that include `Glimmer::LibUI::CustomControl` (thus are "class-based"), thus enabling offloading each custom control into its own file when needed for better code organization.
8945
+
8946
+ [examples/class_based_custom_controls.rb](examples/class_based_custom_controls.rb)
8947
+
8948
+ Run with this command from the root of the project if you cloned the project:
8949
+
8950
+ ```
8951
+ ruby -r './lib/glimmer-dsl-libui' examples/class_based_custom_controls.rb
8952
+ ```
8953
+
8954
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
8955
+
8956
+ ```
8957
+ ruby -r glimmer-dsl-libui -e "require 'examples/class_based_custom_controls'"
8958
+ ```
8959
+
8960
+ Mac | Windows | Linux
8961
+ ----|---------|------
8962
+ ![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)
8963
+
8964
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version (with [data-binding](#data-binding)):
8965
+
8966
+ ```ruby
8967
+ require 'glimmer-dsl-libui'
8968
+ require 'facets'
8969
+
8970
+ include Glimmer
8971
+
8972
+ Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
8973
+
8974
+ class FormField
8975
+ include Glimmer::LibUI::CustomControl
8976
+
8977
+ options :model, :attribute
8978
+
8979
+ body {
8980
+ entry { |e|
8981
+ label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')
8982
+ text <=> [model, attribute]
8983
+ }
8984
+ }
8985
+ end
8986
+
8987
+ class AddressForm
8988
+ include Glimmer::LibUI::CustomControl
8989
+
8990
+ options :address
8991
+
8992
+ body {
8993
+ form {
8994
+ form_field(model: address, attribute: :street)
8995
+ form_field(model: address, attribute: :p_o_box)
8996
+ form_field(model: address, attribute: :city)
8997
+ form_field(model: address, attribute: :state)
8998
+ form_field(model: address, attribute: :zip_code)
8999
+ }
9000
+ }
9001
+ end
9002
+
9003
+ class LabelPair
9004
+ include Glimmer::LibUI::CustomControl
9005
+
9006
+ options :model, :attribute, :value
9007
+
9008
+ body {
9009
+ horizontal_box {
9010
+ label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
9011
+ label(value.to_s) {
9012
+ text <= [model, attribute]
9013
+ }
9014
+ }
9015
+ }
9016
+ end
9017
+
9018
+ class AddressView
9019
+ include Glimmer::LibUI::CustomControl
9020
+
9021
+ options :address
9022
+
9023
+ body {
9024
+ vertical_box {
9025
+ address.each_pair do |attribute, value|
9026
+ label_pair(model: address, attribute: attribute, value: value)
9027
+ end
9028
+ }
9029
+ }
9030
+ end
9031
+
9032
+ address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014')
9033
+ address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101')
9034
+
9035
+ window('Class-Based Custom Keyword') {
9036
+ margined true
9037
+
9038
+ horizontal_box {
9039
+ vertical_box {
9040
+ label('Address 1') {
9041
+ stretchy false
9042
+ }
9043
+
9044
+ address_form(address: address1)
9045
+
9046
+ horizontal_separator {
9047
+ stretchy false
9048
+ }
9049
+
9050
+ label('Address 1 (Saved)') {
9051
+ stretchy false
9052
+ }
9053
+
9054
+ address_view(address: address1)
9055
+ }
9056
+
9057
+ vertical_separator {
9058
+ stretchy false
9059
+ }
9060
+
9061
+ vertical_box {
9062
+ label('Address 2') {
9063
+ stretchy false
9064
+ }
9065
+
9066
+ address_form(address: address2)
9067
+
9068
+ horizontal_separator {
9069
+ stretchy false
9070
+ }
9071
+
9072
+ label('Address 2 (Saved)') {
9073
+ stretchy false
9074
+ }
9075
+
9076
+ address_view(address: address2)
8656
9077
  }
8657
9078
  }
8658
9079
  }.show
@@ -9369,6 +9790,8 @@ Snake provides an example of building a desktop application [test-first](/spec/e
9369
9790
 
9370
9791
  Use arrows to move and spacebar to pause/resume.
9371
9792
 
9793
+ Note that Snake relies on the new [Ruby Pattern Matching feature](https://docs.ruby-lang.org/en/3.0/doc/syntax/pattern_matching_rdoc.html) available starting in Ruby 2.7 experimentally and in Ruby 3.0 officially.
9794
+
9372
9795
  [examples/snake.rb](examples/snake.rb)
9373
9796
 
9374
9797
  Run with this command from the root of the project if you cloned the project:
@@ -9593,6 +10016,8 @@ Snake.new.launch
9593
10016
 
9594
10017
  Glimmer Tetris utilizes many small areas to represent Tetromino blocks because this ensures smaller redraws per tetromino block color change, thus achieving higher performance than redrawing one large area on every little change.
9595
10018
 
10019
+ Note that Tetris relies on the new [Ruby Pattern Matching feature](https://docs.ruby-lang.org/en/3.0/doc/syntax/pattern_matching_rdoc.html) available starting in Ruby 2.7 experimentally and in Ruby 3.0 officially.
10020
+
9596
10021
  [examples/tetris.rb](examples/tetris.rb)
9597
10022
 
9598
10023
  Run with this command from the root of the project if you cloned the project: