Last active
November 16, 2024 22:04
-
-
Save KurtJacobson/57679e5036dc78e6a7a3ba5e0155dad1 to your computer and use it in GitHub Desktop.
Re-positioning Gtk widget with a mouse drag in C and Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <gtk/gtk.h> | |
// Source: | |
// http://www.linuxforums.org/forum/programming-scripting/117713-gtk-moving-widget-mouse-post906490.html#post906490 | |
// higher values make movement more performant | |
// lower values make movement smoother | |
const gint Sensitivity = 1; | |
const gint EvMask = GDK_BUTTON_PRESS_MASK | GDK_BUTTON1_MOTION_MASK; | |
GtkWidget *fixed; | |
int offsetx, offsety, px, py, maxx, maxy; | |
inline static int Min(const int a, const int b) { return b < a ? b : a; } | |
inline static int Max(const int a, const int b) { return b > a ? b : a; } | |
inline static int RoundDownToMultiple(const int i, const int m) | |
{ | |
return i/m*m; | |
} | |
inline static int RoundToNearestMultiple(const int i, const int m) | |
{ | |
if (i % m > (double)m / 2.0d) | |
return (i/m+1)*m; | |
return i/m*m; | |
} | |
static void destroy( GtkWidget *widget, gpointer data ) { gtk_main_quit (); } | |
static gboolean button_press_event( GtkWidget *w, GdkEventButton *event ) | |
{ | |
if (event->button == 1) { | |
GtkWidget* p = w->parent; | |
// offset == distance of parent widget from edge of screen ... | |
gdk_window_get_position(p->window, &offsetx, &offsety); | |
// plus distance from pointer to edge of widget | |
offsetx += (int)event->x; | |
offsety += (int)event->y; | |
// maxx, maxy both relative to the parent | |
// note that we're rounding down now so that these max values don't get | |
// rounded upward later and push the widget off the edge of its parent. | |
maxx = RoundDownToMultiple(p->allocation.width - w->allocation.width, Sensitivity); | |
maxy = RoundDownToMultiple(p->allocation.height - w->allocation.height, Sensitivity); | |
} | |
return TRUE; | |
} | |
static gboolean motion_notify_event( GtkWidget *widget, GdkEventMotion *event ) | |
{ | |
// x_root,x_root relative to screen | |
// x,y relative to parent (fixed widget) | |
// px,py stores previous values of x,y | |
// get starting values for x,y | |
int x = (int)event->x_root - offsetx; | |
int y = (int)event->y_root - offsety; | |
// make sure the potential coordinates x,y: | |
// 1) will not push any part of the widget outside of its parent container | |
// 2) is a multiple of Sensitivity | |
x = RoundToNearestMultiple(Max(Min(x, maxx), 0), Sensitivity); | |
y = RoundToNearestMultiple(Max(Min(y, maxy), 0), Sensitivity); | |
if (x != px || y != py) { | |
px = x; | |
py = y; | |
gtk_fixed_move(GTK_FIXED(fixed), widget, x, y); | |
} | |
return TRUE; | |
} | |
GtkWidget* make_button(const gchar* const text) | |
{ | |
GtkWidget* b = gtk_button_new_with_label(text); | |
gtk_widget_add_events(b, EvMask); | |
gtk_signal_connect(GTK_OBJECT(b), "button_press_event", (GtkSignalFunc)button_press_event, NULL); | |
gtk_signal_connect(GTK_OBJECT(b), "motion_notify_event", (GtkSignalFunc)motion_notify_event, NULL); | |
gtk_widget_show(b); | |
return b; | |
} | |
int main(int argc, char *argv[] ) | |
{ | |
gtk_init (&argc, &argv); | |
// top level window | |
GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL); | |
gtk_container_set_border_width (GTK_CONTAINER (window), 0); | |
g_signal_connect (G_OBJECT (window), "destroy",G_CALLBACK (destroy), NULL); | |
// fixed container | |
fixed=gtk_fixed_new(); | |
gtk_container_add((GtkContainer*)window,(GtkWidget*)fixed); | |
gtk_widget_show (fixed); | |
// buttons | |
gtk_fixed_put(GTK_FIXED(fixed), make_button("A button"), 50, 50); | |
gtk_fixed_put(GTK_FIXED(fixed), make_button("Another button"), 250, 100); | |
gtk_widget_show (window); | |
gtk_main (); | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import gi | |
gi.require_version('Gtk', '3.0') | |
from gi.repository import Gtk, Gdk | |
from gi.repository import GObject | |
# higher values make movement more performant | |
# lower values make movement smoother | |
SENSITIVITY = 1 | |
EvMask = Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON1_MOTION_MASK | |
offsetx = 0 | |
offsety = 0 | |
px = 0 | |
py = 0 | |
maxx = 0 | |
maxy = 0 | |
def Min(a, b): | |
if b < a: | |
return b | |
return a | |
def Max(a, b): | |
if b > a: | |
return b | |
return a | |
def RoundDownToMultiple(i, m): | |
return i/m*m | |
def RoundToNearestMultiple(i, m): | |
if i % m > m / 2: | |
return (i/m+1)*m | |
return i/m*m | |
def button_press_event(w, event): | |
if event.button == 1: | |
p = w.get_parent() | |
# offset == distance of parent widget from edge of screen ... | |
global offsetx, offsety | |
offsetx, offsety = p.get_window().get_position() | |
# plus distance from pointer to edge of widget | |
offsetx += event.x | |
offsety += event.y | |
# maxx, maxy both relative to the parent | |
# note that we're rounding down now so that these max values don't get | |
# rounded upward later and push the widget off the edge of its parent. | |
global maxx, maxy | |
maxx = RoundDownToMultiple(p.get_allocation().width - w.get_allocation().width, SENSITIVITY) | |
maxy = RoundDownToMultiple(p.get_allocation().height - w.get_allocation().height, SENSITIVITY) | |
def motion_notify_event(widget, event): | |
# x_root,x_root relative to screen | |
# x,y relative to parent (fixed widget) | |
# px,py stores previous values of x,y | |
global px, py | |
global offsetx, offsety | |
# get starting values for x,y | |
x = event.x_root - offsetx | |
y = event.y_root - offsety | |
# make sure the potential coordinates x,y: | |
# 1) will not push any part of the widget outside of its parent container | |
# 2) is a multiple of SENSITIVITY | |
x = RoundToNearestMultiple(Max(Min(x, maxx), 0), SENSITIVITY) | |
y = RoundToNearestMultiple(Max(Min(y, maxy), 0), SENSITIVITY) | |
if x != px or y != py: | |
px = x | |
py = y | |
fixed.move(widget, x, y) | |
def make_button(text): | |
b = Gtk.Button.new_with_label(text) | |
b.set_events(EvMask) | |
b.connect("button_press_event", button_press_event) | |
b.connect("motion_notify_event", motion_notify_event) | |
b.show() | |
return b | |
# top level window | |
window = Gtk.Window() | |
window.connect("destroy", Gtk.main_quit) | |
# fixed container | |
fixed = Gtk.Fixed() | |
window.add(fixed) | |
# buttons | |
fixed.put(make_button("A button"), 50, 50) | |
fixed.put(make_button("Another button"), 250, 100) | |
window.show_all() | |
Gtk.main() |
This problem was solved by using the event position relative to the screen root rather than to the window by changing
# get starting values for x,y
x = event.x - offsetx
y = event.y - offsety
to
# get starting values for x,y
x = event.x_root - offsetx
y = event.y_root - offsety
How do I do this with C++
I already corrected the errors, they were due to ambiguities, the updated code for GTK 3 in C:
https://gist.github.com/Fenshu28/cb91acffd95d5a9cee6da2e5e61da5ae
I recently encountered this problem and write a GTK4 version of this.
https://gist.github.com/crab2313/9b1087677388e4d9156b5668b1794349
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here are the erratic position values returned by 'motion-notify-event':
And a video comparing the two examples:
https://youtu.be/mLjwxWCYP2Y