# author: Miguel Martinez Lopez
try:
from Tkinter import Tk, Frame, BitmapImage, Label
from Tkconstants import *
except ImportError:
from tkinter import Tk, Frame, BitmapImage, Label
from tkinter.constants import *
import base64
class Animation(object):
def __init__(self, w, ticks, config_function, duration=1, interval_time=None, easing_function=None, start_value=0, end_value=1, callback=None):
self._w = w
self._tick = 0
self._total_ticks = float(ticks)
if easing_function is None:
self._easing_function = lambda x: x
self._duration = duration
if interval_time:
self._interval_time = int(interval_time * 1000)
else:
self._interval_time = int(duration * 1000 / self._total_ticks)
self._start_value = start_value
self._end_value = end_value
self._interval_value = end_value - start_value
self._config_function = config_function
self._callback = callback
def start_animation(self, after=0):
if after != 0:
self.after(int(after*1000), self._animate)
else:
self._animate()
def _animate(self):
t = self._tick / self._total_ticks
value = self._start_value + self._interval_value * self._easing_function(t)
self._config_function(value)
self._tick += 1
if self._tick <= self._total_ticks:
self._w.after(self._interval_time, self._animate)
else:
if self._callback is not None:
self._w.after(self._interval_time, self._callback)
class Chord(Frame):
RIGHT_ARROW_ICON = 'I2RlZmluZSBpbWFnZV93aWR0aCAxNwojZGVmaW5lIGltYWdlX2hlaWdodCAxNwpzdGF0aWMgY2hhciBpbWFnZV9iaXRzW10gPSB7CjB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLAoweDYwLDB4MDAsMHgwMCwweGUwLDB4MDAsMHgwMCwweGUwLDB4MDMsMHgwMCwweGUwLDB4MGYsMHgwMCwweGUwLDB4MDMsMHgwMCwKMHhlMCwweDAxLDB4MDAsMHg2MCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsCjB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwCn07'
DOWN_ARROW_ICON = 'I2RlZmluZSBpbWFnZV93aWR0aCAxNwojZGVmaW5lIGltYWdlX2hlaWdodCAxNwpzdGF0aWMgY2hhciBpbWFnZV9iaXRzW10gPSB7CjB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLAoweDAwLDB4MDAsMHgwMCwweGUwLDB4MGYsMHgwMCwweGUwLDB4MGYsMHgwMCwweGMwLDB4MDcsMHgwMCwweGMwLDB4MDMsMHgwMCwKMHg4MCwweDAzLDB4MDAsMHgwMCwweDAxLDB4MDAsMHgwMCwweDAxLDB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwLDB4MDAsCjB4MDAsMHgwMCwweDAwLDB4MDAsMHgwMCwweDAwCn07'
def __init__(self, master, title, width, body_background="white", background="#f0f0f0", foreground="#333333", selected_background="#1ba1e2", selected_foreground="white", active_foreground="#0067cb", cursor="hand1"):
Frame.__init__(self, master, background="white")
self._title = title
self._background = background
self._foreground = foreground
self._active_foreground = active_foreground
self._selected_foreground = selected_foreground
self._selected_background = selected_background
self._cursor = cursor
self._right_arrow_icon = BitmapImage(data=base64.b64decode(Chord.RIGHT_ARROW_ICON))
self._down_arrow_icon = BitmapImage(data=base64.b64decode(Chord.DOWN_ARROW_ICON))
self._caption = Frame(self, width =width, background=background, padx=2)
self._caption.pack(fill=X, pady=(0,2))
self._caption.pack_propagate(False)
self._icon_label = Label(self._caption, image=self._right_arrow_icon, background=background)
self._icon_label.pack(side=LEFT)
self._title_label = Label(self._caption, text=title, bg = background, fg=foreground)
self._title_label.pack(side=LEFT, padx=4, fill=X)
self._caption.configure(height= self._title_label.winfo_reqheight())
self.body = Frame(self, background=body_background)
self._body_height = None
self._is_opened = False
self._is_animating = False
self._caption.bind('<Button-1>', self._on_click)
self._title_label.bind('<Button-1>', self._on_click)
self._icon_label.bind('<Button-1>', self._on_click)
self._caption.bind('<Enter>', self._on_enter)
self._caption.bind('<Leave>', self._on_leave)
@property
def title(self):
return self._title
@title.setter
def title(self, text):
self._title = text
self._title_label.configure(text=text)
def _on_enter(self, event):
if not self._is_opened:
self._down_arrow_icon.configure(foreground=self._active_foreground)
self._right_arrow_icon.configure(foreground=self._active_foreground)
self.config(cursor=self._cursor)
def _on_leave(self, event):
if not self._is_opened:
self._down_arrow_icon.configure(foreground=self._foreground)
self._right_arrow_icon.configure(foreground=self._foreground)
self.config(cursor="arrow")
def _on_click(self, event):
if self._is_animating: return
self.toggle()
def open(self):
if self._is_animating: return
if not self._is_opened: self._open()
def _open(self):
self.body.pack()
self.body.pack_propagate(False)
self._icon_label.configure(image=self._down_arrow_icon, background = self._selected_background)
self._title_label.configure(foreground= self._selected_foreground, background = self._selected_background)
self._caption.configure(background = self._selected_background)
self._down_arrow_icon.configure(foreground=self._selected_foreground)
if self._body_height is None:
self._body_height= self.body.winfo_reqheight()
end_value = self._body_height
self.body.configure(width=self.winfo_width())
self._is_opened = True
self._is_animating = True
animation = Animation(
self,
ticks=16,
interval_time=0.01,
start_value=0,
end_value=end_value,
config_function=lambda height: self.body.configure(height=int(height)),
callback=self._on_finnish_animation)
animation.start_animation()
def _on_finnish_animation(self):
self._is_animating = False
if not self._is_opened:
self.body.pack_forget()
def close(self):
if self._is_animating:
return
if self._is_opened: self._close()
def _close(self):
self._icon_label.configure(image=self._right_arrow_icon, background = self._background)
self._title_label.configure(foreground= self._foreground, background = self._background)
self._caption.configure(background = self._background)
self._right_arrow_icon.configure(foreground=self._foreground)
start_value = self.body.winfo_height()
self._is_opened = False
self._is_animating = True
animation = Animation(
self,
ticks=16,
interval_time=0.01,
start_value=start_value,
end_value=0,
config_function=lambda height: self.body.configure(height=int(height)),
callback=self._on_finnish_animation)
animation.start_animation()
def toggle(self):
if self._is_opened:
self._close()
else:
self._open()
class Accordion(Frame):
def __init__(self, parent, width, **kwargs):
Frame.__init__(self, **kwargs)
self._width = width
self._list_of_chords = []
def create_chord(self, title, background="white"):
chord = Chord(self, title=title, body_background=background, width=self._width)
self._list_of_chords.append(chord)
if len(self._list_of_chords) == 1:
chord.pack(fill=X)
else:
chord.pack(fill=X, pady=(1,0))
return chord
if __name__ == '__main__':
try:
from Tkinter import Entry, Button, Text
except ImportError:
from tkinter import Entry, Button, Text
root = Tk()
root.geometry("400x300")
root.configure(background="white")
# create the Accordion
accordion = Accordion(root, width=200)
accordion.pack(pady=10)
first_chord = accordion.create_chord('First Chord')
Label(first_chord.body, text='hello world', bg='white').pack()
# second chord
second_chord = accordion.create_chord('Second Chord')
Entry(second_chord.body).pack()
Button(second_chord.body, text='Button').pack()
# third chord
third_chord = accordion.create_chord(title='Third Chord')
Text(third_chord.body).pack()
root.mainloop()
Diff to Previous Revision
--- revision 4 2017-04-11 15:06:49
+++ revision 5 2017-04-11 15:52:23
@@ -34,9 +34,9 @@
self._callback = callback
- def start_animation(self, start_time=0):
- if start_time != 0:
- self.after(int(start_time*1000), self._animate)
+ def start_animation(self, after=0):
+ if after != 0:
+ self.after(int(after*1000), self._animate)
else:
self._animate()