Files
fcco-Converter/fcco_converter/icons.py
T

305 lines
9.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
fcco-Converter V2 Icon-Modul
Erzeugt saubere, professionelle Icons programmatisch mit PIL.
Keine externen Bilddateien nötig alle Icons sind Vektorgrafiken,
die in beliebiger Größe gerendert werden können.
"""
from PIL import Image, ImageDraw
import customtkinter as ctk
def _create_canvas(size: int = 24, bg: str | None = None) -> tuple[Image.Image, ImageDraw.ImageDraw]:
"""Erstellt eine transparente Zeichenfläche."""
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img, "RGBA")
return img, draw
def _hex_to_rgba(hex_color: str, alpha: int = 255) -> tuple[int, int, int, int]:
"""Konvertiert einen Hex-Farbwert zu RGBA."""
hex_color = hex_color.lstrip("#")
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
return (r, g, b, alpha)
def make_ctk_image(light_img: Image.Image, dark_img: Image.Image | None = None, size: tuple[int, int] = (20, 20)) -> ctk.CTkImage:
"""Erzeugt ein CTkImage aus PIL-Bildern."""
return ctk.CTkImage(
light_image=light_img,
dark_image=dark_img or light_img,
size=size,
)
# =========================================================================
# Icon-Generatoren
# =========================================================================
def icon_folder(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Ordner-Icon."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
# Ordner-Körper
draw.rounded_rectangle(
[s * 0.08, s * 0.25, s * 0.92, s * 0.85],
radius=s * 0.08, fill=c
)
# Tab oben
draw.rounded_rectangle(
[s * 0.08, s * 0.15, s * 0.45, s * 0.35],
radius=s * 0.06, fill=c
)
return img
def icon_file_add(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Datei-hinzufügen-Icon."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(1, int(s * 0.08))
# Dokument
draw.rounded_rectangle(
[s * 0.17, s * 0.04, s * 0.71, s * 0.96],
radius=s * 0.06, outline=c, width=lw
)
# Plus-Zeichen
cx, cy = s * 0.44, s * 0.50
arm = s * 0.15
draw.line([(cx - arm, cy), (cx + arm, cy)], fill=c, width=lw)
draw.line([(cx, cy - arm), (cx, cy + arm)], fill=c, width=lw)
return img
def icon_play(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Play/Start-Icon (Dreieck)."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
# Dreieck (nach rechts zeigend)
points = [
(s * 0.25, s * 0.15),
(s * 0.80, s * 0.50),
(s * 0.25, s * 0.85),
]
draw.polygon(points, fill=c)
return img
def icon_settings(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Zahnrad/Einstellungen-Icon."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(1, int(s * 0.08))
cx, cy = s * 0.5, s * 0.5
# Äußerer Ring
r_outer = s * 0.38
draw.ellipse(
[cx - r_outer, cy - r_outer, cx + r_outer, cy + r_outer],
outline=c, width=lw
)
# Innerer Punkt
r_inner = s * 0.12
draw.ellipse(
[cx - r_inner, cy - r_inner, cx + r_inner, cy + r_inner],
fill=c
)
# Zähne (6 Stück als kurze Linien)
import math
for i in range(6):
angle = math.pi * i / 3
x1 = cx + r_outer * math.cos(angle)
y1 = cy + r_outer * math.sin(angle)
x2 = cx + (r_outer + s * 0.12) * math.cos(angle)
y2 = cy + (r_outer + s * 0.12) * math.sin(angle)
draw.line([(x1, y1), (x2, y2)], fill=c, width=lw)
return img
def icon_refresh(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Refresh/Update-Icon (kreisförmiger Pfeil)."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(1, int(s * 0.08))
# Kreisbogen
draw.arc(
[s * 0.15, s * 0.15, s * 0.85, s * 0.85],
start=30, end=330, fill=c, width=lw
)
# Pfeilspitze
import math
angle = math.radians(30)
cx, cy = s * 0.5, s * 0.5
r = s * 0.35
tip_x = cx + r * math.cos(angle)
tip_y = cy - r * math.sin(angle)
arrow_size = s * 0.15
points = [
(tip_x, tip_y),
(tip_x + arrow_size, tip_y + arrow_size * 0.3),
(tip_x + arrow_size * 0.3, tip_y + arrow_size),
]
draw.polygon(points, fill=c)
return img
def icon_save(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Speichern-Icon (Diskette)."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(1, int(s * 0.08))
# Äußeres Rechteck
draw.rounded_rectangle(
[s * 0.10, s * 0.10, s * 0.90, s * 0.90],
radius=s * 0.06, outline=c, width=lw
)
# Inneres Rechteck (Etikett)
draw.rectangle(
[s * 0.30, s * 0.55, s * 0.70, s * 0.85],
outline=c, width=lw
)
# Oberer Bereich (Schieber)
draw.rectangle(
[s * 0.30, s * 0.10, s * 0.70, s * 0.38],
fill=c
)
return img
def icon_browse(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Durchsuchen-Icon (Lupe)."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(1, int(s * 0.08))
# Kreis
draw.ellipse(
[s * 0.12, s * 0.08, s * 0.62, s * 0.58],
outline=c, width=lw
)
# Griff
draw.line(
[(s * 0.55, s * 0.55), (s * 0.88, s * 0.88)],
fill=c, width=max(2, int(s * 0.10))
)
return img
def icon_check(color: str = "#34d399", size: int = 24) -> Image.Image:
"""Häkchen-Icon."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(2, int(s * 0.10))
draw.line(
[(s * 0.18, s * 0.52), (s * 0.40, s * 0.75), (s * 0.82, s * 0.25)],
fill=c, width=lw, joint="curve"
)
return img
def icon_cross(color: str = "#f87171", size: int = 24) -> Image.Image:
"""Kreuz/Fehler-Icon."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(2, int(s * 0.10))
draw.line([(s * 0.22, s * 0.22), (s * 0.78, s * 0.78)], fill=c, width=lw)
draw.line([(s * 0.78, s * 0.22), (s * 0.22, s * 0.78)], fill=c, width=lw)
return img
def icon_music(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Musik/Audio-Icon (Notensymbol)."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(1, int(s * 0.08))
# Notenhals
draw.line([(s * 0.60, s * 0.15), (s * 0.60, s * 0.70)], fill=c, width=lw)
# Notenkopf
draw.ellipse(
[s * 0.35, s * 0.60, s * 0.65, s * 0.85],
fill=c
)
# Fähnchen
draw.arc(
[s * 0.55, s * 0.12, s * 0.88, s * 0.45],
start=270, end=90, fill=c, width=lw
)
return img
def icon_info(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Info-Icon (i im Kreis)."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(1, int(s * 0.08))
# Kreis
draw.ellipse(
[s * 0.10, s * 0.10, s * 0.90, s * 0.90],
outline=c, width=lw
)
# Punkt oben
draw.ellipse(
[s * 0.44, s * 0.22, s * 0.56, s * 0.34],
fill=c
)
# Strich unten
draw.line([(s * 0.50, s * 0.42), (s * 0.50, s * 0.75)], fill=c, width=lw)
return img
def icon_download(color: str = "#e2e8f0", size: int = 24) -> Image.Image:
"""Download-Icon (Pfeil nach unten)."""
img, draw = _create_canvas(size)
c = _hex_to_rgba(color)
s = size
lw = max(1, int(s * 0.08))
# Vertikale Linie
draw.line([(s * 0.50, s * 0.12), (s * 0.50, s * 0.62)], fill=c, width=lw)
# Pfeilspitze
draw.line([(s * 0.30, s * 0.48), (s * 0.50, s * 0.68)], fill=c, width=lw)
draw.line([(s * 0.70, s * 0.48), (s * 0.50, s * 0.68)], fill=c, width=lw)
# Unterstrich
draw.line([(s * 0.20, s * 0.85), (s * 0.80, s * 0.85)], fill=c, width=lw)
return img
# =========================================================================
# Vorgefertigte CTkImage-Sets (Light + Dark)
# =========================================================================
class IconSet:
"""Hält alle Icons als CTkImage-Instanzen.
Muss nach CTk-Initialisierung erstellt werden.
"""
def __init__(self, size: tuple[int, int] = (18, 18)):
s = size[0]
light = "#3f3f46" # Für hellen Modus
dark = "#e2e8f0" # Für dunklen Modus
accent = "#8b7cf6"
success = "#34d399"
error = "#f87171"
self.folder = make_ctk_image(icon_folder(light, s), icon_folder(dark, s), size)
self.file_add = make_ctk_image(icon_file_add(light, s), icon_file_add(dark, s), size)
self.play = make_ctk_image(icon_play(light, s), icon_play(dark, s), size)
self.settings = make_ctk_image(icon_settings(light, s), icon_settings(dark, s), size)
self.refresh = make_ctk_image(icon_refresh(light, s), icon_refresh(dark, s), size)
self.save = make_ctk_image(icon_save(light, s), icon_save(dark, s), size)
self.browse = make_ctk_image(icon_browse(light, s), icon_browse(dark, s), size)
self.check = make_ctk_image(icon_check(success, s), icon_check(success, s), size)
self.cross = make_ctk_image(icon_cross(error, s), icon_cross(error, s), size)
self.music = make_ctk_image(icon_music(light, s), icon_music(dark, s), size)
self.info = make_ctk_image(icon_info(light, s), icon_info(dark, s), size)
self.download = make_ctk_image(icon_download(light, s), icon_download(dark, s), size)