pycforge 0.15.2__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.
Files changed (221) hide show
  1. pycforge/__init__.py +8 -0
  2. pycforge/__main__.py +2 -0
  3. pycforge/_version.py +1 -0
  4. pycforge/converter/__init__.py +0 -0
  5. pycforge/converter/analysis/__init__.py +5 -0
  6. pycforge/converter/analysis/functions.py +723 -0
  7. pycforge/converter/analysis/model.py +158 -0
  8. pycforge/converter/analysis/planning.py +464 -0
  9. pycforge/converter/analysis/stage.py +1976 -0
  10. pycforge/converter/analysis/symbols.py +237 -0
  11. pycforge/converter/analysis/validation.py +2166 -0
  12. pycforge/converter/c_output/__init__.py +2 -0
  13. pycforge/converter/c_output/conformance.py +297 -0
  14. pycforge/converter/c_output/renderer.py +220 -0
  15. pycforge/converter/conditional_regions/__init__.py +71 -0
  16. pycforge/converter/conditional_regions/analysis.py +481 -0
  17. pycforge/converter/conditional_regions/lowering.py +579 -0
  18. pycforge/converter/conditional_regions/model.py +243 -0
  19. pycforge/converter/conditional_regions/validation.py +690 -0
  20. pycforge/converter/containers/__init__.py +19 -0
  21. pycforge/converter/containers/analysis.py +542 -0
  22. pycforge/converter/containers/lowering.py +290 -0
  23. pycforge/converter/containers/model.py +146 -0
  24. pycforge/converter/contracts/__init__.py +0 -0
  25. pycforge/converter/contracts/configuration.py +178 -0
  26. pycforge/converter/contracts/identifiers.py +68 -0
  27. pycforge/converter/contracts/versions.py +71 -0
  28. pycforge/converter/core/__init__.py +0 -0
  29. pycforge/converter/core/artifact_io.py +87 -0
  30. pycforge/converter/core/cancellation.py +7 -0
  31. pycforge/converter/core/canonicalization.py +254 -0
  32. pycforge/converter/core/context.py +13 -0
  33. pycforge/converter/core/diagnostics.py +95 -0
  34. pycforge/converter/core/enums.py +23 -0
  35. pycforge/converter/core/fingerprint.py +22 -0
  36. pycforge/converter/core/pipeline.py +8 -0
  37. pycforge/converter/core/progress.py +25 -0
  38. pycforge/converter/core/request.py +50 -0
  39. pycforge/converter/core/resource_policy.py +31 -0
  40. pycforge/converter/core/result.py +23 -0
  41. pycforge/converter/core/serialization.py +36 -0
  42. pycforge/converter/core/stage.py +10 -0
  43. pycforge/converter/core/stage_artifact.py +80 -0
  44. pycforge/converter/core/stage_outcome.py +15 -0
  45. pycforge/converter/decision_trace/__init__.py +0 -0
  46. pycforge/converter/decision_trace/recorder.py +52 -0
  47. pycforge/converter/facade.py +459 -0
  48. pycforge/converter/frontend/__init__.py +4 -0
  49. pycforge/converter/frontend/normalizer.py +59 -0
  50. pycforge/converter/frontend/parser.py +20 -0
  51. pycforge/converter/frontend/source_document.py +93 -0
  52. pycforge/converter/frontend/stages.py +518 -0
  53. pycforge/converter/frontend/validation.py +76 -0
  54. pycforge/converter/io/__init__.py +0 -0
  55. pycforge/converter/io/atomic_writer.py +33 -0
  56. pycforge/converter/ir/__init__.py +1 -0
  57. pycforge/converter/ir/c_ir/__init__.py +4 -0
  58. pycforge/converter/ir/c_ir/builder.py +26 -0
  59. pycforge/converter/ir/c_ir/model.py +318 -0
  60. pycforge/converter/ir/c_ir/serialization.py +88 -0
  61. pycforge/converter/ir/c_ir/validation.py +834 -0
  62. pycforge/converter/ir/python_ir/__init__.py +17 -0
  63. pycforge/converter/ir/python_ir/nodes.py +86 -0
  64. pycforge/converter/keyword_calls/__init__.py +68 -0
  65. pycforge/converter/keyword_calls/analysis.py +458 -0
  66. pycforge/converter/keyword_calls/lowering.py +271 -0
  67. pycforge/converter/keyword_calls/model.py +304 -0
  68. pycforge/converter/keyword_calls/validation.py +802 -0
  69. pycforge/converter/keyword_only_calls/__init__.py +68 -0
  70. pycforge/converter/keyword_only_calls/analysis.py +640 -0
  71. pycforge/converter/keyword_only_calls/lowering.py +376 -0
  72. pycforge/converter/keyword_only_calls/model.py +328 -0
  73. pycforge/converter/keyword_only_calls/validation.py +1357 -0
  74. pycforge/converter/lowering.py +1000 -0
  75. pycforge/converter/modules/__init__.py +13 -0
  76. pycforge/converter/modules/analysis.py +1250 -0
  77. pycforge/converter/modules/lowering.py +200 -0
  78. pycforge/converter/modules/model.py +73 -0
  79. pycforge/converter/modules/stage.py +192 -0
  80. pycforge/converter/numeric_semantics/__init__.py +23 -0
  81. pycforge/converter/numeric_semantics/analysis.py +420 -0
  82. pycforge/converter/numeric_semantics/lowering.py +153 -0
  83. pycforge/converter/numeric_semantics/model.py +123 -0
  84. pycforge/converter/records/__init__.py +34 -0
  85. pycforge/converter/records/analysis.py +1394 -0
  86. pycforge/converter/records/lowering.py +393 -0
  87. pycforge/converter/records/model.py +319 -0
  88. pycforge/converter/rules/__init__.py +3 -0
  89. pycforge/converter/support_templates/__init__.py +49 -0
  90. pycforge/converter/support_templates/assembly.py +106 -0
  91. pycforge/converter/support_templates/factories.py +348 -0
  92. pycforge/converter/support_templates/model.py +159 -0
  93. pycforge/converter/support_templates/registry.py +388 -0
  94. pycforge/converter/telemetry/__init__.py +0 -0
  95. pycforge/converter/telemetry/sink.py +39 -0
  96. pycforge/ide/__init__.py +19 -0
  97. pycforge/ide/__main__.py +5 -0
  98. pycforge/ide/_worker_event_codec.py +276 -0
  99. pycforge/ide/_worker_protocol_json.py +197 -0
  100. pycforge/ide/_worker_protocol_types.py +90 -0
  101. pycforge/ide/_worker_request_codec.py +200 -0
  102. pycforge/ide/_worker_request_data.py +260 -0
  103. pycforge/ide/_worker_result_data.py +343 -0
  104. pycforge/ide/action_contract.py +469 -0
  105. pycforge/ide/action_validation.py +229 -0
  106. pycforge/ide/command_palette.py +223 -0
  107. pycforge/ide/controller.py +904 -0
  108. pycforge/ide/controller_conversion.py +546 -0
  109. pycforge/ide/controller_io.py +205 -0
  110. pycforge/ide/editor.py +951 -0
  111. pycforge/ide/editor_commands_qt.py +274 -0
  112. pycforge/ide/editor_lexical.py +43 -0
  113. pycforge/ide/editor_sidebars.py +236 -0
  114. pycforge/ide/editor_syntax.py +243 -0
  115. pycforge/ide/find_replace.py +535 -0
  116. pycforge/ide/icons.py +91 -0
  117. pycforge/ide/io_service.py +196 -0
  118. pycforge/ide/model.py +164 -0
  119. pycforge/ide/panels.py +903 -0
  120. pycforge/ide/positions.py +220 -0
  121. pycforge/ide/process_worker.py +130 -0
  122. pycforge/ide/qt.py +230 -0
  123. pycforge/ide/qt_actions.py +418 -0
  124. pycforge/ide/qt_close.py +169 -0
  125. pycforge/ide/qt_command_palette.py +297 -0
  126. pycforge/ide/qt_contract.py +220 -0
  127. pycforge/ide/qt_documents.py +590 -0
  128. pycforge/ide/qt_editor_buffers.py +113 -0
  129. pycforge/ide/qt_editor_surfaces.py +112 -0
  130. pycforge/ide/qt_menus.py +324 -0
  131. pycforge/ide/qt_projection.py +500 -0
  132. pycforge/ide/qt_shell.py +348 -0
  133. pycforge/ide/qt_shell_interactions.py +320 -0
  134. pycforge/ide/qt_state.py +393 -0
  135. pycforge/ide/qt_workspace_features.py +398 -0
  136. pycforge/ide/qt_workspace_navigation.py +208 -0
  137. pycforge/ide/qt_workspace_observers.py +173 -0
  138. pycforge/ide/qt_workspace_panels.py +545 -0
  139. pycforge/ide/qt_workspace_widgets.py +228 -0
  140. pycforge/ide/resources/icons/about.svg +1 -0
  141. pycforge/ide/resources/icons/add-document.svg +1 -0
  142. pycforge/ide/resources/icons/brand-mark.svg +1 -0
  143. pycforge/ide/resources/icons/bundle-search.svg +1 -0
  144. pycforge/ide/resources/icons/cancel.svg +1 -0
  145. pycforge/ide/resources/icons/check.svg +1 -0
  146. pycforge/ide/resources/icons/chevron-right.svg +1 -0
  147. pycforge/ide/resources/icons/close.svg +1 -0
  148. pycforge/ide/resources/icons/collapse-all.svg +1 -0
  149. pycforge/ide/resources/icons/command-palette.svg +1 -0
  150. pycforge/ide/resources/icons/convert.svg +1 -0
  151. pycforge/ide/resources/icons/copy.svg +1 -0
  152. pycforge/ide/resources/icons/cut.svg +1 -0
  153. pycforge/ide/resources/icons/decision-trace.svg +1 -0
  154. pycforge/ide/resources/icons/details.svg +1 -0
  155. pycforge/ide/resources/icons/diagnostics.svg +1 -0
  156. pycforge/ide/resources/icons/duplicate-line.svg +1 -0
  157. pycforge/ide/resources/icons/exit.svg +1 -0
  158. pycforge/ide/resources/icons/expand-all.svg +1 -0
  159. pycforge/ide/resources/icons/export.svg +1 -0
  160. pycforge/ide/resources/icons/find.svg +1 -0
  161. pycforge/ide/resources/icons/go-to-line.svg +1 -0
  162. pycforge/ide/resources/icons/go-to-output.svg +1 -0
  163. pycforge/ide/resources/icons/go-to-source.svg +1 -0
  164. pycforge/ide/resources/icons/history.svg +1 -0
  165. pycforge/ide/resources/icons/indent.svg +1 -0
  166. pycforge/ide/resources/icons/link-c.svg +1 -0
  167. pycforge/ide/resources/icons/mappings.svg +1 -0
  168. pycforge/ide/resources/icons/module.svg +1 -0
  169. pycforge/ide/resources/icons/move-down.svg +1 -0
  170. pycforge/ide/resources/icons/move-line-down.svg +1 -0
  171. pycforge/ide/resources/icons/move-line-up.svg +1 -0
  172. pycforge/ide/resources/icons/move-up.svg +1 -0
  173. pycforge/ide/resources/icons/next.svg +1 -0
  174. pycforge/ide/resources/icons/open.svg +1 -0
  175. pycforge/ide/resources/icons/outdent.svg +1 -0
  176. pycforge/ide/resources/icons/outline.svg +1 -0
  177. pycforge/ide/resources/icons/paste.svg +1 -0
  178. pycforge/ide/resources/icons/previous.svg +1 -0
  179. pycforge/ide/resources/icons/primary-module.svg +1 -0
  180. pycforge/ide/resources/icons/redo.svg +1 -0
  181. pycforge/ide/resources/icons/remove-document.svg +1 -0
  182. pycforge/ide/resources/icons/replace.svg +1 -0
  183. pycforge/ide/resources/icons/save-as.svg +1 -0
  184. pycforge/ide/resources/icons/save.svg +1 -0
  185. pycforge/ide/resources/icons/select-all.svg +1 -0
  186. pycforge/ide/resources/icons/settings.svg +1 -0
  187. pycforge/ide/resources/icons/show-c.svg +1 -0
  188. pycforge/ide/resources/icons/split-view.svg +1 -0
  189. pycforge/ide/resources/icons/summary.svg +1 -0
  190. pycforge/ide/resources/icons/telemetry.svg +1 -0
  191. pycforge/ide/resources/icons/toggle-comment.svg +1 -0
  192. pycforge/ide/resources/icons/toggle-fold.svg +1 -0
  193. pycforge/ide/resources/icons/undo.svg +1 -0
  194. pycforge/ide/resources/icons/whitespace.svg +1 -0
  195. pycforge/ide/revisions.py +400 -0
  196. pycforge/ide/search_service.py +280 -0
  197. pycforge/ide/session_history.py +150 -0
  198. pycforge/ide/source_editing.py +417 -0
  199. pycforge/ide/source_structure.py +558 -0
  200. pycforge/ide/source_structure_async.py +191 -0
  201. pycforge/ide/supervisor.py +503 -0
  202. pycforge/ide/theme.py +119 -0
  203. pycforge/ide/theme_stylesheet.py +598 -0
  204. pycforge/ide/theme_workspace_stylesheet.py +155 -0
  205. pycforge/ide/visual_tokens.py +142 -0
  206. pycforge/ide/worker_protocol.py +80 -0
  207. pycforge/ide/workspace_search.py +462 -0
  208. pycforge/ide/workspace_session.py +256 -0
  209. pycforge/laboratory/__init__.py +6 -0
  210. pycforge/laboratory/audits.py +2244 -0
  211. pycforge/laboratory/checkpoint_e.py +2685 -0
  212. pycforge/laboratory/cli.py +119 -0
  213. pycforge/laboratory/exit_codes.py +11 -0
  214. pycforge/laboratory/keyword_audit.py +661 -0
  215. pycforge/laboratory/keyword_only_audit.py +481 -0
  216. pycforge-0.15.2.dist-info/METADATA +209 -0
  217. pycforge-0.15.2.dist-info/RECORD +221 -0
  218. pycforge-0.15.2.dist-info/WHEEL +5 -0
  219. pycforge-0.15.2.dist-info/entry_points.txt +5 -0
  220. pycforge-0.15.2.dist-info/licenses/LICENSE +674 -0
  221. pycforge-0.15.2.dist-info/top_level.txt +1 -0
