BreezeWidget
This repository is an entirely vibe-coded, work-in-progress project for PySide6. The API is not yet final, internal structures may change, and the repository is currently intended primarily for development, testing, experimentation, and migrations.
BreezeWidget is a clean-room, local PySide6 widget and theming layer with a Breeze-inspired look. The project aims to provide Qt/PySide6 desktop apps with consistent windows, theme management, icons, navigation, status components, input controls, cards, views, and demo interfaces, without depending on an external GPL widget library.
Current Status
The package is pre-1.0 and is being actively restructured. Its public API is documented in the API contract and covered by tests; anything outside that documented interface is considered internal. The repository already contains working demos, tests, local icons, theme and stylesheet infrastructure, and a growing collection of PySide6 components, but it is not yet a stable framework release.
What You Can Do
- Install BreezeWidget locally and use it in your own PySide6 prototypes.
- Install published releases directly from PyPI using
pip install. - Run the demo applications to explore the current UI components.
- Run the tests to verify the documented public API and core behavior.
- Build your own widgets or stylesheet fragments using the existing theme and icon infrastructure.
- Freely fork, adapt, and reuse the project.
Install from PyPI
python -m pip install breeze-widget
The PyPI package is named breeze-widget; the Python import is
breezewidget.
Install for Development
.\.venv\Scripts\python.exe -m pip install -e ".[test]"
Run the Demos
.\.venv\Scripts\python.exe .\examples\breeze_demo.py
.\.venv\Scripts\python.exe .\examples\widget_demo.py
Run the Tests
$env:QT_QPA_PLATFORM = "offscreen"
.\.venv\Scripts\python.exe -m pytest
Public API
The root imports from breezewidget and the curated subsystem modules
breezewidget.window, breezewidget.theme, breezewidget.icons,
breezewidget.navigation, and breezewidget.rich_text form the public interface. Details, symbol names,
stable Qt dynamic properties, and internal areas are documented in the API
contract in the API contract.
Avoid direct imports from deeper implementation modules, as these may be moved or renamed during refactoring.
Rich-text table cells (opt-in)
from PySide6.QtWidgets import QTableWidgetItem
from breezewidget import (
TableWidget, RichTextTableItemDelegate, RichTextSegment, RICH_TEXT_ROLE,
)
table = TableWidget()
table.setRowCount(2)
table.setColumnCount(1)
table.setColumnWidth(0, 150)
delegate = RichTextTableItemDelegate(table)
table.setItemDelegate(delegate)
item = QTableWidgetItem("Mathe Sport")
item.setData(RICH_TEXT_ROLE, (
RichTextSegment("Mathe", "#008000"), RichTextSegment(" Sport"),
))
table.setItem(0, 0, item)
table.setItem(1, 0, QTableWidgetItem("a" * 100))
table.setMinimumRowHeight(0, 72)
table.setMinimumRowHeight(1, 36)
table.setAutoRowHeightEnabled(True)
The delegate wraps long words with QTextOption.WrapAtWordBoundaryOrAnywhere;
pass wrapMode= to choose another mode. Display and editing share the same
document width, font and padding. Row sizing is separately opt-in and works
with other delegates too. Rows grow and shrink to their content/minimum after
model edits, column resizing, font/theme/DPI changes and row insertion/removal.
Horizontal spans use the full visible cell width, skip covered cell contents,
and reflow when setSpan or clearSpans changes the layout.
Disabling sizing leaves current row heights in place. Ordinary tables retain
their default delegate and height behavior.
Double-click to edit. Enter adds a paragraph; Tab/Shift+Tab commit and move;
Escape cancels. Select text and right-click → Text color to use a labeled
swatch or the Breeze custom color dialog. Color changes form one undo step,
including a selection spanning paragraphs. Menus and dialogs keep the active
editor and its selection alive. External rich-text paste into cells becomes
plain text. Standalone RichTextEdit provides the same menu with the usual
90-pixel minimum; table editors use a zero minimum.
The immutable segment tuple contains only text and optional RGB #rrggbb
foreground colors. None follows the item's foreground/theme. DisplayRole and
EditRole contain the exact concatenated text. Missing, malformed or mismatched
metadata displays as plain text. richTextRole= selects another user role;
the default is Qt.UserRole + 1. All four symbols are also available from the
stable breezewidget.rich_text module.
Use editor.setSegments(...), editor.segments() and editor.plainText() to
round-trip data. Adjacent segments with equal color merge. A paragraph break
is \n, a soft break within a paragraph is U+2028, and an automatic line wrap
adds no text. Unlike Qt's toPlainText(), plainText() preserves soft breaks
and nonbreaking spaces. Empty paragraphs, emoji and color boundaries within
combining-character or emoji sequences also survive. This paragraph
representation leaves room for later block metadata without changing segments.
Paragraph separators retain their own color, including leading/trailing and
empty paragraphs.
Persist from delegate.modelUpdated, which fires after successful updates
to plain text and the full segment tuple:
def save_cell(index):
text = index.data() # DisplayRole; EditRole has the same text
segments = index.data(delegate.richTextRole())
# Serialize/save both values in your application here.
delegate.modelUpdated.connect(save_cell)
The delegate calls model.setItemData with all three roles. Custom models must
support them; implement transactional validation there if rejection is possible.
A rejected update emits no modelUpdated. The emitted index follows the edited
item if the update sorts the model; no signal fires if the update removes the
item or resets the model. Raw dataChanged/itemChanged
notifications can expose intermediate roles in custom models, so use
modelUpdated for persistence. BreezeWidget does not serialize application data
or commit an active editor merely because a save button was pressed.
Checklist paragraphs (opt-in)
from breezewidget import RichTextBlock, RICH_TEXT_BLOCKS_ROLE
delegate.setChecklistsEnabled(True)
blocks = (
RichTextBlock((RichTextSegment("Copy worksheets", "#008000"),), True),
RichTextBlock((RichTextSegment("Lesson preparation"),)), # ordinary paragraph
RichTextBlock((RichTextSegment("Inform parents"),), False),
RichTextBlock((RichTextSegment("Prepare the exam"),), False),
)
item = QTableWidgetItem("\n".join("".join(s.text for s in b.segments) for b in blocks))
item.setData(RICH_TEXT_BLOCKS_ROLE, blocks)
table.setItem(0, 0, item)
Click a task marker once to toggle it, including outside the editor. Ordinary
text clicks keep the view's normal selection/editing behavior. A cell's native
CheckStateRole checkbox remains independent. Long task paragraphs wrap with
one marker, and automatic row sizing includes list indentation.
In an enabled editor, right-click Insert checkbox, Remove checkbox, or Toggle completed. The actions also work from the keyboard context menu. Insert/remove apply to every selected paragraph; a selection ending exactly at the next paragraph start excludes that paragraph. Existing task states and text colors survive conversion. Enter at a task's end creates an open task; Enter in an empty task ends the list. Insert, remove and toggle each support undo/redo. Multiline plain-text paste/insertion also creates open tasks in one undo step. At the paragraph start the original task state follows its existing text; otherwise it stays with the first part, as when pressing Enter. Standalone HTML clipboard content keeps its incoming text formatting and uses the same task-state rule. Replacing a selection preserves its insertion format. Surviving prefix/suffix paragraphs keep their own states; fully removed tasks do not transfer completion to pasted text. If surviving text joins into one paragraph, its prefix state takes precedence.
RichTextBlock(segments, checked=None, *, separatorColor=None) is frozen.
segments must be a tuple of RichTextSegment without paragraph separators;
U+2028 soft breaks remain valid. checked accepts only True, False, or
None (ordinary text). Blocks are joined with LF. The optional RGB
separatorColor preserves the color of the LF following that block, including
empty paragraphs; it is ignored for the final block. Text, emoji, NBSP, colors
and states round-trip through editor.setBlocks(...) / editor.blocks().
Adjacent equal-color segments merge. Loading blocks clears undo history and
does not itself enable interaction.
Use setChecklistsEnabled(True) on a standalone RichTextEdit or the delegate;
isChecklistsEnabled() reports the switch, which defaults to False. Public
editor actions are insertChecklist(), removeChecklist(), and
toggleChecklistItem(). Both editor and delegate offer
setChecklistMenuLabels(insert, remove, toggle) for application translations,
for example ("Als Checkliste", "Checkbox entfernen", "Erledigt umschalten").
Defaults use Qt translation; delegate labels apply to newly created editors.
Read-only and disabled editors/items cannot change task state through actions.
The default block role is 258, or 259 when the existing custom color role is
258. Omitting richTextBlocksRole or passing None chooses this free default;
pass an explicit user role to choose another role distinct from richTextRole=
(default 257). Both have
matching accessors. With checklists enabled, valid blocks whose text matches
the model are authoritative over color metadata. Missing, invalid or stale
blocks fall back to the existing color/plain-text behavior without overwriting
plain text. Disabled delegates keep their previous color-only behavior.
Enabled commits and external toggles submit all four roles together via
setItemData: DisplayRole, EditRole, color segments, and blocks. Persist all
of them from modelUpdated. External toggles additionally emit
checklistItemToggled(index, block_index, checked) once after accepted updates;
editor toggles use the normal commit. Rejection emits neither success signal.
Persistent indexes follow synchronous sorting; reset/removal suppresses signals,
including a reset/removal in a modelUpdated subscriber before the toggle
signal. Custom models own transactional validation/rollback. BreezeWidget
does not add application persistence or Excel dependencies.
Default labels use Qt translation. Customize a standalone editor directly, or
customize delegate-created editors through the standard createEditor hook:
class LocalizedDelegate(RichTextTableItemDelegate):
def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
editor.setColorPresets([("Grün", "#008000"), ("Rot", "#ff0000")])
editor.setColorMenuLabels("Textfarbe", "Eigene Farbe …")
return editor
The data-views gallery includes narrow columns, long words, six manual lines, multiple colors and different row minimums.
License
BreezeWidget is released under the MIT License. You may freely use, copy,
modify, and redistribute the project at no cost. See LICENSE
for details.
Metadata
Release files for breeze-widget 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| breeze_widget-0.6.0.tar.gz | 177.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| breeze_widget-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 351.6 kB
Release files / breeze_widget-0.6.0.tar.gz
| Download URL | breeze_widget-0.6.0.tar.gz |
|---|---|
| Size | 177.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
03c11e12a9439d74540671247ff3dad81748052e32eff919e4e547dc014429ba
|
|
BLAKE2b-256 checksum How to use checksums |
d32257f76cc5a6dcf858546721a9a873d49af83cf07b4892874216ae0a24d706
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.
Transparency logRelease files / breeze_widget-0.6.0-py3-none-any.whl
| Download URL | breeze_widget-0.6.0-py3-none-any.whl |
|---|---|
| Size | 174.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7b713a4be0e01e94257671d5ef7acea89eba384344a5bf54cf08a374f9e97f3d
|
|
BLAKE2b-256 checksum How to use checksums |
4e8081ca071a752786d31cac390201b4b936c4fdacf938ccbf0cbf22a71e08f3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.
Transparency log