Skip to content

Instantly share code, notes, and snippets.

@monkstone
Created January 8, 2015 15:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save monkstone/b3ca7a0eb31fcf59069c to your computer and use it in GitHub Desktop.
Save monkstone/b3ca7a0eb31fcf59069c to your computer and use it in GitHub Desktop.
refinements
# version without embedded or online
# This class is a thin wrapper around Processing's PApplet.
# Most of the code here is for interfacing with Swing,
# web applets, going fullscreen and so on.
require 'java'
require_relative '../ruby-processing/helper_methods'
require_relative '../ruby-processing/helpers/string'
require_relative '../ruby-processing/library_loader'
require_relative '../ruby-processing/config'
Dir["#{Processing::RP_CONFIG['PROCESSING_ROOT']}/core/library/\*.jar"].each do
|jar|
require jar
end
module Processing
# This is the main Ruby-Processing class, and is what you'll
# inherit from when you create a sketch. This class can call
# all of the methods available in Processing, and has two
# mandatory methods, 'setup' and 'draw', both of which you
# should define in your sketch. 'setup' will be called one
# time when the sketch is first loaded, and 'draw' will be
# called constantly, for every frame.
using StringUtil
# Include some core processing classes that we'd like to use:
include_package 'processing.core'
# Watch the definition of these methods, to make sure
# that Processing is able to call them during events.
METHODS_TO_ALIAS ||= {
mouse_pressed: :mousePressed,
mouse_dragged: :mouseDragged,
mouse_clicked: :mouseClicked,
mouse_moved: :mouseMoved,
mouse_released: :mouseReleased,
key_pressed: :keyPressed,
key_released: :keyReleased,
key_typed: :keyTyped
}
# All sketches extend this class
class App < PApplet
include Math
include HelperMethods
# Alias some methods for familiarity for Shoes coders.
# attr_accessor :frame, :title
alias_method :oval, :ellipse
alias_method :stroke_width, :stroke_weight
alias_method :rgb, :color
alias_method :gray, :color
# When certain special methods get added to the sketch, we need to let
# Processing call them by their expected Java names.
def self.method_added(method_name) #:nodoc:
if METHODS_TO_ALIAS.key?(method_name)
alias_method METHODS_TO_ALIAS[method_name], method_name
end
end
# Handy getters and setters on the class go here:
class << self
attr_accessor :sketch_class
end
def sketch_class
self.class.sketch_class
end
# Keep track of what inherits from the Processing::App, because we're going
# to want to instantiate one.
def self.inherited(subclass)
super(subclass)
@sketch_class = subclass
end
@@library_loader = LibraryLoader.new
class << self
def load_libraries(*args)
@@library_loader.load_library(*args)
end
alias_method :load_library, :load_libraries
def library_loaded?(library_name)
@@library_loader.library_loaded?(library_name)
end
def load_ruby_library(*args)
@@library_loader.load_ruby_library(*args)
end
def load_java_library(*args)
@@library_loader.load_java_library(*args)
end
end
def library_loaded?(library_name)
self.class.library_loaded?(library_name)
end
# It is 'NOT' usually necessary to directly pass options to a sketch, it
# gets done automatically for you. Since processing-2.0 you should prefer
# setting the sketch width and height and renderer using the size method,
# in the sketch (as with vanilla processing), which should be the first
# argument in setup. Sensible options to pass are x and y to locate sketch
# on the screen, or full_screen: true (prefer new hash syntax)
def initialize(options = {})
super()
post_initialize(options)
$app = self
proxy_java_fields
set_sketch_path # unless Processing.online?
mix_proxy_into_inner_classes
java.lang.Thread.default_uncaught_exception_handler = proc do
|_thread_, exception|
puts(exception.class.to_s)
puts(exception.message)
puts(exception.backtrace.map { |trace| "\t#{trace}" })
close
end
run_sketch(options)
end
def size(*args)
w, h, mode = *args
@width ||= w
@height ||= h
@render_mode ||= mode
import_opengl if /opengl/ =~ mode
super(*args)
end
def post_initialize(_args)
nil
end
# Set the size if we set it before we start the animation thread.
def start
size(@width, @height) if @width && @height
super()
end
# Provide a loggable string to represent this sketch.
def inspect
"#<Processing::App:#{self.class}:#{@title}>"
end
# Cleanly close and shutter a running sketch.
def close
control_panel.remove if respond_to?(:control_panel)
dispose
frame.dispose
end
private
# Mix the Processing::Proxy into any inner classes defined for the
# sketch, attempting to mimic the behavior of Java's inner classes.
def mix_proxy_into_inner_classes
klass = Processing::App.sketch_class
klass.constants.each do |name|
const = klass.const_get name
next if const.class != Class || const.to_s.match(/^Java::/)
const.class_eval 'include Processing::Proxy'
end
end
def import_opengl
# Include processing opengl classes that we'd like to use:
%w(FontTexture FrameBuffer LinePath LineStroker PGL
PGraphics2D PGraphics3D PGraphicsOpenGL PShader
PShapeOpenGL Texture).each do |klass|
java_import "processing.opengl.#{klass}"
end
end
def run_sketch(options = {})
args = []
@width, @height = options[:width], options[:height]
if options[:full_screen]
present = true
args << '--full-screen'
args << "--bgcolor=#{options[:bgcolor]}" if options[:bgcolor]
end
xc = Processing::RP_CONFIG['X_OFF'] ||= 0
yc = Processing::RP_CONFIG['Y_OFF'] ||= 0
x = options.fetch(:x, xc)
y = options.fetch(:y, yc)
args << "--location=#{x},#{y}" # important no spaces here
title = options.fetch(
:title,
File.basename(SKETCH_PATH).sub(/(\.rb)$/, '').titleize
)
args << title
PApplet.run_sketch(args.to_java(:string), self)
end
end # Processing::App
# This module will get automatically mixed in to any inner class of
# a Processing::App, in order to mimic Java's inner classes, which have
# unfettered access to the methods defined in the surrounding class.
module Proxy
include Math
# Generate a list of method names to proxy for inner classes.
# Nothing camelCased, nothing __internal__, just the Processing API.
def self.desired_method_names(inner_class)
bad_method = /__/ # Internal JRuby methods.
unwanted = PApplet.superclass.instance_methods + Object.instance_methods
unwanted -= %w(width height cursor create_image background size resize)
methods = Processing::App.public_instance_methods
methods.reject do |m|
unwanted.include?(m) ||
bad_method.match(m) ||
inner_class.method_defined?(m)
end
end
# Proxy methods through to the sketch.
def self.proxy_methods(inner_class)
code = desired_method_names(inner_class).reduce('') do |rcode, method|
rcode << <<-EOS
def #{method}(*args, &block) # def rect(*args, &block)
if block_given? # if block_given?
$app.send :'#{method}', *args, &block # ...
else # else
$app.#{method} *args # $app.rect *args
end # end
end # end
EOS
end
inner_class.class_eval(code)
end
# Proxy the sketch's constants on to the inner classes.
def self.proxy_constants(inner_class)
Processing::App.constants.each do |name|
next if inner_class.const_defined?(name)
inner_class.const_set(name, Processing::App.const_get(name))
end
end
# Don't do all of the work unless we have an inner class that needs it.
def self.included(inner_class)
proxy_methods(inner_class)
proxy_constants(inner_class)
end
end # Processing::Proxy
end # Processing
module StringUtil
refine String do
def titleize
underscore.humanize.gsub(/\b([a-z])/) { $1.capitalize }
end
def humanize
gsub(/_id$/, '').gsub(/_/, ' ').capitalize
end
def camelize(first_letter_in_uppercase = true)
if first_letter_in_uppercase
gsub(/\/(.?)/) { '::' + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
else
first + camelize[1..-1]
end
end
def underscore
gsub(/::/, '/')
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.tr('-', '_')
.downcase
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment