glimmer-dsl-libui 0.9.7 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +392 -216
- data/VERSION +1 -1
- data/docs/examples/GLIMMER-DSL-LIBUI-ADVANCED-EXAMPLES.md +5 -5
- data/docs/examples/GLIMMER-DSL-LIBUI-BASIC-EXAMPLES.md +23 -0
- data/examples/basic_composite_shape.rb +2 -0
- data/examples/basic_custom_shape.rb +168 -0
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/dsl/libui/custom_shape_expression.rb +58 -0
- data/lib/glimmer/dsl/libui/dsl.rb +1 -0
- data/lib/glimmer/dsl/libui/listener_expression.rb +6 -1
- data/lib/glimmer/launcher.rb +2 -5
- data/lib/glimmer/libui/custom_control.rb +25 -19
- data/lib/glimmer/libui/custom_shape.rb +258 -0
- data/lib/glimmer/rake_task/scaffold.rb +1 -2
- data/lib/glimmer-dsl-libui.rb +1 -1
- metadata +5 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.0
|
@@ -1271,11 +1271,11 @@ window('Login') {
|
|
1271
1271
|
|
1272
1272
|
## Method-Based Custom Controls
|
1273
1273
|
|
1274
|
-
[Custom
|
1274
|
+
[Custom components](#custom-components) can be defined to represent custom controls (components) that provide new features or act as composites of existing controls that need to be reused multiple times in an application or across multiple applications. Custom components save a lot of development time, improving productivity and maintainability immensely.
|
1275
1275
|
|
1276
1276
|
This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom controls (keywords).
|
1277
1277
|
|
1278
|
-
The
|
1278
|
+
The Custom components are defined via methods (thus are "method-based").
|
1279
1279
|
|
1280
1280
|
[examples/method_based_custom_controls.rb](/examples/method_based_custom_controls.rb)
|
1281
1281
|
|
@@ -1494,11 +1494,11 @@ window('Method-Based Custom Controls') {
|
|
1494
1494
|
|
1495
1495
|
## Class-Based Custom Controls
|
1496
1496
|
|
1497
|
-
[Custom
|
1497
|
+
[Custom components](#custom-components) can be defined to represent custom controls (components) that provide new features or act as composites of existing controls that need to be reused multiple times in an application or across multiple applications. Custom components save a lot of development time, improving productivity and maintainability immensely.
|
1498
1498
|
|
1499
1499
|
This example defines `form_field`, `address_form`, `label_pair`, and `address` as custom controls (keywords).
|
1500
1500
|
|
1501
|
-
The
|
1501
|
+
The Custom components are defined via classes that include `Glimmer::LibUI::CustomControl` (thus are "class-based"), thus enabling offloading each custom control into its own file when needed for better code organization.
|
1502
1502
|
|
1503
1503
|
[examples/class_based_custom_controls.rb](/examples/class_based_custom_controls.rb)
|
1504
1504
|
|
@@ -1646,7 +1646,7 @@ ClassBasedCustomControls.launch
|
|
1646
1646
|
|
1647
1647
|
## Area-Based Custom Controls
|
1648
1648
|
|
1649
|
-
[Custom
|
1649
|
+
[Custom components](#custom-components) can be defined for graphical custom controls (components) built completely from scratch as vector-graphics on top of the [`area`](#area-api) control while leveraging keyboard and mouse listeners.
|
1650
1650
|
|
1651
1651
|
This example defines `text_label` and `push_button` as [`area`](#area-api)-based graphical custom controls that can have width, height, font, fill, stroke, border, and custom text location.
|
1652
1652
|
|
@@ -26,6 +26,7 @@
|
|
26
26
|
- [Basic Draw Text](#basic-draw-text)
|
27
27
|
- [Basic Code Area](#basic-code-area)
|
28
28
|
- [Basic Composite Shape](#basic-composite-shape)
|
29
|
+
- [Basic Custom Shape](#basic-custom-shape)
|
29
30
|
|
30
31
|
## Basic Window
|
31
32
|
|
@@ -2347,6 +2348,8 @@ New [Glimmer DSL for LibUI](https://rubygems.org/gems/glimmer-dsl-libui) Version
|
|
2347
2348
|
|
2348
2349
|
## Basic Composite Shape
|
2349
2350
|
|
2351
|
+
(this is the method-based custom shape version of Basic Custom Shape)
|
2352
|
+
|
2350
2353
|
[examples/basic_composite_shape.rb](/examples/basic_composite_shape.rb)
|
2351
2354
|
|
2352
2355
|
Run with this command from the root of the project if you cloned the project:
|
@@ -2362,3 +2365,23 @@ ruby -r glimmer-dsl-libui -e "require 'examples/basic_composite_shape.rb'"
|
|
2362
2365
|
```
|
2363
2366
|
|
2364
2367
|
![glimmer-dsl-libui-mac-basic-composite-shape.gif](/images/glimmer-dsl-libui-mac-basic-composite-shape.gif)
|
2368
|
+
|
2369
|
+
## Basic Custom Shape
|
2370
|
+
|
2371
|
+
(this is the class-based custom shape version of Basic Composite Shape)
|
2372
|
+
|
2373
|
+
[examples/basic_custom_shape.rb](/examples/basic_custom_shape.rb)
|
2374
|
+
|
2375
|
+
Run with this command from the root of the project if you cloned the project:
|
2376
|
+
|
2377
|
+
```
|
2378
|
+
ruby -r './lib/glimmer-dsl-libui' examples/basic_custom_shape.rb
|
2379
|
+
```
|
2380
|
+
|
2381
|
+
Run with this command if you installed the [Ruby gem](https://rubygems.org/gems/glimmer-dsl-libui):
|
2382
|
+
|
2383
|
+
```
|
2384
|
+
ruby -r glimmer-dsl-libui -e "require 'examples/basic_custom_shape.rb'"
|
2385
|
+
```
|
2386
|
+
|
2387
|
+
![glimmer-dsl-libui-mac-basic-custom-shape.gif](/images/glimmer-dsl-libui-mac-basic-composite-shape.gif)
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'glimmer-dsl-libui'
|
2
|
+
|
3
|
+
# This is the class-based custom shape version of basic_composite_shape
|
4
|
+
|
5
|
+
# class-based custom shape using Glimmer::LibUI::CustomShape mixin, which automatically
|
6
|
+
# augments the Glimmer GUI DSL with the underscored version of the class name: `cube`
|
7
|
+
# while accepting hash options matching the options declared on the class.
|
8
|
+
# (e.g. `cube(location_x: 50, location_y: 100)` )
|
9
|
+
class Cube
|
10
|
+
include Glimmer::LibUI::CustomShape
|
11
|
+
|
12
|
+
DEFAULT_SIZE = 28
|
13
|
+
|
14
|
+
option :location_x, default: 0
|
15
|
+
option :location_y, default: 0
|
16
|
+
option :rectangle_width, default: nil
|
17
|
+
option :rectangle_height, default: nil
|
18
|
+
option :cube_height, default: 75
|
19
|
+
option :background_color, default: :brown
|
20
|
+
option :foreground_color
|
21
|
+
option :line_thickness, default: 1
|
22
|
+
|
23
|
+
# The before_body block executes before building the body
|
24
|
+
before_body do
|
25
|
+
self.rectangle_width ||= rectangle_height || cube_height || DEFAULT_SIZE
|
26
|
+
self.rectangle_height ||= rectangle_width || cube_height || DEFAULT_SIZE
|
27
|
+
self.cube_height ||= rectangle_width || rectangle_height || DEFAULT_SIZE
|
28
|
+
if foreground_color
|
29
|
+
self.foreground_color = Glimmer::LibUI.interpret_color(foreground_color)
|
30
|
+
self.foreground_color[:thickness] ||= line_thickness
|
31
|
+
else
|
32
|
+
self.foreground_color = [0, 0, 0, thickness: line_thickness]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Optionally, after_body could be defined to perform operations after building the body
|
37
|
+
# like setting up observers.
|
38
|
+
#
|
39
|
+
# after_body do
|
40
|
+
# end
|
41
|
+
|
42
|
+
body {
|
43
|
+
# the shape keyword (alias for composite_shape) enables building a composite shape that is treated as one shape
|
44
|
+
# like a cube containing polygons, a polyline, a rectangle, and a line
|
45
|
+
# with the fill and stroke colors getting inherited by all children that do not specify them
|
46
|
+
shape(location_x, location_y) {
|
47
|
+
fill background_color
|
48
|
+
stroke foreground_color
|
49
|
+
|
50
|
+
bottom = polygon(0, cube_height + rectangle_height / 2.0,
|
51
|
+
rectangle_width / 2.0, cube_height,
|
52
|
+
rectangle_width, cube_height + rectangle_height / 2.0,
|
53
|
+
rectangle_width / 2.0, cube_height + rectangle_height) {
|
54
|
+
# inherits fill property from parent shape if not set
|
55
|
+
# inherits stroke property from parent shape if not set
|
56
|
+
}
|
57
|
+
body = rectangle(0, rectangle_height / 2.0, rectangle_width, cube_height) {
|
58
|
+
# inherits fill property from parent shape if not set
|
59
|
+
# stroke is overridden to ensure a different value from parent
|
60
|
+
stroke thickness: 0
|
61
|
+
}
|
62
|
+
polyline(0, rectangle_height / 2.0 + cube_height,
|
63
|
+
0, rectangle_height / 2.0,
|
64
|
+
rectangle_width, rectangle_height / 2.0,
|
65
|
+
rectangle_width, rectangle_height / 2.0 + cube_height) {
|
66
|
+
# inherits stroke property from parent shape if not set
|
67
|
+
}
|
68
|
+
top = polygon(0, rectangle_height / 2.0,
|
69
|
+
rectangle_width / 2.0, 0,
|
70
|
+
rectangle_width, rectangle_height / 2.0,
|
71
|
+
rectangle_width / 2.0, rectangle_height) {
|
72
|
+
# inherits fill property from parent shape if not set
|
73
|
+
# inherits stroke property from parent shape if not set
|
74
|
+
}
|
75
|
+
line(rectangle_width / 2.0, cube_height + rectangle_height,
|
76
|
+
rectangle_width / 2.0, rectangle_height) {
|
77
|
+
# inherits stroke property from parent shape if not set
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
class BasicCustomShape
|
84
|
+
include Glimmer::LibUI::Application
|
85
|
+
|
86
|
+
body {
|
87
|
+
window {
|
88
|
+
title 'Basic Custom Shape'
|
89
|
+
content_size 200, 225
|
90
|
+
|
91
|
+
@area = area {
|
92
|
+
rectangle(0, 0, 200, 225) {
|
93
|
+
fill :white
|
94
|
+
}
|
95
|
+
|
96
|
+
7.times do |n|
|
97
|
+
x_location = (rand*125).to_i%200 + (rand*15).to_i
|
98
|
+
y_location = (rand*125).to_i%200 + (rand*15).to_i
|
99
|
+
shape_color = [rand*125 + 130, rand*125 + 130, rand*125 + 130]
|
100
|
+
shape_size = 20+n
|
101
|
+
|
102
|
+
cube(
|
103
|
+
location_x: x_location,
|
104
|
+
location_y: y_location,
|
105
|
+
rectangle_width: shape_size*2,
|
106
|
+
rectangle_height: shape_size,
|
107
|
+
cube_height: shape_size*2,
|
108
|
+
background_color: shape_color,
|
109
|
+
line_thickness: 2
|
110
|
+
) { |the_shape|
|
111
|
+
on_mouse_up do |area_mouse_event|
|
112
|
+
# Change color on mouse up without dragging
|
113
|
+
if @drag_shape.nil?
|
114
|
+
background_color = [rand(255), rand(255), rand(255)]
|
115
|
+
the_shape.fill = background_color
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
on_mouse_drag_start do |area_mouse_event|
|
120
|
+
@drag_shape = the_shape
|
121
|
+
@drag_x = area_mouse_event[:x]
|
122
|
+
@drag_y = area_mouse_event[:y]
|
123
|
+
end
|
124
|
+
|
125
|
+
on_mouse_drag do |area_mouse_event|
|
126
|
+
if @drag_shape && @drag_x && @drag_y
|
127
|
+
drag_distance_width = area_mouse_event[:x] - @drag_x
|
128
|
+
drag_distance_height = area_mouse_event[:y] - @drag_y
|
129
|
+
@drag_shape.x += drag_distance_width
|
130
|
+
@drag_shape.y += drag_distance_height
|
131
|
+
@drag_x = area_mouse_event[:x]
|
132
|
+
@drag_y = area_mouse_event[:y]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
on_mouse_drop do |area_mouse_event|
|
137
|
+
@drag_shape = nil
|
138
|
+
@drag_x = nil
|
139
|
+
@drag_y = nil
|
140
|
+
end
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
# this general area on_mouse_drag listener is needed to ensure that dragging a shape
|
145
|
+
# outside of its boundaries would still move the dragged shape
|
146
|
+
on_mouse_drag do |area_mouse_event|
|
147
|
+
if @drag_shape && @drag_x && @drag_y
|
148
|
+
drag_distance_width = area_mouse_event[:x] - @drag_x
|
149
|
+
drag_distance_height = area_mouse_event[:y] - @drag_y
|
150
|
+
@drag_shape.x += drag_distance_width
|
151
|
+
@drag_shape.y += drag_distance_height
|
152
|
+
@drag_x = area_mouse_event[:x]
|
153
|
+
@drag_y = area_mouse_event[:y]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
on_mouse_drop do |area_mouse_event|
|
158
|
+
@drag_shape = nil
|
159
|
+
@drag_x = nil
|
160
|
+
@drag_y = nil
|
161
|
+
end
|
162
|
+
}
|
163
|
+
}
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
BasicCustomShape.launch
|
168
|
+
|
data/glimmer-dsl-libui.gemspec
CHANGED
Binary file
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright (c) 2021-2023 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'
|
23
|
+
require 'glimmer/dsl/expression'
|
24
|
+
require 'glimmer/dsl/parent_expression'
|
25
|
+
require 'glimmer/dsl/top_level_expression'
|
26
|
+
require 'glimmer/libui/custom_shape'
|
27
|
+
|
28
|
+
module Glimmer
|
29
|
+
module DSL
|
30
|
+
module Libui
|
31
|
+
class CustomShapeExpression < Expression
|
32
|
+
# TODO Consider making custom shapes automatically generate static expressions
|
33
|
+
include ParentExpression
|
34
|
+
include TopLevelExpression
|
35
|
+
|
36
|
+
def can_interpret?(parent, keyword, *args, &block)
|
37
|
+
LibUI::CustomShape.for(keyword)
|
38
|
+
end
|
39
|
+
|
40
|
+
def interpret(parent, keyword, *args, &block)
|
41
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
42
|
+
LibUI::CustomShape.for(keyword).new(keyword, parent, args, options, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_content(custom_shape, keyword, *args, &block)
|
46
|
+
options = args.last.is_a?(Hash) ? args.last : {post_add_content: true}
|
47
|
+
# TODO consider avoiding source_location
|
48
|
+
if block.source_location == custom_shape.content&.__getobj__&.source_location
|
49
|
+
custom_shape.content.call(custom_shape) unless custom_shape.content.called?
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
custom_shape.post_add_content if options[:post_add_content]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -27,7 +27,12 @@ module Glimmer
|
|
27
27
|
module Libui
|
28
28
|
class ListenerExpression < Expression
|
29
29
|
def can_interpret?(parent, keyword, *args, &block)
|
30
|
-
(
|
30
|
+
(
|
31
|
+
parent.is_a?(Glimmer::LibUI::ControlProxy) or
|
32
|
+
parent.is_a?(Glimmer::LibUI::Shape) or
|
33
|
+
parent.is_a?(Glimmer::LibUI::CustomControl) or
|
34
|
+
parent.is_a?(Glimmer::LibUI::CustomShape)
|
35
|
+
) and
|
31
36
|
block_given? and
|
32
37
|
parent.can_handle_listener?(keyword)
|
33
38
|
end
|
data/lib/glimmer/launcher.rb
CHANGED
@@ -100,11 +100,9 @@ module Glimmer
|
|
100
100
|
def launch(application, ruby_options: [], env_vars: {}, glimmer_options: {})
|
101
101
|
ruby_options_string = ruby_options.join(' ') + ' ' if ruby_options.any?
|
102
102
|
env_vars = env_vars.merge(glimmer_option_env_vars(glimmer_options))
|
103
|
-
env_vars
|
104
|
-
ENV[k] = v
|
105
|
-
end
|
103
|
+
load_env_vars(env_vars)
|
106
104
|
the_glimmer_lib = glimmer_lib
|
107
|
-
require 'puts_debuggerer' if the_glimmer_lib == GLIMMER_LIB_LOCAL
|
105
|
+
require 'puts_debuggerer' if (ENV['PD'] || ENV['pd']).to_s.downcase == 'true' || the_glimmer_lib == GLIMMER_LIB_LOCAL
|
108
106
|
is_rake_task = !application.end_with?('.rb')
|
109
107
|
rake_tasks = []
|
110
108
|
if is_rake_task
|
@@ -117,7 +115,6 @@ module Glimmer
|
|
117
115
|
rake_task_args = potential_rake_task_parts[2].split(',')
|
118
116
|
end
|
119
117
|
if rake_tasks.include?(application)
|
120
|
-
load_env_vars(glimmer_option_env_vars(glimmer_options))
|
121
118
|
rake_task = "glimmer:#{application}"
|
122
119
|
puts "Running Glimmer rake task: #{rake_task}" if ruby_options_string.to_s.include?('--debug')
|
123
120
|
Rake::Task[rake_task].invoke(*rake_task_args)
|
@@ -31,11 +31,34 @@ module Glimmer
|
|
31
31
|
module CustomControl
|
32
32
|
include SuperModule
|
33
33
|
include DataBinding::ObservableModel
|
34
|
-
|
34
|
+
|
35
|
+
# This module was only created to prevent Glimmer from checking method_missing first
|
36
|
+
module GlimmerSupersedable
|
37
|
+
def method_missing(method_name, *args, &block)
|
38
|
+
# TODO Consider supporting a glimmer error silencing option for methods defined here
|
39
|
+
# but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
|
40
|
+
if block && can_handle_listener?(method_name)
|
41
|
+
handle_listener(method_name, &block)
|
42
|
+
elsif @body_root.respond_to?(method_name, true)
|
43
|
+
@body_root.send(method_name, *args, &block)
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def respond_to?(method_name, *args, &block)
|
50
|
+
result = false
|
51
|
+
result ||= super
|
52
|
+
result ||= can_handle_listener?(method_name)
|
53
|
+
result ||= @body_root.respond_to?(method_name, *args, &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
35
57
|
super_module_included do |klass|
|
36
|
-
# TODO clear memoization of
|
58
|
+
# TODO clear memoization of ControlProxy.libui_class_for for a keyword if a custom control was defined with that keyword
|
37
59
|
unless klass.name.include?('Glimmer::LibUI::CustomWindow')
|
38
60
|
klass.include(Glimmer)
|
61
|
+
klass.include(GlimmerSupersedable) # prevent Glimmer from running method_missing first
|
39
62
|
Glimmer::LibUI::CustomControl.add_custom_control_namespaces_for(klass)
|
40
63
|
end
|
41
64
|
end
|
@@ -220,23 +243,6 @@ module Glimmer
|
|
220
243
|
@content
|
221
244
|
end
|
222
245
|
end
|
223
|
-
|
224
|
-
def method_missing(method_name, *args, &block)
|
225
|
-
# TODO Consider supporting a glimmer error silencing option for methods defined here
|
226
|
-
# but fail the glimmer DSL for the right reason to avoid seeing noise in the log output
|
227
|
-
if block && can_handle_listener?(method_name)
|
228
|
-
handle_listener(method_name, &block)
|
229
|
-
else
|
230
|
-
@body_root.send(method_name, *args, &block)
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
def respond_to?(method_name, *args, &block)
|
235
|
-
result = false
|
236
|
-
result ||= super
|
237
|
-
result ||= can_handle_listener?(method_name)
|
238
|
-
result ||= @body_root.respond_to?(method_name, *args, &block)
|
239
|
-
end
|
240
246
|
|
241
247
|
private
|
242
248
|
|