[Tkinter] COMPLEX! Transparent Canvas Linux - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: GUI (https://python-forum.io/forum-10.html) +--- Thread: [Tkinter] COMPLEX! Transparent Canvas Linux (/thread-40211.html) |
COMPLEX! Transparent Canvas Linux - AceScottie - Jun-20-2023 Im looking to make my code cross compatible with Linux. Unfortunatly i have some bits of code that rely on the win32 module and windows API calls to properly maintain. One of these piceces is to create a transparent Canvas background so i can draw a circle without having a square border behind it. The code for this was copied from SO: https://stackoverflow.com/a/70150296/3310078 Im trying to find a way to duplicate this in Linux using X11 but cannt find and resources as a starting location. Has anyone tried anything like this before who can point me in the correct direction ? import tkinter as tk import win32gui import win32con import win32api root = tk.Tk() root.configure(bg="black") canvas = tk.Canvas(root, bg='#010101')# not quirte full black hwnd = canvas.winfo_id() colorkey = win32api.RGB(1,1,1) #not quite 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) canvas.create_rectangle(50,50,100,100,fill='blue') canvas.pack() root.mainloop() RE: COMPLEX! Transparent Canvas Linux - deanhystad - Jun-20-2023 I do not understand this: Quote:One of these piceces is to create a transparent Canvas background so i can draw a circle without having a square border behind it.Drawing a circle on a canvas does result in a square border. Placing a widget on a canvas will do this. but drawing on the canvas does not. RE: COMPLEX! Transparent Canvas Linux - AceScottie - Jun-21-2023 (Jun-20-2023, 11:47 PM)deanhystad Wrote: I do not understand this: what i mean by the square border is the canvas itself being that border. basically the square canvas will show behind the drawn circle. The code i posted makes the background colour of the canvas transparent so when run it only shows the root widget in black and the drawn rectangle in blue. without the win32 code (has border) https://prnt.sc/P_bmwQMuMM-u with win32 code (no border) https://prnt.sc/cl6lzs3W-KAi RE: COMPLEX! Transparent Canvas Linux - deanhystad - Jun-21-2023 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. RE: COMPLEX! Transparent Canvas Linux - rob101 - Jun-21-2023 (Jun-20-2023, 10:55 PM)AceScottie Wrote: ... draw a circle without having a square border behind it. Not too sure if this will help: import tkinter as tk root = tk.Tk() root.geometry('800x600') root.title('Canvas Demo') canvas = tk.Canvas(root, width=600, height=400, bg='#F0F0F0') canvas.pack(anchor=tk.CENTER, expand=True) points = ( (50, 150), (200, 300), ) canvas.create_oval(*points, fill='purple') root.mainloop() RE: COMPLEX! Transparent Canvas Linux - deanhystad - Jun-21-2023 Why do you want the canvas to be transparent? What is behind the canvas that you want to see? RE: COMPLEX! Transparent Canvas Linux - AceScottie - Jun-21-2023 (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.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() RE: COMPLEX! Transparent Canvas Linux - AceScottie - Jun-21-2023 (Jun-21-2023, 02:51 PM)deanhystad Wrote: Why do you want the canvas to be transparent? What is behind the canvas that you want to see? looks like that but it can vary from application to application. aka., the JS clock time picker https://prnt.sc/VRmgWFKP5Omk |