Fancied up the text window with pango. Added lots of font options and

effects.

Fixed some bugs with resizing a window when it's not draggable and
resizing a window when the max size is (-1, -1)
This commit is contained in:
Joey Payne 2014-02-06 08:00:24 -07:00
commit 5854cfee86
3 changed files with 195 additions and 65 deletions

View file

@ -117,6 +117,8 @@ class Size(StructureBase):
return Size(self.width*other, self.height*other) return Size(self.width*other, self.height*other)
def area(self): def area(self):
if self.width < 0 or self.height < 0:
return -1
return self.width*self.height return self.width*self.height
def __iter__(self): def __iter__(self):
@ -124,21 +126,7 @@ class Size(StructureBase):
yield self.height yield self.height
class BorderRadius(StructureBase): class FourValueStructure(StructureBase):
def __init__(self, *args, **kwargs):
values = self._parse_value(args)
self.topleft = kwargs.get('topleft', values[0])
self.topright = kwargs.get('topright', values[1])
self.bottomright = kwargs.get('bottomright', values[2])
self.bottomleft = kwargs.get('bottomleft', values[3])
def __iter__(self):
yield self.topleft
yield self.topright
yield self.bottomright
yield self.bottomleft
@classmethod @classmethod
def _parse_value(cls, value): def _parse_value(cls, value):
f = lambda *args: args f = lambda *args: args
@ -161,6 +149,36 @@ class BorderRadius(StructureBase):
return cls(*cls._parse_value(value)) return cls(*cls._parse_value(value))
class BorderRadius(FourValueStructure):
def __init__(self, *args, **kwargs):
values = self._parse_value(args)
self.topleft = kwargs.get('topleft', values[0])
self.topright = kwargs.get('topright', values[1])
self.bottomright = kwargs.get('bottomright', values[2])
self.bottomleft = kwargs.get('bottomleft', values[3])
def __iter__(self):
yield self.topleft
yield self.topright
yield self.bottomright
yield self.bottomleft
class Padding(FourValueStructure):
def __init__(self, *args, **kwargs):
values = self._parse_value(args)
self.top = kwargs.get('top', values[0])
self.right = kwargs.get('right', values[1])
self.bottom = kwargs.get('bottom', values[2])
self.left = kwargs.get('left', values[3])
def __iter__(self):
yield self.top
yield self.right
yield self.bottom
yield self.left
class Color(StructureBase): class Color(StructureBase):
def __init__(self, r=0, g=0, b=0, a=1): def __init__(self, r=0, g=0, b=0, a=1):
self.r = float(r) self.r = float(r)
@ -265,6 +283,26 @@ class Rectangle(StructureBase):
def size(self, size): def size(self, size):
self._size = Size.from_value(size) self._size = Size.from_value(size)
def contains(self, other):
other = Rectangle.from_value(other)
if self.intersects_with(other.position) and\
self.intersects_with(other.position + other.size - [1,1]):
return True
return False
def intersection(self, other):
other = Rectangle.from_value(other)
if other.contains(self):
return self
elif self.contains(other):
return other
if self.intersects_with(other.position):
return Rectangle(other.position, self.position + self.size - other.position)
elif other.intersects_with(self.position):
return Rectangle(self.position, other.position + other.size - self.position)
else:
return Rectangle()
def intersects_with(self, position): def intersects_with(self, position):
"""Checks if the position is within the bounds of the rectangle including the edges""" """Checks if the position is within the bounds of the rectangle including the edges"""
pos = Position.from_value(position) pos = Position.from_value(position)

View file

