Factor common code out of xboard and gnushogi engines.
[kaya/ydirson.git] / lib / board / scene.rb
blobe0a57d922ca2d9a7c0f0730f54421213cd8871d4
1 # Copyright (c) 2009 Paolo Capriotti <p.capriotti@gmail.com>
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 require 'observer_utils'
10 class Scene < Qt::GraphicsScene
11   MINIMAL_DRAG_DISTANCE = 3
12   include Observer
14   def initialize
15     super
16     self.background_brush = $qApp.palette.brush(Qt::Palette::Window)
17     @elements = []
18   end
20   def add_clickable_element(element)
21     @elements << element
22   end
23   
24   def mousePressEvent(e)
25     if e.button == Qt::LeftButton
26       pos = e.scene_pos.to_i
27       if find_element(pos)
28         @drag_data = { :pos => pos }
29       end
30     end
31   end
32   
33   def mouseReleaseEvent(e)
34     if e.button == Qt::LeftButton
35       if @drag_data
36         old_pos = @drag_data[:pos]
37         item = @drag_data[:item]
38         data = @drag_data
39         @drag_data = nil
40         
41         pos = e.scene_pos.to_i
42         element_src = find_element(old_pos)
43         element_dst = find_element(pos)
44         
45         if data[:dragging]
46           # normal drag and drop
48           if element_dst.nil?
49             # if the drop is in a blank area,
50             # notify the source of the drop
51             notify(element_src, :drop, [old_pos, nil], data)
52           else
53             src = if element_src == element_dst
54               old_pos
55             end
56             # if the drag and drop is close and there's no
57             # dragged item,  notify a click instead
58             if src and
59                same_square(element_dst, src, pos) and
60                (not data[:item])
61               notify(element_dst, :click, [pos])
62             else
63               notify(element_dst, :drop, [src, pos], data)
64             end
65           end
66         elsif element_src == element_dst
67           # close drag and drop == click, unless
68           # old_pos and pos fall on different squares
69           if same_square(element_src, old_pos, pos)
70             notify(element_dst, :click, [pos])
71           end
72         else
73           # a rapid drag and drop between different elements
74           # is never considered a click
75           notify(element_src, :drag, [old_pos])
76           notify(element_src, :drop, [nil, pos], data)
77         end
78       end
79     end
80   end
81   
82   def mouseMoveEvent(e)
83     if @drag_data
84       pos = e.scene_pos.to_i
85       if !@drag_data[:dragging]
86         dx = (@drag_data[:pos].x - pos.x).abs
87         dy = (@drag_data[:pos].y - pos.y).abs
88         if dx >= MINIMAL_DRAG_DISTANCE ||
89            dy >= MINIMAL_DRAG_DISTANCE
90           @drag_data[:dragging] = true
91           notify(find_element(pos), :drag, [@drag_data[:pos]])
92         else
93           return
94         end
95       end
96       
97       if @drag_data[:item]
98         @drag_data[:item].pos = (pos - @drag_data[:size] / 2).to_f
99       end
100     end
101   end
102   
103   def find_element(pos)
104     @elements.detect do |element|
105       element.rect.contains(pos)
106     end
107   end
108   
109   def notify(element, event, pos, *args)
110     if element
111       relpos = pos.map{|p| rel(element, p) }
112       element.send("on_#{event}", *(relpos + args))
113     end
114   end
115   
116   def rel(element, pos)
117     if pos
118       pos - element.rect.top_left
119     end
120   end
121   
122   def same_square(element, pos1, pos2)
123     element.to_logical(rel(element, pos1)) == 
124     element.to_logical(rel(element, pos2))
125   end
126   
127   # invoked by the controller when one of the elements 
128   # accepts a drag
129   def on_drag(data)
130     if @drag_data
131       @drag_data = @drag_data.merge(data)
132     end
133   end
134   
135   def remove_element(item)
136     @elements.delete(item)
137     remove_item(item)
138     item.dispose
139   end