Skip to content

Instantly share code, notes, and snippets.

@kennytm
Created April 19, 2015 19:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kennytm/2f62dafae26e5c02b5e2 to your computer and use it in GitHub Desktop.
Save kennytm/2f62dafae26e5c02b5e2 to your computer and use it in GitHub Desktop.
split_view.py
#!/usr/bin/env python3
from tkinter import *
from tkinter.ttk import *
from PIL import Image
from PIL.ImageDraw import Draw
from PIL.ImageTk import PhotoImage
import enum
SPLIT_HANDLE_SIZE = 7
SPLIT_HANDLE_OPTIONS = {
'fill': '#00ff00',
'activefill': '#ffff00',
'tags': 'splitter',
}
SPLIT_LINE_OUTLINE_OPTIONS = {
'fill': '',
'activefill': '#ffff00',
'width': 3,
'tags': 'splitter',
}
SPLIT_LINE_INLINE_OPTIONS = {
'fill': '#000000',
'width': 1,
'state': DISABLED,
}
class Edge(enum.Enum):
left = 0
top = 1
right = 2
bottom = 3
def neighbors(self):
yield self
yield Edge((self.value - 1) % 4)
yield Edge((self.value + 1) % 4)
def inner_vertices(self, end, width, height):
'''Finds the extra vertices to form a loop between two edges.
'''
'''
Consider an arbitrary point on the "start" edge and another point on the "end"
edge. Draw an arrow from "start" to "end". Now continue the arrow by following
the boundary of the rectangle clockwise to get back to the starting point. This
should draw a 3 to 5-sided polynomial. This dictionary lists of intermediate
vertices while going through the rectangle's boundary.
'''
SPLIT_INNER_VERTICES = {
# Degenerate
(Edge.top, Edge.top): [],
(Edge.left, Edge.left): [],
(Edge.bottom, Edge.bottom): [],
(Edge.right, Edge.right): [],
# Counter-clockwise neighbor: Triangle
(Edge.top, Edge.left): [(0, 0)],
(Edge.left, Edge.bottom): [(0, 1)],
(Edge.bottom, Edge.right): [(1, 1)],
(Edge.right, Edge.top): [(1, 0)],
# Opposite: Quadrilateral
(Edge.top, Edge.bottom): [(0, 1), (0, 0)],
(Edge.left, Edge.right): [(1, 1), (0, 1)],
(Edge.bottom, Edge.top): [(1, 0), (1, 1)],
(Edge.right, Edge.left): [(0, 0), (1, 0)],
# Clockwise neight: Pentagon
(Edge.top, Edge.right): [(1, 1), (0, 1), (0, 0)],
(Edge.left, Edge.top): [(1, 0), (1, 1), (0, 1)],
(Edge.bottom, Edge.left): [(0, 0), (1, 0), (1, 1)],
(Edge.right, Edge.bottom): [(0, 1), (0, 0), (1, 0)],
}
class SplitScreenCanvas(Canvas):
def __init__(self, master):
super().__init__(master, bg='#808080')
self._handle_coords = [(0.5, 0), (0.5, 1)]
self._image = self.create_image(SPLIT_HANDLE_SIZE//2, SPLIT_HANDLE_SIZE//2, anchor=NW)
self._line_outline = self.create_line(0, 0, 0, 0, **SPLIT_LINE_OUTLINE_OPTIONS)
self._line_inline = self.create_line(0, 0, 0, 0, **SPLIT_LINE_INLINE_OPTIONS)
self._handles = [
self.create_oval(0, 0, 0, 0, **SPLIT_HANDLE_OPTIONS),
self.create_oval(0, 0, 0, 0, **SPLIT_HANDLE_OPTIONS),
]
self.bind('<Configure>', self.on_resize)
self.tag_bind('splitter', '<ButtonPress-1>', self.on_drag_begin)
self.tag_bind('splitter', '<ButtonRelease-1>', self.on_drag_end)
self.tag_bind('splitter', '<Enter>', self.change_cursor)
self.tag_bind('splitter', '<Leave>', self.reset_cursor)
def on_resize(self, event):
self.update_handle_positions(event.width, event.height)
def set_images(self, image1, image2):
self._image1 = image1
self._image2 = image2
self.update_handle_positions(self.winfo_width(), self.winfo_height())
@staticmethod
def edge(rx, ry):
if rx <= 0:
return Edge.left
elif ry <= 0:
return Edge.top
elif rx >= 1:
return Edge.right
elif ry >= 1:
return Edge.bottom
def cur_edges(self):
(a, b) = self._handle_coords
return (self.edge(*a), self.edge(*b))
def update_handle_positions(self, width, height):
width = width - SPLIT_HANDLE_SIZE
height = height - SPLIT_HANDLE_SIZE
((r1x, r1y), (r2x, r2y)) = self._handle_coords
(handle1, handle2) = self._handles
left1 = round(r1x * width)
top1 = round(r1y * height)
left2 = round(r2x * width)
top2 = round(r2y * height)
self.coords(handle1, left1, top1, left1 + SPLIT_HANDLE_SIZE, top1 + SPLIT_HANDLE_SIZE)
self.coords(handle2, left2, top2, left2 + SPLIT_HANDLE_SIZE, top2 + SPLIT_HANDLE_SIZE)
center1x = round(r1x * width + SPLIT_HANDLE_SIZE/2)
center1y = round(r1y * height + SPLIT_HANDLE_SIZE/2)
center2x = round(r2x * width + SPLIT_HANDLE_SIZE/2)
center2y = round(r2y * height + SPLIT_HANDLE_SIZE/2)
self.coords(self._line_outline, center1x, center1y, center2x, center2y)
self.coords(self._line_inline, center1x, center1y, center2x, center2y)
if width > 0 and height > 0:
rescaled_image1 = self._image1.resize((width, height))
rescaled_image2 = self._image2.resize((width, height))
mask_image = Image.new('1', (width, height))
from_edge = self.edge(r1x, r1y)
to_edge = self.edge(r2x, r2y)
polygon = [(left1, top1), (left2, top2)]
polygon.extend((width*w, height*h) for w, h in SPLIT_INNER_VERTICES[(from_edge, to_edge)])
Draw(mask_image).polygon(polygon, outline=1, fill=1)
composed_image = Image.composite(rescaled_image1, rescaled_image2, mask_image)
self._canvas_image = PhotoImage(composed_image)
self.itemconfigure(self._image, image=self._canvas_image)
def on_drag_begin(self, event):
event.widget.bind("<Motion>", self.on_drag_move)
def on_drag_end(self, event):
event.widget.unbind("<Motion>")
def on_drag_move(self, event):
(id_,) = self.find_withtag(CURRENT)
width = self.winfo_width()
height = self.winfo_height()
x = event.x
y = event.y
if x < 0:
x = 0
elif x > width:
x = width
if y < 0:
y = 0
elif y > height:
y = height
if id_ == self._line_outline:
self.move_line(x, y, width, height)
else:
self.move_handle(x, y, width, height, self._handles.index(id_))
def change_cursor(self, event):
self['cursor'] = 'fleur'
def reset_cursor(self, event):
self['cursor'] = 'left_ptr'
def move_line(self, x, y, width, height):
rx = x / width
ry = y / height
((r1x, r1y), (r2x, r2y)) = self._handle_coords
if r1x == r2x:
if r1y <= 0 and r2y >= 1 or r1y >= 1 and r2y <= 0:
self._handle_coords = [(rx, r1y), (rx, r2y)]
elif r1y == r2y:
if r1x <= 0 and r2x >= 1 or r1x >= 1 and r2x <= 0:
self._handle_coords = [(r1x, ry), (r2x, ry)]
else:
m = (r2y - r1y) / (r2x - r1x)
edges = {
Edge.top: (rx - ry / m, 0),
Edge.bottom: (rx + (1 - ry) / m, 1),
Edge.left: (0, ry - m * rx),
Edge.right: (1, ry + m * (1 - rx))
}
edges = {e: (rx, ry) for e, (rx, ry) in edges.items() if 0 <= rx <= 1 and 0 <= ry <= 1}
cur_edges = self.cur_edges()
for i, edge in enumerate(cur_edges):
for real_edge in edge.neighbors():
try:
self._handle_coords[i] = edges[real_edge]
break
except KeyError:
pass
else:
raise ValueError('all neighbors eliminated?')
self.update_handle_positions(width, height)
def move_handle(self, x, y, width, height, handle_index):
edge_distance = {
Edge.left: x,
Edge.right: width - x,
Edge.top: y,
Edge.bottom: height - y,
}
opposite = self._handle_coords[1 - handle_index]
del edge_distance[self.edge(*opposite)]
closest_edge = min(edge_distance.items(), key=lambda p: p[1])[0]
if closest_edge == Edge.left:
(rtx, rty) = (0, y / height)
elif closest_edge == Edge.right:
(rtx, rty) = (1, y / height)
elif closest_edge == Edge.top:
(rtx, rty) = (x / width, 0)
elif closest_edge == Edge.bottom:
(rtx, rty) = (x / width, 1)
self._handle_coords[handle_index] = (rtx, rty)
self.update_handle_positions(width, height)
class App(Frame):
def __init__(self, master):
super().__init__(master, border=2)
self.grid(sticky=NSEW)
self.make_resizable()
self.create_widgets()
def make_resizable(self):
top = self.winfo_toplevel()
top.rowconfigure(0, weight=1)
top.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
def create_widgets(self):
image1 = Image.open('1.png')
image2 = Image.open('2.png')
self._canvas = SplitScreenCanvas(self)
self._canvas.grid(sticky=NSEW)
self._canvas.set_images(image1, image2)
self._lol = Button(self, text='lol', command=self.quit)
self._lol.grid(sticky=S)
def main():
root = Tk()
app = App(root)
app.mainloop()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment