rubylabs 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/rubylabs.rb CHANGED
@@ -494,38 +494,212 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
494
494
 
495
495
  module Canvas
496
496
 
497
+ class TkObject
498
+
499
+ attr_accessor :name, :coords, :penpoint
500
+
501
+ def TkObject.reset(p)
502
+ @@pipe = p
503
+ @@id = 0
504
+ end
505
+
506
+ def TkObject.nextId
507
+ @@id += 1
508
+ return "obj" + @@id.to_s
509
+ end
510
+
511
+ def TkObject.options(h)
512
+ a = []
513
+ h.each do |k,v|
514
+ a << "-#{k} #{v}"
515
+ end
516
+ return a.join(" ")
517
+ end
518
+
519
+ def raise
520
+ @@pipe.puts ".canvas raise $#{@name}"
521
+ end
522
+
523
+ def lower
524
+ @@pipe.puts ".canvas lower $#{@name}"
525
+ end
526
+
527
+ def erase
528
+ @@pipe.puts ".canvas delete $#{@name}"
529
+ end
530
+
531
+ def coords=(a)
532
+ @coords = a
533
+ @@pipe.puts ".canvas coords $#{@name} #{a.join(' ')}"
534
+ end
535
+
536
+ def fill=(x)
537
+ @@pipe.puts ".canvas itemconfigure $#{name} -fill #{x}"
538
+ end
539
+
540
+ end
541
+
542
+ =begin rdoc
543
+ Draw a line from (x0,y0) to (x1,y1)
544
+ =end
545
+
546
+ class Line < TkObject
547
+ def initialize(x0, y0, x1, y1, args = {})
548
+ raise "No canvas" unless @@pipe
549
+ @name = TkObject.nextId
550
+ @coords = [ x0, y0, x1, y1 ]
551
+ @penpoint = nil
552
+ cmnd = "set #{@name} [.canvas create line #{@coords.join(" ")} #{TkObject.options(args)}]"
553
+ @@pipe.puts cmnd
554
+ end
555
+
556
+ def Line.erase_all(tag)
557
+ @@pipe.puts ".canvas delete withtag #{tag}"
558
+ end
559
+
560
+ end
561
+
562
+ =begin rdoc
563
+ Create a circle with center at (x,y) and radius r.
564
+ =end
565
+
566
+ class Circle < TkObject
567
+ def initialize(x, y, r, args = {})
568
+ raise "No canvas" unless @@pipe
569
+ @name = TkObject.nextId
570
+ @coords = [ x-r, y-r, x+r, y+r ]
571
+ @penpoint = [ r, r ]
572
+ cmnd = "set #{@name} [.canvas create oval #{@coords.join(" ")} #{TkObject.options(args)}]"
573
+ @@pipe.puts cmnd
574
+ end
575
+ end
576
+
577
+ =begin rdoc
578
+ Create a rectangle with upper left at (x0,y0) and lower right at (x1,y1).
579
+ =end
580
+
581
+ class Rectangle < TkObject
582
+ def initialize(x0, y0, x1, y1, args = {})
583
+ raise "No canvas" unless @@pipe
584
+ @name = TkObject.nextId
585
+ @coords = [ x0, y0, x1, y1 ]
586
+ @penpoint = [ (x1-x0)/2, (y1-y0)/2 ]
587
+ cmnd = "set #{@name} [.canvas create rectangle #{@coords.join(" ")} #{TkObject.options(args)}]"
588
+ @@pipe.puts cmnd
589
+ end
590
+ end
591
+
592
+ =begin rdoc
593
+ Create a polygon with vertices defined by array a. The array can be a flat
594
+ list of x's and y's (e.g. [100,100,100,200,200,100]) or a list of (x,y)
595
+ pairs (e.g. [[100,100], [100,200], [200,100]]).
596
+ =end
597
+
598
+ class Polygon < TkObject
599
+ def initialize(a, args = {})
600
+ raise "No canvas" unless @@pipe
601
+ @name = TkObject.nextId
602
+ @coords = a
603
+ @penpoint = [0,0]
604
+ cmnd = "set #{@name} [.canvas create polygon #{@coords.join(" ")} #{TkObject.options(args)}]"
605
+ @@pipe.puts cmnd
606
+ end
607
+ end
608
+
609
+ =begin rdoc
610
+ Add text at (x, y). Sets the anchor point to northwest unless it is passed
611
+ as an argument.
612
+ =end
613
+
614
+ class Text < TkObject
615
+ def initialize(s, x, y, args = {})
616
+ raise "No canvas" unless @@pipe
617
+ @name = TkObject.nextId
618
+ @coords = [ x, y ]
619
+ @penpoint = nil
620
+ args[:anchor] = :nw unless args.has_key?(:anchor)
621
+ args[:text] = "\"#{s}\""
622
+ cmnd = "set #{@name} [.canvas create text #{@coords.join(" ")} #{TkObject.options(args)}]"
623
+ @@pipe.puts cmnd
624
+ end
625
+
626
+ def update(s)
627
+ @@pipe.puts ".canvas itemconfigure $#{name} -text \"#{s}\""
628
+ end
629
+ end
630
+
631
+ class Font < TkObject
632
+
633
+ @@fonts = []
634
+
635
+ def initialize(name, args)
636
+ @name = name
637
+ if @@fonts.index(name).nil?
638
+ cmnd = "font create #{name} #{TkObject.options(args)}"
639
+ @@pipe.puts cmnd
640
+ @@fonts << name
641
+ end
642
+ end
643
+
644
+ end
645
+
646
+ =begin rdoc
647
+ Initialize a drawing canvas. This version uses Tk to draw objects used during
648
+ a visualization. If it's not already initialized, open a connection to a wish
649
+ shell and configure the window.
650
+ =end
651
+
652
+ =begin
653
+ TODO Read the path to wish from a configuration file, so users can alter it
654
+ TODO there are probably other configuration options that can go there, too
655
+ =end
656
+
657
+ @@tkpipe = nil
658
+ @@title = ""
659
+ @@height = 0
660
+ @@width = 0
661
+
497
662
  def Canvas.init(width, height, title, *opts)
498
- require 'tk'
499
663
 
500
664
  @@title = "RubyLabs::#{title}"
501
665
  @@width = width
502
666
  @@height = height
667
+ pad = 12
503
668
 
504
- if @@tkroot.nil?
505
- # @@tkroot = TkRoot.new { title @@title }
506
- # @@content = TkFrame.new(@@tkroot)
507
- # @@canvas = TkCanvas.new(@@content, :width => width, :height => height)
508
- # @@canvas.grid :column => 0, :row => 0, :columnspan => 4, :padx => 10, :pady => 10
509
- # @@content.pack :pady => 20
510
-
511
- @@tkroot = TkRoot.new { title @@title }
512
- # @@content = TkFrame.new(@@tkroot)
513
- @@canvas = TkCanvas.new(@@tkroot, :width => width, :height => height)
514
- # @@canvas.grid :column => 0, :row => 0, :columnspan => 4, :padx => 10, :pady => 10
515
- @@canvas.pack :padx => 10, :pady => 10
516
- # @@content.pack :pady => 20
517
-
518
- @@threads = []
519
- @@threads << Thread.new() do
520
- Tk.mainloop
521
- end
522
- else
523
- Canvas.clear
524
- @@canvas.height = height
525
- @@canvas.width = width
526
- @@tkroot.title = @@title
527
- end
528
- return opts[0] == :debug ? @@canvas : true
669
+ if @@tkpipe.nil?
670
+ if Canvas.OS == "Windows"
671
+ @@tkpipe = IO.popen("wish", "w")
672
+ elsif Canvas.OS == "Linux"
673
+ @@tkpipe = IO.popen("/opt/ActiveTcl-8.5/bin/wish", "w")
674
+ else
675
+ @@tkpipe = IO.popen("/usr/local/bin/wish", "w")
676
+ end
677
+ at_exit { Canvas.close }
678
+ @@tkpipe.puts "tk::canvas .canvas -bg white -width #{width} -height #{height}"
679
+ @@tkpipe.puts ". configure -bg gray"
680
+ @@tkpipe.puts "pack .canvas -padx #{pad} -pady #{pad}"
681
+ TkObject.reset(@@tkpipe)
682
+ else
683
+ @@tkpipe.puts ".canvas delete all"
684
+ @@tkpipe.puts ".canvas configure -width #{width} -height #{height}"
685
+ end
686
+
687
+ @@tkpipe.puts "wm geometry . #{width+2*pad}x#{height+2*pad}+30+50"
688
+ @@tkpipe.puts "wm title . #{@@title}"
689
+
690
+ return opts[0] == :debug ? @@tkpipe : true
691
+ end
692
+
693
+ def Canvas.close
694
+ if @@tkpipe
695
+ @@tkpipe.puts "exit"
696
+ @@tkpipe = nil
697
+ TkObject.reset(nil)
698
+ end
699
+ end
700
+
701
+ def Canvas.flush
702
+ @@tkpipe.flush
529
703
  end
530
704
 
531
705
  def Canvas.width
@@ -536,14 +710,10 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
536
710
  @@height
537
711
  end
538
712
 
539
- def Canvas.root
540
- @@tkroot
713
+ def Canvas.pipe
714
+ @@tkpipe
541
715
  end
542
-
543
- def Canvas.create(*args)
544
- @@canvas.send(:create, *args)
545
- end
546
-
716
+
547
717
  # Idea for the future, abandoned for now: allow applications to define a coordinate
548
718
  # system, e.g. cartesian with the origin in the middle of the canvas, and map coords
549
719
  # pass to line, rectangle, etc from user-specified coordinates to Tk coordinates.
@@ -567,82 +737,21 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
567
737
  # a[i], a[i+1] = @@map.call( a[i], a[i+1] )
568
738
  # end
569
739
  # end
570
-
571
- def Canvas.clear
572
- @@objects.each { |x| x.delete }
573
- @@objects.clear
574
- end
575
-
576
- # total hack -- if on OS X and version is 1.8 and called directly from irb pause to
577
- # synch the drawing thread....
578
-
579
- def Canvas.sync
580
- if RUBY_VERSION =~ %r{^1\.8} && RUBY_PLATFORM =~ %r{darwin} && caller[1].index("(irb)") == 0
581
- sleep(0.1)
582
- end
583
- # @@tkroot.update :idletasks
584
- end
585
-
586
- =begin rdoc
587
- Add text at (x, y). Note -- looks like :anchor is required, otherwise runtime error
588
- from Tk (something about illegal coords).
589
- =end
590
-
591
- def Canvas.text(s, x, y, opts = {})
592
- return nil unless @@canvas
593
- opts[:anchor] = :nw unless opts.has_key?(:anchor)
594
- opts[:text] = s
595
- text = TkcText.new( @@canvas, x, y, opts)
596
- @@objects << text
597
- return text
598
- end
599
-
600
- def Canvas.font(options)
601
- return TkFont.new(options)
602
- end
603
-
604
- =begin rdoc
605
- Draw a line from (x0,y0) to (x1,y1)
606
- =end
607
-
608
- def Canvas.line(x0, y0, x1, y1, opts = {})
609
- return nil unless @@canvas
610
- line = TkcLine.new( @@canvas, x0, y0, x1, y1, opts )
611
- @@objects << line
612
- return line
613
- end
614
-
615
- =begin rdoc
616
- Draw a rectangle with upper left at (x0,y0) and lower right at (x1,y1).
617
- =end
618
740
 
619
- def Canvas.rectangle(x0, y0, x1, y1, opts = {})
620
- return nil unless @@canvas
621
- rect = TkcRectangle.new( @@canvas, x0, y0, x1, y1, opts)
622
- Canvas.makeObject(rect, (x1-x0)/2, (y1-y0)/2)
623
- end
624
-
625
- =begin rdoc
626
- Draw a circle with center at (x,y) and radius r
627
- =end
628
-
629
- def Canvas.circle(x, y, r, opts = {})
630
- return nil unless @@canvas
631
- ulx, uly, lrx, lry = x-r, y-r, x+r, y+r
632
- circ = TkcOval.new( @@canvas, ulx, uly, lrx, lry, opts )
633
- Canvas.makeObject(circ, r, r)
634
- end
635
-
636
- =begin rdoc
637
- Draw a polygon with vertices defined by array a. The array can be a flat
638
- list of x's and y's (e.g. [100,100,100,200,200,100]) or a list of (x,y)
639
- pairs (e.g. [[100,100], [100,200], [200,100]]).
640
- =end
641
-
642
- def Canvas.polygon(a, opts = {})
643
- return nil unless @@canvas
644
- poly = TkcPolygon.new( @@canvas, a, opts)
645
- Canvas.makeObject(poly, 0, 0)
741
+ # Figure out what type of system we're on.
742
+ # TODO: find out what the platform string is for the legacy windows one-click installer;
743
+ # the new installer uses the "mingw" compiler.
744
+
745
+ def Canvas.OS
746
+ if RUBY_PLATFORM =~ %r{darwin}
747
+ return "OS X"
748
+ elsif RUBY_PLATFORM =~ %r{linux}
749
+ return "Linux"
750
+ elsif RUBY_PLATFORM =~ %r{mingw}
751
+ return "Windows"
752
+ else
753
+ return "Unknown"
754
+ end
646
755
  end
647
756
 
648
757
  =begin rdoc
@@ -652,8 +761,8 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
652
761
  def Canvas.move(obj, dx, dy, option = nil)
653
762
  a = obj.coords
654
763
  if option == :track
655
- x0 = a[0] + obj.penx
656
- y0 = a[1] + obj.peny
764
+ x0 = a[0] + obj.penpoint[0]
765
+ y0 = a[1] + obj.penpoint[1]
657
766
  end
658
767
  (0...a.length).step(2) do |i|
659
768
  a[i] += dx
@@ -661,9 +770,9 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
661
770
  end
662
771
  obj.coords = a
663
772
  if option == :track
664
- x1 = a[0] + obj.penx
665
- y1 = a[1] + obj.peny
666
- @@objects << TkcLine.new( @@canvas, x0, y0, x1, y1, :width => 1, :fill => '#777777' )
773
+ x1 = a[0] + obj.penpoint[0]
774
+ y1 = a[1] + obj.penpoint[1]
775
+ Canvas::Line.new( x0, y0, x1, y1, :width => 1, :fill => '#777777' )
667
776
  obj.raise
668
777
  end
669
778
  return a
@@ -674,15 +783,15 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
674
783
  and save the object in a local list so it can be erased by Canvas.clear
675
784
  =end
676
785
 
677
- def Canvas.makeObject(obj, xoff, yoff)
678
- class <<obj
679
- attr_accessor :penx, :peny
680
- end
681
- obj.penx = xoff
682
- obj.peny = yoff
683
- @@objects << obj
684
- return obj
685
- end
786
+ # def Canvas.makeObject(obj, xoff, yoff)
787
+ # class <<obj
788
+ # attr_accessor :penx, :peny
789
+ # end
790
+ # obj.penx = xoff
791
+ # obj.peny = yoff
792
+ # @@objects << obj
793
+ # return obj
794
+ # end
686
795
 
687
796
  =begin rdoc
688
797
  Rotate an object by an angle theta (expressed in degrees). The object is
@@ -711,16 +820,14 @@ Call +a.random(:success)+ to get a value that is in the array +a+, or call
711
820
  def Canvas.degrees(rad)
712
821
  180 * rad / Math::PI
713
822
  end
714
-
715
- @@tkroot = nil
716
- @@objects = Array.new
717
- @@title = ""
718
- @@height = 0
719
- @@width = 0
720
- @@map = lambda { |x,y| return x, y }
721
-
823
+
722
824
  =begin rdoc
723
- Make an array of n colors that vary from first to last.
825
+ # Make a range of colors starting from first and going to last in n steps.
826
+ # First and last are expected to be 3-tuples of integer RGB values. The
827
+ # result is an array that starts with first, has n-1 intermediate colors,
828
+ # and ends with last. Example:
829
+ # makePalette( [255,0,0], [0,0,0], 10)
830
+ # makes 11 colors starting with red and ending with black.
724
831
  =end
725
832
 
726
833
  def Canvas.palette(first, last, n)
data/lib/spherelab.rb CHANGED
@@ -215,7 +215,7 @@ module SphereLab
215
215
  copy.position = position.clone
216
216
  copy.velocity = velocity.clone
217
217
  copy.force = force.clone
218
- copy.graphic = Canvas.circle(prevx, prevy, size, :fill => color)
218
+ copy.graphic = Canvas::Circle.new(prevx, prevy, size, :fill => color)
219
219
  end
220
220
  return copy
221
221
  end
@@ -327,7 +327,6 @@ module SphereLab
327
327
  @velocity.y = y
328
328
  if @graphic
329
329
  Canvas.rotate(@graphic,alpha)
330
- Canvas.sync
331
330
  end
332
331
  return alpha
333
332
  end
@@ -338,7 +337,6 @@ module SphereLab
338
337
  @position.add( @velocity * dt )
339
338
  if @graphic
340
339
  Canvas.move(@graphic, @position.x-prevx, prevy-@position.y, @tracking)
341
- Canvas.sync
342
340
  end
343
341
  return dt
344
342
  end
@@ -354,7 +352,6 @@ module SphereLab
354
352
  theta = Canvas.degrees(mid.angle(@velocity))
355
353
  alpha = 2 * (90.0 - theta)
356
354
  turn(alpha)
357
- Canvas.sync if @graphic
358
355
  return alpha
359
356
  end
360
357
 
@@ -386,9 +383,9 @@ module SphereLab
386
383
  poly[i] += edge/10
387
384
  poly[i+1] += edge/2
388
385
  end
389
- Canvas.init(edge, edge, "SphereLab")
386
+ Canvas.init(edge, edge, "SphereLab::Robot")
390
387
  turtle = Turtle.new( :x => edge/10, :y => edge/2, :heading => 0, :speed => 10 )
391
- turtle.graphic = Canvas.polygon(poly, :outline => 'black', :fill => '#00ff88')
388
+ turtle.graphic = Canvas::Polygon.new(poly, :outline => 'black', :fill => '#00ff88')
392
389
  if options[:flag]
393
390
  turtle.set_flag( *options[:flag] )
394
391
  end
@@ -398,17 +395,15 @@ module SphereLab
398
395
  class <<turtle
399
396
  def plant_flag
400
397
  set_flag( @position.x, @position.y )
401
- Canvas.sync
402
398
  end
403
399
  end
404
400
  @@drawing = TurtleView.new( turtle, options )
405
- Canvas.sync
406
401
  return true
407
402
  end
408
403
 
409
404
  def set_flag(fx, fy)
410
405
  r = 3.0
411
- Canvas.circle( fx + r/2, fy + r/2, r, :fill => 'darkblue' )
406
+ Canvas::Circle.new( fx + r/2, fy + r/2, r, :fill => 'darkblue' )
412
407
  @reference = [ fx, fy ]
413
408
  end
414
409
 
@@ -574,7 +569,7 @@ module SphereLab
574
569
  =end
575
570
 
576
571
  def view_system(blist, userOptions = {})
577
- Canvas.init(700, 700, "SphereLab")
572
+ Canvas.init(700, 700, "SphereLab::N-Body")
578
573
  if prev = @@drawing
579
574
  prev.bodies.each { |x| x.graphic = nil }
580
575
  end
@@ -583,7 +578,7 @@ module SphereLab
583
578
  sf = setScale(blist, options[:origin], options[:scale])
584
579
  blist.each do |b|
585
580
  x, y = scale(b.position, origin, sf)
586
- b.graphic = Canvas.circle(x, y, b.size, :fill => b.color)
581
+ b.graphic = Canvas::Circle.new(x, y, b.size, :fill => b.color)
587
582
  b.prevx = x
588
583
  b.prevy = y
589
584
  end
@@ -594,7 +589,6 @@ module SphereLab
594
589
  elsif options.has_key?(:pendown)
595
590
  options[:pendown] = :track
596
591
  end
597
- Canvas.sync
598
592
  return true
599
593
  end
600
594
 
@@ -622,7 +616,6 @@ module SphereLab
622
616
  falling.prevx = newx
623
617
  falling.prevy = newy
624
618
  falling.clear_force
625
- Canvas.sync
626
619
  return true
627
620
  end
628
621
 
@@ -668,7 +661,6 @@ module SphereLab
668
661
  b.prevx = newx
669
662
  b.prevy = newy
670
663
  end
671
- Canvas.sync
672
664
  return true
673
665
  end
674
666
 
@@ -728,15 +720,14 @@ module SphereLab
728
720
  mxmin = options[:mxmin]
729
721
  mymin = options[:mymin]
730
722
  hmax = options[:hmax]
731
- Canvas.init(edge, edge, "SphereLab")
732
- earth = Canvas.circle(200, 2150, 1800, :fill => blist[1].color)
723
+ Canvas.init(edge, edge, "SphereLab::Melon")
724
+ earth = Canvas::Circle.new(200, 2150, 1800, :fill => blist[1].color)
733
725
  blist[1].graphic = earth
734
726
  mymax = earth.coords[1]
735
- melon = Canvas.circle(mxmin, mymax, 5, :fill => blist[0].color)
727
+ melon = Canvas::Circle.new(mxmin, mymax, 5, :fill => blist[0].color)
736
728
  blist[0].graphic = melon
737
729
  scale = (mymax-mymin) / hmax.to_f
738
730
  @@drawing = MelonView.new(blist, scale, blist[0].position.y, melon.coords, options)
739
- Canvas.sync
740
731
  return true
741
732
  end
742
733
 
@@ -759,7 +750,6 @@ module SphereLab
759
750
  a[1] -= height * @@drawing.scale
760
751
  a[3] -= height * @@drawing.scale
761
752
  melon.graphic.coords = a
762
- Canvas.sync
763
753
  else
764
754
  melon.prevy = melon.position.y
765
755
  melon.position.y += height
@@ -784,7 +774,6 @@ module SphereLab
784
774
  dx = (melon.position.x - mx) * @@drawing.scale
785
775
  dy = (my - melon.position.y) * @@drawing.scale
786
776
  Canvas.move(melon.graphic, dx, dy, @@drawing.options[:pendown])
787
- Canvas.sync
788
777
  end
789
778
  return blist[0].height
790
779
  end
@@ -797,7 +786,6 @@ module SphereLab
797
786
  break if res == "splat"
798
787
  count += 1
799
788
  end
800
- Canvas.sync if @@drawing
801
789
  return count * dt
802
790
  end
803
791
  # :end :drop_melon