glimmer-dsl-libui 0.4.16 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +451 -68
- data/VERSION +1 -1
- data/examples/area_based_custom_controls.rb +278 -0
- data/examples/basic_table.rb +1 -1
- data/examples/cpu_percentage.rb +1 -1
- data/examples/custom_draw_text.rb +14 -7
- data/examples/custom_draw_text2.rb +15 -8
- data/examples/method_based_custom_keyword.rb +9 -9
- data/examples/method_based_custom_keyword2.rb +9 -9
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/dsl.rb +1 -0
- data/lib/glimmer/dsl/libui/observe_expression.rb +1 -1
- data/lib/glimmer/dsl/libui/operation_expression.rb +47 -0
- data/lib/glimmer/dsl/libui/property_expression.rb +2 -2
- data/lib/glimmer/libui/control_proxy/box.rb +1 -0
- data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/button_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/checkbox_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/checkbox_text_color_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/checkbox_text_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/image_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/image_text_color_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/image_text_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/progress_bar_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/text_color_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column/text_column_proxy.rb +6 -0
- data/lib/glimmer/libui/control_proxy/column.rb +7 -0
- data/lib/glimmer/libui/control_proxy/form_proxy.rb +1 -0
- data/lib/glimmer/libui/control_proxy/image_proxy.rb +1 -0
- data/lib/glimmer/libui/control_proxy/menu_item_proxy/quit_menu_item_proxy.rb +0 -1
- data/lib/glimmer/libui/control_proxy/path_proxy.rb +6 -4
- data/lib/glimmer/libui/control_proxy/table_proxy.rb +51 -28
- data/lib/glimmer/libui/control_proxy.rb +1 -0
- data/lib/glimmer/libui/data_bindable.rb +1 -1
- data/lib/glimmer/libui/shape/bezier.rb +20 -3
- data/lib/glimmer/libui/shape/line.rb +23 -3
- data/lib/glimmer-dsl-libui.rb +6 -0
- metadata +23 -6
    
        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. | 
| 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.20
         | 
| 2 2 | 
             
            ## Prerequisite-Free Ruby Desktop Development GUI Library
         | 
