Running clock in a transparent window drawn with Cairo

Linux specific forum
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Running clock in a transparent window drawn with Cairo

Post by Shardik »

This is a simple example to demonstrate how to display a running clock using Cairo functions. Cairo is built-in in all Linux distributions, so you don't have to install any additional libraries.
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.
GTK+ started in 2005 with version 2.8 to use Cairo to render the majority of its widgets.

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:

Image

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
For the second example I modified the first one to display the same opaque clock as above in a totally transparent window. You can use this technique to position widgets directly on the desktop or on other windows without any visible window. Since the window has no titlebar anymore you have to close the clock by pressing the Escape key...

Image

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
User avatar
idle
Always Here
Always Here
Posts: 5042
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Running clock in a transparent window drawn with Cairo

Post by idle »

thanks.
Windows 11, Manjaro, Raspberry Pi OS
Image
lakomet
User
User
Posts: 53
Joined: Mon Apr 04, 2011 3:56 am
Location: Russia,Angarsk

Re: Running clock in a transparent window drawn with Cairo

Post by lakomet »

Goodly, thanks.
Linux Mint Maya(Mate), x86, PureBasic 5.00(5.10b1)
Oma
Enthusiast
Enthusiast
Posts: 312
Joined: Thu Jun 26, 2014 9:17 am
Location: Germany

Re: Running clock in a transparent window drawn with Cairo

Post by Oma »

Hi Shardik!

Unfortunately, I have never seen this posting before.
Since i had no luck with my gtk2-experiments on transparent linux window backgrounds, i say many thanks - again :wink:

So i've implemented a gtk3-method for transparent window backgrounds and made some mods for gtk3 in your code.
Furthermore (since i've tried it and it worked) I've added a way, to drag the 'clock / window' by a left-mouse-click in the content, based on an example from mestnyi: http://www.purebasic.fr/english/viewtop ... 15&t=63938.

Code: Select all

;For gtk3...
EnableExplicit

#CAIRO_OPERATOR_SOURCE = 1

ImportC ""
	gdk_cairo_create(*Drawable.GdkDrawable)
	gdk_screen_get_rgba_visual(*screen.GdkScreen)
	gdk_display_get_pointer(*display.GdkDisplay, *screen.GdkScreen, *x, *y, *mask);    gtk2.2+
	gtk_widget_set_visual(*widget.GtkWidget, *visual.GdkVisual)
	gtk_widget_get_window(*widget.GtkWidget)
	gdk_screen_get_system_visual(*screen.GdkScreen)
	g_signal_connect(*instance, detailed_signal.p-ascii, *c_handler, *pdata, destroy= 0, flags= 0) As "g_signal_connect_data"
	gtk_widget_get_allocation(*widget.GtkWidget, *allocation.GtkAllocation)
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 Callback_ButtonMotion(*widget.GtkWindow, *event.GdkEventButton, window); to drag window, based on a mestnyi-example
	Static.i    isPressed
	Static.i    wX, wY;                                                          pointer window relative
	Static.i    wBorderL, wBorderT;                                              left, upper window delta
	Protected.i rX, rY, rMask;                                                   pointer root relative
	
	If *event\type = #GDK_BUTTON_PRESS And *event\button= 1; 2 / 3
		wX = WindowMouseX(window)
		wY = WindowMouseY(window)
		isPressed= #True
		gdk_window_get_position_(gtk_widget_get_window(*widget), @wBorderL, @wBorderT)
		gdk_window_get_root_origin_(gtk_widget_get_window(*widget), @rX, @rY)
		wBorderL- rX : wBorderT- rY
	ElseIf *event\type = #GDK_BUTTON_RELEASE
		isPressed= #False
	EndIf

	If isPressed
		gdk_display_get_pointer(gdk_display_get_default_(), #Null, @rX, @rY, @rMask)
		gtk_window_move_(*widget, rX- wX- wBorderL, rY- wY- wBorderT)
		If Not (rMask & #GDK_BUTTON1_MASK) : isPressed= #False : EndIf;            against trailing, quick stop
	EndIf

	ProcedureReturn #False
EndProcedure

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
  Protected Allocation.GtkAllocation

  ; ----- Draw transparent background

  CairoContext = gdk_cairo_create(gtk_widget_get_window(*widget))
  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
  
  gtk_widget_get_allocation(*widget, @Allocation)
  xCenter = Allocation\width / 2
  yCenter = Allocation\height / 2
  
  If xCenter < yCenter
    Radius = xCenter - 5
  Else
    Radius = yCenter - 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

ProcedureC Callback_ScreenChanged(*widget.GtkWidget, *old_screen.GdkScreen, user_data)
	Protected *Screen.GdkScreen= gtk_widget_get_screen_(*widget)
	Protected *Visual.GdkVisual= gdk_screen_get_rgba_visual(*Screen)
	
	If Not *Visual
		*Visual = gdk_screen_get_system_visual(*Screen)
	EndIf
	gtk_widget_set_visual(*widget, *Visual)
EndProcedure

Define Screen.I
Define *Window.GtkWidget

OpenWindow(0, 140, 100, 200, 200, "Clock", #PB_Window_Invisible)
	*Window= WindowID(0)
	
	gtk_window_set_decorated_(*Window, #False)
	gtk_widget_set_app_paintable_(*Window, #True)
	gtk_widget_unrealize_(*Window)
	
	g_signal_connect(*Window, "draw", @DrawClockCallback(), 0)
	g_signal_connect(*Window, "screen-changed", @Callback_ScreenChanged(), #Null)
	Callback_ScreenChanged(*Window, #Null, #Null)
	g_signal_connect(*Window, "event",  @Callback_ButtonMotion(), 0);            to drag window by click in the content
	
	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
			DrawClockCallback(*Window, 0)
			
	EndSelect
ForEver
Regards, Charly
PureBasic 5.4-5.7, Linux: (X/L/K)Ubuntus+Mint - Windows XP (32Bit)
PureBasic Linux-API-Library & Viewer: http://www.chabba.de
vwidmer
Enthusiast
Enthusiast
Posts: 282
Joined: Mon Jan 20, 2014 6:32 pm

Re: Running clock in a transparent window drawn with Cairo

Post by vwidmer »

Awesome thanks
WARNING: I dont know what I am doing! I just put stuff here and there and sometimes like magic it works. So please improve on my code and post your changes so I can learn more. TIA
Post Reply