· 8 years ago · Feb 21, 2018, 12:26 PM
1# Ruby-Processing is for Code Art.
2# Send suggestions, ideas, and hate-mail to jashkenas [at] gmail.com
3# Also, send samples and libraries.
4#
5# This class is a thin wrapper around Processing's PApplet.
6# Most of the code here is for interfacing with Swing,
7# web applets, going fullscreen, and drawing sliders.
8#
9# Revision 0.8
10# - omygawshkenas
11
12require 'java'
13
14module Processing
15
16 # Conditionally load core.jar
17 require File.expand_path(File.dirname(__FILE__)) + "/core.jar" unless Object.const_defined?(:JRUBY_APPLET)
18 include_package "processing.core"
19
20 class App < PApplet
21
22 include_class "javax.swing.JFrame"
23 include_class "javax.swing.JPanel"
24 include_class "javax.swing.JLabel"
25
26 # Alias some methods for familiarity for Shoes coders.
27 attr_accessor :frame, :title
28 alias_method :oval, :ellipse
29 alias_method :stroke_width, :stroke_weight
30 alias_method :rgb, :color
31 alias_method :gray, :color
32
33 # Watch the definition of these methods, to make sure
34 # that Processing is able to call them during events.
35 METHODS_TO_WATCH_FOR = { :mouse_pressed => :mousePressed,
36 :mouse_dragged => :mouseDragged,
37 :mouse_clicked => :mouseClicked,
38 :mouse_moved => :mouseMoved,
39 :mouse_released => :mouseReleased,
40 :key_pressed => :keyPressed,
41 :key_released => :keyReleased }
42
43 def self.method_added(method_name)
44 if METHODS_TO_WATCH_FOR.keys.include?(method_name)
45 alias_method METHODS_TO_WATCH_FOR[method_name], method_name
46 end
47 end
48
49 def self.current=(app); @current_app = app; end
50 def self.current; @current_app; end
51 def self.slider_frame; @slider_frame; end
52
53 # Detect if a library has been loaded (for conditional loading)
54 @@loaded_libraries = Hash.new(false)
55 def self.library_loaded?(folder)
56 @@loaded_libraries[folder.to_sym]
57 end
58 def library_loaded?(folder); self.class.library_loaded?(folder); end
59
60 # For pure ruby libs.
61 # The library should have an initialization ruby file
62 # of the same name as the library folder.
63 def self.load_ruby_library(folder)
64 unless @@loaded_libraries[folder.to_sym]
65 Object.const_defined?(:JRUBY_APPLET) ? prefix = "" : prefix = "library/"
66 @@loaded_libraries[folder.to_sym] = require "#{prefix}#{folder}/#{folder}"
67 end
68 return @@loaded_libraries[folder.to_sym]
69 end
70
71 # Loading libraries which include native code needs to
72 # hack the Java ClassLoader, so that you don't have to
73 # futz with your PATH. But its probably bad juju.
74 def self.load_java_library(folder)
75 unless @@loaded_libraries[folder.to_sym]
76 if Object.const_defined?(:JRUBY_APPLET) # Applets preload all the java libraries.
77 @@loaded_libraries[folder.to_sym] = true if JRUBY_APPLET.get_parameter("archive").match(%r(#{folder}))
78 else
79 base = "library#{File::SEPARATOR}#{folder}#{File::SEPARATOR}"
80 jars = Dir.glob("#{base}*.jar")
81 base2 = "#{base}library#{File::SEPARATOR}"
82 jars = jars + Dir.glob("#{base2}*.jar")
83 jars.each {|jar| require jar }
84 return false if jars.length == 0
85 # Here goes...
86 sep = java.io.File.pathSeparator
87 path = java.lang.System.getProperty("java.library.path")
88 new_path = base + sep + base + "library" + sep + path
89 java.lang.System.setProperty("java.library.path", new_path)
90 field = java.lang.Class.for_name("java.lang.ClassLoader").get_declared_field("sys_paths")
91 if field
92 field.accessible = true
93 field.set(java.lang.Class.for_name("java.lang.System").get_class_loader, nil)
94 end
95 @@loaded_libraries[folder.to_sym] = true
96 end
97 end
98 return @@loaded_libraries[folder.to_sym]
99 end
100
101 # Creates a slider, in a new window, to control an instance variable.
102 # Sliders take a name and a range (optionally), returning an integer.
103 def self.has_slider(name, range=0..100)
104 return if Object.const_defined?(:JRUBY_APPLET)
105 min, max = range.begin * 100, range.end * 100
106 attr_accessor name
107 initialize_slider_frame unless @slider_frame
108 slider = Slider.new(min, max)
109 slider.add_change_listener do
110 val = slider.get_value / 100.0
111 slider.update_label(val)
112 Processing::App.current.send(name.to_s + "=", val)
113 end
114 slider.set_minor_tick_spacing((max - min).abs / 10)
115 slider.set_paint_ticks true
116 slider.paint_labels = true
117 label = JLabel.new("<html><br>" + name.to_s + "</html>")
118 slider.label = label
119 slider.name = name
120 @slider_frame.sliders << slider
121 @slider_frame.panel.add label
122 @slider_frame.panel.add slider
123 end
124
125 def self.initialize_slider_frame
126 @slider_frame ||= JFrame.new
127 class << @slider_frame
128 attr_accessor :sliders, :panel
129 end
130 @slider_frame.sliders ||= []
131 slider_panel ||= JPanel.new(java.awt.FlowLayout.new(1, 0, 0))
132 @slider_frame.panel = slider_panel
133 end
134
135 def self.remove_slider_frame
136 if @slider_frame
137 @slider_frame.remove_all
138 @slider_frame.dispose
139 @slider_frame = nil
140 end
141 end
142
143 def initialize(options = {})
144 super()
145 App.current = self
146 options = {:width => 400,
147 :height => 400,
148 :title => "",
149 :full_screen => false}.merge(options)
150 @width, @height, @title = options[:width], options[:height], options[:title]
151 display options
152 display_slider_frame if self.class.slider_frame
153 end
154
155 def inspect
156 "#<Processing::App:#{self.class}:#{@title}>"
157 end
158
159 def display_slider_frame
160 f = self.class.slider_frame
161 f.add f.panel
162 f.set_size 200, 32 + (71 * f.sliders.size)
163 f.setDefaultCloseOperation(JFrame::DISPOSE_ON_CLOSE)
164 f.set_resizable false
165 f.set_location(@width + 10, 0)
166 f.show
167 end
168
169 def display_full_screen(graphics_env)
170 @frame = java.awt.Frame.new
171 mode = graphics_env.display_mode
172 @width, @height = mode.get_width, mode.get_height
173 gc = graphics_env.get_default_configuration
174 @frame.set_undecorated true
175 @frame.set_background java.awt.Color.black
176 @frame.set_layout java.awt.BorderLayout.new
177 @frame.add(self, java.awt.BorderLayout::CENTER)
178 @frame.pack
179 graphics_env.set_full_screen_window @frame
180 @frame.set_location(0, 0)
181 @frame.show
182 self.init
183 self.request_focus
184 end
185
186 def display_in_a_window
187 @frame = JFrame.new(@title)
188 @frame.add(self)
189 @frame.setSize(@width, @height + 22)
190 @frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
191 @frame.setResizable(false)
192 @frame.show
193 self.init
194 end
195
196 def display_in_an_applet
197 JRUBY_APPLET.setSize(@width, @height)
198 JRUBY_APPLET.background_color = nil
199 JRUBY_APPLET.double_buffered = false
200 JRUBY_APPLET.add self
201 JRUBY_APPLET.validate
202 # Add the callbacks to peacefully expire.
203 JRUBY_APPLET.on_stop { self.stop }
204 JRUBY_APPLET.on_destroy { self.destroy }
205 self.init
206 end
207
208 # Tests to see which display method should run.
209 def display(options)
210 if Object.const_defined?(:JRUBY_APPLET) # Then display it in an applet.
211 display_in_an_applet
212 elsif options[:full_screen] # Then display it fullscreen.
213 graphics_env = java.awt.GraphicsEnvironment.get_local_graphics_environment.get_default_screen_device
214 graphics_env.is_full_screen_supported ? display_full_screen(graphics_env) : display_in_a_window
215 else # Then display it in a window.
216 display_in_a_window
217 end
218 end
219
220 # There's just so many functions in Processing,
221 # Here's a convenient way to look for them.
222 def find_method(method_name)
223 reg = Regexp.new("#{method_name}", true)
224 self.methods.sort.select {|meth| reg.match(meth)}
225 end
226
227 # Specify what rendering Processing should use.
228 def render_mode(mode_const)
229 size(@width, @height, mode_const)
230 end
231
232 def mouse_x; mouseX; end
233 def mouse_y; mouseY; end
234 def pmouse_x; pmouseX; end
235 def pmouse_y; pmouseY; end
236
237 def mouse_pressed?
238 Java.java_to_primitive(java_class.field("mousePressed").value(java_object))
239 end
240
241 def key_pressed?
242 Java.java_to_primitive(java_class.field("keyPressed").value(java_object))
243 end
244
245 def close
246 Processing::App.current = nil
247 self.class.remove_slider_frame
248 @frame.remove(self)
249 self.destroy
250 @frame.dispose
251 end
252
253 def quit
254 java.lang.System.exit(0)
255 end
256
257 end
258
259 class Slider < javax.swing.JSlider
260 attr_accessor :name, :label
261
262 def initialize(*args)
263 super(*args)
264 end
265
266 def update_label(value)
267 value = value.to_s
268 value << "0" if value.length < 4
269 label.set_text "<html><br>#{@name.to_s}: #{value}</html>"
270 end
271 end
272
273end