Source code for enable.drawing.drawing_canvas
# (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 enable.api import Container, Component, ColorTrait
from kiva.api import FILL, FILL_STROKE
from enable.trait_defs.kiva_font_trait import KivaFont
from traits.api import Any, Bool, Delegate, Enum, Instance, Int, List, Str
[docs]class Button(Component):
color = ColorTrait("lightblue")
down_color = ColorTrait("darkblue")
border_color = ColorTrait("blue")
label = Str
label_font = KivaFont("modern 12")
label_color = ColorTrait("white")
down_label_color = ColorTrait("white")
button_state = Enum("up", "down")
# A reference to the radio group that this button belongs to
radio_group = Any
# Default size of the button if no label is present
bounds = [32, 32]
# Generally, buttons are not resizable
resizable = ""
_got_mousedown = Bool(False)
[docs] def perform(self, event):
"""
Called when the button is depressed. 'event' is the Enable mouse event
that triggered this call.
"""
pass
def _draw_mainlayer(self, gc, view_bounds, mode="default"):
if self.button_state == "up":
self.draw_up(gc, view_bounds)
else:
self.draw_down(gc, view_bounds)
[docs] def draw_up(self, gc, view_bounds):
with gc:
gc.set_fill_color(self.color_)
gc.set_stroke_color(self.border_color_)
gc.draw_rect(
(
int(self.x),
int(self.y),
int(self.width) - 1,
int(self.height) - 1,
),
FILL_STROKE,
)
self._draw_label(gc)
[docs] def draw_down(self, gc, view_bounds):
with gc:
gc.set_fill_color(self.down_color_)
gc.set_stroke_color(self.border_color_)
gc.draw_rect(
(
int(self.x),
int(self.y),
int(self.width) - 1,
int(self.height) - 1,
),
FILL_STROKE,
)
self._draw_label(gc, color=self.down_label_color_)
def _draw_label(self, gc, color=None):
if self.label != "":
gc.set_font(self.label_font)
x, y, w, h = gc.get_text_extent(self.label)
if color is None:
color = self.label_color_
gc.set_fill_color(color)
gc.set_stroke_color(color)
gc.show_text(
self.label,
(
self.x + (self.width - w - x) / 2,
self.y + (self.height - h - y) / 2,
),
)
[docs] def normal_left_down(self, event):
self.button_state = "down"
self._got_mousedown = True
self.request_redraw()
event.handled = True
[docs] def normal_left_up(self, event):
self.button_state = "up"
self._got_mousedown = False
self.request_redraw()
self.perform(event)
event.handled = True
[docs]class ToolbarButton(Button):
toolbar = Any
canvas = Delegate("toolbar")
def __init__(self, *args, **kw):
toolbar = kw.pop("toolbar", None)
super().__init__(*args, **kw)
if toolbar:
self.toolbar = toolbar
toolbar.add(self)
[docs]class DrawingCanvasToolbar(Container):
""" The tool bar hosts Buttons and also consumes other mouse events, so
that tools on the underlying canvas don't get them.
FIXME: Right now this toolbar only supports the addition of buttons, and
not button removal. (Why would you ever want to remove a useful
button?)
"""
canvas = Instance("DrawingCanvas")
button_spacing = Int(5)
auto_size = False
_last_button_position = Int(0)
[docs] def add_button(self, *buttons):
for button in buttons:
self.add(button)
button.toolbar = self
# Compute the new position for the button
button.x = self.button_spacing + self._last_button_position
self._last_button_position += (
button.width + self.button_spacing * 2
)
button.y = int((self.height - button.height) / 2)
def _canvas_changed(self, old, new):
if old:
old.observe(
self._canvas_bounds_updated, "bounds.items", remove=True
)
if new:
new.observe(self._canvas_bounds_updated, "bounds.items")
def _canvas_bounds_updated(self, event):
self.width = self.canvas.width
self.y = self.canvas.height - self.height
def _dispatch_stateful_event(self, event, suffix):
super()._dispatch_stateful_event(
event, suffix
)
event.handled = True
[docs]class DrawingCanvas(Container):
"""
A DrawingCanvas has some buttons which toggle what kind of drawing tools
are active on the canvas, then allow arbitrary painting on the canvas.
"""
# The active tool is the primary interactor on the canvas. It gets
# a chance to handle events before they are passed on to other components
# and listener tools.
active_tool = Any
# Listening tools are always enabled and get all events (unless the active
# tool has vetoed it), but they cannot prevent other tools from getting
# events.
listening_tools = List
# The background color of the canvas
bgcolor = ColorTrait("white")
toolbar = Instance(DrawingCanvasToolbar, args=())
fit_window = True
[docs] def dispatch(self, event, suffix):
# See if the event happened on the toolbar:
event.offset_xy(*self.position)
if self.toolbar.is_in(event.x, event.y):
self.toolbar.dispatch(event, suffix)
event.pop()
if event.handled:
return
if self.active_tool is not None:
self.active_tool.dispatch(event, suffix)
if event.handled:
return
for tool in self.listening_tools:
tool.dispatch(event, suffix)
super().dispatch(event, suffix)
[docs] def activate(self, tool):
"""
Makes the indicated tool the active tool on the canvas and moves the
current active tool back into the list of tools.
"""
self.active_tool = tool
def _draw_container_mainlayer(self, gc, view_bounds=None, mode="default"):
active_tool = self.active_tool
if active_tool and active_tool.draw_mode == "exclusive":
active_tool.draw(gc, view_bounds, mode)
else:
# super()._draw(gc, view_bounds, mode)
for tool in self.listening_tools:
tool.draw(gc, view_bounds, mode)
if active_tool:
active_tool.draw(gc, view_bounds, mode)
self.toolbar.draw(gc, view_bounds, mode)
def _draw_container_background(self, gc, view_bounds=None, mode="default"):
if self.bgcolor not in ("clear", "transparent", "none"):
with gc:
gc.set_antialias(False)
gc.set_fill_color(self.bgcolor_)
gc.draw_rect(
(
int(self.x),
int(self.y),
int(self.width) - 1,
int(self.height) - 1,
),
FILL,
)
# ------------------------------------------------------------------------
# Event listeners
# ------------------------------------------------------------------------
def _tools_items_changed(self):
self.request_redraw()