glimmer-dsl-libui 0.4.16 → 0.4.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +451 -68
  4. data/VERSION +1 -1
  5. data/examples/area_based_custom_controls.rb +278 -0
  6. data/examples/basic_table.rb +1 -1
  7. data/examples/cpu_percentage.rb +1 -1
  8. data/examples/custom_draw_text.rb +14 -7
  9. data/examples/custom_draw_text2.rb +15 -8
  10. data/examples/method_based_custom_keyword.rb +9 -9
  11. data/examples/method_based_custom_keyword2.rb +9 -9
  12. data/glimmer-dsl-libui.gemspec +0 -0
  13. data/lib/glimmer/dsl/libui/dsl.rb +1 -0
  14. data/lib/glimmer/dsl/libui/observe_expression.rb +1 -1
  15. data/lib/glimmer/dsl/libui/operation_expression.rb +47 -0
  16. data/lib/glimmer/dsl/libui/property_expression.rb +2 -2
  17. data/lib/glimmer/libui/control_proxy/box.rb +1 -0
  18. data/lib/glimmer/libui/control_proxy/column/background_color_column_proxy.rb +6 -0
  19. data/lib/glimmer/libui/control_proxy/column/button_column_proxy.rb +6 -0
  20. data/lib/glimmer/libui/control_proxy/column/checkbox_column_proxy.rb +6 -0
  21. data/lib/glimmer/libui/control_proxy/column/checkbox_text_color_column_proxy.rb +6 -0
  22. data/lib/glimmer/libui/control_proxy/column/checkbox_text_column_proxy.rb +6 -0
  23. data/lib/glimmer/libui/control_proxy/column/image_column_proxy.rb +6 -0
  24. data/lib/glimmer/libui/control_proxy/column/image_text_color_column_proxy.rb +6 -0
  25. data/lib/glimmer/libui/control_proxy/column/image_text_column_proxy.rb +6 -0
  26. data/lib/glimmer/libui/control_proxy/column/progress_bar_column_proxy.rb +6 -0
  27. data/lib/glimmer/libui/control_proxy/column/text_color_column_proxy.rb +6 -0
  28. data/lib/glimmer/libui/control_proxy/column/text_column_proxy.rb +6 -0
  29. data/lib/glimmer/libui/control_proxy/column.rb +7 -0
  30. data/lib/glimmer/libui/control_proxy/form_proxy.rb +1 -0
  31. data/lib/glimmer/libui/control_proxy/image_proxy.rb +1 -0
  32. data/lib/glimmer/libui/control_proxy/menu_item_proxy/quit_menu_item_proxy.rb +0 -1
  33. data/lib/glimmer/libui/control_proxy/path_proxy.rb +6 -4
  34. data/lib/glimmer/libui/control_proxy/table_proxy.rb +51 -28
  35. data/lib/glimmer/libui/control_proxy.rb +1 -0
  36. data/lib/glimmer/libui/data_bindable.rb +1 -1
  37. data/lib/glimmer/libui/shape/bezier.rb +20 -3
  38. data/lib/glimmer/libui/shape/line.rb +23 -3
  39. data/lib/glimmer-dsl-libui.rb +6 -0
  40. 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.16
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
  [![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)
@@ -324,7 +324,7 @@ Mac | Windows | Linux
324
324
  ----|---------|------
325
325
  ![glimmer-dsl-libui-mac-tetris.png](images/glimmer-dsl-libui-mac-tetris.png) | ![glimmer-dsl-libui-windows-tetris.png](images/glimmer-dsl-libui-windows-tetris.png) | ![glimmer-dsl-libui-linux-tetris.png](images/glimmer-dsl-libui-linux-tetris.png)
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.16'
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 destorying `font_button` controls
1369
- - Automatically allocate color value pointers upon instantiating `color_button` controls and free them when destorying `color_button` controls
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
- To define custom keywords, simply define a method representing the custom control you want. To make reusable, you can define in modules and simply include the modules in the view classes that need them.
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, property)
1411
- property = property.to_s
1420
+ def form_field(model, attribute)
1421
+ attribute = attribute.to_s
1412
1422
  entry { |e|
1413
- label property.underscore.split('_').map(&:capitalize).join(' ')
1414
- text model.send(property).to_s
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(address)
1428
+ def address_form(address_model)
1423
1429
  form {
1424
- form_field(address, :street)
1425
- form_field(address, :p_o_box)
1426
- form_field(address, :city)
1427
- form_field(address, :state)
1428
- form_field(address, :zip_code)
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
- name_label = label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' '))
1437
- value_label = label(value.to_s)
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(address)
1447
+ def address(address_model)
1445
1448
  vertical_box {
1446
- address.each_pair do |attribute, value|
1447
- label_pair(address, attribute, value)
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
  ![glimmer-dsl-libui-mac-method-based-custom-keyword.png](images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)
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 the View.
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 and updating the View.
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 cannot hook into the listener directly anymore as that would negate data-binding. Instead, you can add an `after_write: ->(val) {}` option to perform something on trigger of the control listener instead.
1799
- - Data-binding a View control to another View control directly is not a good idea. Instead, data-bind both View controls to the same Presenter/Model attribute, and that keeps them in sync while keeping the code decoupled.
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")[2].to_i
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
- color_button { |cb|
6121
- label 'Background'
6122
-
6123
- on_changed do
6124
- @string.background = cb.color
6125
- end
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
- color_button { |cb|
6212
- label 'Background'
6213
-
6214
- on_changed do
6215
- @background = cb.color
6216
- @area.queue_redraw_all
6217
- end
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
+ ![glimmer-dsl-libui-mac-area-based-custom-controls.png](images/glimmer-dsl-libui-mac-area-based-custom-controls-text-label.png) ![glimmer-dsl-libui-mac-area-based-custom-controls.png](images/glimmer-dsl-libui-mac-area-based-custom-controls-push-button.png) ![glimmer-dsl-libui-mac-area-based-custom-controls.png](images/glimmer-dsl-libui-mac-area-based-custom-controls-push-button-clicked.png) | ![glimmer-dsl-libui-windows-area-based-custom-controls.png](images/glimmer-dsl-libui-windows-area-based-custom-controls-text-label.png) ![glimmer-dsl-libui-windows-area-based-custom-controls.png](images/glimmer-dsl-libui-windows-area-based-custom-controls-push-button.png) ![glimmer-dsl-libui-windows-area-based-custom-controls.png](images/glimmer-dsl-libui-windows-area-based-custom-controls-push-button-clicked.png) | ![glimmer-dsl-libui-linux-area-based-custom-controls.png](images/glimmer-dsl-libui-linux-area-based-custom-controls-text-label.png) ![glimmer-dsl-libui-linux-area-based-custom-controls.png](images/glimmer-dsl-libui-linux-area-based-custom-controls-push-button.png) ![glimmer-dsl-libui-linux-area-based-custom-controls.png](images/glimmer-dsl-libui-linux-area-based-custom-controls-push-button-clicked.png)
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 until issues are resolved.
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