glimmer-dsl-libui 0.7.7 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc2c8178ea922168b2df15740230c175155c96313d847dd117137ba18eba0991
4
- data.tar.gz: 49cbaff89df8bb0d1099b6adf9f0dcdaefe05010c4ee7b295160b0f7fbe8bd66
3
+ metadata.gz: c03b331e5839b335cf2945538ed66eb6faf988f6ec6230f41abbb3f4fb918159
4
+ data.tar.gz: 7e2a9da5462232b63f28602811b81b15d28e20a10f322058513142e0a103ceb0
5
5
  SHA512:
6
- metadata.gz: 30c0cbe41224d6c18e32c204a9f8fab80330ce27d8174f3293399ea9aeb73474f2083145d361d708f062d7035762a4417a135be886dacc09785034ed2b8f0887
7
- data.tar.gz: 3f5adf2374b390a21f4e6e7c28003c0446d5fd2a1c810beaebedb820530f56f7415c4a44f91887056be8e7745bc6cc6cd9728c9833cbd7688886f1b3a293d355
6
+ metadata.gz: 8153d251be4588058275826d42a1eace167148a25a9b54b3906948438888532276773143b4f26b21ae7fab4cc071bca33f36da8db6627cef5c38bb6ce273e6e4
7
+ data.tar.gz: '0921ce14a33189f3145a72536c2feed44626a4ace129ea2978581d17514bcf048c1b70b58eb6a8fd7207b58b1bfd1d5cbb5c4bc6bc9ece7e3acc223d6e95d8d8'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.8.0
4
+
5
+ - Support `composite_shape` keyword (alias: `shape`) as aggregate (composite) shape that can have arbitrary shapes, text, transforms underneath, which inherit its `fill`/`stroke` colors and `transform`
6
+ - New `examples/basic_composite_shape.rb` with use of `shape` + drag and drop support for moving shapes and click support for changing shape colors
7
+ - Invert `Glimmer::LibUI::ControlProxy::KEYWORD_ALIASES` to enable adding multiple aliases per keyword
8
+ - Support `Glimmer::LibUI::Shape::KEYWORD_ALIASES` to enable adding multiple aliases per keyword
9
+ - Small update for `examples/button_counter.rb`
10
+
11
+ ## 0.7.8
12
+
13
+ - Upgrade to `perfect-shape` gem version 1.0.8 to fix a crashing issue in `examples/shape_coloring.rb`
14
+
3
15
  ## 0.7.7
4
16
 
5
17
  - Support ability for `area` `on_key_event`/`on_key_down`/`on_key_up` listeners to return a boolean value indicating whether they handled a key event or not in case some key events need to be left to other operating system key handlers like COMMAND+Q for the Mac quit menu item
data/README.md CHANGED
@@ -1,9 +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.7.7
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.8.0
2
2
  ## Prerequisite-Free Ruby Desktop Development GUI Library
3
3
  ### The Quickest Way From Zero To GUI
