feat(*): implement v0.1.0, add docs
This commit is contained in:
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
40
docs/api.rst
Normal file
40
docs/api.rst
Normal file
@@ -0,0 +1,40 @@
|
||||
API Reference
|
||||
=============
|
||||
|
||||
This page documents the public API of pyqt-liquidglass.
|
||||
|
||||
pyqt_liquidglass
|
||||
----------------
|
||||
|
||||
.. automodule:: pyqt_liquidglass
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:exclude-members: __all__
|
||||
|
||||
Platform Constants
|
||||
------------------
|
||||
|
||||
.. py:data:: pyqt_liquidglass.IS_MACOS
|
||||
:type: bool
|
||||
:noindex:
|
||||
|
||||
``True`` if running on macOS, ``False`` otherwise.
|
||||
|
||||
.. py:data:: pyqt_liquidglass.MACOS_VERSION
|
||||
:type: tuple[int, int, int] | None
|
||||
:noindex:
|
||||
|
||||
macOS version as a tuple (major, minor, patch), or ``None`` on other platforms.
|
||||
|
||||
.. py:data:: pyqt_liquidglass.HAS_GLASS_EFFECT
|
||||
:type: bool
|
||||
:noindex:
|
||||
|
||||
``True`` if ``NSGlassEffectView`` is available (macOS 26+).
|
||||
|
||||
.. py:data:: pyqt_liquidglass.HAS_VISUAL_EFFECT
|
||||
:type: bool
|
||||
:noindex:
|
||||
|
||||
``True`` if ``NSVisualEffectView`` is available.
|
||||
55
docs/conf.py
Normal file
55
docs/conf.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tomllib
|
||||
|
||||
sys.path.insert(0, os.path.abspath("."))
|
||||
sys.path.insert(0, os.path.abspath("../."))
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = "pyqt-liquidglass"
|
||||
copyright = "2025, h"
|
||||
author = "h"
|
||||
release = tomllib.load(open("../pyproject.toml", "rb"))["project"]["version"]
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx_autodoc_typehints",
|
||||
]
|
||||
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
autodoc_default_options = {
|
||||
"members": True,
|
||||
"member-order": "bysource",
|
||||
"special-members": "__init__",
|
||||
"undoc-members": True,
|
||||
"exclude-members": "__weakref__",
|
||||
"inherited-members": False,
|
||||
}
|
||||
|
||||
autodoc_typehints = "description"
|
||||
typehints_use_signature = True
|
||||
typehints_use_signature_return = True
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = "furo"
|
||||
html_static_path = ["_static"]
|
||||
html_title = "pyqt-liquidglass"
|
||||
html_theme_options = {"sidebar_hide_name": False, "navigation_with_keys": True}
|
||||
200
docs/core_concepts.rst
Normal file
200
docs/core_concepts.rst
Normal file
@@ -0,0 +1,200 @@
|
||||
Core Concepts
|
||||
=============
|
||||
|
||||
This page explains how pyqt-liquidglass works under the hood.
|
||||
|
||||
How Glass Effects Work
|
||||
----------------------
|
||||
|
||||
Native Views
|
||||
~~~~~~~~~~~~
|
||||
|
||||
macOS provides two classes for blur/glass effects:
|
||||
|
||||
- **NSGlassEffectView**: New in macOS 26 (Tahoe), provides the Liquid Glass effect
|
||||
- **NSVisualEffectView**: Available since macOS 10.10, provides vibrancy effects
|
||||
|
||||
pyqt-liquidglass automatically uses ``NSGlassEffectView`` when available and falls back to ``NSVisualEffectView`` on older systems.
|
||||
|
||||
View Hierarchy
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Qt widgets map to native ``NSView`` objects. When you apply a glass effect, the library:
|
||||
|
||||
1. Gets the native ``NSView`` for your Qt widget
|
||||
2. Creates a glass effect view (``NSGlassEffectView`` or ``NSVisualEffectView``)
|
||||
3. Inserts it behind your widget's view in the z-order
|
||||
4. Configures autoresizing so it tracks widget size changes
|
||||
|
||||
The glass view renders the blur effect, and your Qt content draws on top.
|
||||
|
||||
Window vs Widget Glass
|
||||
----------------------
|
||||
|
||||
Window Glass
|
||||
~~~~~~~~~~~~
|
||||
|
||||
``apply_glass_to_window()`` fills the entire window content area with glass:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
glass.apply_glass_to_window(window)
|
||||
|
||||
This is ideal for:
|
||||
|
||||
- Full-window blur backgrounds
|
||||
- Floating panels
|
||||
- Modal dialogs
|
||||
|
||||
The function configures the window for transparent titlebar and full-size content view automatically.
|
||||
|
||||
Widget Glass
|
||||
~~~~~~~~~~~~
|
||||
|
||||
``apply_glass_to_widget()`` applies glass to a specific widget region:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
glass.apply_glass_to_widget(sidebar, options=glass.GlassOptions.sidebar())
|
||||
|
||||
This is ideal for:
|
||||
|
||||
- Sidebars (like macOS System Settings)
|
||||
- Navigation panels
|
||||
- Toolbars
|
||||
|
||||
The glass view tracks the widget's position and size, updating automatically on resize or move.
|
||||
|
||||
GlassOptions Configuration
|
||||
--------------------------
|
||||
|
||||
The ``GlassOptions`` dataclass controls glass effect appearance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pyqt_liquidglass import GlassOptions
|
||||
|
||||
options = GlassOptions(
|
||||
corner_radius=16.0, # Rounded corners (NSGlassEffectView only)
|
||||
material=GlassMaterial.SIDEBAR, # Material type
|
||||
blending_mode=BlendingMode.BEHIND_WINDOW, # Blending mode
|
||||
padding=(10, 10, 10, 10), # Left, top, right, bottom
|
||||
)
|
||||
|
||||
Attributes
|
||||
~~~~~~~~~~
|
||||
|
||||
**corner_radius** (float)
|
||||
Corner radius in points. Only applies to ``NSGlassEffectView`` on macOS 26+. Default: 0.0
|
||||
|
||||
**material** (GlassMaterial)
|
||||
The visual effect material. Only applies to ``NSVisualEffectView`` fallback. Options include:
|
||||
|
||||
- ``TITLEBAR``, ``SIDEBAR``, ``MENU``, ``POPOVER``
|
||||
- ``SHEET``, ``WINDOW_BACKGROUND``, ``HUD``, ``TOOLTIP``
|
||||
- ``CONTENT_BACKGROUND``, ``UNDER_WINDOW_BACKGROUND``
|
||||
|
||||
**blending_mode** (BlendingMode)
|
||||
How the effect blends with content:
|
||||
|
||||
- ``BEHIND_WINDOW``: Blurs content behind the window
|
||||
- ``WITHIN_WINDOW``: Blurs content within the window
|
||||
|
||||
**padding** (tuple)
|
||||
Inset from widget edges in points: (left, top, right, bottom). Default: (0, 0, 0, 0)
|
||||
|
||||
Preset Methods
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
For common use cases:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Full window glass (no corner radius, no padding)
|
||||
options = GlassOptions.window()
|
||||
|
||||
# Sidebar glass (10pt radius, 9pt padding)
|
||||
options = GlassOptions.sidebar()
|
||||
|
||||
# Custom sidebar
|
||||
options = GlassOptions.sidebar(corner_radius=16.0, padding=12.0)
|
||||
|
||||
Coordinate Systems
|
||||
------------------
|
||||
|
||||
Qt and Cocoa use different coordinate systems:
|
||||
|
||||
- **Qt**: Origin at top-left, Y increases downward
|
||||
- **Cocoa**: Origin at bottom-left, Y increases upward
|
||||
|
||||
pyqt-liquidglass handles this conversion internally. When specifying ``y_offset`` for traffic lights, positive values move the buttons down from center.
|
||||
|
||||
Traffic Lights
|
||||
--------------
|
||||
|
||||
The traffic lights are the close, minimize, and zoom buttons in the window titlebar.
|
||||
|
||||
Positioning
|
||||
~~~~~~~~~~~
|
||||
|
||||
``setup_traffic_lights_inset()`` repositions the buttons using ``NSLayoutConstraint``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
glass.setup_traffic_lights_inset(window, x_offset=20, y_offset=12)
|
||||
|
||||
- **x_offset**: Distance from the left edge in points
|
||||
- **y_offset**: Vertical offset from center (positive = down)
|
||||
|
||||
This method survives window resizes because it uses Auto Layout constraints rather than absolute positioning.
|
||||
|
||||
Visibility
|
||||
~~~~~~~~~~
|
||||
|
||||
Hide the buttons while keeping window functionality:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
glass.hide_traffic_lights(window)
|
||||
glass.show_traffic_lights(window)
|
||||
|
||||
The window remains closable, minimizable, and zoomable via keyboard shortcuts and menu commands.
|
||||
|
||||
Platform Detection
|
||||
------------------
|
||||
|
||||
The library provides constants for platform detection:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pyqt_liquidglass import (
|
||||
IS_MACOS, # True if running on macOS
|
||||
MACOS_VERSION, # Tuple like (15, 1, 0) or None
|
||||
HAS_GLASS_EFFECT, # True if NSGlassEffectView is available
|
||||
HAS_VISUAL_EFFECT, # True if NSVisualEffectView is available
|
||||
)
|
||||
|
||||
All glass functions are safe to call on non-macOS platforms. They return ``None`` or ``False`` without side effects.
|
||||
|
||||
Effect Lifecycle
|
||||
----------------
|
||||
|
||||
Effect IDs
|
||||
~~~~~~~~~~
|
||||
|
||||
``apply_glass_to_window()`` and ``apply_glass_to_widget()`` return an effect ID:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
effect_id = glass.apply_glass_to_window(window)
|
||||
|
||||
Use this ID to remove the effect later:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
glass.remove_glass_effect(effect_id)
|
||||
|
||||
Cleanup
|
||||
~~~~~~~
|
||||
|
||||
When a window is closed, Qt destroys the widget hierarchy. The glass effect views are removed automatically as part of the native view cleanup. You don't need to manually remove effects before closing windows.
|
||||
544
docs/examples.rst
Normal file
544
docs/examples.rst
Normal file
@@ -0,0 +1,544 @@
|
||||
Examples
|
||||
========
|
||||
|
||||
This page contains complete, runnable examples demonstrating various use cases.
|
||||
|
||||
Full Window Glass
|
||||
-----------------
|
||||
|
||||
The simplest example: glass filling the entire window content area.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Full window glass effect example."""
|
||||
|
||||
import sys
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget
|
||||
|
||||
import pyqt_liquidglass as glass
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.setWindowTitle("Window Glass")
|
||||
self.resize(600, 400)
|
||||
|
||||
central = QWidget()
|
||||
central.setStyleSheet("background: transparent;")
|
||||
|
||||
layout = QVBoxLayout(central)
|
||||
layout.setContentsMargins(40, 60, 40, 40)
|
||||
|
||||
label = QLabel("Hello, Liquid Glass!")
|
||||
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
label.setStyleSheet("""
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
layout.addWidget(label)
|
||||
self.setCentralWidget(central)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
window = MainWindow()
|
||||
|
||||
# Prepare window BEFORE showing
|
||||
glass.prepare_window_for_glass(window)
|
||||
|
||||
window.show()
|
||||
|
||||
# Apply glass AFTER showing
|
||||
glass.apply_glass_to_window(window)
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Sidebar Pattern
|
||||
---------------
|
||||
|
||||
Settings-style window with a glass sidebar and opaque content area. This is the most common pattern for macOS applications.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Sidebar with glass effect example."""
|
||||
|
||||
import sys
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QListWidget,
|
||||
QListWidgetItem,
|
||||
QMainWindow,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
import pyqt_liquidglass as glass
|
||||
|
||||
|
||||
class Sidebar(QWidget):
|
||||
"""Transparent sidebar widget for glass effect."""
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setFixedWidth(250)
|
||||
self.setStyleSheet("background: transparent;")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(9, 18, 9, 9)
|
||||
|
||||
self.list_widget = QListWidget()
|
||||
self.list_widget.setStyleSheet("""
|
||||
QListWidget {
|
||||
font-size: 13px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
QListWidget::item {
|
||||
padding: 5px 2px;
|
||||
margin: 0px 9px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QListWidget::item:selected {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
""")
|
||||
self.list_widget.viewport().setStyleSheet("background: transparent;")
|
||||
|
||||
for item_text in ["General", "Appearance", "Sound", "Network", "Privacy"]:
|
||||
self.list_widget.addItem(QListWidgetItem(item_text))
|
||||
|
||||
self.list_widget.setCurrentRow(0)
|
||||
layout.addWidget(self.list_widget)
|
||||
|
||||
|
||||
class Content(QWidget):
|
||||
"""Opaque content area."""
|
||||
|
||||
def __init__(self, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setStyleSheet("background-color: #1e1e1e;")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
self.title = QLabel("General")
|
||||
self.title.setStyleSheet("""
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
self.description = QLabel("Configure general application settings.")
|
||||
self.description.setStyleSheet("""
|
||||
font-size: 14px;
|
||||
color: #888888;
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
layout.addWidget(self.title)
|
||||
layout.addSpacing(8)
|
||||
layout.addWidget(self.description)
|
||||
layout.addStretch()
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.setWindowTitle("Settings")
|
||||
self.resize(720, 600)
|
||||
|
||||
central = QWidget()
|
||||
layout = QHBoxLayout(central)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
self.sidebar = Sidebar()
|
||||
self.content = Content()
|
||||
|
||||
layout.addWidget(self.sidebar)
|
||||
layout.addWidget(self.content, 1)
|
||||
|
||||
self.setCentralWidget(central)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
window = MainWindow()
|
||||
glass.prepare_window_for_glass(window)
|
||||
window.show()
|
||||
|
||||
# Inset traffic lights to sit nicely on sidebar
|
||||
glass.setup_traffic_lights_inset(window, x_offset=18, y_offset=12)
|
||||
|
||||
# Apply glass to sidebar with rounded corners
|
||||
glass.apply_glass_to_widget(window.sidebar, options=glass.GlassOptions.sidebar())
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Frameless Floating Panel
|
||||
------------------------
|
||||
|
||||
A frameless, draggable panel useful for HUDs, tool palettes, or popovers.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Frameless window with glass effect example."""
|
||||
|
||||
import sys
|
||||
|
||||
from PySide6.QtCore import QPoint, Qt
|
||||
from PySide6.QtGui import QMouseEvent
|
||||
from PySide6.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
import pyqt_liquidglass as glass
|
||||
|
||||
|
||||
class FloatingPanel(QWidget):
|
||||
"""A frameless, draggable floating panel with glass effect."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.setWindowTitle("Floating Panel")
|
||||
self.resize(300, 200)
|
||||
|
||||
self._drag_position: QPoint | None = None
|
||||
|
||||
self.setStyleSheet("background: transparent;")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(24, 24, 24, 24)
|
||||
layout.setSpacing(16)
|
||||
|
||||
title = QLabel("Floating Panel")
|
||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
title.setStyleSheet("""
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
subtitle = QLabel("Drag anywhere to move")
|
||||
subtitle.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
subtitle.setStyleSheet("""
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
close_button = QPushButton("Close")
|
||||
close_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 10px 24px;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
""")
|
||||
close_button.clicked.connect(self.close)
|
||||
|
||||
layout.addWidget(title)
|
||||
layout.addWidget(subtitle)
|
||||
layout.addStretch()
|
||||
layout.addWidget(close_button)
|
||||
|
||||
def mousePressEvent(self, event: QMouseEvent) -> None:
|
||||
if event.button() == Qt.MouseButton.LeftButton:
|
||||
self._drag_position = (
|
||||
event.globalPosition().toPoint() - self.frameGeometry().topLeft()
|
||||
)
|
||||
event.accept()
|
||||
|
||||
def mouseMoveEvent(self, event: QMouseEvent) -> None:
|
||||
if (
|
||||
event.buttons() == Qt.MouseButton.LeftButton
|
||||
and self._drag_position is not None
|
||||
):
|
||||
self.move(event.globalPosition().toPoint() - self._drag_position)
|
||||
event.accept()
|
||||
|
||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||
self._drag_position = None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
panel = FloatingPanel()
|
||||
|
||||
# Prepare with frameless=True to remove window decorations
|
||||
glass.prepare_window_for_glass(panel, frameless=True)
|
||||
|
||||
panel.show()
|
||||
|
||||
# Apply glass with rounded corners for the floating look
|
||||
glass.apply_glass_to_window(panel, options=glass.GlassOptions(corner_radius=16.0))
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Custom GlassOptions
|
||||
-------------------
|
||||
|
||||
Demonstrates different glass configurations side by side.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Custom GlassOptions example."""
|
||||
|
||||
import sys
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
import pyqt_liquidglass as glass
|
||||
|
||||
|
||||
class GlassPanel(QWidget):
|
||||
"""A panel that will have glass applied to it."""
|
||||
|
||||
def __init__(self, title: str, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setStyleSheet("background: transparent;")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
label = QLabel(title)
|
||||
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
label.setStyleSheet("""
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
layout.addWidget(label)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.setWindowTitle("Custom Glass Options")
|
||||
self.resize(800, 400)
|
||||
|
||||
central = QWidget()
|
||||
central.setStyleSheet("background: transparent;")
|
||||
|
||||
layout = QHBoxLayout(central)
|
||||
layout.setContentsMargins(20, 60, 20, 20)
|
||||
layout.setSpacing(20)
|
||||
|
||||
self.panel_default = GlassPanel("Default\n(no radius)")
|
||||
self.panel_default.setFixedWidth(200)
|
||||
|
||||
self.panel_rounded = GlassPanel("Rounded\n(radius: 16)")
|
||||
self.panel_rounded.setFixedWidth(200)
|
||||
|
||||
self.panel_padded = GlassPanel("Padded\n(padding: 20)")
|
||||
self.panel_padded.setFixedWidth(200)
|
||||
|
||||
layout.addWidget(self.panel_default)
|
||||
layout.addWidget(self.panel_rounded)
|
||||
layout.addWidget(self.panel_padded)
|
||||
|
||||
self.setCentralWidget(central)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
window = MainWindow()
|
||||
|
||||
glass.prepare_window_for_glass(window)
|
||||
|
||||
window.show()
|
||||
|
||||
# Apply window glass as background
|
||||
glass.apply_glass_to_window(window)
|
||||
|
||||
# Apply different glass options to each panel
|
||||
glass.apply_glass_to_widget(
|
||||
window.panel_default,
|
||||
options=glass.GlassOptions(corner_radius=0.0, padding=(8, 8, 8, 8)),
|
||||
)
|
||||
|
||||
glass.apply_glass_to_widget(
|
||||
window.panel_rounded,
|
||||
options=glass.GlassOptions(corner_radius=16.0, padding=(8, 8, 8, 8)),
|
||||
)
|
||||
|
||||
glass.apply_glass_to_widget(
|
||||
window.panel_padded,
|
||||
options=glass.GlassOptions(corner_radius=12.0, padding=(20, 20, 20, 20)),
|
||||
)
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Traffic Lights Control
|
||||
----------------------
|
||||
|
||||
Demonstrates hiding, showing, and repositioning the macOS window buttons.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"""Traffic lights control example."""
|
||||
|
||||
import sys
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
import pyqt_liquidglass as glass
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.setWindowTitle("Traffic Lights Demo")
|
||||
self.resize(500, 300)
|
||||
|
||||
self._lights_visible = True
|
||||
|
||||
central = QWidget()
|
||||
central.setStyleSheet("background: transparent;")
|
||||
|
||||
layout = QVBoxLayout(central)
|
||||
layout.setContentsMargins(40, 80, 40, 40)
|
||||
layout.setSpacing(20)
|
||||
|
||||
title = QLabel("Traffic Lights Control")
|
||||
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
title.setStyleSheet("""
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
description = QLabel(
|
||||
"The traffic lights have been repositioned.\n"
|
||||
"Use the button below to hide or show them."
|
||||
)
|
||||
description.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
description.setStyleSheet("""
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
buttons_layout = QHBoxLayout()
|
||||
buttons_layout.setSpacing(12)
|
||||
|
||||
self.toggle_button = QPushButton("Hide Traffic Lights")
|
||||
self.toggle_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 12px 24px;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
min-width: 160px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
""")
|
||||
self.toggle_button.clicked.connect(self._toggle_traffic_lights)
|
||||
|
||||
buttons_layout.addStretch()
|
||||
buttons_layout.addWidget(self.toggle_button)
|
||||
buttons_layout.addStretch()
|
||||
|
||||
layout.addWidget(title)
|
||||
layout.addWidget(description)
|
||||
layout.addStretch()
|
||||
layout.addLayout(buttons_layout)
|
||||
|
||||
self.setCentralWidget(central)
|
||||
|
||||
def _toggle_traffic_lights(self) -> None:
|
||||
if self._lights_visible:
|
||||
glass.hide_traffic_lights(self)
|
||||
self.toggle_button.setText("Show Traffic Lights")
|
||||
else:
|
||||
glass.show_traffic_lights(self)
|
||||
self.toggle_button.setText("Hide Traffic Lights")
|
||||
self._lights_visible = not self._lights_visible
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
window = MainWindow()
|
||||
|
||||
glass.prepare_window_for_glass(window)
|
||||
|
||||
window.show()
|
||||
|
||||
# Apply glass to window
|
||||
glass.apply_glass_to_window(window)
|
||||
|
||||
# Reposition traffic lights with custom offset
|
||||
glass.setup_traffic_lights_inset(window, x_offset=20, y_offset=16)
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
151
docs/getting_started.rst
Normal file
151
docs/getting_started.rst
Normal file
@@ -0,0 +1,151 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
This guide covers installation and basic usage of pyqt-liquidglass.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Using pip
|
||||
~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install pyqt-liquidglass
|
||||
|
||||
Using uv
|
||||
~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
uv add pyqt-liquidglass
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- **Python**: 3.12 or higher
|
||||
- **Operating System**: macOS (functions are safe no-ops on other platforms)
|
||||
- **Qt Binding**: PySide6 or PyQt6
|
||||
|
||||
The library automatically detects your Qt binding.
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
The Three-Step Pattern
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Every glass effect follows the same pattern:
|
||||
|
||||
1. **Prepare** the window before showing
|
||||
2. **Show** the window
|
||||
3. **Apply** the glass effect
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pyqt_liquidglass as glass
|
||||
|
||||
# 1. Prepare before showing
|
||||
glass.prepare_window_for_glass(window)
|
||||
|
||||
# 2. Show the window
|
||||
window.show()
|
||||
|
||||
# 3. Apply glass after showing
|
||||
glass.apply_glass_to_window(window)
|
||||
|
||||
Full Window Example
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget
|
||||
|
||||
import pyqt_liquidglass as glass
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.setWindowTitle("Glass Demo")
|
||||
self.resize(600, 400)
|
||||
|
||||
central = QWidget()
|
||||
central.setStyleSheet("background: transparent;")
|
||||
|
||||
layout = QVBoxLayout(central)
|
||||
layout.setContentsMargins(40, 60, 40, 40)
|
||||
|
||||
label = QLabel("Hello, Liquid Glass!")
|
||||
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
label.setStyleSheet("""
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: transparent;
|
||||
""")
|
||||
|
||||
layout.addWidget(label)
|
||||
self.setCentralWidget(central)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
window = MainWindow()
|
||||
glass.prepare_window_for_glass(window)
|
||||
window.show()
|
||||
glass.apply_glass_to_window(window)
|
||||
|
||||
return app.exec()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Key Points
|
||||
~~~~~~~~~~
|
||||
|
||||
- Set ``background: transparent`` on widgets that should show the glass through
|
||||
- Call ``prepare_window_for_glass()`` before ``show()``
|
||||
- Call ``apply_glass_to_window()`` after ``show()``
|
||||
- The window needs to be visible for the native view hierarchy to exist
|
||||
|
||||
Widget Glass
|
||||
~~~~~~~~~~~~
|
||||
|
||||
For applying glass to specific widgets (like sidebars):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
sidebar = QWidget()
|
||||
sidebar.setFixedWidth(250)
|
||||
sidebar.setStyleSheet("background: transparent;")
|
||||
|
||||
# After window.show():
|
||||
glass.apply_glass_to_widget(sidebar, options=glass.GlassOptions.sidebar())
|
||||
|
||||
Traffic Lights
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Reposition the macOS window buttons:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
glass.setup_traffic_lights_inset(window, x_offset=20, y_offset=12)
|
||||
|
||||
Hide or show them:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
glass.hide_traffic_lights(window)
|
||||
glass.show_traffic_lights(window)
|
||||
|
||||
Next Steps
|
||||
----------
|
||||
|
||||
- Learn about :doc:`core_concepts` to understand how glass effects work
|
||||
- See :doc:`examples` for complete working examples
|
||||
- Check the :doc:`api` for detailed function signatures
|
||||
89
docs/index.rst
Normal file
89
docs/index.rst
Normal file
@@ -0,0 +1,89 @@
|
||||
pyqt-liquidglass
|
||||
================
|
||||
|
||||
**macOS Liquid Glass effects for PySide6 and PyQt6**
|
||||
|
||||
pyqt-liquidglass brings Apple's Liquid Glass visual effects to your Qt applications on macOS. It provides a clean Python API to apply the native ``NSGlassEffectView`` (macOS 26+) or ``NSVisualEffectView`` (fallback) to windows and widgets.
|
||||
|
||||
----
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- **Window Glass**: Apply glass effects to entire windows
|
||||
- **Widget Glass**: Target specific widgets like sidebars or panels
|
||||
- **Traffic Lights Control**: Reposition, hide, or show window buttons
|
||||
- **GlassOptions**: Configure corner radius, padding, materials, and blending
|
||||
- **Cross-Version**: Uses ``NSGlassEffectView`` on macOS 26+, falls back to ``NSVisualEffectView``
|
||||
- **Safe No-ops**: All functions work on non-macOS platforms (return ``None`` or ``False``)
|
||||
|
||||
Quick Example
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from PySide6.QtWidgets import QApplication, QMainWindow
|
||||
import pyqt_liquidglass as glass
|
||||
|
||||
app = QApplication([])
|
||||
window = QMainWindow()
|
||||
window.resize(800, 600)
|
||||
|
||||
# Prepare before showing
|
||||
glass.prepare_window_for_glass(window)
|
||||
window.show()
|
||||
|
||||
# Apply glass after showing
|
||||
glass.apply_glass_to_window(window)
|
||||
|
||||
app.exec()
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install pyqt-liquidglass
|
||||
|
||||
Or with uv:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
uv add pyqt-liquidglass
|
||||
|
||||
**Requirements**: Python 3.12+, macOS, PySide6 or PyQt6
|
||||
|
||||
----
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: User Guide
|
||||
|
||||
getting_started
|
||||
core_concepts
|
||||
examples
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: API Reference
|
||||
|
||||
api
|
||||
|
||||
----
|
||||
|
||||
Indices
|
||||
=======
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
----
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
MIT License. See LICENSE.md for details.
|
||||
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
3
docs/requirements.txt
Normal file
3
docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
sphinx
|
||||
furo
|
||||
sphinx-autodoc-typehints
|
||||
Reference in New Issue
Block a user