Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DrawEngine changes for smoother rendering in Linux platforms #2646

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions customtkinter/windows/widgets/core_rendering/draw_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,39 @@ def __init__(self, canvas: CTkCanvas):
self._canvas = canvas
self._round_width_to_even_numbers: bool = True
self._round_height_to_even_numbers: bool = True
if sys.platform.startswith("linux"):
self.preferred_drawing_method = "circle_shapes"

def set_round_to_even_numbers(self, round_width_to_even_numbers: bool = True, round_height_to_even_numbers: bool = True):
self._round_width_to_even_numbers: bool = round_width_to_even_numbers
self._round_height_to_even_numbers: bool = round_height_to_even_numbers

def __calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]:
# optimize for drawing with polygon shapes
"""Optimize corner radius based on the preferred drawing method and platform."""

# Optimize for drawing with polygon shapes
if self.preferred_drawing_method == "polygon_shapes":
if sys.platform == "darwin":
return user_corner_radius
return user_corner_radius # Direct use for macOS
elif sys.platform.startswith("linux"):
return round(user_corner_radius * 1.25) / 1.25 # Fine-tuned rounding for smoother rendering on Linux
else:
return round(user_corner_radius)
return round(user_corner_radius) # Default for other platforms

# optimize for drawing with antialiased font shapes
# Optimize for drawing with antialiased font shapes
elif self.preferred_drawing_method == "font_shapes":
return round(user_corner_radius)

# optimize for drawing with circles and rects
# Optimize for drawing with circles and rects
elif self.preferred_drawing_method == "circle_shapes":
user_corner_radius = 0.5 * round(user_corner_radius / 0.5) # round to 0.5 steps
if sys.platform.startswith("linux"):
# Allow finer increments for smoother Linux rendering
user_corner_radius = 0.25 * round(user_corner_radius / 0.25)
else:
# Default behavior for other platforms
user_corner_radius = 0.5 * round(user_corner_radius / 0.5)

# make sure the value is always with .5 at the end for smoother corners
# Ensure the value always ends in .5 for smoother corners
if user_corner_radius == 0:
return 0
elif user_corner_radius % 1 == 0:
Expand Down Expand Up @@ -325,6 +336,11 @@ def __draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, c
def __draw_rounded_rect_with_border_circle_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool:
requires_recoloring = False

# Adjust shapes for Linux rendering
if sys.platform.startswith("linux"):
corner_radius = max(corner_radius - 0.5, 0)
border_width = max(border_width - 0.2, 0)

# border button parts
if border_width > 0:
if corner_radius > 0:
Expand Down Expand Up @@ -1079,7 +1095,7 @@ def __draw_rounded_scrollbar_polygon_shapes(self, width: int, height: int, corne
corner_radius + (width - 2 * corner_radius) * start_value, corner_radius,
corner_radius + (width - 2 * corner_radius) * end_value, corner_radius,
corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius,
corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius,)
corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, )

self._canvas.itemconfig("scrollbar_polygon_1", width=inner_corner_radius * 2)

Expand Down
55 changes: 41 additions & 14 deletions customtkinter/windows/widgets/font/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,29 @@
import os
import shutil
from typing import Union
import subprocess


class FontManager:

linux_font_path = "~/.fonts/"
linux_font_paths = [
os.path.expanduser("~/.fonts/"), # Default path
os.path.expanduser("~/.local/share/fonts/"), # Fallback path
]

@classmethod
def init_font_manager(cls):
# Linux
"""
Initialize font manager by ensuring the required font directories exist.
"""
if sys.platform.startswith("linux"):
try:
if not os.path.isdir(os.path.expanduser(cls.linux_font_path)):
os.mkdir(os.path.expanduser(cls.linux_font_path))
for path in cls.linux_font_paths:
if not os.path.isdir(path):
os.makedirs(path, exist_ok=True)
return True
except Exception as err:
sys.stderr.write("FontManager error: " + str(err) + "\n")
sys.stderr.write(f"FontManager error (init): {err}\n")
return False

# other platforms
else:
return True

Expand Down Expand Up @@ -48,19 +52,42 @@ def windows_load_font(cls, font_path: Union[str, bytes], private: bool = True, e

@classmethod
def load_font(cls, font_path: str) -> bool:
"""
Load a font into the system for different platforms.
"""
# Check if the font file exists
if not os.path.isfile(font_path):
sys.stderr.write(f"FontManager error: Font file '{font_path}' does not exist.\n")
return False

# Windows
if sys.platform.startswith("win"):
return cls.windows_load_font(font_path, private=True, enumerable=False)

# Linux
elif sys.platform.startswith("linux"):
try:
shutil.copy(font_path, os.path.expanduser(cls.linux_font_path))
return True
except Exception as err:
sys.stderr.write("FontManager error: " + str(err) + "\n")
return False
for path in cls.linux_font_paths:
try:
dest_path = os.path.join(path, os.path.basename(font_path))
if not os.path.isfile(dest_path): # Avoid redundant copying
shutil.copy(font_path, dest_path)
cls.refresh_font_cache(path) # Refresh the font cache
return True
except Exception as err:
sys.stderr.write(f"FontManager error (Linux): {err}\n")
return False

# macOS and others
else:
sys.stderr.write("FontManager warning: Font loading is not supported on this platform.\n")
return False

@staticmethod
def refresh_font_cache(directory: str):
"""
Refresh the font cache on Linux using fc-cache.
"""
try:
subprocess.run(["fc-cache", "-fv", directory], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except Exception as err:
sys.stderr.write(f"FontManager error (fc-cache): {err}\n")