Reputation: 1491
By default when we create a simple window on macOS using tkinter
it looks like the window on the left. I'm trying to create a window that looks like the one on the right.
I've tried Googling this but I can't seem to find a solution. I assume it is because I am not using the correct key terms as "modern title bar" is most likely wrong.
If doing this is not possible using tkinter
, are there any recommendations for other GUI libraries that supports this "modern" look?
Upvotes: 3
Views: 823
Reputation: 27
Here is some code to create a window similar to the window you want.
win
is where your gui will be. It is a Canvas
from tkinter import *
## if installed: from tkmacosx import *
from tkinter.ttk import Sizegrip
import time
Title="finish mac titlebar"
root=Tk()
root.overrideredirect(1)
## if needed for your mac: root.overrideredirect(0)
root.geometry("300x150")
root.update_idletasks()
root.title(" "+Title+" ")
root.resizable(True, True)
root.maxsize(root.winfo_screenwidth(), root.winfo_screenheight())
root.attributes("-transparent", True)
dummy=Tk()
dummy.geometry("1x1")
dummy.resizable(0,0)
dummy.attributes("-alpha", False)
dummy.title(Title)
def unminimize_me(e):
root.deiconify()
dummy.bind("<FocusIn>", unminimize_me)
gray="#221F1E"
black="#111010"
root.config(bg=black)
title=Frame(root, bg=gray)
title.pack(fill=X, side=TOP)
win=Canvas(root, width=1, height=1, bg=black, highlightthickness=0)
win.pack(side=TOP, expand=True, fill=BOTH)
bottom=Frame(root, bg=black)
bottom.pack(side=BOTTOM, fill=X)
bottomright=Canvas(bottom, width=8, height=8, highlightthickness=0, bg="systemTransparent")
bottomright.pack(side=RIGHT)
bottomright.create_oval(-9,-9, 7,7, fill=black, outline=black)
bottomleft=Canvas(bottom, width=8, height=8, highlightthickness=0, bg="systemTransparent")
bottomleft.pack(side=LEFT)
bottomleft.create_oval(1,-9, 17,7, fill=black, outline=black)
left=Canvas(title, width=8, height=8, highlightthickness=0, bg="systemTransparent")
left.place(x=0, y=0)
left.create_oval(0,0, 16,16, fill=gray, outline=gray)
right=Canvas(title, width=8, height=8, highlightthickness=0, bg="systemTransparent")
right.pack(side=RIGHT, anchor=NE)
right.create_oval(7,1, -9,17, fill=gray, outline=gray)
def get_pos(e):
xwin=root.winfo_x()
ywin=root.winfo_y()
startx=e.x_root
starty=e.y_root
ywin=ywin-starty
xwin=xwin-startx
def move_window(e):
if e.x_root+xwin > -1 and e.y_root+ywin >-1:
root.geometry("+{0}+{1}".format(e.x_root+xwin, e.y_root+ywin))
startx=e.x_root
starty=e.y_root
title.bind("<Button1-Motion>", move_window)
title.bind("<Button-1>", get_pos)
def onx(e):
close.create_text(8,8, text="x", fill="dark red")
def offx(e):
close.delete(ALL)
close.create_oval(1,1, 15,15, outline="dark red", fill="red")
def x(e):
dummy.unbind("<FocusIn>")
root.destroy()
dummy.destroy()
close=Canvas(title, bg=gray, height=16, width=16, highlightthickness=0)
close.create_oval(1,1, 15,15, outline="dark red", fill="red")
close.pack(side=LEFT, pady=3, padx=2)
close.bind("<Enter>", onx)
close.bind("<Leave>", offx)
close.bind("<Button-1>", x)
def onmin(e):
minimize.create_text(8,8, text="-", fill="goldenrod")
def offmin(e):
minimize.delete(ALL)
minimize.create_oval(1,1, 15,15, outline="goldenrod", fill="yellow")
def minimize_me(e):
root.update_idletasks()
root.state("withdrawn")
minimize=Canvas(title, bg=gray, height=16, width=16, highlightthickness=0)
minimize.create_oval(1,1, 15,15, outline="goldenrod", fill="yellow")
minimize.pack(side=LEFT, pady=3, padx=2)
minimize.bind("<Enter>", onmin)
minimize.bind("<Leave>", offmin)
minimize.bind("<Button-1>", minimize_me)
window = 0
run=True
def maximize_me_for_command():
run=True
def loop():
global window, run
if run == True:
if window < 2000:
window += 132
root.geometry(f"{window}x{window}")
else:
window = 0
run=False
root.after(50, loop)
loop()
root.geometry("+0+0")
def titleleft():
root.geometry(f"400x1500+0+0")
def titleright():
root.geometry(f"400x1500+{root.winfo_screenwidth()-400}+0")
maxmenu=Menu(root)
maxmenu.add_cascade(label="▬ Enter Full Screen", command=maximize_me_for_command)
maxmenu.add_cascade(label="⇤ Title Window to Left of Screen", command=titleleft)
maxmenu.add_cascade(label="⇥ Title Window to Right of Screen", command=titleright)
go=False
def show():
global go
if go==True:
maxmenu.tk_popup(root.winfo_x()+55, root.winfo_y()+32)
go=False
def onmax(e):
global go
maximize.create_rectangle(4,4, 12,12, width=0, fill="darkolivegreen")
maximize.create_text(8,8, text="⁄", fill="green")
go=True
root.after(1000, show)
def offmax(e):
global go
go=False
maximize.delete(ALL)
maximize.create_oval(1,1, 15,15, outline="darkolivegreen", fill="green")
def maximize_me(e):
run=True
def loop():
global window, run
if run == True:
if window < 2000:
window += 132
root.geometry(f"{window}x{window}")
else:
window = 0
run=False
root.after(50, loop)
loop()
root.geometry("+0+0")
maximize=Canvas(title, bg=gray, height=16, width=16, highlightthickness=0)
maximize.create_oval(1,1, 15,15, outline="darkolivegreen", fill="green")
maximize.pack(side=LEFT, pady=3, padx=2)
maximize.bind("<Enter>", onmax)
maximize.bind("<Leave>", offmax)
maximize.bind("<Button-1>", maximize_me)
text=Frame(title, bg=gray)
text.pack(pady=3)
textintext=Frame(text, bg=gray)
textintext.pack()
l=Label(textintext, text=Title, bg=gray, fg=black, font="Helvetica 8")
l.grid(row=1, column=2)
icn=Label(textintext, text="I", font="Helvetica 8")
icn.grid(row=1, column=1)
sg = Sizegrip(root, cursor="lr_angle")
sg.pack(anchor=SE, side=BOTTOM, pady=1, padx=9)
Here is a different version.
import tkinter as tk
def nothing():
pass
class BTk():
def __init__(self, title="tk", bg="systemWindowBackgroundColor", fg="systemTextColor", titlebar_c="systemMenu"):
self.BG_COLOR = bg
self.FG_COLOR = fg
self.TRANS_COLOR = "systemTransparent"
self.TITLEBAR_COLOR = titlebar_c
## Create borderless window
self.root = tk.Tk()
self.root.geometry("400x170+400+200")
self.root.overrideredirect(True)
self.root.resizable(True, True)
self.root.attributes("-transparent", True)
self.root["bg"] = self.BG_COLOR
## Create fake window to show in doc
self.doc_dummy = tk.Tk()
self.doc_dummy.geometry("50x50+500+500")
self.doc_dummy.attributes("-alpha", 0.0)
self.doc_dummy.protocol("WM_DELETE_WINDOW", nothing)
## Create titlebar and window frame
self.titlebar = tk.Label(self.root, highlightthicknes=0, bd=0, text=title, bg=self.TITLEBAR_COLOR)
self.titlebar.pack(fill=tk.X)
self.top_left = tk.Canvas(self.titlebar, highlightthicknes=0, bg=self.TRANS_COLOR, width=60, height=30)
self.top_left.pack(side=tk.LEFT)
self.top_left.create_oval(0, 0, 18, 18, fill=self.TITLEBAR_COLOR, width=0)
self.top_left.create_polygon(9, -1, 60, -1, 60, 30, -1, 30, -1, 9, fill=self.TITLEBAR_COLOR, width=0)
self.top_right = tk.Canvas(self.titlebar, highlightthicknes=0, bg=self.TRANS_COLOR, width=30, height=30)
self.top_right.pack(side=tk.RIGHT)
self.top_right.create_oval(30, 0, 12, 18, fill=self.TITLEBAR_COLOR, width=0)
self.top_right.create_polygon(21, -1, -1, -1, -1, 30, 30, 30, 30, 9, fill=self.TITLEBAR_COLOR, width=0)
self.bottom_left = tk.Canvas(self.root, highlightthicknes=0, bg=self.TRANS_COLOR, width=30, height=30)
self.bottom_left.place(x=0, y=-30, relx=0, rely=1)
self.bottom_left.create_oval(0, 30, 18, 12, fill=self.BG_COLOR, width=0)
self.bottom_left.create_polygon(9, 30, 30, 30, 30, -1, -1, -1, -1, 21, fill=self.BG_COLOR, width=0)
self.bottom_right = tk.Canvas(self.root, highlightthicknes=0, bg=self.TRANS_COLOR, width=30, height=30)
self.bottom_right.place(x=-30, y=-30, relx=1, rely=1)
self.bottom_right.create_oval(30, 30, 12, 12, fill=self.BG_COLOR, width=0)
self.bottom_right.create_polygon(21, 30, -1, 30, -1, -1, 30, -1, 30, 21, fill=self.BG_COLOR, width=0)
self.main_frame = tk.Frame(self.root, highlightthicknes=0, bd=0, bg=self.BG_COLOR)
self.main_frame.pack(expand=True, fill=tk.BOTH, pady=[0, 9])
## Create buttons
self.close_btn = self.top_left.create_oval(7, 7, 18, 18, fill="#ED6A5F", outline="#CE4C3E")
self.minimize_btn = self.top_left.create_oval(27, 7, 38, 18, fill="#F4BE4F", outline="#D59E3A")
self.options_btn = self.top_left.create_oval(47, 7, 58, 18, fill="#62C554", outline="#4DA438")
self.close_smbl = self.top_left.create_text(13, 13, text="✕", fill="#dd0000", font="Arial 8 bold")
self.top_left.itemconfig(self.close_smbl, state="hidden")
self.minimize_smbl = self.top_left.create_text(33, 13, text="⎯", fill="#994500", font="Arial 8 bold")
self.top_left.itemconfig(self.minimize_smbl, state="hidden")
self.options_smbl = self.top_left.create_text(53, 13, text="◼", fill="#009900")
self.top_left.create_text(53, 13, text="╲", fill="#62C554", font="Arial 8 bold")
self.top_left.itemconfig(self.options_smbl, state="hidden")
## Setup bindings
self.top_left.tag_bind(self.close_smbl, "<Button-1>", lambda _e: self.quit())
self.top_left.tag_bind(self.close_btn, "<Button-1>", lambda _e: self.quit())
self.top_left.tag_bind(self.minimize_smbl, "<Button-1>", lambda _e:self.minimize())
self.top_left.tag_bind(self.minimize_btn, "<Button-1>", lambda _e:self.minimize())
self.top_left.tag_bind(self.options_smbl, "<Button-1>", lambda _e: self.maximize())
self.top_left.tag_bind(self.options_btn, "<Button-1>", lambda _e: self.maximize())
self.titlebar.bind("<Double-Button-1>", lambda _e: self.maximize())
self.titlebar.bind("<B1-Motion>", self.drag)
self.titlebar.bind("<Button-1>", self.get_pos)
self.doc_dummy.bind("<Motion>", self.check_btns_hover)
## adding widgets and other window setup
self.tk = self.main_frame.tk
self._last_child_ids = self.main_frame._last_child_ids
self._w = self.main_frame._w
self.children = self.main_frame.children
self.winfo_width = self.root.winfo_width
self.winfo_height = lambda: self.root.winfo_height()-37
self.winfo_x = self.root.winfo_x
self.winfo_y = self.root.winfo_y
self.winfo_pointerx = self.root.winfo_pointerx
self.winfo_pointery = self.root.winfo_pointery
self.winfo_screenwidth = self.root.winfo_screenwidth
self.winfo_screenheight = self.root.winfo_screenheight
self.title(title)
self.destroy_fun = None
def minimize(self):
self.root.withdraw()
self.doc_dummy.iconify()
def loop():
if self.doc_dummy.state() == "iconic":
self.doc_dummy.after(50, loop)
else:
self.unminimize()
loop()
def unminimize(self):
self.root.deiconify()
self.focus_force()
def get_pos(self, event):
self.last_x = event.x_root
self.last_y = event.y_root
def drag(self, event):
deltax = event.x_root - self.last_x
deltay = event.y_root - self.last_y
x = self.root.winfo_x() + deltax
y = self.root.winfo_y() + deltay
self.root.geometry("+%s+%s" % (x, y))
self.last_x = event.x_root
self.last_y = event.y_root
def mainloop(self):
self.root.mainloop()
def bind(self, key, fun):
self.doc_dummy.bind(key, fun)
def quit(self):
if self.destroy_fun == None:
self.root.quit()
self.doc_dummy.destroy()
self.root.withdraw()
else:
self.destroy_fun()
def destroy(self):
self.root.quit()
self.doc_dummy.destroy()
self.root.withdraw()
def maximize(self):
self.root.geometry(f"{self.root.winfo_screenwidth()}x{self.root.winfo_screenheight()}+0+0")
self.root.update_idletasks()
def focus_force(self):
self.root.focus_force()
self.doc_dummy.focus_force()
def title(self, title):
self.root.title(title)
self.titlebar.text = title
self.doc_dummy.title(title)
def geometry(self, string):
global wh, x, y, w, h
if "+" in string and "x" in string:
wh, x, y = string.split("+")
w, h = wh.split("x")
self.root.geometry(f"{w}x{int(h)+30}+{x}+{y}")
elif "x":
w, h = string.split("x")
self.root.geometry(f"{w}x{int(h)+30}")
else:
x, y = string.split("+")
self.root.geometry(f"+{x}+{y}")
def check_btns_hover(self, _e):
px = self.root.winfo_pointerx()-self.root.winfo_x()
py = self.root.winfo_pointery()-self.root.winfo_y()
if px < 60 and px > 0 and py < 30 and px > 0:
self.top_left.itemconfig(self.close_smbl, state="normal")
self.top_left.itemconfig(self.minimize_smbl, state="normal")
self.top_left.itemconfig(self.options_smbl, state="normal")
else:
self.top_left.itemconfig(self.close_smbl, state="hidden")
self.top_left.itemconfig(self.minimize_smbl, state="hidden")
self.top_left.itemconfig(self.options_smbl, state="hidden")
def after(self, time, fun, *args):
self.root.after(time, fun, *args)
def protocol(self, call, fun):
if call == "WM_DELETE_WINDOW":
self.destroy_fun = fun
else:
root.protocol(call, fun)
def iconphoto(self, img, compound=tk.LEFT):
self.titlebar["image"] = img
self.titlebar["compound"] = compound
example use:
root = BTk(title="Custom Modern Window")
tk.Label(root, text="Try to resize, move minimize or maximize me.\nI can also recive inputs. Press a key.").pack()
root.bind("<KeyPress>", print)
img = tk.PhotoImage("path/to/file.png") # for icon
root.iconphoto(img)
root.mainloop()
Upvotes: 1