@ -1,6 +1,8 @@
import cairo import cairo
import pango
import pangocairo as pc
import math import math
from .structures import Size, Position, Rectangle, Color, BorderRadius from .structures import Size, Position, Rectangle, Color, BorderRadius, Padding
from ..events.events import WindowEventSource from ..events.events import WindowEventSource
debug = True debug = True
@ -71,10 +73,32 @@ class WindowSurface(object):
'butt': cairo.LINE_CAP_BUTT, 'butt': cairo.LINE_CAP_BUTT,
'square': cairo.LINE_CAP_SQUARE} 'square': cairo.LINE_CAP_SQUARE}
font_weights = {'bold': pango.WEIGHT_BOLD,
'normal': pango.WEIGHT_NORMAL,
'book': pango.WEIGHT_BOOK,
'heavy': pango.WEIGHT_HEAVY,
'light': pango.WEIGHT_LIGHT,
'medium': pango.WEIGHT_MEDIUM,
'semibold': pango.WEIGHT_SEMIBOLD,
'thin': pango.WEIGHT_THIN,
'ultrabold': pango.WEIGHT_ULTRABOLD,
'ultraheavy': pango.WEIGHT_ULTRAHEAVY,
'ultralight': pango.WEIGHT_ULTRALIGHT}
font_styles = {'italic': pango.STYLE_ITALIC,
'oblique': pango.STYLE_OBLIQUE,
'normal': pango.STYLE_NORMAL}
wrap_modes = {'word': pango.WRAP_WORD,
'char': pango.WRAP_CHAR,
'word_char': pango.WRAP_WORD_CHAR}
font_map = pc.cairo_font_map_get_default()
font_list = [f.get_name() for f in font_map.list_families()]
def __init__(self): def __init__(self):
super(WindowSurface, self).__init__() super(WindowSurface, self).__init__()
def draw_circle(self, position, size, color=(1,1,1,1), line_width=1.0, line_color=(0,0,0,1), start_angle=0.0, end_angle=360.0): def draw_circle(self, position, size, color=(1,1,1,1), line_width=1.0, line_color=(0,0,0,1), start_angle=0.0, end_angle=360.0):
color = Color.from_value(color) color = Color.from_value(color)
line_color = Color.from_value(line_color) line_color = Color.from_value(line_color)
@ -100,27 +124,48 @@ class WindowSurface(object):
context.set_source_rgba(*line_color) context.set_source_rgba(*line_color)
context.stroke() context.stroke()
def draw_text(self, text, position, size, color=(0,0,0,1), line_width=1.0, background_color=(1,1,1,0), fill_color=None): def draw_text(self, text, position, font_size=12,
color = Color.from_value(color) font_weight='normal',
font_style='normal', font_color=(0,0,0,1),
font_family='Sans', word_wrap='word',
alignment=pango.ALIGN_LEFT, line_width=1.0,
background_color=(1,1,1,0), fill_color=None):
color = Color.from_value(font_color)
background_color = Color.from_value(background_color) background_color = Color.from_value(background_color)
position = Position.from_value(position) position = Position.from_value(position)
size = Size.from_value(size)
context = self.context context = self.context
width = size.width font_weight = self.font_weights[font_weight]
height = size.height font_style = self.font_styles[font_style]
pc_context = pc.CairoContext(context)
pc_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
layout = pc_context.create_layout()
font = pango.FontDescription('{} {}'.format(font_family, font_size))
font.set_weight(font_weight)
font.set_style(font_style)
layout.set_font_description(font)
layout.set_text(text)
layout.set_wrap(self.wrap_modes[word_wrap])
width = self.size.width - (self.padding.left + self.padding.right)
layout.set_width(int(width*pango.SCALE))
layout.set_alignment(alignment)
context.set_line_width(line_width) context.set_line_width(line_width)
context.set_source_rgba(*color) context.set_source_rgba(*font_color)
context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) #context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
context.set_font_size(size.height) #context.set_font_size(size.height)
extents = context.text_extents(text) extents = context.text_extents(text)
x,y = (self.position.x+position.x+2, x,y = (self.position.x+position.x+self.padding.left,
self.position.y+position.y+extents[3]+2) self.position.y+position.y+self.padding.top)
context.move_to(x, y) context.move_to(x, y)
context.show_text(text) pc_context.update_layout(layout)
pc_context.show_layout(layout)
#context.show_text(text)
def draw_lines(self, lines, line_color=(0,0,0,1), background_color=(1,1,1,1), line_width=1, line_join='miter', line_cap='butt'): def draw_lines(self, lines, line_color=(0,0,0,1), background_color=(1,1,1,1), line_width=1, line_join='miter', line_cap='butt'):
@ -245,19 +290,21 @@ class Window(WindowEventSource, WindowSurface):
position = Position.from_value(kwargs.pop('position', Position())) position = Position.from_value(kwargs.pop('position', Position()))
size = Size.from_value(kwargs.pop('size', Size())) size = Size.from_value(kwargs.pop('size', Size()))
self.context = kwargs.pop('context', None) self.context = kwargs.pop('context', None)
self.min_size = kwargs.pop('min_size', Size(1,1)) self.min_size = Size.from_value(kwargs.pop('min_size', Size(1,1)))
self.corner_handle_size = kwargs.pop('corner_handle_size', Size(20, 20)) self.max_size = Size.from_value(kwargs.pop('max_size', Size(-1,-1)))
self.corner_handle_size = Size.from_value(kwargs.pop('corner_handle_size', Size(20, 20)))
self.edge_handle_width = kwargs.pop('edge_handle_width', 10) self.edge_handle_width = kwargs.pop('edge_handle_width', 10)
self.edge_handle_buffer = kwargs.pop('edge_handle_buffer', Size(5, 5)) self.edge_handle_buffer = Size.from_value(kwargs.pop('edge_handle_buffer', Size(5, 5)))
self.border_width = kwargs.pop('border_width', 1) self.border_width = kwargs.pop('border_width', 1)
self.border_color = Color.from_value(kwargs.pop('border_color', (0,0,0,0))) self.border_color = Color.from_value(kwargs.pop('border_color', (0,0,0,0)))
self.background_color = Color.from_value(kwargs.pop('background_color', (0,0,0,0))) self.background_color = Color.from_value(kwargs.pop('background_color', (0,0,0,0)))
self.border_radius = BorderRadius.from_value(kwargs.pop('border_radius', 1)) self.border_radius = BorderRadius.from_value(kwargs.pop('border_radius', 1))
self.padding = Padding.from_value(kwargs.pop('padding', 0))
self.dashed_border = kwargs.pop('dashed_border', False) self.dashed_border = kwargs.pop('dashed_border', False)
self.clip_children = kwargs.pop('clip_children', False) self.clip_children = kwargs.pop('clip_children', False)
self.ignore_debug = kwargs.pop('ignore_debug', False) self.ignore_debug = kwargs.pop('ignore_debug', False)
self.rectangle = Rectangle(position, size) self.rectangle = Rectangle()
self.children = [] self.children = []
self.parent = None self.parent = None
self.mouse_pos = Position(size.width/2, size.height/2) self.mouse_pos = Position(size.width/2, size.height/2)
@ -269,6 +316,8 @@ class Window(WindowEventSource, WindowSurface):
self.focused = False self.focused = False
self.visible = True self.visible = True
self.accept('mouse-move', self.process_mouse_move) self.accept('mouse-move', self.process_mouse_move)
self.size = size
self.position = position
self.resizable = kwargs.pop('resizable', self._resizable) self.resizable = kwargs.pop('resizable', self._resizable)
self.draggable = kwargs.pop('draggable', self._draggable) self.draggable = kwargs.pop('draggable', self._draggable)
@ -316,6 +365,25 @@ class Window(WindowEventSource, WindowSurface):
self.reject('mouse-left', self.click) self.reject('mouse-left', self.click)
self.reject('mouse-left-up', self.click_up) self.reject('mouse-left-up', self.click_up)
def _restrict_pos_size(self, new_pos, new_size):
if new_size.height <= self.min_size.height:
new_pos.y = self.position.y + self.size.height - self.min_size.height
new_size.height = self.min_size.height
if new_size.width <= self.min_size.width:
new_pos.x = self.position.x + self.size.width - self.min_size.width
new_size.width = self.min_size.width
if self.max_size.height > -1 and new_size.height >= self.max_size.height:
new_pos.y = self.position.y + self.size.height - self.max_size.height
new_size.height = self.max_size.height
if self.max_size.width > -1 and new_size.width >= self.max_size.width:
new_pos.x = self.position.x + self.size.width - self.max_size.width
new_size.width = self.max_size.width
return new_pos, new_size
def drag_bottomright_handle(self, obj, mouse_pos): def drag_bottomright_handle(self, obj, mouse_pos):
diff = mouse_pos - self.mouse_diff diff = mouse_pos - self.mouse_diff
old_size = Size.from_value(self.size) old_size = Size.from_value(self.size)
@ -326,13 +394,16 @@ class Window(WindowEventSource, WindowSurface):
def drag_bottomleft_handle(self, obj, mouse_pos): def drag_bottomleft_handle(self, obj, mouse_pos):
diff = mouse_pos - self.mouse_diff diff = mouse_pos - self.mouse_diff
old_size = Size.from_value(self.size) old_size = Size.from_value(self.size)
new_pos = Position(self.position.x+diff.x, self.position.y) new_pos = Position(self.position.x + diff.x, self.position.y)
new_size = Size(self.size.width - diff.x, self.size.height + diff.y) new_size = Size(self.size.width - diff.x, self.size.height + diff.y)
if new_size.width <= self.min_size.width:
new_pos.x = self.position.x + self.size.width - self.min_size.width new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
new_size.width = self.min_size.width
if self.draggable:
self.position = new_pos self.position = new_pos
self.size = new_size self.size = new_size
else:
self.size = [self.size.width, new_size.height]
self.mouse_diff.y = obj.position.y + self.handle_diff.y self.mouse_diff.y = obj.position.y + self.handle_diff.y
self.mouse_diff.x = obj.position.x + self.handle_diff.x self.mouse_diff.x = obj.position.x + self.handle_diff.x
@ -342,12 +413,14 @@ class Window(WindowEventSource, WindowSurface):
old_size = Size.from_value(self.size) old_size = Size.from_value(self.size)
new_pos = Position(self.position.x, self.position.y + diff.y) new_pos = Position(self.position.x, self.position.y + diff.y)
new_size = Size(self.size.width + diff.x, self.size.height - diff.y) new_size = Size(self.size.width + diff.x, self.size.height - diff.y)
if new_size.height <= self.min_size.height:
new_pos.y = self.position.y + self.size.height - self.min_size.height
new_size.height = self.min_size.height
new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
if self.draggable:
self.position = new_pos self.position = new_pos
self.size = new_size self.size = new_size
else:
self.size = [new_size.width, self.size.height]
self.mouse_diff.y = obj.position.y + self.handle_diff.y self.mouse_diff.y = obj.position.y + self.handle_diff.y
self.mouse_diff.x = obj.position.x + self.handle_diff.x self.mouse_diff.x = obj.position.x + self.handle_diff.x
@ -357,13 +430,10 @@ class Window(WindowEventSource, WindowSurface):
old_size = Size.from_value(self.size) old_size = Size.from_value(self.size)
new_pos = Position(self.position.x + diff.x, self.position.y + diff.y) new_pos = Position(self.position.x + diff.x, self.position.y + diff.y)
new_size = Size(self.size.width - diff.x, self.size.height - diff.y) new_size = Size(self.size.width - diff.x, self.size.height - diff.y)
if new_size.height <= self.min_size.height:
new_pos.y = self.position.y + self.size.height - self.min_size.height
new_size.height = self.min_size.height
if new_size.width <= self.min_size.width:
new_pos.x = self.position.x + self.size.width - self.min_size.width
new_size.width = self.min_size.width
new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
if self.draggable:
self.position = new_pos self.position = new_pos
self.size = new_size self.size = new_size
@ -375,9 +445,10 @@ class Window(WindowEventSource, WindowSurface):
old_size = Size.from_value(self.size) old_size = Size.from_value(self.size)
new_pos = Position(self.position.x, self.position.y + diff.y) new_pos = Position(self.position.x, self.position.y + diff.y)
new_size = Size(self.size.width, self.size.height - diff.y) new_size = Size(self.size.width, self.size.height - diff.y)
if new_size.height <= self.min_size.height:
new_pos.y = self.position.y + self.size.height - self.min_size.height new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
new_size.height = self.min_size.height
if self.draggable:
self.position = new_pos self.position = new_pos
self.size = new_size self.size = new_size
@ -389,9 +460,10 @@ class Window(WindowEventSource, WindowSurface):
old_size = Size.from_value(self.size) old_size = Size.from_value(self.size)
new_pos = Position(self.position.x+diff.x, self.position.y) new_pos = Position(self.position.x+diff.x, self.position.y)
new_size = Size(self.size.width - diff.x, self.size.height) new_size = Size(self.size.width - diff.x, self.size.height)
if new_size.width <= self.min_size.width:
new_pos.x = self.position.x + self.size.width - self.min_size.width new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
new_size.width = self.min_size.width
if self.draggable:
self.position = new_pos self.position = new_pos
self.size = new_size self.size = new_size
@ -705,7 +777,9 @@ class Window(WindowEventSource, WindowSurface):
def add_child(self, child_window): def add_child(self, child_window):
if child_window not in self.children: if child_window not in self.children:
child_window.parent = self child_window.parent = self
child_window.position = child_window.position + self.position + [self.border_width/2, self.border_width/2] child_window.position = child_window.position + self.position +\
[self.border_width/2, self.border_width/2] +\
[self.padding.left, self.padding.top]
child_window.context = self.context child_window.context = self.context
self.children.append(child_window) self.children.append(child_window)
@ -737,10 +811,17 @@ class Window(WindowEventSource, WindowSurface):
@size.setter @size.setter
def size(self, size): def size(self, size):
size = Size.from_value(size) size = Size.from_value(size)
if size.height <= self.min_size.height: if size.height <= self.min_size.height:
size.height = self.min_size.height size.height = self.min_size.height
if size.width <= self.min_size.width: if size.width <= self.min_size.width:
size.width = self.min_size.width size.width = self.min_size.width
if self.max_size.width > -1 and size.width >= self.max_size.width:
size.width = self.max_size.width
if self.max_size.height > -1 and size.height >= self.max_size.height:
size.height = self.max_size.height
diff = size - self.rectangle.size diff = size - self.rectangle.size
if diff.height != 0 or diff.width != 0: if diff.height != 0 or diff.width != 0:
self.dispatch('resize', self, size) self.dispatch('resize', self, size)
@ -769,10 +850,20 @@ class Mouse(Window):
class TextWindow(Window): class TextWindow(Window):
def __init__(self, name, text, *args, **kwargs): def __init__(self, name, text, *args, **kwargs):
super(TextWindow, self).__init__(name, *args, **kwargs) super(TextWindow, self).__init__(name, *args, **kwargs)
self.font_size = kwargs.pop('font_size', 12)
self.font_style = kwargs.pop('font_style', 'normal')
self.font_weight = kwargs.pop('font_weight', 'normal')
self.font_family = kwargs.pop('font_family', 'Sans')
self.word_wrap = kwargs.pop('word_wrap', 'word')
self.font_color = Color.from_value(kwargs.pop('font_color', (0,0,0,1)))
self.text = text self.text = text
def render(self): def render(self):
super(TextWindow, self).render() super(TextWindow, self).render()
self.draw_text(self.text, [0,0], self.size) self.draw_text(self.text, [0,0],
self.font_size, font_weight=self.font_weight,
font_style=self.font_style, font_family=self.font_family,
font_color=self.font_color, word_wrap=self.word_wrap)

