// +build !windows,!darwin,!plan9 // 14 march 2014 package ui import ( "unsafe" "image" "fmt" ) // #cgo pkg-config: gtk+-3.0 // #include "gtk_unix.h" // extern gboolean our_area_draw_callback(GtkWidget *, cairo_t *, gpointer); // extern gboolean our_area_button_press_event_callback(GtkWidget *, GdkEvent *, gpointer); // extern gboolean our_area_button_release_event_callback(GtkWidget *, GdkEvent *, gpointer); // extern gboolean our_area_motion_notify_event_callback(GtkWidget *, GdkEvent *, gpointer); // extern gboolean our_area_key_press_event_callback(GtkWidget *, GdkEvent *, gpointer); // extern gboolean our_area_key_release_event_callback(GtkWidget *, GdkEvent *, gpointer); // /* HACK - see https://code.google.com/p/go/issues/detail?id=7548 */ // struct _cairo {}; import "C" func gtkAreaNew() *gtkWidget { drawingarea := C.gtk_drawing_area_new() C.gtk_widget_set_size_request(drawingarea, 320, 240) // we need to explicitly subscribe to mouse events with GtkDrawingArea C.gtk_widget_add_events(drawingarea, C.GDK_BUTTON_PRESS_MASK | C.GDK_BUTTON_RELEASE_MASK | C.GDK_POINTER_MOTION_MASK | C.GDK_BUTTON_MOTION_MASK) // and we need to allow focusing on a GtkDrawingArea to enable keyboard events C.gtk_widget_set_can_focus(drawingarea, C.TRUE) scrollarea := C.gtk_scrolled_window_new((*C.GtkAdjustment)(nil), (*C.GtkAdjustment)(nil)) // need a viewport because GtkDrawingArea isn't natively scrollable C.gtk_scrolled_window_add_with_viewport((*C.GtkScrolledWindow)(unsafe.Pointer(scrollarea)), drawingarea) return fromgtkwidget(scrollarea) } func gtkAreaGetControl(scrollarea *gtkWidget) *gtkWidget { viewport := C.gtk_bin_get_child((*C.GtkBin)(unsafe.Pointer(scrollarea))) control := C.gtk_bin_get_child((*C.GtkBin)(unsafe.Pointer(viewport))) return fromgtkwidget(control) } //export our_area_draw_callback func our_area_draw_callback(widget *C.GtkWidget, cr *C.cairo_t, data C.gpointer) C.gboolean { var x, y, w, h C.double s := (*sysData)(unsafe.Pointer(data)) // thanks to desrt in irc.gimp.net/#gtk+ C.cairo_clip_extents(cr, &x, &y, &w, &h) cliprect := image.Rect(int(x), int(y), int(w), int(h)) i := s.handler.Paint(cliprect) // pixel order is [R G B A] (see Example 1 on https://developer.gnome.org/gdk-pixbuf/2.26/gdk-pixbuf-The-GdkPixbuf-Structure.html) so we don't have to convert anything // gdk-pixbuf is not alpha-premultiplied (thanks to desrt in irc.gimp.net/#gtk+) pixbuf := C.gdk_pixbuf_new_from_data( (*C.guchar)(unsafe.Pointer(&i.Pix[0])), C.GDK_COLORSPACE_RGB, C.TRUE, // has alpha channel 8, // bits per sample C.int(i.Rect.Dx()), C.int(i.Rect.Dy()), C.int(i.Stride), nil, nil) // do not free data C.gdk_cairo_set_source_pixbuf(cr, pixbuf, C.gdouble(cliprect.Min.X), C.gdouble(cliprect.Min.Y)) // that just set the brush that cairo uses: we have to actually draw now // (via https://developer.gnome.org/gtkmm-tutorial/stable/sec-draw-images.html.en) C.cairo_rectangle(cr, x, y, w, h) // breaking the nrom here since we have the double data already C.cairo_fill(cr) C.g_object_unref((C.gpointer)(unsafe.Pointer(pixbuf))) // free pixbuf return C.FALSE // signals handled without stopping the event chain (thanks to desrt again) } var area_draw_callback = C.GCallback(C.our_area_draw_callback) // shared code for finishing up and sending a mouse event func finishMouseEvent(data C.gpointer, me MouseEvent, mb uint, x C.gdouble, y C.gdouble, state C.guint, gdkwindow *C.GdkWindow) { s := (*sysData)(unsafe.Pointer(data)) // GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+) C.gdk_keymap_add_virtual_modifiers( C.gdk_keymap_get_for_display(C.gdk_window_get_display(gdkwindow)), (*C.GdkModifierType)(unsafe.Pointer(&state))) if (state & C.GDK_CONTROL_MASK) != 0 { me.Modifiers |= Ctrl } if (state & C.GDK_META_MASK) != 0 { me.Modifiers |= Alt } if (state & C.GDK_SHIFT_MASK) != 0 { me.Modifiers |= Shift } // the mb != # checks exclude the Up/Down button from Held if mb != 1 && (state & C.GDK_BUTTON1_MASK) != 0 { me.Held = append(me.Held, 1) } if mb != 2 && (state & C.GDK_BUTTON2_MASK) != 0 { me.Held = append(me.Held, 2) } if mb != 3 && (state & C.GDK_BUTTON3_MASK) != 0 { me.Held = append(me.Held, 3) } // TODO keep? if mb != 4 && (state & C.GDK_BUTTON4_MASK) != 0 { me.Held = append(me.Held, 4) } if mb != 5 && (state & C.GDK_BUTTON5_MASK) != 0 { me.Held = append(me.Held, 5) } me.Pos = image.Pt(int(x), int(y)) s.handler.Mouse(me) } //export our_area_button_press_event_callback func our_area_button_press_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { e := (*C.GdkEventButton)(unsafe.Pointer(event)) me := MouseEvent{ // GDK button ID == our button ID Down: uint(e.button), } switch e._type { case C.GDK_BUTTON_PRESS: me.Count = 1 case C.GDK_2BUTTON_PRESS: me.Count = 2 default: // ignore triple-clicks and beyond; we don't handle those return C.FALSE // TODO really false? } finishMouseEvent(data, me, me.Down, e.x, e.y, e.state, e.window) return C.FALSE // TODO really false? } var area_button_press_event_callback = C.GCallback(C.our_area_button_press_event_callback) //export our_area_button_release_event_callback func our_area_button_release_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { e := (*C.GdkEventButton)(unsafe.Pointer(event)) me := MouseEvent{ // GDK button ID == our button ID Up: uint(e.button), } finishMouseEvent(data, me, me.Up, e.x, e.y, e.state, e.window) return C.FALSE // TODO really false? } var area_button_release_event_callback = C.GCallback(C.our_area_button_release_event_callback) //export our_area_motion_notify_event_callback func our_area_motion_notify_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { e := (*C.GdkEventMotion)(unsafe.Pointer(event)) me := MouseEvent{} finishMouseEvent(data, me, 0, e.x, e.y, e.state, e.window) return C.FALSE // TODO really false? } var area_motion_notify_event_callback = C.GCallback(C.our_area_motion_notify_event_callback) //export our_area_key_press_event_callback func our_area_key_press_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { e := (*C.GdkEventKey)(unsafe.Pointer(event)) fmt.Printf("PRESS %#v\n", e) fmt.Printf("this (%d/GDK_KEY_%s):\n", e.keyval, C.GoString((*C.char)(unsafe.Pointer( C.gdk_keyval_name(e.keyval))))) pk(e.keyval, e.window) fmt.Printf("%d/GDK_KEY_A:\n", C.GDK_KEY_A) pk(C.GDK_KEY_A, e.window) fmt.Printf("%d/GDK_KEY_a:\n", C.GDK_KEY_a) pk(C.GDK_KEY_a, e.window) return C.FALSE // TODO really false? } var area_key_press_event_callback = C.GCallback(C.our_area_key_press_event_callback) //export our_area_key_release_event_callback func our_area_key_release_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { e := (*C.GdkEventKey)(unsafe.Pointer(event)) fmt.Printf("RELEASE %#v\n", e) fmt.Printf("this (%d/GDK_KEY_%s):\n", e.keyval, C.GoString((*C.char)(unsafe.Pointer( C.gdk_keyval_name(e.keyval))))) pk(e.keyval, e.window) fmt.Printf("%d/GDK_KEY_A:\n", C.GDK_KEY_A) pk(C.GDK_KEY_A, e.window) fmt.Printf("%d/GDK_KEY_a:\n", C.GDK_KEY_a) pk(C.GDK_KEY_a, e.window) return C.FALSE // TODO really false? } var area_key_release_event_callback = C.GCallback(C.our_area_key_release_event_callback) func pk(keyval C.guint, window *C.GdkWindow) { var kk *C.GdkKeymapKey var nk C.gint km := C.gdk_keymap_get_for_display(C.gdk_window_get_display(window)) b := C.gdk_keymap_get_entries_for_keyval(km, keyval, &kk, &nk) if b == C.FALSE { fmt.Println("(no key equivalent)") return } ok := kk for i := C.gint(0); i < nk; i++ { fmt.Printf("equiv %d/%d: %#v\n", i + 1, nk, kk) xkk := uintptr(unsafe.Pointer(kk)) xkk += unsafe.Sizeof(kk) kk = (*C.GdkKeymapKey)(unsafe.Pointer(xkk)) } C.g_free(C.gpointer(unsafe.Pointer(ok))) }