Skip to content

Instantly share code, notes, and snippets.

@sahib
Created April 6, 2013 19:26
Show Gist options
  • Save sahib/5327302 to your computer and use it in GitHub Desktop.
Save sahib/5327302 to your computer and use it in GitHub Desktop.
Two Cairo Widgets usable for Gtk. Ported from FreyaMPD.
import cairo
from math import pi
from gi.repository import Gtk, Gdk
class CairoGtkWidget(Gtk.DrawingArea):
def __init__(self):
Gtk.DrawingArea.__init__(self)
# Theming Information (so cairo widgets look natural)
self._style_context = self.get_style_context()
# Progress from 0 - allocation.width
self._position = 0
# True when user drags on the widget with the mouse
self._drag_mode = False
# Enable the receival of the appropiate signals:
self.add_events(self.get_events() |
Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK |
Gdk.EventMask.POINTER_MOTION_MASK |
Gdk.EventMask.SCROLL_MASK
)
# Signals used to know when redrawing is desired
self.connect('scroll-event', self.on_scroll_event)
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)
self.connect('draw', self.on_draw)
@property
def theme_active_color(self):
return self._style_context.get_background_color(Gtk.StateFlags.SELECTED)
@property
def theme_inactive_color(self):
return self._style_context.get_color(Gtk.StateFlags.NORMAL)
@property
def percent(self):
return min(max(float(self._position) / self.get_allocation().width, 0.0), 1.0)
@percent.setter
def percent(self, value):
clamped = min(max(value, 0.0), 1.0)
self._position = clamped * self.get_allocation().width
####################
# Widget Signals #
####################
def on_button_press_event(self, widget, event):
self._position = event.x
self.queue_draw()
self._drag_mode = True
return True
def on_button_release_event(self, widget, event):
self._drag_mode = False
return True
def on_scroll_event(self, widget, event):
if event.direction == Gdk.ScrollDirection.UP:
offset = +10
else:
offset = -10
self._position = self._position + offset
self.queue_draw()
return True
def on_motion_notify(self, widget, event):
if self._drag_mode:
self._position = event.x
self.queue_draw()
def on_draw(self, widget, ctx):
# To filled by subclass
pass
class CairoProgressSlider(CairoGtkWidget):
def __init__(self, line_width=2):
# Needs to be first.
CairoGtkWidget.__init__(self)
# If False the colored part is not drawn
self._draw_full_line = True
# Some const attributes
self._line_width = line_width
self._thickness = (self._line_width + 2) / 2
@property
def draw_full_line(self, value):
'If True, color is drawn, if False only the bar is drawn'
self._draw_full_line = value
@property
def draw_full_line(self):
'Check if we shall draw a full line'
return self._draw_full_line
def on_draw(self, area, ctx):
'Draw a nice rounded bar with cairo, colored to show the progress'
# Get the space the widget allocates
alloc = self.get_allocation()
width, height = alloc.width, alloc.height
# inactive and active theming colors
i_color = self.theme_inactive_color
a_color = self.theme_active_color
# Precalculate often used values
radius = height / 2 - self._thickness
mid = self._thickness + radius
midx2 = mid * 2
length = width - midx2
pid2 = pi / 2
# Configure the context
ctx.set_line_cap(cairo.LINE_CAP_SQUARE)
ctx.set_source_rgb(a_color.red, a_color.green, a_color.blue)
ctx.set_line_width(self._line_width)
# Left Rounded Corner
ctx.arc_negative(mid, mid, radius, -pid2, pid2)
# Upper line
ctx.move_to(mid, self._thickness)
ctx.line_to(mid + length, self._thickness)
# Right Rounded Corner
ctx.arc(mid + length, mid, radius, -pid2, pid2)
# Down line
ctx.move_to(mid, midx2 - self._thickness)
ctx.line_to(mid + length, midx2 - self._thickness)
# If we shall draw the full thing: paint a colored rectangle and clip
# it in. Otherwise, we just use the clip mask and draw it.
if self._draw_full_line is True:
ctx.rectangle(mid, self._thickness, length, midx2 - 2 * self._thickness)
ctx.clip()
ctx.paint()
else:
ctx.stroke()
if self._draw_full_line is True:
for col, x, y, w, h in [(a_color, 0, 0, self._position, height),
(i_color, self._position, 0, width - self._position, height)]:
ctx.set_source_rgb(col.red, col.green, col.blue)
ctx.rectangle(x, y, w, h)
ctx.fill()
ctx.stroke()
# Call no other draw functions
return True
class CairoBarSlider(CairoGtkWidget):
def __init__(self, line_width=2, line_gap=1):
CairoGtkWidget.__init__(self)
self._line_width = line_width
self._line_gap = line_gap
def on_draw(self, area, ctx):
alloc = self.get_allocation()
width, height = alloc.width, alloc.height
# Theming support
a_color = self.theme_active_color
i_color = self.theme_inactive_color
# True when bars after self._position are drawn
color_turned = False
ratio = height / float(width)
extra_gap = self._line_width + self._line_gap
# Configure the context to use filling lines and a nice color
ctx.set_line_width(self._line_width)
ctx.set_source_rgb(a_color.red, a_color.green, a_color.blue)
for i in range(0, width, extra_gap):
# Height of this line
lim = height - (i * ratio) - 2
# Draw a straight line up
ctx.move_to(i, height)
ctx.line_to(i, lim)
# No sense in drawing any further
if lim < 1:
break
else:
if color_turned is False and i >= self._position:
# Draw everything till now
ctx.stroke()
# Change the color for the next bars
ctx.set_source_rgb(i_color.red, i_color.green, i_color.blue)
color_turned = True
# Draw the rest
ctx.stroke()
return True
if __name__ == '__main__':
box = Gtk.Box()
box.pack_start(CairoBarSlider(), True, True, 2)
box.pack_start(CairoProgressSlider(), True, True, 2)
win = Gtk.Window()
win.connect("delete-event", Gtk.main_quit)
win.add(box)
win.show_all()
Gtk.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment