feat: add white outline SVG icons for EU aesthetic

Added 12 custom SVG icons:
- search.svg (magnifying glass)
- calculator.svg (minimal calc)
- music.svg (music notes)
- globe.svg (world/web)
- camera.svg (camera/ocr)
- skills.svg (chart/pie)
- file.svg (document)
- trash.svg (delete)
- external.svg (external link)
- close.svg (X)
- check.svg (checkmark)
- settings.svg (gear)

All icons:
- White stroke on transparent background
- Minimal outline style
- 24x24 viewbox
- Match Entropia Universe sci-fi aesthetic

Also updated:
- icon_manager.py with SVG support
- requirements.txt with PyQt6-Qt6-SVG
This commit is contained in:
LemonNexus 2026-02-13 13:45:53 +00:00
parent d3d69c41cc
commit 3ea24f4989
14 changed files with 148 additions and 135 deletions

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="2" width="16" height="20" rx="2"></rect>
<line x1="8" y1="6" x2="16" y2="6"></line>
<line x1="8" y1="10" x2="16" y2="10"></line>
<line x1="8" y1="14" x2="16" y2="14"></line>
<line x1="8" y1="18" x2="12" y2="18"></line>
</svg>

After

Width:  |  Height:  |  Size: 402 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path>
<circle cx="12" cy="13" r="4"></circle>
</svg>

After

Width:  |  Height:  |  Size: 302 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 206 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>

After

Width:  |  Height:  |  Size: 250 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>

After

Width:  |  Height:  |  Size: 330 B

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 425 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="2" y1="12" x2="22" y2="12"></line>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>

After

Width:  |  Height:  |  Size: 359 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 18V5l12-2v13"></path>
<circle cx="6" cy="18" r="3"></circle>
<circle cx="18" cy="16" r="3"></circle>
</svg>

After

Width:  |  Height:  |  Size: 277 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>

After

Width:  |  Height:  |  Size: 254 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>

After

Width:  |  Height:  |  Size: 955 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21.21 15.89A10 10 0 1 1 8 2.83"></path>
<path d="M22 12A10 10 0 0 0 12 2v10z"></path>
</svg>

After

Width:  |  Height:  |  Size: 258 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>

After

Width:  |  Height:  |  Size: 303 B

View File

