Created
April 19, 2015 19:54
-
-
Save kennytm/2f62dafae26e5c02b5e2 to your computer and use it in GitHub Desktop.
split_view.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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