pycforge/ide/theme.py ADDED
@@ -0,0 +1,119 @@
1
+ """Qt adapter for the PyCForge visual system.
2
+
3
+ The authoritative palette, vector catalogue, and stylesheet builder live in
4
+ headless-safe modules. This adapter remains importable when PyQt5 is absent so
5
+ the source-transpiler core never acquires a desktop dependency.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from .icons import PYCFORGE_ICON_FILES, pycforge_icon_path
13
+ from .theme_stylesheet import build_pycforge_stylesheet
14
+ from .visual_tokens import (
15
+ PYCFORGE_COLORS,
16
+ PYCFORGE_METRICS,
17
+ PyCForgeColors,
18
+ PyCForgeMetrics,
19
+ )
20
+
21
+ try: # Keep diagnostics useful if a required dependency is damaged.
22
+ from PyQt5.QtGui import QColor, QPalette
23
+ except (ImportError, ModuleNotFoundError):
24
+ QColor = None # type: ignore[assignment]
25
+ QPalette = None # type: ignore[assignment]
26
+ QT_THEME_AVAILABLE = False
27
+ else:
28
+ QT_THEME_AVAILABLE = True
29
+
30
+
31
+ PYCFORGE_QSS = build_pycforge_stylesheet()
32
+
33
+
34
+ def pycforge_palette(
35
+ colors: PyCForgeColors = PYCFORGE_COLORS,
36
+ ) -> Any | None:
37
+ """Build the Qt palette, or return ``None`` if PyQt5 is unavailable."""
38
+
39
+ if not QT_THEME_AVAILABLE or QColor is None or QPalette is None:
40
+ return None
41
+
42
+ palette = QPalette()
43
+ role_colors = {
44
+ QPalette.Window: colors.canvas,
45
+ QPalette.WindowText: colors.text,
46
+ QPalette.Base: colors.void,
47
+ QPalette.AlternateBase: colors.surface,
48
+ QPalette.ToolTipBase: colors.surface_active,
49
+ QPalette.ToolTipText: colors.text,
50
+ QPalette.Text: colors.text,
51
+ QPalette.Button: colors.surface_raised,
52
+ QPalette.ButtonText: colors.text,
53
+ QPalette.BrightText: colors.blue_bright,
54
+ QPalette.Highlight: colors.selection,
55
+ QPalette.HighlightedText: "#FFFFFF",
56
+ QPalette.Link: colors.blue,
57
+ QPalette.LinkVisited: colors.violet,
58
+ QPalette.Light: colors.border_strong,
59
+ QPalette.Midlight: colors.border,
60
+ QPalette.Dark: colors.canvas,
61
+ QPalette.Mid: colors.border,
62
+ QPalette.Shadow: colors.void,
63
+ }
64
+ for role, value in role_colors.items():
65
+ palette.setColor(role, QColor(value))
66
+
67
+ placeholder_role = getattr(QPalette, "PlaceholderText", None)
68
+ if placeholder_role is not None:
69
+ palette.setColor(
70
+ placeholder_role, QColor(colors.text_muted)
71
+ )
72
+ for role in (
73
+ QPalette.WindowText,
74
+ QPalette.Text,
75
+ QPalette.ButtonText,
76
+ ):
77
+ palette.setColor(
78
+ QPalette.Disabled, role, QColor(colors.text_disabled)
79
+ )
80
+ palette.setColor(
81
+ QPalette.Disabled,
82
+ QPalette.Highlight,
83
+ QColor(colors.surface_active),
84
+ )
85
+ palette.setColor(
86
+ QPalette.Disabled,
87
+ QPalette.HighlightedText,
88
+ QColor(colors.text_disabled),
89
+ )
90
+ return palette
91
+
92
+
93
+ def apply_pycforge_theme(application: Any) -> bool:
94
+ """Apply Fusion, the palette, and PyCForge QSS to an application."""
95
+
96
+ palette = pycforge_palette()
97
+ if palette is None:
98
+ return False
99
+ if application is None:
100
+ raise TypeError("application must be a QApplication instance")
101
+ application.setStyle("Fusion")
102
+ application.setPalette(palette)
103
+ application.setStyleSheet(PYCFORGE_QSS)
104
+ return True
105
+
106
+
107
+ __all__ = [
108
+ "QT_THEME_AVAILABLE",
109
+ "PYCFORGE_COLORS",
110
+ "PYCFORGE_ICON_FILES",
111
+ "PYCFORGE_METRICS",
112
+ "PYCFORGE_QSS",
113
+ "PyCForgeColors",
114
+ "PyCForgeMetrics",
115
+ "apply_pycforge_theme",
116
+ "build_pycforge_stylesheet",
117
+ "pycforge_icon_path",
118
+ "pycforge_palette",
119
+ ]
@@ -0,0 +1,598 @@
1
+ """Build the PyCForge Qt stylesheet from semantic visual tokens."""
2
+
3
+ from __future__ import annotations
4
+ from string import Template
5
+ from .icons import pycforge_icon_path
6
+ from .theme_workspace_stylesheet import build_workspace_stylesheet
7
+ from .visual_tokens import PYCFORGE_COLORS, PyCForgeColors, color_tokens
8
+
9
+
10
+ _PYCFORGE_QSS_TEMPLATE = Template(
11
+ r"""
12
+ QMainWindow, QDialog {
13
+ background: qlineargradient(x1:0,y1:0,x2:0,y2:1,
14
+ stop:0 ${surface_raised},stop:0.42 ${canvas},stop:1 ${void});
15
+ color: ${text};
16
+ }
17
+ QWidget {
18
+ color: ${text};
19
+ selection-background-color: ${selection};
20
+ selection-color: #FFFFFF;
21
+ }
22
+ QWidget#workspaceShell, QWidget[role="workspace-shell"] { background: transparent; }
23
+ QFrame#DocumentNavigator {
24
+ background: qlineargradient(x1:0,y1:0,x2:0,y2:1,
25
+ stop:0 ${surface_raised},stop:0.58 ${surface},stop:1 ${canvas});
26
+ border: 1px solid ${border};
27
+ border-right: 1px solid ${border_strong};
28
+ border-radius: 8px;
29
+ }
30
+ QFrame#editorPanel, QFrame#detailsPanel, QFrame[role="panel"] {
31
+ background: qlineargradient(x1:0,y1:0,x2:0,y2:1,
32
+ stop:0 ${surface_raised},stop:1 ${surface});
33
+ border: 1px solid ${border};
34
+ border-radius: 8px;
35
+ }
36
+ QLabel {
37
+ background: transparent;
38
+ color: ${text_soft};
39
+ }
40
+ QLabel#PanelEyebrow {
41
+ color: ${blue_bright};
42
+ font-weight: 700;
43
+ padding: 3px 0 5px 0;
44
+ border-bottom: 1px solid ${blue_dim};
45
+ }
46
+ QLabel#MutedLabel { color: ${text_muted}; padding: 2px 3px; }
47
+ QLabel#PathLabel { color: ${text_muted}; font-style: italic; padding: 2px 4px; }
48
+ QLabel#ActiveModuleLabel {
49
+ color: ${violet_bright};
50
+ font-weight: 600;
51
+ padding: 2px 6px;
52
+ border-left: 2px solid ${violet_dim};
53
+ }
54
+ QLabel[role="panel-title"] {
55
+ color: ${text};
56
+ font-weight: 600;
57
+ padding: 4px 2px 5px 2px;
58
+ border-bottom: 1px solid ${blue_dim};
59
+ }
60
+ QFrame#SourceEditorSurface, QFrame#GeneratedCPanel {
61
+ background: ${void};
62
+ border: 1px solid ${border};
63
+ border-radius: 7px;
64
+ }
65
+ QFrame#GeneratedCPanel { border-top: 2px solid ${warm_dim}; }
66
+ QPlainTextEdit#PythonSourceEditor, QPlainTextEdit#PythonSourceEditorSecondary, QPlainTextEdit#GeneratedCEditor {
67
+ border: 0;
68
+ border-radius: 6px;
69
+ padding: 2px;
70
+ }
71
+ QPlainTextEdit#GeneratedCEditor { background: ${void}; color: ${text_soft}; }
72
+ QLabel[status="neutral"], QLabel[role="status-chip"] {
73
+ color: ${text_soft};
74
+ background: ${surface_active};
75
+ border: 1px solid ${border_strong};
76
+ border-radius: 9px;
77
+ padding: 2px 8px;
78
+ }
79
+ QLabel[status="success"] {
80
+ color: ${success_bright};
81
+ background: ${success_dim};
82
+ border: 1px solid ${success};
83
+ border-radius: 9px;
84
+ padding: 2px 8px;
85
+ }
86
+ QLabel[status="warning"] {
87
+ color: ${warning_bright};
88
+ background: ${warning_dim};
89
+ border: 1px solid ${warning};
90
+ border-radius: 9px;
91
+ padding: 2px 8px;
92
+ }
93
+ QLabel[status="error"] {
94
+ color: ${error_bright};
95
+ background: ${error_dim};
96
+ border: 1px solid ${error};
97
+ border-radius: 9px;
98
+ padding: 2px 8px;
99
+ }
100
+ QFrame#ToastBanner { border-radius: 7px; padding: 1px; }
101
+ QFrame#ToastBanner QLabel {
102
+ color: ${text}; background: transparent; font-weight: 500;
103
+ }
104
+ QFrame#ToastBanner QToolButton {
105
+ min-width: 22px;
106
+ min-height: 22px;
107
+ padding: 2px;
108
+ color: ${text_soft};
109
+ background: transparent;
110
+ border: 1px solid transparent;
111
+ }
112
+ QFrame#ToastBanner QToolButton:hover,
113
+ QFrame#ToastBanner QToolButton:focus {
114
+ color: #FFFFFF;
115
+ background: ${surface_hover};
116
+ border-color: ${focus_ring};
117
+ }
118
+ QFrame#ToastBanner[tone="info"] {
119
+ color: #CDEFFC;
120
+ background: #12313D;
121
+ border: 1px solid #3E8FB0;
122
+ border-left: 3px solid ${blue};
123
+ }
124
+ QFrame#ToastBanner[tone="success"] {
125
+ color: #C9F7E2;
126
+ background: #123329;
127
+ border: 1px solid #448E70;
128
+ border-left: 3px solid ${success};
129
+ }
130
+ QFrame#ToastBanner[tone="warning"] {
131
+ color: #FFE4AB;
132
+ background: #3A301B;
133
+ border: 1px solid #8D7037;
134
+ border-left: 3px solid ${warning};
135
+ }
136
+ QFrame#ToastBanner[tone="error"] {
137
+ color: #FFD0D6;
138
+ background: #3A1E26;
139
+ border: 1px solid #9A4958;
140
+ border-left: 3px solid ${error};
141
+ }
142
+ QFrame#ToastBanner[tone="info"] QLabel { color: #CDEFFC; }
143
+ QFrame#ToastBanner[tone="success"] QLabel { color: #C9F7E2; }
144
+ QFrame#ToastBanner[tone="warning"] QLabel { color: #FFE4AB; }
145
+ QFrame#ToastBanner[tone="error"] QLabel { color: #FFD0D6; }
146
+ QMenuBar {
147
+ color: ${text_soft};
148
+ background: qlineargradient(x1:0,y1:0,x2:0,y2:1,
149
+ stop:0 ${surface_raised},stop:1 ${canvas});
150
+ border: 0;
151
+ border-bottom: 1px solid ${border_strong};
152
+ padding: 3px 6px;
153
+ spacing: 3px;
154
+ }
155
+ QMenuBar::item {
156
+ background: transparent;
157
+ border: 1px solid transparent;
158
+ border-radius: 5px;
159
+ padding: 6px 10px;
160
+ }
161
+ QMenuBar::item:selected, QMenuBar::item:pressed {
162
+ color: #FFFFFF;
163
+ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,
164
+ stop:0 ${blue_dim},stop:0.62 ${violet_dim},stop:1 ${warm_dim});
165
+ border-color: ${border_strong};
166
+ border-bottom: 2px solid ${warm};
167
+ }
168
+ QMenuBar::item:disabled { color: ${text_disabled}; }
169
+ QMenu, QMenu#PyCForgeMenu, QComboBox QAbstractItemView {
170
+ color: ${text};
171
+ background: qlineargradient(x1:0,y1:0,x2:0,y2:1,
172
+ stop:0 ${surface_raised},stop:1 ${surface});
173
+ border: 1px solid ${border_strong};
174
+ border-radius: 9px;
175
+ padding: 6px;
176
+ selection-background-color: ${selection};
177
+ selection-color: #FFFFFF;
178
+ }
179
+ QMenu::item, QMenu#PyCForgeMenu::item {
180
+ min-height: 18px;
181
+ padding: 7px 36px 7px 38px;
182
+ margin: 1px 3px;
183
+ border: 1px solid transparent;
184
+ border-radius: 5px;
185
+ }
186
+ QMenu::item:selected, QMenu#PyCForgeMenu::item:selected {
187
+ color: #FFFFFF;
188
+ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,
189
+ stop:0 ${selection},stop:0.64 ${violet_dim},stop:1 ${surface_hover});
190
+ border: 1px solid ${border_strong};
191
+ border-left: 3px solid ${blue_bright};
192
+ }
193
+ QMenu::item:pressed, QMenu#PyCForgeMenu::item:pressed {
194
+ color: #FFFFFF;
195
+ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,
196
+ stop:0 ${blue_dim},stop:0.58 ${violet_dim},stop:1 ${warm_dim});
197
+ border-left: 3px solid ${blue_bright};
198
+ border-bottom: 2px solid ${warm};
199
+ }
200
+ QMenu::item:disabled, QMenu#PyCForgeMenu::item:disabled {
201
+ color: ${text_disabled}; background: transparent;
202
+ border-color: transparent;
203
+ }
204
+ QMenu#PyCForgeMenu[pycforgeTone="primary"]::item:selected {
205
+ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,
206
+ stop:0 ${blue_dim},stop:0.58 ${violet_dim},stop:1 ${warm_dim});
207
+ color: #FFFFFF; border-left: 3px solid ${warm_bright};
208
+ }
209
+ QMenu#PyCForgeMenu[pycforgeTone="danger"]::item:selected { color: ${error_bright}; background: ${error_dim}; border-left: 3px solid ${error}; }
210
+ QMenu::separator { height: 1px; margin: 6px 12px; background: ${border}; }
211
+ QMenu::icon { left: 12px; width: 18px; height: 18px; }
212
+ QMenu::indicator {
213
+ width: 15px;
214
+ height: 15px;
215
+ left: 12px;
216
+ border: 1px solid ${border_strong};
217
+ border-radius: 4px;
218
+ background: ${canvas};
219
+ }
220
+ QMenu::indicator:checked {
221
+ image: url("${check_icon}");
222
+ background: ${blue_dim};
223
+ border: 1px solid ${blue_bright};
224
+ }
225
+ QMenu::right-arrow {
226
+ image: url("${chevron_icon}");
227
+ width: 14px;
228
+ height: 14px;
229
+ right: 10px;
230
+ }
231
+ QMenu::scroller {
232
+ height: 18px;
233
+ background: ${surface_active};
234
+ border: 1px solid ${border};
235
+ }
236
+ QToolBar {
237
+ color: ${text};
238
+ background: qlineargradient(x1:0,y1:0,x2:0,y2:1,
239
+ stop:0 ${surface_raised},stop:1 ${surface});
240
+ border: 0;
241
+ border-bottom: 1px solid ${border_strong};
242
+ spacing: 5px;
243
+ padding: 7px 9px;
244
+ }
245
+ QToolBar::separator { width: 1px; margin: 4px 7px; background: ${border}; }
246
+ QPushButton, QToolButton {
247
+ color: ${text_soft};
248
+ background: qlineargradient(x1:0,y1:0,x2:0,y2:1,
249
+ stop:0 ${surface_hover},stop:1 ${surface_raised});
250
+ border: 1px solid ${border_strong};
251
+ border-radius: 6px;
252
+ padding: 6px 10px;
253
+ min-height: 18px;
254
+ }
255
+ QPushButton:hover, QToolButton:hover {
256
+ color: #FFFFFF;
257
+ background: ${surface_hover};
258
+ border-color: ${blue};
259
+ border-bottom: 2px solid ${blue};
260
+ }
261
+ QPushButton:focus, QToolButton:focus {
262
+ color: #FFFFFF;
263
+ border: 1px solid ${focus_ring};
264
+ border-bottom: 2px solid ${violet};
265
+ }
266
+ QPushButton:pressed, QToolButton:pressed,
267
+ QPushButton:checked, QToolButton:checked {
268
+ color: #FFFFFF;
269
+ background: ${blue_dim};
270
+ border-color: ${blue};
271
+ border-bottom: 2px solid ${warm};
272
+ }
273
+ QPushButton:disabled, QToolButton:disabled {
274
+ color: ${text_disabled};
275
+ background: ${surface};
276
+ border-color: ${border};
277
+ }
278
+ QPushButton[role="primary"], QToolButton[role="primary"] {
279
+ color: #FFFFFF;
280
+ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,
281
+ stop:0 ${selection_active},stop:0.7 ${violet_dim},stop:1 ${warm_dim});
282
+ border-color: ${blue_bright};
283
+ font-weight: 600;
284
+ }
285
+ QPushButton[role="danger"], QToolButton[role="danger"] {
286
+ color: ${error_bright}; background: ${error_dim}; border-color: ${error};
287
+ }
288
+ QToolButton#IconButton {
289
+ min-width: 28px;
290
+ max-width: 28px;
291
+ min-height: 28px;
292
+ max-height: 28px;
293
+ padding: 2px;
294
+ border-radius: 6px;
295
+ }
296
+ QPlainTextEdit, QTextEdit, QLineEdit, QSpinBox, QComboBox {
297
+ color: ${text};
298
+ background: ${canvas};
299
+ border: 1px solid ${border};
300
+ border-radius: 6px;
301
+ padding: 5px 7px;
302
+ }
303
+ QPlainTextEdit:focus, QTextEdit:focus, QLineEdit:focus,
304
+ QSpinBox:focus, QComboBox:focus {
305
+ background: ${surface};
306
+ border: 1px solid ${blue};
307
+ border-bottom: 2px solid ${violet};
308
+ }
309
+ QPlainTextEdit:read-only, QTextEdit:read-only {
310
+ color: ${text_soft};
311
+ background: ${void};
312
+ border-color: ${border};
313
+ }
314
+ QLineEdit[state="error"], QPlainTextEdit[state="error"] {
315
+ border: 1px solid ${error};
316
+ border-bottom: 2px solid ${error};
317
+ background: ${error_dim};
318
+ }
319
+ QLineEdit[state="warning"], QPlainTextEdit[state="warning"] {
320
+ border: 1px solid ${warning};
321
+ border-bottom: 2px solid ${warning};
322
+ background: ${warning_dim};
323
+ }
324
+ QLineEdit[state="success"], QPlainTextEdit[state="success"] {
325
+ border: 1px solid ${success};
326
+ border-bottom: 2px solid ${success};
327
+ background: ${success_dim};
328
+ }
329
+ QLineEdit:disabled, QPlainTextEdit:disabled, QTextEdit:disabled,
330
+ QComboBox:disabled, QSpinBox:disabled {
331
+ color: ${text_disabled};
332
+ background: ${surface};
333
+ border-color: ${border};
334
+ }
335
+ QLineEdit#NavigatorFilter {
336
+ background: ${void}; border-bottom: 2px solid ${blue_dim};
337
+ }
338
+ QWidget#findReplaceBar, QFrame#findReplaceBar,
339
+ QWidget[role="find-bar"], QFrame[role="find-bar"] {
340
+ color: ${text};
341
+ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,
342
+ stop:0 ${surface},stop:0.72 ${surface_raised},stop:1 ${violet_dim});
343
+ border: 1px solid ${border_strong};
344
+ border-bottom: 2px solid ${warm_dim};
345
+ border-radius: 7px;
346
+ }
347
+ QListWidget#DocumentList {
348
+ color: ${text_soft};
349
+ background: ${void};
350
+ border: 1px solid ${border};
351
+ border-radius: 6px;
352
+ outline: 0;
353
+ padding: 3px;
354
+ }
355
+ QListWidget#DocumentList:focus {
356
+ border: 1px solid ${blue}; border-bottom: 2px solid ${violet};
357
+ }
358
+ QListWidget#DocumentList::item {
359
+ border: 1px solid transparent;
360
+ border-bottom: 1px solid ${surface_raised};
361
+ border-radius: 5px;
362
+ padding: 8px 7px;
363
+ margin: 1px 0;
364
+ }
365
+ QListWidget#DocumentList::item:hover {
366
+ color: #FFFFFF;
367
+ background: ${surface_hover};
368
+ border-color: ${border_strong};
369
+ }
370
+ QListWidget#DocumentList::item:selected {
371
+ color: #FFFFFF;
372
+ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,
373
+ stop:0 ${selection},stop:1 ${violet_dim});
374
+ border: 1px solid ${blue};
375
+ border-left: 3px solid ${blue_bright};
376
+ }
377
+ QListWidget#DocumentList::item:disabled {
378
+ color: ${text_disabled}; background: ${surface};
379
+ }
380
+ QTabWidget::pane {
381
+ background: ${surface};
382
+ border: 1px solid ${border};
383
+ border-top: 1px solid ${border_strong};
384
+ border-radius: 6px;
385
+ top: -1px;
386
+ }
387
+ QTabBar::tab {
388
+ color: ${text_muted};
389
+ background: ${canvas};
390
+ border: 1px solid ${border};
391
+ border-bottom: 1px solid ${border_strong};
392
+ padding: 8px 13px;
393
+ min-width: 72px;
394
+ }
395
+ QTabBar::tab:hover {
396
+ color: ${text};
397
+ background: ${surface_active};
398
+ border-bottom: 2px solid ${blue};
399
+ }
400
+ QTabBar::tab:selected {
401
+ color: #FFFFFF;
402
+ background: ${surface_hover};
403
+ border-color: ${border_strong};
404
+ border-bottom: 2px solid ${violet};
405
+ }
406
+ QTabBar::tab:disabled { color: ${text_disabled}; background: ${canvas}; }
407
+ QSplitter::handle { background: ${surface_raised}; border: 0; }
408
+ QSplitter::handle:horizontal {
409
+ width: 5px;
410
+ border-left: 1px solid ${border};
411
+ border-right: 1px solid ${blue_dim};
412
+ }
413
+ QSplitter::handle:vertical {
414
+ height: 5px;
415
+ border-top: 1px solid ${border};
416
+ border-bottom: 1px solid ${blue_dim};
417
+ }
418
+ QSplitter::handle:hover { background: ${border_strong}; }
419
+ QTreeView, QListView, QTableView {
420
+ color: ${text_soft};
421
+ background: ${canvas};
422
+ alternate-background-color: ${surface};
423
+ border: 1px solid ${border};
424
+ gridline-color: ${border};
425
+ outline: 0;
426
+ }
427
+ QTreeView:focus, QListView:focus, QTableView:focus {
428
+ border: 1px solid ${blue}; border-bottom: 2px solid ${violet};
429
+ }
430
+ QWidget#DiagnosticsView, QWidget#MappingsView, QWidget#InspectorTree {
431
+ background: qlineargradient(x1:0,y1:0,x2:0,y2:1,
432
+ stop:0 ${surface_raised},stop:1 ${canvas});
433
+ border: 0;
434
+ }
435
+ QWidget#DiagnosticsView QLineEdit,
436
+ QWidget#MappingsView QLineEdit,
437
+ QWidget#InspectorTree QLineEdit {
438
+ background: ${void};
439
+ border-color: ${border_strong};
440
+ border-bottom: 2px solid ${blue_dim};
441
+ }
442
+ QWidget#DiagnosticsView QLineEdit:focus,
443
+ QWidget#MappingsView QLineEdit:focus,
444
+ QWidget#InspectorTree QLineEdit:focus {
445
+ border-color: ${blue};
446
+ border-bottom: 2px solid ${violet};
447
+ }
448
+ QTreeWidget#DiagnosticsTree, QTreeWidget#MappingsTree,
449
+ QWidget#InspectorTree QTreeWidget {
450
+ background: ${void};
451
+ border: 1px solid ${border};
452
+ border-radius: 6px;
453
+ }
454
+ QTreeWidget#DiagnosticsTree:focus,
455
+ QTreeWidget#MappingsTree:focus,
456
+ QWidget#InspectorTree QTreeWidget:focus {
457
+ border: 1px solid ${blue};
458
+ border-bottom: 2px solid ${violet};
459
+ }
460
+ QTextBrowser#DiagnosticDetails {
461
+ color: ${text_soft};
462
+ background: ${surface};
463
+ border: 1px solid ${border_strong};
464
+ border-left: 2px solid ${violet};
465
+ border-radius: 6px;
466
+ padding: 7px;
467
+ }
468
+ QTreeView::item, QListView::item, QTableView::item { padding: 4px; }
469
+ QTreeView::item:hover, QListView::item:hover, QTableView::item:hover {
470
+ color: #FFFFFF; background: ${surface_hover};
471
+ }
472
+ QTreeView::item:selected, QListView::item:selected, QTableView::item:selected {
473
+ color: #FFFFFF;
474
+ background: ${selection};
475
+ border-left: 2px solid ${blue_bright};
476
+ }
477
+ QTreeView:focus::item:selected,
478
+ QListView:focus::item:selected,
479
+ QTableView:focus::item:selected {
480
+ border: 1px solid ${focus_ring};
481
+ border-left: 3px solid ${violet};
482
+ }
483
+ QHeaderView::section {
484
+ color: ${text_soft};
485
+ background: ${surface_raised};
486
+ border: 0;
487
+ border-right: 1px solid ${border};
488
+ border-bottom: 1px solid ${border_strong};
489
+ padding: 6px 8px;
490
+ }
491
+ QScrollBar:vertical { background: ${canvas}; width: 12px; margin: 1px; }
492
+ QScrollBar:horizontal { background: ${canvas}; height: 12px; margin: 1px; }
493
+ QScrollBar::handle:vertical, QScrollBar::handle:horizontal {
494
+ background: ${border_strong};
495
+ border: 1px solid ${text_disabled};
496
+ border-radius: 5px;
497
+ min-height: 28px;
498
+ min-width: 28px;
499
+ }
500
+ QScrollBar::handle:vertical:hover, QScrollBar::handle:horizontal:hover {
501
+ background: ${selection_active}; border-color: ${blue};
502
+ }
503
+ QScrollBar::add-line, QScrollBar::sub-line,
504
+ QScrollBar::add-page, QScrollBar::sub-page {
505
+ background: none;
506
+ border: none;
507
+ width: 0;
508
+ height: 0;
509
+ }
510
+ QStatusBar {
511
+ color: ${text_soft};
512
+ background: ${canvas};
513
+ border-top: 1px solid ${border_strong};
514
+ }
515
+ QStatusBar::item { border: 0; }
516
+ QProgressBar {
517
+ color: ${text};
518
+ background: ${void};
519
+ border: 1px solid ${border};
520
+ border-radius: 6px;
521
+ text-align: center;
522
+ min-height: 16px;
523
+ }
524
+ QProgressBar::chunk {
525
+ background: qlineargradient(x1:0,y1:0,x2:1,y2:0,
526
+ stop:0 ${blue},stop:0.62 ${violet},stop:1 ${warm});
527
+ border-radius: 5px;
528
+ }
529
+ QCheckBox, QRadioButton { color: ${text_soft}; spacing: 7px; }
530
+ QCheckBox::indicator, QRadioButton::indicator {
531
+ width: 15px;
532
+ height: 15px;
533
+ background: ${canvas};
534
+ border: 1px solid ${border_strong};
535
+ }
536
+ QCheckBox::indicator:checked, QRadioButton::indicator:checked {
537
+ background: ${selection}; border: 2px solid ${blue};
538
+ }
539
+ QCheckBox::indicator:focus, QRadioButton::indicator:focus {
540
+ border-color: ${focus_ring};
541
+ }
542
+ QToolTip {
543
+ color: #FFFFFF;
544
+ background: ${surface_active};
545
+ border: 1px solid ${blue};
546
+ padding: 5px 7px;
547
+ }
548
+ QDockWidget {
549
+ color: ${text}; titlebar-close-icon: none; titlebar-normal-icon: none;
550
+ }
551
+ QDockWidget::title {
552
+ color: ${text_soft};
553
+ background: ${surface_raised};
554
+ border-bottom: 1px solid ${border_strong};
555
+ padding: 6px;
556
+ }
557
+ QMainWindow[visualMode="high-contrast"],
558
+ QDialog[visualMode="high-contrast"] { background: ${void}; }
559
+ QWidget[visualMode="high-contrast"] QMenu,
560
+ QWidget[visualMode="high-contrast"] QToolButton,
561
+ QWidget[visualMode="high-contrast"] QPushButton,
562
+ QWidget[visualMode="high-contrast"] QLineEdit,
563
+ QWidget[visualMode="high-contrast"] QPlainTextEdit,
564
+ QWidget[visualMode="high-contrast"] QTreeView,
565
+ QWidget[visualMode="high-contrast"] QListView {
566
+ border-color: ${text_muted};
567
+ }
568
+ QWidget[visualMode="high-contrast"] QMenu::item:selected,
569
+ QWidget[visualMode="high-contrast"] QTreeView::item:selected,
570
+ QWidget[visualMode="high-contrast"] QListView::item:selected {
571
+ color: #FFFFFF;
572
+ background: ${selection_active};
573
+ border: 2px solid ${focus_ring};
574
+ }
575
+ """
576
+ )
577
+
578
+
579
+ def build_pycforge_stylesheet(
580
+ colors: PyCForgeColors = PYCFORGE_COLORS,
581
+ ) -> str:
582
+ """Return the complete deterministic stylesheet for *colors*."""
583
+
584
+ values = dict(color_tokens(colors))
585
+ values.update(
586
+ {
587
+ "check_icon": pycforge_icon_path("check").as_posix(),
588
+ "chevron_icon": pycforge_icon_path(
589
+ "chevron-right"
590
+ ).as_posix(),
591
+ }
592
+ )
593
+ foundation = _PYCFORGE_QSS_TEMPLATE.substitute(values).strip()
594
+ workspace = build_workspace_stylesheet(colors).strip()
595
+ return foundation + "\n" + workspace + "\n"
596
+
597
+
598
+ __all__ = ["build_pycforge_stylesheet"]