@ -2,160 +2,116 @@
EU-Utility - Icon Manager EU-Utility - Icon Manager
Loads and manages white/frosted style icons for EU aesthetic. Loads and manages white/frosted style icons for EU aesthetic.
Icons should be: Icons are SVG format - scalable and crisp at any size.
- White/light colored
- Frosted/glass effect
- 24x24 or 32x32 PNG with transparency
- Match Entropia Universe sci-fi style
Recommended sources:
- flaticon.com (search: "white icon", "frosted glass", "minimal")
- phosphoricons.com (Phosphor icons - great frosted look)
- heroicons.com (outline style)
""" """
from pathlib import Path from pathlib import Path
from PyQt6.QtGui import QIcon, QPixmap, QColor from PyQt6.QtGui import QIcon, QPixmap, QColor
from PyQt6.QtCore import QSize from PyQt6.QtCore import QSize, Qt
from PyQt6.QtSvg import QSvgRenderer
class IconManager: class IconManager:
"""Manage application icons with EU styling.""" """Manage application icons with EU styling."""
# Icon names mapping _instance = None
ICONS = {
# Navigation def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, icons_dir="assets/icons"):
if self._initialized:
return
self.icons_dir = Path(icons_dir)
self.svg_icons = {}
self._load_svg_icons()
self._initialized = True
def _load_svg_icons(self):
"""Load SVG icons."""
if not self.icons_dir.exists():
return
for icon_file in self.icons_dir.glob("*.svg"):
name = icon_file.stem
self.svg_icons[name] = str(icon_file)
def get(self, name, size=24, color="white"):
"""Get an icon by name as QIcon."""
# Check for SVG icon first
if name in self.svg_icons:
return self._svg_to_icon(self.svg_icons[name], size, color)
# Return None if not found - caller should use fallback
return None
def _svg_to_icon(self, svg_path, size, color):
"""Convert SVG to QIcon."""
renderer = QSvgRenderer(svg_path)
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPixmap(pixmap)
renderer.render(painter)
return QIcon(pixmap)
def get_pixmap(self, name, size=24):
"""Get icon as QPixmap."""
if name in self.svg_icons:
renderer = QSvgRenderer(self.svg_icons[name])
pixmap = QPixmap(size, size)
pixmap.fill(Qt.GlobalColor.transparent)
from PyQt6.QtGui import QPainter
painter = QPainter(pixmap)
renderer.render(painter)
painter.end()
return pixmap
return None
def get_emoji(self, name):
"""Get emoji fallback for icon."""
return self.EMOJIS.get(name, '')
# Emoji fallbacks
EMOJIS = {
'search': '🔍', 'search': '🔍',
'calculator': '🧮', 'calculator': '🧮',
'music': '🎵', 'music': '🎵',
'globe': '🌐', 'globe': '🌐',
'skills': '📊', 'skills': '📊',
'camera': '📷', 'camera': '📷',
# Actions
'scan': '📸',
'save': '💾',
'export': '📤',
'copy': '📋',
'close': '', 'close': '',
'check': '',
'settings': '⚙️', 'settings': '⚙️',
'file': '📄',
# Status 'trash': '🗑️',
'success': '', 'external': '',
'error': '',
'warning': '⚠️',
'loading': '',
# EU Specific
'ped': 'P',
'esi': '💉',
'weapon': '🔫',
'armor': '🛡️',
'mob': '👾',
'loot': '🎁',
} }
def __init__(self, icons_dir="assets/icons"):
self.icons_dir = Path(icons_dir)
self.custom_icons = {}
self._load_custom_icons()
def _load_custom_icons(self):
"""Load custom PNG icons if they exist."""
if not self.icons_dir.exists():
return
for icon_file in self.icons_dir.glob("*.png"):
name = icon_file.stem
self.custom_icons[name] = str(icon_file)
def get(self, name, size=24):
"""Get an icon by name."""
# Check for custom icon first
if name in self.custom_icons:
icon = QIcon(self.custom_icons[name])
# Apply white tint if needed
return icon
# Return emoji as fallback
return self.ICONS.get(name, '')
def get_emoji_label(self, name, size=20):
"""Get an emoji styled as an icon label."""
emoji = self.ICONS.get(name, '')
return f'<span style="font-size: {size}px;">{emoji}</span>'
def tint_icon(self, icon_path, color='#ffffff'):
"""Tint an icon to match EU aesthetic."""
pixmap = QPixmap(icon_path)
# Create tinted version
tinted = QPixmap(pixmap.size())
tinted.fill(QColor(color))
# Apply original as mask
painter = QPainter(tinted)
painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
painter.drawPixmap(0, 0, pixmap)
painter.end()
return QIcon(tinted)
# EU Styling Constants # Singleton instance
EU_STYLES = { _icons = None
'floating_icon': """
QLabel { def get_icon_manager():
font-size: 20px; """Get the singleton icon manager."""
background-color: rgba(20, 25, 35, 220); global _icons
border-radius: 8px; if _icons is None:
border: 1px solid rgba(100, 150, 200, 80); _icons = IconManager()
color: white; return _icons
}
""",
'floating_icon_hover': """
QLabel {
font-size: 20px;
background-color: rgba(40, 55, 75, 240);
border-radius: 8px;
border: 1px solid rgba(100, 180, 255, 150);
color: white;
}
""",
'plugin_button': """
QPushButton {
background-color: rgba(255, 255, 255, 15);
color: white;
font-size: 20px;
border: none;
border-radius: 22px;
}
QPushButton:hover {
background-color: rgba(255, 255, 255, 30);
}
QPushButton:checked {
background-color: #4a9eff;
}
""",
}
def get_frosted_button_style(color='#4a9eff'): def get_icon(name, size=24):
"""Get frosted glass button style.""" """Quick access to get an icon."""
return f""" return get_icon_manager().get(name, size)
QPushButton {{
background-color: rgba(255, 255, 255, 15);
color: white; def get_pixmap(name, size=24):
border: 1px solid rgba(255, 255, 255, 30); """Quick access to get a pixmap."""
border-radius: 12px; return get_icon_manager().get_pixmap(name, size)
padding: 10px 20px;
font-weight: 500;
}}
QPushButton:hover {{
background-color: rgba(255, 255, 255, 25);
border: 1px solid rgba(255, 255, 255, 50);
}}
QPushButton:pressed {{
background-color: {color};
}}
"""

View File

@ -1,5 +1,6 @@
# Core dependencies # Core dependencies
PyQt6>=6.4.0 PyQt6>=6.4.0
PyQt6-Qt6-SVG # SVG support for icons
keyboard>=0.13.5 keyboard>=0.13.5
# OCR and Image Processing (for Game Reader and Skill Scanner) # OCR and Image Processing (for Game Reader and Skill Scanner)