draw_color_repeat 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +556 -0
  4. data/VERSION +1 -0
  5. data/app/dcr.rb +51 -0
  6. data/app/dcr/launch.rb +3 -0
  7. data/app/models/dcr/command.rb +129 -0
  8. data/app/models/dcr/command/backward.rb +45 -0
  9. data/app/models/dcr/command/color.rb +120 -0
  10. data/app/models/dcr/command/empty.rb +30 -0
  11. data/app/models/dcr/command/forward.rb +47 -0
  12. data/app/models/dcr/command/left.rb +37 -0
  13. data/app/models/dcr/command/repeat.rb +42 -0
  14. data/app/models/dcr/command/right.rb +36 -0
  15. data/app/models/dcr/polygon.rb +37 -0
  16. data/app/models/dcr/program.rb +130 -0
  17. data/app/views/dcr/app_view.rb +128 -0
  18. data/app/views/dcr/compass.rb +56 -0
  19. data/bin/dcr +13 -0
  20. data/config/warble.rb +183 -0
  21. data/dcr.gemspec +0 -0
  22. data/lib/icon.rb +53 -0
  23. data/package/linux/Draw Color Repeat.png +0 -0
  24. data/package/macosx/Draw Color Repeat.icns +0 -0
  25. data/package/windows/Draw Color Repeat.ico +0 -0
  26. data/samples/aztec_pyramid.dcr +15 -0
  27. data/samples/bee_hive.dcr +15 -0
  28. data/samples/circle.dcr +4 -0
  29. data/samples/circle_of_circles.dcr +7 -0
  30. data/samples/envelope.dcr +14 -0
  31. data/samples/equilateral_triangle.dcr +6 -0
  32. data/samples/five_pointed_star.dcr +4 -0
  33. data/samples/house.dcr +25 -0
  34. data/samples/octagon.dcr +4 -0
  35. data/samples/octagon_of_octagons.dcr +6 -0
  36. data/samples/octagon_of_squares.dcr +7 -0
  37. data/samples/playing_cards.dcr +9 -0
  38. data/samples/rectangle.dcr +6 -0
  39. data/samples/sherrif_badge_star.dcr +8 -0
  40. data/samples/spider_web.dcr +6 -0
  41. data/samples/square.dcr +4 -0
  42. data/samples/stairs.dcr +16 -0
  43. data/samples/stick_figure.dcr +21 -0
  44. data/samples/sun.dcr +9 -0
  45. data/samples/swirl.dcr +7 -0
  46. data/samples/traffic_light.dcr +29 -0
  47. data/samples/triangle.dcr +5 -0
  48. data/vendor/jars/org/yaml/snakeyaml/1.28/snakeyaml-1.28.jar +0 -0
  49. 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