# coding:utf-8 from enum import Enum from typing import Union from PyQt5 import QtGui from PyQt5.QtXml import QDomDocument from PyQt5.QtCore import QRectF, Qt, QFile, QObject from PyQt5.QtGui import QIcon, QIconEngine from PyQt5.QtWidgets import QAction from PyQt5.QtSvg import QSvgRenderer from .config import isDarkTheme, Theme from .overload import singledispatchmethod class MenuIconEngine(QIconEngine): def __init__(self, icon): super().__init__() self.icon = icon def paint(self, painter, rect, mode, state): painter.save() if mode == QIcon.Disabled: painter.setOpacity(0.5) elif mode == QIcon.Selected: painter.setOpacity(0.7) # change icon color according to the theme icon = self.icon if isinstance(self.icon, Icon): icon = self.icon.fluentIcon.icon() # prevent the left side of the icon from being cropped rect.adjust(-1, 0, 0, 0) icon.paint(painter, rect, Qt.AlignHCenter, QIcon.Normal, state) painter.restore() def getIconColor(theme=Theme.AUTO, reverse=False): """ get the color of icon based on theme """ if not reverse: lc, dc = "black", "white" else: lc, dc = "white", "black" if theme == Theme.AUTO: color = dc if isDarkTheme() else lc else: color = dc if theme == Theme.DARK else lc return color def drawSvgIcon(icon, painter, rect): """ draw svg icon Parameters ---------- icon: str | bytes | QByteArray the path or code of svg icon painter: QPainter painter rect: QRect | QRectF the rect to render icon """ renderer = QSvgRenderer(icon) renderer.render(painter, QRectF(rect)) def writeSvg(iconPath: str, indexes=None, **attributes): """ write svg with specified attributes Parameters ---------- iconPath: str svg icon path indexes: List[int] the path to be filled **attributes: the attributes of path Returns ------- svg: str svg code """ if not iconPath.lower().endswith('.svg'): return "" f = QFile(iconPath) f.open(QFile.ReadOnly) dom = QDomDocument() dom.setContent(f.readAll()) f.close() # change the color of each path pathNodes = dom.elementsByTagName('path') indexes = range(pathNodes.length()) if not indexes else indexes for i in indexes: element = pathNodes.at(i).toElement() for k, v in attributes.items(): element.setAttribute(k, v) return dom.toString() def drawIcon(icon, painter, rect, **attributes): """ draw icon Parameters ---------- icon: str | QIcon | FluentIconBaseBase the icon to be drawn painter: QPainter painter rect: QRect | QRectF the rect to render icon **attribute: the attribute of svg icon """ if isinstance(icon, FluentIconBase): icon.render(painter, rect, **attributes) else: icon = QIcon(icon) rect = QRectF(rect).toRect() image = icon.pixmap(rect.width(), rect.height()) painter.drawPixmap(rect, image) class FluentIconBase: """ Fluent icon base class """ def path(self, theme=Theme.AUTO): """ get the path of icon Parameters ---------- theme: Theme the theme of icon * `Theme.Light`: black icon * `Theme.DARK`: white icon * `Theme.AUTO`: icon color depends on `config.theme` """ raise NotImplementedError def icon(self, theme=Theme.AUTO): """ create an fluent icon Parameters ---------- theme: Theme the theme of icon * `Theme.Light`: black icon * `Theme.DARK`: white icon * `Theme.AUTO`: icon color depends on `config.theme` """ return QIcon(self.path(theme)) def render(self, painter, rect, theme=Theme.AUTO, indexes=None, **attributes): """ draw svg icon Parameters ---------- painter: QPainter painter rect: QRect | QRectF the rect to render icon theme: Theme the theme of icon * `Theme.Light`: black icon * `Theme.DARK`: white icon * `Theme.AUTO`: icon color depends on `config.theme` indexes: List[int] the svg path to be modified **attributes: the attributes of modified path """ if attributes: svg = writeSvg(self.path(theme), indexes, **attributes).encode() else: svg = self.path(theme) drawSvgIcon(svg, painter, rect) class FluentIcon(FluentIconBase, Enum): """ Fluent icon """ ADD = "Add" CUT = "Cut" PIN = "Pin" TAG = "Tag" CHAT = "Chat" COPY = "Copy" CODE = "Code" EDIT = "Edit" FONT = "Font" HELP = "Help" HIDE = "Hide" HOME = "Home" INFO = "Info" LINK = "Link" MAIL = "Mail" MENU = "Menu" MORE = "More" SAVE = "Save" SEND = "Send" SYNC = "Sync" VIEW = "View" ZOOM = "Zoom" ALBUM = "Album" BRUSH = "Brush" CLOSE = "Close" EMBED = "Embed" GLOBE = "Globe" HEART = "Heart" MEDIA = "Media" MOVIE = "Movie" MUSIC = "Music" PASTE = "Paste" PHOTO = "Photo" PHONE = "Phone" PRINT = "Print" SHARE = "Share" UNPIN = "Unpin" VIDEO = "Video" ACCEPT = "Accept" CAMERA = "Camera" CANCEL = "Cancel" DELETE = "Delete" FOLDER = "Folder" SCROLL = "Scroll" LAYOUT = "Layout" GITHUB = "GitHub" UPDATE = "Update" RETURN = "Return" RINGER = "Ringer" SEARCH = "Search" SAVE_AS = "SaveAs" ZOOM_IN = "ZoomIn" HISTORY = "History" SETTING = "Setting" PALETTE = "Palette" MESSAGE = "Message" ZOOM_OUT = "ZoomOut" FEEDBACK = "Feedback" MINIMIZE = "Minimize" CHECKBOX = "CheckBox" DOCUMENT = "Document" LANGUAGE = "Language" DOWNLOAD = "Download" QUESTION = "Question" DATE_TIME = "DateTime" SEND_FILL = "SendFill" COMPLETED = "Completed" CONSTRACT = "Constract" ALIGNMENT = "Alignment" BOOK_SHELF = "BookShelf" HIGHTLIGHT = "Highlight" FOLDER_ADD = "FolderAdd" PENCIL_INK = "PencilInk" ZIP_FOLDER = "ZipFolder" BASKETBALL = "Basketball" MICROPHONE = "Microphone" ARROW_DOWN = "ChevronDown" TRANSPARENT = "Transparent" MUSIC_FOLDER = "MusicFolder" CARE_UP_SOLID = "CareUpSolid" CHEVRON_RIGHT = "ChevronRight" CARE_DOWN_SOLID = "CareDownSolid" CARE_LEFT_SOLID = "CareLeftSolid" BACKGROUND_FILL = "BackgroundColor" CARE_RIGHT_SOLID = "CareRightSolid" EMOJI_TAB_SYMBOLS = "EmojiTabSymbols" def path(self, theme=Theme.AUTO): return f':/qfluentwidgets/images/icons/{self.value}_{getIconColor(theme)}.svg' class Icon(QIcon): def __init__(self, fluentIcon: FluentIcon): super().__init__(fluentIcon.path()) self.fluentIcon = fluentIcon class Action(QAction): """ Fluent action """ @singledispatchmethod def __init__(self, parent: QObject = None, **kwargs): super().__init__(parent, **kwargs) self.fluentIcon = None @__init__.register def _(self, text: str, parent: QObject = None, **kwargs): super().__init__(text, parent, **kwargs) self.fluentIcon = None @__init__.register def _(self, icon: QIcon, text: str, parent: QObject = None, **kwargs): super().__init__(icon, text, parent, **kwargs) self.fluentIcon = None @__init__.register def _(self, icon: FluentIconBase, text: str, parent: QObject = None, **kwargs): super().__init__(icon.icon(), text, parent, **kwargs) self.fluentIcon = icon def icon(self) -> QIcon: if self.fluentIcon: return Icon(self.fluentIcon) return super().icon() def setIcon(self, icon: Union[FluentIconBase, QIcon]): if isinstance(icon, FluentIconBase): self.fluentIcon = icon icon = icon.icon() super().setIcon(icon)