Vala vs. Python vs. C# - cairo
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).
- namespace Egg {
- public class ClockFace : Gtk.DrawingArea {
- construct {
- GLib.Timeout.add(1000, () => {
- time = GLib.Time.local(time_t());
- if(window != null) {
- weak Gdk.Region region = window.get_clip_region();
- window.invalidate_region (region, true);
- window.process_updates (true);
- }
- });
- time = GLib.Time.local(time_t());
- }
- public override bool expose_event(Gdk.EventExpose event) {
- Cairo.Context context;
- double x, y, radius;
- context = Gdk.cairo_create(window);
- x = allocation.x + allocation.width/2;
- y = allocation.y + allocation.height/2;
- radius = ((allocation.width > allocation.height) ? allocation.height/2
- : allocation.width/2)
- - 5;
- context.arc(x, y, radius, 0, 2 * Math.PI);
- context.set_source_rgb(1, 1, 1);
- context.fill_preserve();
- context.set_source_rgb(0, 0, 0);
- context.stroke();
- for(int i = 0; i < 12; i++) {
- int inset;
- context.save();
- if(i % 3 == 0) {
- inset = (int)(0.2 * radius);
- } else {
- inset = (int)(0.1 * radius);
- context.set_line_width(0.5 * context.get_line_width());
- }
- context.move_to(x + (radius - inset) * Math.cos(i * Math.PI / 6),
- y + (radius - inset) * Math.sin(i * Math.PI / 6));
- context.line_to(x + radius * Math.cos(i * Math.PI / 6),
- y + radius * Math.sin(i * Math.PI / 6));
- context.stroke();
- context.restore();
- }
- context.save();
- context.set_line_width(2.5 * context.get_line_width());
- context.move_to(x, y);
- context.line_to(x + radius * 0.5 * Math.sin(Math.PI/6*time.hour +
- Math.PI/360*time.minute),
- y - radius * 0.5 * Math.cos(Math.PI/6*time.hour +
- Math.PI/360*time.minute));
- context.stroke();
- context.restore();
- context.move_to(x, y);
- context.line_to(x + radius * 0.75 * Math.sin(Math.PI/30*time.minute),
- y - radius * 0.75 * Math.cos(Math.PI/30*time.minute));
- context.stroke();
- context.save();
- context.move_to(x, y);
- context.line_to(x + radius * Math.sin(Math.PI/30*time.second),
- y - radius * Math.cos(Math.PI/30*time.second));
- context.set_source_rgb(1, 0, 0);
- context.stroke();
- context.restore();
- context.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height);
- context.clip();
- return false;
- }
- private GLib.Time time;
- }
- public static int main(string[] args) {
- Gtk.Window window;
- Egg.ClockFace clock;
- Gtk.init(ref args);
- window.add(clock);
- window.destroy += Gtk.main_quit;
- window.show_all();
- Gtk.main();
- return 0;
- }
- }
- import pygtk
- pygtk.require('2.0')
- import gobject
- import gtk
- import gtk.gdk
- import cairo
- import datetime
- import math
- class ClockFace(gtk.DrawingArea):
- def __init__(self):
- gtk.DrawingArea.__init__(self)
- gobject.timeout_add(1000, self._timeout_handle)
- self._time = datetime.datetime.now()
- self.connect("expose-event", self._expose)
- def _timeout_handle(self):
- self._time = datetime.datetime.now()
- if self.window != None:
- region = self.window.get_clip_region()
- self.window.invalidate_region(region, True)
- self.window.process_updates(True)
- return True
- def _expose(self, widget, event):
- cairo = self.window.cairo_create()
- x = self.allocation.x + self.allocation.width/2
- y = self.allocation.y + self.allocation.height/2
- radius = min(self.allocation.width, self.allocation.height)/2 - 5
- cairo.arc(x, y, radius, 0, 2 * math.pi)
- cairo.set_source_rgb(1, 1, 1)
- cairo.fill_preserve()
- cairo.set_source_rgb(0, 0, 0)
- cairo.stroke()
- for i in xrange(12):
- cairo.save()
- if i % 3 == 0:
- inset = 0.2 * radius
- else:
- inset = 0.1 * radius
- cairo.set_line_width(0.5 * cairo.get_line_width())
- cairo.move_to(x + (radius - inset) * math.cos(i * math.pi / 6),
- y - (radius - inset) * math.sin(i * math.pi / 6))
- cairo.line_to(x + radius * math.cos(i * math.pi / 6),
- y - radius * math.sin(i * math.pi / 6))
- cairo.stroke()
- cairo.restore()
- cairo.save()
- cairo.set_line_width(2.5 * cairo.get_line_width())
- cairo.move_to(x, y)
- cairo.line_to(x + radius * 0.5 * math.sin(math.pi/6*self._time.hour +
- math.pi/360*self._time.minute),
- y - radius * 0.5 * math.cos(math.pi/6*self._time.hour +
- math.pi/360*self._time.minute))
- cairo.stroke()
- cairo.restore()
- cairo.move_to(x, y);
- cairo.line_to(x + radius * 0.75 * math.sin(math.pi/30*self._time.minute),
- y - radius * 0.75 * math.cos(math.pi/30*self._time.minute))
- cairo.stroke()
- cairo.save()
- cairo.move_to(x, y)
- cairo.line_to(x + radius * math.sin(math.pi/30*self._time.second),
- y - radius * math.cos(math.pi/30*self._time.second))
- cairo.set_source_rgb(1, 0, 0)
- cairo.stroke()
- cairo.restore()
- cairo.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height)
- cairo.clip()
- window = gtk.Window()
- clock = ClockFace()
- window.add(clock)
- window.connect("destroy", gtk.main_quit)
- window.show_all()
- 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#:
- using System;
- namespace Egg
- {
- public class ClockFace : Gtk.DrawingArea
- {
- public ClockFace()
- {
- GLib.Timeout.Add(1000, () => {
- time = DateTime.Now;
- if(GdkWindow != null)
- {
- GdkWindow.InvalidateRegion(GdkWindow.ClipRegion, true);
- GdkWindow.ProcessUpdates(true);
- }
- return true;
- });
- time = DateTime.Now;
- }
- protected override bool OnExposeEvent(Gdk.EventExpose evnt)
- {
- Cairo.Context context;
- double x, y, radius;
- context = Gdk.CairoHelper.Create(GdkWindow);
- x = Allocation.X + Allocation.Width/2;
- y = Allocation.Y + Allocation.Height/2;
- radius = ((Allocation.Width > Allocation.Height) ? Allocation.Height/2
- : Allocation.Width/2)
- - 5;
- context.Arc(x, y, radius, 0, 2 * Math.PI);
- context.SetSourceRGB(1, 1, 1);
- context.FillPreserve();
- context.SetSourceRGB(0, 0, 0);
- context.Stroke();
- for(int i = 0; i < 12; i++) {
- int inset;
- context.Save();
- if(i % 3 == 0) {
- inset = (int)(0.2 * radius);
- } else {
- inset = (int)(0.1 * radius);
- context.LineWidth = 0.5 * context.LineWidth;
- }
- context.MoveTo(x + (radius - inset) * Math.Cos(i * Math.PI / 6),
- y + (radius - inset) * Math.Sin(i * Math.PI / 6));
- context.LineTo(x + radius * Math.Cos(i * Math.PI / 6),
- y + radius * Math.Sin(i * Math.PI / 6));
- context.Stroke();
- context.Restore();
- }
- context.Save();
- context.LineWidth = 2.5 * context.LineWidth;
- context.MoveTo(x, y);
- context.LineTo(x + radius * 0.5 * Math.Sin(Math.PI/6*time.Hour +
- Math.PI/360*time.Minute),
- y - radius * 0.5 * Math.Cos(Math.PI/6*time.Hour +
- Math.PI/360*time.Minute));
- context.Stroke();
- context.Restore();
- context.MoveTo(x, y);
- context.LineTo(x + radius * 0.75 * Math.Sin(Math.PI/30*time.Minute),
- y - radius * 0.75 * Math.Cos(Math.PI/30*time.Minute));
- context.Stroke();
- context.Save();
- context.MoveTo(x, y);
- context.LineTo(x + radius * Math.Sin(Math.PI/30*time.Second),
- y - radius * Math.Cos(Math.PI/30*time.Second));
- context.SetSourceRGB(1, 0, 0);
- context.Stroke();
- context.Restore();
- context.Rectangle(evnt.Area.X, evnt.Area.Y,
- evnt.Area.Width, evnt.Area.Height);
- context.Clip();
- return false;
- }
- private DateTime time;
- public static void Main(string[] args)
- {
- Gtk.Window window;
- Egg.ClockFace clock;
- Gtk.Application.Init("Clock", ref args);
- window.Add(clock);
- window.ShowAll();
- Gtk.Application.Run();
- }
- }
- }
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