Add and example of mouse interaction with cairo and Gtk+
authorAntonio Ospite <ospite@studenti.unina.it>
Thu, 7 Mar 2013 19:10:51 +0000 (20:10 +0100)
committerAntonio Ospite <ospite@studenti.unina.it>
Thu, 7 Mar 2013 19:10:51 +0000 (20:10 +0100)
cairo-gtk-interactivity.py [new file with mode: 0755]

diff --git a/cairo-gtk-interactivity.py b/cairo-gtk-interactivity.py
new file mode 100755 (executable)
index 0000000..5efa973
--- /dev/null
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+
+# An example of mouse interaction with cairo and Gtk+
+#
+# Copyright (C) 2013  Antonio Ospite <ospite@studenti.unina.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+# 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)