GTK+ started in 2005 with version 2.8 to use Cairo to render the majority of its widgets.Wikipedia wrote:Cairo is a software library used to provide a vector graphics-based, device-independent API for software developers. It is designed to provide primitives for 2-dimensional drawing across a number of different backends. Cairo is designed to use hardware acceleration[1] when available.
I didn't reinvent the wheel but converted C sourcecode snippets from a tutorial of Davyd Madely to PureBasic. The first example simply displays a running clock in a PB window:

Code: Select all
; Converted from C sourcecode in Davyd Madeley's tutorial:
; http://www.gnomejournal.org/article/34/writing-a-widget-using-cairo-and-gtk28
; http://gnomejournal.org/article/36/writing-a-widget-using-cairo-and-gtk28-part-2
EnableExplicit
ImportC ""
  gdk_cairo_create(*Drawable.GdkDrawable)
  gdk_window_set_composited(*Window.GdkWindowObject, Composited.I)
EndImport
ImportC "-lcairo"
  cairo_arc(*CairoContext, xCenter.D, yCenter.D, Radius.D, StartAngle.D, EndAngle.D)
  cairo_destroy(*CairoContext)
  cairo_fill_preserve(*CairoContext)
  cairo_get_line_width(*CairoContext)
  cairo_line_to(*CairoContext, x.D, y.D)
  cairo_move_to(*CairoContext, x.D, y.D)
  cairo_restore(*CairoContext)
  cairo_save(*CairoContext)
  cairo_set_line_width(*CairoContext, LineWidth.D)
  cairo_set_source_rgb(*CairoContext, Red.D, Green.D, Blue.D)
  cairo_stroke(*CairoContext)
