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.
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