draw_color_repeat 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|