| 3 3 | 
             
            [](http://badge.fury.io/rb/glimmer-dsl-libui)
         | 
| 4 4 | 
             
            [](https://gitter.im/AndyObtiva/glimmer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
         | 
| @@ -324,7 +324,7 @@ Mac | Windows | Linux | |
| 324 324 | 
             
            ----|---------|------
         | 
| 325 325 | 
             
             |  | 
         | 
| 326 326 |  | 
| 327 | 
            -
            NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is feature-complete and in beta mode (though the C [libui](https://github.com/andlabs/libui) is still mid-alpha). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. The more feedback and issues you report the better.
         | 
| 327 | 
            +
            NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is 100% feature-complete and in beta mode (though the C [libui](https://github.com/andlabs/libui) is still mid-alpha). Please help make better by contributing, adopting for small or low risk projects, and providing feedback. The more feedback and issues you report the better.
         | 
| 328 328 |  | 
| 329 329 | 
             
            Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interested in:
         | 
| 330 330 | 
             
            - [glimmer-dsl-swt](https://github.com/AndyObtiva/glimmer-dsl-swt): Glimmer DSL for SWT (JRuby Desktop Development GUI Framework)
         | 
| @@ -409,6 +409,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes | |
| 409 409 | 
             
                  - [Histogram](#histogram)
         | 
| 410 410 | 
             
                  - [Login](#login)
         | 
| 411 411 | 
             
                  - [Method-Based Custom Keyword](#method-based-custom-keyword)
         | 
| 412 | 
            +
                  - [Area-Based Custom Controls](#area-based-custom-controls)
         | 
| 412 413 | 
             
                  - [Midi Player](#midi-player)
         | 
| 413 414 | 
             
                  - [Snake](#snake)
         | 
| 414 415 | 
             
                  - [Tetris](#tetris)
         | 
| @@ -506,7 +507,7 @@ gem install glimmer-dsl-libui | |
| 506 507 | 
             
            Or install via Bundler `Gemfile`:
         | 
| 507 508 |  | 
| 508 509 | 
             
            ```ruby
         | 
| 509 | 
            -
            gem 'glimmer-dsl-libui', '~> 0.4. | 
| 510 | 
            +
            gem 'glimmer-dsl-libui', '~> 0.4.20'
         | 
| 510 511 | 
             
            ```
         | 
| 511 512 |  | 
| 512 513 | 
             
            Test that installation worked by running the [Meta-Example](#examples):
         | 
| @@ -595,7 +596,7 @@ Keyword(Args) | Properties | Listeners | |
| 595 596 | 
             
            `area` | `auto_draw_enabled` | `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)`
         | 
| 596 597 | 
             
            `arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)` | `x_center` (`Numeric`), `y_center` (`Numeric`), `radius` (`Numeric`), `start_angle` (`Numeric`), `sweep` (`Numeric`), `is_negative` (Boolean) | None
         | 
| 597 598 | 
             
            `background_color_column` | None | None
         | 
| 598 | 
            -
            `bezier(c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)` | `c1_x` (`Numeric`), `c1_y` (`Numeric`), `c2_x` (`Numeric`), `c2_y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None
         | 
| 599 | 
            +
            `bezier(x = nil as Numeric, y = nil as Numeric, c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `c1_x` (`Numeric`), `c1_y` (`Numeric`), `c2_x` (`Numeric`), `c2_y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None
         | 
| 599 600 | 
             
            `button(text as String)` | `text` (`String`) | `on_clicked`
         | 
| 600 601 | 
             
            `button_column(name as String)` | `enabled` (Boolean) | None
         | 
| 601 602 | 
             
            `checkbox(text as String)` | `checked` (Boolean), `text` (`String`) | `on_toggled`
         | 
| @@ -622,7 +623,7 @@ Keyword(Args) | Properties | Listeners | |
| 622 623 | 
             
            `image_text_column(name as String)` | None | None
         | 
| 623 624 | 
             
            `image_text_color_column(name as String)` | None | None
         | 
| 624 625 | 
             
            `label(text as String)` | `text` (`String`) | None
         | 
| 625 | 
            -
            `line(x as Numeric, y as Numeric)` | `x` (`Numeric`), `y` (`Numeric`) | None
         | 
| 626 | 
            +
            `line(x as Numeric, y as Numeric, end_x = nil as Numeric, end_y = nil as Numeric)` | `x` (`Numeric`), `y` (`Numeric`), `end_x` (`Numeric`), `end_y` (`Numeric`) | None
         | 
| 626 627 | 
             
            `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
         | 
| 627 628 | 
             
            `menu(text as String)` | None | None
         | 
| 628 629 | 
             
            `menu_item(text as String)` | None | `on_clicked`
         | 
| @@ -958,8 +959,8 @@ Available `path` shapes (that can be nested explicitly under `path` or implicitl | |
| 958 959 | 
             
            - `square(x as Numeric, y as Numeric, length as Numeric)`
         | 
| 959 960 | 
             
            - `arc(x_center as Numeric, y_center as Numeric, radius as Numeric, start_angle as Numeric, sweep as Numeric, is_negative as Boolean)`
         | 
| 960 961 | 
             
            - `circle(x_center as Numeric, y_center as Numeric, radius as Numeric)`
         | 
| 961 | 
            -
            - `line(x as Numeric, y as Numeric)`: must be placed in a figure (check `polyline`/`polygon` alternatives that do not require a `figure`)
         | 
| 962 | 
            -
            - `bezier(c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)`: must be placed in a figure (check `polybezier` alternative that does not require a `figure`)
         | 
| 962 | 
            +
            - `line(x as Numeric, y as Numeric, end_x = nil as Numeric, end_y = nil as Numeric)`: must be placed in a `figure` if only `x`/`y` are specified or have `end_x`/`end_y` otherwise if outside of `figure` (check `polyline`/`polygon` alternatives that do not require a `figure`)
         | 
| 963 | 
            +
            - `bezier(x = nil as Numeric, y = nil as Numeric, c1_x as Numeric, c1_y as Numeric, c2_x as Numeric, c2_y as Numeric, end_x as Numeric, end_y as Numeric)`: must be placed in a `figure` if `x`/`y` are not specified or have `x`/`y` as start point otherwise if outside of `figure` (check `polybezier` alternative that does not require a `figure`)
         | 
| 963 964 | 
             
            - `polygon(point_array as Array of Arrays of Numeric or Array of Numeric)`: shortcut for a closed figure of lines; can receive points as [[x1, y1], [x2, y2], ...] or [x1, y1, x2, y2, ...]
         | 
| 964 965 | 
             
            - `polyline(point_array as Array of Arrays of Numeric or Array of Numeric)`: shortcut for an open figure of lines; can receive points as [[x1, y1], [x2, y2], ...] or [x1, y1, x2, y2, ...]
         | 
| 965 966 | 
             
            - `polybezier(point_array as Array of Arrays of Numeric or Array of Numeric)`: shortcut for an open figure of beziers; can receive points as [[start_x1, start_y1], [c1_x2, c1_y2, c2_x2, c2_y2, end_x2, end_y2], [c1_x3, c1_y3, c2_x3, c2_y3, end_x3, end_y3], ...] or [start_x1, start_y1, c1_x2, c1_y2, c2_x2, c2_y2, end_x2, end_y2, c1_x3, c1_y3, c2_x3, c2_y3, end_x3, end_y3, ...]
         | 
| @@ -1365,8 +1366,8 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t | |
| 1365 1366 | 
             
            - All boolean property readers return `true` or `false` in Ruby instead of the [libui](https://github.com/andlabs/libui) original `0` or `1` in C.
         | 
| 1366 1367 | 
             
            - All boolean property writers accept `true`/`false` in addition to `1`/`0` in Ruby
         | 
| 1367 1368 | 
             
            - All string property readers return a `String` object in Ruby instead of the [libui](https://github.com/andlabs/libui) Fiddle pointer object.
         | 
| 1368 | 
            -
            - Automatically allocate font descriptors upon instantiating `font_button` controls and free them when  | 
| 1369 | 
            -
            - Automatically allocate color value pointers upon instantiating `color_button` controls and free them when  | 
| 1369 | 
            +
            - Automatically allocate font descriptors upon instantiating `font_button` controls and free them when destroying `font_button` controls
         | 
| 1370 | 
            +
            - Automatically allocate color value pointers upon instantiating `color_button` controls and free them when destroying `color_button` controls
         | 
| 1370 1371 | 
             
            - On the Mac, if no `menu` items were added, an automatic `quit_menu_item` is added to enable quitting with CTRL+Q
         | 
| 1371 1372 | 
             
            - When destroying a control nested under a `horizontal_box` or `vertical_box`, it is automatically deleted from the box's children
         | 
| 1372 1373 | 
             
            - When destroying a control nested under a `form`, it is automatically deleted from the form's children
         | 
| @@ -1384,6 +1385,7 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t | |
| 1384 1385 | 
             
            - `scrolling_area` `#scroll_to` 3rd and 4th arguments (`width` and `height`) default to main window width and height if not specified.
         | 
| 1385 1386 | 
             
            - `area` paths are specified declaratively with shapes/figures underneath (e.g. `rectangle`), and `area` draw listener is automatically generated
         | 
| 1386 1387 | 
             
            - `area` path shapes can be added directly under `area` without declaring `path` explicitly as a convenient shorthand
         | 
| 1388 | 
            +
            - `line` and `bezier` automatically start a new figure if placed outside of `figure`
         | 
| 1387 1389 | 
             
            - Observe figure properties (e.g. `rectangle` `width`) for changes and automatically redraw containing area accordingly
         | 
| 1388 1390 | 
             
            - Observe `path` `fill` and `stroke` hashes for changes and automatically redraw containing area accordingly
         | 
| 1389 1391 | 
             
            - Observe `text` and `string` properties for changes and automatically redraw containing area accordingly
         | 
| @@ -1395,7 +1397,15 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t | |
| 1395 1397 |  | 
| 1396 1398 | 
             
            ### Custom Keywords
         | 
| 1397 1399 |  | 
| 1398 | 
            -
             | 
| 1400 | 
            +
            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.
         | 
| 1401 | 
            +
             | 
| 1402 | 
            +
            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.
         | 
| 1403 | 
            +
             | 
| 1404 | 
            +
            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)`).
         | 
| 1405 | 
            +
             | 
| 1406 | 
            +
            To make custom keywords externally reusable, you can define in modules and simply include the modules in the view classes that need them.
         | 
| 1407 | 
            +
             | 
| 1408 | 
            +
            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`).
         | 
| 1399 1409 |  | 
| 1400 1410 | 
             
            Example that defines `form_field`, `address_form`, `label_pair`, and `address` keywords (you may copy/paste in [`girb`](#girb-glimmer-irb)):
         | 
| 1401 1411 |  | 
| @@ -1407,44 +1417,37 @@ include Glimmer | |
| 1407 1417 |  | 
| 1408 1418 | 
             
            Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code)
         | 
| 1409 1419 |  | 
| 1410 | 
            -
            def form_field(model,  | 
| 1411 | 
            -
               | 
| 1420 | 
            +
            def form_field(model, attribute)
         | 
| 1421 | 
            +
              attribute = attribute.to_s
         | 
| 1412 1422 | 
             
              entry { |e|
         | 
| 1413 | 
            -
                label  | 
| 1414 | 
            -
                text model | 
| 1415 | 
            -
             | 
| 1416 | 
            -
                on_changed do
         | 
| 1417 | 
            -
                  model.send("#{property}=", e.text)
         | 
| 1418 | 
            -
                end
         | 
| 1423 | 
            +
                label attribute.underscore.split('_').map(&:capitalize).join(' ')
         | 
| 1424 | 
            +
                text <=> [model, attribute]
         | 
| 1419 1425 | 
             
              }
         | 
| 1420 1426 | 
             
            end
         | 
| 1421 1427 |  | 
| 1422 | 
            -
            def address_form( | 
| 1428 | 
            +
            def address_form(address_model)
         | 
| 1423 1429 | 
             
              form {
         | 
| 1424 | 
            -
                form_field( | 
| 1425 | 
            -
                form_field( | 
| 1426 | 
            -
                form_field( | 
| 1427 | 
            -
                form_field( | 
| 1428 | 
            -
                form_field( | 
| 1430 | 
            +
                form_field(address_model, :street)
         | 
| 1431 | 
            +
                form_field(address_model, :p_o_box)
         | 
| 1432 | 
            +
                form_field(address_model, :city)
         | 
| 1433 | 
            +
                form_field(address_model, :state)
         | 
| 1434 | 
            +
                form_field(address_model, :zip_code)
         | 
| 1429 1435 | 
             
              }
         | 
| 1430 1436 | 
             
            end
         | 
| 1431 1437 |  | 
| 1432 1438 | 
             
            def label_pair(model, attribute, value)
         | 
| 1433 | 
            -
              name_label = nil
         | 
| 1434 | 
            -
              value_label = nil
         | 
| 1435 1439 | 
             
              horizontal_box {
         | 
| 1436 | 
            -
                 | 
| 1437 | 
            -
                 | 
| 1440 | 
            +
                label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
         | 
| 1441 | 
            +
                label(value.to_s) {
         | 
| 1442 | 
            +
                  text <= [model, attribute]
         | 
| 1443 | 
            +
                }
         | 
| 1438 1444 | 
             
              }
         | 
| 1439 | 
            -
              Glimmer::DataBinding::Observer.proc do
         | 
| 1440 | 
            -
                value_label.text = model.send(attribute)
         | 
| 1441 | 
            -
              end.observe(model, attribute)
         | 
| 1442 1445 | 
             
            end
         | 
| 1443 1446 |  | 
| 1444 | 
            -
            def address( | 
| 1447 | 
            +
            def address(address_model)
         | 
| 1445 1448 | 
             
              vertical_box {
         | 
| 1446 | 
            -
                 | 
| 1447 | 
            -
                  label_pair( | 
| 1449 | 
            +
                address_model.each_pair do |attribute, value|
         | 
| 1450 | 
            +
                  label_pair(address_model, attribute, value)
         | 
| 1448 1451 | 
             
                end
         | 
| 1449 1452 | 
             
              }
         | 
| 1450 1453 | 
             
            end
         | 
| @@ -1501,6 +1504,12 @@ window('Method-Based Custom Keyword') { | |
| 1501 1504 |  | 
| 1502 1505 | 
             
            
         | 
| 1503 1506 |  | 
| 1507 | 
            +
            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.
         | 
| 1508 | 
            +
             | 
| 1509 | 
            +
            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.
         | 
| 1510 | 
            +
             | 
| 1511 | 
            +
            Learn more from custom keyword usage in [Method-Based Custom Keyword](#method-based-custom-keyword), [Area-Based Custom Controls](#area-based-custom-controls), [Basic Scrolling Area](#basic-scrolling-area), [Histogram](#histogram), and [Tetris](#tetris) examples.
         | 
| 1512 | 
            +
             | 
| 1504 1513 | 
             
            ### Observer Pattern
         | 
| 1505 1514 |  | 
| 1506 1515 | 
             
            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.
         | 
| @@ -1772,9 +1781,9 @@ Data-bound model attribute can be: | |
| 1772 1781 | 
             
            - **Indexed:** `String` containing array attribute index (e.g. `[customer, 'addresses[0].street']`). That results in "indexed data-binding"
         | 
| 1773 1782 |  | 
| 1774 1783 | 
             
            Data-binding options include:
         | 
| 1775 | 
            -
            - `before_read {|value| ...}`: performs an operation before reading data from Model to update  | 
| 1784 | 
            +
            - `before_read {|value| ...}`: performs an operation before reading data from Model to update View.
         | 
| 1776 1785 | 
             
            - `on_read {|value| ...}`: converts value read from Model to update the View.
         | 
| 1777 | 
            -
            - `after_read {|converted_value| ...}`: performs an operation after read from Model  | 
| 1786 | 
            +
            - `after_read {|converted_value| ...}`: performs an operation after read from Model to update View.
         | 
| 1778 1787 | 
             
            - `before_write {|value| ...}`: performs an operation before writing data to Model from View.
         | 
| 1779 1788 | 
             
            - `on_write {|value| ...}`: converts value read from View to update the Model.
         | 
| 1780 1789 | 
             
            - `after_write {|converted_value| ...}`: performs an operation after writing to Model from View.
         | 
| @@ -1795,8 +1804,8 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions), | |
| 1795 1804 | 
             
            #### Data-Binding Gotchas
         | 
| 1796 1805 |  | 
| 1797 1806 | 
             
            - 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)
         | 
| 1798 | 
            -
            - Data-binding a property utilizes the control's listener associated with the property (e.g. `on_changed` for `entry` `text`), so you  | 
| 1799 | 
            -
            - Data-binding a View control to another View control directly is not a good  | 
| 1807 | 
            +
            - Data-binding a property utilizes the control's listener associated with the property (e.g. `on_changed` for `entry` `text`), so although you can add another listener if you want ([Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) will happily add your listener to the list of listeners that will get notified by a certain event), sometimes it is recommended that you add an `after_read: ->(val) {}` or `after_write: ->(val) {}` block instead to perform something after data-binding reads from or writes to the Model attribute.
         | 
| 1808 | 
            +
            - Data-binding a View control to another View control directly is not a good practice as it causes tight-coupling. Instead, data-bind both View controls to the same Presenter/Model attribute, and that keeps them in sync while keeping the code decoupled.
         | 
| 1800 1809 |  | 
| 1801 1810 | 
             
            ### API Gotchas
         | 
| 1802 1811 |  | 
| @@ -1805,8 +1814,8 @@ Learn more from data-binding usage in [Login](#login) (4 data-binding versions), | |
| 1805 1814 | 
             
            - `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).
         | 
| 1806 1815 | 
             
            - `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)
         | 
| 1807 1816 | 
             
            - `text` `string` `background` does not work on Windows due to an [issue in libui](https://github.com/andlabs/libui/issues/347).
         | 
| 1808 | 
            -
            - `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.
         | 
| 1809 1817 | 
             
            - `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.
         | 
| 1818 | 
            +
            - `radio_buttons` on Linux has an issue where it always selects the first item even if you did not set its `selected` value or set it to `-1` (meaning unselected). It works correctly on Mac and Windows.
         | 
| 1810 1819 | 
             
            - 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.
         | 
| 1811 1820 | 
             
            - As per the code of [examples/basic_transform.rb](#basic-transform), Windows requires different ordering of transforms than Mac and Linux.
         | 
| 1812 1821 | 
             
            - `scrolling_area#scroll_to` does not seem to work on Windows and Linux, but works fine on Mac
         | 
| @@ -6039,7 +6048,7 @@ Glimmer::LibUI.timer(1) do | |
| 6039 6048 | 
             
              cpu_percentage_value = nil
         | 
| 6040 6049 | 
             
              if OS.windows?
         | 
| 6041 6050 | 
             
                cpu_percentage_raw_value = `wmic cpu get loadpercentage`
         | 
| 6042 | 
            -
                cpu_percentage_value = cpu_percentage_raw_value.split("\n") | 
| 6051 | 
            +
                cpu_percentage_value = cpu_percentage_raw_value.split("\n").map(&:strip).find {|l| l.match(/^\d+$/)}.to_i
         | 
| 6043 6052 | 
             
              elsif OS.mac?
         | 
| 6044 6053 | 
             
                cpu_percentage_value = `ps -A -o %cpu | awk '{s+=$1} END {print s}'`.to_i
         | 
| 6045 6054 | 
             
              elsif OS.linux?
         | 
| @@ -6110,6 +6119,7 @@ class CustomDrawText | |
| 6110 6119 | 
             
                          @string.font = fb.font
         | 
| 6111 6120 | 
             
                        end
         | 
| 6112 6121 | 
             
                      }
         | 
| 6122 | 
            +
             | 
| 6113 6123 | 
             
                      color_button { |cb|
         | 
| 6114 6124 | 
             
                        label 'Color'
         | 
| 6115 6125 |  | 
| @@ -6117,13 +6127,17 @@ class CustomDrawText | |
| 6117 6127 | 
             
                          @string.color = cb.color
         | 
| 6118 6128 | 
             
                        end
         | 
| 6119 6129 | 
             
                      }
         | 
| 6120 | 
            -
             | 
| 6121 | 
            -
             | 
| 6122 | 
            -
                        
         | 
| 6123 | 
            -
             | 
| 6124 | 
            -
                           | 
| 6125 | 
            -
             | 
| 6126 | 
            -
             | 
| 6130 | 
            +
             | 
| 6131 | 
            +
                      unless OS.windows?
         | 
| 6132 | 
            +
                        color_button { |cb|
         | 
| 6133 | 
            +
                          label 'Background'
         | 
| 6134 | 
            +
                          
         | 
| 6135 | 
            +
                          on_changed do
         | 
| 6136 | 
            +
                            @string.background = cb.color
         | 
| 6137 | 
            +
                          end
         | 
| 6138 | 
            +
                        }
         | 
| 6139 | 
            +
                      end
         | 
| 6140 | 
            +
             | 
| 6127 6141 | 
             
                      combobox { |c|
         | 
| 6128 6142 | 
             
                        label 'Underline'
         | 
| 6129 6143 | 
             
                        items Glimmer::LibUI.enum_symbols(:underline).map(&:to_s).map {|word| word.split('_').map(&:capitalize).join(' ')}
         | 
| @@ -6133,12 +6147,36 @@ class CustomDrawText | |
| 6133 6147 | 
             
                          @string.underline = c.selected_item.underscore
         | 
| 6134 6148 | 
             
                        end
         | 
| 6135 6149 | 
             
                      }
         | 
| 6150 | 
            +
             | 
| 6151 | 
            +
                      combobox { |c|
         | 
| 6152 | 
            +
                        label 'Underline Built-In Color'
         | 
| 6153 | 
            +
                        items Glimmer::LibUI.enum_symbols(:underline_color).map(&:to_s).map(&:capitalize)
         | 
| 6154 | 
            +
                        selected 'Custom'
         | 
| 6155 | 
            +
                        
         | 
| 6156 | 
            +
                        on_selected do
         | 
| 6157 | 
            +
                          @underline_custom_color_button.enabled = c.selected_item == 'Custom'
         | 
| 6158 | 
            +
                          if c.selected_item == 'Custom'
         | 
| 6159 | 
            +
                            @string.underline_color = @underline_custom_color_button.color
         | 
| 6160 | 
            +
                          else
         | 
| 6161 | 
            +
                            @string.underline_color = c.selected_item.underscore
         | 
| 6162 | 
            +
                            @underline_custom_color_button.color = :black
         | 
| 6163 | 
            +
                          end
         | 
| 6164 | 
            +
                        end
         | 
| 6165 | 
            +
                      }
         | 
| 6166 | 
            +
             | 
| 6167 | 
            +
                      @underline_custom_color_button = color_button {
         | 
| 6168 | 
            +
                        label 'Underline Custom Color'
         | 
| 6169 | 
            +
                        
         | 
| 6170 | 
            +
                        on_changed do
         | 
| 6171 | 
            +
                          @string.underline_color = @underline_custom_color_button.color
         | 
| 6172 | 
            +
                        end
         | 
| 6173 | 
            +
                      }
         | 
| 6136 6174 | 
             
                    }
         | 
| 6137 6175 |  | 
| 6138 6176 | 
             
                    area {
         | 
| 6139 | 
            -
                      text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
         | 
| 6177 | 
            +
                      text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width] - 2*x)
         | 
| 6140 6178 | 
             
                        # align :left # default alignment
         | 
| 6141 | 
            -
             | 
| 6179 | 
            +
                        
         | 
| 6142 6180 | 
             
                        @string = string {
         | 
| 6143 6181 | 
             
                          '  At last Ygramul sensed that something was coming toward ' \
         | 
| 6144 6182 | 
             
                          'her. With the speed of lightning, she turned about, confronting ' \
         | 
| @@ -6183,62 +6221,94 @@ require 'glimmer-dsl-libui' | |
| 6183 6221 | 
             
            # The English version, translated by Ralph Manheim, was published in 1983.
         | 
| 6184 6222 | 
             
            class CustomDrawText
         | 
| 6185 6223 | 
             
              include Glimmer
         | 
| 6186 | 
            -
             | 
| 6224 | 
            +
             | 
| 6187 6225 | 
             
              def launch
         | 
| 6188 6226 | 
             
                window('Michael Ende (1929-1995) The Neverending Story', 600, 500) {
         | 
| 6189 6227 | 
             
                  margined true
         | 
| 6190 | 
            -
             | 
| 6228 | 
            +
             | 
| 6191 6229 | 
             
                  vertical_box {
         | 
| 6192 6230 | 
             
                    form {
         | 
| 6193 6231 | 
             
                      stretchy false
         | 
| 6194 | 
            -
             | 
| 6232 | 
            +
             | 
| 6195 6233 | 
             
                      font_button { |fb|
         | 
| 6196 6234 | 
             
                        label 'Font'
         | 
| 6197 | 
            -
             | 
| 6235 | 
            +
             | 
| 6198 6236 | 
             
                        on_changed do
         | 
| 6199 6237 | 
             
                          @font = fb.font
         | 
| 6200 6238 | 
             
                          @area.queue_redraw_all
         | 
| 6201 6239 | 
             
                        end
         | 
| 6202 6240 | 
             
                      }
         | 
| 6241 | 
            +
             | 
| 6203 6242 | 
             
                      color_button { |cb|
         | 
| 6204 6243 | 
             
                        label 'Color'
         | 
| 6205 | 
            -
             | 
| 6244 | 
            +
             | 
| 6206 6245 | 
             
                        on_changed do
         | 
| 6207 6246 | 
             
                          @color = cb.color
         | 
| 6208 6247 | 
             
                          @area.queue_redraw_all
         | 
| 6209 6248 | 
             
                        end
         | 
| 6210 6249 | 
             
                      }
         | 
| 6211 | 
            -
             | 
| 6212 | 
            -
             | 
| 6213 | 
            -
                        
         | 
| 6214 | 
            -
             | 
| 6215 | 
            -
             | 
| 6216 | 
            -
                           | 
| 6217 | 
            -
             | 
| 6218 | 
            -
             | 
| 6250 | 
            +
             | 
| 6251 | 
            +
                      unless OS.windows?
         | 
| 6252 | 
            +
                        color_button { |cb|
         | 
| 6253 | 
            +
                          label 'Background'
         | 
| 6254 | 
            +
             | 
| 6255 | 
            +
                          on_changed do
         | 
| 6256 | 
            +
                            @background = cb.color
         | 
| 6257 | 
            +
                            @area.queue_redraw_all
         | 
| 6258 | 
            +
                          end
         | 
| 6259 | 
            +
                        }
         | 
| 6260 | 
            +
                      end
         | 
| 6261 | 
            +
             | 
| 6219 6262 | 
             
                      combobox { |c|
         | 
| 6220 6263 | 
             
                        label 'Underline'
         | 
| 6221 6264 | 
             
                        items Glimmer::LibUI.enum_symbols(:underline).map(&:to_s).map {|word| word.split('_').map(&:capitalize).join(' ')}
         | 
| 6222 6265 | 
             
                        selected 'None'
         | 
| 6223 | 
            -
             | 
| 6266 | 
            +
             | 
| 6224 6267 | 
             
                        on_selected do
         | 
| 6225 6268 | 
             
                          @underline = c.selected_item.underscore
         | 
| 6226 6269 | 
             
                          @area.queue_redraw_all
         | 
| 6227 6270 | 
             
                        end
         | 
| 6228 6271 | 
             
                      }
         | 
| 6272 | 
            +
             | 
| 6273 | 
            +
                      combobox { |c|
         | 
| 6274 | 
            +
                        label 'Underline Built-In Color'
         | 
| 6275 | 
            +
                        items Glimmer::LibUI.enum_symbols(:underline_color).map(&:to_s).map(&:capitalize)
         | 
| 6276 | 
            +
                        selected 'Custom'
         | 
| 6277 | 
            +
             | 
| 6278 | 
            +
                        on_selected do
         | 
| 6279 | 
            +
                          @underline_custom_color_button.enabled = c.selected_item == 'Custom'
         | 
| 6280 | 
            +
                          if c.selected_item == 'Custom'
         | 
| 6281 | 
            +
                            @underline_color = @underline_custom_color_button.color
         | 
| 6282 | 
            +
                          else
         | 
| 6283 | 
            +
                            @underline_color = c.selected_item.underscore
         | 
| 6284 | 
            +
                            @underline_custom_color_button.color = :black
         | 
| 6285 | 
            +
                          end
         | 
| 6286 | 
            +
                          @area.queue_redraw_all
         | 
| 6287 | 
            +
                        end
         | 
| 6288 | 
            +
                      }
         | 
| 6289 | 
            +
             | 
| 6290 | 
            +
                      @underline_custom_color_button = color_button {
         | 
| 6291 | 
            +
                        label 'Underline Custom Color'
         | 
| 6292 | 
            +
             | 
| 6293 | 
            +
                        on_changed do
         | 
| 6294 | 
            +
                          @underline_color = @underline_custom_color_button.color
         | 
| 6295 | 
            +
                          @area.queue_redraw_all
         | 
| 6296 | 
            +
                        end
         | 
| 6297 | 
            +
                      }
         | 
| 6229 6298 | 
             
                    }
         | 
| 6230 | 
            -
             | 
| 6299 | 
            +
             | 
| 6231 6300 | 
             
                    @area = area {
         | 
| 6232 6301 | 
             
                      on_draw do |area_draw_params|
         | 
| 6233 | 
            -
                        text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
         | 
| 6302 | 
            +
                        text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width] - 2*x)
         | 
| 6234 6303 | 
             
                          # align :left # default alignment
         | 
| 6235 | 
            -
             | 
| 6304 | 
            +
             | 
| 6236 6305 | 
             
                          string {
         | 
| 6237 6306 | 
             
                            font @font
         | 
| 6238 6307 | 
             
                            color @color
         | 
| 6239 6308 | 
             
                            background @background
         | 
| 6240 6309 | 
             
                            underline @underline
         | 
| 6241 | 
            -
                            
         | 
| 6310 | 
            +
                            underline_color @underline_color
         | 
| 6311 | 
            +
             | 
| 6242 6312 | 
             
                            '  At last Ygramul sensed that something was coming toward ' \
         | 
| 6243 6313 | 
             
                            'her. With the speed of lightning, she turned about, confronting ' \
         | 
| 6244 6314 | 
             
                            'Atreyu with an enormous steel-blue face. Her single eye had a ' \
         | 
| @@ -8309,6 +8379,12 @@ window('Login') { | |
| 8309 8379 |  | 
| 8310 8380 | 
             
            #### Method-Based Custom Keyword
         | 
| 8311 8381 |  | 
| 8382 | 
            +
            [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.
         | 
| 8383 | 
            +
              
         | 
| 8384 | 
            +
            This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom control keywords.
         | 
| 8385 | 
            +
             | 
| 8386 | 
            +
            The custom keywords are defined via methods (thus are "method-based").
         | 
| 8387 | 
            +
             | 
| 8312 8388 | 
             
            [examples/method_based_custom_keyword.rb](examples/method_based_custom_keyword.rb)
         | 
| 8313 8389 |  | 
| 8314 8390 | 
             
            Run with this command from the root of the project if you cloned the project:
         | 
| @@ -8524,6 +8600,313 @@ window('Method-Based Custom Keyword') { | |
| 8524 8600 | 
             
            }.show
         | 
| 8525 8601 | 
             
            ```
         | 
| 8526 8602 |  | 
| 8603 | 
            +
            #### Area-Based Custom Controls
         | 
| 8604 | 
            +
             | 
| 8605 | 
            +
            [Custom keywords](#custom-keywords) can be defined for graphical custom controls (components) built completely from scratch as vector-graphics on top of the [`area`](#area-api) control while leveraging keyboard and mouse listeners.
         | 
| 8606 | 
            +
             | 
| 8607 | 
            +
            This example defines `text_label` and `push_button` as [`area`](#area-api)-based graphical custom controls that can have width, height, font, fill, stroke, border, and custom text location.
         | 
| 8608 | 
            +
                  
         | 
| 8609 | 
            +
            [examples/area_based_custom_controls.rb](examples/area_based_custom_controls.rb)
         | 
| 8610 | 
            +
             | 
| 8611 | 
            +
            Run with this command from the root of the project if you cloned the project:
         | 
| 8612 | 
            +
             | 
| 8613 | 
            +
            ```
         | 
| 8614 | 
            +
            ruby -r './lib/glimmer-dsl-libui' examples/area_based_custom_controls.rb
         | 
| 8615 | 
            +
            ```
         | 
| 8616 | 
            +
             | 
| 8617 | 
            +
            Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
         | 
| 8618 | 
            +
             | 
| 8619 | 
            +
            ```
         | 
| 8620 | 
            +
            ruby -r glimmer-dsl-libui -e "require 'examples/area_based_custom_controls'"
         | 
| 8621 | 
            +
            ```
         | 
| 8622 | 
            +
             | 
| 8623 | 
            +
            Mac | Windows | Linux
         | 
| 8624 | 
            +
            ----|---------|------
         | 
| 8625 | 
            +
               |    |   
         | 
| 8626 | 
            +
             | 
| 8627 | 
            +
            New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
         | 
| 8628 | 
            +
             | 
| 8629 | 
            +
            ```ruby
         | 
| 8630 | 
            +
            require 'glimmer-dsl-libui'
         | 
| 8631 | 
            +
             | 
| 8632 | 
            +
            class AreaBasedCustomControls
         | 
| 8633 | 
            +
              include Glimmer
         | 
| 8634 | 
            +
              
         | 
| 8635 | 
            +
              attr_accessor :label_width, :label_height, :label_font_descriptor,
         | 
| 8636 | 
            +
                            :label_text_color, :label_background_fill, :label_border_stroke,
         | 
| 8637 | 
            +
                            :label_text_x, :label_text_y,
         | 
| 8638 | 
            +
                            :button_width, :button_height, :button_font_descriptor,
         | 
| 8639 | 
            +
                            :button_text_color, :button_background_fill, :button_border_stroke,
         | 
| 8640 | 
            +
                            :button_text_x, :button_text_y
         | 
| 8641 | 
            +
              
         | 
| 8642 | 
            +
              def initialize
         | 
| 8643 | 
            +
                self.label_width = 335
         | 
| 8644 | 
            +
                self.label_height = 50
         | 
| 8645 | 
            +
                self.label_font_descriptor = {family: OS.linux? ? 'Bitstream Vera Sans Mono' : 'Courier New', size: 16, weight: :bold, italic: :italic}
         | 
| 8646 | 
            +
                self.label_text_color = :red
         | 
| 8647 | 
            +
                self.label_background_fill = :yellow
         | 
| 8648 | 
            +
                self.label_border_stroke = :limegreen
         | 
| 8649 | 
            +
                
         | 
| 8650 | 
            +
                self.button_width = 130
         | 
| 8651 | 
            +
                self.button_height = 50
         | 
| 8652 | 
            +
                self.button_font_descriptor = {family: OS.linux? ? 'Bitstream Vera Sans Mono' : 'Courier New', size: 36, weight: :bold, italic: :italic}
         | 
| 8653 | 
            +
                self.button_text_color = :green
         | 
| 8654 | 
            +
                self.button_background_fill = :yellow
         | 
| 8655 | 
            +
                self.button_border_stroke = :limegreen
         | 
| 8656 | 
            +
              end
         | 
| 8657 | 
            +
              
         | 
| 8658 | 
            +
              def rebuild_text_label
         | 
| 8659 | 
            +
                @text_label.destroy
         | 
| 8660 | 
            +
                @text_label_vertical_box.content { # re-open vertical box content and shove in a new button
         | 
| 8661 | 
            +
                  @text_label = text_label('This is a text label.',
         | 
| 8662 | 
            +
                                           width: label_width, height: label_height, font_descriptor: label_font_descriptor,
         | 
| 8663 | 
            +
                                           background_fill: label_background_fill, text_color: label_text_color, border_stroke: label_border_stroke,
         | 
| 8664 | 
            +
                                           text_x: label_text_x, text_y: label_text_y)
         | 
| 8665 | 
            +
                }
         | 
| 8666 | 
            +
              end
         | 
| 8667 | 
            +
              
         | 
| 8668 | 
            +
              def rebuild_push_button
         | 
| 8669 | 
            +
                @push_button.destroy
         | 
| 8670 | 
            +
                @push_button_vertical_box.content { # re-open vertical box content and shove in a new button
         | 
| 8671 | 
            +
                  @push_button = push_button('Push',
         | 
| 8672 | 
            +
                                             width: button_width, height: button_height, font_descriptor: button_font_descriptor,
         | 
| 8673 | 
            +
                                             background_fill: button_background_fill, text_color: button_text_color, border_stroke: button_border_stroke,
         | 
| 8674 | 
            +
                                             text_x: button_text_x, text_y: button_text_y) {
         | 
| 8675 | 
            +
                    on_mouse_up do
         | 
| 8676 | 
            +
                      message_box('Button Pushed', 'Thank you for pushing the button!')
         | 
| 8677 | 
            +
                    end
         | 
| 8678 | 
            +
                  }
         | 
| 8679 | 
            +
                }
         | 
| 8680 | 
            +
              end
         | 
| 8681 | 
            +
              
         | 
| 8682 | 
            +
              def launch
         | 
| 8683 | 
            +
                window('Area-Based Custom Controls', 385, 385) { |w|
         | 
| 8684 | 
            +
                  margined true
         | 
| 8685 | 
            +
                  
         | 
| 8686 | 
            +
                  tab {
         | 
| 8687 | 
            +
                    tab_item('Text Label') {
         | 
| 8688 | 
            +
                      @text_label_vertical_box = vertical_box {
         | 
| 8689 | 
            +
                        vertical_box {
         | 
| 8690 | 
            +
                          text_label('Text Label Construction Form:', width: 250, height: 30, font_descriptor: {size: 16, weight: :bold}, text_x: 0, text_y: 0)
         | 
| 8691 | 
            +
                          
         | 
| 8692 | 
            +
                          horizontal_box {
         | 
| 8693 | 
            +
                            label('Width')
         | 
| 8694 | 
            +
                            spinbox(1, 1000) {
         | 
| 8695 | 
            +
                              value <=> [self, :label_width, after_write: method(:rebuild_text_label)]
         | 
| 8696 | 
            +
                            }
         | 
| 8697 | 
            +
                          }
         | 
| 8698 | 
            +
                          
         | 
| 8699 | 
            +
                          horizontal_box {
         | 
| 8700 | 
            +
                            label('Height')
         | 
| 8701 | 
            +
                            spinbox(1, 1000) {
         | 
| 8702 | 
            +
                              value <=> [self, :label_height, after_write: method(:rebuild_text_label)]
         | 
| 8703 | 
            +
                            }
         | 
| 8704 | 
            +
                          }
         | 
| 8705 | 
            +
                          
         | 
| 8706 | 
            +
                          horizontal_box {
         | 
| 8707 | 
            +
                            label('Font')
         | 
| 8708 | 
            +
                            font_button {
         | 
| 8709 | 
            +
                              font <=> [self, :label_font_descriptor, after_write: method(:rebuild_text_label)]
         | 
| 8710 | 
            +
                            }
         | 
| 8711 | 
            +
                          }
         | 
| 8712 | 
            +
                          
         | 
| 8713 | 
            +
                          horizontal_box {
         | 
| 8714 | 
            +
                            label('Text Color')
         | 
| 8715 | 
            +
                            color_button {
         | 
| 8716 | 
            +
                              color <=> [self, :label_text_color, after_write: method(:rebuild_text_label)]
         | 
| 8717 | 
            +
                            }
         | 
| 8718 | 
            +
                          }
         | 
| 8719 | 
            +
                          
         | 
| 8720 | 
            +
                          horizontal_box {
         | 
| 8721 | 
            +
                            label('Background Color')
         | 
| 8722 | 
            +
                            color_button {
         | 
| 8723 | 
            +
                              color <=> [self, :label_background_fill, after_write: method(:rebuild_text_label)]
         | 
| 8724 | 
            +
                            }
         | 
| 8725 | 
            +
                          }
         | 
| 8726 | 
            +
                          
         | 
| 8727 | 
            +
                          horizontal_box {
         | 
| 8728 | 
            +
                            label('Border Color')
         | 
| 8729 | 
            +
                            color_button {
         | 
| 8730 | 
            +
                              color <=> [self, :label_border_stroke, after_write: method(:rebuild_text_label)]
         | 
| 8731 | 
            +
                            }
         | 
| 8732 | 
            +
                          }
         | 
| 8733 | 
            +
                          
         | 
| 8734 | 
            +
                          horizontal_box {
         | 
| 8735 | 
            +
                            label('Text X (0=centered)')
         | 
| 8736 | 
            +
                            spinbox(0, 1000) {
         | 
| 8737 | 
            +
                              value <=> [self, :label_text_x, on_read: ->(x) {x.nil? ? 0 : x}, on_write: ->(x) {x == 0 ? nil : x}, after_write: method(:rebuild_text_label)]
         | 
| 8738 | 
            +
                            }
         | 
| 8739 | 
            +
                          }
         | 
| 8740 | 
            +
                          
         | 
| 8741 | 
            +
                          horizontal_box {
         | 
| 8742 | 
            +
                            label('Text Y (0=centered)')
         | 
| 8743 | 
            +
                            spinbox(0, 1000) {
         | 
| 8744 | 
            +
                              value <=> [self, :label_text_y, on_read: ->(y) {y.nil? ? 0 : y}, on_write: ->(y) {y == 0 ? nil : y}, after_write: method(:rebuild_text_label)]
         | 
| 8745 | 
            +
                            }
         | 
| 8746 | 
            +
                          }
         | 
| 8747 | 
            +
                        }
         | 
| 8748 | 
            +
                        
         | 
| 8749 | 
            +
                        @text_label = text_label('This is a text label.',
         | 
| 8750 | 
            +
                                                 width: label_width, height: label_height, font_descriptor: label_font_descriptor,
         | 
| 8751 | 
            +
                                                 background_fill: label_background_fill, text_color: label_text_color, border_stroke: label_border_stroke,
         | 
| 8752 | 
            +
                                                 text_x: label_text_x, text_y: label_text_y)
         | 
| 8753 | 
            +
                      }
         | 
| 8754 | 
            +
                    }
         | 
| 8755 | 
            +
                    
         | 
| 8756 | 
            +
                    tab_item('Push Button') {
         | 
| 8757 | 
            +
                      @push_button_vertical_box = vertical_box {
         | 
| 8758 | 
            +
                        vertical_box {
         | 
| 8759 | 
            +
                          text_label('Push Button Construction Form:', width: 250, height: 30, font_descriptor: {size: 16, weight: :bold}, text_x: 0, text_y: 0)
         | 
| 8760 | 
            +
                          
         | 
| 8761 | 
            +
                          horizontal_box {
         | 
| 8762 | 
            +
                            label('Width')
         | 
| 8763 | 
            +
                            spinbox(1, 1000) {
         | 
| 8764 | 
            +
                              value <=> [self, :button_width, after_write: method(:rebuild_push_button)]
         | 
| 8765 | 
            +
                            }
         | 
| 8766 | 
            +
                          }
         | 
| 8767 | 
            +
                          
         | 
| 8768 | 
            +
                          horizontal_box {
         | 
| 8769 | 
            +
                            label('Height')
         | 
| 8770 | 
            +
                            spinbox(1, 1000) {
         | 
| 8771 | 
            +
                              value <=> [self, :button_height, after_write: method(:rebuild_push_button)]
         | 
| 8772 | 
            +
                            }
         | 
| 8773 | 
            +
                          }
         | 
| 8774 | 
            +
                          
         | 
| 8775 | 
            +
                          horizontal_box {
         | 
| 8776 | 
            +
                            label('Font')
         | 
| 8777 | 
            +
                            font_button {
         | 
| 8778 | 
            +
                              font <=> [self, :button_font_descriptor, after_write: method(:rebuild_push_button)]
         | 
| 8779 | 
            +
                            }
         | 
| 8780 | 
            +
                          }
         | 
| 8781 | 
            +
                          
         | 
| 8782 | 
            +
                          horizontal_box {
         | 
| 8783 | 
            +
                            label('Text Color')
         | 
| 8784 | 
            +
                            color_button {
         | 
| 8785 | 
            +
                              color <=> [self, :button_text_color, after_write: method(:rebuild_push_button)]
         | 
| 8786 | 
            +
                            }
         | 
| 8787 | 
            +
                          }
         | 
| 8788 | 
            +
                          
         | 
| 8789 | 
            +
                          horizontal_box {
         | 
| 8790 | 
            +
                            label('Background Color')
         | 
| 8791 | 
            +
                            color_button {
         | 
| 8792 | 
            +
                              color <=> [self, :button_background_fill, after_write: method(:rebuild_push_button)]
         | 
| 8793 | 
            +
                            }
         | 
| 8794 | 
            +
                          }
         | 
| 8795 | 
            +
                          
         | 
| 8796 | 
            +
                          horizontal_box {
         | 
| 8797 | 
            +
                            label('Border Color')
         | 
| 8798 | 
            +
                            color_button {
         | 
| 8799 | 
            +
                              color <=> [self, :button_border_stroke, after_write: method(:rebuild_push_button)]
         | 
| 8800 | 
            +
                            }
         | 
| 8801 | 
            +
                          }
         | 
| 8802 | 
            +
                          
         | 
| 8803 | 
            +
                          horizontal_box {
         | 
| 8804 | 
            +
                            label('Text X (0=centered)')
         | 
| 8805 | 
            +
                            spinbox(0, 1000) {
         | 
| 8806 | 
            +
                              value <=> [self, :button_text_x, on_read: ->(x) {x.nil? ? 0 : x}, on_write: ->(x) {x == 0 ? nil : x}, after_write: method(:rebuild_push_button)]
         | 
| 8807 | 
            +
                            }
         | 
| 8808 | 
            +
                          }
         | 
| 8809 | 
            +
                          
         | 
| 8810 | 
            +
                          horizontal_box {
         | 
| 8811 | 
            +
                            label('Text Y (0=centered)')
         | 
| 8812 | 
            +
                            spinbox(0, 1000) {
         | 
| 8813 | 
            +
                              value <=> [self, :button_text_y, on_read: ->(y) {y.nil? ? 0 : y}, on_write: ->(y) {y == 0 ? nil : y}, after_write: method(:rebuild_push_button)]
         | 
| 8814 | 
            +
                            }
         | 
| 8815 | 
            +
                          }
         | 
| 8816 | 
            +
                        }
         | 
| 8817 | 
            +
                        
         | 
| 8818 | 
            +
                        @push_button = push_button('Push',
         | 
| 8819 | 
            +
                                                   width: button_width, height: button_height, font_descriptor: button_font_descriptor,
         | 
| 8820 | 
            +
                                                   background_fill: button_background_fill, text_color: button_text_color, border_stroke: button_border_stroke,
         | 
| 8821 | 
            +
                                                   text_x: button_text_x, text_y: button_text_y) {
         | 
| 8822 | 
            +
                          on_mouse_up do
         | 
| 8823 | 
            +
                            message_box('Button Pushed', 'Thank you for pushing the button!')
         | 
| 8824 | 
            +
                          end
         | 
| 8825 | 
            +
                        }
         | 
| 8826 | 
            +
                      }
         | 
| 8827 | 
            +
                    }
         | 
| 8828 | 
            +
                  }
         | 
| 8829 | 
            +
                }.show
         | 
| 8830 | 
            +
              end
         | 
| 8831 | 
            +
                
         | 
| 8832 | 
            +
              # text label (area-based custom control) built with vector graphics on top of area.
         | 
| 8833 | 
            +
              #
         | 
| 8834 | 
            +
              # background_fill is transparent by default.
         | 
| 8835 | 
            +
              # background_fill can accept a single color or gradient stops just as per `fill` property in README.
         | 
| 8836 | 
            +
              # border_stroke is transparent by default.
         | 
| 8837 | 
            +
              # border_stroke can accept thickness and dashes in addition to color just as per `stroke` property in README.
         | 
| 8838 | 
            +
              def text_label(label_text,
         | 
| 8839 | 
            +
                              width: 80, height: 30, font_descriptor: {},
         | 
| 8840 | 
            +
                              background_fill: {a: 0}, text_color: :black, border_stroke: {a: 0},
         | 
| 8841 | 
            +
                              text_x: nil, text_y: nil,
         | 
| 8842 | 
            +
                              &content)
         | 
| 8843 | 
            +
                area { |the_area|
         | 
| 8844 | 
            +
                  rectangle(1, 1, width, height) {
         | 
| 8845 | 
            +
                    fill background_fill
         | 
| 8846 | 
            +
                  }
         | 
| 8847 | 
            +
                  rectangle(1, 1, width, height) {
         | 
| 8848 | 
            +
                    stroke border_stroke
         | 
| 8849 | 
            +
                  }
         | 
| 8850 | 
            +
                  
         | 
| 8851 | 
            +
                  text_height = (font_descriptor[:size] || 12) * 0.75
         | 
| 8852 | 
            +
                  text_width = (text_height * label_text.size) * 0.75
         | 
| 8853 | 
            +
                  text_x ||= (width - text_width) / 2.0
         | 
| 8854 | 
            +
                  text_y ||= (height - 4 - text_height) / 2.0
         | 
| 8855 | 
            +
                  text(text_x, text_y, width) {
         | 
| 8856 | 
            +
                    string(label_text) {
         | 
| 8857 | 
            +
                      color text_color
         | 
| 8858 | 
            +
                      font font_descriptor
         | 
| 8859 | 
            +
                    }
         | 
| 8860 | 
            +
                  }
         | 
| 8861 | 
            +
                  
         | 
| 8862 | 
            +
                  content&.call(the_area)
         | 
| 8863 | 
            +
                }
         | 
| 8864 | 
            +
              end
         | 
| 8865 | 
            +
              
         | 
| 8866 | 
            +
              # push button (area-based custom control) built with vector graphics on top of area.
         | 
| 8867 | 
            +
              #
         | 
| 8868 | 
            +
              # background_fill is white by default.
         | 
| 8869 | 
            +
              # background_fill can accept a single color or gradient stops just as per `fill` property in README.
         | 
| 8870 | 
            +
              # border_stroke is black by default.
         | 
| 8871 | 
            +
              # border_stroke can accept thickness and dashes in addition to color just as per `stroke` property in README.
         | 
| 8872 | 
            +
              # text_x and text_y are the offset of the button text in releation to its top-left corner
         | 
| 8873 | 
            +
              # When text_x, text_y are left nil, the text is automatically centered in the button area.
         | 
| 8874 | 
            +
              # Sometimes, the centering calculation is not perfect due to using a custom font, so
         | 
| 8875 | 
            +
              # in that case, pass in text_x, and text_y manually
         | 
| 8876 | 
            +
              #
         | 
| 8877 | 
            +
              # reuses the text_label custom control
         | 
| 8878 | 
            +
              def push_button(button_text,
         | 
| 8879 | 
            +
                              width: 80, height: 30, font_descriptor: {},
         | 
| 8880 | 
            +
                              background_fill: :white, text_color: :black, border_stroke: {r: 201, g: 201, b: 201},
         | 
| 8881 | 
            +
                              text_x: nil, text_y: nil,
         | 
| 8882 | 
            +
                              &content)
         | 
| 8883 | 
            +
                text_label(button_text,
         | 
| 8884 | 
            +
                              width: width, height: height, font_descriptor: font_descriptor,
         | 
| 8885 | 
            +
                              background_fill: background_fill, text_color: text_color, border_stroke: border_stroke,
         | 
| 8886 | 
            +
                              text_x: text_x, text_y: text_y) { |the_area|
         | 
| 8887 | 
            +
                  
         | 
| 8888 | 
            +
                  # dig into the_area content and grab elements to modify in mouse listeners below
         | 
| 8889 | 
            +
                  background_rectangle = the_area.children[0]
         | 
| 8890 | 
            +
                  button_string = the_area.children[2].children[0]
         | 
| 8891 | 
            +
                  
         | 
| 8892 | 
            +
                  on_mouse_down do
         | 
| 8893 | 
            +
                    background_rectangle.fill = {x0: 0, y0: 0, x1: 0, y1: height, stops: [{pos: 0, r: 72, g: 146, b: 247}, {pos: 1, r: 12, g: 85, b: 214}]}
         | 
| 8894 | 
            +
                    button_string.color = :white
         | 
| 8895 | 
            +
                  end
         | 
| 8896 | 
            +
                  
         | 
| 8897 | 
            +
                  on_mouse_up do
         | 
| 8898 | 
            +
                    background_rectangle.fill = background_fill
         | 
| 8899 | 
            +
                    button_string.color = text_color
         | 
| 8900 | 
            +
                  end
         | 
| 8901 | 
            +
                  
         | 
| 8902 | 
            +
                  content&.call(the_area)
         | 
| 8903 | 
            +
                }
         | 
| 8904 | 
            +
              end
         | 
| 8905 | 
            +
            end
         | 
| 8906 | 
            +
             | 
| 8907 | 
            +
            AreaBasedCustomControls.new.launch
         | 
| 8908 | 
            +
            ```
         | 
| 8909 | 
            +
             | 
| 8527 8910 | 
             
            #### Midi Player
         | 
| 8528 8911 |  | 
| 8529 8912 | 
             
            To run this example, install [TiMidity](http://timidity.sourceforge.net) and ensure `timidity` command is in `PATH` (can be installed via [Homebrew](https://brew.sh) on Mac or [apt-get](https://help.ubuntu.com/community/AptGet/Howto) on Linux).
         | 
| @@ -10046,7 +10429,7 @@ https://github.com/iraamaro/i3off-gtk-ruby | |
| 10046 10429 |  | 
| 10047 10430 | 
             
            ### Issues
         | 
| 10048 10431 |  | 
| 10049 | 
            -
            If you encounter [issues](https://github.com/AndyObtiva/glimmer-dsl-libui/issues) that are not reported, discover missing features that are not mentioned in [TODO.md](TODO.md), or think up better ways to use [libui](https://github.com/andlabs/libui) than what is possible with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui), you may submit an [issue](https://github.com/AndyObtiva/glimmer-dsl-libui/issues/new) or [pull request](https://github.com/AndyObtiva/glimmer-dsl-libui/compare) on [GitHub](https://github.com). In the meantime, you may try older gem versions of [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) till you find one that works | 
| 10432 | 
            +
            If you encounter [issues](https://github.com/AndyObtiva/glimmer-dsl-libui/issues) that are not reported, discover missing features that are not mentioned in [TODO.md](TODO.md), or think up better ways to use [libui](https://github.com/andlabs/libui) than what is possible with [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui), you may submit an [issue](https://github.com/AndyObtiva/glimmer-dsl-libui/issues/new) or [pull request](https://github.com/AndyObtiva/glimmer-dsl-libui/compare) on [GitHub](https://github.com). In the meantime, you may try older gem versions of [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) till you find one that works.
         | 
| 10050 10433 |  | 
| 10051 10434 | 
             
            ### Chat
         | 
| 10052 10435 |  |