Source code for enable.compass

# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
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


[docs]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 # ------------------------------------------------------------------------
[docs] 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
[docs] def mouse_up(self): """ Called when the mouse is released. This gets called after self.clicked is unset. """ pass
# ------------------------------------------------------------------------ # Event handling methods # ------------------------------------------------------------------------
[docs] 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
[docs] def normal_left_dclick(self, event): return self.normal_left_down(event)
[docs] def clicked_left_up(self, event): self.event_state = "normal" self.clicked = None event.handled = True self.mouse_up() self.request_redraw()
[docs] def clicked_mouse_leave(self, event): self.clicked_left_up(event)
# ------------------------------------------------------------------------ # Rendering methods # ------------------------------------------------------------------------
[docs] 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()