Implemented drag and drop.
[kaya.git] / lib / board / scene.rb
blobb98e17892bd6cc334b0892cfa145985f31d359b6
1 require 'observer_utils'
3 class Scene < Qt::GraphicsScene
4   MINIMAL_DRAG_DISTANCE = 3
5   include Observer
7   def initialize
8     super
9     
10     @elements = []
11   end
13   def add_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             notify(element_dst, :drop, [src, pos], data)
50           end
51         elsif element_src == element_dst
52           # close drag and drop == click
53           # the element will decide how to handle it based on the distance
54           # between the coordinates
55           notify(element_dst, :click, [old_pos, pos])
56         else
57           # a rapid drag and drop between different elements
58           # is never considered a click
59           notify(element_src, :drag, [old_pos])
60           notify(element_src, :drop, [nil, pos], data)
61         end
62       end
63     end
64   end
65   
66   def mouseMoveEvent(e)
67     if @drag_data
68       pos = e.scene_pos.to_i
69       if !@drag_data[:dragging]
70         dx = (@drag_data[:pos].x - pos.x).abs
71         dy = (@drag_data[:pos].y - pos.y).abs
72         if dx >= MINIMAL_DRAG_DISTANCE ||
73            dy >= MINIMAL_DRAG_DISTANCE
74           @drag_data[:dragging] = true
75           notify(find_element(pos), :drag, [@drag_data[:pos]])
76         else
77           return
78         end
79       end
80       
81       if @drag_data[:item]
82         @drag_data[:item].pos = (pos - @drag_data[:size] / 2).to_f
83       end
84     end
85   end
86   
87   def find_element(pos)
88     @elements.detect do |element|
89       element.rect.contains(pos)
90     end
91   end
92   
93   def notify(element, event, pos, *args)
94     if element
95       relpos = pos.map{|p| rel(element, p) }
96       element.send("on_#{event}", *(relpos + args))
97     end
98   end
99   
100   def rel(element, pos)
101     if pos
102       pos - element.rect.top_left
103     end
104   end
105   
106   # invoked by the controller when one of the elements 
107   # accepts a drag
108   def on_drag(data)
109     if @drag_data
110       @drag_data = @drag_data.merge(data)
111     end
112   end