JGUI/jgui/surface/surface.py

1009 lines
39 KiB
Python

import cairo
import pango
import pangocairo as pc
import gtk
import math
from .structures import Size, Position, Rectangle, Color, BorderRadius, Padding
from ..events.events import WindowEventSource
from ..logger import log
debug = True
class Surface(object):
def __init__(self, size=None, context=None, render_mouse=True):
self.size = Size.from_value(size)
if context is None:
self.csurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.size.width, self.size.height)
self.context = cairo.Context(self.csurface)
else:
self.context = context
self.root_window = Window('root', position=Position(0,0), size=self.size, context=self.context)
self.render_mouse = render_mouse
if self.render_mouse:
self.mouse_icon = Mouse('mouse', position=Position(0, 0), size=Size(12,20), context=self.context)
self.windows = [self.root_window, self.mouse_icon]
def setTopZero(self, context):
context.identity_matrix()
matrix = cairo.Matrix(1, 0, 0,
1, 0, 0)
context.transform(matrix)
def inject_mouse_position(self, pos):
if self.render_mouse:
self.mouse_icon.position = pos
self.root_window.inject_mouse_position(pos)
def inject_mouse_down(self, button):
self.root_window.inject_mouse_down(button)
def inject_mouse_up(self, button):
self.root_window.inject_mouse_up(button)
def notify_window_resize(self, width, height):
self.size = Size(width, height)
self.root_window.size = self.size
self.csurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.size.width, self.size.height)
self.context = cairo.Context(self.csurface)
stack = self.windows[:]
while stack:
item = stack.pop()
item.context = self.context
if item.children:
stack.extend(item.children)
def draw(self):
#self.setTopZero(ctx)
#self.context.identity_matrix()
self.context.set_operator(cairo.OPERATOR_CLEAR)
self.context.rectangle(0.0, 0.0, self.size.width, self.size.height)
self.context.fill()
self.context.set_operator(cairo.OPERATOR_OVER)
for window in self.windows:
window.draw()
class WindowSurface(object):
line_joins = {'miter': cairo.LINE_JOIN_MITER,
'round': cairo.LINE_JOIN_ROUND,
'bevel': cairo.LINE_JOIN_BEVEL}
line_caps = {'round': cairo.LINE_CAP_ROUND,
'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()]
filters = {'none': cairo.FILTER_FAST,
'good': cairo.FILTER_GOOD,
'best': cairo.FILTER_BEST,
'bilinear': cairo.FILTER_BILINEAR,
'gaussian': cairo.FILTER_GAUSSIAN,
'nearest' : cairo.FILTER_NEAREST}
def __init__(self):
super(WindowSurface, self).__init__()
def load_image(self, image_path):
if image_path is not None:
if isinstance(image_path, basestring):
return gtk.gdk.pixbuf_new_from_file(image_path)
else:
return image_path
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)
context = self.context
position = Position.from_value(position)
size = Size.from_value(size)
width = size.width
height = size.height
x,y = (self.position.x+position.x+width/2,
self.position.y+position.y+height/2)
context.set_line_width(line_width)
context.save()
context.translate(x, y)
context.scale(width/2.0-line_width/2.0, height/2.0-line_width/2.0)
context.arc(0, 0, 1, start_angle, end_angle * math.pi/180.0)
context.restore()
context.set_source_rgba(*color)
context.fill_preserve()
context.set_source_rgba(*line_color)
context.stroke()
def draw_image(self, image, position, size,
filter='none', stretch=True,
keep_aspect=True, center_horizontal=True,
center_vertical=True, image_offset=(0, 0)):
context = self.context
position = Position.from_value(position)
offset = Position.from_value(image_offset)
size = Size.from_value(size)
width = size.width
height = size.height
x,y = (self.position.x+position.x,
self.position.y+position.y)
context.rectangle(x, y, width, height)
context.clip()
if isinstance(image, basestring):
image = self.load_image(image)
im_width = image.get_width()
im_height = image.get_height()
new_height = height
new_width = width
if keep_aspect:
aspect_ratio = min(im_width, im_height)/float(max(im_width, im_height))
if width >= height:
new_width = aspect_ratio * new_height
else:
new_height = aspect_ratio * new_width
if center_horizontal:
x += width/2.0 - new_width/2.0
if x < 0:
x = 0
if center_vertical:
y += height/2.0 - new_height/2.0
if y < 0:
y = 0
im_surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(new_width), int(new_height))
sp = cairo.SurfacePattern(im_surf)
ct2 = cairo.Context(im_surf)
ct2.set_source(sp)
ct3 = gtk.gdk.CairoContext(ct2)
if stretch:
ct3.scale(new_width/float(im_width), new_height/float(im_height))
ct3.set_source_pixbuf(image, -offset.x, -offset.y)
ct3.get_source().set_filter(self.filters[filter])
ct3.paint()
context.set_source_surface(im_surf,x,y)
context.paint()
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)
context = self.context
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(*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+self.padding.left,
self.position.y+position.y+self.padding.top)
context.move_to(x, y)
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'):
if lines:
line_color = Color.from_value(line_color)
background_color = Color.from_value(background_color)
context = self.context
start_pos = self.position + lines[0]
context.set_line_width(line_width)
context.move_to(start_pos.x, start_pos.y)
for line in lines[1:]:
next_pos = self.position+line
context.line_to(next_pos.x, next_pos.y)
context.close_path()
try:
context.set_line_cap(self.line_joins[line_join])
except KeyError:
pass
try:
context.set_line_join(self.line_caps[line_cap])
except KeyError:
pass
context.set_source_rgba(*background_color)
context.fill_preserve()
context.set_source_rgba(*line_color)
context.stroke()
def draw_rounded_rect(self, position, size, background_color=(1,1,1), line_width=1, line_color=(0,0,0), corner_radius=0, line_dashed=False, clip=False):
position = Position.from_value(position)
size = Size.from_value(size)
background_color = Color.from_value(background_color)
line_color = Color.from_value(line_color)
corner_radius = BorderRadius.from_value(corner_radius)
context = self.context
radius = corner_radius
degrees = math.pi / 180.0
x = position.x + self.position.x
y = position.y + self.position.y
width = size.width
height = size.height
if clip: #clips the entire region so any child windows will be confined to the parent
context.arc(x + width - radius.topright - line_width/2.0,
y + radius.topright + line_width/2.0,
radius.topright+self.border_width/2.0, -90 * degrees, 0 * degrees)
context.arc(x + width - radius.bottomright - line_width/2.0,
y + height - radius.bottomright - line_width/2.0,
radius.bottomright+self.border_width/2.0, 0 * degrees, 90 * degrees)
context.arc(x + radius.bottomleft + line_width/2.0,
y + height - radius.bottomleft - line_width/2.0,
radius.bottomleft+self.border_width/2.0, 90 * degrees, 180 * degrees)
context.arc(x + radius.topleft + line_width/2.0,
y + radius.topleft + line_width/2.0,
radius.topleft+self.border_width/2.0, 180 * degrees, 270 * degrees)
context.close_path()
context.clip()
context.new_path()
context.arc(x + width - radius.topright - line_width/2.0,
y + radius.topright + line_width/2.0,
radius.topright, -90 * degrees, 0 * degrees)
context.arc(x + width - radius.bottomright - line_width/2.0,
y + height - radius.bottomright - line_width/2.0,
radius.bottomright, 0 * degrees, 90 * degrees)
context.arc(x + radius.bottomleft + line_width/2.0,
y + height - radius.bottomleft - line_width/2.0,
radius.bottomleft, 90 * degrees, 180 * degrees)
context.arc(x + radius.topleft + line_width/2.0,
y + radius.topleft + line_width/2.0,
radius.topleft, 180 * degrees, 270 * degrees)
context.close_path()
context.set_source_rgba(*background_color)
context.fill_preserve()
context.set_source_rgba(*line_color)
context.set_line_width(line_width)
context.save()
if line_dashed:
context.set_dash([line_width, line_width])
context.stroke()
context.restore()
if clip: #clips the entire region so any child windows will be confined to the parent
context.arc(x + width - radius.topright - line_width/2.0,
y + radius.topright + line_width/2.0,
radius.topright-self.border_width/2.0, -90 * degrees, 0 * degrees)
context.arc(x + width - radius.bottomright - line_width/2.0,
y + height - radius.bottomright - line_width/2.0,
radius.bottomright-self.border_width/2.0, 0 * degrees, 90 * degrees)
context.arc(x + radius.bottomleft + line_width/2.0,
y + height - radius.bottomleft - line_width/2.0,
radius.bottomleft-self.border_width/2.0, 90 * degrees, 180 * degrees)
context.arc(x + radius.topleft + line_width/2.0,
y + radius.topleft + line_width/2.0,
radius.topleft-self.border_width/2.0, 180 * degrees, 270 * degrees)
context.close_path()
context.clip()
def render(self):
if debug and not self.ignore_debug:
self.draw_rounded_rect([0,0], [self.size.width, self.size.height], background_color=(0,0,1,0.1), line_color=(0,0,1,0.4), line_width=self.border_width+0.5, corner_radius=self.border_radius, line_dashed=True)
def draw(self):
if self.visible:
self.context.save()
self.render()
for child in self.children:
child.draw()
self.context.restore()
class Window(WindowEventSource, WindowSurface):
def __init__(self, name, **kwargs):
super(Window, self).__init__()
self._draggable = False
self._resizable = False
self._root = None
self.name = name
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 = 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 = 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.background_image = self.load_image(kwargs.pop('background_image', None))
self.background_image_filter = kwargs.pop('background_image_filter','none')
self.background_image_stretch = kwargs.pop('background_image_stretch', True)
self.background_image_keep_ratio = kwargs.pop('background_image_keep_ratio', True)
self.background_image_center_horizontal = kwargs.pop('background_image_center_horizontal', True)
self.background_image_center_vertical = kwargs.pop('background_image_center_vertical', True)
self.background_image_offset = Position.from_value(kwargs.pop('background_image_offset', (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()
self.children = []
self.parent = None
self.mouse_pos = Position(size.width/2, size.height/2)
self.mouse_diff = Position(0, 0)
self.mouse_in = False
self.mouse_hover = False
self.mouse_down = False
self.mouse_inputs = dict.fromkeys(self.mouse_button_down_events, False)
self.focused = False
self.visible = True
self.accept('mouse-move', self.process_mouse_move)
self.size = size
self.position = position
self.context = self._context
self.resizable = kwargs.pop('resizable', self._resizable)
self.draggable = kwargs.pop('draggable', self._draggable)
for key, value in kwargs.items():
setattr(self, key, value)
def render(self):
super(Window, self).render()
self.draw_rounded_rect([0,0], [self.size.width, self.size.height],
background_color=self.background_color,
line_color=self.border_color,
line_width=self.border_width,
corner_radius=self.border_radius,
line_dashed=self.dashed_border,
clip=self.clip_children)
if self.background_image is not None:
self.draw_image(self.background_image, [0, 0], self.size,
filter=self.background_image_filter,
stretch=self.background_image_stretch,
keep_aspect=self.background_image_keep_ratio,
center_horizontal=self.background_image_center_horizontal,
center_vertical=self.background_image_center_vertical,
image_offset=self.background_image_offset)
@property
def resizable(self):
return self._resizable
@resizable.setter
def resizable(self, value):
if not self._resizable and value:
self.init_resize_handles()
elif not value and self._resizable:
self.remove_resize_handles()
self._resizable = value
@property
def draggable(self):
return self._draggable
@draggable.setter
def draggable(self, value):
self._draggable = value
self.enable_drag(self._draggable)
def enable_drag(self, value):
if value:
self.accept('mouse-left-drag', self.drag)
self.accept('mouse-left', self.click)
self.accept('mouse-left-up', self.click_up)
else:
self.reject('mouse-left-drag', self.drag)
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)
self.size = self.size + diff
self.mouse_diff.y = obj.position.y + self.handle_diff.y
self.mouse_diff.x = obj.position.x + self.handle_diff.x
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_size = Size(self.size.width - diff.x, self.size.height + diff.y)
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
def drag_topright_handle(self, obj, mouse_pos):
diff = mouse_pos - self.mouse_diff
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)
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
def drag_topleft_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 + diff.y)
new_size = Size(self.size.width - diff.x, self.size.height - diff.y)
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
def drag_top_handle(self, obj, mouse_pos):
diff = mouse_pos - self.mouse_diff
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)
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
def drag_left_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_size = Size(self.size.width - diff.x, self.size.height)
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
def drag_bottom_handle(self, obj, mouse_pos):
diff = mouse_pos - self.mouse_diff
old_height = self.size.height
self.size = Size(self.size.width, self.size.height+diff.y)
self.mouse_diff.y = obj.position.y + self.handle_diff.y
def drag_right_handle(self, obj, mouse_pos):
diff = mouse_pos - self.mouse_diff
old_width = self.size.width
self.size = Size(self.size.width + diff.x, self.size.height)
self.mouse_diff.x = obj.position.x + self.handle_diff.x
def remove_resize_handles(self):
self.remove_child(self.top_handle)
self.remove_child(self.topleft_handle)
self.remove_child(self.topright_handle)
self.remove_child(self.left_handle)
self.remove_child(self.right_handle)
self.remove_child(self.bottom_handle)
self.remove_child(self.bottomright_handle)
self.remove_child(self.bottomleft_handle)
self.top_handle = None
self.topleft_handle = None
self.topright_handle = None
self.left_handle = None
self.right_handle = None
self.bottom_handle = None
self.bottomright_handle = None
self.bottomleft_handle = None
def init_resize_handles(self):
self.handle_diff = Position(0,0)
self.top_handle = Window('top_handle')
self.top_handle.accept('drag', self.drag_top_handle)
self.top_handle.accept('mouse-left', self.handle_click)
self.top_handle.accept('mouse-left-up', self.handle_click_up)
self.topleft_handle = Window('topleft_handle')
self.topleft_handle.accept('drag', self.drag_topleft_handle)
self.topleft_handle.accept('mouse-left', self.handle_click)
self.topleft_handle.accept('mouse-left-up', self.handle_click_up)
self.topright_handle = Window('topright_handle')
self.topright_handle.accept('drag', self.drag_topright_handle)
self.topright_handle.accept('mouse-left', self.handle_click)
self.topright_handle.accept('mouse-left-up', self.handle_click_up)
self.right_handle = Window('right_handle')
self.right_handle.accept('drag', self.drag_right_handle)
self.right_handle.accept('mouse-left', self.handle_click)
self.right_handle.accept('mouse-left-up', self.handle_click_up)
self.bottomright_handle = Window('bottomright_handle')
self.bottomright_handle.accept('drag', self.drag_bottomright_handle)
self.bottomright_handle.accept('mouse-left', self.handle_click)
self.bottomright_handle.accept('mouse-left-up', self.handle_click_up)
self.bottom_handle = Window('bottom_handle')
self.bottom_handle.accept('drag', self.drag_bottom_handle)
self.bottom_handle.accept('mouse-left', self.handle_click)
self.bottom_handle.accept('mouse-left-up', self.handle_click_up)
self.bottomleft_handle = Window('bottomleft_handle')
self.bottomleft_handle.accept('drag', self.drag_bottomleft_handle)
self.bottomleft_handle.accept('mouse-left', self.handle_click)
self.bottomleft_handle.accept('mouse-left-up', self.handle_click_up)
self.left_handle = Window('left_handle')
self.left_handle.accept('drag', self.drag_left_handle)
self.left_handle.accept('mouse-left', self.handle_click)
self.left_handle.accept('mouse-left-up', self.handle_click_up)
self.vertical_edge_size = Size()
self.horizontal_edge_size = Size()
self.add_child(self.top_handle)
self.add_child(self.topleft_handle)
self.add_child(self.topright_handle)
self.add_child(self.left_handle)
self.add_child(self.right_handle)
self.add_child(self.bottom_handle)
self.add_child(self.bottomright_handle)
self.add_child(self.bottomleft_handle)
self.update_resize_handles()
def update_resize_handles(self):
buffer = self.edge_handle_buffer
top = self.top_handle
topleft = self.topleft_handle
topright = self.topright_handle
right = self.right_handle
bottomright = self.bottomright_handle
bottom = self.bottom_handle
bottomleft = self.bottomleft_handle
left = self.left_handle
vertical_edge_size = self.vertical_edge_size
horizontal_edge_size = self.horizontal_edge_size
corner_handle_size = self.corner_handle_size
x, y = self.position.x, self.position.y
vertical_edge_size.width = self.size.width - 2*corner_handle_size.width
vertical_edge_size.height = self.edge_handle_width
horizontal_edge_size.width = self.edge_handle_width
horizontal_edge_size.height = self.size.height - 2*corner_handle_size.height
top.position.x = x + corner_handle_size.width
top.position.y = y - buffer.height
top.size = vertical_edge_size + [0, buffer.height]
topleft.position.x = x - buffer.width
topleft.position.y = y - buffer.height
topleft.size = corner_handle_size + buffer
topright.position.x = x + self.size.width - corner_handle_size.width
topright.position.y = y - buffer.height
topright.size = corner_handle_size + buffer
bottom.position.x = x + corner_handle_size.width
bottom.position.y = y + self.size.height - vertical_edge_size.height
bottom.size = vertical_edge_size + [0, buffer.height]
bottomleft.position.x = x - buffer.width
bottomleft.position.y = y + self.size.height - corner_handle_size.height
bottomleft.size = corner_handle_size + buffer
bottomright.position.x = x + self.size.width-corner_handle_size.width
bottomright.position.y = y + self.size.height-corner_handle_size.height
bottomright.size = corner_handle_size + buffer
right.position.x = x + self.size.width-horizontal_edge_size.width
right.position.y = y + corner_handle_size.height
right.size = horizontal_edge_size + [buffer.width, 0]
left.position.x = x - buffer.width
left.position.y = y + corner_handle_size.height
left.size = horizontal_edge_size + [buffer.width, 0]
def drag(self, obj, mouse_pos):
obj.position = mouse_pos - self.mouse_diff
def click(self, obj, mouse_pos):
self.mouse_diff = mouse_pos - obj.position
def click_up(self, obj, mouse_pos):
self.mouse_diff = Position(0, 0)
def handle_click_up(self, obj, mouse_pos):
self.mouse_diff = Position(0, 0)
self.handle_diff = Position(0, 0)
self.dispatch('resize-end', self)
def handle_click(self, obj, mouse_pos):
self.mouse_diff = mouse_pos
self.handle_diff = mouse_pos - obj.position
self.dispatch('resize-start', self)
def show(self):
self.visible = True
def hide(self):
self.visible = False
def get_clip_parent(self):
parent = self.parent
l = []
while parent is not None:
if parent.clip_children:
l.append(parent)
parent = parent.parent
if l:
return l[-1]
def inject_mouse_down(self, button):
if self.mouse_inside():
log(button, self.name)
self.mouse_inputs[button] = True
self.mouse_down = True
self.grab_focus()
self.dispatch(button, self, self.mouse_pos)
else:
self.release_focus()
for child in self.children:
child.inject_mouse_down(button)
def inject_mouse_up(self, button):
if self.mouse_held() and self.mouse_inputs[button]:
log(button+'-up', self.name)
self.mouse_down = False
self.mouse_inputs[button] = False
self.dispatch('{}-up'.format(button), self, self.mouse_pos)
for child in self.children:
child.inject_mouse_up(button)
def inject_mouse_position(self, pos):
mouse_pos = Position.from_value(pos)
diff = mouse_pos - self.mouse_pos
old_pos = self.mouse_pos
self.mouse_pos = mouse_pos
if diff != Position(0,0):
self.dispatch('mouse-move', self, old_pos, self.mouse_pos)
if self.focused and self.mouse_down:
for button, down in self.mouse_inputs.items():
if down:
self.dispatch('{}-drag'.format(button), self, self.mouse_pos)
self.dispatch('drag', self, self.mouse_pos)
for child in self.children:
child.inject_mouse_position(pos)
def inject_mouse_wheel(self, value):
self.dispatch('scroll', self, value)
def grab_focus(self):
self.focused = True
parent = self
#Reorder all the windows so that they are drawn on top
while parent is not None:
if parent.parent is not None:
children = parent.parent.children[:]
children.remove(parent)
children.append(parent)
parent.parent.children = children
parent = parent.parent
self.dispatch('focus', self)
def release_focus(self):
if self.focused:
log('released', self.name)
self.focused = False
self.mouse_down = False
#clear events that may have been triggered
#eg. User clicks on one window, holds, and
#then releases on another
for key in self.mouse_inputs:
self.mouse_inputs[key] = False
self.dispatch('focus-lost', self)
@property
def root(self):
if self._root is None:
parent = self.parent
if parent is None:
return self
while parent is not None:
root = parent
parent = parent.parent
self._root = root
return self._root
def mouse_inside(self):
"""
Checks if the mouse is inside the window taking into account
all other windows and draw priorities.
"""
rec = self.rectangle
clip_parent = self.get_clip_parent()
if clip_parent is not None:
rec = self.rectangle.intersection(clip_parent.rectangle)
res = rec.intersects_with(self.mouse_pos)
if not res:
return False
#Check all children of root to see if there is a higher priority
#window than the current one
root = self.root
stack = [root]
visited = set()
while stack:
item = stack[-1]
rec = item.rectangle
clip_parent = item.get_clip_parent()
if clip_parent is not None:
rec = item.rectangle.intersection(clip_parent.rectangle)
intersects_mouse = rec.intersects_with(self.mouse_pos)
if item.children and not set(item.children).issubset(visited):
stack.extend(item.children)
else:
if item is self:
break
else:
res &= not intersects_mouse
visited.add(item)
stack.pop()
return res
def mouse_held(self):
res = self.mouse_down
stack = [self.root]
visited = set()
while len(stack) > 0:
item = stack[-1]
if item.children and not set(item.children).issubset(visited):
stack.extend(item.children)
else:
if self is not item:
res |= item.mouse_down
visited.add(item)
stack.pop()
return res
def process_mouse_move(self, obj, old_mpos, new_mpos):
mouse_focus = self.mouse_inside()
if not self.mouse_held():
if mouse_focus:
if not self.mouse_in:
log('mouse-enter', self.name)
self.dispatch('mouse-enter', self)
self.dispatch('hover', self)
self.mouse_in = True
else:
if self.mouse_in:
log('mouse-leave', self.name)
self.dispatch('mouse-leave', self)
self.mouse_in = False
@property
def context(self):
return self._context
@context.setter
def context(self, context):
self._context = context
for child in self.children:
child.context = self._context
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] +\
[self.padding.left, self.padding.top]
child_window.context = self.context
self.children.append(child_window)
def remove_child(self, child_window):
try:
self.children.remove(child_window)
child_window.parent = None
except ValueError:
pass
@property
def position(self):
return self.rectangle.position
@position.setter
def position(self, position):
position = Position.from_value(position)
diff = position - self.rectangle.position
if diff.x != 0 or diff.y != 0:
self.dispatch('move', self, position)
for child in self.children:
child.position = child.position + diff
self.rectangle.position = position
@property
def size(self):
return self.rectangle.size
@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)
for child in self.children:
pass
# child.size = child.size + diff
self.rectangle.size = size
if self.resizable:
self.update_resize_handles()
class Mouse(Window):
def __init__(self, *args, **kwargs):
super(Mouse, self).__init__(*args, **kwargs)
self.lines = [
[0, 0],
[0, self.size.height*0.85],
[self.size.width*0.32, self.size.height*0.675],
[self.size.width*0.52, self.size.height*0.9],
[self.size.width*0.72, self.size.height*0.85],
[self.size.width*0.52, self.size.height*0.62],
[self.size.width*0.92, self.size.height*0.6]
]
def render(self):
self.draw_lines(self.lines, line_width=self.size.height/20)
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.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)
class ImageWindow(Window):
def __init__(self, name, image_path, *args, **kwargs):
super(ImageWindow, self).__init__(name, *args, **kwargs)
self.image_path = image_path
self.image = self.load_image(self.image_path)
def render(self):
self.draw_image(self.image, [0,0], self.size)
super(ImageWindow, self).render()