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 |
71 Gdk.EventMask.BUTTON_RELEASE_MASK |
72 Gdk.EventMask.POINTER_MOTION_MASK)
73 self.connect('button-press-event', self.on_button_press_event)
74 self.connect('button-release-event', self.on_button_release_event)
75 self.connect('motion-notify-event', self.on_motion_notify_event)
77 def on_configure_event(self, widget, event):
78 self.width = event.width
79 self.height = event.height
81 def on_draw(self, widget, cr):
82 cr.rectangle(0, 0, self.width, self.height)
83 cr.set_source_rgb(1.0, 0.5, 0.5)
86 cr.arc(self.x, self.y, self.radius, 0, 2 * math.pi)
88 _h = self.x / float(self.width)
96 _s = self.y / float(self.height)
97 _r, _g, _b = colorsys.hls_to_rgb(_h, _l, _s)
98 cr.set_source_rgb(_r, _g, _b)
101 cr.set_source_rgb(0, 0, 0)
102 cr.set_line_width(self.border)
105 def on_button_press_event(self, widget, event):
108 self.drag_offset_x = self.x - event.x
109 self.drag_offset_y = self.y - event.y
112 def on_button_release_event(self, widget, event):
113 self.selected = False
116 def on_motion_notify_event(self, widget, event):
118 self.x = clamp(event.x + self.drag_offset_x, [0, self.width])
119 self.y = clamp(event.y + self.drag_offset_y, [0, self.height])
122 old_selectable = self.selectable
123 if in_circle(self.x, self.y, self.radius + self.border,
125 self.selectable = True
127 self.selectable = False
129 if self.selectable != old_selectable:
133 class InteractiveApp(Gtk.Application):
134 def __init__(self, title, width, height, *args, **kwargs):
135 super(InteractiveApp, self).__init__(*args, **kwargs)
136 self.connect("activate", self.on_activate)
141 def on_activate(self, data=None):
142 window = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
143 window.set_title(self.title)
145 canvas = InteractiveCanvas(self.width, self.height)
148 self.add_window(window)
151 if __name__ == "__main__":
152 app = InteractiveApp(
153 "InteractiveApp", 640, 480,
154 application_id="apps.test.interactiveapp",
155 flags=Gio.ApplicationFlags.FLAGS_NONE)