feat: add QIcon backed by iconify (#209)

* feat: add iconify

* update docs

* update docs

* rearrange

* update example

* update test deps

* importorskip

* Update src/superqt/iconify/__init__.py

Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com>

* Update src/superqt/iconify/__init__.py

Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com>

* merge

* change test

* bump dep

* change doc

* Update src/superqt/iconify/__init__.py

Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com>

* pragma and bump version

---------

Co-authored-by: Grzegorz Bokota <bokota+github@gmail.com>
This commit is contained in:
Talley Lambert
2023-10-09 09:20:42 -04:00
committed by GitHub
parent 65a4a6e17c
commit a5740f0109
6 changed files with 116 additions and 1 deletions

14
examples/iconify.py Normal file
View File

@@ -0,0 +1,14 @@
from qtpy.QtCore import QSize
from qtpy.QtWidgets import QApplication, QPushButton
from superqt import QIconifyIcon
app = QApplication([])
btn = QPushButton()
# search https://icon-sets.iconify.design for available icon keys
btn.setIcon(QIconifyIcon("fluent-emoji-flat:alarm-clock"))
btn.setIconSize(QSize(60, 60))
btn.show()
app.exec()

View File

@@ -48,7 +48,7 @@ dependencies = [
# extras # extras
# https://peps.python.org/pep-0621/#dependencies-optional-dependencies # https://peps.python.org/pep-0621/#dependencies-optional-dependencies
[project.optional-dependencies] [project.optional-dependencies]
test = ["pint", "pytest", "pytest-cov", "pytest-qt", "numpy", "cmap"] test = ["pint", "pytest", "pytest-cov", "pytest-qt", "numpy", "cmap", "pyconify"]
dev = [ dev = [
"black", "black",
"ipython", "ipython",
@@ -74,6 +74,7 @@ font-fa5 = ["fonticon-fontawesome5"]
font-fa6 = ["fonticon-fontawesome6"] font-fa6 = ["fonticon-fontawesome6"]
font-mi6 = ["fonticon-materialdesignicons6"] font-mi6 = ["fonticon-materialdesignicons6"]
font-mi7 = ["fonticon-materialdesignicons7"] font-mi7 = ["fonticon-materialdesignicons7"]
iconify = ["pyconify >=0.1.4"]
[project.urls] [project.urls]
Documentation = "https://pyapp-kit.github.io/superqt/" Documentation = "https://pyapp-kit.github.io/superqt/"

View File

@@ -10,6 +10,7 @@ except PackageNotFoundError:
from .collapsible import QCollapsible from .collapsible import QCollapsible
from .combobox import QColorComboBox, QEnumComboBox, QSearchableComboBox from .combobox import QColorComboBox, QEnumComboBox, QSearchableComboBox
from .elidable import QElidingLabel, QElidingLineEdit from .elidable import QElidingLabel, QElidingLineEdit
from .iconify import QIconifyIcon
from .selection import QSearchableListWidget, QSearchableTreeWidget from .selection import QSearchableListWidget, QSearchableTreeWidget
from .sliders import ( from .sliders import (
QDoubleRangeSlider, QDoubleRangeSlider,
@@ -35,6 +36,7 @@ __all__ = [
"QElidingLineEdit", "QElidingLineEdit",
"QEnumComboBox", "QEnumComboBox",
"QLabeledDoubleRangeSlider", "QLabeledDoubleRangeSlider",
"QIconifyIcon",
"QLabeledDoubleSlider", "QLabeledDoubleSlider",
"QLabeledRangeSlider", "QLabeledRangeSlider",
"QLabeledSlider", "QLabeledSlider",

View File

@@ -10,6 +10,7 @@ __all__ = [
"IconFontMeta", "IconFontMeta",
"IconOpts", "IconOpts",
"pulse", "pulse",
"QIconifyIcon",
"setTextIcon", "setTextIcon",
"spin", "spin",
] ]

View File

@@ -0,0 +1,75 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from qtpy.QtGui import QIcon
if TYPE_CHECKING:
from typing import Literal
Flip = Literal["horizontal", "vertical", "horizontal,vertical"]
Rotation = Literal["90", "180", "270", 90, 180, 270, "-90", 1, 2, 3]
class QIconifyIcon(QIcon):
"""QIcon backed by an iconify icon.
Iconify includes 150,000+ icons from most major icon sets including Bootstrap,
FontAwesome, Material Design, and many more.
Search availble icons at https://icon-sets.iconify.design
Once you find one you like, use the key in the format `"prefix:name"` to create an
icon: `QIconifyIcon("bi:bell")`.
This class is a thin wrapper around the
[pyconify](https://github.com/pyapp-kit/pyconify) `svg_path` function. It pulls SVGs
from iconify, creates a temporary SVG file and uses it as the source for a QIcon.
SVGs are cached to disk, and persist across sessions (until `pyconify.clear_cache()`
is called).
Parameters
----------
*key: str
Icon set prefix and name. May be passed as a single string in the format
`"prefix:name"` or as two separate strings: `'prefix', 'name'`.
color : str, optional
Icon color. If not provided, the icon will appear black (the icon fill color
will be set to the string "currentColor").
flip : str, optional
Flip icon. Must be one of "horizontal", "vertical", "horizontal,vertical"
rotate : str | int, optional
Rotate icon. Must be one of 0, 90, 180, 270,
or 0, 1, 2, 3 (equivalent to 0, 90, 180, 270, respectively)
dir : str, optional
If 'dir' is not None, the file will be created in that directory, otherwise a
default
[directory](https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp) is
used.
Examples
--------
>>> from qtpy.QtWidgets import QPushButton
>>> from superqt import QIconifyIcon
>>> btn = QPushButton()
>>> icon = QIconifyIcon("bi:alarm-fill", color="red", rotate=90)
>>> btn.setIcon(icon)
"""
def __init__(
self,
*key: str,
color: str | None = None,
flip: Flip | None = None,
rotate: Rotation | None = None,
dir: str | None = None,
):
try:
from pyconify import svg_path
except ModuleNotFoundError as e: # pragma: no cover
raise ImportError(
"pyconify is required to use QIconifyIcon. "
"Please install it with `pip install pyconify` or use the "
"`pip install superqt[iconify]` extra."
) from e
self.path = svg_path(*key, color=color, flip=flip, rotate=rotate, dir=dir)
super().__init__(str(self.path))

22
tests/test_iconify.py Normal file
View File

@@ -0,0 +1,22 @@
from typing import TYPE_CHECKING
import pytest
from qtpy.QtWidgets import QPushButton
from superqt import QIconifyIcon
if TYPE_CHECKING:
from pytestqt.qtbot import QtBot
def test_qiconify(qtbot: "QtBot", monkeypatch: "pytest.MonkeyPatch") -> None:
monkeypatch.setenv("PYCONIFY_CACHE", "0")
pytest.importorskip("pyconify")
icon = QIconifyIcon("bi:alarm-fill", color="red", rotate=90)
assert icon.path.name.endswith(".svg")
btn = QPushButton()
qtbot.addWidget(btn)
btn.setIcon(icon)
btn.show()