EndImport
ProcedureC DrawClockCallback(*Widget.GtkWidget, *Event.GdkEventExpose)
  Protected CairoContext.I
  Protected Hours.I
  Protected i.I
  Protected Inset.I
  Protected Minutes.I
  Protected Radius.D
  Protected Seconds.I
  Protected xCenter.D
  Protected yCenter.D
  Hours = Hour(Date())
  Minutes = Minute(Date())
  Seconds = Second(Date())
  ; ----- Draw clock face
  xCenter = *Widget\allocation\width / 2
  yCenter = *Widget\allocation\height / 2
  If xCenter < yCenter
    Radius = *Widget\allocation\width / 2 - 5
  Else
    Radius = *Widget\allocation\height / 2 - 5
  EndIf
  CairoContext = gdk_cairo_create(*Widget\window)
  cairo_arc(CairoContext, xCenter, yCenter, Radius, 0, 2 * #PI)
  cairo_set_source_rgb(CairoContext, 1, 1, 1)
  cairo_fill_preserve(CairoContext)
  cairo_set_source_rgb(CairoContext, 0, 0, 0)
  cairo_stroke(CairoContext)
  For i = 0 To 11
    If i % 3 = 0
      Inset = 0.2 * Radius
    Else
      Inset = 0.1 * Radius
    EndIf
    cairo_move_to(CairoContext, xCenter + (Radius - Inset) * Cos(i * #PI / 6), yCenter + (Radius - Inset) * Sin(i * #PI / 6))
    cairo_line_to(CairoContext, xCenter + Radius * Cos(i * #PI / 6), yCenter + Radius * Sin(i * #PI / 6))
    cairo_stroke(CairoContext)
  Next i
  cairo_save(CairoContext)
  ; ----- Draw hour hand
  ;       The hour hand is rotated 30 degrees (Pi/6r) per hour + 1/2 a Degree
  ;       (Pi/360) per minute
  ; cairo_set_line_width(CairoContext, 2.5 * cairo_get_line_width(CairoContext))
  cairo_set_line_width(CairoContext, 5)
  cairo_move_to(CairoContext, xCenter, yCenter)
  cairo_line_to(CairoContext, xCenter + Radius / 2 * Sin(#PI / 6 * Hours + #PI / 360 * Minutes), yCenter + Radius / 2 * -Cos(#PI / 6 * Hours + #PI / 360 * Minutes))
  cairo_stroke(CairoContext)
  ; ----- Draw minute hand
  ;       The minute hand is rotated 6 degrees (Pi/30r) per minute
  cairo_set_line_width(CairoContext, 2)
  cairo_move_to (CairoContext, xCenter, yCenter)
  cairo_line_to (CairoContext, xCenter + Radius * 0.75 * Sin(#PI / 30 * Minutes), yCenter + Radius * 0.75 * -Cos(#PI / 30 * Minutes))
  cairo_stroke (CairoContext)
  ; ----- Draw seconds hand
  ;       Operates identically to the minute hand
  cairo_save(CairoContext)
  cairo_set_source_rgb(CairoContext, 1, 0, 0)
  cairo_move_to (CairoContext, xCenter, yCenter)
  cairo_line_to (CairoContext, xCenter + Radius * 0.7 * Sin(#PI / 30 * Seconds), yCenter + Radius * 0.7 * -Cos(#PI / 30 * Seconds))
  cairo_stroke (CairoContext)
  cairo_restore(CairoContext)
  cairo_destroy(CairoContext)
EndProcedure
Define FixedBox.I
Define Region.I
Define *Window.GtkWidget
*Window = OpenWindow(0, 140, 100, 200, 200, "Clock", #PB_Window_SizeGadget)
FixedBox = g_list_nth_data_(gtk_container_get_children_(gtk_bin_get_child_(WindowID(0))), 0)
g_signal_connect_(FixedBox, "expose-event", @DrawClockCallback(), 0)
AddWindowTimer(0, 0, 1000)
Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      RemoveWindowTimer(0, 0)
      Break
    Case #PB_Event_Timer
      Region = gdk_drawable_get_clip_region_(*Window\window)
      gdk_window_invalidate_region_(*Window\window, Region, #True)
      gdk_window_process_updates_(*Window\window, #True)
      gdk_region_destroy_(Region)
  EndSelect
ForEver
I tested both examples successfully in these Linux distributions:
- andLinux/Kubuntu 9.04 (2nd example will terminate with an error message because of missing support for transparency)
- Linux Mint 13 Cinnamon
- Kubuntu 10.04 (only partial transparency of window in 2nd example)
- Kubuntu 12.04
- OpenSuSE 12.1
- Ubuntu 12.04 (Unity)
- Xubuntu 12.04
Code: Select all
; Converted from C sourcecode in Davyd Madeley's tutorial:
; http://www.gnomejournal.org/article/34/writing-a-widget-using-cairo-and-gtk28
; http://gnomejournal.org/article/36/writing-a-widget-using-cairo-and-gtk28-part-2
EnableExplicit
#CAIRO_OPERATOR_SOURCE = 1
ImportC ""
  gdk_cairo_create(*Drawable.GdkDrawable)
  gdk_screen_get_rgba_colormap(*Screen.GdkScreen)
EndImport
ImportC "-lcairo"
  cairo_arc(*CairoContext, xCenter.D, yCenter.D, Radius.D, StartAngle.D, EndAngle.D)
  cairo_destroy(*CairoContext)
  cairo_fill_preserve(*CairoContext)
  cairo_line_to(*CairoContext, x.D, y.D)
  cairo_move_to(*CairoContext, x.D, y.D)
  cairo_paint(CairoContext)
  cairo_restore(*CairoContext)
  cairo_save(*CairoContext)
  cairo_set_line_width(*CairoContext, LineWidth.D)
  cairo_set_operator(*CairoRegion, CairoOperator.I)
  cairo_set_source_rgba(*CairoRegion, Red.D, Green.D, Blue.D, Alpha.D)
  cairo_stroke(*CairoContext)
EndImport
ProcedureC DrawClockCallback(*Widget.GtkWidget, *Event.GdkEventExpose)
  Protected CairoContext.I
  Protected Hours.I
  Protected i.I
  Protected Inset.I
  Protected Minutes.I
  Protected Radius.D
  Protected Seconds.I
  Protected xCenter.D
  Protected yCenter.D
  ; ----- Draw transparent background
  CairoContext = gdk_cairo_create(*Widget\window)
  cairo_set_source_rgba(CairoContext, 1, 1, 1, 0)
  cairo_set_operator(CairoContext, #CAIRO_OPERATOR_SOURCE)
  cairo_paint(CairoContext)
  ; ----- Update current time
  Hours = Hour(Date())
  Minutes = Minute(Date())
  Seconds = Second(Date())
  ; ----- Draw clock face
  xCenter = *Widget\allocation\width / 2
  yCenter = *Widget\allocation\height / 2
  If xCenter < yCenter
    Radius = *Widget\allocation\width / 2 - 5
  Else
    Radius = *Widget\allocation\height / 2 - 5
  EndIf
  cairo_arc(CairoContext, xCenter, yCenter, Radius, 0, 2 * #PI)
  cairo_set_source_rgba(CairoContext, 1, 1, 1, 1)
  cairo_fill_preserve(CairoContext)
  cairo_set_source_rgba(CairoContext, 0, 0, 0, 1)
  cairo_stroke(CairoContext)
  For i = 0 To 11
    If i % 3 = 0
      Inset = 0.2 * Radius
    Else
      Inset = 0.1 * Radius
    EndIf
    cairo_move_to(CairoContext, xCenter + (Radius - Inset) * Cos(i * #PI / 6), yCenter + (Radius - Inset) * Sin(i * #PI / 6))
    cairo_line_to(CairoContext, xCenter + Radius * Cos(i * #PI / 6), yCenter + Radius * Sin(i * #PI / 6))
    cairo_stroke(CairoContext)
  Next i
  cairo_save(CairoContext)
  ; ----- Draw hour hand
  ;       The hour hand is rotated 30 degrees (Pi/6r) per hour + 1/2 a Degree
  ;       (Pi/360) per minute
  cairo_set_line_width(CairoContext, 5)
  cairo_move_to(CairoContext, xCenter, yCenter)
  cairo_line_to(CairoContext, xCenter + Radius / 2 * Sin(#PI / 6 * Hours + #PI / 360 * Minutes), yCenter + Radius / 2 * -Cos(#PI / 6 * Hours + #PI / 360 * Minutes))
  cairo_stroke(CairoContext)
  ; ----- Draw minute hand
  ;       The minute hand is rotated 6 degrees (Pi/30r) per minute
  cairo_set_line_width(CairoContext, 2)
  cairo_move_to(CairoContext, xCenter, yCenter)
  cairo_line_to(CairoContext, xCenter + Radius * 0.75 * Sin(#PI / 30 * Minutes), yCenter + Radius * 0.75 * -Cos(#PI / 30 * Minutes))
  cairo_stroke(CairoContext)
  ; ----- Draw seconds hand
  ;       Operates identically to the minute hand
  cairo_save(CairoContext)
  cairo_set_source_rgba(CairoContext, 1, 0, 0, 1)
  cairo_move_to(CairoContext, xCenter, yCenter)
  cairo_line_to(CairoContext, xCenter + Radius * 0.7 * Sin(#PI / 30 * Seconds), yCenter + Radius * 0.7 * -Cos(#PI / 30 * Seconds))
  cairo_stroke(CairoContext)
  cairo_restore(CairoContext)
  cairo_destroy(CairoContext)
EndProcedure
Procedure RedrawWidget(*Widget.GtkWidget)
  Protected CairoContext.I
  
  CairoContext = gdk_cairo_create(*Widget\window)
  cairo_set_source_rgba(CairoContext, 1.0, 1.0, 1.0, 0.5)
  cairo_set_operator(CairoContext, #CAIRO_OPERATOR_SOURCE)
  cairo_paint(CairoContext)
  cairo_destroy(CairoContext)
EndProcedure
Define Colormap.I
Define FixedBox.I
Define Region.I
Define Screen.I
Define *Window.GtkWidget
*Window = OpenWindow(0, 140, 100, 200, 200, "Clock", #PB_Window_Invisible)
gtk_window_set_decorated_(*Window, #False)
FixedBox = g_list_nth_data_(gtk_container_get_children_(gtk_bin_get_child_(WindowID(0))), 0)
g_signal_connect_(FixedBox, "expose-event", @DrawClockCallback(), 0)
gtk_widget_set_app_paintable_(FixedBox, #True)
; ----- Delete GdkWindow resources to be able to change colormap
gtk_widget_unrealize_(*Window)
; ----- Change colormap to support alpha values and hence allow transparency
Screen = gtk_widget_get_screen_(*Window)
Colormap = gdk_screen_get_rgba_colormap(Screen)
If Colormap
  gtk_widget_set_colormap_(*Window, Colormap)
Else
  MessageRequester("Error", "Your current system configuration doesn't support transparency!")
  End
EndIf
; ----- Realize window and rebuild new GdkWindow
gtk_widget_show_all_(*Window)
AddWindowTimer(0, 0, 1000)
AddKeyboardShortcut(0, #PB_Shortcut_Escape, 0)
Repeat
  Select WaitWindowEvent()
    Case #PB_Event_Menu
      If EventMenu() = 0
        RemoveWindowTimer(0, 0)
        Break
      EndIf
    Case #PB_Event_Timer
      Region = gdk_drawable_get_clip_region_(*Window\window)
      gdk_window_invalidate_region_(*Window\window, Region, #True)
      gdk_window_process_updates_(*Window\window, #True)
      gdk_region_destroy_(Region)
  EndSelect
ForEver



