729591c7a368910cf2cc5fd78633e6cc6a810c7c
[darcs-mirror-ozeanien.git] / cairoboard.py
1 #!/usr/bin/python
2 # encoding:utf8
3
4
5 import pygtk
6 pygtk.require('2.0')
7 import gtk
8 import gobject
9 import cairo
10 import math
11
12 import engine
13
14 # Constants
15
16 GRIDWIDTH = 70
17 SCALE = GRIDWIDTH/2
18 GRIDPAD = 30
19
20 PILE_X = 2 * GRIDPAD + 10 * GRIDWIDTH
21 PILE_Y = 3 * GRIDWIDTH
22
23 BH_X = 2 * GRIDPAD + 10*GRIDWIDTH
24 BH_Y = 5 * GRIDWIDTH
25
26 class CairoBoard(gtk.DrawingArea):
27     def __init__(self,fabric, board):
28         assert board
29         self.board = board
30         self.fabric = fabric
31
32         self.base_surface = None
33         
34         self.highlighted_slot = None
35
36         gtk.DrawingArea.__init__(self)
37         self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
38                 gtk.gdk.BUTTON_RELEASE_MASK |
39                 gtk.gdk.LEAVE_NOTIFY_MASK |
40                 gtk.gdk.EXPOSURE_MASK |
41                 gtk.gdk.POINTER_MOTION_MASK)
42         self.connect_after("expose-event",self.do_expose_event)
43         self.connect("motion-notify-event",self.hover)
44
45         self.set_size_request(
46             2 * GRIDPAD + 9 * GRIDWIDTH + 2 * GRIDWIDTH,
47             2 * GRIDPAD + 6 * GRIDWIDTH
48         )
49         
50         self.reset()
51
52         # Creatings Drawing Things
53         for col in self.board.slots:
54             for slot in col:
55                 self.fabric.setup(slot)
56         self.fabric.setup(self.board.bh)
57         self.fabric.setup(self.board.pile)
58         self.fabric.setup(self.board.hand)
59
60     def reset(self):
61         self.queue_draw()
62
63     def do_expose_event(self,widget,event):
64         gc = self.window.new_gc()
65         gc.set_clip_rectangle(event.area)
66         cr = self.window.cairo_create()
67         # set a clip region for the expose event
68         cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
69         cr.clip()
70
71         self.draw(self.window)
72
73     def hover(self, widget, event):
74         self.board.hand.guiinfo.move_to(event.x, event.y)
75         if not self.board.hand.is_empty():
76             self.queue_draw()
77
78         slot = self.event2slot(event)
79         if self.highlighted_slot and self.highlighted_slot != slot: 
80             self.highlighted_slot.highlighted = False
81             self.highlighted_slot = None
82             self.queue_draw()
83         if slot:
84             slot.highlighted = True
85             self.highlighted_slot = slot
86             self.queue_draw()
87
88     def pileclick(self, event):
89         return (2*abs(event.x - PILE_X) < GRIDWIDTH and
90                 2*abs(event.y - PILE_Y + 3*SCALE) < GRIDWIDTH)
91
92     def pilerotclick(self, event):
93         if   PILE_Y + GRIDWIDTH/2 <= event.y <= PILE_Y + GRIDWIDTH and\
94                 PILE_X + GRIDWIDTH/4 <= event.x <= PILE_X + GRIDWIDTH:
95             return 1
96         elif PILE_Y + GRIDWIDTH/2 <= event.y <= PILE_Y + GRIDWIDTH and\
97                 PILE_X - GRIDWIDTH/4 >= event.x >= PILE_X - GRIDWIDTH:
98             return -1
99         else:
100             return 0
101
102     def event2slot(self, event):
103         x = int((event.x - GRIDPAD) // GRIDWIDTH)
104         y = int((event.y - GRIDPAD) // GRIDWIDTH)
105         if 0 <= x < len(self.board.slots) and 0 <= y < len(self.board.slots[x]):
106             return self.board.slots[x][y]
107         elif (2*abs(event.x - PILE_X) < GRIDWIDTH and
108               2*abs(event.y - PILE_Y) < GRIDWIDTH):
109             return self.board.pile
110         elif (2*abs(event.x - BH_X) < GRIDWIDTH and
111               2*abs(event.y - BH_Y) < GRIDWIDTH):
112             return self.board.bh
113         else:
114             return None
115
116     def draw(self, window):
117         self.draw_cached_base(window)
118
119         self.draw_points(self.board.points, window)
120
121         for col in self.board.slots:
122             for slot in col:
123                 slot.guiinfo.draw(window)
124         self.board.bh.guiinfo.draw(window)
125         self.board.pile.guiinfo.draw(window)
126         self.board.hand.guiinfo.draw(window)
127
128     
129     def draw_points(self, points, window):
130         cr = window.cairo_create()
131         cr.translate(2 * GRIDPAD + 10*GRIDWIDTH, 6 * GRIDWIDTH)
132         cr.scale(SCALE, SCALE)
133
134         txt = "%ip" % points
135
136         cr.set_font_size(1)
137         cr.set_source_rgb(0,0,0)
138         (_,_,w,h,_,_) = cr.text_extents(txt)
139
140         cr.move_to(-w/2, h/2)
141         cr.show_text(txt)
142         cr.fill()
143
144     def draw_cached_base(self,window):
145         ocr = window.cairo_create()
146         if not self.base_surface:
147             self.base_surface = ocr.get_target().create_similar(cairo.CONTENT_COLOR_ALPHA, 
148                     2*GRIDPAD + 11*GRIDWIDTH, 2*GRIDPAD + 7*GRIDWIDTH)
149
150             cr = cairo.Context(self.base_surface)
151             self.draw_base(cr)
152             cr = cairo.Context(self.base_surface)
153             self.draw_buttons(cr)
154
155         ocr.set_source_surface(self.base_surface,0,0)
156         ocr.rectangle(0,0,
157                 2*GRIDPAD + 11*GRIDWIDTH, 2*GRIDPAD + 7*GRIDWIDTH)
158         ocr.fill()
159
160     def draw_buttons(self,cr):
161         cr.set_font_size(25)
162         cr.set_source_rgb(0,0,0)
163
164         (_,_,w,h,ax,_) = cr.text_extents("QUIT")
165         cr.move_to(20, 1*GRIDPAD + 6*GRIDWIDTH + 10)
166         cr.show_text("QUIT")
167         self.quit_rect = (20, 1*GRIDPAD + 6*GRIDWIDTH + 10 - h, w, h)
168
169         (_,_,w,h,_,_) = cr.text_extents("RESET")
170         cr.move_to(20 + ax + 20, 1*GRIDPAD + 6*GRIDWIDTH + 10)
171         cr.show_text("RESET")
172         self.reset_rect = (20 + ax + 20, 1*GRIDPAD + 6*GRIDWIDTH + 10 - h, w, h)
173
174
175 class CairoSlot:
176     def __init__(self, fabric, slot):
177         self.fabric = fabric
178         self.slot = slot
179
180     def translate(self, cr):
181         raise "Abstract Method of Object" + str(self)
182
183     def draw(self, window):
184         if self.slot.card or self.slot.ship:
185             cr = window.cairo_create()
186             self.translate(cr)
187             cr.scale(SCALE, SCALE)
188
189             matrix = cr.get_matrix()
190             if self.slot.card:
191                 self.fabric.setup(self.slot.card)
192                 self.slot.card.guiinfo.draw(cr)
193                 cr.set_matrix(matrix)
194             if self.slot.ship:
195                 self.fabric.setup(self.slot.ship)
196                 self.slot.ship.guiinfo.draw(cr)
197                 cr.set_matrix(matrix)
198             if self.slot.highlighted:
199                 cr.set_source_rgba(1, 1, 1, 0.5)
200                 cr.rectangle(-1, -1, 2, 2)
201                 cr.fill()
202
203 class CairoBoardSlot:
204     def translate(self, cr):
205         cr.translate(
206                  GRIDPAD + (self.slot.x+0.5)*GRIDWIDTH,
207                  GRIDPAD + (self.slot.y+0.5)*GRIDWIDTH
208         )
209
210 class CairoHandSlot:
211     def __init__(self):
212         self.x = 0
213         self.y = 0
214
215     def move_to(self, x,y):
216         self.x = x
217         self.y = y
218
219     def translate(self, cr):
220         cr.translate(self.x, self.y)
221
222 class CairoFabric:
223     def get_widget(self, board):
224         self.board_draw = self.get_drawer(board)
225         board.guiinfo = self.board_draw
226         return self.board_draw
227
228     def redraw(self):
229         self.board_draw.queue_draw()
230
231     def get_drawer(self, object):
232         for c, drawer in self.classes:
233             if isinstance(object, c):
234                 return drawer(self, object)
235         raise "No Drawing Class for "+str(object)
236
237     def setup(self, object):
238         if not object.guiinfo:
239             object.guiinfo = self.get_drawer(object)
240
241 class CairoShip:
242     def __init__(self, fabric, ship):
243         self.fabric = fabric
244         self.ship = ship
245         self.cache = None
246     
247     def draw_cache(self, cr):
248         raise "Abstrac Method: "+str(self)
249
250     def draw(self, cr):
251         cr.rotate(-(self.ship.rotation * math.pi / 2))
252         cr.rotate(math.pi / 2) # Koordinaten unten passen sonst nicht
253
254         if not self.cache:
255             self.cache = cr.get_target().create_similar(cairo.CONTENT_COLOR_ALPHA, 
256                     GRIDWIDTH + GRIDPAD, GRIDWIDTH + GRIDPAD)
257             scr = cairo.Context(self.cache)
258             scr.translate((GRIDWIDTH + GRIDPAD)/2, (GRIDWIDTH+GRIDPAD)/2)
259             scr.scale(SCALE, SCALE)
260
261             self.draw_cache(scr)
262
263         cr.scale(1.0/SCALE, 1.0/SCALE)
264         cr.set_source_surface(self.cache,-(GRIDWIDTH + GRIDPAD)/2,-(GRIDWIDTH + GRIDPAD)/2)
265         cr.paint()
266
267 class CairoPile:
268     def __init__(self, fabric, slot):
269         self.pile = self.slot
270         self.flip_d = None # How far the card has been flipped
271         self.last_rot = 0
272
273     def card_flipped(self):
274         def update():
275             self.flip_d += 0.06
276             if self.flip_d > 1:
277                 self.flip_d = 1
278             self.fabric.redraw()
279             return self.flip_d < 1
280
281         self.flip_d = 0
282         self.last_rot = 0
283         gobject.timeout_add(40, update)
284
285     def start_rotation(self, dir):
286         def update():
287             if dir != self.rot_dir:
288                 return False
289             self.last_rot -= dir*5
290             if dir*self.last_rot < 0:
291                 self.last_rot = 0
292             self.fabric.redraw()
293             return self.last_rot
294
295         self.last_rot += dir * 100
296         self.rot_dir = dir
297
298         gobject.timeout_add(40, update)
299
300     def translate(self, cr):
301         cr.translate(
302                 PILE_X,
303                 PILE_Y,
304         )
305     
306     def draw_arrow(self, cr):
307         cr.arc(1, 1, 0.5, 0, math.pi/2)
308         cr.set_source_rgb(0, 0, 0)
309         cr.set_line_width(0.1)
310         cr.set_line_cap(cairo.LINE_CAP_ROUND)
311         cr.move_to(1,    1.5)
312         cr.line_to(0.75, 1.5)
313         cr.move_to(0.75, 1.5)
314         cr.line_to(1   , 1.75)
315         cr.move_to(0.75, 1.5)
316         cr.line_to(1   , 1.25)
317         cr.stroke()
318
319     def draw(self, window):
320         cr = window.cairo_create()
321         cr.translate(PILE_X, PILE_Y)
322         cr.scale(SCALE, SCALE)
323
324         cr.set_source_rgb(0.8, 0.8, 0.8)
325         cr.rectangle(-1, -1, 2, 2)
326         cr.fill()
327
328         matrix = cr.get_matrix()
329         self.draw_arrow(cr)
330         cr.scale(-1,1)
331         self.draw_arrow(cr)
332         cr.set_matrix(matrix)
333
334         cr.set_line_cap(cairo.LINE_CAP_SQUARE)
335         cr.translate(0,-3)
336         if self.pile.cards:
337             self.fabric.setup(self.pile.cards[0])
338             self.pile.cards[0].guiinfo.draw_back(cr)
339         else:
340             cr.set_source_rgb(0, 0, 0)
341             cr.set_line_width(0.05)
342             cr.rectangle(-1, -1, 2, 2)
343             cr.stroke()
344
345         cr.set_font_size(1)
346         cr.set_source_rgb(1,1,1)
347         (_,_,w,h,_,_) = cr.text_extents(str(len(self.pile.cards)))
348
349         cr.move_to(-w/2, h/2)
350         cr.show_text(str(len(self.pile.cards)))
351         cr.fill()
352         cr.set_matrix(matrix)
353
354         if self.flip_d == 1 and self.last_rot == 0:
355             self.super.draw(self, window)
356         elif self.slot.card:
357             cr = window.cairo_create()
358             cr.translate( PILE_X, PILE_Y )
359             cr.scale(SCALE, SCALE)
360
361             if self.flip_d != 0.5: # Da gibts nichts zu malen
362                 cr.translate(0, (1-self.flip_d) * (-3))
363                 cr.scale(1, (2 * self.flip_d - 1) )
364
365             if self.last_rot:
366                 cr.rotate(self.last_rot/100.0 * math.pi/2)
367
368             self.fabric.setup(self.slot.card)
369             if self.flip_d < 0.5: # back side
370                 self.slot.card.guiinfo.draw_back(cr)
371             else:
372                 self.slot.card.guiinfo.draw(cr)
373              
374 class CairoCard:
375     def __init__(self, fabric, card):
376         self.fabric = fabric
377         self.card = card
378         self.cache = None
379
380     def draw_back(self, cr):
381         matrix = cr.get_matrix()
382         cr.rotate(-(self.card.rotation * math.pi / 2))
383
384         cr.rectangle(-1,-1, 2,2)
385         cr.set_source_rgb(0.2, 0.2, 0.8)
386         cr.fill_preserve()
387         cr.set_source_rgb(0.2, 0.2, 0.2)
388         cr.set_line_width(0.05)
389         cr.stroke()
390         cr.set_matrix(matrix)
391
392     def draw(self, cr):
393         cr.rotate(-(self.card.rotation * math.pi / 2))
394
395         if not self.cache:
396             self.cache = cr.get_target().create_similar(cairo.CONTENT_COLOR_ALPHA, 
397                     GRIDWIDTH + GRIDPAD, GRIDWIDTH + GRIDPAD)
398             scr = cairo.Context(self.cache)
399             scr.translate((GRIDWIDTH + GRIDPAD)/2, (GRIDWIDTH+GRIDPAD)/2)
400             scr.scale(SCALE, SCALE)
401
402             self.draw_cache(scr)
403
404         cr.scale(1.0/SCALE, 1.0/SCALE)
405         cr.set_source_surface(self.cache,-(GRIDWIDTH + GRIDPAD)/2,-(GRIDWIDTH + GRIDPAD)/2)
406         cr.paint()
407
408 class CairoBlackHole:
409     def __init__(self, fabric, bh):
410         self.fabric = fabric
411         self.bh = bh
412         self.falling_card = None
413         self.falling_d = 0
414
415     def fall_card(self, card):
416         def update():
417             self.falling_d += 0.03
418             if self.falling_d > 1:
419                 self.falling_d = 1
420                 self.falling_card = None
421             self.fabric.redraw()
422             return self.falling_d < 1
423
424         self.falling_d = 0
425         self.falling_card = card
426         gobject.timeout_add(40, update)
427
428     def draw(self, window):
429         cr = window.cairo_create()
430         cr.translate(BH_X, BH_Y)
431         cr.scale(SCALE, SCALE)
432
433         cr.set_source_rgb(0,0,0)
434         cr.arc(0, 0, 0.8, 0, 2*math.pi)
435         cr.fill()
436         
437         cr.set_font_size(1)
438         cr.set_source_rgb(1,1,1)
439         (_,_,w,h,_,_) = cr.text_extents(str(self.bh.count))
440
441         cr.move_to(-w/2, h/2)
442         cr.show_text(str(self.bh.count))
443         cr.fill()
444
445         if self.falling_card:
446             matrix = cr.get_matrix()
447             cr.scale((1-self.falling_d * self.falling_d),(1-self.falling_d*self.falling_d))
448             cr.rotate(4*(self.falling_d * self.falling_d))
449             self.falling_card.guiinfo.draw(cr)
450             cr.set_matrix(matrix)
451
452
453
454 # vim:ts=4:sw=4:sts=4:et