View file

@ -7,6 +7,7 @@ class TestSurface(Surface):
my_win = Window('child', my_win = Window('child',
position=[0,0], position=[0,0],
size=[500,200], size=[500,200],
max_size=[600, -1],
draggable=True, draggable=True,
resizable=True, resizable=True,
min_size=Size(40,40), min_size=Size(40,40),
@ -16,11 +17,11 @@ class TestSurface(Surface):
background_color=(1,1,1), background_color=(1,1,1),
clip_children=True, clip_children=True,
ignore_debug=True) ignore_debug=True)
my_win.add_child(TextWindow('text','Micro Bean.', position=[20,20], size=[100, 50])) my_win.add_child(TextWindow('text','Micro Bean is the best bean ever.', position=[20,20], size=[100, 50], resizable=True, clip_children=True, font_color=(1,0,1), padding=20, draggable=True))
self.root_window.add_child(my_win) self.root_window.add_child(my_win)
self.root_window.add_child(TestWindow('child2', position=[200,200], size=[200,200], draggable=True)) self.root_window.add_child(TestWindow('child2', position=[200,200], size=[200,200], draggable=True))
child3 = TestWindow('child3', position=[300,300], size=[200,200], draggable=True) child3 = TestWindow('child3', position=[300,300], size=[200,200], draggable=True)
child3.add_child(TestWindow('child3-1', position=[10,10], size=[50,50], draggable=True)) child3.add_child(TestWindow('child3-1', position=[10,10], size=[50,50], draggable=True, resizable=True))
self.root_window.add_child(child3) self.root_window.add_child(child3)