fluent-blue 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fluent_blue/__init__.py +27 -0
- fluent_blue/agent_prompts.py +197 -0
- fluent_blue/compatibility/__init__.py +4 -0
- fluent_blue/compatibility/qt.py +108 -0
- fluent_blue/components/__init__.py +10 -0
- fluent_blue/components/advanced/__init__.py +10 -0
- fluent_blue/components/advanced/datetime_pickers.py +380 -0
- fluent_blue/components/advanced/editors.py +557 -0
- fluent_blue/components/advanced/pickers.py +344 -0
- fluent_blue/components/advanced/structure.py +534 -0
- fluent_blue/components/base.py +124 -0
- fluent_blue/components/buttons/__init__.py +18 -0
- fluent_blue/components/buttons/button.py +230 -0
- fluent_blue/components/buttons/split_button.py +50 -0
- fluent_blue/components/data/__init__.py +5 -0
- fluent_blue/components/data/model.py +232 -0
- fluent_blue/components/data/table.py +473 -0
- fluent_blue/components/display/__init__.py +12 -0
- fluent_blue/components/display/card.py +192 -0
- fluent_blue/components/display/console.py +139 -0
- fluent_blue/components/display/content.py +387 -0
- fluent_blue/components/display/media.py +379 -0
- fluent_blue/components/display/text.py +215 -0
- fluent_blue/components/feedback/__init__.py +17 -0
- fluent_blue/components/feedback/feedback.py +566 -0
- fluent_blue/components/inputs/__init__.py +9 -0
- fluent_blue/components/inputs/form.py +125 -0
- fluent_blue/components/inputs/input.py +287 -0
- fluent_blue/components/inputs/tags_otp.py +286 -0
- fluent_blue/components/layout/__init__.py +9 -0
- fluent_blue/components/layout/containers.py +318 -0
- fluent_blue/components/layout/overlays.py +247 -0
- fluent_blue/components/layout/window.py +159 -0
- fluent_blue/components/navigation/__init__.py +10 -0
- fluent_blue/components/navigation/command_palette.py +185 -0
- fluent_blue/components/navigation/menu.py +118 -0
- fluent_blue/components/navigation/sidebar.py +243 -0
- fluent_blue/components/navigation/wayfinding.py +252 -0
- fluent_blue/components/popup.py +61 -0
- fluent_blue/components/selection/__init__.py +8 -0
- fluent_blue/components/selection/choice.py +242 -0
- fluent_blue/components/selection/controls.py +317 -0
- fluent_blue/core/__init__.py +6 -0
- fluent_blue/core/animations.py +51 -0
- fluent_blue/core/configuration.py +25 -0
- fluent_blue/core/palette.py +315 -0
- fluent_blue/core/theme_manager.py +273 -0
- fluent_blue/core/tokens.py +175 -0
- fluent_blue/core/typography.py +57 -0
- fluent_blue/icons/__init__.py +8 -0
- fluent_blue/icons/engine.py +137 -0
- fluent_blue/icons/library.py +147 -0
- fluent_blue/resources/fonts/Inter-Bold.ttf +0 -0
- fluent_blue/resources/fonts/Inter-Medium.ttf +0 -0
- fluent_blue/resources/fonts/Inter-Regular.ttf +0 -0
- fluent_blue/resources/fonts/Inter-SemiBold.ttf +0 -0
- fluent_blue/studio/CHANGELOG.md +23 -0
- fluent_blue/studio/__init__.py +1 -0
- fluent_blue/studio/app.py +324 -0
- fluent_blue/studio/art.py +164 -0
- fluent_blue/studio/catalog/__init__.py +46 -0
- fluent_blue/studio/catalog/added.py +49 -0
- fluent_blue/studio/catalog/advanced.py +120 -0
- fluent_blue/studio/catalog/buttons.py +88 -0
- fluent_blue/studio/catalog/data_tables.py +43 -0
- fluent_blue/studio/catalog/display.py +132 -0
- fluent_blue/studio/catalog/feedback.py +100 -0
- fluent_blue/studio/catalog/inputs.py +63 -0
- fluent_blue/studio/catalog/layout.py +82 -0
- fluent_blue/studio/catalog/navigation.py +105 -0
- fluent_blue/studio/catalog/selection.py +65 -0
- fluent_blue/studio/introspect.py +156 -0
- fluent_blue/studio/pages/__init__.py +1 -0
- fluent_blue/studio/pages/components.py +217 -0
- fluent_blue/studio/pages/foundations.py +316 -0
- fluent_blue/studio/pages/overview.py +669 -0
- fluent_blue/studio/pages/tools.py +191 -0
- fluent_blue/themes/__init__.py +4 -0
- fluent_blue/themes/stylesheet/__init__.py +11 -0
- fluent_blue/themes/stylesheet/base.py +85 -0
- fluent_blue/themes/stylesheet/buttons.py +56 -0
- fluent_blue/themes/stylesheet/context.py +55 -0
- fluent_blue/themes/stylesheet/data.py +20 -0
- fluent_blue/themes/stylesheet/display.py +82 -0
- fluent_blue/themes/stylesheet/feedback.py +30 -0
- fluent_blue/themes/stylesheet/inputs.py +85 -0
- fluent_blue/themes/stylesheet/navigation.py +51 -0
- fluent_blue/themes/stylesheet/selection.py +56 -0
- fluent_blue/themes/stylesheet/studio.py +26 -0
- fluent_blue/utilities/__init__.py +0 -0
- fluent_blue-0.1.0.dist-info/METADATA +303 -0
- fluent_blue-0.1.0.dist-info/RECORD +95 -0
- fluent_blue-0.1.0.dist-info/WHEEL +4 -0
- fluent_blue-0.1.0.dist-info/entry_points.txt +2 -0
- fluent_blue-0.1.0.dist-info/licenses/LICENSE +21 -0
fluent_blue/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Fluent Blue: a design system for PyQt6 and PySide6 desktop applications.
|
|
2
|
+
|
|
3
|
+
>>> from fluent_blue import Button, ThemeManager # doctest: +SKIP
|
|
4
|
+
"""
|
|
5
|
+
from .compatibility import BINDING, BINDING_VERSION, QT_VERSION
|
|
6
|
+
from .components import advanced, buttons, data, display, feedback, inputs, layout, navigation, selection
|
|
7
|
+
from .components.advanced import * # noqa: F403
|
|
8
|
+
from .components.buttons import * # noqa: F403
|
|
9
|
+
from .components.data import * # noqa: F403
|
|
10
|
+
from .components.display import * # noqa: F403
|
|
11
|
+
from .components.feedback import * # noqa: F403
|
|
12
|
+
from .components.inputs import * # noqa: F403
|
|
13
|
+
from .components.layout import * # noqa: F403
|
|
14
|
+
from .components.navigation import * # noqa: F403
|
|
15
|
+
from .components.selection import * # noqa: F403
|
|
16
|
+
from .core import Configuration, DesignTokens, Theme, ThemeManager, config, configure
|
|
17
|
+
from .icons import has_icon, icon, icon_names, icon_pixmap, register_icon, register_icon_file
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"BINDING", "BINDING_VERSION", "QT_VERSION", "Configuration", "DesignTokens", "Theme", "ThemeManager", "__version__",
|
|
23
|
+
"config", "configure", "has_icon", "icon", "icon_names", "icon_pixmap", "register_icon", "register_icon_file",
|
|
24
|
+
]
|
|
25
|
+
for _module in (advanced, buttons, data, display, feedback, inputs, layout, navigation, selection):
|
|
26
|
+
__all__ += _module.__all__
|
|
27
|
+
del _module
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Ready-to-use briefs that let an AI coding agent work with each Fluent Blue component.
|
|
2
|
+
|
|
3
|
+
Every brief is generated from the installed source: constructor signatures, docstrings,
|
|
4
|
+
signals, public methods and the catalog snippet that the test suite executes. It therefore
|
|
5
|
+
cannot describe an API that does not exist.
|
|
6
|
+
|
|
7
|
+
python -m fluent_blue.agent_prompts Button # print one brief
|
|
8
|
+
python -m fluent_blue.agent_prompts --list # list component names
|
|
9
|
+
python -m fluent_blue.agent_prompts --export DIR # write AGENTS.md, llms.txt and one file each
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import inspect
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from .studio import catalog, introspect
|
|
18
|
+
|
|
19
|
+
PROJECT_RULES = """\
|
|
20
|
+
- Import components from `fluent_blue`, and Qt names from `fluent_blue.compatibility.qt`. Never import `PyQt6` or `PySide6` directly in library code, and never load both in one process.
|
|
21
|
+
- Style through props and design tokens. Do not call `setStyleSheet` on components, and do not hardcode colors, spacing, radii or fonts.
|
|
22
|
+
- Read theme values with `ThemeManager.color("role")`, `.qcolor("role")`, `.spacing(step)`, `.radius("md")` and `.font("role")`. For custom painting, read them inside `paintEvent`.
|
|
23
|
+
- Subclass with `ThemedMixin`, call `self.bind_theme()`, and rebuild cached pixmaps in `refresh_theme()`, so that runtime theme switches work.
|
|
24
|
+
- Never connect a lambda or closure that captures a widget to `ThemeManager` signals: it outlives the widget, and PyQt aborts when it fires. Use `ThemedMixin` or a bound method.
|
|
25
|
+
- Keep theme switches fast: do not rebuild child widgets in `refresh_theme()`, and free one-shot menus and dialogs with `WA_DeleteOnClose` or `finished.connect(dialog.deleteLater)`.
|
|
26
|
+
- Use icon names from `icon_names()`, or add one with `register_icon`. Icon-only controls need a `tooltip`, which also becomes the accessible name.
|
|
27
|
+
- Text needs at least 4.5:1 contrast. Keep keyboard access and visible focus.
|
|
28
|
+
- Fluent Blue signals and methods use `snake_case`. Inherited Qt API stays `camelCase`.
|
|
29
|
+
- Verify your change on both bindings: `FLUENT_BLUE_QT_BINDING=PyQt6 pytest` and `FLUENT_BLUE_QT_BINDING=PySide6 pytest`, then `python scripts/ui_audit.py`. Never report a result you did not run."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_INTERNAL_METHODS = {"bind_theme", "refresh_theme", "paint_gutter", "show"}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def public_classes() -> list[str]:
|
|
36
|
+
"""Names of every exported widget and helper class that needs a brief."""
|
|
37
|
+
import fluent_blue
|
|
38
|
+
|
|
39
|
+
skip = {"Configuration", "DesignTokens", "Theme"}
|
|
40
|
+
names = []
|
|
41
|
+
for name in fluent_blue.__all__:
|
|
42
|
+
value = getattr(fluent_blue, name)
|
|
43
|
+
if inspect.isclass(value) and name not in skip and value.__module__.startswith("fluent_blue.components"):
|
|
44
|
+
names.append(name)
|
|
45
|
+
return sorted(set(names))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _entries() -> dict[str, catalog.Entry]:
|
|
49
|
+
return {entry.name: entry for entry in catalog.entries()}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _signature(cls) -> str:
|
|
53
|
+
target = introspect._api_target(cls)
|
|
54
|
+
label = f"{cls.__name__}.show" if target is not cls.__init__ else cls.__name__
|
|
55
|
+
parameters = []
|
|
56
|
+
for prop in introspect.properties(cls):
|
|
57
|
+
parameters.append(prop.name if prop.default == "required" else f"{prop.name}={prop.default}")
|
|
58
|
+
return f"{label}({', '.join(parameters)})"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _methods(cls) -> list[tuple[str, str]]:
|
|
62
|
+
found = {}
|
|
63
|
+
for klass in cls.__mro__:
|
|
64
|
+
if not klass.__module__.startswith("fluent_blue"):
|
|
65
|
+
continue
|
|
66
|
+
for name, value in vars(klass).items():
|
|
67
|
+
if name.startswith("_") or name[0].isupper() or name in found or name in _INTERNAL_METHODS:
|
|
68
|
+
continue
|
|
69
|
+
function = value.__func__ if isinstance(value, (staticmethod, classmethod)) else value
|
|
70
|
+
if inspect.isfunction(function) and name[0].islower() and not any(c.isupper() for c in name):
|
|
71
|
+
doc = inspect.cleandoc(function.__doc__ or "").split("\n")[0]
|
|
72
|
+
signature = str(inspect.signature(function)).replace("(self, ", "(").replace("(self)", "()").replace("'", "")
|
|
73
|
+
found[name] = (f"{name}{signature}".replace(" -> None", ""), introspect.clean_markup(doc))
|
|
74
|
+
return sorted(found.values())
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def group_of(name: str) -> str:
|
|
78
|
+
entry = _entries().get(name)
|
|
79
|
+
return entry.group if entry else "Uncatalogued"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def component_prompt(name: str) -> str:
|
|
83
|
+
"""Return the Markdown brief for one component. Raises ``KeyError`` for an unknown name."""
|
|
84
|
+
if name not in public_classes():
|
|
85
|
+
raise KeyError(f"Unknown component {name!r}. Available: {', '.join(public_classes())}")
|
|
86
|
+
import fluent_blue
|
|
87
|
+
|
|
88
|
+
cls = getattr(fluent_blue, name)
|
|
89
|
+
entry = _entries().get(name)
|
|
90
|
+
bases = [b.__name__ for b in cls.__mro__[1:] if b.__module__.split(".")[0] in ("PyQt6", "PySide6")]
|
|
91
|
+
lines = [
|
|
92
|
+
f"# {name}", "",
|
|
93
|
+
"You are working with the Fluent Blue design system for PyQt6 and PySide6. Use this brief as the source of truth for this component's API, and do not invent parameters.", "",
|
|
94
|
+
"## Purpose", "", introspect.summary(cls), "",
|
|
95
|
+
f"Group: {group_of(name)}. " + (f"Qt base: `{bases[0]}`, whose full API stays available." if bases else "Plain Python helper."), "",
|
|
96
|
+
"## Import", "", "```python", f"from fluent_blue import {name}", "```", "",
|
|
97
|
+
"## Signature", "", "```python", _signature(cls), "```", "",
|
|
98
|
+
]
|
|
99
|
+
props = [p for p in introspect.properties(cls)]
|
|
100
|
+
if props:
|
|
101
|
+
lines += ["## Parameters", "", "| Name | Default | Description |", "| --- | --- | --- |"]
|
|
102
|
+
lines += [f"| `{p.name}` | `{p.default}` | {introspect.clean_markup(p.description).replace('|', chr(92) + '|')} |" for p in props]
|
|
103
|
+
lines.append("")
|
|
104
|
+
signals = introspect.signals(cls)
|
|
105
|
+
note = introspect.inherited_signals_note(cls)
|
|
106
|
+
if signals or note:
|
|
107
|
+
lines += ["## Signals", ""]
|
|
108
|
+
lines += [f"- `{s.name}({s.args})`: {introspect.clean_markup(s.description)}" for s in signals]
|
|
109
|
+
if note:
|
|
110
|
+
lines.append(f"- {introspect.clean_markup(note)}")
|
|
111
|
+
lines.append("")
|
|
112
|
+
methods = _methods(cls)
|
|
113
|
+
if methods:
|
|
114
|
+
lines += ["## Public methods", ""]
|
|
115
|
+
lines += [f"- `{signature}`" + (f": {doc}" if doc else "") for signature, doc in methods]
|
|
116
|
+
lines.append("")
|
|
117
|
+
if entry:
|
|
118
|
+
lines += ["## Verified example", "", "This snippet is executed by the test suite on both Qt bindings.", "", "```python", entry.code.strip(), "```", ""]
|
|
119
|
+
lines += ["## Usage guidance", ""] + [f"- {tip}" for tip in entry.tips] + [""]
|
|
120
|
+
lines += ["## Project rules", "", PROJECT_RULES, ""]
|
|
121
|
+
return "\n".join(lines)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def library_prompt() -> str:
|
|
125
|
+
"""Return the brief for the whole library, suitable for ``AGENTS.md``."""
|
|
126
|
+
lines = [
|
|
127
|
+
"# Fluent Blue: guide for AI coding agents", "",
|
|
128
|
+
"Fluent Blue is a design system for Python desktop applications on PyQt6 and PySide6.", "",
|
|
129
|
+
"## Rules", "", PROJECT_RULES, "",
|
|
130
|
+
"## Quick start", "", "```python",
|
|
131
|
+
"from fluent_blue import Button, Card, ThemeManager",
|
|
132
|
+
"from fluent_blue.compatibility.qt import QApplication, QVBoxLayout", "",
|
|
133
|
+
"app = QApplication([])", 'ThemeManager.set_theme("light")', "card = Card()", "layout = QVBoxLayout(card)",
|
|
134
|
+
'layout.addWidget(Button("Get Started", variant="primary"))', "card.show()", "app.exec()", "```", "",
|
|
135
|
+
"## Component briefs", "",
|
|
136
|
+
"Read the brief for a component before you use or change it. Print one with `python -m fluent_blue.agent_prompts <Name>`.", "",
|
|
137
|
+
]
|
|
138
|
+
grouped: dict[str, list[str]] = {}
|
|
139
|
+
for name in public_classes():
|
|
140
|
+
grouped.setdefault(group_of(name), []).append(name)
|
|
141
|
+
for group in (*catalog.GROUPS, "Uncatalogued"):
|
|
142
|
+
if group in grouped:
|
|
143
|
+
lines += [f"### {group}", ""]
|
|
144
|
+
import fluent_blue
|
|
145
|
+
|
|
146
|
+
for name in grouped[group]:
|
|
147
|
+
lines.append(f"- [`{name}`](docs/ai/components/{name}.md): {introspect.summary(getattr(fluent_blue, name))}")
|
|
148
|
+
lines.append("")
|
|
149
|
+
return "\n".join(lines)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def llms_txt() -> str:
|
|
153
|
+
"""Return an index in the llms.txt format."""
|
|
154
|
+
import fluent_blue
|
|
155
|
+
|
|
156
|
+
lines = ["# Fluent Blue", "", "> A design system for Python desktop applications on PyQt6 and PySide6.", "", "## Components", ""]
|
|
157
|
+
lines += [f"- [{n}](docs/ai/components/{n}.md): {introspect.summary(getattr(fluent_blue, n))}" for n in public_classes()]
|
|
158
|
+
return "\n".join(lines) + "\n"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def export(root: Path) -> int:
|
|
162
|
+
"""Write ``AGENTS.md``, ``llms.txt`` and ``docs/ai/components/*.md``. Returns the brief count."""
|
|
163
|
+
folder = root / "docs" / "ai" / "components"
|
|
164
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
names = public_classes()
|
|
166
|
+
for stale in folder.glob("*.md"):
|
|
167
|
+
if stale.stem not in names:
|
|
168
|
+
stale.unlink()
|
|
169
|
+
for name in names:
|
|
170
|
+
(folder / f"{name}.md").write_text(component_prompt(name), encoding="utf-8")
|
|
171
|
+
(root / "AGENTS.md").write_text(library_prompt(), encoding="utf-8")
|
|
172
|
+
(root / "llms.txt").write_text(llms_txt(), encoding="utf-8")
|
|
173
|
+
return len(names)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def main(argv=None) -> int:
|
|
177
|
+
parser = argparse.ArgumentParser(description="Print or export Fluent Blue briefs for AI coding agents.")
|
|
178
|
+
parser.add_argument("component", nargs="?", help="Component name, such as Button.")
|
|
179
|
+
parser.add_argument("--list", action="store_true", help="List component names.")
|
|
180
|
+
parser.add_argument("--library", action="store_true", help="Print the whole-library brief.")
|
|
181
|
+
parser.add_argument("--export", type=Path, metavar="DIR", help="Write AGENTS.md, llms.txt and docs/ai/components/ under DIR.")
|
|
182
|
+
args = parser.parse_args(argv)
|
|
183
|
+
if args.export:
|
|
184
|
+
print(f"Wrote {export(args.export)} component briefs to {args.export}")
|
|
185
|
+
elif args.list:
|
|
186
|
+
print("\n".join(public_classes()))
|
|
187
|
+
elif args.library:
|
|
188
|
+
print(library_prompt())
|
|
189
|
+
elif args.component:
|
|
190
|
+
print(component_prompt(args.component))
|
|
191
|
+
else:
|
|
192
|
+
parser.print_help()
|
|
193
|
+
return 0
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Single import point for the active Qt binding.
|
|
2
|
+
|
|
3
|
+
Fluent Blue supports PyQt6 and PySide6 from the same source tree. Every module in
|
|
4
|
+
the package imports Qt names from here, so no other module knows which binding is
|
|
5
|
+
active. Selection order:
|
|
6
|
+
|
|
7
|
+
1. A binding already imported by the host application.
|
|
8
|
+
2. The ``FLUENT_BLUE_QT_BINDING`` environment variable (``PyQt6`` or ``PySide6``).
|
|
9
|
+
3. PyQt6, then PySide6, whichever is installed first.
|
|
10
|
+
|
|
11
|
+
Loading both bindings in one process is unsafe, so that raises immediately.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
SUPPORTED_BINDINGS = ("PyQt6", "PySide6")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BindingError(ImportError):
|
|
23
|
+
"""Raised when no usable Qt binding exists or the selection is conflicting."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _select_binding() -> str:
|
|
27
|
+
loaded = [name for name in SUPPORTED_BINDINGS if name in sys.modules]
|
|
28
|
+
if len(loaded) > 1:
|
|
29
|
+
raise BindingError("PyQt6 and PySide6 are both loaded; use only one per process.")
|
|
30
|
+
forced = os.environ.get("FLUENT_BLUE_QT_BINDING")
|
|
31
|
+
if forced and forced not in SUPPORTED_BINDINGS:
|
|
32
|
+
raise BindingError(f"FLUENT_BLUE_QT_BINDING must be one of {SUPPORTED_BINDINGS}, got {forced!r}.")
|
|
33
|
+
if loaded:
|
|
34
|
+
if forced and forced != loaded[0]:
|
|
35
|
+
raise BindingError(f"FLUENT_BLUE_QT_BINDING={forced} but {loaded[0]} is already loaded.")
|
|
36
|
+
return loaded[0]
|
|
37
|
+
if forced:
|
|
38
|
+
return forced
|
|
39
|
+
for name in SUPPORTED_BINDINGS:
|
|
40
|
+
try:
|
|
41
|
+
importlib.import_module(f"{name}.QtCore")
|
|
42
|
+
except ImportError:
|
|
43
|
+
continue
|
|
44
|
+
return name
|
|
45
|
+
raise BindingError("Fluent Blue needs a Qt binding: pip install 'fluent-blue[pyqt6]' or 'fluent-blue[pyside6]'.")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
BINDING = _select_binding()
|
|
49
|
+
|
|
50
|
+
# The Linux wheels of PySide6 6.9 to 6.12 corrupt the reference count of True/False whenever
|
|
51
|
+
# a Python-declared Signal is emitted (the macOS wheels do not). Python 3.12 makes those
|
|
52
|
+
# objects immortal, so the fault is harmless there; on 3.11 the interpreter aborts after a
|
|
53
|
+
# few hundred emits.
|
|
54
|
+
PYSIDE_MIN_PYTHON_ON_LINUX = (3, 12)
|
|
55
|
+
if BINDING == "PySide6" and sys.platform.startswith("linux") and sys.version_info < PYSIDE_MIN_PYTHON_ON_LINUX:
|
|
56
|
+
import warnings
|
|
57
|
+
|
|
58
|
+
warnings.warn(
|
|
59
|
+
"On Linux, Fluent Blue supports PySide6 on Python 3.12 or newer: the Linux wheels of PySide6 6.9 to 6.12 "
|
|
60
|
+
"corrupt reference counts when custom signals are emitted on Python 3.11, and the process can abort. "
|
|
61
|
+
"Use Python 3.12+ or PyQt6.",
|
|
62
|
+
RuntimeWarning, stacklevel=2,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
QtCore = importlib.import_module(f"{BINDING}.QtCore")
|
|
66
|
+
QtGui = importlib.import_module(f"{BINDING}.QtGui")
|
|
67
|
+
QtWidgets = importlib.import_module(f"{BINDING}.QtWidgets")
|
|
68
|
+
QtSvg = importlib.import_module(f"{BINDING}.QtSvg")
|
|
69
|
+
|
|
70
|
+
if BINDING == "PyQt6":
|
|
71
|
+
Signal = QtCore.pyqtSignal
|
|
72
|
+
Slot = QtCore.pyqtSlot
|
|
73
|
+
Property = QtCore.pyqtProperty
|
|
74
|
+
BINDING_VERSION = QtCore.PYQT_VERSION_STR
|
|
75
|
+
else:
|
|
76
|
+
Signal = QtCore.Signal
|
|
77
|
+
Slot = QtCore.Slot
|
|
78
|
+
Property = QtCore.Property
|
|
79
|
+
BINDING_VERSION = importlib.import_module(BINDING).__version__
|
|
80
|
+
|
|
81
|
+
QT_VERSION = QtCore.qVersion()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def delete_now(obj) -> None:
|
|
85
|
+
"""Destroy a QObject immediately, so app-wide restyling no longer has to visit it.
|
|
86
|
+
|
|
87
|
+
Deletion goes through Qt's deferred delete, which works identically on both bindings. Do
|
|
88
|
+
not disconnect Python slots first: ``disconnect`` of a bound method on PySide6 6.12 with
|
|
89
|
+
Python 3.11 corrupts the reference count of ``True``/``False`` and aborts the process
|
|
90
|
+
later, whereas destroying a connected receiver is clean.
|
|
91
|
+
"""
|
|
92
|
+
if isinstance(obj, QtWidgets.QWidget):
|
|
93
|
+
obj.hide()
|
|
94
|
+
obj.setParent(None)
|
|
95
|
+
obj.deleteLater()
|
|
96
|
+
QtCore.QCoreApplication.sendPostedEvents(obj, QtCore.QEvent.Type.DeferredDelete.value)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
SignalInstanceTypes = (QtCore.pyqtBoundSignal,) if BINDING == "PyQt6" else (QtCore.SignalInstance,)
|
|
100
|
+
SignalTypes = (QtCore.pyqtSignal,) if BINDING == "PyQt6" else (QtCore.Signal,)
|
|
101
|
+
|
|
102
|
+
# Re-export every Qt class so components can write
|
|
103
|
+
# ``from fluent_blue.compatibility.qt import QFrame, Qt, Signal``.
|
|
104
|
+
for _module in (QtCore, QtGui, QtWidgets, QtSvg):
|
|
105
|
+
for _name in dir(_module):
|
|
106
|
+
if _name.startswith("Q") and _name != "QT_VERSION":
|
|
107
|
+
globals()[_name] = getattr(_module, _name)
|
|
108
|
+
del _module, _name
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Fluent Blue component library."""
|
|
2
|
+
from .advanced import * # noqa: F403
|
|
3
|
+
from .buttons import * # noqa: F403
|
|
4
|
+
from .data import * # noqa: F403
|
|
5
|
+
from .display import * # noqa: F403
|
|
6
|
+
from .feedback import * # noqa: F403
|
|
7
|
+
from .inputs import * # noqa: F403
|
|
8
|
+
from .layout import * # noqa: F403
|
|
9
|
+
from .navigation import * # noqa: F403
|
|
10
|
+
from .selection import * # noqa: F403
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Advanced components."""
|
|
2
|
+
from .datetime_pickers import Calendar, DatePicker, TimePicker, TimePopup
|
|
3
|
+
from .editors import CodeEditor, LogViewer, RichTextArea, RichTextEditor, SyntaxHighlighter
|
|
4
|
+
from .pickers import ColorPicker, FilePicker
|
|
5
|
+
from .structure import FileTree, ImageViewer, Stepper, TreeView, Wizard
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Calendar", "CodeEditor", "ColorPicker", "DatePicker", "FilePicker", "FileTree", "ImageViewer", "LogViewer",
|
|
9
|
+
"RichTextArea", "RichTextEditor", "Stepper", "SyntaxHighlighter", "TimePicker", "TimePopup", "TreeView", "Wizard",
|
|
10
|
+
]
|