JGUI/jgui/surface/surface.py

1129 lines
44 KiB
Python

import cairo
import pango
import pangocairo as pc
import gtk
import math
from .structures import Size, Position, Rectangle, Color, BorderRadius, Padding, Gradient, RadialGradient
from ..events.events import WindowEventSource
from ..logger import log
from datetime import datetime
from jgui.settings import DEBUG as debug
class Surface(WindowEventSource):
def __init__(self, size=None, context=None, data=None, render_mouse=True, show_fps=False):
super(Surface, self).__init__()
self.show_fps = show_fps
self.size = Size.from_value(size)
self.mouse_pos = Position()
self.drawing = False
if context is None:
if data is not None:
self.csurface = cairo.ImageSurface.create_for_data(data, cairo.FORMAT_ARGB32, self.size.width, self.size.height)
else:
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, surface=self, ignore_debug=True)
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, surface=self)
self.fps_counter = TextWindow('fps', '0 fps', position=Position(self.size.width-70, 10), size=Size(80,20), context=self.context, surface=self)
self.windows = [self.root_window, self.mouse_icon, self.fps_counter]
self.current_hover_window = self.root_window
self.current_focused_window = self.root_window
self.dt = 0
self.old_time = datetime.now()
self.dtindex = 0
self.max_samples = 60
self.dtlist = [0]*self.max_samples
self.accept('mouse-move', self.process_mouse_move)
def setTopZero(self, context):
context.identity_matrix()
matrix = cairo.Matrix(1, 0, 0,
1, 0, 0)
context.transform(matrix)
def inject_mouse_down(self, button):
self.current_focused_window = self.current_hover_window
self.root_window.inject_mouse_down(button)
def inject_mouse_double(self, button):
self.root_window.inject_mouse_double(button)
def inject_mouse_wheel(self, value):
self.root_window.inject_mouse_wheel(value)
def inject_mouse_up(self, button):
self.root_window.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.render_mouse:
self.mouse_icon.position = pos
self.root_window.inject_mouse_position(pos)
def mouse_inside(self):
"""
Checks if the mouse is inside the window taking into account
all other windows and draw priorities.
"""
#Check all children of root to see if there is a higher priority
#window than the current one
root = self.root_window
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 intersects_mouse:
return item
visited.add(item)
stack.pop()
return None
def process_mouse_move(self, obj, old_mpos, new_mpos):
if not self.current_hover_window or not self.current_hover_window.mouse_down:
self.current_hover_window = self.mouse_inside()
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 calcfps(self, dt):
self.dtlist[self.dtindex] = float(dt)
self.dtindex += 1
if self.dtindex >= self.max_samples:
self.dtindex = 0
return 1.0/(sum(self.dtlist)/float(self.max_samples))
def draw(self):
self.drawing = True
if self.show_fps:
self.dt = (datetime.now() - self.old_time).total_seconds()
self.old_time = datetime.now()
fps = self.calcfps(self.dt)
self.fps_counter.text = '{} fps'.format(round(fps))
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()
self.drawing = False
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.surface.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.r, color.g, color.b, color.a)
context.fill_preserve()
context.set_source_rgba(line_color.r, line_color.g, line_color.b, line_color.a)
context.stroke()
def draw_image(self, image, position, size,
filter='none',
stretch_horizontal=False,
stretch_vertical=False,
keep_ratio=False,
center_horizontal=True,
center_vertical=True, image_offset=(0, 0)):
context = self.surface.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)
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_ratio:
aspect_ratio = im_width/float(im_height)
if width >= height:
if im_height < im_width:
new_height = width/aspect_ratio
else:
new_width = aspect_ratio * height
else:
if im_height > im_width:
new_width = aspect_ratio * height
else:
new_height = width/aspect_ratio
if center_horizontal:
x += width/2.0 - new_width/2.0
if x < self.position.x:
x = self.position.x
if center_vertical:
y += height/2.0 - new_height/2.0
if y < self.position.y:
y = self.position.y
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)
new_scale_x = 1
new_scale_y = 1
if stretch_horizontal or keep_ratio:
new_scale_x = new_width/float(im_width)
if stretch_vertical or keep_ratio:
new_scale_y = new_height/float(im_height)
ct3.scale(new_scale_x, new_scale_y)
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.surface.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.r, font_color.g, font_color.b, font_color.a)
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)
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.surface.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.r, background_color.g, background_color.b, background_color.a)
context.fill_preserve()
context.set_source_rgba(line_color.r, line_color.g, line_color.b, line_color.a)
context.stroke()
def render_radial_gradient(self, gradient, inner_radius=None, outer_radius=None):
context = self.surface.context
position = self.position
size = self.size
gradient = RadialGradient.from_value(gradient)
rp = cairo.RadialGradient(gradient.start_position.x*float(size.width) + position.x,
gradient.start_position.y*float(size.height) + position.y,
inner_radius or gradient.inner_radius,
gradient.end_position.x*float(size.width) + position.x,
gradient.end_position.y*float(size.height) + position.y,
outer_radius or gradient.outer_radius)
for gstop in gradient.stops:
rp.add_color_stop_rgba(gstop.offset, gstop.color.r, gstop.color.g,
gstop.color.b, gstop.color.a)
if gradient.stops:
context.save()
context.set_source(rp)
context.fill_preserve()
context.restore()
def render_linear_gradient(self, gradient):
context = self.surface.context
position = self.position
size = self.size
gradient = Gradient.from_value(gradient)
lp = cairo.LinearGradient(gradient.start_position.x*float(size.width) + position.x,
gradient.start_position.y*float(size.height) + position.y,
gradient.end_position.x*float(size.width) + position.x,
gradient.end_position.y*float(size.height) + position.y)
for gstop in gradient.stops:
lp.add_color_stop_rgba(gstop.offset, gstop.color.r, gstop.color.g,
gstop.color.b, gstop.color.a)
if gradient.stops:
context.save()
context.set_source(lp)
context.fill_preserve()
context.restore()
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, gradient=()):
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)
gradient = Gradient.from_value(gradient)
context = self.surface.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.new_path()
context.rectangle(x, y, width, height)
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()
if gradient.stops:
if gradient._type == 'linear':
self.render_linear_gradient(gradient)
elif gradient._type == 'radial':
self.render_radial_gradient(gradient)
else:
context.set_source_rgba(background_color.r, background_color.g, background_color.b, background_color.a)
else:
context.set_source_rgba(background_color.r, background_color.g, background_color.b, background_color.a)
context.fill_preserve()
context.set_source_rgba(line_color.r, line_color.g, line_color.b, line_color.a)
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.new_path()
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.surface.context.save()
self.render()
for child in self.children:
child.draw()
self.surface.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._surface = kwargs.pop('surface', 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_horizontal = kwargs.pop('background_image_stretch_horizontal', False)
self.background_image_stretch_vertical = kwargs.pop('background_image_stretch_vertical', False)
self.background_image_keep_ratio = kwargs.pop('background_image_keep_ratio', False)
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.gradient = Gradient.from_value(kwargs.pop('gradient', ()))
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.surface = self._surface
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, gradient=self.gradient)
if self.background_image is not None:
self.draw_image(self.background_image, [0, 0], self.size,
filter=self.background_image_filter,
stretch_horizontal=self.background_image_stretch_horizontal,
stretch_vertical=self.background_image_stretch_vertical,
keep_ratio=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_height(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 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
return new_pos, new_size
def _restrict_pos_size_width(self, new_pos, new_size):
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.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 _restrict_pos_size(self, new_pos, new_size):
self._restrict_pos_size_height(new_pos, new_size)
self._restrict_pos_size_width(new_pos, new_size)
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_width(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_height(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):
stack = [self]
while stack:
item = stack.pop()
if item.mouse_inside():
log(button, item.name)
item.mouse_inputs[button] = True
item.mouse_down = True
item.grab_focus()
item.dispatch(button, item, item.mouse_pos)
else:
item.release_focus()
if item.children:
stack.extend(item.children)
def inject_mouse_double(self, button):
stack = [self]
while stack:
item = stack.pop()
if item.mouse_inside():
log(button+'-double', item.name)
item.dispatch(button+'-double', item, item.mouse_pos)
if item.children:
stack.extend(item.children)
def inject_mouse_up(self, button):
stack = [self]
while stack:
item = stack.pop()
if item.mouse_held() and item.mouse_inputs[button]:
log(button+'-up', item.name)
item.mouse_down = False
item.mouse_inputs[button] = False
item.dispatch('{}-up'.format(button), item, item.mouse_pos)
if item.children:
stack.extend(item.children)
def inject_mouse_position(self, pos):
mouse_pos = Position.from_value(pos)
stack = [self]
while stack:
item = stack.pop()
diff = mouse_pos - item.mouse_pos
old_pos = item.mouse_pos
item.mouse_pos = mouse_pos
if diff != Position(0,0):
item.dispatch('mouse-move', item, old_pos, self.mouse_pos)
if item.focused and item.mouse_down:
for button, down in item.mouse_inputs.items():
if down:
item.dispatch('{}-drag'.format(button), item, self.mouse_pos)
item.dispatch('drag', item, self.mouse_pos)
if item.children:
stack.extend(item.children)
def inject_mouse_wheel(self, value):
stack = [self]
while stack:
item = stack.pop()
if item.mouse_inside():
log(item.name, 'scroll', value)
item.dispatch('scroll', item, value)
if item.children:
stack.extend(item.children)
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('focus-lost', 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):
return self is self.surface.current_hover_window
def mouse_held(self):
return self.mouse_down
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 surface(self):
return self._surface
@surface.setter
def surface(self, surface):
self._surface = surface
for child in self.children:
child.surface = self._surface
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.surface = self.surface
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()