layer_a.add_constraint(Clutter.AlignConstraint(source=stage, align_axis=Clutter.AlignAxis.BOTH, factor=0.5)) # Second actor, with no explicit size layer_b = Clutter.Actor() layer_b.props.background_color = Clutter.Color.get_static(Clutter.StaticColor.BUTTER_DARK) stage.add_child(layer_b) # The second actor tracks the X coordinate and the width of the first actor layer_b.add_constraint(Clutter.BindConstraint(source=layer_a, coordinate=Clutter.BindCoordinate.X)) layer_b.add_constraint(Clutter.BindConstraint(source=layer_a, coordinate=Clutter.BindCoordinate.WIDTH)) # The second actor is snapped between the bottom edge of the first actor # and the bottom edge of the stage; vertical spacing of 10px is added for # padding layer_b.add_constraint(Clutter.SnapConstraint(source=layer_a, from_edge=Clutter.SnapEdge.TOP, to_edge=Clutter.SnapEdge.BOTTOM, offset=10)) layer_b.add_constraint(Clutter.SnapConstraint(source=stage, from_edge=Clutter.SnapEdge.BOTTOM, to_edge=Clutter.SnapEdge.BOTTOM, offset=-10)) # The third actor, again with no explicit size layer_c = Clutter.Actor() layer_c.props.background_color = Clutter.Color.get_static(Clutter.StaticColor.CHAMELEON_LIGHT) stage.add_child(layer_c) # Like the second actor, the third one also track the X coordinate and # width of the first actor layer_c.add_constraint(Clutter.BindConstraint(source=layer_a, coordinate=Clutter.BindCoordinate.X)) layer_c.add_constraint(Clutter.BindConstraint(source=layer_a, coordinate=Clutter.BindCoordinate.WIDTH))
def init_draggable_handles(self): # Setup the four handles on the cropbox's corners. These are inset into the corners # of the cropbox, and don't actually spill over onto the image stage; the illusion # of their outward-facing corners overlapping the stage is made by offsetting the # opaque squares slightly onto the cropbox handles = [] coordinates = [TOP | LEFT, TOP | RIGHT, BOT | LEFT, BOT | RIGHT] for coordinate in coordinates: handle = DraggableHandle(self, coordinate) # Each handle has four constraints: # horiz_constraint: binds the handle to the cropbox's top/bottom sides # vert_constraint: binds the handle to the cropbox's left/right sides # width_constraint: binds the handle such that its width stays constant # height_constraint: binds the handle such that its height stays constant # # The height/width constraints are set by binding the opposite side of the handle # that horiz/vert constraints bind (respectively) to the same edge of the cropbox, # and setting an offset equal to the desired height/width. The handle's hitbox is # constrainted by position to the handle image horiz_constraint = Clutter.SnapConstraint() horiz_constraint.set_source(self) vert_constraint = Clutter.SnapConstraint() vert_constraint.set_source(self) height_constraint = Clutter.SnapConstraint() height_constraint.set_source(self) width_constraint = Clutter.SnapConstraint() width_constraint.set_source(self) hitbox_pos_constraint = Clutter.BindConstraint() hitbox_pos_constraint.set_source(handle.current_knob) hitbox_pos_constraint.set_offset(4) hitbox_pos_constraint.set_coordinate( Clutter.BindCoordinate.POSITION) offset = handle.get_offset() if coordinate & TOP: horiz_constraint.set_edges(CLUTTER_EDGES[TOP], CLUTTER_EDGES[TOP]) height_constraint.set_edges(CLUTTER_EDGES[BOT], CLUTTER_EDGES[TOP]) horiz_constraint.set_offset(BORDER_THICKNESS / 2 - offset) height_constraint.set_offset(BORDER_THICKNESS / 2 - offset) else: horiz_constraint.set_edges(CLUTTER_EDGES[BOT], CLUTTER_EDGES[BOT]) height_constraint.set_edges(CLUTTER_EDGES[TOP], CLUTTER_EDGES[BOT]) horiz_constraint.set_offset(-(BORDER_THICKNESS / 2 + offset)) height_constraint.set_offset(-(BORDER_THICKNESS / 2 + offset)) if coordinate & LEFT: vert_constraint.set_edges(CLUTTER_EDGES[LEFT], CLUTTER_EDGES[LEFT]) width_constraint.set_edges(CLUTTER_EDGES[RIGHT], CLUTTER_EDGES[LEFT]) vert_constraint.set_offset(BORDER_THICKNESS / 2 - offset) width_constraint.set_offset(BORDER_THICKNESS / 2 - offset) else: vert_constraint.set_edges(CLUTTER_EDGES[RIGHT], CLUTTER_EDGES[RIGHT]) width_constraint.set_edges(CLUTTER_EDGES[LEFT], CLUTTER_EDGES[RIGHT]) vert_constraint.set_offset(-(BORDER_THICKNESS / 2 + offset)) width_constraint.set_offset(-(BORDER_THICKNESS / 2 + offset)) handle.add_constraint(height_constraint) handle.add_constraint(width_constraint) handle.add_constraint(horiz_constraint) handle.add_constraint(vert_constraint) handle.hitbox.add_constraint(hitbox_pos_constraint) handles.append(handle) return handles
def init_surrounding_squares(self): # Setup the four opaque squares which cover the part # of the photo which isn't in the crop box squares = [] coordinates = [TOP, BOT, LEFT, RIGHT] # Roughly, the stage looks like this: # +---------------------+ # | TOP | # |---------------------| # | LEFT | CROP | RIGHT | # |---------------------| # | BOT | # +---------------------+ # Each box (except for CROP) is bound by their respective # edge of the CROP box (i.e. RIGHT's left side is bound to # CROP's right side), as well as adjacent sides of the clutter # stage for coordinate in coordinates: square = Clutter.Actor() square.set_reactive(True) square.set_background_color(BACKGROUND_COLOR) square.set_opacity(BACKGROUND_OPACITY) if coordinate & TOP or coordinate & BOT: # Both the TOP and BOT squares are bound on their right # side to the right side of the stage; likewise for left right_constraint = Clutter.SnapConstraint() right_constraint.set_source(self.stage) right_constraint.set_edges(CLUTTER_EDGES[RIGHT], CLUTTER_EDGES[RIGHT]) left_constraint = Clutter.SnapConstraint() left_constraint.set_source(self.stage) left_constraint.set_edges(CLUTTER_EDGES[LEFT], CLUTTER_EDGES[LEFT]) square.add_constraint(left_constraint) square.add_constraint(right_constraint) if coordinate & LEFT or coordinate & RIGHT: # The LEFT and RIGHT squares have their top and bottom edges # bound to the top and bottom edges of the CROP box top_constraint = Clutter.SnapConstraint() top_constraint.set_source(self) top_constraint.set_edges(CLUTTER_EDGES[TOP], CLUTTER_EDGES[TOP]) bot_constraint = Clutter.SnapConstraint() bot_constraint.set_source(self) bot_constraint.set_edges(CLUTTER_EDGES[BOT], CLUTTER_EDGES[BOT]) square.add_constraint(top_constraint) square.add_constraint(bot_constraint) # All the boxes have their namesake's edge bound to the same # named edge of the stage, and have the opposite side bound to their # namesake's edge of CROP. # # For example, LEFT's left side is bound to the left edge of # the stage, and LEFT's right side is bound to the left side of CROP cropbox_constraint = Clutter.SnapConstraint() cropbox_constraint.set_source(self) cropbox_constraint.set_edges( CLUTTER_EDGES[self.opposing_side(coordinate)], CLUTTER_EDGES[coordinate]) stage_constraint = Clutter.SnapConstraint() stage_constraint.set_source(self.stage) stage_constraint.set_edges(CLUTTER_EDGES[coordinate], CLUTTER_EDGES[coordinate]) square.add_constraint(cropbox_constraint) square.add_constraint(stage_constraint) squares.append(square) return squares
def init_draggable_borders(self): # Setup the borders which lay between the draggable handles borders = [] coordinates = [TOP, BOT, LEFT, RIGHT] for coordinate in coordinates: border_wrapper = DraggableBorder(self, coordinate) border = border_wrapper.line border_hitbox = border_wrapper.hitbox # Each border has four constraints: # cropbox_edge_constraint: binds the border's x/y position to the respective edge # of the cropbox # thickness_constraint: binds the height/width of the border to the same edge, in # order to maintain a constant thickness # length_constraint (1 & 2): these two bind the two loose ends of the border to the # cropbox's corners # Its hitbox is constrained in the same fashion as its length_constraints, but also has # a positional constraint to the border cropbox_edge_constraint = Clutter.SnapConstraint() cropbox_edge_constraint.set_source(self) thickness_constraint = Clutter.SnapConstraint() thickness_constraint.set_source(self) wrapper_constraint_1 = Clutter.SnapConstraint() wrapper_constraint_1.set_source(self) wrapper_constraint_2 = Clutter.SnapConstraint() wrapper_constraint_2.set_source(self) wrapper_pos_constraint = Clutter.BindConstraint() wrapper_pos_constraint.set_source(border) length_constraint_1 = Clutter.SnapConstraint() length_constraint_1.set_source(self) length_constraint_2 = Clutter.SnapConstraint() length_constraint_2.set_source(self) if coordinate & TOP or coordinate & LEFT: thickness_offset = border_wrapper.get_offset() else: thickness_offset = -border_wrapper.get_offset() cropbox_edge_constraint.set_edges(CLUTTER_EDGES[coordinate], CLUTTER_EDGES[coordinate]) thickness_constraint.set_edges( CLUTTER_EDGES[self.opposing_side(coordinate)], CLUTTER_EDGES[coordinate]) thickness_constraint.set_offset(thickness_offset) wrapper_pos_constraint.set_coordinate( Clutter.BindCoordinate.POSITION) wrapper_pos_constraint.set_offset(-MARGIN / 2) if coordinate & TOP or coordinate & BOT: length_constraint_1.set_edges(CLUTTER_EDGES[LEFT], CLUTTER_EDGES[LEFT]) length_constraint_2.set_edges(CLUTTER_EDGES[RIGHT], CLUTTER_EDGES[RIGHT]) wrapper_constraint_1.set_edges(CLUTTER_EDGES[LEFT], CLUTTER_EDGES[LEFT]) wrapper_constraint_2.set_edges(CLUTTER_EDGES[RIGHT], CLUTTER_EDGES[RIGHT]) border_hitbox.set_height(MARGIN) else: length_constraint_1.set_edges(CLUTTER_EDGES[TOP], CLUTTER_EDGES[TOP]) length_constraint_2.set_edges(CLUTTER_EDGES[BOT], CLUTTER_EDGES[BOT]) wrapper_constraint_1.set_edges(CLUTTER_EDGES[TOP], CLUTTER_EDGES[TOP]) wrapper_constraint_2.set_edges(CLUTTER_EDGES[BOT], CLUTTER_EDGES[BOT]) border_hitbox.set_width(MARGIN) border.add_constraint(cropbox_edge_constraint) border.add_constraint(thickness_constraint) border.add_constraint(length_constraint_1) border.add_constraint(length_constraint_2) border_hitbox.add_constraint(wrapper_constraint_1) border_hitbox.add_constraint(wrapper_constraint_2) border_hitbox.add_constraint(wrapper_pos_constraint) borders.append(border_wrapper) return borders