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)
def area(self):
if self.width < 0 or self.height < 0:
return -1
return self.width*self.height
def __iter__(self):
@ -124,21 +126,7 @@ class Size(StructureBase):
yield self.height
class BorderRadius(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
class FourValueStructure(StructureBase):
@classmethod
def _parse_value(cls, value):
f = lambda *args: args
@ -161,6 +149,36 @@ class BorderRadius(StructureBase):
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):
def __init__(self, r=0, g=0, b=0, a=1):
self.r = float(r)
@ -265,6 +283,26 @@ class Rectangle(StructureBase):
def size(self, 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):
"""Checks if the position is within the bounds of the rectangle including the edges"""
pos = Position.from_value(position)

View file

@ -1,6 +1,8 @@
import cairo
import pango
import pangocairo as pc
import math
from .structures import Size, Position, Rectangle, Color, BorderRadius
from .structures import Size, Position, Rectangle, Color, BorderRadius, Padding
from ..events.events import WindowEventSource
debug = True
@ -71,10 +73,32 @@ class WindowSurface(object):
'butt': cairo.LINE_CAP_BUTT,
'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):
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):
color = Color.from_value(color)
line_color = Color.from_value(line_color)
@ -100,27 +124,48 @@ class WindowSurface(object):
context.set_source_rgba(*line_color)
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):
color = Color.from_value(color)
def draw_text(self, text, position, font_size=12,
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)
position = Position.from_value(position)
size = Size.from_value(size)
context = self.context
width = size.width
height = size.height
font_weight = self.font_weights[font_weight]
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_source_rgba(*color)
context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
context.set_font_size(size.height)
context.set_source_rgba(*font_color)
#context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
#context.set_font_size(size.height)
extents = context.text_extents(text)
x,y = (self.position.x+position.x+2,
self.position.y+position.y+extents[3]+2)
x,y = (self.position.x+position.x+self.padding.left,
self.position.y+position.y+self.padding.top)
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'):
@ -245,19 +290,21 @@ class Window(WindowEventSource, WindowSurface):
position = Position.from_value(kwargs.pop('position', Position()))
size = Size.from_value(kwargs.pop('size', Size()))
self.context = kwargs.pop('context', None)
self.min_size = kwargs.pop('min_size', Size(1,1))
self.corner_handle_size = kwargs.pop('corner_handle_size', Size(20, 20))
self.min_size = Size.from_value(kwargs.pop('min_size', Size(1,1)))
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_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_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.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.clip_children = kwargs.pop('clip_children', False)
self.ignore_debug = kwargs.pop('ignore_debug', False)
self.rectangle = Rectangle(position, size)
self.rectangle = Rectangle()
self.children = []
self.parent = None
self.mouse_pos = Position(size.width/2, size.height/2)
@ -269,6 +316,8 @@ class Window(WindowEventSource, WindowSurface):
self.focused = False
self.visible = True
self.accept('mouse-move', self.process_mouse_move)
self.size = size
self.position = position
self.resizable = kwargs.pop('resizable', self._resizable)
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-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):
diff = mouse_pos - self.mouse_diff
old_size = Size.from_value(self.size)
@ -326,13 +394,16 @@ class Window(WindowEventSource, WindowSurface):
def drag_bottomleft_handle(self, obj, mouse_pos):
diff = mouse_pos - self.mouse_diff
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)
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
self.position = new_pos
self.size = new_size
new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
if self.draggable:
self.position = new_pos
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.x = obj.position.x + self.handle_diff.x
@ -342,12 +413,14 @@ class Window(WindowEventSource, WindowSurface):
old_size = Size.from_value(self.size)
new_pos = Position(self.position.x, self.position.y + 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
self.position = new_pos
self.size = new_size
new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
if self.draggable:
self.position = new_pos
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.x = obj.position.x + self.handle_diff.x
@ -357,15 +430,12 @@ class Window(WindowEventSource, WindowSurface):
old_size = Size.from_value(self.size)
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)
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
self.position = new_pos
self.size = new_size
new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
if self.draggable:
self.position = new_pos
self.size = new_size
self.mouse_diff.y = obj.position.y + self.handle_diff.y
self.mouse_diff.x = obj.position.x + self.handle_diff.x
@ -375,11 +445,12 @@ class Window(WindowEventSource, WindowSurface):
old_size = Size.from_value(self.size)
new_pos = Position(self.position.x, self.position.y + 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_size.height = self.min_size.height
self.position = new_pos
self.size = new_size
new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
if self.draggable:
self.position = new_pos
self.size = new_size
self.mouse_diff.y = obj.position.y + self.handle_diff.y
self.mouse_diff.x = obj.position.x + self.handle_diff.x
@ -389,11 +460,12 @@ class Window(WindowEventSource, WindowSurface):
old_size = Size.from_value(self.size)
new_pos = Position(self.position.x+diff.x, self.position.y)
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_size.width = self.min_size.width
self.position = new_pos
self.size = new_size
new_pos, new_size = self._restrict_pos_size(new_pos, new_size)
if self.draggable:
self.position = new_pos
self.size = new_size
self.mouse_diff.x = obj.position.x + self.handle_diff.x
@ -705,7 +777,9 @@ class Window(WindowEventSource, WindowSurface):
def add_child(self, child_window):
if child_window not in self.children:
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
self.children.append(child_window)
@ -737,10 +811,17 @@ class Window(WindowEventSource, WindowSurface):
@size.setter
def size(self, size):
size = Size.from_value(size)
if size.height <= self.min_size.height:
size.height = self.min_size.height
if 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
if diff.height != 0 or diff.width != 0:
self.dispatch('resize', self, size)
@ -769,10 +850,20 @@ class Mouse(Window):
class TextWindow(Window):
def __init__(self, name, text, *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
def render(self):
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',
position=[0,0],
size=[500,200],
max_size=[600, -1],
draggable=True,
resizable=True,
min_size=Size(40,40),
@ -16,11 +17,11 @@ class TestSurface(Surface):
background_color=(1,1,1),
clip_children=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(TestWindow('child2', position=[200,200], 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)