#!/usr/bin/env python # An example of mouse interaction with cairo and Gtk+ # # Copyright (C) 2013 Antonio Ospite # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # References: # https://developer.gnome.org/gtk3/3.6/gtk-getting-started.html#idm140520417717488 import gi gi.require_version('Gtk', '3.0') from gi.repository import Gdk, Gtk, GObject, Gio import math import colorsys # from http://stackoverflow.com/questions/481144/ def in_circle(c_x, c_y, r, x, y): return math.hypot(c_x - x, c_y - y) <= r def clamp(x, interval): if x < interval[0]: return interval[0] elif x > interval[1]: return interval[1] else: return x class InteractiveCanvas(Gtk.DrawingArea): def __init__(self, width, height, *args, **kwargs): super(InteractiveCanvas, self).__init__(*args, **kwargs) self.width = width self.height = height self.x_offset = self.width / 2.0 self.y_offset = self.height / 2.0 self.radius = 20 self.border = 4 self.selectable = False self.selected = False self.drag_offset_x = 0 self.drag_offset_y = 0 self.x = self.width / 2.0 self.y = self.height / 2.0 self.set_size_request(width, height) self.connect('draw', self.on_draw) self.connect('configure-event', self.on_configure_event) self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK) self.connect('button-press-event', self.on_button_press_event) self.connect('button-release-event', self.on_button_release_event) self.connect('motion-notify-event', self.on_motion_notify_event) def on_configure_event(self, widget, event): self.width = event.width self.height = event.height def on_draw(self, widget, cr): cr.rectangle(0, 0, self.width, self.height) cr.set_source_rgb(1.0, 0.5, 0.5) cr.fill() cr.arc(self.x, self.y, self.radius, 0, 2 * math.pi) _h = self.x / float(self.width) if self.selected: _l = 0.6 else: if self.selectable: _l = 0.5 else: _l = 0.2 _s = self.y / float(self.height) _r, _g, _b = colorsys.hls_to_rgb(_h, _l, _s) cr.set_source_rgb(_r, _g, _b) cr.fill_preserve() cr.set_source_rgb(0, 0, 0) cr.set_line_width(self.border) cr.stroke() def on_button_press_event(self, widget, event): if self.selectable: self.selected = True self.drag_offset_x = self.x - event.x self.drag_offset_y = self.y - event.y self.queue_draw() def on_button_release_event(self, widget, event): self.selected = False self.queue_draw() def on_motion_notify_event(self, widget, event): if self.selected: self.x = clamp(event.x + self.drag_offset_x, [0, self.width]) self.y = clamp(event.y + self.drag_offset_y, [0, self.height]) self.queue_draw() else: old_selectable = self.selectable if in_circle(self.x, self.y, self.radius + self.border, event.x, event.y): self.selectable = True else: self.selectable = False if self.selectable != old_selectable: self.queue_draw() class InteractiveApp(Gtk.Application): def __init__(self, title, width, height, *args, **kwargs): super(InteractiveApp, self).__init__(*args, **kwargs) self.connect("activate", self.on_activate) self.title = title self.width = width self.height = height def on_activate(self, data=None): window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL) window.set_title(self.title) canvas = InteractiveCanvas(self.width, self.height) window.add(canvas) window.show_all() self.add_window(window) if __name__ == "__main__": app = InteractiveApp( "InteractiveApp", 640, 480, application_id="apps.test.interactiveapp", flags=Gio.ApplicationFlags.FLAGS_NONE) app.run(None)