draw_color_repeat 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +556 -0
- data/VERSION +1 -0
- data/app/dcr.rb +51 -0
- data/app/dcr/launch.rb +3 -0
- data/app/models/dcr/command.rb +129 -0
- data/app/models/dcr/command/backward.rb +45 -0
- data/app/models/dcr/command/color.rb +120 -0
- data/app/models/dcr/command/empty.rb +30 -0
- data/app/models/dcr/command/forward.rb +47 -0
- data/app/models/dcr/command/left.rb +37 -0
- data/app/models/dcr/command/repeat.rb +42 -0
- data/app/models/dcr/command/right.rb +36 -0
- data/app/models/dcr/polygon.rb +37 -0
- data/app/models/dcr/program.rb +130 -0
- data/app/views/dcr/app_view.rb +128 -0
- data/app/views/dcr/compass.rb +56 -0
- data/bin/dcr +13 -0
- data/config/warble.rb +183 -0
- data/dcr.gemspec +0 -0
- data/lib/icon.rb +53 -0
- data/package/linux/Draw Color Repeat.png +0 -0
- data/package/macosx/Draw Color Repeat.icns +0 -0
- data/package/windows/Draw Color Repeat.ico +0 -0
- data/samples/aztec_pyramid.dcr +15 -0
- data/samples/bee_hive.dcr +15 -0
- data/samples/circle.dcr +4 -0
- data/samples/circle_of_circles.dcr +7 -0
- data/samples/envelope.dcr +14 -0
- data/samples/equilateral_triangle.dcr +6 -0
- data/samples/five_pointed_star.dcr +4 -0
- data/samples/house.dcr +25 -0
- data/samples/octagon.dcr +4 -0
- data/samples/octagon_of_octagons.dcr +6 -0
- data/samples/octagon_of_squares.dcr +7 -0
- data/samples/playing_cards.dcr +9 -0
- data/samples/rectangle.dcr +6 -0
- data/samples/sherrif_badge_star.dcr +8 -0
- data/samples/spider_web.dcr +6 -0
- data/samples/square.dcr +4 -0
- data/samples/stairs.dcr +16 -0
- data/samples/stick_figure.dcr +21 -0
- data/samples/sun.dcr +9 -0
- data/samples/swirl.dcr +7 -0
- data/samples/traffic_light.dcr +29 -0
- data/samples/triangle.dcr +5 -0
- data/vendor/jars/org/yaml/snakeyaml/1.28/snakeyaml-1.28.jar +0 -0
- metadata +207 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
# Copyright (c) 2021 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
|
+
class Dcr
|
23
|
+
class Command
|
24
|
+
class Repeat < Command
|
25
|
+
command_alias 'p'
|
26
|
+
|
27
|
+
def call
|
28
|
+
command_index_of_self = @program.expanded_commands.index(self)
|
29
|
+
return if command_index_of_self.nil?
|
30
|
+
commands_up_to_self = @program.expanded_commands[0...command_index_of_self]
|
31
|
+
last_empty_command = commands_up_to_self.reverse.detect {|command| command.is_a?(Empty)}
|
32
|
+
command_index_of_last_empty_command = @program.expanded_commands.index(last_empty_command).to_i # first index is 0 if no empty command is found
|
33
|
+
commands_after_empty_command_up_to_self = @program.expanded_commands[command_index_of_last_empty_command...command_index_of_self]
|
34
|
+
@program.expanded_commands[command_index_of_self..command_index_of_self] = commands_after_empty_command_up_to_self*value
|
35
|
+
end
|
36
|
+
|
37
|
+
def value
|
38
|
+
super.to_i == 0 ? 1 : super.to_i
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright (c) 2021 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
|
+
class Dcr
|
23
|
+
class Command
|
24
|
+
class Right < Command
|
25
|
+
command_alias 'r'
|
26
|
+
|
27
|
+
def call
|
28
|
+
@program.angle += value
|
29
|
+
end
|
30
|
+
|
31
|
+
def value
|
32
|
+
super.to_f == 0 ? 90.0 : super.to_f
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright (c) 2021 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 'equalizer'
|
23
|
+
|
24
|
+
class Dcr
|
25
|
+
class Polygon
|
26
|
+
ATTRIBUTES = [:point_array, :background]
|
27
|
+
|
28
|
+
include Equalizer.new(*ATTRIBUTES)
|
29
|
+
|
30
|
+
attr_accessor *ATTRIBUTES
|
31
|
+
|
32
|
+
def initialize(*point_array)
|
33
|
+
point_array = point_array.first if point_array.size == 1 && point_array.first.is_a?(Array)
|
34
|
+
@point_array = point_array
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Copyright (c) 2021 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_relative 'command'
|
23
|
+
require_relative 'polygon'
|
24
|
+
|
25
|
+
class Dcr
|
26
|
+
# A DCR program that takes text (representing commands) and board width/height for drawing polygons
|
27
|
+
# It has a current location (x/y) and angle. Angle is clockwise, with 0 being upward (north).
|
28
|
+
class Program
|
29
|
+
include Glimmer::DataBinding::ObservableModel
|
30
|
+
|
31
|
+
STICK_FIGURE_SIZE = 30
|
32
|
+
|
33
|
+
attr_accessor :text, :commands, :canvas_width, :canvas_height, :location_x, :location_y, :angle, :expanded_commands
|
34
|
+
|
35
|
+
# array of polygon objects including array of point arrays and color to be drawn/filled in GUI
|
36
|
+
attr_accessor :polygons
|
37
|
+
|
38
|
+
def initialize(text: '', canvas_width: 800, canvas_height: 600)
|
39
|
+
@commands = []
|
40
|
+
@canvas_width = canvas_width
|
41
|
+
@canvas_height = canvas_height
|
42
|
+
reset!
|
43
|
+
self.text = text
|
44
|
+
end
|
45
|
+
|
46
|
+
def canvas_width=(new_width)
|
47
|
+
@canvas_width = new_width
|
48
|
+
@last_expanded_commands = nil
|
49
|
+
calculate_polygons
|
50
|
+
end
|
51
|
+
|
52
|
+
def canvas_height=(new_height)
|
53
|
+
@canvas_height = new_height
|
54
|
+
@last_expanded_commands = nil
|
55
|
+
calculate_polygons
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def text=(value)
|
60
|
+
@text = value
|
61
|
+
parse_commands
|
62
|
+
end
|
63
|
+
|
64
|
+
def commands=(new_commands)
|
65
|
+
# TODO remove observers from old commands
|
66
|
+
@commands = new_commands
|
67
|
+
# TODO observe commands for fine grained changes and have this update "text" as a result (which indirectly fires a "commands" change)
|
68
|
+
calculate_polygons
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset!
|
72
|
+
reset_location!
|
73
|
+
reset_angle!
|
74
|
+
reset_next_color_index!
|
75
|
+
reset_polygons!
|
76
|
+
end
|
77
|
+
|
78
|
+
def reset_location!
|
79
|
+
# also set stick_figure_location_x and stick_figure_location_y, which is slightly different
|
80
|
+
self.location_x = (canvas_width - STICK_FIGURE_SIZE) / 2.0
|
81
|
+
self.location_y = (canvas_height - STICK_FIGURE_SIZE) / 2.0
|
82
|
+
end
|
83
|
+
|
84
|
+
# Resets angle (0 means upward / north). Angle value is clockwise.
|
85
|
+
def reset_angle!
|
86
|
+
self.angle = 0 # means pointing upward (north)
|
87
|
+
end
|
88
|
+
|
89
|
+
def reset_polygons!
|
90
|
+
# reset quietly via instance variable without alerting observers with attribute writer method
|
91
|
+
@polygons = [Polygon.new(location_x, location_y)]
|
92
|
+
end
|
93
|
+
|
94
|
+
def new_polygon!
|
95
|
+
@polygons << Polygon.new(location_x, location_y)
|
96
|
+
end
|
97
|
+
|
98
|
+
def reset_next_color_index!
|
99
|
+
Command::Color.reset_next_color_index!
|
100
|
+
end
|
101
|
+
|
102
|
+
def angle=(value)
|
103
|
+
@angle = value % 360
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def parse_commands
|
109
|
+
self.commands = text.split("\n").map do |command_text|
|
110
|
+
Command.create(program: self, text: command_text)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def calculate_polygons
|
115
|
+
reset!
|
116
|
+
expand_commands!
|
117
|
+
expanded_commands.each(&:call)
|
118
|
+
notify_observers(:polygons) # TODO do away with this using nested data-binding
|
119
|
+
end
|
120
|
+
|
121
|
+
# Calls repeat commands to expand other types of commands (repeat them)
|
122
|
+
def expand_commands!
|
123
|
+
self.expanded_commands = commands.dup
|
124
|
+
commands.each do |command|
|
125
|
+
command.call if command.is_a?(Command::Repeat)
|
126
|
+
end
|
127
|
+
self.expanded_commands.compact!
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'models/dcr/program'
|
2
|
+
|
3
|
+
require 'views/dcr/compass'
|
4
|
+
|
5
|
+
class Dcr
|
6
|
+
class AppView
|
7
|
+
include Glimmer::UI::CustomShell
|
8
|
+
|
9
|
+
TEXT_FONT_HEIGHT = 30
|
10
|
+
GUI_FONT_HEIGHT = TEXT_FONT_HEIGHT - 7
|
11
|
+
DEFAULT_CANVAS_WIDTH = 960
|
12
|
+
DEFAULT_CANVAS_HEIGHT = 464
|
13
|
+
|
14
|
+
option :program
|
15
|
+
|
16
|
+
before_body {
|
17
|
+
@command_composites = []
|
18
|
+
self.program = Program.new(canvas_width: DEFAULT_CANVAS_WIDTH, canvas_height: DEFAULT_CANVAS_HEIGHT)
|
19
|
+
Display.app_name = 'Draw Color Repeat'
|
20
|
+
Display.app_version = VERSION
|
21
|
+
@display = display {
|
22
|
+
on_about {
|
23
|
+
display_about_dialog
|
24
|
+
}
|
25
|
+
on_preferences {
|
26
|
+
display_about_dialog
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
after_body {
|
32
|
+
observe(self, 'program.polygons') do |new_polygons|
|
33
|
+
if new_polygons != @last_polygons
|
34
|
+
@polygon_container.shapes.dup.each(&:dispose)
|
35
|
+
@polygon_container.content {
|
36
|
+
new_polygons.each do |new_polygon|
|
37
|
+
if new_polygon.background.nil?
|
38
|
+
polyline(new_polygon.point_array) {
|
39
|
+
foreground :black
|
40
|
+
}
|
41
|
+
else
|
42
|
+
polygon(new_polygon.point_array) {
|
43
|
+
background new_polygon.background
|
44
|
+
}
|
45
|
+
polygon(new_polygon.point_array) {
|
46
|
+
foreground :black
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
}
|
51
|
+
@last_polygons = new_polygons
|
52
|
+
end
|
53
|
+
end
|
54
|
+
}
|
55
|
+
|
56
|
+
## Add widget content inside custom shell body
|
57
|
+
## Top-most widget must be a shell or another custom shell
|
58
|
+
#
|
59
|
+
body {
|
60
|
+
shell(:fill_screen, :no_resize) {
|
61
|
+
minimum_size DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT + 316
|
62
|
+
image File.join(APP_ROOT, 'package', 'windows', "Draw Color Repeat.ico")
|
63
|
+
text "Draw Color Repeat"
|
64
|
+
|
65
|
+
sash_form(:horizontal) {
|
66
|
+
weights 2, 4
|
67
|
+
sash_width 10
|
68
|
+
|
69
|
+
@program_text = code_text(lines: true) { |code_text_proxy|
|
70
|
+
font height: TEXT_FONT_HEIGHT, name: code_text_proxy.font.font_data[0].name
|
71
|
+
text <=> [self, 'program.text']
|
72
|
+
top_margin 0
|
73
|
+
bottom_margin 0
|
74
|
+
}
|
75
|
+
|
76
|
+
@canvas_container = scrolled_composite(:none) {
|
77
|
+
@canvas = canvas {
|
78
|
+
background :white
|
79
|
+
|
80
|
+
on_control_resized {
|
81
|
+
program.canvas_width = @canvas.bounds.width
|
82
|
+
program.canvas_height = @canvas.bounds.height
|
83
|
+
}
|
84
|
+
|
85
|
+
|
86
|
+
# This is where drawn shapes are added
|
87
|
+
@polygon_container = shape(0, 0, :max, :max)
|
88
|
+
|
89
|
+
# TODO show arrow pointing in the right direction (based on program.angle)
|
90
|
+
@stick_figure = stick_figure(
|
91
|
+
size: Program::STICK_FIGURE_SIZE,
|
92
|
+
) {
|
93
|
+
location_x <= [self, 'program.location_x', on_read: ->(x) {x - 6}]
|
94
|
+
location_y <= [self, 'program.location_y', on_read: ->(y) {y - 6}]
|
95
|
+
}
|
96
|
+
|
97
|
+
@compass = compass {
|
98
|
+
location_x <= [self, 'program.location_x']
|
99
|
+
location_y <= [self, 'program.location_y']
|
100
|
+
angle <= [self, 'program.angle']
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
menu_bar {
|
107
|
+
menu {
|
108
|
+
text '&File'
|
109
|
+
menu_item {
|
110
|
+
text '&About...'
|
111
|
+
on_widget_selected {
|
112
|
+
display_about_dialog
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
def display_about_dialog
|
121
|
+
message_box(body_root) {
|
122
|
+
text 'About'
|
123
|
+
message "The DCR Programming Language (Draw Color Repeat) #{VERSION}\n\n#{LICENSE}"
|
124
|
+
}.open
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Dcr
|
2
|
+
module View
|
3
|
+
class Compass
|
4
|
+
include Glimmer::UI::CustomShape
|
5
|
+
|
6
|
+
option :location_x, default: 0
|
7
|
+
option :location_y, default: 0
|
8
|
+
option :angle, default: 0
|
9
|
+
|
10
|
+
def location_x=(value)
|
11
|
+
options[:location_x] = value
|
12
|
+
apply_transform
|
13
|
+
end
|
14
|
+
|
15
|
+
def location_y=(value)
|
16
|
+
options[:location_y] = value
|
17
|
+
apply_transform
|
18
|
+
end
|
19
|
+
|
20
|
+
def angle=(value)
|
21
|
+
options[:angle] = value
|
22
|
+
apply_transform
|
23
|
+
end
|
24
|
+
|
25
|
+
body {
|
26
|
+
shape(0, 0) {
|
27
|
+
# using polygons because transforms are not working properly at 45/135 degrees when used with unfilled shapes like lines
|
28
|
+
polygon(0, -1, 18, -1, 18, 1, 0, 1) {
|
29
|
+
background :black
|
30
|
+
}
|
31
|
+
polygon(14, -5, 14, 5, 19, 0) {
|
32
|
+
background :black
|
33
|
+
}
|
34
|
+
|
35
|
+
current_transform
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
def apply_transform
|
40
|
+
body_root.content {
|
41
|
+
current_transform
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_transform
|
46
|
+
transform {
|
47
|
+
translate location_x, location_y
|
48
|
+
rotate(angle - 90)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/bin/dcr
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env jruby
|
2
|
+
|
3
|
+
runner = File.expand_path("../../app/dcr/launch.rb", __FILE__)
|
4
|
+
|
5
|
+
# Detect if inside a JAR file or not
|
6
|
+
if runner.include?('uri:classloader')
|
7
|
+
require runner
|
8
|
+
else
|
9
|
+
require 'glimmer/launcher'
|
10
|
+
|
11
|
+
launcher = Glimmer::Launcher.new([runner] + ARGV)
|
12
|
+
launcher.launch
|
13
|
+
end
|