Ok, so I'm not real sure about lots of things in Shoes, but my trial and error approach has failed me so far on this one.
I've got a class that does some sort of computation that takes a while, and I want to throw up a progress bar for the user to look at while it finishes. My computationally intensive method yields its percent complete if passed a block:
class MathyStuff
def initialize()
end
## Some expensive, time consuming method which yields it's percent complete
def expensiveMethod(&block)
0.upto(100) do |i|
0.upto(100000) do |j|
k = j;
end
yield i.to_f/100;
end
end
end
Here's what I'd like to say in Shoes:
require 'MathyStuff.rb'
Shoes.app do
@myMathyStuff = MathyStuff.new();
button("Do expensive mathy thing...") do
window() do
@progress = progress();
@myMathyStuff.expensiveMethod() {|percent| @progress.fraction = percent;}
end
end
end
But it doesn't seem to work. I've tried with/without the window call, I've tried animate() in various ways, I even tried calling Thread.new and passing it the window block, having them converse via Shoes.APPS()[0].get/setPercent methods; nothing seems to work properly.
Maybe I'm not using the progress bar the way it's meant to be used. Then again, what else would a progress bar be for? ;-)
First of all, sharing data between two windows in Shoes is a royal pain. I don't recommend it. Instead, hide the contents of the first window and bring up the progress bar in its place.
Second, we'll extend MathyStuff to switch it from processing a block to providing a percent attribute, so we can access it from an animation thread:
class MathyStuff
attr_accessor :percent
def expensiveMethodWrapper
@percent = 0.0
expensiveMethod {|x| @percent = x}
end
end
Shoes.app do
@myMathyStuff = MathyStuff.new();
@window_slot = stack do
button("Do expensive mathy thing...") do
@window_slot.toggle
@progress_slot = flow do
@progress = progress :width => 1.0
end
end
Thread.new do
@myMathyStuff.expensiveMethodWrapper
end
@animate = animate do
@progress.fraction = @myMathyStuff.percent
if @myMathyStuff.percent == 1.0
@progress_slot.remove
@window_slot.toggle
@animate.stop
end
end
end
end
As I understand it, things like the progress bar need to be redrawn to screen after being fed a percentage value, this is what you'd use animate for in this case.
If you just want to do what you've stated in your question, then this approach - although not very flexible - does work for your example. But because it separates your progress logic from your actual method, you can only change the percentage values before and after you run those methods. So, since you're just running an iteration 100 times, then you can do it effectively this way.
class Mathy
def foo
100000.times do |bar|
foo = bar
end
end
end
Shoes.app do
@mathy = Mathy.new
button("Run") do
@p = progress
animate do |percent|
break if percent > 100
@mathy.foo
@p.fraction = percent.to_f / 100
end
end
end
If your method is doing more than just repeating the same iteration, then yes, you'd want to yield its progress frequently from within the method. Then, in order to return that progress from the method while it's running, you could put it in a separate thread as Pesto suggested, and just poll it for the progress in your animate block. Using an attr_accessor for returning the percentage is also a good idea. Hope that helps.