def size(self, context: UpdateContext): style = merge_styles(context.style, self._inline_style) min_width = style.get("min-width", 0) min_height = style.get("min-height", 0) padding_top, padding_right, padding_bottom, padding_left = style[ "padding"] self.sizes = [c.size(context) for c in self.children] width, height = self.icon.size(context) return ( max(min_width, width + padding_right + padding_left), max(min_height, height + padding_top + padding_bottom), )
def draw(self, context: DrawContext, bounding_box: Rectangle): """Draw the editable text.""" super().draw(context, bounding_box) style = merge_styles(context.style, self._inline_style) text_box = self.text_box(style, bounding_box) text_align = style.get("text-align", TextAlign.CENTER) focus_box = self.focus_box x, y = focus_box_pos(text_box, (focus_box.width, focus_box.height), text_align) focus_box.x = x focus_box.y = y text_draw_focus_box(context, *focus_box)
def draw(self, context: DrawContext, bounding_box: Rectangle): style = merge_styles(context.style, self._inline_style) new_context = replace(context, style=style) padding_top, padding_right, padding_bottom, padding_left = style[ "padding"] x = bounding_box.x + padding_left y = bounding_box.y + padding_top w = bounding_box.width - padding_right - padding_left h = bounding_box.height - padding_top - padding_bottom self.icon.draw(new_context, Rectangle(x, y, w, h)) cx, cy, max_w, total_h = self.child_pos(style, bounding_box) for c, (cw, ch) in zip(self.children, self.sizes): c.draw(context, Rectangle(cx + (max_w - cw) / 2, cy, cw, ch)) cy += ch
def draw(self, context: DrawContext, bounding_box: Rectangle): """Draw the text, return the location and size.""" style = merge_styles(context.style, self._inline_style) min_w = max(style.get("min-width", 0), bounding_box.width) min_h = max(style.get("min-height", 0), bounding_box.height) text_box = self.text_box(style, bounding_box) with cairo_state(context.cairo) as cr: text_color = style.get("text-color") if text_color: cr.set_source_rgba(*text_color) layout = self._layout cr.move_to(text_box.x, text_box.y) layout.set(font=style) layout.show_layout(cr, text_box.width, default_size=(min_w, min_h))
def size(self, context: UpdateContext): style = merge_styles(context.style, self._inline_style) min_w = style.get("min-width", 0) min_h = style.get("min-height", 0) text_align = style.get("text-align", TextAlign.CENTER) padding_top, padding_right, padding_bottom, padding_left = style[ "padding"] layout = self._layout layout.set(text=self.text(), font=style, width=self.width(), text_align=text_align) width, height = layout.size() return ( max(min_w, width + padding_right + padding_left), max(min_h, height + padding_top + padding_bottom), )
def size(self, context: UpdateContext): style: Style = merge_styles(context.style, self._inline_style) min_width = style.get("min-width", 0) min_height = style.get("min-height", 0) padding_top, padding_right, padding_bottom, padding_left = style[ "padding"] self.sizes = sizes = [c.size(context) for c in self.children] if sizes: widths, heights = list(zip(*sizes)) return ( max( min_width, max(widths) + padding_right + padding_left, ), max( min_height, sum(heights) + padding_top + padding_bottom, ), ) else: return min_width, min_height
def draw(self, context): def draw_line_end(end_handle, second_handle, draw): pos, p1 = end_handle.pos, second_handle.pos angle = atan2(p1.y - pos.y, p1.x - pos.x) cr = context.cairo cr.save() try: cr.translate(*pos) cr.rotate(angle) draw(context) finally: cr.restore() style = merge_styles(context.style, self.style) context = replace(context, style=style) cr = context.cairo cr.set_line_width(style["line-width"]) cr.set_dash(style.get("dash-style", ()), 0) stroke = style["color"] if stroke: cr.set_source_rgba(*stroke) handles = self._handles draw_line_end(handles[0], handles[1], self.draw_head) for h in self._handles[1:-1]: cr.line_to(*h.pos) draw_line_end(handles[-1], handles[-2], self.draw_tail) cr.stroke() for shape, rect in ( (self.shape_head, self._shape_head_rect), (self.shape_middle, self._shape_middle_rect), (self.shape_tail, self._shape_tail_rect), ): if shape: shape.draw(context, rect)
def draw(self, context: DrawContext, bounding_box: Rectangle): style: Style = merge_styles(context.style, self._inline_style) new_context = replace(context, style=style) padding_top, padding_right, padding_bottom, padding_left = style[ "padding"] valign = style.get("vertical-align", VerticalAlign.MIDDLE) height = sum(h for _w, h in self.sizes) if self._draw_border: self._draw_border(self, new_context, bounding_box) x = bounding_box.x + padding_left if valign is VerticalAlign.MIDDLE: y = (bounding_box.y + padding_top + (max(height, bounding_box.height - padding_top) - height) / 2) elif valign is VerticalAlign.BOTTOM: y = bounding_box.y + bounding_box.height - height - padding_bottom else: y = bounding_box.y + padding_top w = bounding_box.width - padding_right - padding_left for c, (_w, h) in zip(self.children, self.sizes): c.draw(context, Rectangle(x, y, w, h)) y += h
def post_update(self, context, p1, p2): """Update label placement for association's name and multiplicity label. p1 is the line end and p2 is the last but one point of the line. """ style = merge_styles(context.style, self._inline_style) ofs = 5 dx = float(p2[0]) - float(p1[0]) dy = float(p2[1]) - float(p1[1]) def max_text_size(size1, size2): w1, h1 = size1 w2, h2 = size2 return (max(w1, w2), max(h1, h2)) name_layout = self._name_layout name_layout.set_text(self._name) name_layout.set_font(style) name_w, name_h = max_text_size(name_layout.size(), (10, 10)) mult_layout = self._mult_layout mult_layout.set_text(self._mult) mult_layout.set_font(style) mult_w, mult_h = max_text_size(mult_layout.size(), (10, 10)) if dy == 0: rc = 1000.0 # quite a lot... else: rc = dx / dy abs_rc = abs(rc) h = dx > 0 # right side of the box v = dy > 0 # bottom side if abs_rc > 6: # horizontal line if h: name_dx = ofs name_dy = -ofs - name_h mult_dx = ofs mult_dy = ofs else: name_dx = -ofs - name_w name_dy = -ofs - name_h mult_dx = -ofs - mult_w mult_dy = ofs elif 0 <= abs_rc <= 0.2: # vertical line if v: name_dx = -ofs - name_w name_dy = ofs mult_dx = ofs mult_dy = ofs else: name_dx = -ofs - name_w name_dy = -ofs - name_h mult_dx = ofs mult_dy = -ofs - mult_h else: # Should both items be placed on the same side of the line? r = abs_rc < 1.0 # Find out alignment of text (depends on the direction of the line) align_left = h ^ r align_bottom = v ^ r if align_left: name_dx = ofs mult_dx = ofs else: name_dx = -ofs - name_w mult_dx = -ofs - mult_w if align_bottom: name_dy = -ofs - name_h mult_dy = -ofs - name_h - mult_h else: name_dy = ofs mult_dy = ofs + mult_h self._name_bounds = Rectangle(p1[0] + name_dx, p1[1] + name_dy, width=name_w, height=name_h) self._mult_bounds = Rectangle(p1[0] + mult_dx, p1[1] + mult_dy, width=mult_w, height=mult_h)
def test_merge_opacity(): style = merge_styles({"color": (0, 0, 1, 1), "opacity": 0.7}) assert style["color"] == (0, 0, 1, 0.7)
def test_font_size_override_with_relative_size(): style = merge_styles({"font-size": 10}, {"font-size": 24}, {"font-size": "x-small"}) assert style["font-size"] == 24 * 3 / 4
def test_final_font_size_is_a_number(): style = merge_styles({"font-size": 10}, {"font-size": "x-small"}, {"font-size": 24}) assert style["font-size"] == 24
def test_font_size(font_size, factor): style = merge_styles({"font-size": 10}, {"font-size": font_size}) assert style["font-size"] == 10 * factor
def test_merge_opacity_with_transparency(): style = merge_styles({"color": (0, 0, 0, 0.8), "opacity": 0.5}) assert style["color"] == (0, 0, 0, 0.4)