Skip to content

Instantly share code, notes, and snippets.

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 sancarn/79042ba2036fd86bb23e00dc383cdcec to your computer and use it in GitHub Desktop.
Save sancarn/79042ba2036fd86bb23e00dc383cdcec to your computer and use it in GitHub Desktop.

Infoworks ICM - GUIs with Ruby (Part 2)

In the last part of this series we explored how we could use the Windows Forms .NET framework from Ruby using an intermediate Powershell script. This time we will go down a completely different route. Today we will build GUIs using the popular web technologies - HTML, CSS and JavaScript.

Why web technologies?

Web technologies over the years have improved greatly. Open source JavaScript engines have become faster at processing data and HTML and CSS have greatly improved to provide clean interfaces for the ever growing market of the web. JavaScript, executed from Chrome's V8 Engine, is currently one of the fastest interpreted languages on the market. This is largely due to the fact that there is a huge demand for bigger, faster webpages.

Recently there has been an emerging trend of web technologies being used for desktop applications. Frameworks such as, Electron and NW.js make this not only possible, but both easy to implement and easy to debug.

So the first thing to mention is, IF YOU CAN USE FRAMEWORKS, DO SO!! I cannot stress this enough. If you have the luxury of being able to use any software you desire at your organisation, do so! The only exception is if you want to make open source software for the community. In theses cases I'd strongly suggest you use other techniques mentioned in this article.

My first Ruby desktop-webapp

Over the past few years there have been countless occasions where I have wanted to implement web technologies into my Ruby scripts. In one such application I wanted to give the user the ability to select a selection list from a combobox, similar to the first example presented in my last article.

I came across a technique, written in AHK, where users could instantiate and control Internet Explorer COM Objects dynamically. This allowed a developer to inject HTML into an Internet Explorer page and receive information about what the user is doing with the page.

You can find my code here. But the general concept is as follows:

First we create the internet explorer object using Win32 OLE and navigate to a blank page:

# Create a new Internet Explorer COM object (use Late Binding PROGID/CLSID):
require 'win32ole'
ie = WIN32OLE.new('InternetExplorer.Application')

# Navigate to blank page
ie.navigate("about:blank")
sleep(0.1) until !ie.busy

Now we need to create our HTML. The important part here, is we need to make the submit mechanism change the page's URL. We can do this using document.url=<<MY DATA>>. In this example we execute this via a button's onclick HTML attribute, however generally we would add this event listener using button.addEventListener("click",function(ev){...}).

html =<<-Heredoc
  <html>
    <body>
      <h1>Hello World</h1>
      <button onclick="document.url = 'hello world'">Click me!</button>
    </body>
  </html>
Heredoc

Next we need to write this data to the IE object, which we can do as follows:

# Write HTML to page
ie.document.write(s)

#Force no scroll bar
ie.document.body.scroll = "no"

#focus focusable UI elements
ie.document.all.entries.Focus

#Make IE visible
ie.visible = true

Finally, we continually wait and test for a change in the document's URL. When the URL does finally change, we can react to this by setting retVar to the contents of the URL. Otherwise this process will continue till IE doesn't exist anymore. If IE doesn't exist, the rescue statement will be executed instead:

# Wait till url not about:blank
begin
  until (ie.document.url!="about:blank") do
    sleep(0.1)
  end
  retVar = ie.document.url
rescue
  retVar =  -1
end
puts retVar

And this works! It is isn't overly flexible though and really it's just a dirty hack! It'd be much better if we had a more direct link to Ruby, instead of relying on the document's URL all the time...

Browser Helper Objects

In the example above we use document.write() to inject data into the loaded HTML document. We can access the very same document object via ie.document in Ruby. So you might be wondering whether we can install our own JavaScript 'plugins' into the document object and use them directly from within Ruby... And, well, you can! For example, take the following JavaScript:

  document.eval = window.eval

This allows us to evaluate JavaScript directly from Ruby! For example, ie.document.eval("alert 'hello world'") will indeed call JavaScript's alert function. Neat huh? So what more can we do? We can inject Ruby variables into IE.

Take the following JavaScript code:

  document.setVar = function(name,value){
    document[name]=value;
  }

Here we are installing a new function into the document object, which allows us to create new JavaScript variables, on the fly. Using this function we can inject Ruby objects and execute Ruby methods directly from JavaScript! This is called a Browser Helper Object (BHO).

  class BHO
    attr_accessor :running
    # Quit can be used to halt the runtime
    def quit()
      @running=false
    end

    #Required methods:
    def call
      nil
    end
    def value(*args)
      nil
    end
  end
  bho = BHO.new
  ie.document.setVar("BHO",bho)
  sleep 0.1 while ie.document.BHO.running

Injecting this object into Internet Explorer allows the JavaScript engine to call document.BHO.quit(). This will set @running to false, causing the Ruby host to continue onwards.

Furthermore, we can extend our object to give JavaScript new functionality! For example, let's give JavaScript the ability to read and write files!

  # Extend the File object giving JS access to File API.
  class WIN32OLE_File < File
    # Allow calling of class methods
    def self.call
      nil
    end
    def self.value(*args)
      nil
    end

    # Allow calling of instance methods
    def call
      nil
    end
    def value(*args)
      nil
    end
  end
  ie.document.setVar("RubyFile",WIN32OLE_File)

To more easily wrap ruby objects in these dispatchable wrappers I suggest you use WIN32OLE::getDispatch(Class) function I created.

Now we have totally wrapped Ruby's File object giving us full file access rights in JavaScript. For example, let's say we wanted to read a file. In Ruby we could use File.read("my/file/path") or you could use File.new("my/file/path").read(). In JavaScript we can use the wrapped class as follows: document.RubyFile.read("my/file/path") or document.RubyFile.new("my/file/path").read().

Ultimately, we can port all functionality in the Ruby environment to JavaScript. This extends even to the ICM libraries themselves:

  class WIN32OLE_WSApplication < WSApplication
    # Allow calling of class methods
    def self.call
      nil
    end
    def self.value(*args)
      nil
    end
  end
  ie.document.setVar("WSApplication",WIN32OLE_WSApplication)

Beware! WSApplication.current_network and similar methods may not work correctly as there is no way to ensure WSOpenNetwork objects are wrapped in the proper way. That is unless you use a Proxy object. I'll discuss more about this in a future article.

So, great! We have a fully functional system for creating dynamic, GUIs, based on powerful web technologies. We can control the JavaScript environment from Ruby, and can control Ruby from JavaScript. And last of all, it uses technology which is innately available on all Windows machines, so it's a perfect candidate for GUIs built for ICM.

Primitive Technology

For most purposes using Internet Explorer (IE) is good enough for building GUIs for usage inside ICM. However there are downsides.

So what are the downsides? Well there is a reason why Microsoft has scrapped IE for the newer Edge browser. Although IE works decently as a browser, it is far from a good browser by modern standards. You can visually see how feature lacking IE is by comparing it's API to other browsers. IE is simply put, painful to work with. Over the years there have been innovative systems which simplify working with IE, for example BabelJS and SASS. These technologies are pre-processors which compile source code into multi-browser friendly JavaScript and CSS. However some problems that arise in IE, simply cannot be overcome by these pre-processors at their current level of abstraction.

So of course there are a few options here. We could continue using IE, and continue using the outdated technologies that come with it. We have the advantage that we are always 100% compatible with the user (assuming ICM will stay on Windows), and for most projects this is likely the direction that we'd like to go in. Or we could scrap IE, and figure out how to communicate and control more modern browsers like for example Google Chrome.

The other question to bare in mind is "Who is your audience?". If you plan on sharing scripts, then IE is great because you can always guarantee that it'll work the same everywhere. However if you're making scripts for a company, where you can ensure everyone will have Chrome (or similar) installed, perhaps that'd be a better option.

In the end, the choice is yours, but in the interest of exploring all avenues of GUIs in Ruby, let's get cracking and learn how we can build fully functional GUIs in more modern browsers like Chrome.

GUIs with Chrome

