qt-css-engine 0.1.0__tar.gz

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.
Files changed (30) hide show
  1. qt_css_engine-0.1.0/.github/CODE_OF_CONDUCT.md +58 -0
  2. qt_css_engine-0.1.0/.github/CONTRIBUTING.md +34 -0
  3. qt_css_engine-0.1.0/.github/workflows/publish.yml +36 -0
  4. qt_css_engine-0.1.0/.github/workflows/test-publish.yml +33 -0
  5. qt_css_engine-0.1.0/.gitignore +14 -0
  6. qt_css_engine-0.1.0/LICENSE.md +9 -0
  7. qt_css_engine-0.1.0/PKG-INFO +187 -0
  8. qt_css_engine-0.1.0/README.md +162 -0
  9. qt_css_engine-0.1.0/demo/main.py +378 -0
  10. qt_css_engine-0.1.0/demo/styles.css +278 -0
  11. qt_css_engine-0.1.0/pyproject.toml +95 -0
  12. qt_css_engine-0.1.0/qt_css_engine/__init__.py +23 -0
  13. qt_css_engine-0.1.0/qt_css_engine/constants.py +190 -0
  14. qt_css_engine-0.1.0/qt_css_engine/css_parser.py +397 -0
  15. qt_css_engine-0.1.0/qt_css_engine/engine.py +1035 -0
  16. qt_css_engine-0.1.0/qt_css_engine/gradients.py +321 -0
  17. qt_css_engine-0.1.0/qt_css_engine/handlers.py +402 -0
  18. qt_css_engine-0.1.0/qt_css_engine/py.typed +0 -0
  19. qt_css_engine-0.1.0/qt_css_engine/qt_compat/QtCore.py +15 -0
  20. qt_css_engine-0.1.0/qt_css_engine/qt_compat/QtGui.py +10 -0
  21. qt_css_engine-0.1.0/qt_css_engine/qt_compat/QtWidgets.py +10 -0
  22. qt_css_engine-0.1.0/qt_css_engine/qt_compat/__init__.py +22 -0
  23. qt_css_engine-0.1.0/qt_css_engine/qt_compat/_api.py +15 -0
  24. qt_css_engine-0.1.0/qt_css_engine/types.py +77 -0
  25. qt_css_engine-0.1.0/qt_css_engine/utils.py +447 -0
  26. qt_css_engine-0.1.0/tests/conftest.py +14 -0
  27. qt_css_engine-0.1.0/tests/test_anim.py +2374 -0
  28. qt_css_engine-0.1.0/tests/test_css_parser.py +1285 -0
  29. qt_css_engine-0.1.0/tests/test_gradients.py +371 -0
  30. qt_css_engine-0.1.0/tests/test_interactions.py +41 -0
@@ -0,0 +1,58 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or advances of any kind
22
+ * Trolling, insulting or derogatory comments, and personal or political attacks
23
+ * Public or private harassment
24
+ * Publishing others’ private information, such as a physical or email address, without their explicit permission
25
+ * Other conduct which could reasonably be considered inappropriate in a professional setting
26
+
27
+ ## Enforcement Responsibilities
28
+
29
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30
+
31
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32
+
33
+ ## Scope
34
+
35
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces.
36
+
37
+ ## Enforcement
38
+
39
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement. All complaints will be reviewed and investigated promptly and fairly.
40
+
41
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42
+
43
+ ## Enforcement Guidelines
44
+
45
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
46
+
47
+ 1. **Correction**
48
+ 2. **Warning**
49
+ 3. **Temporary Ban**
50
+ 4. **Permanent Ban**
51
+
52
+ (See [Contributor Covenant Community Impact Guidelines](https://www.contributor-covenant.org/version/2/1/code_of_conduct/#enforcement-guidelines) for details.)
53
+
54
+ ## Attribution
55
+
56
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct/](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
57
+
58
+ [homepage]: https://www.contributor-covenant.org
@@ -0,0 +1,34 @@
1
+ ## Contributing
2
+
3
+ Contributions are welcome. Make sure to run the tests and check the demo app and all widgets before submitting a pull request.
4
+ Tests do not cover all the cases so visual confirmation is required.
5
+ Keep in mind that there might be big changes to the codebase so prepare to rebase your branch if needed.
6
+ Messy commit histories will be squashed before merging.
7
+
8
+ PR names and commit messages are in the format: `type(scope): short description`
9
+
10
+ Extra details must be added to the commit message after a newline so:
11
+
12
+ ```text
13
+ fix(css_parser): fix parsing of :hover pseudo-class
14
+
15
+ - :hover pseudo-class was not being parsed correctly in some cases
16
+ - added tests to cover that case
17
+ - more details here
18
+ ```
19
+
20
+ ## Dev commands
21
+
22
+ ```bash
23
+ # Install dependencies and run the demo
24
+ uv sync --all-extras
25
+ uv run ./demo/main.py
26
+
27
+ # Run the test suite
28
+ uv run pytest
29
+
30
+ # Check and format the code
31
+ uvx ruff check --fix
32
+ uvx ruff format
33
+ uvx basedpyright
34
+ ```
@@ -0,0 +1,36 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ runs-on: ubuntu-latest
11
+ environment: pypi
12
+ if: github.event.base_ref == 'refs/heads/main'
13
+ permissions:
14
+ id-token: write
15
+ contents: read
16
+
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - uses: astral-sh/setup-uv@v8.0.0
23
+ with:
24
+ python-version: "3.14"
25
+
26
+ - name: Build
27
+ run: uv build
28
+
29
+ - name: Smoke test wheel
30
+ run: uv run --isolated --no-project --with dist/*.whl -- python -c "import qt_css_engine"
31
+
32
+ - name: Smoke test sdist
33
+ run: uv run --isolated --no-project --with dist/*.tar.gz -- python -c "import qt_css_engine"
34
+
35
+ - name: Publish
36
+ run: uv publish
@@ -0,0 +1,33 @@
1
+ name: Publish to TestPyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ build-and-publish:
8
+ runs-on: ubuntu-latest
9
+ environment: testpypi
10
+ permissions:
11
+ id-token: write
12
+ contents: read
13
+
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ with:
17
+ fetch-depth: 0
18
+
19
+ - uses: astral-sh/setup-uv@v8.0.0
20
+ with:
21
+ python-version: "3.14"
22
+
23
+ - name: Build
24
+ run: uv build
25
+
26
+ - name: Smoke test wheel
27
+ run: uv run --isolated --no-project --with dist/*.whl -- python -c "import qt_css_engine"
28
+
29
+ - name: Smoke test sdist
30
+ run: uv run --isolated --no-project --with dist/*.tar.gz -- python -c "import qt_css_engine"
31
+
32
+ - name: Publish
33
+ run: uv publish --index testpypi
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[oc]
3
+ build/
4
+ dist/
5
+ wheels/
6
+ *.egg-info
7
+
8
+ .venv
9
+ .pytest_cache
10
+ .ruff_cache
11
+ .vscode
12
+ .python-version
13
+
14
+ uv.lock
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 George Sladkovsky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: qt-css-engine
3
+ Version: 0.1.0
4
+ Summary: CSS transition animations for PyQt6/PySide6 — extends static QSS with transition and other properties
5
+ Project-URL: Homepage, https://github.com/Video-Nomad/qt-css-engine
6
+ Project-URL: Repository, https://github.com/Video-Nomad/qt-css-engine
7
+ Project-URL: Issues, https://github.com/Video-Nomad/qt-css-engine/issues
8
+ Author-email: George Sladkovsky <george.sladkovsky@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE.md
11
+ Keywords: animation,css,pyqt6,qss,qt,stylesheet,transition
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: X11 Applications :: Qt
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: Software Development :: User Interfaces
22
+ Requires-Python: >=3.14
23
+ Requires-Dist: tinycss2>=1.5.1
24
+ Description-Content-Type: text/markdown
25
+
26
+ # Qt CSS Engine
27
+
28
+ A CSS animation engine for PyQt6/PySide6 that extends Qt's static stylesheet system (QSS) with dynamic CSS transitions and extra properties like `box-shadow`, `opacity` and CSS gradients. Qt's stylesheet engine has no concept of time or interpolation — this project implements an out-of-band animation system that intercepts `transition:` declarations from a stylesheet, tracks widget pseudo-states (hover, pressed, focus), and drives smooth property animations via Qt's animation framework. Dynamic class change is also supported so `.btn` -> `.btn.active` -> `.btn.other-state` will animate based on the `transition` property.
29
+
30
+ All that is required is to install `TransitionEngine` as an event filter and it will take care of the rest.
31
+
32
+ CSS Hot reload is supported via `TransitionEngine.reload_rules(new_rules)`
33
+
34
+ Subcontrols (e.g `::item`, `::handle`) are not supported because they are not real QWidgets. They will be just passed through as classic QSS stylesheet blocks.
35
+
36
+ This engine was made primarily for use in [YASB](https://github.com/amnweb/yasb) project and this will be the main focus for now, but it can be integrated into any Qt application.
37
+
38
+ **WARNING: This project is still in early development, very experimental and is not ready for production use. There will be bugs and breaking changes.**
39
+
40
+ ## Python and Qt version
41
+
42
+ Python 3.14+
43
+
44
+ PySide6/PyQt6 6.10+
45
+
46
+ ## Installation
47
+
48
+ ```
49
+ uv add qt-css-engine
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ```python
55
+ from qt_css_engine import TransitionEngine, extract_rules
56
+
57
+ app = QApplication([])
58
+ cleaned_qss, rules = extract_rules(stylesheet)
59
+ app.setStyleSheet(cleaned_qss)
60
+ engine = TransitionEngine(rules)
61
+ app.installEventFilter(engine)
62
+ ```
63
+
64
+ ## Simple transition examples
65
+
66
+ ```css
67
+ /* A simple widget with a hover transition */
68
+ .btn {
69
+ background-color: steelblue;
70
+ color: white;
71
+ border-radius: 4px;
72
+ transition: background-color 300ms ease;
73
+ }
74
+
75
+ .btn:hover {
76
+ background-color: royalblue;
77
+ }
78
+
79
+ /* A widget with a box-shadow transition */
80
+ #btn {
81
+ background-color: steelblue;
82
+ color: white;
83
+ border-radius: 4px;
84
+ box-shadow: 0px 0px 0px black;
85
+ transition: box-shadow 300ms ease;
86
+ }
87
+
88
+ #btn:hover {
89
+ box-shadow: 4px 4px 4px black;
90
+ }
91
+ ```
92
+
93
+ ## More complex examples
94
+
95
+ Check the `./demo/main.py` for hot reloading, dynamic class change and more.
96
+
97
+ ## Transition syntax and supported values
98
+
99
+ ### Shorthand
100
+
101
+ ```css
102
+ transition: <property> <duration> [<easing>] [<delay>];
103
+ transition: <property> <duration> [<delay>] [<easing>];
104
+
105
+ /* multiple */
106
+ transition: background-color 300ms ease, border-radius 200ms linear 50ms;
107
+
108
+ /* all animatable properties */
109
+ transition: all 300ms ease-in-out;
110
+ ```
111
+
112
+ ### Longhands
113
+
114
+ ```css
115
+ transition-property: background-color, border-radius;
116
+ transition-duration: 300ms, 200ms;
117
+ transition-timing-function: ease, linear;
118
+ transition-delay: 0ms, 50ms;
119
+ ```
120
+
121
+ Longhands override the shorthand when both are declared in the same block. Values cycle per the CSS spec when list lengths differ.
122
+
123
+ ### Time units
124
+
125
+ | Unit | Example |
126
+ | --- | --- |
127
+ | Milliseconds | `300ms` |
128
+ | Seconds | `0.3s` |
129
+
130
+ ### Easing curves
131
+
132
+ | Value | Description |
133
+ | --- | --- |
134
+ | `linear` | Constant speed |
135
+ | `ease` | Slow in, slow out (default) |
136
+ | `ease-in` | Slow start |
137
+ | `ease-out` | Slow end |
138
+ | `ease-in-out` | Slow start and end |
139
+ | `cubic-bezier(x1, y1, x2, y2)` | Custom curve — values outside `[0, 1]` produce overshoot |
140
+ | `steps(n)` / `steps(n, jump-end\|jump-start\|jump-none\|jump-both)` | Discrete stepped animation |
141
+ | `step-start` / `step-end` | Aliases for `steps(1, jump-start)` / `steps(1, jump-end)` |
142
+
143
+ ### Delay
144
+
145
+ Positive delay: animation starts after the delay elapses. The property is frozen at its current rendered value during the delay period.
146
+
147
+ Negative delay: animation starts immediately but offset `|delay|` ms into the timeline, as if it had already been running that long.
148
+
149
+ ```css
150
+ transition: background 400ms ease 100ms; /* 100ms positive delay */
151
+ transition: background 400ms ease -100ms; /* starts 100ms in */
152
+ /* OR */
153
+ transition: background 400ms 100ms ease;
154
+ transition: background 400ms -100ms ease;
155
+ ```
156
+
157
+ ## Supported properties
158
+
159
+ Color values accepted everywhere a color is listed: named (`red`, `steelblue`, …), `#rrggbb`, `#rrggbbaa`, `rgb()`, `rgba()`, `hsl()`, `hsla()`.
160
+
161
+ Numeric values accepted everywhere a length is listed: `<n>px`, `<n>pt`, `<n>em`.
162
+
163
+ | Property | Description | Supported values | Transition | Static |
164
+ | --- | --- | --- | --- | --- |
165
+ | `background-color` / `background` | Background color | color values; `linear-gradient()`, `radial-gradient()`, `conic-gradient()` (static only) | ✅ solid colors | ✅ |
166
+ | `color` | Text color | color values | ✅ | ✅ |
167
+ | `border-color` | Border color shorthand (→ 4 sides) | color values | ✅ | ✅ |
168
+ | `border-top-color`, `border-right-color`, `border-bottom-color`, `border-left-color` | Per-side border color | color values | ✅ | ✅ |
169
+ | `border-width` | Border width shorthand (→ 4 sides) | length values | ✅ | ✅ |
170
+ | `border-top-width`, `border-right-width`, `border-bottom-width`, `border-left-width` | Per-side border width | length values | ✅ | ✅ |
171
+ | `border-radius` | Border radius shorthand (→ 4 corners) | length values | ✅ | ✅ |
172
+ | `border-top-left-radius`, `border-top-right-radius`, `border-bottom-right-radius`, `border-bottom-left-radius` | Per-corner border radius | length values | ✅ | ✅ |
173
+ | `padding` | Padding shorthand (→ 4 sides) | length values | ✅ | ✅ |
174
+ | `padding-top`, `padding-right`, `padding-bottom`, `padding-left` | Per-side padding | length values | ✅ | ✅ |
175
+ | `margin` | Margin shorthand (→ 4 sides) | length values | ✅ | ✅ |
176
+ | `margin-top`, `margin-right`, `margin-bottom`, `margin-left` | Per-side margin | length values | ✅ | ✅ |
177
+ | `width`, `height` | Widget size | length values | ✅ | ✅ |
178
+ | `min-width`, `max-width`, `min-height`, `max-height` | Size constraints | length values | ✅ | ✅ |
179
+ | `font-size` | Font size | length values | ✅ | ✅ |
180
+ | `font-weight` | Font weight | `100`–`900` | ✅ | ✅ |
181
+ | `letter-spacing` | Letter spacing | length values | ✅ | ✅ |
182
+ | `word-spacing` | Word spacing | length values | ✅ | ✅ |
183
+ | `spacing` | Qt widget item spacing | length values | ✅ | ✅ |
184
+ | `opacity` | Widget opacity — not native QSS, applied via `QGraphicsOpacityEffect` | `0.0`–`1.0` | ✅ | ✅ |
185
+ | `box-shadow` | Drop shadow — not native QSS, applied via `QGraphicsDropShadowEffect`. No `inset`, `spread` is ignored. First shadow wins when multiple are declared. | `<x> <y> [blur] [spread] <color>` | ✅ | ✅ |
186
+ | `cursor` | Mouse cursor — Qt QSS ignores `cursor`, applied via `setCursor()` | `default`, `pointer`, `text`, `crosshair`, `wait`, `progress`, `help`, `move`, `grab`, `grabbing`, `copy`, `alias`, `not-allowed`, `no-drop`, `cell`, `all-scroll`, `n-resize`, `s-resize`, `e-resize`, `w-resize`, `ne-resize`, `nw-resize`, `se-resize`, `sw-resize`, `ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `row-resize`, `col-resize`, `none` | ❌ | ✅ |
187
+
@@ -0,0 +1,162 @@
1
+ # Qt CSS Engine
2
+
3
+ A CSS animation engine for PyQt6/PySide6 that extends Qt's static stylesheet system (QSS) with dynamic CSS transitions and extra properties like `box-shadow`, `opacity` and CSS gradients. Qt's stylesheet engine has no concept of time or interpolation — this project implements an out-of-band animation system that intercepts `transition:` declarations from a stylesheet, tracks widget pseudo-states (hover, pressed, focus), and drives smooth property animations via Qt's animation framework. Dynamic class change is also supported so `.btn` -> `.btn.active` -> `.btn.other-state` will animate based on the `transition` property.
4
+
5
+ All that is required is to install `TransitionEngine` as an event filter and it will take care of the rest.
6
+
7
+ CSS Hot reload is supported via `TransitionEngine.reload_rules(new_rules)`
8
+
9
+ Subcontrols (e.g `::item`, `::handle`) are not supported because they are not real QWidgets. They will be just passed through as classic QSS stylesheet blocks.
10
+
11
+ This engine was made primarily for use in [YASB](https://github.com/amnweb/yasb) project and this will be the main focus for now, but it can be integrated into any Qt application.
12
+
13
+ **WARNING: This project is still in early development, very experimental and is not ready for production use. There will be bugs and breaking changes.**
14
+
15
+ ## Python and Qt version
16
+
17
+ Python 3.14+
18
+
19
+ PySide6/PyQt6 6.10+
20
+
21
+ ## Installation
22
+
23
+ ```
24
+ uv add qt-css-engine
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from qt_css_engine import TransitionEngine, extract_rules
31
+
32
+ app = QApplication([])
33
+ cleaned_qss, rules = extract_rules(stylesheet)
34
+ app.setStyleSheet(cleaned_qss)
35
+ engine = TransitionEngine(rules)
36
+ app.installEventFilter(engine)
37
+ ```
38
+
39
+ ## Simple transition examples
40
+
41
+ ```css
42
+ /* A simple widget with a hover transition */
43
+ .btn {
44
+ background-color: steelblue;
45
+ color: white;
46
+ border-radius: 4px;
47
+ transition: background-color 300ms ease;
48
+ }
49
+
50
+ .btn:hover {
51
+ background-color: royalblue;
52
+ }
53
+
54
+ /* A widget with a box-shadow transition */
55
+ #btn {
56
+ background-color: steelblue;
57
+ color: white;
58
+ border-radius: 4px;
59
+ box-shadow: 0px 0px 0px black;
60
+ transition: box-shadow 300ms ease;
61
+ }
62
+
63
+ #btn:hover {
64
+ box-shadow: 4px 4px 4px black;
65
+ }
66
+ ```
67
+
68
+ ## More complex examples
69
+
70
+ Check the `./demo/main.py` for hot reloading, dynamic class change and more.
71
+
72
+ ## Transition syntax and supported values
73
+
74
+ ### Shorthand
75
+
76
+ ```css
77
+ transition: <property> <duration> [<easing>] [<delay>];
78
+ transition: <property> <duration> [<delay>] [<easing>];
79
+
80
+ /* multiple */
81
+ transition: background-color 300ms ease, border-radius 200ms linear 50ms;
82
+
83
+ /* all animatable properties */
84
+ transition: all 300ms ease-in-out;
85
+ ```
86
+
87
+ ### Longhands
88
+
89
+ ```css
90
+ transition-property: background-color, border-radius;
91
+ transition-duration: 300ms, 200ms;
92
+ transition-timing-function: ease, linear;
93
+ transition-delay: 0ms, 50ms;
94
+ ```
95
+
96
+ Longhands override the shorthand when both are declared in the same block. Values cycle per the CSS spec when list lengths differ.
97
+
98
+ ### Time units
99
+
100
+ | Unit | Example |
101
+ | --- | --- |
102
+ | Milliseconds | `300ms` |
103
+ | Seconds | `0.3s` |
104
+
105
+ ### Easing curves
106
+
107
+ | Value | Description |
108
+ | --- | --- |
109
+ | `linear` | Constant speed |
110
+ | `ease` | Slow in, slow out (default) |
111
+ | `ease-in` | Slow start |
112
+ | `ease-out` | Slow end |
113
+ | `ease-in-out` | Slow start and end |
114
+ | `cubic-bezier(x1, y1, x2, y2)` | Custom curve — values outside `[0, 1]` produce overshoot |
115
+ | `steps(n)` / `steps(n, jump-end\|jump-start\|jump-none\|jump-both)` | Discrete stepped animation |
116
+ | `step-start` / `step-end` | Aliases for `steps(1, jump-start)` / `steps(1, jump-end)` |
117
+
118
+ ### Delay
119
+
120
+ Positive delay: animation starts after the delay elapses. The property is frozen at its current rendered value during the delay period.
121
+
122
+ Negative delay: animation starts immediately but offset `|delay|` ms into the timeline, as if it had already been running that long.
123
+
124
+ ```css
125
+ transition: background 400ms ease 100ms; /* 100ms positive delay */
126
+ transition: background 400ms ease -100ms; /* starts 100ms in */
127
+ /* OR */
128
+ transition: background 400ms 100ms ease;
129
+ transition: background 400ms -100ms ease;
130
+ ```
131
+
132
+ ## Supported properties
133
+
134
+ Color values accepted everywhere a color is listed: named (`red`, `steelblue`, …), `#rrggbb`, `#rrggbbaa`, `rgb()`, `rgba()`, `hsl()`, `hsla()`.
135
+
136
+ Numeric values accepted everywhere a length is listed: `<n>px`, `<n>pt`, `<n>em`.
137
+
138
+ | Property | Description | Supported values | Transition | Static |
139
+ | --- | --- | --- | --- | --- |
140
+ | `background-color` / `background` | Background color | color values; `linear-gradient()`, `radial-gradient()`, `conic-gradient()` (static only) | ✅ solid colors | ✅ |
141
+ | `color` | Text color | color values | ✅ | ✅ |
142
+ | `border-color` | Border color shorthand (→ 4 sides) | color values | ✅ | ✅ |
143
+ | `border-top-color`, `border-right-color`, `border-bottom-color`, `border-left-color` | Per-side border color | color values | ✅ | ✅ |
144
+ | `border-width` | Border width shorthand (→ 4 sides) | length values | ✅ | ✅ |
145
+ | `border-top-width`, `border-right-width`, `border-bottom-width`, `border-left-width` | Per-side border width | length values | ✅ | ✅ |
146
+ | `border-radius` | Border radius shorthand (→ 4 corners) | length values | ✅ | ✅ |
147
+ | `border-top-left-radius`, `border-top-right-radius`, `border-bottom-right-radius`, `border-bottom-left-radius` | Per-corner border radius | length values | ✅ | ✅ |
148
+ | `padding` | Padding shorthand (→ 4 sides) | length values | ✅ | ✅ |
149
+ | `padding-top`, `padding-right`, `padding-bottom`, `padding-left` | Per-side padding | length values | ✅ | ✅ |
150
+ | `margin` | Margin shorthand (→ 4 sides) | length values | ✅ | ✅ |
151
+ | `margin-top`, `margin-right`, `margin-bottom`, `margin-left` | Per-side margin | length values | ✅ | ✅ |
152
+ | `width`, `height` | Widget size | length values | ✅ | ✅ |
153
+ | `min-width`, `max-width`, `min-height`, `max-height` | Size constraints | length values | ✅ | ✅ |
154
+ | `font-size` | Font size | length values | ✅ | ✅ |
155
+ | `font-weight` | Font weight | `100`–`900` | ✅ | ✅ |
156
+ | `letter-spacing` | Letter spacing | length values | ✅ | ✅ |
157
+ | `word-spacing` | Word spacing | length values | ✅ | ✅ |
158
+ | `spacing` | Qt widget item spacing | length values | ✅ | ✅ |
159
+ | `opacity` | Widget opacity — not native QSS, applied via `QGraphicsOpacityEffect` | `0.0`–`1.0` | ✅ | ✅ |
160
+ | `box-shadow` | Drop shadow — not native QSS, applied via `QGraphicsDropShadowEffect`. No `inset`, `spread` is ignored. First shadow wins when multiple are declared. | `<x> <y> [blur] [spread] <color>` | ✅ | ✅ |
161
+ | `cursor` | Mouse cursor — Qt QSS ignores `cursor`, applied via `setCursor()` | `default`, `pointer`, `text`, `crosshair`, `wait`, `progress`, `help`, `move`, `grab`, `grabbing`, `copy`, `alias`, `not-allowed`, `no-drop`, `cell`, `all-scroll`, `n-resize`, `s-resize`, `e-resize`, `w-resize`, `ne-resize`, `nw-resize`, `se-resize`, `sw-resize`, `ns-resize`, `ew-resize`, `nesw-resize`, `nwse-resize`, `row-resize`, `col-resize`, `none` | ❌ | ✅ |
162
+