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