diff --git a/jgui/surface/structures.py b/jgui/surface/structures.py index 1012022..1378ecd 100644 --- a/jgui/surface/structures.py +++ b/jgui/surface/structures.py @@ -117,6 +117,8 @@ class Size(StructureBase): return Size(self.width*other, self.height*other) def area(self): + if self.width < 0 or self.height < 0: + return -1 return self.width*self.height def __iter__(self): @@ -124,21 +126,7 @@ class Size(StructureBase): yield self.height -class BorderRadius(StructureBase): - def __init__(self, *args, **kwargs): - values = self._parse_value(args) - self.topleft = kwargs.get('topleft', values[0]) - self.topright = kwargs.get('topright', values[1]) - self.bottomright = kwargs.get('bottomright', values[2]) - self.bottomleft = kwargs.get('bottomleft', values[3]) - - - def __iter__(self): - yield self.topleft - yield self.topright - yield self.bottomright - yield self.bottomleft - +class FourValueStructure(StructureBase): @classmethod def _parse_value(cls, value): f = lambda *args: args @@ -161,6 +149,36 @@ class BorderRadius(StructureBase): return cls(*cls._parse_value(value)) +class BorderRadius(FourValueStructure): + def __init__(self, *args, **kwargs): + values = self._parse_value(args) + self.topleft = kwargs.get('topleft', values[0]) + self.topright = kwargs.get('topright', values[1]) + self.bottomright = kwargs.get('bottomright', values[2]) + self.bottomleft = kwargs.get('bottomleft', values[3]) + + def __iter__(self): + yield self.topleft + yield self.topright + yield self.bottomright + yield self.bottomleft + + +class Padding(FourValueStructure): + def __init__(self, *args, **kwargs): + values = self._parse_value(args) + self.top = kwargs.get('top', values[0]) + self.right = kwargs.get('right', values[1]) + self.bottom = kwargs.get('bottom', values[2]) + self.left = kwargs.get('left', values[3]) + + def __iter__(self): + yield self.top + yield self.right + yield self.bottom + yield self.left + + class Color(StructureBase): def __init__(self, r=0, g=0, b=0, a=1): self.r = float(r) @@ -265,6 +283,26 @@ class Rectangle(StructureBase): def size(self, size): self._size = Size.from_value(size) + def contains(self, other): + other = Rectangle.from_value(other) + if self.intersects_with(other.position) and\ + self.intersects_with(other.position + other.size - [1,1]): + return True + return False + + def intersection(self, other): + other = Rectangle.from_value(other) + if other.contains(self): + return self + elif self.contains(other): + return other + if self.intersects_with(other.position): + return Rectangle(other.position, self.position + self.size - other.position) + elif other.intersects_with(self.position): + return Rectangle(self.position, other.position + other.size - self.position) + else: + return Rectangle() + def intersects_with(self, position): """Checks if the position is within the bounds of the rectangle including the edges""" pos = Position.from_value(position) diff --git a/jgui/surface/surface.py b/jgui/surface/surface.py index 48126e8..f982030 100644 --- a/jgui/surface/surface.py +++ b/jgui/surface/surface.py @@ -1,6 +1,8 @@ import cairo +import pango +import pangocairo as pc import math -from .structures import Size, Position, Rectangle, Color, BorderRadius +from .structures import Size, Position, Rectangle, Color, BorderRadius, Padding from ..events.events import WindowEventSource debug = True @@ -71,10 +73,32 @@ class WindowSurface(object): 'butt': cairo.LINE_CAP_BUTT, 'square': cairo.LINE_CAP_SQUARE} + font_weights = {'bold': pango.WEIGHT_BOLD, + 'normal': pango.WEIGHT_NORMAL, + 'book': pango.WEIGHT_BOOK, + 'heavy': pango.WEIGHT_HEAVY, + 'light': pango.WEIGHT_LIGHT, + 'medium': pango.WEIGHT_MEDIUM, + 'semibold': pango.WEIGHT_SEMIBOLD, + 'thin': pango.WEIGHT_THIN, + 'ultrabold': pango.WEIGHT_ULTRABOLD, + 'ultraheavy': pango.WEIGHT_ULTRAHEAVY, + 'ultralight': pango.WEIGHT_ULTRALIGHT} + + font_styles = {'italic': pango.STYLE_ITALIC, + 'oblique': pango.STYLE_OBLIQUE, + 'normal': pango.STYLE_NORMAL} + + wrap_modes = {'word': pango.WRAP_WORD, + 'char': pango.WRAP_CHAR, + 'word_char': pango.WRAP_WORD_CHAR} + + font_map = pc.cairo_font_map_get_default() + font_list = [f.get_name() for f in font_map.list_families()] + def __init__(self): super(WindowSurface, self).__init__() - def draw_circle(self, position, size, color=(1,1,1,1), line_width=1.0, line_color=(0,0,0,1), start_angle=0.0, end_angle=360.0): color = Color.from_value(color) line_color = Color.from_value(line_color) @@ -100,27 +124,48 @@ class WindowSurface(object): context.set_source_rgba(*line_color) context.stroke() - def draw_text(self, text, position, size, color=(0,0,0,1), line_width=1.0, background_color=(1,1,1,0), fill_color=None): - color = Color.from_value(color) + def draw_text(self, text, position, font_size=12, + font_weight='normal', + font_style='normal', font_color=(0,0,0,1), + font_family='Sans', word_wrap='word', + alignment=pango.ALIGN_LEFT, line_width=1.0, + background_color=(1,1,1,0), fill_color=None): + + color = Color.from_value(font_color) background_color = Color.from_value(background_color) position = Position.from_value(position) - size = Size.from_value(size) context = self.context - width = size.width - height = size.height + font_weight = self.font_weights[font_weight] + font_style = self.font_styles[font_style] + + pc_context = pc.CairoContext(context) + pc_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL) + layout = pc_context.create_layout() + font = pango.FontDescription('{} {}'.format(font_family, font_size)) + font.set_weight(font_weight) + font.set_style(font_style) + + layout.set_font_description(font) + layout.set_text(text) + layout.set_wrap(self.wrap_modes[word_wrap]) + width = self.size.width - (self.padding.left + self.padding.right) + layout.set_width(int(width*pango.SCALE)) + layout.set_alignment(alignment) context.set_line_width(line_width) - context.set_source_rgba(*color) - context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) - context.set_font_size(size.height) + context.set_source_rgba(*font_color) + #context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) + #context.set_font_size(size.height) extents = context.text_extents(text) - x,y = (self.position.x+position.x+2, - self.position.y+position.y+extents[3]+2) + x,y = (self.position.x+position.x+self.padding.left, + self.position.y+position.y+self.padding.top) context.move_to(x, y) - context.show_text(text) + pc_context.update_layout(layout) + pc_context.show_layout(layout) + #context.show_text(text) def draw_lines(self, lines, line_color=(0,0,0,1), background_color=(1,1,1,1), line_width=1, line_join='miter', line_cap='butt'): @@ -245,19 +290,21 @@ class Window(WindowEventSource, WindowSurface): position = Position.from_value(kwargs.pop('position', Position())) size = Size.from_value(kwargs.pop('size', Size())) self.context = kwargs.pop('context', None) - self.min_size = kwargs.pop('min_size', Size(1,1)) - self.corner_handle_size = kwargs.pop('corner_handle_size', Size(20, 20)) + self.min_size = Size.from_value(kwargs.pop('min_size', Size(1,1))) + self.max_size = Size.from_value(kwargs.pop('max_size', Size(-1,-1))) + self.corner_handle_size = Size.from_value(kwargs.pop('corner_handle_size', Size(20, 20))) self.edge_handle_width = kwargs.pop('edge_handle_width', 10) - self.edge_handle_buffer = kwargs.pop('edge_handle_buffer', Size(5, 5)) + self.edge_handle_buffer = Size.from_value(kwargs.pop('edge_handle_buffer', Size(5, 5))) self.border_width = kwargs.pop('border_width', 1) self.border_color = Color.from_value(kwargs.pop('border_color', (0,0,0,0))) self.background_color = Color.from_value(kwargs.pop('background_color', (0,0,0,0))) self.border_radius = BorderRadius.from_value(kwargs.pop('border_radius', 1)) + self.padding = Padding.from_value(kwargs.pop('padding', 0)) self.dashed_border = kwargs.pop('dashed_border', False) self.clip_children = kwargs.pop('clip_children', False) self.ignore_debug = kwargs.pop('ignore_debug', False) - self.rectangle = Rectangle(position, size) + self.rectangle = Rectangle() self.children = [] self.parent = None self.mouse_pos = Position(size.width/2, size.height/2) @@ -269,6 +316,8 @@ class Window(WindowEventSource, WindowSurface): self.focused = False self.visible = True self.accept('mouse-move', self.process_mouse_move) + self.size = size + self.position = position self.resizable = kwargs.pop('resizable', self._resizable) self.draggable = kwargs.pop('draggable', self._draggable) @@ -316,6 +365,25 @@ class Window(WindowEventSource, WindowSurface): self.reject('mouse-left', self.click) self.reject('mouse-left-up', self.click_up) + def _restrict_pos_size(self, new_pos, new_size): + if new_size.height <= self.min_size.height: + new_pos.y = self.position.y + self.size.height - self.min_size.height + new_size.height = self.min_size.height + + if new_size.width <= self.min_size.width: + new_pos.x = self.position.x + self.size.width - self.min_size.width + new_size.width = self.min_size.width + + if self.max_size.height > -1 and new_size.height >= self.max_size.height: + new_pos.y = self.position.y + self.size.height - self.max_size.height + new_size.height = self.max_size.height + + if self.max_size.width > -1 and new_size.width >= self.max_size.width: + new_pos.x = self.position.x + self.size.width - self.max_size.width + new_size.width = self.max_size.width + + return new_pos, new_size + def drag_bottomright_handle(self, obj, mouse_pos): diff = mouse_pos - self.mouse_diff old_size = Size.from_value(self.size) @@ -326,13 +394,16 @@ class Window(WindowEventSource, WindowSurface): def drag_bottomleft_handle(self, obj, mouse_pos): diff = mouse_pos - self.mouse_diff old_size = Size.from_value(self.size) - new_pos = Position(self.position.x+diff.x, self.position.y) + new_pos = Position(self.position.x + diff.x, self.position.y) new_size = Size(self.size.width - diff.x, self.size.height + diff.y) - if new_size.width <= self.min_size.width: - new_pos.x = self.position.x + self.size.width - self.min_size.width - new_size.width = self.min_size.width - self.position = new_pos - self.size = new_size + + new_pos, new_size = self._restrict_pos_size(new_pos, new_size) + + if self.draggable: + self.position = new_pos + self.size = new_size + else: + self.size = [self.size.width, new_size.height] self.mouse_diff.y = obj.position.y + self.handle_diff.y self.mouse_diff.x = obj.position.x + self.handle_diff.x @@ -342,12 +413,14 @@ class Window(WindowEventSource, WindowSurface): old_size = Size.from_value(self.size) new_pos = Position(self.position.x, self.position.y + diff.y) new_size = Size(self.size.width + diff.x, self.size.height - diff.y) - if new_size.height <= self.min_size.height: - new_pos.y = self.position.y + self.size.height - self.min_size.height - new_size.height = self.min_size.height - self.position = new_pos - self.size = new_size + new_pos, new_size = self._restrict_pos_size(new_pos, new_size) + + if self.draggable: + self.position = new_pos + self.size = new_size + else: + self.size = [new_size.width, self.size.height] self.mouse_diff.y = obj.position.y + self.handle_diff.y self.mouse_diff.x = obj.position.x + self.handle_diff.x @@ -357,15 +430,12 @@ class Window(WindowEventSource, WindowSurface): old_size = Size.from_value(self.size) new_pos = Position(self.position.x + diff.x, self.position.y + diff.y) new_size = Size(self.size.width - diff.x, self.size.height - diff.y) - if new_size.height <= self.min_size.height: - new_pos.y = self.position.y + self.size.height - self.min_size.height - new_size.height = self.min_size.height - if new_size.width <= self.min_size.width: - new_pos.x = self.position.x + self.size.width - self.min_size.width - new_size.width = self.min_size.width - self.position = new_pos - self.size = new_size + new_pos, new_size = self._restrict_pos_size(new_pos, new_size) + + if self.draggable: + self.position = new_pos + self.size = new_size self.mouse_diff.y = obj.position.y + self.handle_diff.y self.mouse_diff.x = obj.position.x + self.handle_diff.x @@ -375,11 +445,12 @@ class Window(WindowEventSource, WindowSurface): old_size = Size.from_value(self.size) new_pos = Position(self.position.x, self.position.y + diff.y) new_size = Size(self.size.width, self.size.height - diff.y) - if new_size.height <= self.min_size.height: - new_pos.y = self.position.y + self.size.height - self.min_size.height - new_size.height = self.min_size.height - self.position = new_pos - self.size = new_size + + new_pos, new_size = self._restrict_pos_size(new_pos, new_size) + + if self.draggable: + self.position = new_pos + self.size = new_size self.mouse_diff.y = obj.position.y + self.handle_diff.y self.mouse_diff.x = obj.position.x + self.handle_diff.x @@ -389,11 +460,12 @@ class Window(WindowEventSource, WindowSurface): old_size = Size.from_value(self.size) new_pos = Position(self.position.x+diff.x, self.position.y) new_size = Size(self.size.width - diff.x, self.size.height) - if new_size.width <= self.min_size.width: - new_pos.x = self.position.x + self.size.width - self.min_size.width - new_size.width = self.min_size.width - self.position = new_pos - self.size = new_size + + new_pos, new_size = self._restrict_pos_size(new_pos, new_size) + + if self.draggable: + self.position = new_pos + self.size = new_size self.mouse_diff.x = obj.position.x + self.handle_diff.x @@ -705,7 +777,9 @@ class Window(WindowEventSource, WindowSurface): def add_child(self, child_window): if child_window not in self.children: child_window.parent = self - child_window.position = child_window.position + self.position + [self.border_width/2, self.border_width/2] + child_window.position = child_window.position + self.position +\ + [self.border_width/2, self.border_width/2] +\ + [self.padding.left, self.padding.top] child_window.context = self.context self.children.append(child_window) @@ -737,10 +811,17 @@ class Window(WindowEventSource, WindowSurface): @size.setter def size(self, size): size = Size.from_value(size) + if size.height <= self.min_size.height: size.height = self.min_size.height if size.width <= self.min_size.width: size.width = self.min_size.width + + if self.max_size.width > -1 and size.width >= self.max_size.width: + size.width = self.max_size.width + if self.max_size.height > -1 and size.height >= self.max_size.height: + size.height = self.max_size.height + diff = size - self.rectangle.size if diff.height != 0 or diff.width != 0: self.dispatch('resize', self, size) @@ -769,10 +850,20 @@ class Mouse(Window): class TextWindow(Window): + def __init__(self, name, text, *args, **kwargs): super(TextWindow, self).__init__(name, *args, **kwargs) + self.font_size = kwargs.pop('font_size', 12) + self.font_style = kwargs.pop('font_style', 'normal') + self.font_weight = kwargs.pop('font_weight', 'normal') + self.font_family = kwargs.pop('font_family', 'Sans') + self.word_wrap = kwargs.pop('word_wrap', 'word') + self.font_color = Color.from_value(kwargs.pop('font_color', (0,0,0,1))) self.text = text def render(self): super(TextWindow, self).render() - self.draw_text(self.text, [0,0], self.size) + self.draw_text(self.text, [0,0], + self.font_size, font_weight=self.font_weight, + font_style=self.font_style, font_family=self.font_family, + font_color=self.font_color, word_wrap=self.word_wrap) diff --git a/testclasses.py b/testclasses.py index 2ece11f..1504534 100644 --- a/testclasses.py +++ b/testclasses.py @@ -7,6 +7,7 @@ class TestSurface(Surface): my_win = Window('child', position=[0,0], size=[500,200], + max_size=[600, -1], draggable=True, resizable=True, min_size=Size(40,40), @@ -16,11 +17,11 @@ class TestSurface(Surface): background_color=(1,1,1), clip_children=True, ignore_debug=True) - my_win.add_child(TextWindow('text','Micro Bean.', position=[20,20], size=[100, 50])) + my_win.add_child(TextWindow('text','Micro Bean is the best bean ever.', position=[20,20], size=[100, 50], resizable=True, clip_children=True, font_color=(1,0,1), padding=20, draggable=True)) self.root_window.add_child(my_win) self.root_window.add_child(TestWindow('child2', position=[200,200], size=[200,200], draggable=True)) child3 = TestWindow('child3', position=[300,300], size=[200,200], draggable=True) - child3.add_child(TestWindow('child3-1', position=[10,10], size=[50,50], draggable=True)) + child3.add_child(TestWindow('child3-1', position=[10,10], size=[50,50], draggable=True, resizable=True)) self.root_window.add_child(child3)