glimmer-dsl-libui 0.2.0 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.md +975 -23
  4. data/VERSION +1 -1
  5. data/examples/area_gallery.rb +7 -1
  6. data/examples/area_gallery2.rb +14 -4
  7. data/examples/area_gallery3.rb +8 -2
  8. data/examples/area_gallery4.rb +15 -5
  9. data/examples/basic_draw_text.rb +65 -0
  10. data/examples/basic_draw_text2.rb +68 -0
  11. data/examples/color_the_circles.rb +220 -0
  12. data/examples/custom_draw_text.rb +84 -0
  13. data/examples/custom_draw_text2.rb +95 -0
  14. data/examples/midi_player.rb +2 -2
  15. data/examples/timer.rb +135 -0
  16. data/glimmer-dsl-libui.gemspec +0 -0
  17. data/lib/glimmer/dsl/libui/control_expression.rb +0 -1
  18. data/lib/glimmer/dsl/libui/property_expression.rb +3 -1
  19. data/lib/glimmer/dsl/libui/string_expression.rb +48 -0
  20. data/lib/glimmer/libui/attributed_string.rb +135 -0
  21. data/lib/glimmer/libui/control_proxy/area_proxy.rb +14 -3
  22. data/lib/glimmer/libui/control_proxy/color_button_proxy.rb +0 -1
  23. data/lib/glimmer/libui/control_proxy/combobox_proxy.rb +18 -0
  24. data/lib/glimmer/libui/control_proxy/font_button_proxy.rb +3 -3
  25. data/lib/glimmer/libui/control_proxy/menu_item_proxy/radio_menu_item_proxy.rb +69 -0
  26. data/lib/glimmer/libui/control_proxy/menu_proxy.rb +3 -0
  27. data/lib/glimmer/libui/control_proxy/path_proxy.rb +0 -2
  28. data/lib/glimmer/libui/control_proxy/text_proxy.rb +172 -0
  29. data/lib/glimmer/libui/control_proxy/window_proxy.rb +0 -6
  30. data/lib/glimmer/libui/control_proxy.rb +7 -1
  31. data/lib/glimmer/libui/shape/arc.rb +6 -3
  32. data/lib/glimmer/libui/shape/circle.rb +50 -0
  33. data/lib/glimmer/libui/shape/rectangle.rb +4 -0
  34. data/lib/glimmer/libui/shape/square.rb +4 -0
  35. data/lib/glimmer/libui.rb +73 -6
  36. metadata +13 -2
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.1.11
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.2.4
2
2
  ## Prerequisite-Free Ruby Desktop Development GUI Library
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
4
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/ce2853efdbecf6ebdc73/maintainability)](https://codeclimate.com/github/AndyObtiva/glimmer-dsl-libui/maintainability)
@@ -118,11 +118,17 @@ window('Area Gallery', 400, 400) {
118
118
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
119
119
  }
120
120
  path { # declarative stable path
121
- arc(200, 200, 90, 0, 360, false)
121
+ circle(200, 200, 90)
122
122
 
123
123
  fill r: 202, g: 102, b: 204, a: 0.5
124
124
  stroke r: 0, g: 0, b: 0, thickness: 2
125
125
  }
126
+ path { # declarative stable path
127
+ arc(400, 220, 180, 90, 90, false)
128
+
129
+ fill r: 204, g: 102, b: 204, a: 0.5
130
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
131
+ }
126
132
 
127
133
  on_mouse_event do |area_mouse_event|
128
134
  p area_mouse_event
@@ -191,7 +197,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
191
197
 
192
198
  ## Table of Contents
193
199
 
194
- - [Glimmer DSL for LibUI 0.1.10](#-glimmer-dsl-for-libui-0111)
200
+ - [Glimmer DSL for LibUI 0.2.4](#-glimmer-dsl-for-libui-024)
195
201
  - [Glimmer GUI DSL Concepts](#glimmer-gui-dsl-concepts)
196
202
  - [Usage](#usage)
197
203
  - [Girb (Glimmer IRB)](#girb-glimmer-irb)
@@ -199,6 +205,7 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
199
205
  - [Supported Controls](#supported-controls)
200
206
  - [Common Control Properties](#common-control-properties)
201
207
  - [Common Control Operations](#common-control-operations)
208
+ - [LibUI Operations](#libui-operations)
202
209
  - [Extra Dialogs](#extra-dialogs)
203
210
  - [Extra Operations](#extra-operations)
204
211
  - [Table API](#table-api)
@@ -235,6 +242,10 @@ Other [Glimmer](https://rubygems.org/gems/glimmer) DSL gems you might be interes
235
242
  - [Histogram](#histogram)
236
243
  - [Basic Transform](#basic-transform)
237
244
  - [Login](#login)
245
+ - [Timer](#timer)
246
+ - [Color The Circles](#color-the-circles)
247
+ - [Basic Draw Text](#basic-draw-text)
248
+ - [Custom Draw Text](#custom-draw-text)
238
249
  - [Contributing to glimmer-dsl-libui](#contributing-to-glimmer-dsl-libui)
239
250
  - [Help](#help)
240
251
  - [Issues](#issues)
@@ -322,7 +333,7 @@ gem install glimmer-dsl-libui
322
333
  Or install via Bundler `Gemfile`:
323
334
 
324
335
  ```ruby
325
- gem 'glimmer-dsl-libui', '~> 0.1.10'
336
+ gem 'glimmer-dsl-libui', '~> 0.2.4'
326
337
  ```
327
338
 
328
339
  Add `require 'glimmer-dsl-libui'` at the top, and then `include Glimmer` into the top-level main object for testing or into an actual class for serious usage.
@@ -475,6 +486,13 @@ Control(Args) | Properties | Listeners
475
486
  - `hide`
476
487
  - `show`
477
488
 
489
+ ### LibUI Operations
490
+
491
+ All operations that could normally be called on `LibUI` can also be called on `Glimmer::LibUI`, but some have enhancements as detailed below.
492
+
493
+ - `Glimmer::LibUI::queue_main(&block)`: queues an operation to be run on the main event loop at the earliest opportunity possible
494
+ - `Glimmer::LibUI::timer(time_in_seconds=0.1, repeat: true, &block)`: calls block after time_in_seconds has elapsed, repeating indefinitely unless repeat is `false` or an `Integer` for finite number of repeats. Block can return `false` or `true` to override next repetition.
495
+
478
496
  ### Extra Dialogs
479
497
 
480
498
  - `open_file(window as Glimmer::LibUI::WindowProxy)`: returns selected file (`String`) or `nil` if cancelled
@@ -766,6 +784,8 @@ Note that `area`, `path`, and nested shapes are all truly declarative, meaning t
766
784
 
767
785
  `fill` and `stroke` accept [X11](https://en.wikipedia.org/wiki/X11_color_names) color `Symbol`s/`String`s like `:skyblue` and `'sandybrown'` or 6-number hex or 3-number hex-shorthand (as `Integer` or `String` with or without `0x` prefix)
768
786
 
787
+ Available [X11](https://en.wikipedia.org/wiki/X11_color_names) colors can be obtained through `Glimmer::LibUI.x11_colors` method.
788
+
769
789
  Check [Basic Transform](#basic-transform) example for use of [X11](https://en.wikipedia.org/wiki/X11_color_names) colors.
770
790
 
771
791
  Check [Histogram](#histogram) example for use of hex colors.
@@ -1258,9 +1278,7 @@ window('Notepad', 500, 300) {
1258
1278
 
1259
1279
  ### Midi Player
1260
1280
 
1261
- This example has prerequisites:
1262
- - 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).
1263
- - Add `*.mid` files to `~/Music` directory (you may copy the ones included in [sounds](sounds) directory)
1281
+ 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).
1264
1282
 
1265
1283
  [examples/midi_player.rb](examples/midi_player.rb)
1266
1284
 
@@ -1399,7 +1417,7 @@ class TinyMidiPlayer
1399
1417
 
1400
1418
  def initialize
1401
1419
  @pid = nil
1402
- @music_directory = File.expand_path(ARGV[0] || '~/Music/')
1420
+ @music_directory = File.expand_path('../sounds', __dir__)
1403
1421
  @midi_files = Dir.glob(File.join(@music_directory, '**/*.mid'))
1404
1422
  .sort_by { |path| File.basename(path) }
1405
1423
  at_exit { stop_midi }
@@ -1438,7 +1456,7 @@ class TinyMidiPlayer
1438
1456
  end
1439
1457
 
1440
1458
  def create_gui
1441
- menu('Help') { |m|
1459
+ menu('Help') {
1442
1460
  menu_item('Version') {
1443
1461
  on_clicked do
1444
1462
  show_version
@@ -3523,11 +3541,17 @@ window('Area Gallery', 400, 400) {
3523
3541
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3524
3542
  }
3525
3543
  path { # declarative stable path
3526
- arc(200, 200, 90, 0, 360, false)
3544
+ circle(200, 200, 90)
3527
3545
 
3528
3546
  fill r: 202, g: 102, b: 204, a: 0.5
3529
3547
  stroke r: 0, g: 0, b: 0, thickness: 2
3530
3548
  }
3549
+ path { # declarative stable path
3550
+ arc(400, 220, 180, 90, 90, false)
3551
+
3552
+ fill r: 204, g: 102, b: 204, a: 0.5
3553
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3554
+ }
3531
3555
 
3532
3556
  on_mouse_event do |area_mouse_event|
3533
3557
  p area_mouse_event
@@ -3680,18 +3704,28 @@ window('Area Gallery', 400, 400) {
3680
3704
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3681
3705
  }
3682
3706
  path { # declarative stable path
3683
- arc {
3707
+ circle {
3684
3708
  x_center 200
3685
3709
  y_center 200
3686
3710
  radius 90
3687
- start_angle 0
3688
- sweep 360
3689
- is_negative false
3690
3711
  }
3691
3712
 
3692
3713
  fill r: 202, g: 102, b: 204, a: 0.5
3693
3714
  stroke r: 0, g: 0, b: 0, thickness: 2
3694
3715
  }
3716
+ path { # declarative stable path
3717
+ arc {
3718
+ x_center 400
3719
+ y_center 220
3720
+ radius 180
3721
+ start_angle 90
3722
+ sweep 90
3723
+ is_negative false
3724
+ }
3725
+
3726
+ fill r: 204, g: 102, b: 204, a: 0.5
3727
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3728
+ }
3695
3729
 
3696
3730
  on_mouse_event do |area_mouse_event|
3697
3731
  p area_mouse_event
@@ -3791,13 +3825,19 @@ window('Area Gallery', 400, 400) {
3791
3825
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3792
3826
  }
3793
3827
  path { # a dynamic path is added semi-declaratively inside on_draw block
3794
- arc(200, 200, 90, 0, 360, false)
3795
-
3828
+ circle(200, 200, 90)
3829
+
3796
3830
  fill r: 202, g: 102, b: 204, a: 0.5
3797
3831
  stroke r: 0, g: 0, b: 0, thickness: 2
3798
3832
  }
3833
+ path { # a dynamic path is added semi-declaratively inside on_draw block
3834
+ arc(400, 220, 180, 90, 90, false)
3835
+
3836
+ fill r: 204, g: 102, b: 204, a: 0.5
3837
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3838
+ }
3799
3839
  end
3800
-
3840
+
3801
3841
  on_mouse_event do |area_mouse_event|
3802
3842
  p area_mouse_event
3803
3843
  end
@@ -3950,20 +3990,30 @@ window('Area Gallery', 400, 400) {
3950
3990
  stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
3951
3991
  }
3952
3992
  path { # a dynamic path is added semi-declaratively inside on_draw block
3953
- arc {
3993
+ circle {
3954
3994
  x_center 200
3955
3995
  y_center 200
3956
3996
  radius 90
3957
- start_angle 0
3958
- sweep 360
3959
- is_negative false
3960
3997
  }
3961
-
3998
+
3962
3999
  fill r: 202, g: 102, b: 204, a: 0.5
3963
4000
  stroke r: 0, g: 0, b: 0, thickness: 2
3964
4001
  }
4002
+ path { # a dynamic path is added semi-declaratively inside on_draw block
4003
+ arc {
4004
+ x_center 400
4005
+ y_center 220
4006
+ radius 180
4007
+ start_angle 90
4008
+ sweep 90
4009
+ is_negative false
4010
+ }
4011
+
4012
+ fill r: 204, g: 102, b: 204, a: 0.5
4013
+ stroke r: 0, g: 0, b: 0, thickness: 2, dashes: [50, 10, 10, 10], dash_phase: -50.0
4014
+ }
3965
4015
  end
3966
-
4016
+
3967
4017
  on_mouse_event do |area_mouse_event|
3968
4018
  p area_mouse_event
3969
4019
  end
@@ -4486,6 +4536,908 @@ window('Login') {
4486
4536
  }.show
4487
4537
  ```
4488
4538
 
4539
+ ### Timer
4540
+
4541
+ 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).
4542
+
4543
+ [examples/timer.rb](examples/timer.rb)
4544
+
4545
+ Run with this command from the root of the project if you cloned the project:
4546
+
4547
+ ```
4548
+ ruby -r './lib/glimmer-dsl-libui' examples/timer.rb
4549
+ ```
4550
+
4551
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4552
+
4553
+ ```
4554
+ ruby -r glimmer-dsl-libui -e "require 'examples/timer'"
4555
+ ```
4556
+
4557
+ Mac
4558
+
4559
+ ![glimmer-dsl-libui-mac-timer.png](images/glimmer-dsl-libui-mac-timer.png)
4560
+ ![glimmer-dsl-libui-mac-timer-in-progress.png](images/glimmer-dsl-libui-mac-timer-in-progress.png)
4561
+
4562
+ Linux
4563
+
4564
+ ![glimmer-dsl-libui-linux-timer.png](images/glimmer-dsl-libui-linux-timer.png)
4565
+ ![glimmer-dsl-libui-linux-timer-in-progress.png](images/glimmer-dsl-libui-linux-timer-in-progress.png)
4566
+
4567
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4568
+
4569
+ ```ruby
4570
+ require 'glimmer-dsl-libui'
4571
+
4572
+ class Timer
4573
+ include Glimmer
4574
+
4575
+ SECOND_MAX = 59
4576
+ MINUTE_MAX = 59
4577
+ HOUR_MAX = 23
4578
+
4579
+ def initialize
4580
+ @pid = nil
4581
+ @alarm_file = File.expand_path('../sounds/AlanWalker-Faded.mid', __dir__)
4582
+ at_exit { stop_alarm }
4583
+ setup_timer
4584
+ create_gui
4585
+ end
4586
+
4587
+ def stop_alarm
4588
+ if @pid
4589
+ if @th.alive?
4590
+ Process.kill(:SIGKILL, @pid)
4591
+ @pid = nil
4592
+ else
4593
+ @pid = nil
4594
+ end
4595
+ end
4596
+ end
4597
+
4598
+ def play_alarm
4599
+ stop_alarm
4600
+ if @pid.nil?
4601
+ begin
4602
+ @pid = spawn "timidity -G 0.0-10.0 #{@alarm_file}"
4603
+ @th = Process.detach @pid
4604
+ rescue Errno::ENOENT
4605
+ warn 'Timidty++ not found. Please install Timidity++.'
4606
+ warn 'https://sourceforge.net/projects/timidity/'
4607
+ end
4608
+ end
4609
+ end
4610
+
4611
+ def setup_timer
4612
+ unless @setup_timer
4613
+ Glimmer::LibUI.timer(1) do
4614
+ if @started
4615
+ seconds = @sec_spinbox.value
4616
+ minutes = @min_spinbox.value
4617
+ hours = @hour_spinbox.value
4618
+ if seconds > 0
4619
+ @sec_spinbox.value = seconds -= 1
4620
+ end
4621
+ if seconds == 0
4622
+ if minutes > 0
4623
+ @min_spinbox.value = minutes -= 1
4624
+ @sec_spinbox.value = seconds = SECOND_MAX
4625
+ end
4626
+ if minutes == 0
4627
+ if hours > 0
4628
+ @hour_spinbox.value = hours -= 1
4629
+ @min_spinbox.value = minutes = MINUTE_MAX
4630
+ @sec_spinbox.value = seconds = SECOND_MAX
4631
+ end
4632
+ if hours == 0 && minutes == 0 && seconds == 0
4633
+ @start_button.enabled = true
4634
+ @stop_button.enabled = false
4635
+ @started = false
4636
+ unless @played
4637
+ play_alarm
4638
+ msg_box('Alarm', 'Countdown Is Finished!')
4639
+ @played = true
4640
+ end
4641
+ end
4642
+ end
4643
+ end
4644
+ end
4645
+ end
4646
+ @setup_timer = true
4647
+ end
4648
+ end
4649
+
4650
+ def create_gui
4651
+ window('Timer') {
4652
+ margined true
4653
+
4654
+ group('Countdown') {
4655
+ vertical_box {
4656
+ horizontal_box {
4657
+ @hour_spinbox = spinbox(0, HOUR_MAX) {
4658
+ stretchy false
4659
+ value 0
4660
+ }
4661
+ label(':') {
4662
+ stretchy false
4663
+ }
4664
+ @min_spinbox = spinbox(0, MINUTE_MAX) {
4665
+ stretchy false
4666
+ value 0
4667
+ }
4668
+ label(':') {
4669
+ stretchy false
4670
+ }
4671
+ @sec_spinbox = spinbox(0, SECOND_MAX) {
4672
+ stretchy false
4673
+ value 0
4674
+ }
4675
+ }
4676
+ horizontal_box {
4677
+ @start_button = button('Start') {
4678
+ on_clicked do
4679
+ @start_button.enabled = false
4680
+ @stop_button.enabled = true
4681
+ @started = true
4682
+ @played = false
4683
+ end
4684
+ }
4685
+
4686
+ @stop_button = button('Stop') {
4687
+ enabled false
4688
+
4689
+ on_clicked do
4690
+ @start_button.enabled = true
4691
+ @stop_button.enabled = false
4692
+ @started = false
4693
+ end
4694
+ }
4695
+ }
4696
+ }
4697
+ }
4698
+ }.show
4699
+ end
4700
+ end
4701
+
4702
+ Timer.new
4703
+ ```
4704
+
4705
+ ### Color The Circles
4706
+
4707
+ [examples/color_the_circles.rb](examples/color_the_circles.rb)
4708
+
4709
+ Run with this command from the root of the project if you cloned the project:
4710
+
4711
+ ```
4712
+ ruby -r './lib/glimmer-dsl-libui' examples/color_the_circles.rb
4713
+ ```
4714
+
4715
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4716
+
4717
+ ```
4718
+ ruby -r glimmer-dsl-libui -e "require 'examples/color_the_circles'"
4719
+ ```
4720
+
4721
+ Mac
4722
+
4723
+ ![glimmer-dsl-libui-mac-color-the-circles.png](images/glimmer-dsl-libui-mac-color-the-circles.png)
4724
+ ![glimmer-dsl-libui-mac-color-the-circles-lost.png](images/glimmer-dsl-libui-mac-color-the-circles-lost.png)
4725
+ ![glimmer-dsl-libui-mac-color-the-circles-won.png](images/glimmer-dsl-libui-mac-color-the-circles-won.png)
4726
+
4727
+ Linux
4728
+
4729
+ ![glimmer-dsl-libui-linux-color-the-circles.png](images/glimmer-dsl-libui-linux-color-the-circles.png)
4730
+ ![glimmer-dsl-libui-linux-color-the-circles-lost.png](images/glimmer-dsl-libui-linux-color-the-circles-lost.png)
4731
+ ![glimmer-dsl-libui-linux-color-the-circles-won.png](images/glimmer-dsl-libui-linux-color-the-circles-won.png)
4732
+
4733
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
4734
+
4735
+ ```ruby
4736
+ require 'glimmer-dsl-libui'
4737
+
4738
+ class ColorTheCircles
4739
+ include Glimmer
4740
+
4741
+ WINDOW_WIDTH = 800
4742
+ WINDOW_HEIGHT = 600
4743
+ CIRCLE_MIN_RADIUS = 15
4744
+ CIRCLE_MAX_RADIUS = 50
4745
+ MARGIN_WIDTH = 55
4746
+ MARGIN_HEIGHT = 155
4747
+ TIME_MAX_EASY = 4
4748
+ TIME_MAX_MEDIUM = 3
4749
+ TIME_MAX_HARD = 2
4750
+ TIME_MAX_INSANE = 1
4751
+
4752
+ attr_accessor :score
4753
+
4754
+ def initialize
4755
+ @circles_data = []
4756
+ @score = 0
4757
+ @time_max = TIME_MAX_HARD
4758
+ register_observers
4759
+ setup_circle_factory
4760
+ end
4761
+
4762
+ def register_observers
4763
+ observer = Glimmer::DataBinding::Observer.proc do |new_score|
4764
+ @score_label.text = new_score.to_s
4765
+ if new_score == -20
4766
+ msg_box('You Lost!', 'Sorry! Your score reached -20')
4767
+ restart_game
4768
+ elsif new_score == 0
4769
+ msg_box('You Won!', 'Congratulations! Your score reached 0')
4770
+ restart_game
4771
+ end
4772
+ end
4773
+ observer.observe(self, :score) # automatically enhances self to become Glimmer::DataBinding::ObservableModel and notify observer on score attribute changes
4774
+ end
4775
+
4776
+ def setup_circle_factory
4777
+ consumer = Proc.new do
4778
+ if @circles_data.empty?
4779
+ # start with 3 circles to make more challenging
4780
+ add_circle until @circles_data.size > 3
4781
+ else
4782
+ add_circle
4783
+ end
4784
+ delay = rand * @time_max
4785
+ Glimmer::LibUI.timer(delay, repeat: false, &consumer)
4786
+ end
4787
+ Glimmer::LibUI.queue_main(&consumer)
4788
+ end
4789
+
4790
+ def add_circle
4791
+ circle_x_center = rand * (WINDOW_WIDTH - MARGIN_WIDTH - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
4792
+ circle_y_center = rand * (WINDOW_HEIGHT - MARGIN_HEIGHT - CIRCLE_MAX_RADIUS) + CIRCLE_MAX_RADIUS
4793
+ circle_radius = rand * (CIRCLE_MAX_RADIUS - CIRCLE_MIN_RADIUS) + CIRCLE_MIN_RADIUS
4794
+ stroke_color = Glimmer::LibUI.x11_colors.sample
4795
+ @circles_data << {
4796
+ args: [circle_x_center, circle_y_center, circle_radius],
4797
+ fill: nil,
4798
+ stroke: stroke_color
4799
+ }
4800
+ @area.queue_redraw_all
4801
+ self.score -= 1 # notifies score observers automatically of change
4802
+ end
4803
+
4804
+ def restart_game
4805
+ @score = 0 # update variable directly to avoid notifying observers
4806
+ @circles_data.clear
4807
+ end
4808
+
4809
+ def launch
4810
+ menu('Actions') {
4811
+ menu_item('Restart') {
4812
+ on_clicked do
4813
+ restart_game
4814
+ end
4815
+ }
4816
+
4817
+ quit_menu_item
4818
+ }
4819
+
4820
+ menu('Difficulty') {
4821
+ radio_menu_item('Easy') {
4822
+ on_clicked do
4823
+ @time_max = TIME_MAX_EASY
4824
+ end
4825
+ }
4826
+
4827
+ radio_menu_item('Medium') {
4828
+ on_clicked do
4829
+ @time_max = TIME_MAX_MEDIUM
4830
+ end
4831
+ }
4832
+
4833
+ radio_menu_item('Hard') {
4834
+ checked true
4835
+
4836
+ on_clicked do
4837
+ @time_max = TIME_MAX_HARD
4838
+ end
4839
+ }
4840
+
4841
+ radio_menu_item('Insane') {
4842
+ on_clicked do
4843
+ @time_max = TIME_MAX_INSANE
4844
+ end
4845
+ }
4846
+ }
4847
+
4848
+ menu('Help') {
4849
+ menu_item('Instructions') {
4850
+ on_clicked do
4851
+ msg_box('Instructions', "Score goes down as circles are added.\nIf it reaches -20, you lose!\n\nClick circles to color and score!\nOnce score reaches 0, you win!\n\nBeware of concealed light-colored circles!\nThey are revealed once darker circles intersect them.\n\nThere are four levels of difficulty.\nChange via difficulty menu if the game gets too tough.")
4852
+ end
4853
+ }
4854
+ }
4855
+
4856
+ window('Color The Circles', WINDOW_WIDTH, WINDOW_HEIGHT) {
4857
+ margined true
4858
+
4859
+ grid {
4860
+ button('Restart') {
4861
+ left 0
4862
+ top 0
4863
+ halign :center
4864
+
4865
+ on_clicked do
4866
+ restart_game
4867
+ end
4868
+ }
4869
+
4870
+ label('Score goes down as circles are added. If it reaches -20, you lose!') {
4871
+ left 0
4872
+ top 1
4873
+ halign :center
4874
+ }
4875
+
4876
+ label('Click circles to color and score! Once score reaches 0, you win!') {
4877
+ left 0
4878
+ top 2
4879
+ halign :center
4880
+ }
4881
+
4882
+ horizontal_box {
4883
+ left 0
4884
+ top 3
4885
+ halign :center
4886
+
4887
+ label('Score:') {
4888
+ stretchy false
4889
+ }
4890
+
4891
+ @score_label = label(@score.to_s) {
4892
+ stretchy false
4893
+ }
4894
+ }
4895
+
4896
+ vertical_box {
4897
+ left 0
4898
+ top 4
4899
+ hexpand true
4900
+ vexpand true
4901
+ halign :fill
4902
+ valign :fill
4903
+
4904
+ @area = area {
4905
+ on_draw do |area_draw_params|
4906
+ path {
4907
+ rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
4908
+
4909
+ fill :white
4910
+ }
4911
+
4912
+ @circles_data.each do |circle_data|
4913
+ path {
4914
+ circle_data[:circle] = circle(*circle_data[:args])
4915
+
4916
+ fill circle_data[:fill]
4917
+ stroke circle_data[:stroke]
4918
+ }
4919
+ end
4920
+ end
4921
+
4922
+ on_mouse_down do |area_mouse_event|
4923
+ clicked_circle_data = @circles_data.find do |circle_data|
4924
+ circle_data[:fill].nil? && circle_data[:circle].include?(area_mouse_event[:x], area_mouse_event[:y])
4925
+ end
4926
+ if clicked_circle_data
4927
+ clicked_circle_data[:fill] = clicked_circle_data[:stroke]
4928
+ @area.queue_redraw_all
4929
+ self.score += 1 # notifies score observers automatically of change
4930
+ end
4931
+ end
4932
+ }
4933
+ }
4934
+
4935
+ }
4936
+ }.show
4937
+ end
4938
+ end
4939
+
4940
+ ColorTheCircles.new.launch
4941
+ ```
4942
+
4943
+ ### Basic Draw Text
4944
+
4945
+ [examples/basic_draw_text.rb](examples/basic_draw_text.rb)
4946
+
4947
+ Run with this command from the root of the project if you cloned the project:
4948
+
4949
+ ```
4950
+ ruby -r './lib/glimmer-dsl-libui' examples/basic_draw_text.rb
4951
+ ```
4952
+
4953
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
4954
+
4955
+ ```
4956
+ ruby -r glimmer-dsl-libui -e "require 'examples/basic_draw_text'"
4957
+ ```
4958
+
4959
+ Mac
4960
+
4961
+ ![glimmer-dsl-libui-mac-basic-draw-text.png](images/glimmer-dsl-libui-mac-basic-draw-text.png)
4962
+
4963
+ Linux
4964
+
4965
+ ![glimmer-dsl-libui-linux-basic-draw-text.png](images/glimmer-dsl-libui-linux-basic-draw-text.png)
4966
+
4967
+ [LibUI](https://github.com/kojix2/LibUI) Original Version:
4968
+
4969
+ ```ruby
4970
+ require 'libui'
4971
+
4972
+ UI = LibUI
4973
+
4974
+ UI.init
4975
+
4976
+ handler = UI::FFI::AreaHandler.malloc
4977
+ area = UI.new_area(handler)
4978
+
4979
+ # Michael Ende (1929-1995)
4980
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
4981
+ # The English version, translated by Ralph Manheim, was published in 1983.
4982
+
4983
+ TITLE = 'Michael Ende (1929-1995) The Neverending Story'
4984
+
4985
+ str1 = \
4986
+ ' At last Ygramul sensed that something was coming toward ' \
4987
+ 'her. With the speed of lightning, she turned about, confronting ' \
4988
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
4989
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. '
4990
+
4991
+ str2 = \
4992
+ ' A cry of fear escaped Bastian. '
4993
+
4994
+ str3 = \
4995
+ ' A cry of terror passed through the ravine and echoed from ' \
4996
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
4997
+ 'someone else had arrived, for that sound could not have been ' \
4998
+ 'made by the boy who stood there as though paralyzed with ' \
4999
+ 'horror. '
5000
+
5001
+ str4 = \
5002
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5003
+ "But that's not possible. "
5004
+
5005
+ str5 = \
5006
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5007
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5008
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5009
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5010
+ 'words. '
5011
+
5012
+ str = ''
5013
+ attr_str = UI.new_attributed_string(str)
5014
+
5015
+ def attr_str.append(what, color)
5016
+ case color
5017
+ when :red
5018
+ color_attribute = UI.new_color_attribute(0.0, 0.5, 0.0, 0.7)
5019
+ when :green
5020
+ color_attribute = UI.new_color_attribute(0.5, 0.0, 0.25, 0.7)
5021
+ end
5022
+ start = UI.attributed_string_len(self)
5023
+ UI.attributed_string_append_unattributed(self, what)
5024
+ UI.attributed_string_set_attribute(self, color_attribute, start, start + what.size)
5025
+ UI.attributed_string_append_unattributed(self, "\n\n")
5026
+ end
5027
+
5028
+ attr_str.append(str1, :green)
5029
+ attr_str.append(str2, :red)
5030
+ attr_str.append(str3, :green)
5031
+ attr_str.append(str4, :red)
5032
+ attr_str.append(str5, :green)
5033
+
5034
+ Georgia = 'Georgia'
5035
+
5036
+ handler_draw_event = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, adp|
5037
+ area_draw_params = UI::FFI::AreaDrawParams.new(adp)
5038
+ default_font = UI::FFI::FontDescriptor.malloc
5039
+ default_font.Family = Georgia
5040
+ default_font.Size = 13
5041
+ default_font.Weight = 500
5042
+ default_font.Italic = 0
5043
+ default_font.Stretch = 4
5044
+ params = UI::FFI::DrawTextLayoutParams.malloc
5045
+
5046
+ # UI.font_button_font(font_button, default_font)
5047
+ params.String = attr_str
5048
+ params.DefaultFont = default_font
5049
+ params.Width = area_draw_params.AreaWidth
5050
+ params.Align = 0
5051
+ text_layout = UI.draw_new_text_layout(params)
5052
+ UI.draw_text(area_draw_params.Context, text_layout, 0, 0)
5053
+ UI.draw_free_text_layout(text_layout)
5054
+ end
5055
+
5056
+ handler.Draw = handler_draw_event
5057
+ # Assigning to local variables
5058
+ # This is intended to protect Fiddle::Closure from garbage collection.
5059
+ handler.MouseEvent = (c1 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
5060
+ handler.MouseCrossed = (c2 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
5061
+ handler.DragBroken = (c3 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
5062
+ handler.KeyEvent = (c4 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
5063
+
5064
+ box = UI.new_vertical_box
5065
+ UI.box_set_padded(box, 1)
5066
+ UI.box_append(box, area, 1)
5067
+
5068
+ main_window = UI.new_window(TITLE, 600, 400, 1)
5069
+ UI.window_set_margined(main_window, 1)
5070
+ UI.window_set_child(main_window, box)
5071
+
5072
+ UI.window_on_closing(main_window) do
5073
+ UI.control_destroy(main_window)
5074
+ UI.quit
5075
+ 0
5076
+ end
5077
+ UI.control_show(main_window)
5078
+
5079
+ UI.main
5080
+ UI.quit
5081
+ ```
5082
+
5083
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5084
+
5085
+ ```ruby
5086
+ require 'glimmer-dsl-libui'
5087
+
5088
+ # Michael Ende (1929-1995)
5089
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
5090
+ # The English version, translated by Ralph Manheim, was published in 1983.
5091
+ class BasicDrawText
5092
+ include Glimmer
5093
+
5094
+ def alternating_color_string(initial: false, &block)
5095
+ @index = 0 if initial
5096
+ @index += 1
5097
+ string {
5098
+ if @index.odd?
5099
+ color r: 0.5, g: 0, b: 0.25, a: 0.7
5100
+ else
5101
+ color r: 0, g: 0.5, b: 0, a: 0.7
5102
+ end
5103
+
5104
+ block.call + "\n\n"
5105
+ }
5106
+ end
5107
+
5108
+ def launch
5109
+ window('Michael Ende (1929-1995) The Neverending Story', 600, 400) {
5110
+ margined true
5111
+
5112
+ area {
5113
+ text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
5114
+ # align :left # default alignment
5115
+ default_font family: 'Georgia', size: 13, weight: :medium, italic: :normal, stretch: :normal
5116
+
5117
+ alternating_color_string(initial: true) {
5118
+ ' At last Ygramul sensed that something was coming toward ' \
5119
+ 'her. With the speed of lightning, she turned about, confronting ' \
5120
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
5121
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. '
5122
+ }
5123
+ alternating_color_string {
5124
+ ' A cry of fear escaped Bastian. '
5125
+ }
5126
+ alternating_color_string {
5127
+ ' A cry of terror passed through the ravine and echoed from ' \
5128
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
5129
+ 'someone else had arrived, for that sound could not have been ' \
5130
+ 'made by the boy who stood there as though paralyzed with ' \
5131
+ 'horror. '
5132
+ }
5133
+ alternating_color_string {
5134
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5135
+ "But that's not possible. "
5136
+ }
5137
+ alternating_color_string {
5138
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5139
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5140
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5141
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5142
+ 'words. '
5143
+ }
5144
+ }
5145
+ }
5146
+ }.show
5147
+ end
5148
+ end
5149
+
5150
+ BasicDrawText.new.launch
5151
+ ```
5152
+
5153
+ [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2:
5154
+
5155
+ ```ruby
5156
+ require 'glimmer-dsl-libui'
5157
+
5158
+ # Michael Ende (1929-1995)
5159
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
5160
+ # The English version, translated by Ralph Manheim, was published in 1983.
5161
+ class BasicDrawText
5162
+ include Glimmer
5163
+
5164
+ def alternating_color_string(initial: false, &block)
5165
+ @index = 0 if initial
5166
+ @index += 1
5167
+ string {
5168
+ if @index.odd?
5169
+ color r: 0.5, g: 0, b: 0.25, a: 0.7
5170
+ else
5171
+ color r: 0, g: 0.5, b: 0, a: 0.7
5172
+ end
5173
+
5174
+ block.call + "\n\n"
5175
+ }
5176
+ end
5177
+
5178
+ def launch
5179
+ window('Michael Ende (1929-1995) The Neverending Story', 600, 400) {
5180
+ margined true
5181
+
5182
+ area {
5183
+ on_draw do |area_draw_params|
5184
+ text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
5185
+ # align :left # default alignment
5186
+ default_font family: 'Georgia', size: 13, weight: :medium, italic: :normal, stretch: :normal
5187
+
5188
+ alternating_color_string(initial: true) {
5189
+ ' At last Ygramul sensed that something was coming toward ' \
5190
+ 'her. With the speed of lightning, she turned about, confronting ' \
5191
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
5192
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. '
5193
+ }
5194
+ alternating_color_string {
5195
+ ' A cry of fear escaped Bastian. '
5196
+ }
5197
+ alternating_color_string {
5198
+ ' A cry of terror passed through the ravine and echoed from ' \
5199
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
5200
+ 'someone else had arrived, for that sound could not have been ' \
5201
+ 'made by the boy who stood there as though paralyzed with ' \
5202
+ 'horror. '
5203
+ }
5204
+ alternating_color_string {
5205
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5206
+ "But that's not possible. "
5207
+ }
5208
+ alternating_color_string {
5209
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5210
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5211
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5212
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5213
+ 'words. '
5214
+ }
5215
+ }
5216
+ end
5217
+ }
5218
+ }.show
5219
+ end
5220
+ end
5221
+
5222
+ BasicDrawText.new.launch
5223
+ ```
5224
+
5225
+ ### Custom Draw Text
5226
+
5227
+ [examples/custom_draw_text.rb](examples/custom_draw_text.rb)
5228
+
5229
+ Run with this command from the root of the project if you cloned the project:
5230
+
5231
+ ```
5232
+ ruby -r './lib/glimmer-dsl-libui' examples/custom_draw_text.rb
5233
+ ```
5234
+
5235
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
5236
+
5237
+ ```
5238
+ ruby -r glimmer-dsl-libui -e "require 'examples/custom_draw_text'"
5239
+ ```
5240
+
5241
+ Mac
5242
+
5243
+ ![glimmer-dsl-libui-mac-custom-draw-text.png](images/glimmer-dsl-libui-mac-custom-draw-text.png)
5244
+ ![glimmer-dsl-libui-mac-custom-draw-text-changed.png](images/glimmer-dsl-libui-mac-custom-draw-text-changed.png)
5245
+
5246
+ Linux
5247
+
5248
+ ![glimmer-dsl-libui-linux-custom-draw-text.png](images/glimmer-dsl-libui-linux-custom-draw-text.png)
5249
+ ![glimmer-dsl-libui-linux-custom-draw-text-changed.png](images/glimmer-dsl-libui-linux-custom-draw-text-changed.png)
5250
+
5251
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
5252
+
5253
+ ```ruby
5254
+ require 'glimmer-dsl-libui'
5255
+
5256
+ # Michael Ende (1929-1995)
5257
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
5258
+ # The English version, translated by Ralph Manheim, was published in 1983.
5259
+ class CustomDrawText
5260
+ include Glimmer
5261
+
5262
+ def launch
5263
+ window('Michael Ende (1929-1995) The Neverending Story', 600, 400) {
5264
+ margined true
5265
+
5266
+ vertical_box {
5267
+ form {
5268
+ stretchy false
5269
+
5270
+ font_button { |fb|
5271
+ label 'Font'
5272
+
5273
+ on_changed do
5274
+ @string.font = fb.font
5275
+ end
5276
+ }
5277
+ color_button { |cb|
5278
+ label 'Color'
5279
+
5280
+ on_changed do
5281
+ @string.color = cb.color
5282
+ end
5283
+ }
5284
+ color_button { |cb|
5285
+ label 'Background'
5286
+
5287
+ on_changed do
5288
+ @string.background = cb.color
5289
+ end
5290
+ }
5291
+ combobox { |c|
5292
+ label 'Underline'
5293
+ items Glimmer::LibUI.enum_symbols(:underline).map(&:to_s).map {|word| word.split('_').map(&:capitalize).join(' ')}
5294
+ selected 'None'
5295
+
5296
+ on_selected do
5297
+ @string.underline = c.selected_item.underscore
5298
+ end
5299
+ }
5300
+ }
5301
+
5302
+ area {
5303
+ text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
5304
+ # align :left # default alignment
5305
+
5306
+ @string = string {
5307
+ ' At last Ygramul sensed that something was coming toward ' \
5308
+ 'her. With the speed of lightning, she turned about, confronting ' \
5309
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
5310
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. ' \
5311
+ "\n\n" \
5312
+ ' A cry of fear escaped Bastian. ' \
5313
+ "\n\n" \
5314
+ ' A cry of terror passed through the ravine and echoed from ' \
5315
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
5316
+ 'someone else had arrived, for that sound could not have been ' \
5317
+ 'made by the boy who stood there as though paralyzed with ' \
5318
+ 'horror. ' \
5319
+ "\n\n" \
5320
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5321
+ "But that's not possible. " \
5322
+ "\n\n" \
5323
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5324
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5325
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5326
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5327
+ 'words. ' \
5328
+ "\n\n"
5329
+ }
5330
+ }
5331
+ }
5332
+ }
5333
+ }.show
5334
+ end
5335
+ end
5336
+
5337
+ CustomDrawText.new.launch
5338
+ ```
5339
+
5340
+ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version 2:
5341
+
5342
+ ```ruby
5343
+ require 'glimmer-dsl-libui'
5344
+
5345
+ # Michael Ende (1929-1995)
5346
+ # The Neverending Story is a fantasy novel by German writer Michael Ende,
5347
+ # The English version, translated by Ralph Manheim, was published in 1983.
5348
+ class CustomDrawText
5349
+ include Glimmer
5350
+
5351
+ def launch
5352
+ window('Michael Ende (1929-1995) The Neverending Story', 600, 400) {
5353
+ margined true
5354
+
5355
+ vertical_box {
5356
+ form {
5357
+ stretchy false
5358
+
5359
+ font_button { |fb|
5360
+ label 'Font'
5361
+
5362
+ on_changed do
5363
+ @font = fb.font
5364
+ @area.queue_redraw_all
5365
+ end
5366
+ }
5367
+ color_button { |cb|
5368
+ label 'Color'
5369
+
5370
+ on_changed do
5371
+ @color = cb.color
5372
+ @area.queue_redraw_all
5373
+ end
5374
+ }
5375
+ color_button { |cb|
5376
+ label 'Background'
5377
+
5378
+ on_changed do
5379
+ @background = cb.color
5380
+ @area.queue_redraw_all
5381
+ end
5382
+ }
5383
+ combobox { |c|
5384
+ label 'Underline'
5385
+ items Glimmer::LibUI.enum_symbols(:underline).map(&:to_s).map {|word| word.split('_').map(&:capitalize).join(' ')}
5386
+ selected 'None'
5387
+
5388
+ on_selected do
5389
+ @underline = c.selected_item.underscore
5390
+ @area.queue_redraw_all
5391
+ end
5392
+ }
5393
+ }
5394
+
5395
+ @area = area {
5396
+ on_draw do |area_draw_params|
5397
+ text { # default arguments for x, y, and width are (0, 0, area_draw_params[:area_width])
5398
+ # align :left # default alignment
5399
+
5400
+ @string = string {
5401
+ font @font unless @font.nil?
5402
+ color @color unless @color.nil?
5403
+ background @background unless @background.nil?
5404
+ underline @underline unless @underline.nil?
5405
+
5406
+ ' At last Ygramul sensed that something was coming toward ' \
5407
+ 'her. With the speed of lightning, she turned about, confronting ' \
5408
+ 'Atreyu with an enormous steel-blue face. Her single eye had a ' \
5409
+ 'vertical pupil, which stared at Atreyu with inconceivable malignancy. ' \
5410
+ "\n\n" \
5411
+ ' A cry of fear escaped Bastian. ' \
5412
+ "\n\n" \
5413
+ ' A cry of terror passed through the ravine and echoed from ' \
5414
+ 'side to side. Ygramul turned her eye to left and right, to see if ' \
5415
+ 'someone else had arrived, for that sound could not have been ' \
5416
+ 'made by the boy who stood there as though paralyzed with ' \
5417
+ 'horror. ' \
5418
+ "\n\n" \
5419
+ ' Could she have heard my cry? Bastion wondered in alarm. ' \
5420
+ "But that's not possible. " \
5421
+ "\n\n" \
5422
+ ' And then Atreyu heard Ygramuls voice. It was very high ' \
5423
+ 'and slightly hoarse, not at all the right kind of voice for that ' \
5424
+ 'enormous face. Her lips did not move as she spoke. It was the ' \
5425
+ 'buzzing of a great swarm of hornets that shaped itself into ' \
5426
+ 'words. ' \
5427
+ "\n\n"
5428
+ }
5429
+ }
5430
+ end
5431
+ }
5432
+ }
5433
+ }.show
5434
+ end
5435
+ end
5436
+
5437
+ CustomDrawText.new.launch
5438
+
5439
+ ```
5440
+
4489
5441
  ## Contributing to glimmer-dsl-libui
4490
5442
 
4491
5443
  - Check out the latest master to make sure the feature hasn't been