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()