You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

348 lines
8.2 KiB

# coding:utf-8
from enum import Enum
from typing import Union
from PySide6.QtXml import QDomDocument
from PySide6.QtCore import QRectF, Qt, QFile, QObject
from PySide6.QtGui import QIcon, QIconEngine, QAction
from PySide6.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
def toQIcon(icon: Union[QIcon, FluentIconBase, str]) -> QIcon:
""" convet `icon` to `QIcon` """
if isinstance(icon, str):
return QIcon(icon)
if isinstance(icon, FluentIconBase):
return icon.icon()
return icon
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)