(Jun-21-2023, 03:00 AM)deanhystad Wrote: [ -> ]I can make something that looks exactly the same by making the canvas the size of the window and drawing a blue rectangle. No need for win-anything.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, bg='black')
canvas.create_rectangle(50,50,100,100,fill='blue')
canvas.pack(expand=True, fill=tk.BOTH)
root.mainloop()
A blue rectangle on a black background is not doing a great job demonstrating what you are trying to achieve. If you want to create widgets that are partially transparent, that is easy to do with other GUI toolkits like Qt.
The code below is where it is implemented and is part of
rapidTk which will be required for the mutitude of imports from rapidTk
It draws a circle over other widgets while keeping them visable.
The code i posted in my question is just a MCVE for testing purposes and is in no way linked to the actual use case.
As this is possible in windows it should also be possible in Linux.
I included "COMPLEX!" in my question title as this will not be a simple solution of creating a canvase the size of my application which covers evertything just to draw a small UI element somewhere in the middle.
The solution for this will require both an understanding of advanced Tk along with a good understanding of X11 or other Luinx APIs.
class TimePicker(cFrame, widgetBase_override):
def __init__(self, master, **kwargs):
pp = PackProcess()
self._acl = None
self._atx = None
self.split = "am"
self.master = master
self.tformat = kwargs.pop('format', 24)
self.min_interval = kwargs.pop('interval', 5)
tp_bg = kwargs.pop('tp_bg', '#010101')
self.width = self.height = self.radious = rd = kwargs.pop('radious', 100)*2
layout = inline_layout(**kwargs)
widget_args = layout.filter()
super(TimePicker, self).__init__(master, **kwargs)
self.hours, self.minutes = StringVar(), StringVar()
self.hours.set('00')
self.minutes.set('00')
holder_frame= pp.add(cFrame(self, bg="green"),side=TOP)
def pad(item):return f"0{item}" if len(item) == 1 else item
self.hourE = pp.add(cSpinbox(holder_frame, textvariable=self.hours, width=3, values=[pad(str(x)) for x in range(24)], wrap=0), side=LEFT)
self._centre = pp.add(cLabel(holder_frame, text=":"), side=LEFT)
self.minutesE = pp.add(cSpinbox(holder_frame, textvariable=self.minutes, width=3, values=[pad(str(x)) for x in range(60)], wrap=1), side=LEFT)
#holder_frame.pack(side=TOP)
##setup focus bindings
self.hourE.bind("<FocusIn>", self.popup)
self.hourE.bind("<FocusOut>", self.__focus_loss)
self.minutesE.bind("<FocusIn>", self.popup)
self.minutesE.bind("<FocusOut>", self.__focus_loss)
self.radious /= 4
self.radious -=1 ##fixes clipping
assert self.tformat in [12, 24], "Time Format must be '12' or '24'"
pp.pack()
self.sub_can = cCanvas(self.get_root(), bg=tp_bg, width=self.width+5, height=self.height+5, highlightbackground="#010101", highlightthickness=0)
self.sub_can.bind("<FocusIn>", self.popup)
hwnd = self.sub_can.winfo_id()
colorkey = win32api.RGB(1,1,1) #full black in COLORREF structure
wnd_exstyle = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
new_exstyle = wnd_exstyle | win32con.WS_EX_LAYERED
win32gui.SetWindowLong(hwnd,win32con.GWL_EXSTYLE,new_exstyle)
win32gui.SetLayeredWindowAttributes(hwnd, colorkey,255,win32con.LWA_COLORKEY)
self.active_line = None
self._main = self.create_center_circle(self.width/2, self.height/2, self.radious*2, fill="#DDDDDD", outline="#000", width=0)
self.sub_can.tag_bind(self._main, "<Button-1>", self.popup)
self.circle_numbers(self.width/2, self.height/2, self.radious*2-15, 10, [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 'Helvetica 11 bold', "Hours")
self.circle_numbers(self.width/2, self.height/2, self.radious+5, 10, list(range(0, 60, self.min_interval)), 'Helvetica 11 bold', "Minutes")
self.am_pm_switch()
self.center = self.create_center_circle(self.width/2, self.height/2, 5, fill="#DDDDDD", width=0)
if layout.method is not None:
layout.inline(self)
def __focus_loss(self, event):
if self.get_root().focus_get() != self.sub_can:
self.close(event)
else:
self.minutesE.focus_set()
def _on_scroll(self, event, maxn=0):
num = int(event.widget.get())
event.widget.delete(0, END)
if event.delta > 0 :
if num >= maxn:
event.widget.insert(0, '00')
else:
event.widget.insert(0, str(num+1).zfill(2))
elif event.delta < 0:
if num <= 0:
event.widget.insert(0, str(maxn).zfill(2))
else:
event.widget.insert(0, str(num-1).zfill(2))
def am_pm_switch(self):
ovall = 30
ovalw = 40
self.sub_can.create_oval(self.width/2-ovall, self.height/2+ovalw/4, self.width/2+ovall, self.height/2+ovalw, fill="#BBBBBB")
sc, st = self.create_am()
self.sub_can.tag_bind(sc, "<Button-1>", lambda e=Event(), a=sc, b=st:self._switcher(e, a, b))
self.sub_can.tag_bind(st, "<Button-1>", lambda e=Event(), a=sc, b=st:self._switcher(e, a, b))
def create_am(self):
ovalr = 40
am = self.create_center_circle(self.width/2-ovalr/1.75, self.height/2+ovalr/1.75, 12, fill='#0575DD', width=0)
amtx = self.sub_can.create_text(self.width/2-ovalr/1.75, self.height/2+ovalr/1.75, font=('Helvetica 11 bold'), text="AM")
self.split = "am"
return am, amtx
def create_pm(self):
ovalr = 40
pm = self.create_center_circle(self.width/2+ovalr/1.75, self.height/2+ovalr/1.75, 12, fill='#0575DD', width=0)
pmtx = self.sub_can.create_text(self.width/2+ovalr/1.75, self.height/2+ovalr/1.75, font=('Helvetica 11 bold'), text="PM")
self.split = "pm"
return pm, pmtx
def _switcher(self, event, sc, st):
self.sub_can.delete(sc)
self.sub_can.delete(st)
if self.split == "am":
sc, st = self.create_pm()
self.sub_can.tag_bind(sc, "<Button-1>", lambda e=Event(), a=sc, b=st:self._switcher(e, a, b))
self.sub_can.tag_bind(st, "<Button-1>", lambda e=Event(), a=sc, b=st:self._switcher(e, a, b))
elif self.split == "pm":
sc, st = self.create_am()
self.sub_can.tag_bind(sc, "<Button-1>", lambda e=Event(), a=sc, b=st:self._switcher(e, a, b))
self.sub_can.tag_bind(st, "<Button-1>", lambda e=Event(), a=sc, b=st:self._switcher(e, a, b))
def create_center_circle(self, x, y, r, **kwargs):
return self.sub_can.create_oval(x-r, y-r, x+r, y+r, **kwargs)
def create_circle_arc(self, x, y, r, **kwargs):
if "start" in kwargs and "end" in kwargs:
kwargs["extent"] = kwargs["end"] - kwargs["start"]
del kwargs["end"]
return super().create_arc(x-r, y-r, x+r, y+r, **kwargs)
def circle_numbers(self, x: int, y: int, r: int, cr:int, numbers: list, font: str, tp:str):
_angle = 360/len(numbers)
for i, n in enumerate(numbers):
ax = r * sin(pi * 2 * (360-_angle*i-180) / 360);
ay = r * cos(pi * 2 * (360-_angle*i-180) / 360);
tag = f'{tp}:{str(n)}'
cl = self.create_center_circle(x+ax, y+ay, cr, fill="#DDDDDD", outline="#000", width=0, tag=tag)
tx = self.sub_can.create_text(x+ax, y+ay, text=str(n).zfill(2), fill="black", font=(font), tag='tx'+tag )
self.sub_can.tag_bind(f'tx{tp}:{str(n)}', '<Enter>', lambda e=Event(), cl=cl, tx=tx, c=(x+ax, y+ay), t=tag, s=True: self._hover(e, cl, tx, c, s, t))
#self.sub_can.tag_bind(f'tx{tp}:{str(n)}', '<Leave>', lambda e=Event(), cl=cl, tx=tx, c=(x+ax, y+ay), t=tag, s=False: self._left(e, cl, tx, c, s, t))
self.sub_can.tag_bind(f'{tp}:{str(n)}', '<Button-1>', lambda e=Event(), c=cl, s=tx, n=n, t=tp,: self._set_number(e, c, s, n, t))
self.sub_can.tag_bind(f'tx{tp}:{str(n)}', '<Button-1>', lambda e=Event(), c=cl, s=tx, n=n, t=tp,: self._set_number(e, c, s, n, t))
def _hover(self, event, cl, tx, coords, state, tag):
if self.active_line:
self.sub_can.delete(self.active_line)
self.sub_can.itemconfigure(self._acl, fill='#DDDDDD')
self.sub_can.itemconfigure(self._atx, fill="black")
self._acl = cl
self._atx = tx
self.sub_can.itemconfigure(cl, fill='#0797FF')
self.sub_can.itemconfigure(tx, fill="white")
self.sub_can.itemconfigure(self.center, fill='#0797FF')
dx = (1 - 0.8) * self.width/2 + 0.8 * coords[0]
dy = (1 - 0.8) * self.height/2 + 0.8 * coords[1]
self.active_line = self.sub_can.create_line(self.width/2, self.height/2, dx, dy, fill="#0797FF", width=2, tag=None) ##create new line
self.sub_can.tag_lower(self.active_line)
self.sub_can.tag_lower(self._main)
def _left(self, event, cl, tx, coords, state, tag):
if self.active_line is None: ##if there is no line
return
self.sub_can.itemconfigure(cl, fill='#DDDDDD')
self.sub_can.itemconfigure(tx, fill="black")
self.sub_can.itemconfigure(self.center, fill='#DDDDDD')
self.sub_can.delete(self.active_line)
self.active_line = None
def _set_number(self, event, cl, tx, number, tp):
if tp == "Hours":
if self.split == "pm":
number = (number+12)%24
self.hours.set(str(number).zfill(2))
self.minutesE.focus()
elif tp == "Minutes":
self.minutes.set(str(number).zfill(2))
self.close(event)
self.master.focus()
def get(self):
return self.hours.get(), self.minutes.get()
def popup(self, event):
xpos = self._centre.winfo_rootx() - self.winfo_toplevel().winfo_rootx()
ypos = self._centre.winfo_rooty() - self.winfo_toplevel().winfo_rooty() + self._centre.winfo_height()
width = self._centre.winfo_width()/2
self.sub_can.place(x=xpos-(self.width/2)+width, y=ypos)
def close(self, event):
self.sub_can.place_forget()