4
4
  [![Gem Version](https://badge.fury.io/rb/glimmer-dsl-libui.svg)](http://badge.fury.io/rb/glimmer-dsl-libui)
5
5
  [![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)
6
6
 
7
+ **[If You Liked Shoes, You'll Love Glimmer!](https://github.com/AndyObtiva/glimmer#faq)**
8
+
7
9
  (**[Fukuoka Ruby Award Competition 2022 Special Award Winner](https://andymaleh.blogspot.com/2022/02/glimmer-dsl-for-libui-wins-fukuoka-ruby.html)** [[Award Announcement]](http://www.digitalfukuoka.jp/topics/187?locale=ja))
8
10
 
9
11
  (**[***RubyConf 2022 Talk - Building Native GUI Apps in Ruby***](https://andymaleh.blogspot.com/2023/02/rubyconf-2022-talk-video-for-building.html)**)
@@ -335,19 +337,7 @@ Mac | Windows | Linux
335
337
 
336
338
  NOTE: [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) is regularly catching up with changes in the C [libui-ng](https://github.com/libui-ng/libui-ng) library API and in beta mode. The C [libui-ng](https://github.com/libui-ng/libui-ng) is still mid-alpha, which is why [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) cannot be declared v1.0.0 yet. 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.
337
339
 
338
- **[Glimmer](https://rubygems.org/gems/glimmer) DSL Comparison Table:**
339
- DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
340
- ----|-----------|---------|------------------|------|------|--------
341
- [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
342
- [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
343
- [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
344
- [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
345
- [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
346
- [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
347
- [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
348
- [Glimmer DSL for Swing (JRuby Swing Desktop Development GUI Library)](https://github.com/AndyObtiva/glimmer-dsl-swing) | Mac / Windows / Linux | No | Yes (Java2D) | Very Mature | Slow JRuby Startup Time / Heavy Memory Footprint / Widgets Do Not Look Native | Java / JRuby
349
- [Glimmer DSL for XML (& HTML)](https://github.com/AndyObtiva/glimmer-dsl-xml) | All Web Browsers | No | Yes (SVG) | Programmable / Lighter-weight Than Actual XML | XML Elements Are Sometimes Not Well-Named (Many Types of Input) | None
350
- [Glimmer DSL for CSS](https://github.com/AndyObtiva/glimmer-dsl-css) | All Web Browsers | No | Yes | Programmable | CSS Is Over-Engineered / Too Many Features To Learn | None
340
+ Learn more about the differences between various [Glimmer](https://github.com/AndyObtiva/glimmer) DSLs by looking at the **[Glimmer DSL Comparison Table](https://github.com/AndyObtiva/glimmer#glimmer-dsl-comparison-table)**.
351
341
 
352
342
  ## Table of Contents
353
343
 
@@ -373,6 +363,7 @@ DSL | Platforms | Native? | Vector Graphics? | Pros | Cons | Prereqs
373
363
  - [Area Listeners](#area-listeners)
374
364
  - [Area Methods/Attributes](#area-methods-attributes)
375
365
  - [Area Transform Matrix](#area-transform-matrix)
366
+ - [Area Composite Shape](#area-composite-shape)
376
367
  - [Area Animation](#area-animation)
377
368
  - [Smart Defaults and Conventions](#smart-defaults-and-conventions)
378
369
  - [Custom Keywords](#custom-keywords)
@@ -544,7 +535,7 @@ gem install glimmer-dsl-libui
544
535
  Or install via Bundler `Gemfile`:
545
536
 
546
537
  ```ruby
547
- gem 'glimmer-dsl-libui', '~> 0.7.7'
538
+ gem 'glimmer-dsl-libui', '~> 0.8.0'
548
539
  ```
549
540
 
550
541
  Test that installation worked by running the [Meta-Example](#examples):
@@ -773,7 +764,7 @@ Keyword(Args) | Properties | Listeners
773
764
 
774
765
  All operations that could normally be called on `LibUI` can also be called on `Glimmer::LibUI`, but some have enhancements as detailed below.
775
766
 
776
- - `Glimmer::LibUI::queue_main(&block)`: queues an operation to be run on the main event loop at the earliest opportunity possible
767
+ - `Glimmer::LibUI::queue_main(&block)`: queues an operation to be run on the main event loop at the earliest opportunity possible. When writing multi-threaded code, it is required to wrap all code interacting with GUI objects (like `window` or `button`) from another `Thread` with `Glimmer::LibUI::queue_main { ... }`. See [Glimmer Meta-Example](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/examples/meta_example.rb) for an example of using `Glimmer::LibUI::queue_main { ... }` inside another `Thread`.
777
768
  - `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.
778
769
 
779
770
  There are additional useful `Glimmer::LibUI` operations that are not found in `LibUI`, which mostly help if you would like to do advanced lower level [LibUI](https://github.com/kojix2/LibUI) programming:
@@ -1563,6 +1554,170 @@ You can set a `matrix`/`transform` on `area` directly to conveniently apply to a
1563
1554
 
1564
1555
  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.
1565
1556
 
1557
+ #### Area Composite Shape
1558
+
1559
+ If you would like to build a composite shape that contains smaller sub-shapes, which would all get treated as a single unit,
1560
+ you can use the `shape` (or `composite_shape`) keyword, and wrap all the sub-shapes within the composite `shape`.
1561
+
1562
+ If you specify the `fill`, `stroke`, and `transform` at the `shape` level, they will get inherited by all sub-shapes that do not
1563
+ specify values for `fill`, `stroke`, or `transform` (though if they do, they override their parent's value).
1564
+
1565
+ When you use the `include?(x, y)` or `contain?(x, y)` method on a composite `shape`, it automatically includes all its aggregated shapes
1566
+ in the inclusion or containment check using the corresponding [PerfectShape](https://github.com/AndyObtiva/perfect-shape) object.
1567
+
1568
+ Example of a `cube` method-based custom keyword built using the composite `shape` keyword:
1569
+
1570
+ ![glimmer-dsl-libui-mac-basic-composite-shape.gif](/images/glimmer-dsl-libui-mac-basic-composite-shape.gif)
1571
+
1572
+ ```ruby
1573
+ require 'glimmer-dsl-libui'
1574
+
1575
+ class BasicCompositeShape
1576
+ include Glimmer::LibUI::Application
1577
+
1578
+ body {
1579
+ window {
1580
+ title 'Basic Composite Shape'
1581
+ content_size 200, 225
1582
+
1583
+ @area = area {
1584
+ rectangle(0, 0, 200, 225) {
1585
+ fill :white
1586
+ }
1587
+
1588
+ 7.times do |n|
1589
+ x_location = (rand*125).to_i%200 + (rand*15).to_i
1590
+ y_location = (rand*125).to_i%200 + (rand*15).to_i
1591
+ shape_color = [rand*125 + 130, rand*125 + 130, rand*125 + 130]
1592
+ shape_size = 20+n
1593
+
1594
+ cube(
1595
+ location_x: x_location,
1596
+ location_y: y_location,
1597
+ rectangle_width: shape_size*2,
1598
+ rectangle_height: shape_size,
1599
+ cube_height: shape_size*2,
1600
+ background_color: shape_color,
1601
+ line_thickness: 2
1602
+ ) { |the_shape|
1603
+ on_mouse_up do |area_mouse_event|
1604
+ # Change color on mouse up without dragging
1605
+ if @drag_shape.nil?
1606
+ background_color = [rand(255), rand(255), rand(255)]
1607
+ the_shape.fill = background_color
1608
+ end
1609
+ end
1610
+
1611
+ on_mouse_drag_start do |area_mouse_event|
1612
+ @drag_shape = the_shape
1613
+ @drag_x = area_mouse_event[:x]
1614
+ @drag_y = area_mouse_event[:y]
1615
+ end
1616
+
1617
+ on_mouse_drag do |area_mouse_event|
1618
+ if @drag_shape && @drag_x && @drag_y
1619
+ drag_distance_width = area_mouse_event[:x] - @drag_x
1620
+ drag_distance_height = area_mouse_event[:y] - @drag_y
1621
+ @drag_shape.x += drag_distance_width
1622
+ @drag_shape.y += drag_distance_height
1623
+ @drag_x = area_mouse_event[:x]
1624
+ @drag_y = area_mouse_event[:y]
1625
+ end
1626
+ end
1627
+
1628
+ on_mouse_drop do |area_mouse_event|
1629
+ @drag_shape = nil
1630
+ @drag_x = nil
1631
+ @drag_y = nil
1632
+ end
1633
+ }
1634
+ end
1635
+
1636
+ # this general area on_mouse_drag listener is needed to ensure that dragging a shape
1637
+ # outside of its boundaries would still move the dragged shape
1638
+ on_mouse_drag do |area_mouse_event|
1639
+ if @drag_shape && @drag_x && @drag_y
1640
+ drag_distance_width = area_mouse_event[:x] - @drag_x
1641
+ drag_distance_height = area_mouse_event[:y] - @drag_y
1642
+ @drag_shape.x += drag_distance_width
1643
+ @drag_shape.y += drag_distance_height
1644
+ @drag_x = area_mouse_event[:x]
1645
+ @drag_y = area_mouse_event[:y]
1646
+ end
1647
+ end
1648
+
1649
+ on_mouse_drop do |area_mouse_event|
1650
+ @drag_shape = nil
1651
+ @drag_x = nil
1652
+ @drag_y = nil
1653
+ end
1654
+ }
1655
+ }
1656
+ }
1657
+
1658
+ # method-based custom shape using `shape` keyword as a composite shape containing nested shapes
1659
+ # that are declared with relative positioning
1660
+ def cube(location_x: 0,
1661
+ location_y: 0,
1662
+ rectangle_width: nil,
1663
+ rectangle_height: nil,
1664
+ cube_height: nil,
1665
+ background_color: :brown,
1666
+ line_thickness: 1,
1667
+ &content_block)
1668
+ default_size = 28
1669
+ rectangle_width ||= rectangle_height || cube_height || default_size
1670
+ rectangle_height ||= rectangle_width || cube_height || default_size
1671
+ cube_height ||= rectangle_width || rectangle_height || default_size
1672
+ foreground_color = [0, 0, 0, thickness: line_thickness]
1673
+
1674
+ # the shape keyword (alias for composite_shape) enables building a composite shape that is treated as one shape
1675
+ # like a cube containing polygons, a polyline, a rectangle, and a line
1676
+ # with the fill and stroke colors getting inherited by all children that do not specify them
1677
+ shape(location_x, location_y) { |the_shape|
1678
+ fill background_color
1679
+ stroke foreground_color
1680
+
1681
+ bottom = polygon(0, cube_height + rectangle_height / 2.0,
1682
+ rectangle_width / 2.0, cube_height,
1683
+ rectangle_width, cube_height + rectangle_height / 2.0,
1684
+ rectangle_width / 2.0, cube_height + rectangle_height) {
1685
+ # inherits fill property from parent shape if not set
1686
+ # inherits stroke property from parent shape if not set
1687
+ }
1688
+ body = rectangle(0, rectangle_height / 2.0, rectangle_width, cube_height) {
1689
+ # inherits fill property from parent shape if not set
1690
+ # stroke is overridden to ensure a different value from parent
1691
+ stroke thickness: 0
1692
+ }
1693
+ polyline(0, rectangle_height / 2.0 + cube_height,
1694
+ 0, rectangle_height / 2.0,
1695
+ rectangle_width, rectangle_height / 2.0,
1696
+ rectangle_width, rectangle_height / 2.0 + cube_height) {
1697
+ # inherits stroke property from parent shape if not set
1698
+ }
1699
+ top = polygon(0, rectangle_height / 2.0,
1700
+ rectangle_width / 2.0, 0,
1701
+ rectangle_width, rectangle_height / 2.0,
1702
+ rectangle_width / 2.0, rectangle_height) {
1703
+ # inherits fill property from parent shape if not set
1704
+ # inherits stroke property from parent shape if not set
1705
+ }
1706
+ line(rectangle_width / 2.0, cube_height + rectangle_height,
1707
+ rectangle_width / 2.0, rectangle_height) {
1708
+ # inherits stroke property from parent shape if not set
1709
+ }
1710
+
1711
+ content_block&.call(the_shape)
1712
+ }
1713
+ end
1714
+ end
1715
+
1716
+ BasicCompositeShape.launch
1717
+
1718
+
1719
+ ```
1720
+
1566
1721
  #### Area Animation
1567
1722
 
1568
1723
  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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.7
1
+ 0.8.0
@@ -25,6 +25,7 @@
25
25
  - [Basic Transform](#basic-transform)
26
26
  - [Basic Draw Text](#basic-draw-text)
27
27
  - [Basic Code Area](#basic-code-area)
28
+ - [Basic Composite Shape](#basic-composite-shape)
28
29
 
29
30
  ## Basic Window
30
31
 
@@ -2344,36 +2345,20 @@ Mac | Windows | Linux
2344
2345
 
2345
2346
  New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version:
2346
2347
 
2347
- ```ruby
2348
- require 'glimmer-dsl-libui'
2348
+ ## Basic Composite Shape
2349
2349
 
2350
- class BasicCodeArea
2351
- include Glimmer::LibUI::Application
2352
-
2353
- before_body do
2354
- @code = <<~CODE
2355
- # Greets target with greeting
2356
- def greet(greeting: 'Hello', target: 'World')
2357
-
2358
- puts "\#{greeting}, \#{target}!"
2359
- end
2360
-
2361
- greet
2362
- greet(target: 'Robert')
2363
- greet(greeting: 'Aloha')
2364
- greet(greeting: 'Aloha', target: 'Nancy')
2365
- greet(greeting: 'Howdy', target: 'Doodle')
2366
- CODE
2367
- end
2368
-
2369
- body {
2370
- window('Basic Code Area', 400, 300) {
2371
- margined true
2372
-
2373
- code_area(language: 'ruby', code: @code)
2374
- }
2375
- }
2376
- end
2350
+ [examples/basic_composite_shape.rb](/examples/basic_composite_shape.rb)
2351
+
2352
+ Run with this command from the root of the project if you cloned the project:
2377
2353
 
2378
- BasicCodeArea.launch
2379
2354
  ```
2355
+ ruby -r './lib/glimmer-dsl-libui' examples/basic_composite_shape.rb
2356
+ ```
2357
+
2358
+ Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
2359
+
2360
+ ```
2361
+ ruby -r glimmer-dsl-libui -e "require 'examples/basic_composite_shape.rb'"
2362
+ ```
2363
+
2364
+ ![glimmer-dsl-libui-mac-basic-composite-shape.gif](/images/glimmer-dsl-libui-mac-basic-composite-shape.gif)
@@ -0,0 +1,145 @@
1
+ require 'glimmer-dsl-libui'
2
+
3
+ class BasicCompositeShape
4
+ include Glimmer::LibUI::Application
5
+
6
+ body {
7
+ window {
8
+ title 'Basic Composite Shape'
9
+ content_size 200, 225
10
+
11
+ @area = area {
12
+ rectangle(0, 0, 200, 225) {
13
+ fill :white
14
+ }
15
+
16
+ 7.times do |n|
17
+ x_location = (rand*125).to_i%200 + (rand*15).to_i
18
+ y_location = (rand*125).to_i%200 + (rand*15).to_i
19
+ shape_color = [rand*125 + 130, rand*125 + 130, rand*125 + 130]
20
+ shape_size = 20+n
21
+
22
+ cube(
23
+ location_x: x_location,
24
+ location_y: y_location,
25
+ rectangle_width: shape_size*2,
26
+ rectangle_height: shape_size,
27
+ cube_height: shape_size*2,
28
+ background_color: shape_color,
29
+ line_thickness: 2
30
+ ) { |the_shape|
31
+ on_mouse_up do |area_mouse_event|
32
+ # Change color on mouse up without dragging
33
+ if @drag_shape.nil?
34
+ background_color = [rand(255), rand(255), rand(255)]
35
+ the_shape.fill = background_color
36
+ end
37
+ end
38
+
39
+ on_mouse_drag_start do |area_mouse_event|
40
+ @drag_shape = the_shape
41
+ @drag_x = area_mouse_event[:x]
42
+ @drag_y = area_mouse_event[:y]
43
+ end
44
+
45
+ on_mouse_drag do |area_mouse_event|
46
+ if @drag_shape && @drag_x && @drag_y
47
+ drag_distance_width = area_mouse_event[:x] - @drag_x
48
+ drag_distance_height = area_mouse_event[:y] - @drag_y
49
+ @drag_shape.x += drag_distance_width
50
+ @drag_shape.y += drag_distance_height
51
+ @drag_x = area_mouse_event[:x]
52
+ @drag_y = area_mouse_event[:y]
53
+ end
54
+ end
55
+
56
+ on_mouse_drop do |area_mouse_event|
57
+ @drag_shape = nil
58
+ @drag_x = nil
59
+ @drag_y = nil
60
+ end
61
+ }
62
+ end
63
+
64
+ # this general area on_mouse_drag listener is needed to ensure that dragging a shape
65
+ # outside of its boundaries would still move the dragged shape
66
+ on_mouse_drag do |area_mouse_event|
67
+ if @drag_shape && @drag_x && @drag_y
68
+ drag_distance_width = area_mouse_event[:x] - @drag_x
69
+ drag_distance_height = area_mouse_event[:y] - @drag_y
70
+ @drag_shape.x += drag_distance_width
71
+ @drag_shape.y += drag_distance_height
72
+ @drag_x = area_mouse_event[:x]
73
+ @drag_y = area_mouse_event[:y]
74
+ end
75
+ end
76
+
77
+ on_mouse_drop do |area_mouse_event|
78
+ @drag_shape = nil
79
+ @drag_x = nil
80
+ @drag_y = nil
81
+ end
82
+ }
83
+ }
84
+ }
85
+
86
+ # method-based custom shape using `shape` keyword as a composite shape containing nested shapes
87
+ # that are declared with relative positioning
88
+ def cube(location_x: 0,
89
+ location_y: 0,
90
+ rectangle_width: nil,
91
+ rectangle_height: nil,
92
+ cube_height: nil,
93
+ background_color: :brown,
94
+ line_thickness: 1,
95
+ &content_block)
96
+ default_size = 28
97
+ rectangle_width ||= rectangle_height || cube_height || default_size
98
+ rectangle_height ||= rectangle_width || cube_height || default_size
99
+ cube_height ||= rectangle_width || rectangle_height || default_size
100
+ foreground_color = [0, 0, 0, thickness: line_thickness]
101
+
102
+ # the shape keyword (alias for composite_shape) enables building a composite shape that is treated as one shape
103
+ # like a cube containing polygons, a polyline, a rectangle, and a line
104
+ # with the fill and stroke colors getting inherited by all children that do not specify them
105
+ shape(location_x, location_y) { |the_shape|
106
+ fill background_color
107
+ stroke foreground_color
108
+
109
+ bottom = polygon(0, cube_height + rectangle_height / 2.0,
110
+ rectangle_width / 2.0, cube_height,
111
+ rectangle_width, cube_height + rectangle_height / 2.0,
112
+ rectangle_width / 2.0, cube_height + rectangle_height) {
113
+ # inherits fill property from parent shape if not set
114
+ # inherits stroke property from parent shape if not set
115
+ }
116
+ body = rectangle(0, rectangle_height / 2.0, rectangle_width, cube_height) {
117
+ # inherits fill property from parent shape if not set
118
+ # stroke is overridden to ensure a different value from parent
119
+ stroke thickness: 0
120
+ }
121
+ polyline(0, rectangle_height / 2.0 + cube_height,
122
+ 0, rectangle_height / 2.0,
123
+ rectangle_width, rectangle_height / 2.0,
124
+ rectangle_width, rectangle_height / 2.0 + cube_height) {
125
+ # inherits stroke property from parent shape if not set
126
+ }
127
+ top = polygon(0, rectangle_height / 2.0,
128
+ rectangle_width / 2.0, 0,
129
+ rectangle_width, rectangle_height / 2.0,
130
+ rectangle_width / 2.0, rectangle_height) {
131
+ # inherits fill property from parent shape if not set
132
+ # inherits stroke property from parent shape if not set
133
+ }
134
+ line(rectangle_width / 2.0, cube_height + rectangle_height,
135
+ rectangle_width / 2.0, rectangle_height) {
136
+ # inherits stroke property from parent shape if not set
137
+ }
138
+
139
+ content_block&.call(the_shape)
140
+ }
141
+ end
142
+ end
143
+
144
+ BasicCompositeShape.launch
145
+
@@ -6,7 +6,7 @@ class ButtonCounter
6
6
  attr_accessor :count
7
7
 
8
8
  def initialize
9
- @count = 0
9
+ self.count = 0
10
10
  end
11
11
 
12
12
  def launch
Binary file
@@ -33,8 +33,7 @@ module Glimmer
33
33
  end
34
34
 
35
35
  def interpret(parent, keyword, *args, &block)
36
- @@inverted_keyword_aliases = Glimmer::LibUI::ControlProxy::KEYWORD_ALIASES.invert unless defined?(@@inverted_keyword_aliases)
37
- keyword = @@inverted_keyword_aliases[keyword] || keyword
36
+ keyword = Glimmer::LibUI::ControlProxy::KEYWORD_ALIASES[keyword] || keyword
38
37
  Glimmer::LibUI::ControlProxy.create(keyword, parent, args, &block)
39
38
  end
40
39
 
@@ -42,6 +42,7 @@ module Glimmer
42
42
  end
43
43
 
44
44
  def interpret(parent, keyword, *args, &block)
45
+ keyword = Glimmer::LibUI::Shape::KEYWORD_ALIASES[keyword] || keyword
45
46
  args = [args] if args.size > 1 && Glimmer::LibUI::Shape.shape_class(keyword).parameters.size == 1
46
47
  Glimmer::LibUI::Shape.create(keyword, parent, args, &block)
47
48
  end
@@ -129,6 +129,7 @@ module Glimmer
129
129
 
130
130
  def build_control
131
131
  @libui = ::LibUI::FFI::DrawMatrix.malloc
132
+ # TODO is there a way to free the memory allocated for this? Or does it get garbage collected automatically?
132
133
  if @args.empty?
133
134
  set_identity
134
135
  else
@@ -167,9 +167,9 @@ module Glimmer
167
167
  end
168
168
 
169
169
  def perfect_shape
170
- perfect_shape_dependencies = [draw_fill_mode, children]
171
- if perfect_shape_dependencies != @perfect_shape_dependencies
172
- draw_fill_mode, children = @perfect_shape_dependencies = perfect_shape_dependencies
170
+ the_perfect_shape_dependencies = perfect_shape_dependencies
171
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
172
+ draw_fill_mode, _ = @perfect_shape_dependencies = the_perfect_shape_dependencies
173
173
  shapes = children.map(&:perfect_shape)
174
174
  new_shapes = []
175
175
  shapes.each do |shape|
@@ -190,6 +190,10 @@ module Glimmer
190
190
  @perfect_shape
191
191
  end
192
192
 
193
+ def perfect_shape_dependencies
194
+ [draw_fill_mode, children.map(&:perfect_shape_dependencies)]
195
+ end
196
+
193
197
  private
194
198
 
195
199
  def build_control
@@ -35,10 +35,10 @@ module Glimmer
35
35
  end
36
36
 
37
37
  def create(keyword, parent, args, &block)
38
- widget_proxy_class(keyword).new(keyword, parent, args, &block).tap {|c| control_proxies << c}
38
+ control_proxy_class(keyword).new(keyword, parent, args, &block).tap {|c| control_proxies << c}
39
39
  end
40
40
 
41
- def widget_proxy_class(keyword)
41
+ def control_proxy_class(keyword)
42
42
  descendant_keyword_constant_map[keyword] || ControlProxy
43
43
  end
44
44
 
@@ -96,7 +96,7 @@ module Glimmer
96
96
  private
97
97
 
98
98
  def add_aliases_to_keyword_constant_map(keyword_constant_map)
99
- KEYWORD_ALIASES.each do |keyword, alias_keyword|
99
+ KEYWORD_ALIASES.each do |alias_keyword, keyword|
100
100
  keyword_constant_map[alias_keyword] = keyword_constant_map[keyword]
101
101
  end
102
102
  keyword_constant_map
@@ -106,8 +106,8 @@ module Glimmer
106
106
  include DataBindable
107
107
 
108
108
  KEYWORD_ALIASES = {
109
- 'msg_box' => 'message_box',
110
- 'msg_box_error' => 'message_box_error',
109
+ 'message_box' => 'msg_box',
110
+ 'message_box_error' => 'msg_box_error',
111
111
  }
112
112
 
113
113
  BOOLEAN_PROPERTIES = %w[
@@ -8,7 +8,7 @@ module Glimmer
8
8
 
9
9
  def_delegators :perfect_shape,
10
10
  :min_x, :min_y, :max_x, :max_y, :center_point, :center_x, :center_y
11
-
11
+
12
12
  # Returns if shape contains point on the inside when outline is false (default)
13
13
  # or if point is on the outline when outline is true
14
14
  # distance_tolerance is used when outline is true to enable a fuzz factor in
@@ -22,6 +22,7 @@ module Glimmer
22
22
  # or if shape includes point on the outline when stroked
23
23
  def include?(*point)
24
24
  if fill.empty?
25
+ # TODO check if distance_tolerance should be half the thickness in case it is checked against both sides of out and in
25
26
  contain?(*point, outline: true, distance_tolerance: ((stroke[:thickness] || 1) - 1))
26
27
  else
27
28
  contain?(*point)
@@ -60,6 +61,13 @@ module Glimmer
60
61
  def perfect_shape
61
62
  # No Op
62
63
  end
64
+
65
+ # Returns PerfectShape object dependencies to determine if the PerfectShape
66
+ # object changed or not for caching purposes.
67
+ # Every shape/path implements this uniquely for its own PerfectShape attribute dependencies
68
+ def perfect_shape_dependencies
69
+ # No Op
70
+ end
63
71
  end
64
72
  end
65
73
  end
@@ -51,21 +51,25 @@ module Glimmer
51
51
  end
52
52
 
53
53
  def perfect_shape
54
- perfect_shape_dependencies = [x_center, y_center, radius, start_angle, sweep, is_negative]
55
- if perfect_shape_dependencies != @perfect_shape_dependencies
56
- x_center, y_center, radius, start_angle, sweep, is_negative = @perfect_shape_dependencies = perfect_shape_dependencies
54
+ the_perfect_shape_dependencies = perfect_shape_dependencies
55
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
56
+ absolute_x_center, absolute_y_center, radius, start_angle, sweep, is_negative = @perfect_shape_dependencies = the_perfect_shape_dependencies
57
57
  sign = is_negative ? 1 : -1
58
58
  start = is_negative ? (360 - start_angle) : -1*start_angle
59
59
  extent = is_negative ? (360 - sweep) : -1*sweep
60
60
  @perfect_shape = PerfectShape::Arc.new(
61
61
  type: :open,
62
- center_x: x_center, center_y: y_center,
62
+ center_x: absolute_x_center, center_y: absolute_y_center,
63
63
  radius_x: radius, radius_y: radius,
64
64
  start: start, extent: extent
65
65
  )
66
66
  end
67
67
  @perfect_shape
68
68
  end
69
+
70
+ def perfect_shape_dependencies
71
+ [absolute_x_center, absolute_y_center, radius, start_angle, sweep, is_negative]
72
+ end
69
73
  end
70
74
  end
71
75
  end
@@ -62,13 +62,17 @@ module Glimmer
62
62
  end
63
63
 
64
64
  def perfect_shape
65
- perfect_shape_dependencies = [x, y, c1_x, c1_y, c2_x, c2_y, end_x, end_y]
66
- if perfect_shape_dependencies != @perfect_shape_dependencies
67
- x, y, c1_x, c1_y, c2_x, c2_y, end_x, end_y = @perfect_shape_dependencies = perfect_shape_dependencies
68
- @perfect_shape = PerfectShape::CubicBezierCurve.new(points: [x, y, c1_x, c1_y, c2_x, c2_y, end_x, end_y].compact)
65
+ the_perfect_shape_dependencies = perfect_shape_dependencies
66
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
67
+ absolute_x, absolute_y, absolute_c1_x, absolute_c1_y, absolute_c2_x, absolute_c2_y, absolute_end_x, absolute_end_y = @perfect_shape_dependencies = the_perfect_shape_dependencies
68
+ @perfect_shape = PerfectShape::CubicBezierCurve.new(points: [absolute_x, absolute_y, absolute_c1_x, absolute_c1_y, absolute_c2_x, absolute_c2_y, absolute_end_x, absolute_end_y].compact)
69
69
  end
70
70
  @perfect_shape
71
71
  end
72
+
73
+ def perfect_shape_dependencies
74
+ [absolute_x, absolute_y, absolute_c1_x, absolute_c1_y, absolute_c2_x, absolute_c2_y, absolute_end_x, absolute_end_y]
75
+ end
72
76
  end
73
77
  end
74
78
  end
@@ -51,13 +51,17 @@ module Glimmer
51
51
  end
52
52
 
53
53
  def perfect_shape
54
- perfect_shape_dependencies = [x_center, y_center, radius]
55
- if perfect_shape_dependencies != @perfect_shape_dependencies
56
- x_center, y_center, radius = @perfect_shape_dependencies = perfect_shape_dependencies
57
- @perfect_shape = PerfectShape::Circle.new(center_x: x_center, center_y: y_center, radius: radius)
54
+ the_perfect_shape_dependencies = perfect_shape_dependencies
55
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
56
+ absolute_x_center, absolute_y_center, radius = @perfect_shape_dependencies = the_perfect_shape_dependencies
57
+ @perfect_shape = PerfectShape::Circle.new(center_x: absolute_x_center, center_y: absolute_y_center, radius: radius)
58
58
  end
59
59
  @perfect_shape
60
60
  end
61
+
62
+ def perfect_shape_dependencies
63
+ [absolute_x_center, absolute_y_center, radius]
64
+ end
61
65
  end
62
66
  end
63
67
  end
@@ -0,0 +1,101 @@
1
+ # Copyright (c) 2021-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/libui/shape'
23
+
24
+ module Glimmer
25
+ module LibUI
26
+ class Shape
27
+ class CompositeShape < Glimmer::LibUI::Shape
28
+ # TODO support nested shape properties that apply to all children
29
+ parameters :x, :y
30
+ parameter_defaults 0, 0
31
+
32
+ def draw(area_draw_params)
33
+ children.each do |child|
34
+ child_fill = child.fill
35
+ child_stroke = child.stroke
36
+ child_transform = child.transform
37
+ child.fill = fill if Glimmer::LibUI.blank_color?(child.fill)
38
+ child.stroke = stroke if Glimmer::LibUI.blank_color?(child.stroke)
39
+ child.transform = transform if child.transform.nil?
40
+ child.move_by(x, y)
41
+ begin
42
+ child.draw(area_draw_params)
43
+ rescue Exception => e
44
+ raise e
45
+ ensure
46
+ # restore original child attributes
47
+ child.move_by(-x, -y)
48
+ child.transform = child_transform
49
+ child.stroke = Glimmer::LibUI.blank_color?(child_stroke) ? Glimmer::LibUI.blank_color : child_stroke
50
+ child.fill = Glimmer::LibUI.blank_color?(child_fill) ? Glimmer::LibUI.blank_color : child_fill
51
+ end
52
+ end
53
+ super
54
+ end
55
+
56
+ def transform(matrix = nil)
57
+ if matrix.nil?
58
+ @matrix
59
+ else
60
+ @matrix = matrix
61
+ end
62
+ end
63
+
64
+ def move_by(x_delta, y_delta)
65
+ self.x += x_delta
66
+ self.y += y_delta
67
+ end
68
+
69
+ def contain?(*point, outline: false, distance_tolerance: 0)
70
+ children.any? { |child| child.contain?(*point, outline: outline, distance_tolerance: distance_tolerance) }
71
+ end
72
+
73
+ def include?(*point)
74
+ children.any? { |child| child.include?(*point) }
75
+ end
76
+
77
+ def relative_x(x)
78
+ self.x + x
79
+ end
80
+
81
+ def relative_y(y)
82
+ self.y + y
83
+ end
84
+
85
+ def relative_point(*point)
86
+ [relative_x(point.first), relative_y(point.last)]
87
+ end
88
+
89
+ def perfect_shape
90
+ perfect_shape_dependencies = [x, y, children.map(&:perfect_shape_dependencies)]
91
+ if perfect_shape_dependencies != @perfect_shape_dependencies
92
+ x, y, _ = @perfect_shape_dependencies = perfect_shape_dependencies
93
+ shapes = children.map(&:perfect_shape)
94
+ @perfect_shape = PerfectShape::CompositeShape.new(shapes: shapes)
95
+ end
96
+ @perfect_shape
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -58,10 +58,10 @@ module Glimmer
58
58
  end
59
59
 
60
60
  def perfect_shape
61
- perfect_shape_dependencies = [x, y, closed, parent.draw_fill_mode, children]
62
- if perfect_shape_dependencies != @perfect_shape_dependencies
63
- x, y, closed, draw_fill_mode, children = @perfect_shape_dependencies = perfect_shape_dependencies
64
- path_shapes = [[x, y]]
61
+ the_perfect_shape_dependencies = perfect_shape_dependencies
62
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
63
+ absolute_x, absolute_y, closed, draw_fill_mode, children = @perfect_shape_dependencies = the_perfect_shape_dependencies
64
+ path_shapes = [[absolute_x, absolute_y]]
65
65
  path_shapes += children.map(&:perfect_shape)
66
66
  winding_rule = draw_fill_mode == :winding ? :wind_non_zero : :wind_even_odd
67
67
  @perfect_shape = PerfectShape::Path.new(
@@ -73,6 +73,10 @@ module Glimmer
73
73
  end
74
74
  @perfect_shape
75
75
  end
76
+
77
+ def perfect_shape_dependencies
78
+ [absolute_x, absolute_y, closed, parent.draw_fill_mode, children]
79
+ end
76
80
  end
77
81
  end
78
82
  end
@@ -61,13 +61,17 @@ module Glimmer
61
61
  end
62
62
 
63
63
  def perfect_shape
64
- perfect_shape_dependencies = [x, y, end_x, end_y]
65
- if perfect_shape_dependencies != @perfect_shape_dependencies
66
- x, y, end_x, end_y = @perfect_shape_dependencies = perfect_shape_dependencies
67
- @perfect_shape = PerfectShape::Line.new(points: [x, y, end_x, end_y].compact)
64
+ the_perfect_shape_dependencies = perfect_shape_dependencies
65
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
66
+ absolute_x, absolute_y, absolute_end_x, absolute_end_y = @perfect_shape_dependencies = the_perfect_shape_dependencies
67
+ @perfect_shape = PerfectShape::Line.new(points: [absolute_x, absolute_y, absolute_end_x, absolute_end_y].compact)
68
68
  end
69
69
  @perfect_shape
70
70
  end
71
+
72
+ def perfect_shape_dependencies
73
+ [absolute_x, absolute_y, absolute_end_x, absolute_end_y]
74
+ end
71
75
  end
72
76
  end
73
77
  end
@@ -45,16 +45,20 @@ module Glimmer
45
45
  end
46
46
 
47
47
  def perfect_shape
48
- perfect_shape_dependencies = PerfectShape::MultiPoint.normalize_point_array(point_array)
49
- if perfect_shape_dependencies != @perfect_shape_dependencies
50
- point_array = @perfect_shape_dependencies = perfect_shape_dependencies
51
- path_shapes = [point_array.first]
52
- bezier_shape_points = point_array.drop(1).each.with_index.to_a.group_by {|pair| pair.last/3}.values.map {|arr| arr.map(&:first)}
48
+ the_perfect_shape_dependencies = perfect_shape_dependencies
49
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
50
+ absolute_point_array = @perfect_shape_dependencies = the_perfect_shape_dependencies
51
+ path_shapes = [absolute_point_array.first]
52
+ bezier_shape_points = absolute_point_array.drop(1).each.with_index.to_a.group_by {|pair| pair.last/3}.values.map {|arr| arr.map(&:first)}
53
53
  path_shapes += bezier_shape_points.map { |points| PerfectShape::CubicBezierCurve.new(points: points) }
54
54
  @perfect_shape = PerfectShape::Path.new(closed: false, shapes: path_shapes)
55
55
  end
56
56
  @perfect_shape
57
57
  end
58
+
59
+ def perfect_shape_dependencies
60
+ absolute_point_array
61
+ end
58
62
  end
59
63
  end
60
64
  end
@@ -46,15 +46,19 @@ module Glimmer
46
46
  end
47
47
 
48
48
  def perfect_shape
49
- perfect_shape_dependencies = PerfectShape::MultiPoint.normalize_point_array(point_array)
50
- if perfect_shape_dependencies != @perfect_shape_dependencies
51
- point_array = @perfect_shape_dependencies = perfect_shape_dependencies
52
- path_shapes = [point_array.first]
53
- path_shapes += point_array.drop(1).map { |point| PerfectShape::Line.new(points: [point]) }
49
+ the_perfect_shape_dependencies = perfect_shape_dependencies
50
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
51
+ absolute_point_array = @perfect_shape_dependencies = the_perfect_shape_dependencies
52
+ path_shapes = [absolute_point_array.first]
53
+ path_shapes += absolute_point_array.drop(1).map { |point| PerfectShape::Line.new(points: [point]) }
54
54
  @perfect_shape = PerfectShape::Path.new(closed: true, shapes: path_shapes)
55
55
  end
56
56
  @perfect_shape
57
57
  end
58
+
59
+ def perfect_shape_dependencies
60
+ absolute_point_array
61
+ end
58
62
  end
59
63
  end
60
64
  end
@@ -45,15 +45,19 @@ module Glimmer
45
45
  end
46
46
 
47
47
  def perfect_shape
48
- perfect_shape_dependencies = PerfectShape::MultiPoint.normalize_point_array(point_array)
49
- if perfect_shape_dependencies != @perfect_shape_dependencies
50
- point_array = @perfect_shape_dependencies = perfect_shape_dependencies
51
- path_shapes = [point_array.first]
52
- path_shapes += point_array.drop(1).map { |point| PerfectShape::Line.new(points: [point]) }
48
+ the_perfect_shape_dependencies = perfect_shape_dependencies
49
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
50
+ absolute_point_array = @perfect_shape_dependencies = the_perfect_shape_dependencies
51
+ path_shapes = [absolute_point_array.first]
52
+ path_shapes += absolute_point_array.drop(1).map { |point| PerfectShape::Line.new(points: [point]) }
53
53
  @perfect_shape = PerfectShape::Path.new(closed: false, shapes: path_shapes)
54
54
  end
55
55
  @perfect_shape
56
56
  end
57
+
58
+ def perfect_shape_dependencies
59
+ absolute_point_array
60
+ end
57
61
  end
58
62
  end
59
63
  end
@@ -39,13 +39,17 @@ module Glimmer
39
39
  end
40
40
 
41
41
  def perfect_shape
42
- perfect_shape_dependencies = [x, y, width, height]
43
- if perfect_shape_dependencies != @perfect_shape_dependencies
44
- x, y, width, height = @perfect_shape_dependencies = perfect_shape_dependencies
45
- @perfect_shape = PerfectShape::Rectangle.new(x: x, y: y, width: width, height: height)
42
+ the_perfect_shape_dependencies = perfect_shape_dependencies
43
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
44
+ absolute_x, absolute_y, width, height = @perfect_shape_dependencies = the_perfect_shape_dependencies
45
+ @perfect_shape = PerfectShape::Rectangle.new(x: absolute_x, y: absolute_y, width: width, height: height)
46
46
  end
47
47
  @perfect_shape
48
48
  end
49
+
50
+ def perfect_shape_dependencies
51
+ [absolute_x, absolute_y, width, height]
52
+ end
49
53
  end
50
54
  end
51
55
  end
@@ -41,13 +41,17 @@ module Glimmer
41
41
  end
42
42
 
43
43
  def perfect_shape
44
- perfect_shape_dependencies = [x, y, length]
45
- if perfect_shape_dependencies != @perfect_shape_dependencies
46
- @perfect_shape_dependencies = perfect_shape_dependencies
44
+ the_perfect_shape_dependencies = perfect_shape_dependencies
45
+ if the_perfect_shape_dependencies != @perfect_shape_dependencies
46
+ @perfect_shape_dependencies = the_perfect_shape_dependencies
47
47
  @perfect_shape = PerfectShape::Square.new(x: @perfect_shape_dependencies[0], y: @perfect_shape_dependencies[1], length: @perfect_shape_dependencies[2])
48
48
  end
49
49
  @perfect_shape
50
50
  end
51
+
52
+ def perfect_shape_dependencies
53
+ [absolute_x, absolute_y, length]
54
+ end
51
55
  end
52
56
  end
53
57
  end
@@ -31,16 +31,19 @@ module Glimmer
31
31
  class Shape
32
32
  class << self
33
33
  def exists?(keyword)
34
+ keyword = KEYWORD_ALIASES[keyword] || keyword
34
35
  Shape.constants.include?(constant_symbol(keyword)) and
35
36
  shape_class(keyword).respond_to?(:ancestors) and
36
37
  shape_class(keyword).ancestors.include?(Shape)
37
38
  end
38
39
 
39
40
  def create(keyword, parent, args, &block)
41
+ keyword = KEYWORD_ALIASES[keyword] || keyword
40
42
  shape_class(keyword).new(keyword, parent, args, &block)
41
43
  end
42
44
 
43
45
  def shape_class(keyword)
46
+ keyword = KEYWORD_ALIASES[keyword] || keyword
44
47
  Shape.const_get(constant_symbol(keyword))
45
48
  end
46
49
 
@@ -61,6 +64,7 @@ module Glimmer
61
64
  end
62
65
 
63
66
  def constant_symbol(keyword)
67
+ keyword = KEYWORD_ALIASES[keyword] || keyword
64
68
  "#{keyword.camelcase(:upper)}".to_sym
65
69
  end
66
70
  end
@@ -69,10 +73,15 @@ module Glimmer
69
73
  include PerfectShaped
70
74
  include DataBindable
71
75
 
76
+ KEYWORD_ALIASES = {
77
+ 'shape' => 'composite_shape',
78
+ }
79
+
72
80
  attr_reader :parent, :args, :keyword, :block, :content_added
73
81
  alias content_added? content_added
74
82
 
75
83
  def initialize(keyword, parent, args, &block)
84
+ keyword = KEYWORD_ALIASES[keyword] || keyword
76
85
  @keyword = keyword
77
86
  @parent = parent
78
87
  @args = args
@@ -129,6 +138,10 @@ module Glimmer
129
138
  def path_proxy
130
139
  find_parent_in_ancestors { |parent| parent.nil? || parent.is_a?(ControlProxy::PathProxy) }
131
140
  end
141
+
142
+ def composite_shape
143
+ find_parent_in_ancestors { |parent| parent.nil? || parent.is_a?(CompositeShape) }
144
+ end
132
145
 
133
146
  def fill(*args)
134
147
  path_proxy.fill(*args)
@@ -148,6 +161,125 @@ module Glimmer
148
161
  alias transform= transform
149
162
  alias set_transform transform
150
163
 
164
+ def absolute_x
165
+ if composite_shape
166
+ composite_shape.relative_x(x)
167
+ else
168
+ x
169
+ end
170
+ end
171
+
172
+ def absolute_y
173
+ if composite_shape
174
+ composite_shape.relative_y(y)
175
+ else
176
+ y
177
+ end
178
+ end
179
+
180
+ def absolute_point_array
181
+ # TODO Consider moving this method into a module mixed into all shapes having point_array
182
+ return unless respond_to?(:point_array)
183
+
184
+ point_array = self.point_array || []
185
+ point_array = PerfectShape::MultiPoint.normalize_point_array(point_array)
186
+ point_array.map do |point|
187
+ if composite_shape
188
+ composite_shape.relative_point(*point)
189
+ else
190
+ point
191
+ end
192
+ end
193
+ end
194
+
195
+ def absolute_x_center
196
+ # TODO Consider moving this method into a module mixed into all shapes having x_center
197
+ return unless respond_to?(:x_center)
198
+
199
+ if composite_shape
200
+ composite_shape.relative_x(x_center)
201
+ else
202
+ x_center
203
+ end
204
+ end
205
+
206
+ def absolute_y_center
207
+ # TODO Consider moving this method into a module mixed into all shapes having y_center
208
+ return unless respond_to?(:y_center)
209
+
210
+ if composite_shape
211
+ composite_shape.relative_y(y_center)
212
+ else
213
+ y_center
214
+ end
215
+ end
216
+
217
+ def absolute_c1_x
218
+ # TODO Consider moving this method into a module mixed into all shapes having c1_x
219
+ return unless respond_to?(:c1_x)
220
+
221
+ if composite_shape
222
+ composite_shape.relative_x(c1_x)
223
+ else
224
+ c1_x
225
+ end
226
+ end
227
+
228
+ def absolute_c1_y
229
+ # TODO Consider moving this method into a module mixed into all shapes having c1_y
230
+ return unless respond_to?(:c1_y)
231
+
232
+ if composite_shape
233
+ composite_shape.relative_x(c1_y)
234
+ else
235
+ c1_y
236
+ end
237
+ end
238
+
239
+ def absolute_c2_x
240
+ # TODO Consider moving this method into a module mixed into all shapes having c2_x
241
+ return unless respond_to?(:c2_x)
242
+
243
+ if composite_shape
244
+ composite_shape.relative_x(c2_x)
245
+ else
246
+ c2_x
247
+ end
248
+ end
249
+
250
+ def absolute_c2_y
251
+ # TODO Consider moving this method into a module mixed into all shapes having c2_y
252
+ return unless respond_to?(:c2_y)
253
+
254
+ if composite_shape
255
+ composite_shape.relative_x(c2_y)
256
+ else
257
+ c2_y
258
+ end
259
+ end
260
+
261
+ def absolute_end_x
262
+ # TODO Consider moving this method into a module mixed into all shapes having end_x
263
+ return unless respond_to?(:end_x)
264
+
265
+ if composite_shape
266
+ composite_shape.relative_x(end_x)
267
+ else
268
+ end_x
269
+ end
270
+ end
271
+
272
+ def absolute_end_y
273
+ # TODO Consider moving this method into a module mixed into all shapes having end_y
274
+ return unless respond_to?(:end_y)
275
+
276
+ if composite_shape
277
+ composite_shape.relative_x(end_y)
278
+ else
279
+ end_y
280
+ end
281
+ end
282
+
151
283
  def can_handle_listener?(listener_name)
152
284
  area_proxy.can_handle_listener?(listener_name)
153
285
  end
@@ -181,7 +313,16 @@ module Glimmer
181
313
  super
182
314
  end
183
315
  end
184
-
316
+
317
+ # indicates if nested directly under area or on_draw event (having an implicit path not an explicit path parent)
318
+ def implicit_path?
319
+ @implicit_path ||= !!(
320
+ @parent.is_a?(Glimmer::LibUI::ControlProxy::AreaProxy) ||
321
+ @parent.is_a?(Glimmer::LibUI::Shape::CompositeShape) ||
322
+ (@parent.nil? && Glimmer::LibUI::ControlProxy::AreaProxy.current_area_draw_params)
323
+ )
324
+ end
325
+
185
326
  private
186
327
 
187
328
  def build_control
@@ -189,11 +330,6 @@ module Glimmer
189
330
  @parent = Glimmer::LibUI::ControlProxy::PathProxy.new('path', @parent, [], &block)
190
331
  end
191
332
 
192
- # indicates if nested directly under area or on_draw event (having an implicit path not an explicit path parent)
193
- def implicit_path?
194
- @implicit_path ||= !!(@parent.is_a?(Glimmer::LibUI::ControlProxy::AreaProxy) || (@parent.nil? && Glimmer::LibUI::ControlProxy::AreaProxy.current_area_draw_params))
195
- end
196
-
197
333
  def dynamic?
198
334
  ((@parent.nil? || (@parent.is_a?(ControlProxy::PathProxy) && @parent.parent_proxy.nil?)) && Glimmer::LibUI::ControlProxy::AreaProxy.current_area_draw_params)
199
335
  end
data/lib/glimmer/libui.rb CHANGED
@@ -104,6 +104,21 @@ module Glimmer
104
104
  result.merge!(options) if options
105
105
  result
106
106
  end
107
+
108
+ # returns whether the value represents no color (blank) or a present color
109
+ # when a path is first built, it has a blank color
110
+ def blank_color?(value)
111
+ value.nil? ||
112
+ (value.respond_to?(:empty?) && value.empty?) ||
113
+ (value.is_a?(Array) && value.compact.empty?) ||
114
+ (value.is_a?(Hash) && value.values.compact.empty?)
115
+ end
116
+
117
+ # returns a representation of a blank color
118
+ # when a path is first built, it has a blank color
119
+ def blank_color
120
+ [{}]
121
+ end
107
122
 
108
123
  def hex_to_rgb(value)
109
124
  if value.is_a?(String)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer-dsl-libui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.7
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-15 00:00:00.000000000 Z
11
+ date: 2023-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: glimmer
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.0.7
33
+ version: 1.0.8
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.0.7
40
+ version: 1.0.8
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: super_module
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -301,7 +301,7 @@ description: Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development G
301
301
  syntax that visually maps to GUI control hierarchy, convention over configuration
302
302
  via smart defaults, automation of low-level details, requiring the least amount
303
303
  of syntax possible to build GUI, bidirectional data-binding, and custom keyword
304
- support.
304
+ support. If you liked Shoes, You'll love Glimmer!
305
305
  email: andy.am@gmail.com
306
306
  executables:
307
307
  - girb
@@ -331,6 +331,7 @@ files:
331
331
  - examples/basic_button.rb
332
332
  - examples/basic_child_window.rb
333
333
  - examples/basic_code_area.rb
334
+ - examples/basic_composite_shape.rb
334
335
  - examples/basic_draw_text.rb
335
336
  - examples/basic_draw_text2.rb
336
337
  - examples/basic_entry.rb
@@ -533,6 +534,7 @@ files:
533
534
  - lib/glimmer/libui/shape/arc.rb
534
535
  - lib/glimmer/libui/shape/bezier.rb
535
536
  - lib/glimmer/libui/shape/circle.rb
537
+ - lib/glimmer/libui/shape/composite_shape.rb
536
538
  - lib/glimmer/libui/shape/figure.rb
537
539
  - lib/glimmer/libui/shape/line.rb
538
540
  - lib/glimmer/libui/shape/polybezier.rb
@@ -567,7 +569,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
567
569
  - !ruby/object:Gem::Version
568
570
  version: '0'
569
571
  requirements: []
570
- rubygems_version: 3.3.1
572
+ rubygems_version: 3.2.22
571
573
  signing_key:
572
574
  specification_version: 4
573
575
  summary: Glimmer DSL for LibUI