diff options
author | Sebastian Koslowski <koslowski@kit.edu> | 2017-01-10 17:54:02 +0100 |
---|---|---|
committer | Sebastian Koslowski <koslowski@kit.edu> | 2017-01-11 21:33:25 +0100 |
commit | 502c63d341bd839f597a3d23708c72e2aa888bf1 (patch) | |
tree | fdaa588cde56739a5b63d10df12da6c24267e1d3 /grc | |
parent | c52deb5f43cfd5a7083f3568719d88ac09ac838f (diff) |
grc: gtk3: curved connections
Diffstat (limited to 'grc')
-rw-r--r-- | grc/gui/Constants.py | 4 | ||||
-rw-r--r-- | grc/gui/DrawingArea.py | 7 | ||||
-rw-r--r-- | grc/gui/Utils.py | 5 | ||||
-rw-r--r-- | grc/gui/canvas/block.py | 31 | ||||
-rw-r--r-- | grc/gui/canvas/colors.py (renamed from grc/gui/Colors.py) | 31 | ||||
-rw-r--r-- | grc/gui/canvas/connection.py | 221 | ||||
-rw-r--r-- | grc/gui/canvas/flowgraph.py | 14 | ||||
-rw-r--r-- | grc/gui/canvas/port.py | 36 |
8 files changed, 156 insertions, 193 deletions
diff --git a/grc/gui/Constants.py b/grc/gui/Constants.py index 01ea23ed3e..a3d08cbe38 100644 --- a/grc/gui/Constants.py +++ b/grc/gui/Constants.py @@ -78,8 +78,8 @@ CONNECTOR_EXTENSION_MINIMAL = 11 CONNECTOR_EXTENSION_INCREMENT = 11 # connection arrow dimensions -CONNECTOR_ARROW_BASE = 13 -CONNECTOR_ARROW_HEIGHT = 17 +CONNECTOR_ARROW_BASE = 10 +CONNECTOR_ARROW_HEIGHT = 13 # possible rotations in degrees POSSIBLE_ROTATIONS = (0, 90, 180, 270) diff --git a/grc/gui/DrawingArea.py b/grc/gui/DrawingArea.py index 1a75e2d03b..3d001f8c1e 100644 --- a/grc/gui/DrawingArea.py +++ b/grc/gui/DrawingArea.py @@ -19,9 +19,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import -from gi.repository import Gtk, Gdk, GObject +from gi.repository import Gtk, Gdk -from . import Constants, Colors +from .canvas.colors import FLOWGRAPH_BACKGROUND_COLOR +from . import Constants class DrawingArea(Gtk.DrawingArea): @@ -189,7 +190,7 @@ class DrawingArea(Gtk.DrawingArea): width = widget.get_allocated_width() height = widget.get_allocated_height() - cr.set_source_rgba(*Colors.FLOWGRAPH_BACKGROUND_COLOR) + cr.set_source_rgba(*FLOWGRAPH_BACKGROUND_COLOR) cr.rectangle(0, 0, width, height) cr.fill() diff --git a/grc/gui/Utils.py b/grc/gui/Utils.py index 9c19daf0ba..3fffe6dd20 100644 --- a/grc/gui/Utils.py +++ b/grc/gui/Utils.py @@ -22,7 +22,8 @@ from __future__ import absolute_import from gi.repository import GLib import cairo -from . import Colors, Constants +from .canvas.colors import FLOWGRAPH_BACKGROUND_COLOR +from . import Constants def get_rotated_coordinate(coor, rotation): @@ -131,7 +132,7 @@ def make_screenshot(flow_graph, file_path, transparent_bg=False): cr = cairo.Context(psurf) if not transparent_bg: - cr.set_source_rgba(*Colors.FLOWGRAPH_BACKGROUND_COLOR) + cr.set_source_rgba(*FLOWGRAPH_BACKGROUND_COLOR) cr.rectangle(0, 0, width, height) cr.fill() diff --git a/grc/gui/canvas/block.py b/grc/gui/canvas/block.py index 3c1d7daa5a..d336bc139a 100644 --- a/grc/gui/canvas/block.py +++ b/grc/gui/canvas/block.py @@ -24,14 +24,13 @@ import math import six from gi.repository import Gtk, Pango, PangoCairo +from . import colors from .drawable import Drawable - -from .. import Actions, Colors, Utils, Constants +from .. import Actions, Utils, Constants from ..Constants import ( BLOCK_LABEL_PADDING, PORT_SPACING, PORT_SEPARATION, LABEL_SEPARATION, PORT_BORDER_SEPARATION, BLOCK_FONT, PARAM_FONT ) - from ...core import utils from ...core.Block import Block as CoreBlock @@ -58,8 +57,8 @@ class Block(CoreBlock, Drawable): self._comment_layout = None self._area = [] - self._border_color = self._bg_color = Colors.BLOCK_ENABLED_COLOR - self._font_color = list(Colors.FONT_COLOR) + self._border_color = self._bg_color = colors.BLOCK_ENABLED_COLOR + self._font_color = list(colors.FONT_COLOR) @property def coordinate(self): @@ -110,15 +109,15 @@ class Block(CoreBlock, Drawable): def _update_colors(self): self._bg_color = ( - Colors.MISSING_BLOCK_BACKGROUND_COLOR if self.is_dummy_block else - Colors.BLOCK_BYPASSED_COLOR if self.state == 'bypassed' else - Colors.BLOCK_ENABLED_COLOR if self.state == 'enabled' else - Colors.BLOCK_DISABLED_COLOR + colors.MISSING_BLOCK_BACKGROUND_COLOR if self.is_dummy_block else + colors.BLOCK_BYPASSED_COLOR if self.state == 'bypassed' else + colors.BLOCK_ENABLED_COLOR if self.state == 'enabled' else + colors.BLOCK_DISABLED_COLOR ) self._font_color[-1] = 1.0 if self.state == 'enabled' else 0.4 self._border_color = ( - Colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else - Colors.BORDER_COLOR_DISABLED if not self.state == 'enabled' else Colors.BORDER_COLOR + colors.MISSING_BLOCK_BORDER_COLOR if self.is_dummy_block else + colors.BORDER_COLOR_DISABLED if not self.state == 'enabled' else colors.BORDER_COLOR ) def create_shapes(self): @@ -135,7 +134,7 @@ class Block(CoreBlock, Drawable): continue port_separation = PORT_SEPARATION if not has_busses else ports[0].height + PORT_SPACING offset = (self.height - (len(ports) - 1) * port_separation - ports[0].height) / 2 - for index, port in enumerate(ports): + for port in ports: port.create_shapes() port.coordinate = { @@ -143,11 +142,9 @@ class Block(CoreBlock, Drawable): 90: (offset, -port.width), 180: (-port.width, offset), 270: (offset, +self.width), - }[port.get_connector_direction()] - offset += PORT_SEPARATION if not has_busses else port.height + PORT_SPACING + }[port.connector_direction] - port.connector_length = Constants.CONNECTOR_EXTENSION_MINIMAL + \ - Constants.CONNECTOR_EXTENSION_INCREMENT * index + offset += PORT_SEPARATION if not has_busses else port.height + PORT_SPACING def create_labels(self, cr=None): """Create the labels for the signal block.""" @@ -263,7 +260,7 @@ class Block(CoreBlock, Drawable): """ Draw the signal block with label and inputs/outputs. """ - border_color = Colors.HIGHLIGHT_COLOR if self.highlighted else self._border_color + border_color = colors.HIGHLIGHT_COLOR if self.highlighted else self._border_color cr.translate(*self.coordinate) for port in self.active_ports(): # ports first diff --git a/grc/gui/Colors.py b/grc/gui/canvas/colors.py index 17eb151847..77d3203e78 100644 --- a/grc/gui/Colors.py +++ b/grc/gui/canvas/colors.py @@ -22,7 +22,7 @@ from __future__ import absolute_import from gi.repository import Gtk, Gdk, cairo # import pycairo -from . import Constants +from .. import Constants def get_color(color_code): @@ -75,31 +75,4 @@ PORT_TYPE_TO_COLOR.update((key, get_color(color)) for key, (_, color) in Constan ################################################################################# # param box colors ################################################################################# -style_provider = Gtk.CssProvider() - -style_provider.load_from_data(""" - #dtype_complex { background-color: #3399FF; } - #dtype_real { background-color: #FF8C69; } - #dtype_float { background-color: #FF8C69; } - #dtype_int { background-color: #00FF99; } - - #dtype_complex_vector { background-color: #3399AA; } - #dtype_real_vector { background-color: #CC8C69; } - #dtype_float_vector { background-color: #CC8C69; } - #dtype_int_vector { background-color: #00CC99; } - - #dtype_bool { background-color: #00FF99; } - #dtype_hex { background-color: #00FF99; } - #dtype_string { background-color: #CC66CC; } - #dtype_id { background-color: #DDDDDD; } - #dtype_stream_id { background-color: #DDDDDD; } - #dtype_raw { background-color: #FFFFFF; } - - #enum_custom { background-color: #EEEEEE; } -""") - -Gtk.StyleContext.add_provider_for_screen( - Gdk.Screen.get_default(), - style_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION -) + diff --git a/grc/gui/canvas/connection.py b/grc/gui/canvas/connection.py index 14bd0c9280..1da63be389 100644 --- a/grc/gui/canvas/connection.py +++ b/grc/gui/canvas/connection.py @@ -17,13 +17,19 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA """ -from __future__ import absolute_import +from __future__ import absolute_import, division -from .drawable import Drawable - -from .. import Colors, Utils -from ..Constants import CONNECTOR_ARROW_BASE, CONNECTOR_ARROW_HEIGHT, GR_MESSAGE_DOMAIN +from math import pi +from . import colors +from .drawable import Drawable +from .. import Utils +from ..Constants import ( + CONNECTOR_ARROW_BASE, + CONNECTOR_ARROW_HEIGHT, + GR_MESSAGE_DOMAIN, + LINE_SELECT_SENSITIVITY, +) from ...core.Connection import Connection as CoreConnection from ...core.Element import nop_write @@ -46,13 +52,17 @@ class Connection(CoreConnection, Drawable): self._line_width_factor = 1.0 self._color = self._color2 = self._arrow_color = None - self._sink_rot = self._source_rot = None - self._sink_coor = self._source_coor = None + self._current_port_rotations = self._current_coordinates = None + + self._rel_points = None # connection coordinates relative to sink/source + self._arrow_rotation = 0.0 # rotation of the arrow in radians + self._current_cr = None # for what_is_selected() of curved line + self._line_path = None @nop_write @property def coordinate(self): - return self.source_port.get_connector_coordinate() + return self.source_port.connector_coordinate_absolute @nop_write @property @@ -68,123 +78,96 @@ class Connection(CoreConnection, Drawable): def create_shapes(self): """Pre-calculate relative coordinates.""" - self._sink_rot = None - self._source_rot = None - self._sink_coor = None - self._source_coor = None - #get the source coordinate - try: - connector_length = self.source_port.connector_length - except: - return # todo: why? - self.x1, self.y1 = Utils.get_rotated_coordinate((connector_length, 0), self.source_port.rotation) - #get the sink coordinate - connector_length = self.sink_port.connector_length + CONNECTOR_ARROW_HEIGHT - self.x2, self.y2 = Utils.get_rotated_coordinate((-connector_length, 0), self.sink_port.rotation) - #build the arrow - self._arrow_base = [ - (0, 0), - Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2), self.sink_port.rotation), - Utils.get_rotated_coordinate((-CONNECTOR_ARROW_HEIGHT, CONNECTOR_ARROW_BASE/2), self.sink_port.rotation), - ] if self.sink_block.state != 'bypassed' else [] - source_domain = self.source_port.domain - sink_domain = self.sink_port.domain + source = self.source_port + sink = self.sink_port + rotate = Utils.get_rotated_coordinate + + # first two components relative to source connector, rest relative to sink connector + self._rel_points = [ + rotate((15, 0), source.rotation), # line from 0,0 to here, bezier curve start + rotate((50, 0), source.rotation), # bezier curve control point 1 + rotate((-50, 0), sink.rotation), # bezier curve control point 2 + rotate((-15, 0), sink.rotation), # bezier curve end + rotate((-CONNECTOR_ARROW_HEIGHT, 0), sink.rotation), # line to arrow head + ] + self._current_coordinates = None # triggers _make_path() def get_domain_color(domain_name): domain = self.parent_platform.domains.get(domain_name, {}) color_spec = domain.get('color') - return Colors.get_color(color_spec) if color_spec else Colors.DEFAULT_DOMAIN_COLOR + return colors.get_color(color_spec) if color_spec else colors.DEFAULT_DOMAIN_COLOR - if source_domain == GR_MESSAGE_DOMAIN: + if source.domain == GR_MESSAGE_DOMAIN: self._line_width_factor = 1.0 self._color = None - self._color2 = Colors.CONNECTION_ENABLED_COLOR + self._color2 = colors.CONNECTION_ENABLED_COLOR else: - if source_domain != sink_domain: + if source.domain != sink.domain: self._line_width_factor = 2.0 - self._color = get_domain_color(source_domain) - self._color2 = get_domain_color(sink_domain) - self._arrow_color = self._color2 if self.is_valid() else Colors.CONNECTION_ERROR_COLOR - self._update_after_move() - - def _update_after_move(self): - """Calculate coordinates.""" - source = self.source_port - sink = self.sink_port - source_dir = source.get_connector_direction() - sink_dir = sink.get_connector_direction() - - x_pos, y_pos = self.coordinate - x_start, y_start = source.get_connector_coordinate() - x_end, y_end = sink.get_connector_coordinate() - - p3 = x3, y3 = x_end - x_pos, y_end - y_pos - p2 = x2, y2 = self.x2 + x3, self.y2 + y3 - p1 = x1, y1 = self.x1, self.y1 - p0 = x_start - x_pos, y_start - y_pos - self._arrow = [(x + x3, y + y3) for x, y in self._arrow_base] - - if abs(source_dir - sink.get_connector_direction()) == 180: - # 2 possible point sets to create a 3-line connector - mid_x, mid_y = (x1 + x2) / 2.0, (y1 + y2) / 2.0 - points = ((mid_x, y1), (mid_x, y2)) - alt = ((x1, mid_y), (x2, mid_y)) - # source connector -> points[0][0] should be in the direction of source (if possible) - if Utils.get_angle_from_coordinates(p1, points[0]) != source_dir: - points, alt = alt, points - # points[0] -> sink connector should not be in the direction of sink - if Utils.get_angle_from_coordinates(points[0], p2) == sink_dir: - points, alt = alt, points - # points[0] -> source connector should not be in the direction of source - if Utils.get_angle_from_coordinates(points[0], p1) == source_dir: - points, alt = alt, points - # create 3-line connector - i1, i2 = points - self._line = [p0, p1, i1, i2, p2, p3] - else: - # 2 possible points to create a right-angled connector - point, alt = [(x1, y2), (x2, y1)] - # source connector -> point should be in the direction of source (if possible) - if Utils.get_angle_from_coordinates(p1, point) != source_dir: - point, alt = alt, point - # point -> sink connector should not be in the direction of sink - if Utils.get_angle_from_coordinates(point, p2) == sink_dir: - point, alt = alt, point - # point -> source connector should not be in the direction of source - if Utils.get_angle_from_coordinates(point, p1) == source_dir: - point, alt = alt, point - # create right-angled connector - self._line = [p0, p1, point, p2, p3] - self.bounds_from_line(self._line) + self._color = get_domain_color(source.domain) + self._color2 = get_domain_color(sink.domain) + + self._arrow_color = self._color2 if self.is_valid() else colors.CONNECTION_ERROR_COLOR + self._arrow_rotation = -sink.rotation / 180 * pi + + if not self._bounding_points: + self._make_path() # no cr set --> only sets bounding_points for extent + + def _make_path(self, cr=None): + x_pos, y_pos = self.coordinate # is source connector coordinate + # x_start, y_start = self.source_port.get_connector_coordinate() + x_end, y_end = self.sink_port.connector_coordinate_absolute + + # sink connector relative to sink connector + x_e, y_e = x_end - x_pos, y_end - y_pos + + # make rel_point all relative to source connector + p0 = 0, 0 # x_start - x_pos, y_start - y_pos + p1, p2, (dx_e1, dy_e1), (dx_e2, dy_e2), (dx_e3, dy_e3) = self._rel_points + p3 = x_e + dx_e1, y_e + dy_e1 + p4 = x_e + dx_e2, y_e + dy_e2 + p5 = x_e + dx_e3, y_e + dy_e3 + self._bounding_points = p0, p1, p4, p5 # ignores curved part =( + + if cr: + cr.move_to(*p0) + cr.line_to(*p1) + cr.curve_to(*(p2 + p3 + p4)) + cr.line_to(*p5) + self._line_path = cr.copy_path() def draw(self, cr): """ Draw the connection. """ + self._current_cr = cr sink = self.sink_port source = self.source_port + # check for changes - if self._sink_rot != sink.rotation or self._source_rot != source.rotation: - self.create_shapes() - self._sink_rot = sink.rotation - self._source_rot = source.rotation - - elif self._sink_coor != sink.parent_block.coordinate or self._source_coor != source.parent_block.coordinate: - self._update_after_move() - self._sink_coor = sink.parent_block.coordinate - self._source_coor = source.parent_block.coordinate - # draw + port_rotations = (source.rotation, sink.rotation) + if self._current_port_rotations != port_rotations: + self.create_shapes() # triggers _make_path() call below + self._current_port_rotations = port_rotations + + new_coordinates = (source.parent_block.coordinate, sink.parent_block.coordinate) + if self._current_coordinates != new_coordinates: + self._make_path(cr) + self._current_coordinates = new_coordinates + color1, color2, arrow_color = ( None if color is None else - Colors.HIGHLIGHT_COLOR if self.highlighted else - Colors.CONNECTION_DISABLED_COLOR if not self.enabled else color + colors.HIGHLIGHT_COLOR if self.highlighted else + colors.CONNECTION_DISABLED_COLOR if not self.enabled else color for color in (self._color, self._color2, self._arrow_color) ) cr.translate(*self.coordinate) cr.set_line_width(self._line_width_factor * cr.get_line_width()) - for point in self._line: - cr.line_to(*point) + cr.new_path() + cr.append_path(self._line_path) + + arrow_pos = cr.get_current_point() if color1: # not a message connection cr.set_source_rgba(*color1) @@ -199,10 +182,32 @@ class Connection(CoreConnection, Drawable): else: cr.new_path() - if self._arrow: - cr.set_source_rgba(*arrow_color) - cr.move_to(*self._arrow[0]) - cr.line_to(*self._arrow[1]) - cr.line_to(*self._arrow[2]) - cr.close_path() - cr.fill() + cr.move_to(*arrow_pos) + cr.set_source_rgba(*arrow_color) + cr.rotate(self._arrow_rotation) + cr.rel_move_to(CONNECTOR_ARROW_HEIGHT, 0) + cr.rel_line_to(-CONNECTOR_ARROW_HEIGHT, -CONNECTOR_ARROW_BASE/2) + cr.rel_line_to(0, CONNECTOR_ARROW_BASE) + cr.close_path() + cr.fill() + + def what_is_selected(self, coor, coor_m=None): + """ + Returns: + self if one of the areas/lines encompasses coor, else None. + """ + if coor_m: + return Drawable.what_is_selected(self, coor, coor_m) + + x, y = [a - b for a, b in zip(coor, self.coordinate)] + + cr = self._current_cr + cr.save() + cr.new_path() + cr.append_path(self._line_path) + cr.set_line_width(cr.get_line_width() * LINE_SELECT_SENSITIVITY) + hit = cr.in_stroke(x, y) + cr.restore() + + if hit: + return self diff --git a/grc/gui/canvas/flowgraph.py b/grc/gui/canvas/flowgraph.py index ac15510072..9cf5fe44c6 100644 --- a/grc/gui/canvas/flowgraph.py +++ b/grc/gui/canvas/flowgraph.py @@ -26,15 +26,13 @@ from distutils.spawn import find_executable from itertools import count import six -from six.moves import filter - from gi.repository import GLib +from six.moves import filter +from . import colors from .drawable import Drawable - -from .. import Actions, Colors, Constants, Utils, Bars, Dialogs +from .. import Actions, Constants, Utils, Bars, Dialogs from ..external_editor import ExternalEditor - from ...core import Messages from ...core.FlowGraph import FlowGraph as CoreFlowgraph @@ -495,9 +493,9 @@ class FlowGraph(CoreFlowgraph, Drawable): x, y = int(min(x1, x2)), int(min(y1, y2)) w, h = int(abs(x1 - x2)), int(abs(y1 - y2)) cr.set_source_rgba( - Colors.HIGHLIGHT_COLOR[0], - Colors.HIGHLIGHT_COLOR[1], - Colors.HIGHLIGHT_COLOR[2], + colors.HIGHLIGHT_COLOR[0], + colors.HIGHLIGHT_COLOR[1], + colors.HIGHLIGHT_COLOR[2], 0.5, ) cr.rectangle(x, y, w, h) diff --git a/grc/gui/canvas/port.py b/grc/gui/canvas/port.py index 2ea55aaa22..b74e4adfcc 100644 --- a/grc/gui/canvas/port.py +++ b/grc/gui/canvas/port.py @@ -23,10 +23,9 @@ import math from gi.repository import Gtk, PangoCairo, Pango +from . import colors from .drawable import Drawable - -from .. import Actions, Colors, Utils, Constants - +from .. import Actions, Utils, Constants from ...core.Element import nop_write from ...core.Port import Port as CorePort @@ -47,13 +46,12 @@ class Port(CorePort, Drawable): self._area = [] self._bg_color = self._border_color = 0, 0, 0, 0 - self._font_color = list(Colors.FONT_COLOR) + self._font_color = list(colors.FONT_COLOR) self._line_width_factor = 1.0 self._label_layout_offsets = 0, 0 self.width_with_label = self.height = 0 - self.connector_length = 0 self.label_layout = None @@ -76,10 +74,10 @@ class Port(CorePort, Drawable): """ if not self.parent_block.enabled: self._font_color[-1] = 0.4 - color = Colors.BLOCK_DISABLED_COLOR + color = colors.BLOCK_DISABLED_COLOR else: self._font_color[-1] = 1.0 - color = Colors.PORT_TYPE_TO_COLOR.get(self.get_type()) or Colors.PORT_TYPE_TO_COLOR.get('') + color = colors.PORT_TYPE_TO_COLOR.get(self.get_type()) or colors.PORT_TYPE_TO_COLOR.get('') vlen = self.get_vlen() if vlen > 1: dark = (0, 0, 30 / 255.0, 50 / 255.0, 70 / 255.0)[min(4, vlen)] @@ -100,7 +98,7 @@ class Port(CorePort, Drawable): 90: (self.height / 2, 0), 180: (0, self.height / 2), 270: (self.height / 2, self.width) - }[self.get_connector_direction()] + }[self.connector_direction] def create_labels(self, cr=None): """Create the labels for the socket.""" @@ -157,28 +155,18 @@ class Port(CorePort, Drawable): PangoCairo.update_layout(cr, self.label_layout) PangoCairo.show_layout(cr, self.label_layout) - def get_connector_coordinate(self): - """ - Get the coordinate where connections may attach to. - - Returns: - the connector coordinate (x, y) tuple - """ + @property + def connector_coordinate_absolute(self): + """the coordinate where connections may attach to""" return [sum(c) for c in zip( self._connector_coordinate, # relative to port self.coordinate, # relative to block self.parent_block.coordinate # abs )] - def get_connector_direction(self): - """ - Get the direction that the socket points: 0,90,180,270. - This is the rotation degree if the socket is an output or - the rotation degree + 180 if the socket is an input. - - Returns: - the direction in degrees - """ + @property + def connector_direction(self): + """Get the direction that the socket points: 0,90,180,270.""" if self.is_source: return self.rotation elif self.is_sink: |