Let's begin with the theory. If it is possible for Ruby to host a website, then we can view that website locally from any web browser (Internet Explorer, Chrome, Firefox, ...). This allows us to fully control what is shown on the website. Initially I started by emulating my own webserver with sockets, but later I discovered Ruby came with its own web server framework, WEBrick. To use WEBrick we simply call $server = WEBrick::HTTPServer.new(<<OPTIONS>>). A simple HTTP server which works in ICM 6.5.6 can be found below:

=begin
  THIS IS REQUIRED IN ICM 6.5.6 DUE TO AN ERROR IN WEBRICK. IF LOGGER IS OTHERWISE REQUIRED, YOU CAN MAKE YOUR OWN.
  :Logger => WEBrick::Log.new(NullStream.new)
=end
class NullStream
   def <<(o); self; end
end


#remove old reference of server if existent
$server = nil

#create new server
$server = WEBrick::HTTPServer.new(
  :Port=>12357,
  :DocumentRoot => Dir.pwd,
  :Logger => WEBrick::Log.new(NullStream.new),  #fixes some bugs...
  :AccessLog => [],
)
trap 'INT' do $server.shutdown end

$server.start

Now currently our server doesn't actually do anything. But we can connect to it regardless! In Chrome, or another browser, type http://localhost:12357 into the address bar. It should send you to a 404 - Not Found page. Great! So now our server is active, how can we make it display a page? This is the job of WEBrick::HTTPServer#mount. First we define a HTTP GET and HTTP EXIT handler to deal with our requests:

DefaultBody=<<END_BODY
Hello world.
END_BODY
class RequestHandler < WEBrick::HTTPServlet::AbstractServlet
    # Define HTTP GET handler, Use it to retrieve file data/default body
    def do_GET(request,response)
        resource = request.path.to_s[1..-1]

        # If no file requested, send default body
        if resource == ""
            response.body = DefaultBody
        else
            #If a file is requested, try to get the file, else return 404
            begin
                response.body = File.read(resource)
            rescue
                response.status = 404
            end
        end
    end

    # Define HTTP EXIT handler, Use it to exit the server.
    def do_EXIT(request,response)
        $server.shutdown
        response.body = ""
    end
end
$server.mount '/', RequestHandler

Add the following code before $server.start in the first example and now we have a working server! It should display the text "Hello world" when you navigate to the website in your browser! Similarly, it should navigate to and display the contents of files if you use http://localhost:12357/myFile.txt. One problem at the moment though... If you quit the webpage, the Ruby server continues to run and you have to force quit ICM. Less than ideal! To fix this I have provided the HTTP EXIT handler. Ultimately this allows us to tell the server when it needs to shut down. Add the following HTML to our DefaultBody:

  <script>
    //Exit before unload
    window.addEventListener('beforeunload',function(){
        request = new XMLHttpRequest
        request.open("EXIT","")
        request.send()
        window.setTimeout(window.close)
        return null
    });
  </script>

This ensures that a HTTP request is sent before the browser window closes! Furthermore, an HTTP EVAL handler can be added to the WEBrick server code:

  class Sandbox
    def get_binding
      binding
    end
  end
  $evalBinding = Sandbox.new.get_binding
  #...
  def do_EVAL(request,response)
    begin
        result = $evalBinding.eval(request.body)
        response.body = {:type=>"DATA", :data=>result}.to_json
    rescue Exception => e
        response.body = {:type=>"ERROR",:data=>e.to_s}.to_json
    end
  end

Which can be used to evaluate Ruby directly from JavaScript!

  function evaluateRuby(script,callback){
    request = new XMLHttpRequest
    request.onload = function(){
        callback(this.responseText)
    }
    request.open("EVAL","")
    request.send(script)
  }

Finally, we can open Chrome, and navigate to the server directly from the Ruby script! The command line argument --app can be used to hide the address bar, making our GUI look even more professional. Here we also set autoplay-policy allowing us to include music in our GUI automatically.

COMMAND_LINE_ARGS = "--app=\"http://localhost:12357\" --autoplay-policy=no-user-gesture-required"

#Find location of Google Chrome:
require 'win32/registry'
chromePath = Win32::Registry::HKEY_LOCAL_MACHINE.open('SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe')[""]

#Try to open the site in chrome:
case true
  when system("\"#{chromePath}\" #{COMMAND_LINE_ARGS}")
  else
    puts "Cannot open browser. Will now exit."
    exit
end

A full working WEBrick example can be found in the GIST. So this is super cool right? The only issue is how much boiler plate code is required. However whenever you have too much boilerplate code it's always best to wrap it in a class (or function)! For this reason I created the GUI.rb class. With this wrapper it becomes extraordinarily simple to create a new GUI with HTML:

require_relative "3_GUIWrapper.rb"
Default = <<EOF
  <title>Simple</title>
  <h1>Hello world</h1>
  <button style="width:100px;height:30px;" onclick="Ruby.eval('27+79',function(response){alert('27+79 ==> ' + response.data)})"></button>
EOF

gui = GUI::GUI.new(Default)
gui.show()

So by this point you can do just about anything with Chrome GUIs, the limit really is, for the most part, your own imagination! And this leads me swiftly on to my last examples. In ShowOff.rb I show to what extent you can style your GUIs. Styling and design is an important part of GUI design, but unfortunately not one I am well versed in, so most of the original code actually came from ShaderToy. However it does go to show how extreme you can really get with HTML visualisations, and perhaps in the future we'll see real hydraulic visualisations using similar technology directly embedded into ICM Ruby scripts! Another cool water related example can be found here and here.

However, I believe that Console.rb really shows where this technology shines! Within Console.rb I created a full Ruby REPL, runnable from ICM, which looks modern like the Chrome devtools REPL. The console has some issues still:

  • Symbols are displayed as strings
  • Hashes are displayed as javascript objects

However, this could very easily change. I know I'll certainly be gradually improving this project in the future!

Conclusions

In this article we've investigated how to use modern web technologies to create GUIs for our Ruby applications. Compared to PowerShell, HTML offers greater flexibility as well as being a system that more people are familiar with.

We've discussed embedding Ruby objects directly into IE and how we can use HTTP requests to execute Ruby scripts from within Chrome.

HTML, CSS and JavaScript are all extremely flexible and built specifically for making good looking and user friendly GUIs. It'd be great to see this technique being used by more applications in the future.

