H
Hidetoshi NAGAI
Hi,
4 months ago, I wrote about the framework of Ruby/Tk + VNC
([ruby-talk:145031]).
And now, I've exhibited a new demo to try its concept.
The previous demo has the client script embedded in the main script.
But the main script of this demo can load the external file as the
client script which can work as a standalone Ruby/Tk script.
It means that steps from developpng on local to opening to the net
are seamless.
You can also try its interactivity.
One of the client script is an animation demo quoted from "Ruby/Tk
Widget Demo".
Please access 131.206.154.81:5933 by VNC viewer,
or http://131.206.154.81/ by web browser.
In both case, your firewall must pass through the packets to
IP addr: 131.206.154.81 and port: 5933.
# When by a web browser, a dialog to require password is shown.
# Please ignore it and press the "OK" button with empty password.
Any comments (or wish to participate in the development ;-))
are welcome.
Hidetoshi NAGAI ([email protected])
---------------------------------------------------------------
The following is the sample script.
The main script expects that the main and the 2 client scripts are
placed on the same directory.
=======< wrap-test-main2.rb >=========================================
#!/usr/bin/env ruby
require 'multi-tk'
require 'tk/canvas'
class Window_Frame < TkcWindow
def _title_bind
@titlebar.bind('ButtonPress-1',
proc{|rx, ry|
@rx = rx; @ry = ry; @sx, @sy = self.coords
@base.raise
}, '%X', '%Y')
@titlebar.bind('B1-Motion',
proc{|rx, ry|
wx = @wall.winfo_rootx; wy = @wall.winfo_rooty
if rx > wx && rx < wx + @wall.width &&
ry > wy && ry < wy + @wall.height
self.coords = [@sx + (rx - @rx), @sy + (ry - @ry)]
end
}, '%X', '%Y')
end
def initialize(wall, title, *args)
@wall = wall
@base = TkFrame.new(@wall, :borderwidth=>3, :relief=>:ridge)
@titlebar = TkLabel.new(@base, :text=>" #{title} ", :relief=>:raised,
:foreground=>'white',
:background=>'midnight blue').packfill=>:x)
@container = TkFrame.new(@base, :container=>true).packfill=>:both,
:expand=>true)
@container.bind('Destroy'){self.destroy}
super(wall, *args)
self.window = @base
_title_bind
end
def winid
@container.winfo_id
end
end
def new_toplevel(wall, ip, top)
w = Window_Frame.new(wall, "toplevel(#{top})", 200, 150, :anchor=>:nw)
MultiTkIp.invoke_hidden(ip, 'toplevel', top, '-use', w.winid)
end
class TOPLEVEL_ARG < Exception
def self.new(path, *args)
obj = super(path)
obj.instance_variable_set('@args', args)
obj
end
alias value message
attr_reader :args
end
def replace_toplevel_cmd(wall, slave_ip)
th = Thread.new(wall, slave_ip){|w, ip|
begin
Thread.stop
rescue TOPLEVEL_ARG => arg
begin
new_toplevel(w, ip, arg.value, *(arg.args))
rescue Exception => e
p e
end
retry
end
}
cmd = TkComm._get_eval_string(proc{|t, *args|
th.raise(TOPLEVEL_ARG.new(t, *args))
until slave_ip.eval_proc{TkWinfo.exist?(t)}
Thread.pass
Tk.update
end
})
MultiTkIp.hide_cmd(slave_ip, 'toplevel') unless slave_ip.safe?
slave_ip._eval("proc toplevel {path args} {eval \"#{cmd} $path $args\"}")
slave_ip._eval("proc wm {args} {}")
end
##############################################
timeout = 60
wall = TkCanvas.newbackground=>'skyblue').pack
#wall = TkCanvas.newwidth=>500, :height=>400, :background=>'skyblue').pack
#wall = TkCanvas.newwidth=>800, :height=>600, :background=>'skyblue').pack
wall.xscrollbar(h_scroll = TkScrollbar.newwidth=>10))
wall.yscrollbar(v_scroll = TkScrollbar.newwidth=>10))
TkGrid.rowconfigure(Tk.root, 0, 'weight'=>1, 'minsize'=>0)
TkGrid.columnconfigure(Tk.root, 0, 'weight'=>1, 'minsize'=>0)
wall.gridrow=>0, :column=>0, :sticky=>'news')
h_scroll.gridrow=>1, :column=>0, 'sticky'=>'ew')
v_scroll.gridrow=>0, :column=>1, 'sticky'=>'ns')
TkcText.new(wall, 150, 50, :fill=>'navyblue', :font=>'courier -14',
:text=>"Ruby/Tk+VNC :: concept example")
TkcText.new(wall, 150, 300,
:text=>"This example will exit in #{timeout} seconds.")
TkcText.new(wall, 350, 340,
:text=>"This is Master IP's canvas widget.\n\t($SAFE==#{$SAFE})")
Tk.update
Tk.root.geometry('500x380')
Tk.after(timeout * 1000){exit}
ev_thread = Thread.new{Tk.mainloop}
#=========================================================#
demos = [
['wrap-test-target.rb', 'Simple Demo: create a new toplevel'],
['pendulum.rb', 'Test of interactivity (Animation demo)'],
]
v = TkVariable.new('')
sel_frame = TkFrame.new(wall, :relief=>:flat, :borderwidth=>3)
TkLabel.new(sel_frame,
:text=>'Please select the demo which you want to try.',
:foreground=>'red').packpadx=>10, ady=>5)
demos.each{|file, label|
TkButton.new(sel_frame, :text=>label, #:anchor=>:w,
:command=>proc{
v.value = file
sel_frame.destroy
}).packfill=>:x)
}
TkcWindow.new(wall, 40, 100, :anchor=>:nw, :window=>sel_frame)
Tk.update
sel_frame.wait
#p v.value
exit if v.value.empty?
dirname = File.dirname(File.expand_path(__FILE__))
#target_file = ARGV.shift.dup
#target_file = File.expand_path(ARGV.shift.dup)
target_file = File.join(dirname, v.value)
#=========================================================#
wall.scrollregion = [0, 0, 1024, 768]
TkcText.new(wall, 370, 170, :text=><<EOT)
The root window of the safeTk IP is
embedded in Master IP's frame widget.
You can move the root window by
'Button-1 + Motion' on the titlebar.
Master IP works like as a window manager.
No window manager on the VNC server.
Running one Ruby/Tk process only.
EOT
#'
w = Window_Frame.new(wall, 'slave root', 50, 70, :anchor=>:nw)
w.instance_variable_get@container).bind_append('Destroy'){Tk.exit}
#ip = MultiTkIp.new_trusted_slaveuse=>w.winid)
#ip = MultiTkIp.new_trusted_slave(3, :use=>w.winid)
#ip = MultiTkIp.new_trusted_slave(4, :use=>w.winid)
#ip = MultiTkIp.new_safeTk(0, :use=>w.winid)
#ip = MultiTkIp.new_safeTk(3, :use=>w.winid)
ip = MultiTkIp.new_safeTkuse=>w.winid)
replace_toplevel_cmd(wall, ip)
#p ['on main', __FILE__]
#=========================================================#
target_file.untaint
#ldr = proc{|f| $SAFE=ip.safe_level; load f,false}
xp = proc{|val| p val}
alias __require__ require
@req = proc{|f|
# p ['@req', f]
__require__(f)}
def require(f)
@req.call(f)
end
#xp.call '-------------------'
ip.bg_eval_proc{
# xp.call self
begin
# xp.call target_file
#ldr.call target_file
load target_file, true
rescue => e
xp.call e if $DEBUG
end
}
#xp.call '-------------------'
=begin
Tk.after(5000){
w.instance_variable_get@container).configurewidth=>450, :height=>250)
}
Tk.after(10000){
w.instance_variable_get@container).configurewidth=>500, :height=>600)
}
=end
#p 'join'
ev_thread.join
======================================================================
=======< wrap-test-target.rb >========================================
require 'tk'
p ['on target', __FILE__] if $SAFE < 4
TkLabel.newtext=>"safeTk interpreter's root").packpadx=>10, ady=>5)
top = nil
cnt = 0
b1 = TkButton.newtext=>'create Toplevel')
b2 = TkButton.newtext=>'add label to the toplevel', :state=>:disabled,
:command=>proc{
cnt += 1
TkLabel.new(top,
:text=>"Pressed(#{cnt})!! $SAFE=#{$SAFE}"
).pack
})
b1[:command] = proc{
top = TkToplevel.new
b1[:state] = :disabled
b2[:state] = :active
TkLabel.new(top,
:text=>'New toplevel of slaveIP').packpadx=>20, ady=>30)
}
label = TkLabel.newforeground=>'red', :text=>"\n")
timer = TkTimer.new(500, 1, proc{label.text = "\n"})
TkButton.newtext=>'BUTTON',
:command=>proc{
timer.cancel
label.text = "button is pressed!!\n($SAFE==#{$SAFE})"
timer.start
}).packpadx=>5, ady=>5, :fill=>:x)
label.pack
Tk.pack(b1, b2, :fill=>:x, adx=>5, ady=>5)
Tk.mainloop
======================================================================
=======< pendulum.rb >================================================
#!/usr/bin/env ruby
#
# based on Tcl/Tk8.5a2 widget demos
#
require 'tk'
#TkRoot.newtitle=>'Pendulum Animation Demonstration',
# :iconname=>"pendulum")
# create label
msg = TkLabel.new {
font 'Helvetica -12'
wraplength '4i'
justify 'left'
text 'This demonstration shows how Ruby/Tk can be used to carry out animations that are linked to simulations of physical systems. In the left canvas is a graphical representation of the physical system itself, a simple pendulum, and in the right canvas is a graph of the phase space of the system, which is a plot of the angle (relative to the vertical) against the angular velocity. The pendulum bob may be repositioned by clicking and dragging anywhere on the left canvas.'
}
msg.pack('side'=>'top')
# animated wave
class PendulumAnimationDemo
def initialize(frame)
TkFrame.new(frame) {|f|
TkButton.new(f, :command=>proc{Tk.root.destroy},
:text=>'Dismiss').packside=>:left, :expand=>true)
}.packside=>:bottom, :fill=>:x, ady=>'2m')
# Create some structural widgets
pane = TkPanedWindow.new(frame #, :width=>350, :height=>200
).packfill=>:both, :expand=>true)
@lf1 = TkLabelFrame.new(pane, :text=>'Pendulum Simulation')
@lf2 = TkLabelFrame.new(pane, :text=>'Phase Space')
# Create the canvas containing the graphical representation of the
# simulated system.
@c = TkCanvas.new(@lf1, :width=>320, :height=>200, :background=>'white',
:borderwidth=>2, :relief=>:sunken)
TkcText.new(@c, 5, 5, :anchor=>:nw,
:text=>'Click to Adjust Bob Start Position')
# Coordinates of these items don't matter; they will be set properly below
@plate = TkcLine.new(@c, 0, 25, 320, 25, :width=>2, :fill=>'grey50')
@rod = TkcLine.new(@c, 1, 1, 1, 1, :width=>3, :fill=>'black')
@Bob = TkcOval.new(@c, 1, 1, 2, 2,
:width=>3, :fill=>'yellow', utline=>'black')
TkcOval.new(@c, 155, 20, 165, 30, :fill=>'grey50', utline=>'')
# pack
@c.packfill=>:both, :expand=>true)
# Create the canvas containing the phase space graph; this consists of
# a line that gets gradually paler as it ages, which is an extremely
# effective visual trick.
@k = TkCanvas.new(@lf2, :width=>320, :height=>200, :background=>'white',
:borderwidth=>2, :relief=>:sunken)
@y_axis = TkcLine.new(@k, 160, 200, 160, 0, :fill=>'grey75', :arrow=>:last)
@x_axis = TkcLine.new(@k, 0, 100, 320, 100, :fill=>'grey75', :arrow=>:last)
@graph = {}
90.step(0, -10){|i|
# Coordinates of these items don't matter;
# they will be set properly below
@graph = TkcLine.new(@k, 0, 0, 1, 1, :smooth=>true, :fill=>"grey#{i}")
}
# labels
@label_theta = TkcText.new(@k, 0, 0, :anchor=>:ne,
:text=>'q', :font=>'Symbol 8')
@label_dtheta = TkcText.new(@k, 0, 0, :anchor=>:ne,
:text=>'dq', :font=>'Symbol 8')
# pack
@k.packfill=>:both, :expand=>true)
# Initialize some variables
@points = []
@theta = 45.0
@dTheta = 0.0
@length = 150
# init display
showPendulum
# animation loop
@timer = TkTimer.new(15){ repeat }
# binding
@c.bindtags_unshift(btag = TkBindTag.new)
btag.bind('Destroy'){ @timer.stop }
btag.bind('1', proc{|x, y| @timer.stop; showPendulum(x, y)}, '%x %y')
btag.bind('B1-Motion', proc{|x, y| showPendulum(x, y)}, '%x %y')
btag.bind('ButtonRelease-1',
proc{|x, y| showPendulum(x, y); @timer.start }, '%x %y')
btag.bind('Configure', proc{|w| @plate.coords(0, 25, w, 25)}, '%w')
@k.bind('Configure', proc{|h, w|
@psh = h/2;
@psw = w/2
@x_axis.coords(2, @psh, w-2, @psh)
@y_axis.coords(@psw, h-2, @psw, 2)
@label_theta.coords(@psw-4, 6)
@label_dtheta.coords(w-6, @psh+4)
}, '%h %w')
# add to pane
Tk.update
pane.add(@lf1, @lf2) # must be called after setting 'Configure' binding
# animation start
@timer.start(500)
end
# This procedure makes the pendulum appear at the correct place on the
# canvas. If the additional arguments x, y are passed instead of computing
# the position of the pendulum from the length of the pendulum rod and its
# angle, the length and angle are computed in reverse from the given
# location (which is taken to be the centre of the pendulum bob.)
def showPendulum(x=nil, y=nil)
if x && y && (x != 160 || y != 25)
@dTheta = 0.0
x2 = x - 160
y2 = y - 25
@length = Math.hypot(x2, y2)
@theta = Math.atan2(x2,y2)*180/Math:I
else
angle = @theta*Math:I/180
x = 160 + @length*Math.sin(angle)
y = 25 + @length*Math.cos(angle)
end
@rod.coords(160, 25, x, y)
@bob.coords(x-15, y-15, x+15, y+15)
end
# Update the phase-space graph according to the current angle and the
# rate at which the angle is changing (the first derivative with
# respect to time.)
def showPhase
@points << @theta + @psw << -20*@dTheta + @psh
if @points.length > 100
@points = @points[-100..-1]
end
(0...100).step(10){|i|
first = - i
last = 11 - i
last = -1 if last >= 0
next if first > last
lst = @points[first..last]
@graph.coords(lst) if lst && lst.length >= 4
}
end
# This procedure is the "business" part of the simulation that does
# simple numerical integration of the formula for a simple rotational
# pendulum.
def recomputeAngle
scaling = 3000.0/@length/@length
# To estimate the integration accurately, we really need to
# compute the end-point of our time-step. But to do *that*, we
# need to estimate the integration accurately! So we try this
# technique, which is inaccurate, but better than doing it in a
# single step. What we really want is bound up in the
# differential equation:
# .. - sin theta
# theta + theta = -----------
# length
# But my math skills are not good enough to solve this!
# first estimate
firstDDTheta = -Math.sin(@theta * Math:I/180) * scaling
midDTheta = @dTheta + firstDDTheta
midTheta = @theta + (@dTheta + midDTheta)/2
# second estimate
midDDTheta = -Math.sin(midTheta * Math:I/180) * scaling
midDTheta = @dTheta + (firstDDTheta + midDDTheta)/2
midTheta = @theta + (@dTheta + midDTheta)/2
# Now we do a double-estimate approach for getting the final value
# first estimate
midDDTheta = -Math.sin(midTheta * Math:I/180) * scaling
lastDTheta = midDTheta + midDDTheta
lastTheta = midTheta + (midDTheta+ lastDTheta)/2
# second estimate
lastDDTheta = -Math.sin(lastTheta * Math:I/180) * scaling
lastDTheta = midDTheta + (midDDTheta + lastDDTheta)/2
lastTheta = midTheta + (midDTheta + lastDTheta)/2
# Now put the values back in our globals
@dTheta = lastDTheta
@theta = lastTheta
end
# This method ties together the simulation engine and the graphical
# display code that visualizes it.
def repeat
# Simulate
recomputeAngle
# Update the display
showPendulum
showPhase
end
end
# Start the animation processing
PendulumAnimationDemo.new(Tk.root)
Tk.mainloop
======================================================================
4 months ago, I wrote about the framework of Ruby/Tk + VNC
([ruby-talk:145031]).
And now, I've exhibited a new demo to try its concept.
The previous demo has the client script embedded in the main script.
But the main script of this demo can load the external file as the
client script which can work as a standalone Ruby/Tk script.
It means that steps from developpng on local to opening to the net
are seamless.
You can also try its interactivity.
One of the client script is an animation demo quoted from "Ruby/Tk
Widget Demo".
Please access 131.206.154.81:5933 by VNC viewer,
or http://131.206.154.81/ by web browser.
In both case, your firewall must pass through the packets to
IP addr: 131.206.154.81 and port: 5933.
# When by a web browser, a dialog to require password is shown.
# Please ignore it and press the "OK" button with empty password.
Any comments (or wish to participate in the development ;-))
are welcome.
Hidetoshi NAGAI ([email protected])
---------------------------------------------------------------
The following is the sample script.
The main script expects that the main and the 2 client scripts are
placed on the same directory.
=======< wrap-test-main2.rb >=========================================
#!/usr/bin/env ruby
require 'multi-tk'
require 'tk/canvas'
class Window_Frame < TkcWindow
def _title_bind
@titlebar.bind('ButtonPress-1',
proc{|rx, ry|
@rx = rx; @ry = ry; @sx, @sy = self.coords
@base.raise
}, '%X', '%Y')
@titlebar.bind('B1-Motion',
proc{|rx, ry|
wx = @wall.winfo_rootx; wy = @wall.winfo_rooty
if rx > wx && rx < wx + @wall.width &&
ry > wy && ry < wy + @wall.height
self.coords = [@sx + (rx - @rx), @sy + (ry - @ry)]
end
}, '%X', '%Y')
end
def initialize(wall, title, *args)
@wall = wall
@base = TkFrame.new(@wall, :borderwidth=>3, :relief=>:ridge)
@titlebar = TkLabel.new(@base, :text=>" #{title} ", :relief=>:raised,
:foreground=>'white',
:background=>'midnight blue').packfill=>:x)
@container = TkFrame.new(@base, :container=>true).packfill=>:both,
:expand=>true)
@container.bind('Destroy'){self.destroy}
super(wall, *args)
self.window = @base
_title_bind
end
def winid
@container.winfo_id
end
end
def new_toplevel(wall, ip, top)
w = Window_Frame.new(wall, "toplevel(#{top})", 200, 150, :anchor=>:nw)
MultiTkIp.invoke_hidden(ip, 'toplevel', top, '-use', w.winid)
end
class TOPLEVEL_ARG < Exception
def self.new(path, *args)
obj = super(path)
obj.instance_variable_set('@args', args)
obj
end
alias value message
attr_reader :args
end
def replace_toplevel_cmd(wall, slave_ip)
th = Thread.new(wall, slave_ip){|w, ip|
begin
Thread.stop
rescue TOPLEVEL_ARG => arg
begin
new_toplevel(w, ip, arg.value, *(arg.args))
rescue Exception => e
p e
end
retry
end
}
cmd = TkComm._get_eval_string(proc{|t, *args|
th.raise(TOPLEVEL_ARG.new(t, *args))
until slave_ip.eval_proc{TkWinfo.exist?(t)}
Thread.pass
Tk.update
end
})
MultiTkIp.hide_cmd(slave_ip, 'toplevel') unless slave_ip.safe?
slave_ip._eval("proc toplevel {path args} {eval \"#{cmd} $path $args\"}")
slave_ip._eval("proc wm {args} {}")
end
##############################################
timeout = 60
wall = TkCanvas.newbackground=>'skyblue').pack
#wall = TkCanvas.newwidth=>500, :height=>400, :background=>'skyblue').pack
#wall = TkCanvas.newwidth=>800, :height=>600, :background=>'skyblue').pack
wall.xscrollbar(h_scroll = TkScrollbar.newwidth=>10))
wall.yscrollbar(v_scroll = TkScrollbar.newwidth=>10))
TkGrid.rowconfigure(Tk.root, 0, 'weight'=>1, 'minsize'=>0)
TkGrid.columnconfigure(Tk.root, 0, 'weight'=>1, 'minsize'=>0)
wall.gridrow=>0, :column=>0, :sticky=>'news')
h_scroll.gridrow=>1, :column=>0, 'sticky'=>'ew')
v_scroll.gridrow=>0, :column=>1, 'sticky'=>'ns')
TkcText.new(wall, 150, 50, :fill=>'navyblue', :font=>'courier -14',
:text=>"Ruby/Tk+VNC :: concept example")
TkcText.new(wall, 150, 300,
:text=>"This example will exit in #{timeout} seconds.")
TkcText.new(wall, 350, 340,
:text=>"This is Master IP's canvas widget.\n\t($SAFE==#{$SAFE})")
Tk.update
Tk.root.geometry('500x380')
Tk.after(timeout * 1000){exit}
ev_thread = Thread.new{Tk.mainloop}
#=========================================================#
demos = [
['wrap-test-target.rb', 'Simple Demo: create a new toplevel'],
['pendulum.rb', 'Test of interactivity (Animation demo)'],
]
v = TkVariable.new('')
sel_frame = TkFrame.new(wall, :relief=>:flat, :borderwidth=>3)
TkLabel.new(sel_frame,
:text=>'Please select the demo which you want to try.',
:foreground=>'red').packpadx=>10, ady=>5)
demos.each{|file, label|
TkButton.new(sel_frame, :text=>label, #:anchor=>:w,
:command=>proc{
v.value = file
sel_frame.destroy
}).packfill=>:x)
}
TkcWindow.new(wall, 40, 100, :anchor=>:nw, :window=>sel_frame)
Tk.update
sel_frame.wait
#p v.value
exit if v.value.empty?
dirname = File.dirname(File.expand_path(__FILE__))
#target_file = ARGV.shift.dup
#target_file = File.expand_path(ARGV.shift.dup)
target_file = File.join(dirname, v.value)
#=========================================================#
wall.scrollregion = [0, 0, 1024, 768]
TkcText.new(wall, 370, 170, :text=><<EOT)
The root window of the safeTk IP is
embedded in Master IP's frame widget.
You can move the root window by
'Button-1 + Motion' on the titlebar.
Master IP works like as a window manager.
No window manager on the VNC server.
Running one Ruby/Tk process only.
EOT
#'
w = Window_Frame.new(wall, 'slave root', 50, 70, :anchor=>:nw)
w.instance_variable_get@container).bind_append('Destroy'){Tk.exit}
#ip = MultiTkIp.new_trusted_slaveuse=>w.winid)
#ip = MultiTkIp.new_trusted_slave(3, :use=>w.winid)
#ip = MultiTkIp.new_trusted_slave(4, :use=>w.winid)
#ip = MultiTkIp.new_safeTk(0, :use=>w.winid)
#ip = MultiTkIp.new_safeTk(3, :use=>w.winid)
ip = MultiTkIp.new_safeTkuse=>w.winid)
replace_toplevel_cmd(wall, ip)
#p ['on main', __FILE__]
#=========================================================#
target_file.untaint
#ldr = proc{|f| $SAFE=ip.safe_level; load f,false}
xp = proc{|val| p val}
alias __require__ require
@req = proc{|f|
# p ['@req', f]
__require__(f)}
def require(f)
@req.call(f)
end
#xp.call '-------------------'
ip.bg_eval_proc{
# xp.call self
begin
# xp.call target_file
#ldr.call target_file
load target_file, true
rescue => e
xp.call e if $DEBUG
end
}
#xp.call '-------------------'
=begin
Tk.after(5000){
w.instance_variable_get@container).configurewidth=>450, :height=>250)
}
Tk.after(10000){
w.instance_variable_get@container).configurewidth=>500, :height=>600)
}
=end
#p 'join'
ev_thread.join
======================================================================
=======< wrap-test-target.rb >========================================
require 'tk'
p ['on target', __FILE__] if $SAFE < 4
TkLabel.newtext=>"safeTk interpreter's root").packpadx=>10, ady=>5)
top = nil
cnt = 0
b1 = TkButton.newtext=>'create Toplevel')
b2 = TkButton.newtext=>'add label to the toplevel', :state=>:disabled,
:command=>proc{
cnt += 1
TkLabel.new(top,
:text=>"Pressed(#{cnt})!! $SAFE=#{$SAFE}"
).pack
})
b1[:command] = proc{
top = TkToplevel.new
b1[:state] = :disabled
b2[:state] = :active
TkLabel.new(top,
:text=>'New toplevel of slaveIP').packpadx=>20, ady=>30)
}
label = TkLabel.newforeground=>'red', :text=>"\n")
timer = TkTimer.new(500, 1, proc{label.text = "\n"})
TkButton.newtext=>'BUTTON',
:command=>proc{
timer.cancel
label.text = "button is pressed!!\n($SAFE==#{$SAFE})"
timer.start
}).packpadx=>5, ady=>5, :fill=>:x)
label.pack
Tk.pack(b1, b2, :fill=>:x, adx=>5, ady=>5)
Tk.mainloop
======================================================================
=======< pendulum.rb >================================================
#!/usr/bin/env ruby
#
# based on Tcl/Tk8.5a2 widget demos
#
require 'tk'
#TkRoot.newtitle=>'Pendulum Animation Demonstration',
# :iconname=>"pendulum")
# create label
msg = TkLabel.new {
font 'Helvetica -12'
wraplength '4i'
justify 'left'
text 'This demonstration shows how Ruby/Tk can be used to carry out animations that are linked to simulations of physical systems. In the left canvas is a graphical representation of the physical system itself, a simple pendulum, and in the right canvas is a graph of the phase space of the system, which is a plot of the angle (relative to the vertical) against the angular velocity. The pendulum bob may be repositioned by clicking and dragging anywhere on the left canvas.'
}
msg.pack('side'=>'top')
# animated wave
class PendulumAnimationDemo
def initialize(frame)
TkFrame.new(frame) {|f|
TkButton.new(f, :command=>proc{Tk.root.destroy},
:text=>'Dismiss').packside=>:left, :expand=>true)
}.packside=>:bottom, :fill=>:x, ady=>'2m')
# Create some structural widgets
pane = TkPanedWindow.new(frame #, :width=>350, :height=>200
).packfill=>:both, :expand=>true)
@lf1 = TkLabelFrame.new(pane, :text=>'Pendulum Simulation')
@lf2 = TkLabelFrame.new(pane, :text=>'Phase Space')
# Create the canvas containing the graphical representation of the
# simulated system.
@c = TkCanvas.new(@lf1, :width=>320, :height=>200, :background=>'white',
:borderwidth=>2, :relief=>:sunken)
TkcText.new(@c, 5, 5, :anchor=>:nw,
:text=>'Click to Adjust Bob Start Position')
# Coordinates of these items don't matter; they will be set properly below
@plate = TkcLine.new(@c, 0, 25, 320, 25, :width=>2, :fill=>'grey50')
@rod = TkcLine.new(@c, 1, 1, 1, 1, :width=>3, :fill=>'black')
@Bob = TkcOval.new(@c, 1, 1, 2, 2,
:width=>3, :fill=>'yellow', utline=>'black')
TkcOval.new(@c, 155, 20, 165, 30, :fill=>'grey50', utline=>'')
# pack
@c.packfill=>:both, :expand=>true)
# Create the canvas containing the phase space graph; this consists of
# a line that gets gradually paler as it ages, which is an extremely
# effective visual trick.
@k = TkCanvas.new(@lf2, :width=>320, :height=>200, :background=>'white',
:borderwidth=>2, :relief=>:sunken)
@y_axis = TkcLine.new(@k, 160, 200, 160, 0, :fill=>'grey75', :arrow=>:last)
@x_axis = TkcLine.new(@k, 0, 100, 320, 100, :fill=>'grey75', :arrow=>:last)
@graph = {}
90.step(0, -10){|i|
# Coordinates of these items don't matter;
# they will be set properly below
@graph = TkcLine.new(@k, 0, 0, 1, 1, :smooth=>true, :fill=>"grey#{i}")
}
# labels
@label_theta = TkcText.new(@k, 0, 0, :anchor=>:ne,
:text=>'q', :font=>'Symbol 8')
@label_dtheta = TkcText.new(@k, 0, 0, :anchor=>:ne,
:text=>'dq', :font=>'Symbol 8')
# pack
@k.packfill=>:both, :expand=>true)
# Initialize some variables
@points = []
@theta = 45.0
@dTheta = 0.0
@length = 150
# init display
showPendulum
# animation loop
@timer = TkTimer.new(15){ repeat }
# binding
@c.bindtags_unshift(btag = TkBindTag.new)
btag.bind('Destroy'){ @timer.stop }
btag.bind('1', proc{|x, y| @timer.stop; showPendulum(x, y)}, '%x %y')
btag.bind('B1-Motion', proc{|x, y| showPendulum(x, y)}, '%x %y')
btag.bind('ButtonRelease-1',
proc{|x, y| showPendulum(x, y); @timer.start }, '%x %y')
btag.bind('Configure', proc{|w| @plate.coords(0, 25, w, 25)}, '%w')
@k.bind('Configure', proc{|h, w|
@psh = h/2;
@psw = w/2
@x_axis.coords(2, @psh, w-2, @psh)
@y_axis.coords(@psw, h-2, @psw, 2)
@label_theta.coords(@psw-4, 6)
@label_dtheta.coords(w-6, @psh+4)
}, '%h %w')
# add to pane
Tk.update
pane.add(@lf1, @lf2) # must be called after setting 'Configure' binding
# animation start
@timer.start(500)
end
# This procedure makes the pendulum appear at the correct place on the
# canvas. If the additional arguments x, y are passed instead of computing
# the position of the pendulum from the length of the pendulum rod and its
# angle, the length and angle are computed in reverse from the given
# location (which is taken to be the centre of the pendulum bob.)
def showPendulum(x=nil, y=nil)
if x && y && (x != 160 || y != 25)
@dTheta = 0.0
x2 = x - 160
y2 = y - 25
@length = Math.hypot(x2, y2)
@theta = Math.atan2(x2,y2)*180/Math:I
else
angle = @theta*Math:I/180
x = 160 + @length*Math.sin(angle)
y = 25 + @length*Math.cos(angle)
end
@rod.coords(160, 25, x, y)
@bob.coords(x-15, y-15, x+15, y+15)
end
# Update the phase-space graph according to the current angle and the
# rate at which the angle is changing (the first derivative with
# respect to time.)
def showPhase
@points << @theta + @psw << -20*@dTheta + @psh
if @points.length > 100
@points = @points[-100..-1]
end
(0...100).step(10){|i|
first = - i
last = 11 - i
last = -1 if last >= 0
next if first > last
lst = @points[first..last]
@graph.coords(lst) if lst && lst.length >= 4
}
end
# This procedure is the "business" part of the simulation that does
# simple numerical integration of the formula for a simple rotational
# pendulum.
def recomputeAngle
scaling = 3000.0/@length/@length
# To estimate the integration accurately, we really need to
# compute the end-point of our time-step. But to do *that*, we
# need to estimate the integration accurately! So we try this
# technique, which is inaccurate, but better than doing it in a
# single step. What we really want is bound up in the
# differential equation:
# .. - sin theta
# theta + theta = -----------
# length
# But my math skills are not good enough to solve this!
# first estimate
firstDDTheta = -Math.sin(@theta * Math:I/180) * scaling
midDTheta = @dTheta + firstDDTheta
midTheta = @theta + (@dTheta + midDTheta)/2
# second estimate
midDDTheta = -Math.sin(midTheta * Math:I/180) * scaling
midDTheta = @dTheta + (firstDDTheta + midDDTheta)/2
midTheta = @theta + (@dTheta + midDTheta)/2
# Now we do a double-estimate approach for getting the final value
# first estimate
midDDTheta = -Math.sin(midTheta * Math:I/180) * scaling
lastDTheta = midDTheta + midDDTheta
lastTheta = midTheta + (midDTheta+ lastDTheta)/2
# second estimate
lastDDTheta = -Math.sin(lastTheta * Math:I/180) * scaling
lastDTheta = midDTheta + (midDDTheta + lastDDTheta)/2
lastTheta = midTheta + (midDTheta + lastDTheta)/2
# Now put the values back in our globals
@dTheta = lastDTheta
@theta = lastTheta
end
# This method ties together the simulation engine and the graphical
# display code that visualizes it.
def repeat
# Simulate
recomputeAngle
# Update the display
showPendulum
showPhase
end
end
# Start the animation processing
PendulumAnimationDemo.new(Tk.root)
Tk.mainloop
======================================================================