Tkinter-Designer
Tkinter-Designer copied to clipboard
Tkinter-Designer Support for Border Radius
Border Radius (corner radius) is an important feature for modern UI buttons. It is possible to implement pill buttons natively, using a modification of code like this: https://stackoverflow.com/questions/42579927/rounded-button-tkinter-python
Thanks for creating an Issue, We will be eager to solve it.
You can create it using PIL, but it makes the code more complicated. To keep it simple we use native tkinter code with images. It gives the ability to have rounded buttons even with base tkinter.
Here's a code -
Hah, yeah, I understand -- I opened it as an issue b/c while it does complicate the code, I think dynamically generated buttons are a worthwhile feature to incorporate. I managed to do it on my own, but when I start collaborating with non-technical contributors, it's valuable to be able to do so using this tool. Below was my implementation.
from tkinter import *
import tkinter as tk
import tkinter.font as font
class RoundedButton(tk.Canvas):
def __init__(self, parent, border_radius, padding, color, text='', command=None):
tk.Canvas.__init__(self, parent, borderwidth=0,
relief="flat", highlightthickness=0, bg=parent["bg"])
self.command = command
font_size = 16
self.font = font.Font(size=font_size, family='Helvetica', weight="bold")
self.id = None
height = font_size+(2*padding)
width = self.font.measure(text)+(4*padding)
width = width if width >= 80 else 80
if border_radius > 0.5*width:
print("Error: border_radius is greater than width.")
return None
if border_radius > 0.5*height:
print("Error: border_radius is greater than height.")
return None
rad = 2*border_radius
def shape():
self.create_arc((0, rad, rad, 0),
start=90, extent=90, fill=color, outline=color)
self.create_arc((width-rad, 0, width,
rad), start=0, extent=90, fill=color, outline=color)
self.create_arc((width, height-rad, width-rad,
height), start=270, extent=90, fill=color, outline=color)
self.create_arc((0, height-rad, rad, height), start=180, extent=90, fill=color, outline=color)
return self.create_polygon((0, height-border_radius, 0, border_radius, border_radius, 0, width-border_radius, 0, width,
border_radius, width, height-border_radius, width-border_radius, height, border_radius, height), fill=color, outline=color)
id = shape()
(x0, y0, x1, y1) = self.bbox("all")
width = (x1-x0)
height = (y1-y0)
self.configure(width=width, height=height)
self.create_text(width/2, height/2,text=text, fill='white', font= self.font)
self.bind("<ButtonPress-1>", self._on_press)
self.bind("<ButtonRelease-1>", self._on_release)
def _on_press(self, event):
self.configure(relief="sunken")
def _on_release(self, event):
self.configure(relief="raised")
if self.command is not None:
self.command()
And in a separate file:
from views.utils.rounded_button import RoundedButton
# ...
self.start_btn = RoundedButton(
self.controls, border_radius=3, padding=8, color="#16A765", text='Start Camera')
self.start_btn.pack( side='left')
Well, it looks great. I think we can incorporate it in Tkinter Designer. But I created dynamic text button in tkinter designer you can see it in the experimental branch.
Unlike master branch, where the whole button's image is downloaded and rendered. In the experimental the buttons background is separated from the text and only the button background image is downloaded and the text is added dynamically. But it doesn't have a documentation yet. So you wont be able to use it directly.
I'll share a link to Figma file in which I have added that functionality.
Also, I might reconsider. Your implementation could be better than our's. Let me see.
I am going to bed rn, I'll catch up tomorrow.
Sounds great! Thanks :) I've got some magic numbers in this source that I put in there just for the sake of MVP, but was planning a refactor for later. Between the two of us, I think we can probably beef up the source to make it more robust, I just didn't have time for it right now.
Yeah, definitely. That would be great. I'll leave this issue open...
Made some PEP-8 adjustments to your implementation, @afogel. This just makes the code consistent with the project's current formatting:
from tkinter import *
import tkinter as tk
import tkinter.font as font
class RoundedButton(tk.Canvas):
def __init__(self, parent, border_radius, padding,
color, text = "", command = None):
tk.Canvas.__init__(self, parent, borderwidth = 0, relief = "flat",
highlightthickness = 0, bg = parent["bg"])
self.color = color
self.command = command
self.id = None
font_size = 16
height = font_size + (2 * padding)
width = self.font.measure(text) + (4 * padding)
width = width if width >= 80 else 80
self.font = font.Font(size = font_size, family = "Helvetica",
weight = "bold")
if (
border_radius > 0.5 * width
or border_radius > 0.5 * height
):
raise ValueError("Error: total border_radius must not "
"exceed width or height.")
self.id = self._shape(border_radius)
x0, y0, x1, y1 = self.bbox("all")
self.width = (x1 - x0)
self.height = (y1 - y0)
self.configure(width = self.width, height = self.height)
self.create_text(self.width / 2, self.height / 2,text = text,
fill = 'white', font = self.font)
self.bind("<ButtonPress-1>", self._on_press)
self.bind("<ButtonRelease-1>", self._on_release)
def _on_press(self, event):
self.configure(relief = "sunken")
def _on_release(self, event):
self.configure(relief = "raised")
if self.command is not None:
self.command()
def _shape(self, border_radius):
radius = 2 * border_radius
self.create_arc((0, radius, radius, 0),
start = 90, extent = 90,
fill = self.color, outline = self.color)
self.create_arc((self.width - radius, 0, self.width,
radius), start = 0, extent = 90,
fill = self.color, outline = self.color)
self.create_arc((self.width, self.height - radius,
self.width - radius, self.height),
start = 270, extent = 90, fill = self.color,
outline = self.color)
self.create_arc((0, self.height - radius, radius, self.height),
start = 180, extent = 90, fill = self.color,
outline = self.color)
return self.create_polygon(
(
0, self.height - border_radius,
0, border_radius, border_radius,
0, self.width - border_radius, 0,
self.width, border_radius, self.width,
self.height - border_radius,
self.width - border_radius,
self.height, border_radius,
self.height
),
fill = self.color,
outline = self.color)
Hey @afogel, I have created three templates which are compatible with the experimental branch.
Visit Here - Check Calculator and Two forms. (V1 file not supported)
You can select any of it and then click on duplicate. Duplicate = Fork. And then clone the experimental branch and test with that file. It creates the Buttons separated by their background and text.
Also I am thinking something crazy about making the buttons colour dynamic by using Pixel replacement or hue adjustment. I don't know if it's possible but seem's fun 😄 .
I am also thinking on how to incorporate your suggestion into Tkinter designer. Let's see.
Hey @afogel ,
Did you find any way to make the buttons be rounded and transparent ? I mean a simpler way than above :)
not yet! I've had to pivot projects, so my desktop development for tkinter is on hold right now -- I'll probably be turning back to it in a few weeks.
not yet! I've had to pivot projects, so my desktop development for tkinter is on hold right now -- I'll probably be turning back to it in a few weeks.
Sure thing !
Is this still pending? The designer interface, itself seems to be using rounded corners, but mine are still rendering with artifacts or with no background behind the border radius.
Hey @adammpkins the designer used Images where there are rounded corners. Naming the elements which are rounded to -> "Image" might solve the problem. Same goes with the background.