/
compass.py
183 lines (148 loc) · 6.71 KB
/
compass.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
from __future__ import with_statement
from numpy import array, pi
# Enthought library imports
from traits.api import Bool, Enum, Float, Int
# Local, relative imports
from component import Component
from colors import ColorTrait
class Compass(Component):
""" A compass with triangles at the 4 cardinal directions. The center
of the compass of triangles is the center of the widget.
"""
# Which triangle was clicked
clicked = Enum(None, "n", "e", "s", "w", "c")
# Whether or not to allow clicks on the center
enable_center = Bool(False)
#------------------------------------------------------------------------
# Shape and layout
#------------------------------------------------------------------------
# The length of the triangle from tip to base
triangle_length = Int(11)
# The width of the base of the triangle
triangle_width = Int(8)
# Overall scaling factor for the triangle. Note that this also scales
# the outline width.
scale = Float(1.0)
# The distance from the center of the widget to the center of each triangle
# (halfway along its length, not the orthocenter).
spacing = Int(12)
#------------------------------------------------------------------------
# Appearance Traits
#------------------------------------------------------------------------
# The line color of the triangles
color = ColorTrait("black")
# The line width of the triangles
line_width = Int(2)
# The triangle fill color when the mouse has not been clicked
fill_color = ColorTrait("none")
# The fill color of the triangle that the user has clicked on
clicked_color = ColorTrait("lightgray")
# Override the inherited **event_state** attribute
event_state = Enum("normal", "clicked")
#------------------------------------------------------------------------
# Stub methods for subclasses
#------------------------------------------------------------------------
def mouse_down(self, arrow):
""" Called when the mouse is first pressed inside one of the
triangles. This gets called after self.clicked is set.
Parameters
==========
arrow: "n", "e", "s", "w"
indicates which arrow was pressed
"""
pass
def mouse_up(self):
""" Called when the mouse is released. This gets called after
self.clicked is unset.
"""
pass
#------------------------------------------------------------------------
# Event handling methods
#------------------------------------------------------------------------
def normal_left_down(self, event):
# Determine which arrow was clicked; use a rectangular approximation.
x = event.x - (self.x + self.width / 2)
y = event.y - (self.y + self.height / 2)
half_length = self.triangle_length / 2 * self.scale
half_width = self.triangle_width / 2 * self.scale
offset = self.spacing * self.scale
# Create dict mapping direction to (x, y, x2, y2)
near = offset - half_length
far = offset + half_length
rects = { "n": array((-half_width, near, half_width, far)),
"e": array((near, -half_width, far, half_width)),
"s": array((-half_width, -far, half_width, -near)),
"w": array((-far, -half_width, -near, half_width)) }
if self.enable_center:
rects["c"] = array((-near, -near, near, near))
for direction, rect in rects.items():
if (rect[0] <= x <= rect[2]) and (rect[1] <= y <= rect[3]):
self.event_state = "clicked"
self.clicked = direction
self.mouse_down(direction)
self.request_redraw()
break
event.handled = True
return
def normal_left_dclick(self, event):
return self.normal_left_down(event)
def clicked_left_up(self, event):
self.event_state = "normal"
self.clicked = None
event.handled = True
self.mouse_up()
self.request_redraw()
def clicked_mouse_leave(self, event):
self.clicked_left_up(event)
#------------------------------------------------------------------------
# Rendering methods
#------------------------------------------------------------------------
def get_preferred_size(self):
# Since we can compute our preferred size from the size of the
# arrows and the spacing, we can return a sensible preferred
# size, so override the default implementation in Component.
if self.fixed_preferred_size is not None:
return self.fixed_preferred_size
else:
extent = self.scale * 2 * (self.spacing + self.triangle_length/2)
return [extent + self.hpadding, extent + self.vpadding]
def _draw_mainlayer(self, gc, view_bounds=None, mode="normal"):
with gc:
gc.set_stroke_color(self.color_)
gc.set_line_width(self.line_width)
gc.translate_ctm(self.x + self.width/2, self.y + self.height/2)
s = self.spacing
points_and_angles = [ ("n", (0, s), 0),
("e", (s, 0), -pi/2),
("s", (0, -s), pi),
("w", (-s, 0), pi/2) ]
gc.scale_ctm(self.scale, self.scale)
for dir, (dx,dy), angle in points_and_angles:
if self.event_state == "clicked" and self.clicked == dir:
gc.set_fill_color(self.clicked_color_)
else:
gc.set_fill_color(self.fill_color_)
gc.translate_ctm(dx, dy)
gc.rotate_ctm(angle)
half_height = self.triangle_length / 2
half_width = self.triangle_width / 2
gc.begin_path()
gc.lines( [(-half_width, -half_height),
(0, half_height),
(half_width, -half_height),
(-half_width, -half_height),
(0, half_height)] )
gc.draw_path()
gc.rotate_ctm(-angle)
gc.translate_ctm(-dx, -dy)
if self.event_state == "clicked" and self.clicked == 'c':
# Fill in the center
gc.set_fill_color(self.clicked_color_)
half_width = self.triangle_width / 2
gc.begin_path()
gc.lines( [(-half_width, -half_width),
(half_width, -half_height),
(half_width, half_width),
(-half_width, half_width),
(-half_width, -half_width)] )
gc.draw_path()