Refactoring: use Match to handle interactions.
[kaya.git] / lib / board / scene.rb
blob14c871a68c9136792ce3e631828bd8733c73a1be
1 require 'observer_utils'
3 class Scene < Qt::GraphicsScene
4   MINIMAL_DRAG_DISTANCE = 3
5   include Observer
7   def initialize
8     super
9     self.background_brush = $qApp.palette.brush(Qt::Palette::Window)
10     @elements = []
11   end
13   def add_clickable_element(element)
14     @elements << element
15   end
16   
17   def mousePressEvent(e)
18     if e.button == Qt::LeftButton
19       pos = e.scene_pos.to_i
20       if find_element(pos)
21         @drag_data = { :pos => pos }
22       end
23     end
24   end
25   
26   def mouseReleaseEvent(e)
27     if e.button == Qt::LeftButton
28       if @drag_data
29         old_pos = @drag_data[:pos]
30         item = @drag_data[:item]
31         data = @drag_data
32         @drag_data = nil
33         
34         pos = e.scene_pos.to_i
35         element_src = find_element(old_pos)
36         element_dst = find_element(pos)
37         
38         if data[:dragging]
39           # normal drag and drop
41           if element_dst.nil?
42             # if the drop is in a blank area,
43             # notify the source of the drop
44             notify(element_src, :drop, [old_pos, nil], data)
45           else
46             src = if element_src == element_dst
47               old_pos
48             end
49             # if the drag and drop is close and there's no
50             # dragged item,  notify a click instead
51             if src and
52                same_square(element_dst, src, pos) and
53                (not data[:item])
54               notify(element_dst, :click, [pos])
55             else
56               notify(element_dst, :drop, [src, pos], data)
57             end
58           end
59         elsif element_src == element_dst
60           # close drag and drop == click, unless
61           # old_pos and pos fall on different squares
62           if same_square(element_src, old_pos, pos)
63             notify(element_dst, :click, [pos])
64           end
65         else
66           # a rapid drag and drop between different elements
67           # is never considered a click
68           notify(element_src, :drag, [old_pos])
69           notify(element_src, :drop, [nil, pos], data)
70         end
71       end
72     end
73   end
74   
75   def mouseMoveEvent(e)
76     if @drag_data
77       pos = e.scene_pos.to_i
78       if !@drag_data[:dragging]
79         dx = (@drag_data[:pos].x - pos.x).abs
80         dy = (@drag_data[:pos].y - pos.y).abs
81         if dx >= MINIMAL_DRAG_DISTANCE ||
82            dy >= MINIMAL_DRAG_DISTANCE
83           @drag_data[:dragging] = true
84           notify(find_element(pos), :drag, [@drag_data[:pos]])
85         else
86           return
87         end
88       end
89       
90       if @drag_data[:item]
91         @drag_data[:item].pos = (pos - @drag_data[:size] / 2).to_f
92       end
93     end
94   end
95   
96   def find_element(pos)
97     @elements.detect do |element|
98       element.rect.contains(pos)
99     end
100   end
101   
102   def notify(element, event, pos, *args)
103     if element
104       relpos = pos.map{|p| rel(element, p) }
105       element.send("on_#{event}", *(relpos + args))
106     end
107   end
108   
109   def rel(element, pos)
110     if pos
111       pos - element.rect.top_left
112     end
113   end
114   
115   def same_square(element, pos1, pos2)
116     element.to_logical(rel(element, pos1)) == 
117     element.to_logical(rel(element, pos2))
118   end
119   
120   # invoked by the controller when one of the elements 
121   # accepts a drag
122   def on_drag(data)
123     if @drag_data
124       @drag_data = @drag_data.merge(data)
125     end
126   end
127   
128   def remove_element(item)
129     @elements.delete(item)
130     remove_item(item)
131   end