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
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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};
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||