# To learn how to use win32 libraries in Ruby use these resources:
# http://phrogz.net/programmingruby/lib_windows.html
# https://ruby-doc.org/stdlib-2.2.0/libdoc/win32ole/rdoc/WIN32OLE.html
# http://www.rubytips.org/2008/05/13/accessing-windows-api-from-ruby-using-win32api-library/
require 'win32ole'
require 'Win32API'
def combobox(sTitle, aOptions)
# Create a new Internet Explorer COM object (use Late Binding PROGID/CLSID):
ie = WIN32OLE.new('InternetExplorer.Application')
# Set various InternetExplorer options
ie.resizable = false
ie.toolbar = false
ie.registerAsDropTarget = false
ie.statusBar = false
#Navigate to blank page
ie.navigate("about:blank")
sleep(0.1) until !ie.busy
# Set dimensions
ie.width = 450
ie.height = 190
ie.left = ie.document.parentWindow.screen.width / 2 - 200
ie.top = ie.document.parentWindow.screen.height / 2 - 75
# Prevents javascript errors from displaying:
ie.silent = true
######################################################################################################
# SET WINDOW ICON
######################################################################################################
# User32 LoadImageA: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
# User32 SendMessageA: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx
#
# C++ example:
# HANDLE icon = LoadImage(fgDisplay.Instance, "c:\\icon.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
# SendMessage(instance, (UINT)WM_SETICON, ICON_BIG, (LPARAM)icon);
#Define constants:
_IMAGE_ICON = 1
_LR_LOADFROMFILE = 0x00000040
_LR_DEFAULTSIZE = 0x00000010
_WM_SETICON = 0x0080
_ICON_BIG = 1
_ICON_SMALL = 0
#Set icon
icoPath = 'C:\Users\jwa\Desktop\Icons\icm_logo.ico'
icon = Win32API.new("user32","LoadImageA",["l","p","l","l","l","l"],"p").call(0,icoPath, _IMAGE_ICON, 0,0, _LR_DEFAULTSIZE | _LR_LOADFROMFILE | 0)
Win32API.new("user32","SendMessageA",["l","i","i","l"],"i").call(ie.hwnd,_WM_SETICON,_ICON_SMALL,icon) # affects title bar
Win32API.new("user32","SendMessageA",["l","i","i","l"],"i").call(ie.hwnd,_WM_SETICON,_ICON_BIG,icon) # affects alt-tab
######################################################################################################
######################################################################################################
# SET WINDOW TITLE - IE over-rides this option
######################################################################################################
#User32 SetWindowTextA: https://msdn.microsoft.com/en-us/library/windows/desktop/ms633546(v=vs.85).aspx
# wndTitle = "Select from list..."
# Win32API.new("user32","SetWindowTextA", ["l","p"],"i").call(ie.hwnd,wndTitle)
######################################################################################################
#Set window content
s = "
<html>
<head>
<title>#{sTitle}</title>
</head>
<script type=\"text/javascript\">
document.title = document.title + Array(100).join(\"\\u2002\")
document.addEventListener('keydown', function(event){
var sel = document.getElementById('entries')
switch(event.key){
case 'Down':
sel[sel.selectedIndex+1].selected=true
break;
case 'Up':
sel[sel.selectedIndex-1].selected=true
break;
case 'Enter':
submitForm()
break;
case 'Esc':
sel[0].selected=true
submitForm()
break;
default:
}
});
document.focus()
</script>
<script>
submitForm = function(){
document.url = document.getElementById(\"entries\").selectedIndex
}
</script>
<body bgColor=Silver>
<center>
<b>#{sTitle}<b>
<p>
<select id=entries size=1 style='width:250px'>
<option selected></option>"
#Add options
aOptions.each do |item|
s += "<option>#{item}</option>"
end
s += " </select>
<p>
<button id=but0 onclick='submitForm()'>OK</button>
</center>
</body>
</html>"
ie.document.write(s)
ie.document.body.scroll = "no"
ie.document.all.entries.Focus
ie.visible = true
#Bring window to front
Win32API.new("user32","BringWindowToTop",["l"],"i").call(ie.hwnd)
#wait till url not about:blank
begin
until (ie.document.url!="about:blank") do
sleep(0.1)
end
id=ie.document.url
rescue
return -1
end
#Get id and hide ie
id=id.to_i
ie.visible = false
#Return id-1
return id-1
end
WSApplication||=nil
if(WSApplication)
# get all selection lists:
selectionLists = WSApplication.current_database.model_object_collection('Selection list')
# get all names of selection list - we may want to improve this to remove duplicates!
names = []
selectionLists.each do |sel|
names << sel.name
end
else
#DEBUG
selectionLists = [
{:type=>"Selection List",:name=>"CSOs"},
{:type=>"Selection List",:name=>"Tanks"},
{:type=>"Selection List",:name=>"STWs"},
{:type=>"Selection List",:name=>"Trunk sewers"}
]
names = selectionLists.map {|sl| sl[:name]}
end
# show the combobox
id = combobox("Which selection list do you want to append to?", names)
# if the user selected an item, assign that item to $list, for use in other scripts
if id != -1
$list = selectionLists[id]
end
# DEBUG
if !WSApplication
puts id
end
require 'JSON'
require 'win32ole'
ie = WIN32OLE.new('InternetExplorer.Application')
# Navigate to blank page
ie.navigate("about:blank")
sleep(0.1) until !ie.busy
html =<<Heredoc
<html>
<head>
<script>
document.setVar = function(name,val){
document[name]=val;
}
document.getData = function(){
return JSON.parse(document.data)
}
document.setData = function(val){
document.data = JSON.stringify(val)
}
document.quit = function(){
data = document.getData()
data["some data"] = "Hello world"
data["running"] = false
document.setData(data)
}
</script>
</head>
<body>
<h1>Hello!</h1>
<button onclick="document.quit()">Click Me</button>
</body>
</html>
Heredoc
# Write html
ie.document.write(html)
ie.visible = true
#CAN PASS NUMBERS:
# a=1
# ie.document.setVar("a",a)
#CAN PASS STRINGS:
# a="hello world"
# ie.document.setVar("a",a)
# can therefore pass JSON
# CANNOT PASS ARRAYS:
# a=[1,2,3]
# ie.document.setVar("a",a)
# #=> HANG
# CANNOT PASS HASHES:
# a={"A"=>:B, :c=>:d}
# ie.document.setVar("a",a)
# #=> HANG
# CANNOT PASS CLASS INSTANCES:
# class A
# attr_accessor :b
# def initialize()
# @b = "hello"
# end
# end
# ie.document.setVar("a",A.new)
# #=> HANG
# CANNOT PASS CLASSES IN GENERAL
# class A
# def self.test()
# return "hi there"
# end
# end
# ie.document.setVar("a",A)
# #=> HANG
#CANNOT PASS FUNCTIONS:
# def doThis
# puts "hello world"
# end
# ie.document.setVar("a",method(:doThis))
#CANNOT PASS PROCs:
# ie.document.setVar("a", ->{puts "hello world"})
# #=> HANG
#Instead of functions we define a classes with call and value methods:
class HelloFunc
# Call without arguments
def call
return "Hello world"
end
# Call with arguments
def value(*args)
#Sometimes this method is called without args and I'm still unsure why...
if args.length == 0
return nil
end
return "Hello #{args[0]}"
end
end
ie.document.setVar("helloFunc",HelloFunc.new)
class BHO
attr_accessor :test
def initialize
@test = "something"
end
def log(*args)
puts *args
end
# Required default object methods
def call
nil
end
def value(*args)
p *args
nil
end
end
ie.document.setVar("BHO",BHO) # Can call BHO.new()
ie.document.setVar("bho",BHO.new) # Can call bho.log() or bho.test or bho.test="hello"
data = {"running" => true}
ie.document.setVar("data",data.to_json)
# Always access variables through ie.document after saving them there. Never access them through variable reference.
# Otherwise IE throws later permission errors, which are not catchable. E.G. if in this case we passed data into setVar()
# we should always retrieve ie.document.data, instead of retrieving data directly through variable reference.
while (data = JSON.parse(ie.document.data))["running"]; end #Wait for running flag
ie.visible = false #close ie
ie.Quit()
puts data["some data"]
require 'JSON'
require 'win32ole'
ie = WIN32OLE.new('InternetExplorer.Application')
# Navigate to blank page
ie.navigate("about:blank")
sleep(0.1) until !ie.busy
#Build HTML
html =<<Heredoc
<html>
<head>
<script>
// Allow for variable injection
document.setVar = function(name,val){
document[name]=val;
window[name]=val;
}
// Method after_load is called by Ruby after the `Ruby` class has been injected (Ruby-side)
document.after_load = function(){
// Button events:
document.querySelector("#eval").onclick = function(){
window.Ruby.eval('puts "Hello World"\\ndata += 1');
};
document.querySelector("#quit").onclick = function(){
window.Ruby.quit();
};
// Set timeout ensures that Ruby is defined in IE before 'window.Ruby.eval()' is called
setTimeout(function(){
window.Ruby.eval('data = 0');
},0)
}
</script>
</head>
<body>
<h1>Some GUI</h1>
<button id="eval" > Some button </button>
<button id="quit" > Exit </button>
</body>
</html>
Heredoc
# Write html
ie.document.write(html)
sleep(0.1) until !ie.busy
# Make GUI visible
ie.visible = true
# Build callable object for IE injection
class RubyRuntime
attr_accessor :data, :running, :rBinding
def initialize
@rBinding = binding
@running = true
end
# Eval can be used to evaluate ruby code
def eval(expression)
@rBinding.eval(expression)
end
# Quit can be used to halt the runtime
def quit()
@running=false
end
#Required methods:
def call
nil
end
def value(*args)
nil
end
end
# Inject callable object into IE
ieRuby=RubyRuntime.new
ie.document.setVar("Ruby",ieRuby)
sleep 0.1 until ie.document.Ruby
# Call after_load after Ruby variable has been loaded
ie.document.after_load(nil)
#Wait for ruby VM to stop running
while ie.document.Ruby.running; end
# Quit IE and free memory
ie.Quit()
ie.ole_free
# Return data collected
puts "You clicked the button " + ieRuby.rBinding.eval("data").to_s + " time(s)!"
require 'JSON'
require 'win32ole'
ie = WIN32OLE.new('InternetExplorer.Application')
# Navigate to blank page
ie.navigate("about:blank")
sleep(0.1) until !ie.busy
#Build HTML
html =<<Heredoc
<html>
<head>
<script>
// Allow for variable injection
document.setVar = function(name,val){
document[name]=val;
window[name]=val;
}
</script>
</head>
<body>
<h1>Some GUI</h1>
</body>
</html>
Heredoc
# Write html
ie.document.write(html)
sleep(0.1) until !ie.busy
# Make GUI visible
ie.visible = true
# Build callable object for IE injection
class RubyRuntime
attr_accessor :data, :running, :rBinding
def initialize
@rBinding = binding
@running = true
end
# Eval can be used to evaluate ruby code
def eval(expression)
@rBinding.eval(expression)
end
# Quit can be used to halt the runtime
def quit()
@running=false
end
#Required methods:
def call
nil
end
def value(*args)
nil
end
end
# Extend the File object giving JS access to File API.
class WIN32OLE_File < File
def self.call
nil
end
def self.value(*args)
nil
end
# Allow calling of instance methods
def call
nil
end
def value(*args)
nil
end
end
ie.document.setVar("RubyFile",WIN32OLE_File)
# Inject callable object into IE
ieRuby=RubyRuntime.new
ie.document.setVar("Ruby",ieRuby)
sleep 0.1 until ie.document.Ruby
#Wait for ruby VM to stop running
while ie.document.Ruby.running; end
# Quit IE and free memory
ie.Quit()
ie.ole_free
# Return data collected
puts "You clicked the button " + ieRuby.rBinding.eval("data").to_s + " time(s)!"
# Might also want to try:
# https://github.com/postmodern/net-http-server
require 'Socket'
require 'stringio'
class StringIO
def p(string)
self.puts string.inspect
end
def read
self.string.gsub("\n","\r\n")
end
end
class HelperFunctions
def HelperFunctions.tryGet(object,method)
begin
return object[method]
rescue
return ""
end
end
end
class Server
attr_reader :debug__raw_request
def initialize(port=12357,&block)
if !block_given?
throw Error.new("Block must be given")
end
@log = StringIO.new()
#Start server
@server = TCPServer.new 12357
@running = true
#While running, accept incoming sessions
while @running
#Poll for incoming requests
begin
begin
@session = @server.accept_nonblock
rescue IO::WaitReadable, Errno::EINTR
#if server is not meant to be running, exit thread
exit if !@running
#retry accept session
IO.select([@server])
retry
end
rescue
@log.puts "Server.Accept failed..."
@log.p @session
@session = nil
end
raw_request = @session.recvmsg[0]
@log.puts raw_request
#Debugging purposes
$debug__raw_request = raw_request
#Parse request:
request = /(?<METHOD>\w+) \/(?<RESOURCE>[^ ]*) HTTP\/1.\d\r\n(?<HEADERS>(.+\r\n)*)(?:\r\n)?(?<BODY>(.|\s)*)/i.match(raw_request)
wrapper = SessionWrapper.new(@session)
http_method = HelperFunctions.tryGet(request,"METHOD")
http_resource = HelperFunctions.tryGet(request,"RESOURCE")
http_headers = HelperFunctions.tryGet(request,"HEADERS")
http_body = HelperFunctions.tryGet(request,"BODY")
yield(wrapper, http_method, http_resource, http_headers, http_body)
if wrapper.unhandled
wrapper.throw(:unhandled)
end
end
end
def open()
@tServer.join
end
def close()
@running = false
@tServer.join
end
end
class SessionWrapper
attr_accessor :headers, :body, :status
attr_reader :unhandled
def initialize(session)
@session = session
#keep track of headers
@headers = {}
@body = ""
#Default status
@status = 200
@unhandled = true
end
#Sends final message to client
def send()
@io = StringIO.new()
@io.puts "HTTP/1.1 #{@status} OK"
@headers.each do |key,value|
@io.puts "#{key}: #{value}"
end
@io.puts ""
@io.print body
@session.print @io.read
@session.close
@unhandled = false
end
def throw(reason)
puts "Unhandled request..."
if reason == :unhandled
@io = StringIO.new()
@io.puts "HTTP/1.1 501 OK"
@io.puts ""
@io.puts "Unhandled HTTP request."
@session.print @io.read
@session.close
end
end
end
# In my testing the Server.rb class has only worked in the ruby desktop application.
# Whenever running from ICM, ICM 6.5.6 crashes.
require_relative "1_Server.rb"
require 'json'
DefaultBody=<<DEFAULT_BODY
<html>
<head>
<title>Test</title>
<script>
function HandleClick(){
request = new XMLHttpRequest
request.onload = function(){
alert(this.responseText)
}
request.open("EVAL","a")
request.send("a=1\\nb=2\\n'Result = ' + (a+b).to_s")
}
</script>
</head>
<body>
<h1>Hello World</h1>
<button onclick="HandleClick()">Test</button>
</body>
</html>
DEFAULT_BODY
#Making ruby console compatible on all OSes
if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
require 'win32/registry'
chromePath = Win32::Registry::HKEY_LOCAL_MACHINE.open('SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe')[""]
`"#{chromePath}" --app="http://localhost:12357"`
elsif (/darwin/ =~ RUBY_PLATFORM) != nil
`open -n -a "Google Chrome" --args --app="http://localhost:12357"`
end
server = Server.new(12357) do |session,method,resource,headers,body|
puts method, resource, headers, body
puts $debug__raw_request
# Handle EVAL request
if method == "EVAL"
begin
session.body = eval(body).to_json
rescue Exception => e
session.body = e.to_s
end
session.send()
end
#Handle GET request
if method == "GET"
if resource == ""
session.body = DefaultBody
else
begin
session.body = File.read(resource)
rescue
session.status = 404
end
end
session.send()
end
end
server.open
puts $debug__raw_request
# This works fully in ICM 6.5.6 and uses standard Web server libraries
require 'webrick'
require 'json'
DefaultBody=<<DEFAULT_BODY
<html>
<head>
<title> My Water Software </title>
<script>
/**
* Onload we want to create a function:
* evaluateRuby - can be used to evaluate ruby script from your html application
* We want to map our button to this function. Here we use the 'click' event listener:
*/
window.addEventListener("load",function(){
function evaluateRuby(script,callback){
request = new XMLHttpRequest
request.onload = function(){
callback(this.responseText)
}
request.open("EVAL","")
request.send(script)
}
document.querySelector(".button").addEventListener("click",function(){
evaluateRuby("puts \\"hello world\\"",function(){console.log("Executed")})
})
})
//Exit before unload
window.addEventListener('beforeunload',function(){
request = new XMLHttpRequest
request.open("EXIT","")
request.send()
window.setTimeout(window.close)
return null
});
</script>
</head>
<body>
<button class="button"> Hello World </button>
</body>
</html>
DEFAULT_BODY
class Sandbox
def get_binding
binding
end
end
#Make ruby evaluation binding:
$evalBinding = Sandbox.new.get_binding
#Making ruby console compatible on all OSes
COMMAND_LINE_ARGS = "--app=\"http://localhost:12357\" --autoplay-policy=no-user-gesture-required"
#Find location of Google Chrome:
require 'win32/registry'
chromePath = Win32::Registry::HKEY_LOCAL_MACHINE.open('SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe')[""]
#Try to open the site in chrome:
case true
when system("\"#{chromePath}\" #{COMMAND_LINE_ARGS}")
else
puts "Cannot open browser. Will now exit."
exit
end
=begin
THIS IS REQUIRED IN ICM 6.5.6 DUE TO AN ERROR IN WEBRICK. IF LOGGER OTHERWISE REQUIRED, YOU CAN MAKE YOUR OWN.
:Logger => WEBrick::Log.new(NullStream.new)
=end
class NullStream
def <<(o); self; end
end
#remove old reference of server
$server = nil
$server = WEBrick::HTTPServer.new(
:Port=>12357,
:DocumentRoot => Dir.pwd,
:Logger => WEBrick::Log.new(NullStream.new), #fixes some bugs...
:AccessLog => [],
)
trap 'INT' do $server.shutdown end
class RequestHandler < WEBrick::HTTPServlet::AbstractServlet
def do_GET(request,response)
resource = request.path.to_s[1..-1]
if resource == ""
response.body = DefaultBody
else
begin
response.body = File.read(resource)
rescue
response.status = 404
end
end
end
def do_EVAL(request,response)
begin
result = $evalBinding.eval(request.body)
response.body = {:type=>"DATA", :data=>result}.to_json
rescue Exception => e
response.body = {:type=>"ERROR",:data=>e.to_s}.to_json
end
end
def do_EXIT(request,response)
$server.shutdown
response.body = ""
#if chrome in debugmode then the following would be better:
#chrome.close
end
end
$server.mount '/', RequestHandler
$server.start
module GUI
require 'webrick'
require 'json'
require 'win32/registry'
INFOWORKS_ICON = "AAABAAIAICAAAAEAIAAoEAAAJgAAABAQAAABACAAKAQAAE4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8F29vbDt3d3Q/b29sO29vbDtvb2w7b29sO29vbDtvb2w7b29sO29vbDtvb2w7b29sO29vbDtvb2w7b29sO29vbDtvb2w7b29sO3d3dD9vb2w7j4+MJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3t7pKhYWDzWlpa/9oaGn/Z2do/2dnaP9nZ2j/Z2do/2dnaP9nZ2j/Z2do/2dnaP9nZ2j/Z2do/2dnaP9nZ2j/Z2do/2dnaP9nZ2j/Z2do/2dnaP9nZ2j/amhr/2hnaf92dnjQs7OzVAAAAAAAAAAAAAAAAAAAAAAAAAAAnZ2dh4qKjP+3t7n/qqqp/7GxsP+ysrL/srKy/7Kysv+8urT/uLi0/7Ozs/+ysrL/srKy/7Kysv+ysrL/srKy/7Kysv+ysrL/srKy/7Kysv+zs7P/vbe7/829x/+trK7/wbu//+zg6P99fH//jY2NkgAAAAAAAAAAAAAAALOzt0qpqan/oKCh/35+fv/X19f/7u7u//Hx8f/x8fH////2/77E5P/b3OT////v////9f////P/8fHx//Hx8f/x8fH/+vX1///6+P//+ff///n9///6//+11cD/UrJx/zCmWP8YjD7/CUwe/+7g6v+Af4H/s7OzVAAAAAAAAAAAp6enzqurq/9/f3/////////////////////////////j5vP/AATF/0pp9f8bPuz/HDnn/3yP7f/////////////////o7Oz/aMDO/33K3P+r1bf/UMZu/0C0Zv8xqFn/J6ZS/y6yWP8Ur0b/AE4S/+3i6/98fHzRAAAAAMzMzAWwsLL/dXV2/93d3f///////////////////////////wAZw/8IKeb/HUTt/z9s7/9NgvL/VoLs////////////usvP/wCMrf8AtNT/CbjW/wCZw/8ZhUb/KKNM/yelUv8fqU3/DKk//2DJf/+nwrD/wbzB/3Nzcv/f398I29vbDrKysv93d3j/9PT0//////////////////////9Va9T/ABzd/xc37/8vWO7/O2rv/zVq7f////r///////////8AiqP/AK7O/wCz0v8AttX/AMLn/wOQn/8Yojb/G6JH/4jMn//+9fr///////r6//+xs7T/d3d5/93d3Q/Pz88QtbW3/3t7fP/19fX/////////////////0NTu/wAN0v8QMO7/HDvs/zFb7v8jWe3/1t/z/////////////////wCbwP8AweH/AM7w/wDU+P8A0ff/ALDR/8/dvv//////////////////////xqeW/6yflv9+gIP/z8/PEM/PzxC3t7f/fn5+//b29v////////////////8AGtD/HULq/w4t6P8bPez/IU/u/4Kf7f////////////////++07P/AKWp/wDW//8M3v//G+D//wDd//9R2/P/////////////////7OLd/9lvL//SMwD/saii/4GEiP/Pz88Qz8/PELq6uv+AgID/9vb2////////////PVTa/ylT6P8JKOX/ETDp/xAy7P8wV+j///////////////////n+/weOMv80uk//Dc7b/xff//8m3fz/Xt7z///////6////5rKS/95mHf/lSAD/wkAA/7CQfP+5w8n/hYWG/8/PzxDPz88Qurq8/4ODhP/19fX//////7a+7P8bReT/Cyvg/w8v5f8QMOj/ABLo////+f////////////////8ml0z/KbNW/zjKZP8+y1n/ntWZ////////////1ryr/9ZQAP/iUQD/zUYA/6WIeP/i9///+f///7e3t/+IiIn/z8/PEM/PzxC9vb7/hYWF//n59v//////CC/k/yxM4P8KKt3/Di7k/wAQ5f+yvO//////////////////arWC/x+wTv8wvV//N8tp/1/PhP///////////7FxS//hTwD/4VcB/79pNP/g8//////////////w8PD/t7e3/4yMjf/Pz88Qz8/PEL6+v/+Ghoj////5/zFK6P9Xduj/AB/W/w4u2/8AHuH/TWPl/////////////////7zYx/8WrUb/LrFZ/zTJZP85x2j///3///////98gIT/3k8A/+FdDf/bg07//v///////////////////+/v7/+2trj/j4+Q/8/PzxDPz88Qv7/A/5iXjP+QmuX/Q2Tx/wAezv8MLtn/Byba/wEi2v/////////////////++Pz/GaZH/y2vV/8wulz/IsZX/+Tv5////////////1ZBNf/wZA7/3VcG////////////////////////////7+/v/7i4t/+RkZL/z8/PEM/PzxDAwMH/oJyO/1Vw5f8AG87/ABTM/wAg1f8ABtP/8/T2/////////////////zasXf8wsVr/La9Y/xzAUv+q4bv/////////////////RTQp//VnEf/cVAL/99/O///////////////////////v7+//t7e4/5SUlf/Pz88Qz8/PEMLCwf+MjIz//Prv/87T8f+Xpeb/T2XZ/52q5/////////////////9xwYv/NLNe/yqrVf8gslD/Y86E//////////r///////////9BTVb/2lUG/+RlF//cVAH/+NzO/////////////////+/v7/+4uLj/lZWW/8/PzxDPz88QwsLB/4qKi//39vX/////////////////////////////////ud3E/zm1Yv8lok7/Ka9U/ym2WP//////NEzT/w047P9viuz//////7a+w/8/GwP/+2wV/+BjFv/ZTAD/8LqY////////////7+/v/7i4uP+VlZb/z8/PEM/PzxDAwMH/ioqL//T09P/////////////////////////////8+v87uGP/JZ9N/y2rVv8OqED//////4+a6v8AFOn/G1Hs/+fv+P///////////2t8iP91KgD/9WwY/+BkGP/bTQD/7ad9///////w8PD/t7e4/5SUlf/Pz88Qz8/PEMDAwP+JiYr/9PT0///////////////////////h5u//Nal2/zifSv8sqE7/DaQ9/9Pxz//i3f//ABrg/wAU6v/L1/T//////////////////////z5SXf+qQgT/62ka/+BkGf/bTQD/8KuA//P///+4uLf/k5OU/8/PzxDPz88Qv7+//4iIif/19fT/////////////////CIKW/wCqzf8OvuX/AJnE/xKNS/+Cz5H//////w803f8AB97/jZvu////////////////////////////9/r9/yEmKP/tYw//4mYa/+BkGf/eUQD/5LSW/7nEzP+QkJH/z8/PEM/PzxC9vb7/hYWG//b29v///////////3Otuv8Ar9D/AKrI/wCtyv8IxOf/AIWi//////9UbuD/AADJ/1ht4///////////////////////////////////////jp2n/4U0Av/saRn/4GUa/+JlGP/NSAD/t7e3/4yOk//Pz88Qz8/PELy8vP+Jhon///3//83i1P90tHj/AJWa/wC22v8Axeb/AM3w/wDK7f8AuuL///Dy///////a3vL////////////////////////////////////////////r8vb/SysX//JpFv/gZRr/42Ya/89PAf+zrKf/io6R/8/PzxDPz88Qvru+/3l8e/8Qmz3/FaZF/yiwR/8PsZ//AMr5/wDa/v8O3v//CuH//wDO/f+5yaX///////////////////////////////////////////////////////////9IOC7/8mcT/+BlGv/jZhr/z1AC/7Wtqf+Gio3/z8/PEM/PzxC9ub3/cnl1/x6kSf8wvF7/Nslj/z7IWf8AyOP/AN///zTj//8y5P//KLt7/wqVLv+py7X//////////////////////////////////////////////////////1Y/L//wZxP/4GUa/+NmGv/PUAP/tKyo/4OGif/Pz88Qz8/PELq2uv9tdnH/GbdN/yrKX/8zyWT/aNCI/77ksP/k8Pv/hdW6/zTEVv87y1//MLhe/wqgO/+bx6n////////////////////////////////////////////s+f//eToT/+xnFv/gZRr/42Ya/9BRA/+zq6j/fYCD/8/PzxDb29sOuba3/2hzbP9lyIX/yO3U///8////////////////////////jtmk/zHKYv81yGT/L7NZ/xKrRv+Mwp7//////////////////////////////////////5WepP/aUQD/4mYa/+BlGv/jZhr/zk8C/7Gppv96foH/3d3dD8zMzAW6urr/c3B0//Hk7f//////////////////////////////////////ndqx/yjJXf8wwGD/La9X/x+yT/99wJP////////////////////////////T5fD/uEcB/+dmF//gZRr/4GUa/+dnGf+5QQD/sLe8/3h5e//f398IAAAAAKqqqs6oqKf/gYGB////////////////////////////////////////////sOPB/x/JV/8wuVz/Kq1V/yu3Wf92wZD/////////////////3/D6/7lJBf/qZBP/42Ya/+NmGv/nZxn/52IR/28wC//Ay9L/h4eJ0QAAAAAAAAAAs7OwStLS1P9xcXL/gICA/97e3v/29vb/+fn5//j4+P/4+Pj/+Pj4//j4+P//////vd7H/xy7T/8gqE3/Gp5F/yarUf9gtXz//////+bx+P+SPgv/4lYA/9dZC//YWQv/1lcJ/75IAP9uMQz/oq20/5WVmP+zs7NUAAAAAAAAAAAAAAAAm5ubh9PT0/+jo6P/ZmZm/29vb/9wcHD/cHBw/3BwcP9wcHD/cHBw/3BwcP+AdXz/b29v/2JrZ/9ka2f/Xmdi/1tqYP9mbGn/ZGNl/3FhWf9vZmP/bmZj/25mY/9tZmP/b3d9/6izuv+ur7H/lpaWkgAAAAAAAAAAAAAAAAAAAAAAAAAAsLCwSqqqqs23t7f/ra2t/6urq/+rq6v/q6ur/6urq/+rq6v/q6ur/6urq/+vrK7/sa2v/7Gtr/+xrbD/sK2v/62srP+tra3/rK+x/6yvsf+sr7H/rK+x/6uusv+wsrL/p6en0K2trVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMzMzAXb29sOzMzMD8jIyA7IyMgOyMjIDsjIyA7IyMgOyMjIDsjIyA7IyMgOyMjIDsjIyA7IyMgOyMjIDsjIyA7IyMgOyMjIDsjIyA7MzMwPyMjIDuPj4wkAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALGxsW+hoaGHoKCghqSkooagoKCGoKCghqCgoIagoKCGoKCghqSiooato6uIt7C3cf///wUAAAAAAAAAAJCQkPyhoaH/z8/P/97c0f/Kycj/7ebQ/9jWz//Rz8//6tjV/+7Z2f/d09j/eKyJ/2mSdv+VjZP+////BcrKym+SkpL////////////V2vT/ABvl/xdH8f/F0f7//////zW3zv9HvNH/KKRE/wmeOf8Hrz3/k6qd/7KwsnHCwsKJsrKy////////////ABTX/xc/7/9Le+7//////4vP5f8Asdj/AMX0/x+ki//D7M3//////8q6uP+nqauIwsLChrS0s///////U27g/wAi6P8DL+r////9//////8ppWH/ANv//w/k///8/////9fE/9daDf+ze1r/q7W5hsbGxobGxLr/z9f+/wAm3/8ACuP/t8D1//////9quoT/EbhE/5vhnf//+///3VIA/8xgH//p8/r/1+Dl/6urrYbKysiGuLa5/xc86P8ADtP/UGfn///////C4Mz/BKU4/03ReP//////mkAK/+yOVP///////////9DQ0P+vr6+GzszIhpyit/9BXN7/FjXV////////////FKNC/xu4Sv///////////4M0Av/weC7////////////Q0ND/r6+zhsjIyIa/vrr/////////////////RbRj/wOgMv/v/uX/AA3p/+33//+RmJv/z0IA/+FeDf//////0tfa/6+vs4bGxsiGubi4///////l6+3/LrXD/waNTP+x6av/DiXx/5Gg9P///////////2NcWP/uWQD/5F4L/9HY3P+vs7WGxsbGhs2/yf/7+Oj/AJy5/wC43/8Ast///+7//2x/5P/////////////////m+f//nTsA/+xfCf+9dEj/r7rAhtXK04YygUz/B68v/wC9rf8A5f//ANPq/6vNnf///////////////////////////49CFP/uYgz/v3lQ/6u3vobQxc+JQ5Je/4fmpf/k9Nf///j7/zzHVP8EqTj/ls6p/////////////////+T8///DRwD/6mIP/754Tf+psrqIysrMb52Tmv//////////////////////P9Nw/waqPP+L06P////////////KZyn/8GEK/+5bAf+bdVz/srm+cQAAAACkpKb8iYmL/62trv+tra3/tK+y/7uvuP8yhU7/I3c//4Shkf+Kb2T/oEwZ/5xUK/+EXUf/lJ6n/v///wUAAAAAAAAAAMfHx225ub2Hurq8hrq6vIa+vL6GzsDKhs7CyobAvsCGvsLGhr7K0Ya9yc6IydDUcf///wUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
STANDARD_ADDITIONS = <<-EOF
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<script>
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
window.addEventListener("load", function () {
var Ruby = function () {
function Ruby() {
_classCallCheck(this, Ruby);
}
Ruby.prototype.eval = function _eval(code, callback) {
var request = new XMLHttpRequest();
request.onload = function () {
callback(JSON.parse(this.responseText));
};
request.open("EVAL", "");
request.send(code);
};
Ruby.prototype.exit = function _eval(code, callback) {
var request = new XMLHttpRequest();
//request.onload = window.close
request.open("EXIT", "");
request.send();
return null;
};
return Ruby;
}();
window.Ruby = new Ruby();
});
window.addEventListener('beforeunload',function(){
return window.Ruby.exit()
});
</script>
EOF
#THIS IS REQUIRED IN ICM 6.5.6 DUE TO AN ERROR IN WEBRICK. IF LOGGER OTHERWISE REQUIRED, YOU CAN MAKE YOUR OWN.
#:Logger => WEBrick::Log.new(NullStream.new)
class NullStream
def <<(o); self; end
end
#A sandboxed binding
class Sandbox
def get_binding
binding
end
end
#Get default options each time they are requested (ensures logger and ruby binding are new)
def self.getDefaultGuiOptions()
{
:customIcon => "",
:useStandardAdditions => true,
:port => rand(10000..65535),
:documentRoot => Dir.pwd,
:runtime => nil,
:runtimePath => "",
:autoPlayPolicy => true,
:logger => WEBrick::Log.new(NullStream.new),
:rubyBinding => Sandbox.new.get_binding,
:rubyInitScript => "",
:exitIfListenFailure => true
}
end
def self.bestRuntime(os)
if os==:Win32
require 'win32/registry'
if Win32::Registry::HKEY_LOCAL_MACHINE.open('SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe')[""]
return :chrome
elsif Dir.entries("#{ENV["SystemRoot"]}\\SystemApps\\").select {|s| s[/Microsoft\.MicrosoftEdge.+/]}.length
return :edge
else
return :ie
end
elsif os==:Mac
apps = Dir.entries("/Applications/")
if apps.include? "Google Chrome.app"
return :chrome
elsif apps.include? "Safari.app"
return :safari
end
elsif os==:Linux
#Not sure how to test for an application being installed
#so currently we'll simply brute force instead.
return :linux
end
return :unknown
end
def self.launchRuntime(os,url,options)
retHash = {:os=>os,:url=>url,:type=>options[:runtime],:object=>nil}
case os
when :Win32
case options[:runtime]
when :chrome
chromePath = options[:runtimePath]=="" ? Win32::Registry::HKEY_LOCAL_MACHINE.open('SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe')[""] : options[:runtimePath]
cmdArgs = "--app=\"#{url}\""
if options[:autoPlayPolicy]
cmdArgs += " --autoplay-policy=no-user-gesture-required"
end
system("\"#{chromePath}\" #{cmdArgs}")
#TODO OBJECT => WRAPPED DEBUG_PROTOCOL
#retHash[:object] = chrome
return retHash
when :edge
system("start microsoft-edge:#{url}")
#TODO OBJECT => WRAPPED DEBUG_PROTOCOL
#retHash[:object] = chrome
return retHash
when :ie
require 'win32ole'
ie = WIN32OLE.new("InternetExplorer.Application")
ie.Navigate(url)
ie.AddressBar = false
retHash[:object] = ie
return retHash
end
when :Mac
case options[:runtime]
when :chrome
cmdArgs = "--app=\"#{url}\""
if options[:autoPlayPolicy]
cmdArgs += " --autoplay-policy=no-user-gesture-required"
end
system("open -n -a \"Google Chrome\" --args #{cmdArgs}")
#TODO OBJECT => WRAPPED DEBUG_PROTOCOL
#retHash[:object] = chrome
return retHash
when :safari
system("open -a Safari #{url}")
#TODO OBJECT => WRAPPED OSAScript instance --> XX_Safari.rb
#retHash[:object] = safari
return retHash
end
when :Linux
#chromium/chrome args:
cmdArgs = "--app=\"#{url}\""
if options[:autoPlayPolicy]
cmdArgs += " --autoplay-policy=no-user-gesture-required"
end
#Not sure how to test for specific runtimes in linux, so for now they shall be ignored
case true
when system("chromium-browser #{cmdArgs}")
#TODO OBJECT => WRAPPED DEBUG_PROTOCOL
#retHash[:object] = chrome
return retHash
when system("google-chrome #{cmdArgs}")
#TODO OBJECT => WRAPPED DEBUG_PROTOCOL
#retHash[:object] = chrome
return retHash
end
end
return nil
end
def self.platform
if (/cygwin|mswin|mingw|bccwin|wince|emx/i =~ RUBY_PLATFORM) != nil
return :Win32
elsif (/darwin/i =~ RUBY_PLATFORM) != nil
return :Mac
elsif (/linux/i =~ RUBY_PLATFORM) != nil
return :Linux
else
return :Unknown
end
end
module EvalHandler
def do_EVAL(request,response)
begin
result = @parent.options[:rubyBinding].eval(request.body)
response.body = {:type=>"DATA", :data=>result}.to_json
rescue Exception => e
response.body = {:type=>"ERROR",:data=>e.inspect}.to_json
end
end
end
module ExitHandler
def do_EXIT(request,response)
@parent.server.shutdown
response.body = ""
end
end
class RequestHandler < WEBrick::HTTPServlet::AbstractServlet
include EvalHandler
include ExitHandler
def initialize(server,parent)
super(server)
@parent = parent
end
def do_GET(request,response)
@parent.received_requests = true
if request.path == "/"
response.body = @parent.defaultHTML
elsif request.path == "/favicon.ico"
require 'Base64'
if @parent.options[:customIcon]!=""
response.body = Base64.decode64(@parent.options[:customIcon])
return
else
response.body = Base64.decode64(INFOWORKS_ICON)
return
end
else
response.body = File.read(Dir.pwd + request.path)
end
end
end
class GUI
attr_reader :defaultHTML, :options
attr_accessor :server, :runtime, :received_requests
def setArg(symbol,value)
if !@running
eval("@#{symbol}=JSON.parse('#{value.to_json}')")
end
end
def initialize(defaultHTML,options={})
@defaultHTML = defaultHTML
#Merge options given with default options
@options = ::GUI::getDefaultGuiOptions().merge(options)
#Run initialize script - This can be used to instantiate standard methods
@options[:rubyBinding].eval(@options[:rubyInitScript])
#Making ruby console compatible on all OSes
url = "http://localhost:#{@options[:port]}"
if (os = ::GUI.platform)==:Unknown
puts "Trapped Error: #{__LINE__}:: Platform unknown"
exit
end
#Get best runtime if not otherwise provided:
if @options[:runtime]==nil
if (@options[:runtime]=::GUI.bestRuntime(os))==:unknown
puts "Trapped Error: #{__LINE__}:: Runtime unknown"
exit
end
end
#Launch GUI based on runtime:
if !(@runtime = ::GUI.launchRuntime(os,url,@options))
puts "Trapped Error: #{__LINE__}:: Undefined runtime execution for runtime specified"
exit
end
#Create server instance
@server = WEBrick::HTTPServer.new(
:Port =>@options[:port],
:DocumentRoot => @options[:documentRoot],
:Logger => @options[:logger], #fixes some bugs...
:AccessLog => []
)
#Trap interupts and safely shutdown server
trap 'INT' do
@server.shutdown
end
#Inject standard html additions:
if @options[:useStandardAdditions]
if @defaultHTML[/<head>/]
@defaultHTML = @defaultHTML.sub("<head>","<head>#{STANDARD_ADDITIONS}\r\n\r\n")
elsif @defaultHTML[/<html>/]
@defaultHTML = @defaultHTML.sub("<html>","<html><head>#{STANDARD_ADDITIONS}</head>")
else
@defaultHTML = "<head>" + STANDARD_ADDITIONS + "</head>\r\n\r\n" + @defaultHTML
end
end
@server.mount '/', RequestHandler, self
end
#Start server method:
def show()
if @options[:exitIfListenFailure]
Thread.new do
Kernel.sleep(10)
if !@received_requests
puts "Error occurred while listening on port #{@options[:port]}"
end
end
end
@running=true
@server.start
end
#Start server in new thread. Return the thread for easy syncing
def showAsync()
return Thread.new do
@running=true
@server.start
end
end
end
end
require_relative "3_GUIWrapper.rb"
Default = <<EOF
<title>Simple</title>
<h1>Hello world</h1>
<button style="width:100px;height:30px;" onclick="Ruby.eval('27+79',function(response){alert('27+79 ==> ' + response.data)})"></button>
EOF
gui = GUI::GUI.new(Default)
gui.show()
# This works fully in ICM 6.5.6 and uses standard Web server libraries
require_relative "3_GUIWrapper.rb"
# Define the HTML body
DefaultBody=<<DEFAULT_BODY
<html>
<!--< REQUIRES "autoplay-policy" = "no-user-gesture-required" command line argument to be set >-->
<head>
<title> My Water Software </title>
<style>
body {
overflow: hidden;
margin: 0;
height: 100%;
}
.title {
position: absolute;
left: 0px;
width:100%;
text-align: center;
top: 5%;
font-weight:bold;
font-size: 98pt;
font-family:helvetica;
background: -webkit-linear-gradient(#bbf, #eef);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
user-select: none;
}
.renderer {
width:100%;
}
div.container {
position:absolute;
top:40%;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%)
}
button {
display:inline-block;
height:60px;
font-size:1.6em;
padding:0 2em;
cursor:pointer;
user-select: none;
}
button:focus {outline:0;}
button.run{
background:#1AAB8A;
color:#fff;
}
button.run:hover{
background:#fff;
color:#1AAB8A;
}
button.exit{
background:#8e1313;
color:#fff;
}
button.exit:hover{
background:#fff;
color:#8e1313;
}
</style>
<script>
window.console = window.console || function(t) {};
</script>
<!-- THIS is OPENGL Shading language scripts -->
<script id="vertex-shader" type="no-js">
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<!-- original from https://www.shadertoy.com/view/Ms2SD1 -->
<script id="fragment-shader" type="no-js">
uniform float iGlobalTime;
uniform vec2 iResolution;
uniform vec4 iMouse;
const int NUM_STEPS = 8;
const float PI = 3.1415;
const float EPSILON = 1e-3;
float EPSILON_NRM = 0.1 / iResolution.x;
// sea
const int ITER_GEOMETRY = 3;
const int ITER_FRAGMENT = 5;
const float SEA_HEIGHT = 0.6;
const float SEA_CHOPPY = 4.0;
const float SEA_SPEED = 0.8;
const float SEA_FREQ = 0.16;
const vec3 SEA_BASE = vec3(0.1,0.19,0.22);
const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
float SEA_TIME = iGlobalTime * SEA_SPEED;
mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);
// math
mat3 fromEuler(vec3 ang) {
vec2 a1 = vec2(sin(ang.x),cos(ang.x));
vec2 a2 = vec2(sin(ang.y),cos(ang.y));
vec2 a3 = vec2(sin(ang.z),cos(ang.z));
mat3 m;
m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x);
m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);
m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y);
return m;
}
float hash( vec2 p ) {
float h = dot(p,vec2(127.1,311.7));
return fract(sin(h)*43758.5453123);
}
float noise( in vec2 p ) {
vec2 i = floor( p );
vec2 f = fract( p );
vec2 u = f*f*(3.0-2.0*f);
return -1.0+2.0*mix(
mix( hash( i + vec2(0.0,0.0) ),
hash( i + vec2(1.0,0.0) ), u.x),
mix( hash( i + vec2(0.0,1.0) ),
hash( i + vec2(1.0,1.0) ), u.x), u.y);
}
// lighting
float diffuse(vec3 n,vec3 l,float p) {
return pow(dot(n,l) * 0.4 + 0.6,p);
}
float specular(vec3 n,vec3 l,vec3 e,float s) {
float nrm = (s + 8.0) / (3.1415 * 8.0);
return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
}
// sky
vec3 getSkyColor(vec3 e) {
e.y = max(e.y,0.0);
vec3 ret;
ret.x = pow(1.0-e.y,2.0);
ret.y = 1.0-e.y;
ret.z = 0.6+(1.0-e.y)*0.4;
return ret;
}
// sea
float sea_octave(vec2 uv, float choppy) {
uv += noise(uv);
vec2 wv = 1.0-abs(sin(uv));
vec2 swv = abs(cos(uv));
wv = mix(wv,swv,wv);
return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
}
float map(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
float d, h = 0.0;
for(int i = 0; i < ITER_GEOMETRY; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= octave_m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
float map_detailed(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
float d, h = 0.0;
for(int i = 0; i < ITER_FRAGMENT; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= octave_m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) {
float fresnel = 1.0 - max(dot(n,-eye),0.0);
fresnel = pow(fresnel,3.0) * 0.65;
vec3 reflected = getSkyColor(reflect(eye,n));
vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12;
vec3 color = mix(refracted,reflected,fresnel);
float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
color += vec3(specular(n,l,eye,60.0));
return color;
}
// tracing
vec3 getNormal(vec3 p, float eps) {
vec3 n;
n.y = map_detailed(p);
n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
n.y = eps;
return normalize(n);
}
float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) {
float tm = 0.0;
float tx = 1000.0;
float hx = map(ori + dir * tx);
if(hx > 0.0) return tx;
float hm = map(ori + dir * tm);
float tmid = 0.0;
for(int i = 0; i < NUM_STEPS; i++) {
tmid = mix(tm,tx, hm/(hm-hx));
p = ori + dir * tmid;
float hmid = map(p);
if(hmid < 0.0) {
tx = tmid;
hx = hmid;
} else {
tm = tmid;
hm = hmid;
}
}
return tmid;
}
void main() {
vec2 uv = gl_FragCoord.xy / iResolution.xy;
uv = uv * 2.0 - 1.0;
uv.x *= iResolution.x / iResolution.y;
float time = iGlobalTime * 0.3 + iMouse.x*0.01;
// ray
vec3 ang = vec3(0,0,0);
vec3 ori = vec3(0.0,3.5,time*5.0);
vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15;
dir = normalize(dir) * fromEuler(ang);
// tracing
vec3 p;
heightMapTracing(ori,dir,p);
vec3 dist = p - ori;
vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM);
vec3 light = normalize(vec3(0.0,1.0,0.8));
// color
vec3 color = mix(
getSkyColor(dir),
getSeaColor(p,n,light,dir,dist),
pow(smoothstep(0.0,-0.05,dir.y),0.3));
// post
gl_FragColor = vec4(pow(color,vec3(0.75)), 1.0);
}
</script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js'></script>
<script>
// init camera, scene, renderer
var scene, camera, renderer;
scene = new THREE.Scene();
var fov = 75,
aspect = window.innerWidth / window.innerHeight;
camera = new THREE.PerspectiveCamera(fov, aspect, 0.1, 1000);
camera.position.z = 100;
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xc4c4c4);
renderer.setSize(window.innerWidth, window.innerHeight);
window.setTimeout(function(){
document.getElementsByClassName("renderer")[0].appendChild(renderer.domElement);
})
var clock = new THREE.Clock();
var tuniform = {
iGlobalTime: {
type: 'f',
value: 0.1
},
iResolution: {
type: 'v2',
value: new THREE.Vector2()
},
iMouse: {
type: 'v4',
value: new THREE.Vector2()
}
};
// Mouse position in - 1 to 1
renderer.domElement.addEventListener('mousedown', function(e) {
var canvas = renderer.domElement;
var rect = canvas.getBoundingClientRect();
tuniform.iMouse.value.x = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
tuniform.iMouse.value.y = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
renderer.domElement.addEventListener('mouseup', function(e) {
var canvas = renderer.domElement;
var rect = canvas.getBoundingClientRect();
tuniform.iMouse.value.z = (e.clientX - rect.left) / window.innerWidth * 2 - 1;
tuniform.iMouse.value.w = (e.clientY - rect.top) / window.innerHeight * -2 + 1;
});
// resize canvas function
window.addEventListener('resize',function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
tuniform.iResolution.value.x = window.innerWidth;
tuniform.iResolution.value.y = window.innerHeight;
// Create Plane
var material = new THREE.ShaderMaterial({
uniforms: tuniform,
vertexShader: document.getElementById('vertex-shader').textContent,
fragmentShader: document.getElementById('fragment-shader').textContent
});
var mesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(window.innerWidth, window.innerHeight, 40), material
);
scene.add(mesh);
// draw animation
function render(time) {
tuniform.iGlobalTime.value += clock.getDelta();
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
</script>
<script>
if (document.location.search.match(/type=embed/gi)) {
window.parent.postMessage("resize", "*");
}
</script>
<script>
window.addEventListener("load",function(){
document.getElementById("wavesAudio").play()
function evaluateRuby(script,callback){
request = new XMLHttpRequest
request.onload = function(){
callback(this.responseText)
}
request.open("EVAL","")
request.send(script)
}
document.querySelector(".run.button").addEventListener("click",function(){
evaluateRuby("puts \\"hello world\\"",function(){console.log("Executed")})
})
document.querySelector(".exit.button").addEventListener("click",function(){
request = new XMLHttpRequest
request.open("EXIT","")
request.send()
window.setTimeout(window.close,500)
})
})
//Exit before unload
window.addEventListener('beforeunload',function(){
request = new XMLHttpRequest
request.open("EXIT","")
request.send()
window.setTimeout(window.close)
return null
});
</script>
</head>
<body>
<audio hidden controls autoplay loop id="wavesAudio"><source src="4_waves.mp3" type="audio/mpeg"></audio>
<div class="renderer"></div>
<div class="title">NATURAL WATER</div>
<div class="container">
<button class="run button"> Run </button>
<button class="exit button"> Exit</button>
</div>
</body>
</html>
DEFAULT_BODY
# Create a new GUI
gui = GUI::GUI.new(DefaultBody)
# Show the GUI syncronously
gui.show()
This file has been truncated, but you can view the full file.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment