GUI (Gnome, KDE, Gtk...), Programowanie (PHP, Java...), System (GNU, BSD, Windows...)

Vala vs. Python vs. C# - cairo

11 września, 2008 o 10:35:06 Dodaj komentarz Poziom: 0 Permalink

Postanowiłem przepisać 'zegar' na kilka języków. Na pierwszy ogień poszła Vala i python (nie wiem czy było to przepisane - w każdym razie sam to zrobiłem).

  1. namespace Egg {
  2.   public class ClockFace : Gtk.DrawingArea {
  3.     construct {
  4.       GLib.Timeout.add(1000, () => {
  5.         time = GLib.Time.local(time_t());
  6.        
  7.         if(window != null) {
  8.           weak Gdk.Region region = window.get_clip_region();
  9.          
  10.           window.invalidate_region (region, true);
  11.           window.process_updates (true);
  12.         }
  13.       });
  14.       time = GLib.Time.local(time_t());
  15.     }
  16.  
  17.     public override bool expose_event(Gdk.EventExpose event) {
  18.       Cairo.Context context;
  19.       double x, y, radius;
  20.      
  21.       context = Gdk.cairo_create(window);
  22.      
  23.       x = allocation.x + allocation.width/2;
  24.       y = allocation.y + allocation.height/2;
  25.      
  26.       radius = ((allocation.width > allocation.height) ? allocation.height/2
  27.                                                        : allocation.width/2)
  28.                  - 5;
  29.      
  30.       context.arc(x, y, radius, 0, 2 * Math.PI);
  31.       context.set_source_rgb(1, 1, 1);
  32.       context.fill_preserve();
  33.       context.set_source_rgb(0, 0, 0);
  34.       context.stroke();
  35.      
  36.       for(int i = 0; i < 12; i++) {
  37.         int inset;
  38.        
  39.         context.save();
  40.        
  41.         if(i % 3 == 0) {
  42.           inset = (int)(0.2 * radius);
  43.         } else {
  44.           inset = (int)(0.1 * radius);
  45.           context.set_line_width(0.5 * context.get_line_width());
  46.         }
  47.        
  48.         context.move_to(x + (radius - inset) * Math.cos(i * Math.PI / 6),
  49.                         y + (radius - inset) * Math.sin(i * Math.PI / 6));
  50.         context.line_to(x + radius * Math.cos(i * Math.PI / 6),
  51.                         y + radius * Math.sin(i * Math.PI / 6));
  52.        
  53.         context.stroke();
  54.         context.restore();
  55.       }
  56.      
  57.       context.save();
  58.       context.set_line_width(2.5 * context.get_line_width());
  59.       context.move_to(x, y);
  60.       context.line_to(x + radius * 0.5 * Math.sin(Math.PI/6*time.hour +
  61.                                                   Math.PI/360*time.minute),
  62.                       y - radius * 0.5 * Math.cos(Math.PI/6*time.hour +
  63.                                                   Math.PI/360*time.minute));
  64.       context.stroke();
  65.       context.restore();
  66.      
  67.       context.move_to(x, y);
  68.       context.line_to(x + radius * 0.75 * Math.sin(Math.PI/30*time.minute),
  69.                       y - radius * 0.75 * Math.cos(Math.PI/30*time.minute));
  70.       context.stroke();
  71.      
  72.       context.save();
  73.       context.move_to(x, y);
  74.       context.line_to(x + radius * Math.sin(Math.PI/30*time.second),
  75.                       y - radius * Math.cos(Math.PI/30*time.second));
  76.       context.set_source_rgb(1, 0, 0);
  77.       context.stroke();
  78.       context.restore();
  79.      
  80.       context.rectangle(event.area.x, event.area.y,
  81.                         event.area.width, event.area.height);
  82.      
  83.       context.clip();
  84.      
  85.       return false;
  86.     }
  87.    
  88.     private GLib.Time time;
  89.   }
  90.   public static int main(string[] args) {
  91.     Gtk.Window window;
  92.     Egg.ClockFace clock;
  93.    
  94.     Gtk.init(ref args);
  95.    
  96.     window = new Gtk.Window(Gtk.WindowType.TOPLEVEL);
  97.  
  98.     clock = new Egg.ClockFace();
  99.     window.add(clock);
  100.    
  101.     window.destroy += Gtk.main_quit;
  102.    
  103.     window.show_all();
  104.    
  105.     Gtk.main();
  106.    
  107.     return 0;
  108.   }
  109. }
  1. import pygtk
  2. pygtk.require('2.0')
  3.  
  4. import gobject
  5. import gtk
  6. import gtk.gdk
  7. import cairo
  8. import datetime
  9. import math
  10.  
  11. class ClockFace(gtk.DrawingArea):
  12.   def __init__(self):
  13.     gtk.DrawingArea.__init__(self)
  14.     gobject.timeout_add(1000, self._timeout_handle)
  15.     self._time = datetime.datetime.now()
  16.     self.connect("expose-event", self._expose)
  17.  
  18.   def _timeout_handle(self):
  19.     self._time = datetime.datetime.now()
  20.     if self.window != None:
  21.       region = self.window.get_clip_region()
  22.       self.window.invalidate_region(region, True)
  23.       self.window.process_updates(True)
  24.     return True
  25.  
  26.   def _expose(self, widget, event):
  27.     cairo = self.window.cairo_create()
  28.    
  29.     x = self.allocation.x + self.allocation.width/2
  30.     y = self.allocation.y + self.allocation.height/2
  31.     radius = min(self.allocation.width, self.allocation.height)/2 - 5
  32.    
  33.     cairo.arc(x, y, radius, 0, 2 * math.pi)
  34.     cairo.set_source_rgb(1, 1, 1)
  35.     cairo.fill_preserve()
  36.     cairo.set_source_rgb(0, 0, 0)
  37.     cairo.stroke()
  38.    
  39.     for i in xrange(12):
  40.       cairo.save()
  41.      
  42.       if i % 3 == 0:
  43.         inset = 0.2 * radius
  44.       else:
  45.         inset = 0.1 * radius
  46.         cairo.set_line_width(0.5 * cairo.get_line_width())
  47.      
  48.       cairo.move_to(x + (radius - inset) * math.cos(i * math.pi / 6),
  49.                     y - (radius - inset) * math.sin(i * math.pi / 6))
  50.       cairo.line_to(x + radius * math.cos(i * math.pi / 6),
  51.                     y - radius * math.sin(i * math.pi / 6))
  52.      
  53.       cairo.stroke()
  54.       cairo.restore()
  55.    
  56.       cairo.save()
  57.       cairo.set_line_width(2.5 * cairo.get_line_width())
  58.       cairo.move_to(x, y)
  59.       cairo.line_to(x + radius * 0.5 * math.sin(math.pi/6*self._time.hour +
  60.                                                 math.pi/360*self._time.minute),
  61.                     y - radius * 0.5 * math.cos(math.pi/6*self._time.hour +
  62.                                                 math.pi/360*self._time.minute))
  63.       cairo.stroke()
  64.       cairo.restore()
  65.      
  66.       cairo.move_to(x, y);
  67.       cairo.line_to(x + radius * 0.75 * math.sin(math.pi/30*self._time.minute),
  68.                     y - radius * 0.75 * math.cos(math.pi/30*self._time.minute))
  69.       cairo.stroke()
  70.      
  71.       cairo.save()
  72.       cairo.move_to(x, y)
  73.       cairo.line_to(x + radius * math.sin(math.pi/30*self._time.second),
  74.                     y - radius * math.cos(math.pi/30*self._time.second))
  75.       cairo.set_source_rgb(1, 0, 0)
  76.       cairo.stroke()
  77.       cairo.restore()
  78.      
  79.       cairo.rectangle(event.area.x, event.area.y,
  80.                       event.area.width, event.area.height)
  81.       cairo.clip()
  82.  
  83. window = gtk.Window()
  84. clock = ClockFace()
  85.  
  86. window.add(clock)
  87. window.connect("destroy", gtk.main_quit)
  88.  
  89. window.show_all()
  90.  
  91. gtk.main()

