Scrolling a canvas as a shape you're moving approaches its edges
- by Steven Sproat
Hi, I develop a Python-based drawing program, Whyteboard. I have tools that the user can create new shapes on the canvas, such as text/images/rectangles/circles/polygons. I also have a Select tool that allows the users to modify these shapes - for example, moving a shape's position, resizing, or editing polygon's points' positions.
I'm adding in a new feature where moving or resizing a point near the canvas edge will automatically scroll the canvas. I think it's a good idea in terms of program usability, and annoys me when other program's don't have this feature.
I've made some good progress on coding this; below is some Python code to demonstrate what I'm doing. These functions demonstrate how some shapes calculate their "edges":
def find_edges(self):
"""A line."""
self.edges = {EDGE_TOP: min(self.y, self.y2), EDGE_RIGHT: max(self.x, self.x2),
EDGE_BOTTOM: max(self.y, self.y2), EDGE_LEFT: min(self. x, self.x2)}
def find_edges(self):
"""An image"""
self.edges = {EDGE_TOP: self.y, EDGE_RIGHT: self.x + self.image.GetWidth(),
EDGE_BOTTOM: self.y + self.image.GetWidth(), EDGE_LEFT: self.x}
def find_edges(self):
"""Get the bounding rectangle for the polygon"""
xmin = min(x for x, y in self.points)
ymin = min(y for x, y in self.points)
xmax = max(x for x, y in self.points)
ymax = max(y for x, y in self.points)
self.edges = {EDGE_TOP: ymin, EDGE_RIGHT: xmax, EDGE_BOTTOM: ymax, EDGE_LEFT: xmin}
And here's the code I have so far to implement the scrolling when a shape nears the edge:
def check_canvas_scroll(self, x, y, moving=False):
"""
We check that the x/y coords are within 50px from the edge of the canvas
and scroll the canvas accordingly. If the shape is being moved, we need
to check specific edges of the shape (e.g. left/right side of rectangle)
"""
size = self.board.GetClientSizeTuple() # visible area of the canvas
if not self.board.area > size: # canvas is too small to need to scroll
return
start = self.board.GetViewStart() # user's starting "viewport"
scroll = (-1, -1) # -1 means no change
if moving:
if self.shape.edges[EDGE_RIGHT] > start[0] + size[0] - 50:
scroll = (start[0] + 5, -1)
if self.shape.edges[EDGE_BOTTOM] > start[1] + size[1] - 50:
scroll = (-1, start[1] + 5)
# snip others
else:
if x > start[0] + size[0] - 50:
scroll = (start[0] + 5, -1)
if y > start[1] + size[1] - 50:
scroll = (-1, start[1] + 5)
# snip others
self.board.Scroll(*scroll)
This code actually works pretty well. If we're moving a shape, then we need to know its edges to calculate when they're coming close to the canvas edge. If we're resizing just a single point, then we just use the x/y coords of that point to see if it's close to the edge.
The problem I'm having is a little tricky to describe - basically, if you move a shape to the left, and stop moving it, if you positioned the shape within the 50px from the canvas, then the next time you go to move the shape, the code that says "ok, is this shape close to the end?" gets triggered, and the canvas scrolls to the left, even if you're moving the shape to the right.
Can anyone think on how to stop this? I created a youtube video to demonstrate the issue. At about 0:54, I move a polygon to the left of the canvas and position it there. The next time I move it, the canvas scrolls to the left even though I'm moving it right
Another thing I'd like to add, but I'm stuck on is the scroll gaining momentum the longer a shape is scrolling? So, with a large canvas, you're not moving a shape for ages, moving 5px at a time, when you need to cover a 2000px distance. Any suggestions there?
Thanks all - sorry for the super long question!