3 # An example of mouse interaction with cairo and Gtk+
5 # Copyright (C) 2013 Antonio Ospite <ospite@studenti.unina.it>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 # https://developer.gnome.org/gtk3/3.6/gtk-getting-started.html#idm140520417717488
25 gi.require_version('Gtk', '3.0')
26 from gi.repository import Gdk, Gtk, GObject, Gio
32 # from http://stackoverflow.com/questions/481144/
33 def in_circle(c_x, c_y, r, x, y):
34 return math.hypot(c_x - x, c_y - y) <= r
37 def clamp(x, interval):
46 class InteractiveCanvas(Gtk.DrawingArea):
47 def __init__(self, width, height, *args, **kwargs):
48 super(InteractiveCanvas, self).__init__(*args, **kwargs)
53 self.x_offset = self.width / 2.0
54 self.y_offset = self.height / 2.0
58 self.selectable = False
60 self.drag_offset_x = 0
61 self.drag_offset_y = 0
63 self.x = self.width / 2.0
64 self.y = self.height / 2.0
66 self.set_size_request(width, height)
67 self.connect('draw', self.on_draw)
68 self.connect('configure-event', self.on_configure_event)
70 self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK)
71 self.connect('button-press-event', self.on_button_press_event)
72 self.connect('button-release-event', self.on_button_release_event)
73 self.connect('motion-notify-event', self.on_motion_notify_event)
75 def on_configure_event(self, widget, event):
76 self.width = event.width
77 self.height = event.height
79 def on_draw(self, widget, cr):
80 cr.rectangle(0, 0, self.width, self.height)
81 cr.set_source_rgb(1.0, 0.5, 0.5)
84 cr.arc(self.x, self.y, self.radius, 0, 2 * math.pi)
86 _h = self.x / float(self.width)
94 _s = self.y / float(self.height)
95 _r, _g, _b = colorsys.hls_to_rgb(_h, _l, _s)
96 cr.set_source_rgb(_r, _g, _b)
99 cr.set_source_rgb(0, 0, 0)
100 cr.set_line_width(self.border)
103 def on_button_press_event(self, widget, event):
106 self.drag_offset_x = self.x - event.x
107 self.drag_offset_y = self.y - event.y
110 def on_button_release_event(self, widget, event):
111 self.selected = False
114 def on_motion_notify_event(self, widget, event):
116 self.x = clamp(event.x + self.drag_offset_x, [0, self.width])
117 self.y = clamp(event.y + self.drag_offset_y, [0, self.height])
120 old_selectable = self.selectable
121 if in_circle(self.x, self.y, self.radius + self.border, event.x, event.y):
122 self.selectable = True
124 self.selectable = False
126 if self.selectable != old_selectable:
130 class InteractiveApp(Gtk.Application):
131 def __init__(self, title, width, height, *args, **kwargs):
132 super(InteractiveApp, self).__init__(*args, **kwargs)
133 self.connect("activate", self.on_activate)
138 def on_activate(self, data=None):
139 window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
140 window.set_title(self.title)
142 canvas = InteractiveCanvas(self.width, self.height)
145 self.add_window(window)
148 if __name__ == "__main__":
149 app = InteractiveApp(
150 "InteractiveApp", 640, 480,
151 application_id="apps.test.interactiveapp",
152 flags=Gio.ApplicationFlags.FLAGS_NONE)