Kod w zasadzie ma idetyczną długość. Jednakże python ma kilka 'wad'.

  • Dynamiczno-statyczna kombinacja daje nam tutaj swoje znaki. Trzeba zwrócić prawdę - a nikt nam tego nie powie (zegar 'po prostu' nie będzie się uaktualniał)
  • Ułomna lambda w pythonie niczego nie ułatwia. W Vali po prostu mamy 7-linijkową lambdę. W pythonie lambdy mogą być tylko wyrażeniem.

Do gtk+ IMHO lepiej nadaje się vala (zaprojektowana zresztą 'z myślą o' GObject) - ale to może być tylko moja opinia. Próbowałem także coś tam zrobić z gdk, ale poddałem się ;)

Na prośbę - C#:

  1. using System;
  2.  
  3. namespace Egg
  4. {
  5.   public class ClockFace : Gtk.DrawingArea
  6.   {
  7.     public ClockFace()
  8.     {
  9.       GLib.Timeout.Add(1000, () => {
  10.         time = DateTime.Now;
  11.        
  12.         if(GdkWindow != null)
  13.         {
  14.           GdkWindow.InvalidateRegion(GdkWindow.ClipRegion, true);
  15.           GdkWindow.ProcessUpdates(true);
  16.         }
  17.        
  18.         return true;
  19.       });
  20.       time = DateTime.Now;
  21.     }
  22.    
  23.     protected override bool OnExposeEvent(Gdk.EventExpose evnt)
  24.     {
  25.       Cairo.Context context;
  26.       double x, y, radius;
  27.      
  28.       context = Gdk.CairoHelper.Create(GdkWindow);
  29.       x = Allocation.X + Allocation.Width/2;
  30.       y = Allocation.Y + Allocation.Height/2;
  31.      
  32.       radius = ((Allocation.Width > Allocation.Height) ? Allocation.Height/2
  33.                                                        : Allocation.Width/2)
  34.                  - 5;
  35.      
  36.       context.Arc(x, y, radius, 0, 2 * Math.PI);
  37.       context.SetSourceRGB(1, 1, 1);
  38.       context.FillPreserve();
  39.       context.SetSourceRGB(0, 0, 0);
  40.       context.Stroke();
  41.      
  42.       for(int i = 0; i < 12; i++) {
  43.         int inset;
  44.        
  45.         context.Save();
  46.        
  47.         if(i % 3 == 0) {
  48.           inset = (int)(0.2 * radius);
  49.         } else {
  50.           inset = (int)(0.1 * radius);
  51.           context.LineWidth = 0.5 * context.LineWidth;
  52.         }
  53.        
  54.         context.MoveTo(x + (radius - inset) * Math.Cos(i * Math.PI / 6),
  55.                        y + (radius - inset) * Math.Sin(i * Math.PI / 6));
  56.         context.LineTo(x + radius * Math.Cos(i * Math.PI / 6),
  57.                        y + radius * Math.Sin(i * Math.PI / 6));
  58.        
  59.         context.Stroke();
  60.         context.Restore();
  61.       }
  62.      
  63.       context.Save();
  64.       context.LineWidth = 2.5 * context.LineWidth;
  65.       context.MoveTo(x, y);
  66.       context.LineTo(x + radius * 0.5 * Math.Sin(Math.PI/6*time.Hour +
  67.                                                   Math.PI/360*time.Minute),
  68.                      y - radius * 0.5 * Math.Cos(Math.PI/6*time.Hour +
  69.                                                  Math.PI/360*time.Minute));
  70.       context.Stroke();
  71.       context.Restore();
  72.      
  73.       context.MoveTo(x, y);
  74.       context.LineTo(x + radius * 0.75 * Math.Sin(Math.PI/30*time.Minute),
  75.                      y - radius * 0.75 * Math.Cos(Math.PI/30*time.Minute));
  76.       context.Stroke();
  77.      
  78.       context.Save();
  79.       context.MoveTo(x, y);
  80.       context.LineTo(x + radius * Math.Sin(Math.PI/30*time.Second),
  81.                      y - radius * Math.Cos(Math.PI/30*time.Second));
  82.       context.SetSourceRGB(1, 0, 0);
  83.       context.Stroke();
  84.       context.Restore();
  85.      
  86.       context.Rectangle(evnt.Area.X, evnt.Area.Y,
  87.                         evnt.Area.Width, evnt.Area.Height);
  88.      
  89.       context.Clip();
  90.      
  91.       return false;
  92.     }
  93.    
  94.     private DateTime time;
  95.    
  96.     public static void Main(string[] args)
  97.     {
  98.       Gtk.Window window;
  99.       Egg.ClockFace clock;
  100.    
  101.       Gtk.Application.Init("Clock", ref args);
  102.    
  103.       window = new Gtk.Window(Gtk.WindowType.Toplevel);
  104.  
  105.       clock = new Egg.ClockFace();
  106.       window.Add(clock);
  107.    
  108.       window.ShowAll();
  109.    
  110.       Gtk.Application.Run();
  111.     }
  112.   }
  113. }

Tylko nie chce się toto zamknąć (proces 'wisi').

Komentarze do wpisu

Możesz śledzić odpowiedzi poprzez kanał RSS. Możesz dodać komentarz lub zostawić ślad (trackback) ze swojego bloga.

#

shae

„Brak lambd w pythonie wcale nie ułatwia. W Vali po prostu mamy 7-linijkową lambdę.”

Mógłbyś rozszerzyć wypowiedź? Bo:
http://docs.python.org/tut/node6.html#SECTION006750000000000000000

11 września 2008, 11:17:57

#

Livio

O, Vala wygląda na całkiem przyjemny język bez udziwnień :) .

11 września 2008, 14:08:55

#

sprae

„pygtk nie jest kompletne więc nie mamy gtk.main_quit() czy podobnych co wydłuża kod.”

Jak to nie mamy?
>>> import gtk
>>> hasattr(gtk, ‘main_quit’)
True

Jak już masz Vale to może jeszcze c# ;)
A jakie są wyniki tego testu, zegarek w vali szybciej chodzi? ;)))

11 września 2008, 14:40:05

#

sprae

Aha mamy też gtk.main() ;)

11 września 2008, 14:41:59

#

Uzytkownik

@shae:
1. Poprawione. Jakby kto nie wiedział – w pythonie lambda może być tylko wyrażeniem. Czyli nie może być to wielolinijkowa funkcja.

@livio:
2. B. lubię valę. Dodatkowo dynamicznie się rozwija.

@sprae:
3. C# nie udało mi się gdyż nie znalazłem dostępu do Gdk.Window z poziomu gtk#.
4. Poprawione. W dokumentacji na dysku szukałem a nie znalazłem

11 września 2008, 18:56:25

#

Uzytkownik

Dodałem C#. Zaletą Vali jest to, że można mieć do tego bezpośredni dostęp z C.

Co do szybkości – o jaki benchmark chodzi?

11 września 2008, 19:49:34

#

Livio

Vala jest chyba kompilowana do C a następnie do postaci binarnej.

11 września 2008, 19:50:10

#

Uzytkownik

Nie chyba – na pewno

11 września 2008, 19:50:59

#

sprae

Żartowałem z tą szybkością, bo przypomniał mi się kawał, o tym dlaczego ruskie zegarki są najlepsze ;)
Gratuluje! Dobra robota.

11 września 2008, 21:09:37

#

Uzytkownik

IMHO Vala była najszybsza w pisaniu programu ;)

11 września 2008, 21:10:39

#

sprae

Jako następny level proponuje clutter :) http://www.clutter-project.org/

11 września 2008, 21:13:57

#

Livio

O, aplikacje w Clutterze to coś wspaniałego :) .

11 września 2008, 21:14:31

#

Uzytkownik

Może coś skrobnę w ciągu kilku następnych dni. Zegar czy coś innego? Zaznaczam że nie znam API i będę musiał się wgryźć.

11 września 2008, 21:19:08

#

Livio

Jako potencjalny użytkownik, poproszę od Uzytkownika coś używalnego :D .

Myślę, że coś delikatnie interaktywnego. Zegar jest raczej statyczny :) .

11 września 2008, 21:19:57

#

Uzytkownik

Na przykład? Porówna się to z cairo ;) Jeśli będzie można (tzn. to co da się łatwo osiągnąć).

11 września 2008, 21:22:28

#

Livio

Cairo to raczej tylko od rysowania, a Clutter to jak GTK – cały toolkit.

Nie wiem, pobaw się np. w klikalno-przeciągalny kreator emotek, czy coś :P .

11 września 2008, 21:23:37

#

Uzytkownik

Wystarczy klikalno-przeciągalny zegar? Nie wiem jak taki kreator emotek ma wyglądać...

11 września 2008, 21:24:34

#

Livio

No tak na poczekaniu to wymyśliłem. Masz np. dwukropek i poziomą kreskę, a po pociągnięciu za kant kreski w dół robi się otwarty nawias (smutek), a w drugą stronę zamknięty (radość) itp. Może to zbyt skomplikowane i czasochłonne…

Clutter w sumie lepszy jest do aplikacji użytkowych niż do „wisielców” :) .

11 września 2008, 21:26:06

#

Uzytkownik

Ok. Zabieram się do rozgryzania cluttera…

11 września 2008, 21:27:23

#

sprae

Jak poćwiczysz to się dowiesz co można z tego wycisnąć ;) A można całkiem sporo.
Biorąc pod uwagę, że to pozwala na wydajne płynne przekształcenia możesz na początek upłynnić obracanie wskazówek. Pozwala na renderowanie cairo, więc można wstępnie je renderować do aktora i obracać.

11 września 2008, 23:00:20

#

Livio

W Clutterze można wykorzystać także GStreamera. Tak też powstaje Entertainer – media center w Clutterze :) .

11 września 2008, 23:34:30

Dodaj komentarz

Textile Lite włączony ( szczegółowy opis znaczników ):