def boundingRect(self): """ :return: """ if not self.end_point: self.update_end_points() assert self.end_point # Bounding rect that includes the tail and end spot ellipse ex, ey = 0, 0 sx, sy = sub_xy(self.start_point, self.end_point) e2 = end_spot_size * 2 if sx < ex: w = max((ex - sx + end_spot_size, e2)) x = min((sx, ex - end_spot_size)) else: w = max((sx - ex + end_spot_size, e2)) x = ex - end_spot_size if sy < ey: h = max((ey - sy + end_spot_size, e2)) y = min((sy, ey - end_spot_size)) else: h = max((sy - ey + end_spot_size, e2)) y = ey - end_spot_size r = QtCore.QRectF(x, y, w, h) return r
def update_end_points(self, end_point=None): """ :param end_point: End point can be given or it can be calculated. """ e = self.host shape_name = ctrl.settings.get_edge_setting('shape_name', edge=e) self._fill_path = e.is_filled() sx, sy = to_tuple(e.get_point_at(0.5)) self.start_point = sx, sy if end_point: self.end_point = end_point else: d = e.get_angle_at(0.5) d += 90 # 75 angle = math.radians(-d) dx = math.cos(angle) dy = math.sin(angle) l = 12 x = sx + dx * l y = sy + dy * l self.end_point = x, y self.setPos(self.end_point[0], self.end_point[1]) rel_sp = sub_xy(self.start_point, self.end_point) adjust = [] self._path = SHAPE_PRESETS[shape_name].path(rel_sp, (0, 0), alignment=g.RIGHT, curve_adjustment=adjust)[0]
def boundingRect(self): """ :return: """ if not self.end_point: self.update_end_points() assert self.end_point # Bounding rect that includes the tail and end spot ellipse ex, ey = 0, 0 sx, sy = sub_xy(self.start_point, self.end_point) ss2 = BranchingTouchArea.spot_size * 2 ss = BranchingTouchArea.spot_size if sx < ex: w = max((ex - sx + ss, ss2)) x = min((sx, ex - ss)) else: w = max((sx - ex + ss, ss2)) x = ex - ss if sy < ey: h = max((ey - sy + ss, ss2)) y = min((sy, ey - ss)) else: h = max((sy - ey + ss, ss2)) y = ey - ss r = QtCore.QRectF(x, y, w, h) return r
def shape(self): """ Shape is used for collisions and it shouldn't go over the originating node. So use only the last half, starting from the "knee" of the shape. :return: """ path = QtGui.QPainterPath() # Bounding rect that includes the tail and end spot ellipse rel_sp = sub_xy(self.start_point, self.end_point) sx, sy = rel_sp sx /= 2.0 ex, ey = 0, 0 e2 = end_spot_size * 2 if sx < ex: w = max((ex - sx + end_spot_size, e2)) x = min((sx, ex - end_spot_size)) else: w = max((sx - ex + end_spot_size, e2)) x = ex - end_spot_size if sy < ey: h = max((ey - sy + end_spot_size, e2)) y = min((sy, ey - end_spot_size)) else: h = max((sy - ey + end_spot_size, e2)) y = ey - end_spot_size r = QtCore.QRectF(x, y, w, h) path.addRect(r) return path
def timerEvent(self, event): """ Main loop for animations and movement in the scene -- calls nodes and tells them to update their position :param event: timer event? sent by Qt """ # Uncomment to check what is the actual framerate: # n_time = time.time() # print((n_time - self.prev_time) * 1000, prefs._fps_in_msec) # self.prev_time = n_time items_have_moved = False frame_has_moved = False background_fade = False can_normalize = True md = {'sum': (0, 0), 'nodes': []} ctrl.items_moving = True if self._fade_steps: self.setBackgroundBrush(self._fade_steps_list[self._fade_steps - 1]) self._fade_steps -= 1 if self._fade_steps: background_fade = True f = self.main.forest f.edge_visibility_check() for e in f.edges.values(): e.make_path() e.update() if ctrl.pressed: return for node in f.nodes.values(): if not node.isVisible(): continue # Computed movement moved, normalizable = node.move(md) if moved: items_have_moved = True if not normalizable: can_normalize = False # normalize movement so that the trees won't glide away ln = len(md['nodes']) if ln and can_normalize: avg = div_xy(md['sum'], ln) for node in md['nodes']: node.current_position = sub_xy(node.current_position, avg) if items_have_moved and (not self.manual_zoom) and (not ctrl.dragged_focus): self.fit_to_window() if items_have_moved: self.main.ui_manager.get_activity_marker().show() # for area in f.touch_areas: # area.update_position() for group in f.groups.values(): group.update_shape() elif not (items_have_moved or frame_has_moved or background_fade): self.stop_animations() self.main.ui_manager.get_activity_marker().hide() ctrl.items_moving = False self.keep_updating_visible_area = False f.edge_visibility_check() # only does something if flagged
def start_moving(self): """ Initiate moving animation for object. :return: None """ self._use_easing = True self._move_counter = prefs.move_frames self._step = sub_xy(self.target_position, self.current_position) # self.adjustment affects both elements in the previous subtraction, so it can be ignored ctrl.graph_scene.item_moved()
def move(self, md): """ Do one frame of movement: either move towards target position or take a step according to algorithm 1. item folding towards position in part of animation to disappear etc. 2. item is being dragged 3. item is locked by user 4. item is tightly attached to another node which is moving (then the move is handled by the other node, it is _not_ parent node, though.) 5. visualisation algorithm setting it specifically (6) or (0) -- places where subclasses can add new movements. :param md: movement data dict, collects sum of all movement to help normalize it :return: """ # _high_priority_move can be used together with _move_counter self.unmoved = False if not self._high_priority_move: # Dragging overrides (almost) everything, don't try to move this anywhere if self._dragged: return True, False # Locked nodes are immune to physics elif self.locked: return False, False #elif self.locked_to_node: # return False, False # MOVE_TO -based movement has priority over physics. This way e.g. triangles work without # additional stipulation if self._move_counter: position = self.current_position # stop even despite the _move_counter, if we are close enough if about_there(position, self.target_position): self.stop_moving() return False, False # move a precalculated step if self._use_easing: movement = multiply_xy(self._step, qt_prefs.easing_curve[self._move_counter - 1]) else: movement = div_xy(sub_xy(self.target_position, position), self._move_counter) self._move_counter -= 1 # if move counter reaches zero, stop and do clean-up. if not self._move_counter: self.stop_moving() self.current_position = add_xy(self.current_position, movement) if self.locked_to_node: self.locked_to_node.update_bounding_rect() return True, False # Physics move node around only if other movement types have not overridden it elif self.use_physics() and self.is_visible(): movement = ctrl.forest.visualization.calculate_movement(self) md['sum'] = add_xy(movement, md['sum']) md['nodes'].append(self) self.current_position = add_xy(self.current_position, movement) return abs(movement[0]) + abs(movement[1]) > 0.6, True return False, False
def update_end_points(self, end_point=None): """ :param end_point: End point can be given or it can be calculated. """ shape_name = ctrl.settings.get_edge_setting( 'shape_name', edge_type=g.CONSTITUENT_EDGE) self._fill_path = ctrl.settings.get_shape_setting( 'fill', edge_type=g.CONSTITUENT_EDGE) sx, sy = self.host.magnet(2) self.start_point = sx, sy hw_ratio = float(prefs.edge_height - (ConstituentNode.height / 2)) / (prefs.edge_width or 1) if not end_point: good_width = max((prefs.edge_width * 2, self.host.width / 2 + ConstituentNode.width)) if self._align_left: self.end_point = sx - good_width, sy else: self.end_point = sx + good_width, sy self.setPos(self.end_point[0], self.end_point[1]) rel_sp = sub_xy(self.start_point, self.end_point) sx, sy = rel_sp ex, ey = 0, 0 line_middle_point = sx / 2.0, sy - hw_ratio * abs(sx) adjust = [] if self._align_left: self._path = SHAPE_PRESETS[shape_name].path( line_middle_point, (sx, sy), alignment=g.RIGHT, curve_adjustment=adjust)[0] self._path.moveTo(sx, sy) path2 = SHAPE_PRESETS[shape_name].path(line_middle_point, (ex, ey), alignment=g.LEFT, curve_adjustment=adjust)[0] else: self._path = SHAPE_PRESETS[shape_name].path( line_middle_point, (ex, ey), alignment=g.RIGHT, curve_adjustment=adjust)[0] self._path.moveTo(ex, ey) path2 = SHAPE_PRESETS[shape_name].path(line_middle_point, (sx, sy), alignment=g.LEFT, curve_adjustment=adjust)[0] self._path |= path2
def dragged_to(self, scene_pos): """ Dragged focus is in scene_pos. Move there. :param scene_pos: current drag focus :return: """ if self.parentItem(): p = self.parentItem().mapFromScene(scene_pos[0], scene_pos[1]) new_pos = p.x(), p.y() else: new_pos = scene_pos[0], scene_pos[1] if self.use_physics(): self.locked = True self.current_position = new_pos else: self.use_adjustment = True diff = sub_xy(new_pos, self.current_position) self.adjustment = add_xy(self.adjustment, diff) self.target_position = new_pos self.current_position = new_pos
def update_end_points(self, end_point=None): """ :param end_point: End point can be given or it can be calculated. """ shape_name = ctrl.settings.get_edge_setting('shape_name', edge_type=g.CONSTITUENT_EDGE) self._fill_path = ctrl.settings.get_shape_setting('fill', edge_type=g.CONSTITUENT_EDGE) sx, sy = self.host.magnet(0) self.start_point = sx, sy if end_point: self.end_point = end_point else: ex = sx - 20 # 75 ey = sy - 10 self.end_point = ex, ey self.setPos(self.end_point[0], self.end_point[1]) rel_sp = sub_xy(self.start_point, self.end_point) adjust = [] self._path = SHAPE_PRESETS[shape_name].path(rel_sp, (0, 0), alignment=g.LEFT, curve_adjustment=adjust)[0]
def paint(self, painter, option, widget): """ :param painter: :param option: :param widget: :raise: """ if ctrl.pressed is self: pass c = self.contextual_color() painter.setPen(c) painter.drawLine(*sub_xy(self.start_point, self.end_point), 0, 0) if self._hovering: painter.save() painter.setBrush(ctrl.cm.ui()) painter.rotate(-160) draw_leaf(painter, 0, BranchingTouchArea.spot_size / 2, BranchingTouchArea.spot_size) painter.restore() draw_plus(painter, 14, 0)
def update_end_points(self, end_point=None): """ :param end_point: End point can be given or it can be calculated. """ shape_name = ctrl.settings.get_edge_setting('shape_name', edge_type=g.CONSTITUENT_EDGE) self._fill_path = ctrl.settings.get_shape_setting('fill', edge_type=g.CONSTITUENT_EDGE) sx, sy = self.host.magnet(2) self.start_point = sx, sy hw_ratio = float(prefs.edge_height - (ConstituentNode.height / 2)) / (prefs.edge_width or 1) if not end_point: good_width = max((prefs.edge_width * 2, self.host.width / 2 + ConstituentNode.width)) if self._align_left: self.end_point = sx - good_width, sy else: self.end_point = sx + good_width, sy self.setPos(self.end_point[0], self.end_point[1]) rel_sp = sub_xy(self.start_point, self.end_point) sx, sy = rel_sp ex, ey = 0, 0 line_middle_point = sx / 2.0, sy - hw_ratio * abs(sx) adjust = [] if self._align_left: self._path = SHAPE_PRESETS[shape_name].path(line_middle_point, (sx, sy), alignment=g.RIGHT, curve_adjustment=adjust)[0] self._path.moveTo(sx, sy) path2 = SHAPE_PRESETS[shape_name].path(line_middle_point, (ex, ey), alignment=g.LEFT, curve_adjustment=adjust)[0] else: self._path = SHAPE_PRESETS[shape_name].path(line_middle_point, (ex, ey), alignment=g.RIGHT, curve_adjustment=adjust)[0] self._path.moveTo(ex, ey) path2 = SHAPE_PRESETS[shape_name].path(line_middle_point, (sx, sy), alignment=g.LEFT, curve_adjustment=adjust)[0] self._path |= path2
def update_end_points(self, end_point=None): """ :param end_point: End point can be given or it can be calculated. """ shape_name = ctrl.settings.get_edge_setting( 'shape_name', edge_type=g.CONSTITUENT_EDGE) self._fill_path = ctrl.settings.get_shape_setting( 'fill', edge_type=g.CONSTITUENT_EDGE) sx, sy = self.host.magnet(7) self.start_point = sx, sy if end_point: self.end_point = end_point else: ex = sx - 20 # 75 ey = sy + 10 self.end_point = ex, ey self.setPos(self.end_point[0], self.end_point[1]) rel_sp = sub_xy(self.start_point, self.end_point) adjust = [] self._path = SHAPE_PRESETS[shape_name].path(rel_sp, (0, 0), alignment=g.LEFT, curve_adjustment=adjust)[0]
def start_moving(self): """ Initiate moving animation for object. :return: None """ self._use_easing = True dx, dy = sub_xy(self.target_position, self.current_position) d = math.sqrt(dx * dx + dy * dy) self._distance = dx, dy # this scales nicely: # d = 0 -> p = 0 # d = 50 -> p = 0.5849 # d = 100 -> p = 1 # d = 200 -> p = 1.5849 # d = 500 -> p = 2.5849 # d = 1000 -> p = 3.4594 p = math.log2(d * 0.01 + 1) self._move_frames = int(p * prefs.move_frames) if self._move_frames == 0: self._move_frames = 1 #self._move_frames = prefs.move_frames self._move_counter = self._move_frames self._start_position = self.current_position # self.adjustment affects both elements in the previous subtraction, so it can be ignored ctrl.graph_scene.item_moved()
def distance_to(self, movable): """ Return current x,y distance to another movable :param movable: :return: x, y """ return sub_xy(self.current_position, movable.current_position)
def timerEvent(self, event): """ Main loop for animations and movement in the scene -- calls nodes and tells them to update their position :param event: timer event? sent by Qt """ # Uncomment to check what is the actual framerate: # n_time = time.time() # print((n_time - self.prev_time) * 1000, prefs._fps_in_msec) # self.prev_time = n_time items_have_moved = False frame_has_moved = False background_fade = False can_normalize = True md = {'sum': (0, 0), 'nodes': []} ctrl.items_moving = True if self._fade_steps: self.setBackgroundBrush(self._fade_steps_list[self._fade_steps - 1]) self._fade_steps -= 1 if self._fade_steps: background_fade = True f = self.main.forest #e.update() if ctrl.pressed: return for node in chain(f.nodes.values(), f.trees): if not node.isVisible(): continue # Computed movement moved, normalizable = node.move(md) if moved: items_have_moved = True if not normalizable: can_normalize = False # normalize movement so that the trees won't glide away ln = len(md['nodes']) if ln and can_normalize: avg = div_xy(md['sum'], ln) for node in md['nodes']: node.current_position = sub_xy(node.current_position, avg) if items_have_moved: for e in f.edges.values(): e.make_path() if items_have_moved and (not self.manual_zoom) and ( not ctrl.dragged_focus): self.fit_to_window() if items_have_moved: #self.main.ui_manager.get_activity_marker().show() # for area in f.touch_areas: # area.update_position() for group in f.groups.values(): group.update_shape() elif not (items_have_moved or frame_has_moved or background_fade): self.stop_animations() self.main.ui_manager.get_activity_marker().hide() ctrl.items_moving = False self.keep_updating_visible_area = False f.edge_visibility_check() # only does something if flagged
def move(self, md: dict) -> (bool, bool): """ Do one frame of movement: either move towards target position or take a step according to algorithm 1. item folding towards position in part of animation to disappear etc. 2. item is being dragged 3. item is locked by user 4. item is tightly attached to another node which is moving (then the move is handled by the other node, it is _not_ parent node, though.) 5. visualisation algorithm setting it specifically (6) or (0) -- places where subclasses can add new movements. :param md: movement data dict, collects sum of all movement to help normalize it :return: """ # _high_priority_move can be used together with _move_counter self.unmoved = False if not self._high_priority_move: # Dragging overrides (almost) everything, don't try to move this anywhere if self._dragged: return True, False # Locked nodes are immune to physics elif self.locked: return False, False #elif self.locked_to_node: # return False, False # MOVE_TO -based movement has priority over physics. This way e.g. triangles work without # additional stipulation if self._move_counter: position = self.current_position # stop even despite the _move_counter, if we are close enough if about_there(position, self.target_position): self.stop_moving() return False, False self._move_counter -= 1 # move a precalculated step if self._use_easing: if self._move_frames != self._move_counter: time_f = 1 - (self._move_counter / self._move_frames) f = qt_prefs.curve.valueForProgress(time_f) else: f = 0 movement = multiply_xy(self._distance, f) self.current_position = add_xy(self._start_position, movement) else: movement = div_xy(sub_xy(self.target_position, position), self._move_counter) self.current_position = add_xy(self.current_position, movement) # if move counter reaches zero, stop and do clean-up. if not self._move_counter: self.stop_moving() if self.locked_to_node: self.locked_to_node.update_bounding_rect() return True, False # Physics move node around only if other movement types have not overridden it elif self.use_physics() and self.is_visible(): movement = ctrl.forest.visualization.calculate_movement(self) md['sum'] = add_xy(movement, md['sum']) md['nodes'].append(self) self.current_position = add_xy(self.current_position, movement) return abs(movement[0]) + abs(movement[1]) > 0.6, True return False, False