mirror of
https://github.com/pyapp-kit/superqt.git
synced 2026-01-04 19:31:24 +01:00
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:
14
examples/iconify.py
Normal file
14
examples/iconify.py
Normal 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()
|
||||||
@@ -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/"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ __all__ = [
|
|||||||
"IconFontMeta",
|
"IconFontMeta",
|
||||||
"IconOpts",
|
"IconOpts",
|
||||||
"pulse",
|
"pulse",
|
||||||
|
"QIconifyIcon",
|
||||||
"setTextIcon",
|
"setTextIcon",
|
||||||
"spin",
|
"spin",
|
||||||
]
|
]
|
||||||
|
|||||||
75
src/superqt/iconify/__init__.py
Normal file
75
src/superqt/iconify/__init__.py
Normal 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
22
tests/test_iconify.py
Normal 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()
|
||||||
Reference in New Issue
Block a user