bootstack 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.
Files changed (471) hide show
  1. bootstack/__init__.py +157 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/_core/__init__.py +21 -0
  4. bootstack/_core/capabilities/__init__.py +45 -0
  5. bootstack/_core/capabilities/after.py +103 -0
  6. bootstack/_core/capabilities/bind.py +192 -0
  7. bootstack/_core/capabilities/bindtags.py +112 -0
  8. bootstack/_core/capabilities/busy.py +72 -0
  9. bootstack/_core/capabilities/clipboard.py +89 -0
  10. bootstack/_core/capabilities/focus.py +118 -0
  11. bootstack/_core/capabilities/grab.py +65 -0
  12. bootstack/_core/capabilities/grid.py +211 -0
  13. bootstack/_core/capabilities/localization.py +231 -0
  14. bootstack/_core/capabilities/pack.py +140 -0
  15. bootstack/_core/capabilities/place.py +113 -0
  16. bootstack/_core/capabilities/selection.py +136 -0
  17. bootstack/_core/capabilities/signals.py +244 -0
  18. bootstack/_core/capabilities/winfo.py +315 -0
  19. bootstack/_core/colorutils.py +234 -0
  20. bootstack/_core/exceptions.py +25 -0
  21. bootstack/_core/images.py +463 -0
  22. bootstack/_core/mixins/__init__.py +1 -0
  23. bootstack/_core/mixins/ttk_state.py +35 -0
  24. bootstack/_core/mixins/widget.py +132 -0
  25. bootstack/_core/paths.py +49 -0
  26. bootstack/_core/publisher.py +149 -0
  27. bootstack/_core/variables.py +62 -0
  28. bootstack/_runtime/__init__.py +3 -0
  29. bootstack/_runtime/app.py +930 -0
  30. bootstack/_runtime/base_window.py +945 -0
  31. bootstack/_runtime/events.py +399 -0
  32. bootstack/_runtime/shortcuts.py +496 -0
  33. bootstack/_runtime/tk_patch.py +43 -0
  34. bootstack/_runtime/toplevel.py +276 -0
  35. bootstack/_runtime/utility.py +457 -0
  36. bootstack/_runtime/visual_focus.py +240 -0
  37. bootstack/_runtime/window_utilities.py +1123 -0
  38. bootstack/assets/__init__.py +21 -0
  39. bootstack/assets/bootstack.ico +0 -0
  40. bootstack/assets/bootstack.png +0 -0
  41. bootstack/assets/elements/__init__.py +0 -0
  42. bootstack/assets/elements/badge-pill.png +0 -0
  43. bootstack/assets/elements/badge-square.png +0 -0
  44. bootstack/assets/elements/button-compact.png +0 -0
  45. bootstack/assets/elements/button-default.png +0 -0
  46. bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
  47. bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
  48. bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
  49. bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
  50. bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
  51. bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
  52. bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
  53. bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
  54. bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
  55. bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
  56. bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
  57. bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
  58. bootstack/assets/elements/card.png +0 -0
  59. bootstack/assets/elements/checkbox-checked.png +0 -0
  60. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  61. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  62. bootstack/assets/elements/field.png +0 -0
  63. bootstack/assets/elements/input-addon-compact.png +0 -0
  64. bootstack/assets/elements/input-addon-default.png +0 -0
  65. bootstack/assets/elements/input-compact.png +0 -0
  66. bootstack/assets/elements/input-default.png +0 -0
  67. bootstack/assets/elements/list-item-separated.png +0 -0
  68. bootstack/assets/elements/list-item.png +0 -0
  69. bootstack/assets/elements/listrow-compact.png +0 -0
  70. bootstack/assets/elements/listrow-default.png +0 -0
  71. bootstack/assets/elements/manifest.toml +361 -0
  72. bootstack/assets/elements/menu-item.png +0 -0
  73. bootstack/assets/elements/navitem-compact.png +0 -0
  74. bootstack/assets/elements/navitem-default.png +0 -0
  75. bootstack/assets/elements/progressbar-h-compact.png +0 -0
  76. bootstack/assets/elements/progressbar-h-default.png +0 -0
  77. bootstack/assets/elements/progressbar-v-compact.png +0 -0
  78. bootstack/assets/elements/progressbar-v-default.png +0 -0
  79. bootstack/assets/elements/radiobutton.png +0 -0
  80. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  81. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  82. bootstack/assets/elements/slider-handle.png +0 -0
  83. bootstack/assets/elements/slider-track-h.png +0 -0
  84. bootstack/assets/elements/slider-track-v.png +0 -0
  85. bootstack/assets/elements/switch-off.png +0 -0
  86. bootstack/assets/elements/switch-on.png +0 -0
  87. bootstack/assets/elements/tab-h.png +0 -0
  88. bootstack/assets/elements/tab-v.png +0 -0
  89. bootstack/assets/icons/bootstrap.ttf +0 -0
  90. bootstack/assets/icons/glyphmap.json +2080 -0
  91. bootstack/assets/icons/icon_metrics.json +12470 -0
  92. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  93. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
  94. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
  95. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
  96. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  97. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
  98. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  99. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
  100. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  101. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
  102. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  103. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
  104. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  105. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
  106. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  107. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
  108. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  109. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
  110. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  111. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
  112. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  113. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
  114. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  115. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
  116. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  117. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
  118. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  119. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
  120. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  121. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
  122. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  123. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
  124. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  125. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
  126. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  127. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
  128. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  129. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
  130. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  131. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
  132. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  133. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
  134. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  135. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
  136. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  137. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
  138. bootstack/cli/__init__.py +133 -0
  139. bootstack/cli/__main__.py +6 -0
  140. bootstack/cli/add.py +395 -0
  141. bootstack/cli/appicon.py +285 -0
  142. bootstack/cli/build.py +115 -0
  143. bootstack/cli/config.py +313 -0
  144. bootstack/cli/demo.py +564 -0
  145. bootstack/cli/dev.py +153 -0
  146. bootstack/cli/doctor.py +195 -0
  147. bootstack/cli/icons.py +98 -0
  148. bootstack/cli/promote.py +120 -0
  149. bootstack/cli/pyinstaller.py +268 -0
  150. bootstack/cli/run.py +95 -0
  151. bootstack/cli/start.py +117 -0
  152. bootstack/cli/templates/__init__.py +931 -0
  153. bootstack/clipboard.py +48 -0
  154. bootstack/constants.py +318 -0
  155. bootstack/data/README.md +615 -0
  156. bootstack/data/__init__.py +78 -0
  157. bootstack/data/_observable.py +276 -0
  158. bootstack/data/base.py +780 -0
  159. bootstack/data/file_source.py +367 -0
  160. bootstack/data/memory_source.py +450 -0
  161. bootstack/data/query.py +367 -0
  162. bootstack/data/readers.py +289 -0
  163. bootstack/data/sqlite_source.py +869 -0
  164. bootstack/data/types.py +354 -0
  165. bootstack/data/writers.py +232 -0
  166. bootstack/dev/__init__.py +36 -0
  167. bootstack/dev/_capture.py +141 -0
  168. bootstack/dev/_env.py +43 -0
  169. bootstack/dev/_registry.py +140 -0
  170. bootstack/dev/_reloader.py +351 -0
  171. bootstack/dev/_reset.py +50 -0
  172. bootstack/dev/_watcher.py +91 -0
  173. bootstack/dialogs/__init__.py +948 -0
  174. bootstack/dialogs/_impl/__init__.py +47 -0
  175. bootstack/dialogs/_impl/colorchooser.py +588 -0
  176. bootstack/dialogs/_impl/datedialog.py +450 -0
  177. bootstack/dialogs/_impl/dialog.py +594 -0
  178. bootstack/dialogs/_impl/filterdialog.py +358 -0
  179. bootstack/dialogs/_impl/fontdialog.py +364 -0
  180. bootstack/dialogs/_impl/formdialog.py +564 -0
  181. bootstack/dialogs/_impl/message.py +486 -0
  182. bootstack/dialogs/_impl/query.py +570 -0
  183. bootstack/errors.py +67 -0
  184. bootstack/events/__init__.py +111 -0
  185. bootstack/events/_event.py +135 -0
  186. bootstack/events/_payloads.py +539 -0
  187. bootstack/events/_subscription.py +38 -0
  188. bootstack/i18n/README.md +77 -0
  189. bootstack/i18n/__init__.py +23 -0
  190. bootstack/i18n/catalog.py +121 -0
  191. bootstack/i18n/intl_format.py +584 -0
  192. bootstack/i18n/msgcat.py +425 -0
  193. bootstack/i18n/specs.py +156 -0
  194. bootstack/images.py +563 -0
  195. bootstack/py.typed +1 -0
  196. bootstack/scheduling/__init__.py +11 -0
  197. bootstack/scheduling/_schedule.py +218 -0
  198. bootstack/shortcuts.py +21 -0
  199. bootstack/signals/README.md +98 -0
  200. bootstack/signals/__init__.py +10 -0
  201. bootstack/signals/integration.py +100 -0
  202. bootstack/signals/signal.py +353 -0
  203. bootstack/signals/types.py +6 -0
  204. bootstack/store.py +286 -0
  205. bootstack/streams/__init__.py +12 -0
  206. bootstack/streams/_stream.py +321 -0
  207. bootstack/style/__init__.py +38 -0
  208. bootstack/style/builders/__init__.py +51 -0
  209. bootstack/style/builders/badge.py +46 -0
  210. bootstack/style/builders/button.py +339 -0
  211. bootstack/style/builders/buttongroup.py +311 -0
  212. bootstack/style/builders/calendar.py +271 -0
  213. bootstack/style/builders/checkbutton.py +110 -0
  214. bootstack/style/builders/combobox.py +113 -0
  215. bootstack/style/builders/contextmenu.py +268 -0
  216. bootstack/style/builders/entry.py +82 -0
  217. bootstack/style/builders/expander.py +148 -0
  218. bootstack/style/builders/field.py +335 -0
  219. bootstack/style/builders/frame.py +50 -0
  220. bootstack/style/builders/label.py +28 -0
  221. bootstack/style/builders/labelframe.py +34 -0
  222. bootstack/style/builders/listview.py +369 -0
  223. bootstack/style/builders/menubar.py +91 -0
  224. bootstack/style/builders/menubutton.py +359 -0
  225. bootstack/style/builders/panedwindow.py +25 -0
  226. bootstack/style/builders/progressbar.py +67 -0
  227. bootstack/style/builders/radiobutton.py +99 -0
  228. bootstack/style/builders/scale.py +64 -0
  229. bootstack/style/builders/scrollbar.py +225 -0
  230. bootstack/style/builders/separator.py +49 -0
  231. bootstack/style/builders/sidenav.py +643 -0
  232. bootstack/style/builders/sizegrip.py +15 -0
  233. bootstack/style/builders/spinbox.py +119 -0
  234. bootstack/style/builders/switch.py +70 -0
  235. bootstack/style/builders/tabitem.py +204 -0
  236. bootstack/style/builders/togglegroup.py +294 -0
  237. bootstack/style/builders/toolbutton.py +275 -0
  238. bootstack/style/builders/tooltip.py +26 -0
  239. bootstack/style/builders/treeview.py +193 -0
  240. bootstack/style/builders/utils.py +455 -0
  241. bootstack/style/builders_tk/__init__.py +16 -0
  242. bootstack/style/builders_tk/defaults.py +229 -0
  243. bootstack/style/element.py +173 -0
  244. bootstack/style/fonts.py +123 -0
  245. bootstack/style/style.py +609 -0
  246. bootstack/style/style_builder_base.py +716 -0
  247. bootstack/style/style_builder_mixed.py +93 -0
  248. bootstack/style/style_builder_tk.py +109 -0
  249. bootstack/style/style_builder_ttk.py +353 -0
  250. bootstack/style/style_resolver.py +447 -0
  251. bootstack/style/theme.py +245 -0
  252. bootstack/style/theme_provider.py +471 -0
  253. bootstack/style/themes/__init__.py +128 -0
  254. bootstack/style/tk_patch.py +5 -0
  255. bootstack/style/token_maps.py +41 -0
  256. bootstack/style/types.py +32 -0
  257. bootstack/style/typography.py +523 -0
  258. bootstack/style/utility.py +746 -0
  259. bootstack/types.py +39 -0
  260. bootstack/validation/__init__.py +6 -0
  261. bootstack/validation/types.py +15 -0
  262. bootstack/validation/validation_result.py +17 -0
  263. bootstack/validation/validation_rules.py +205 -0
  264. bootstack/widgets/__init__.py +74 -0
  265. bootstack/widgets/_core/__init__.py +31 -0
  266. bootstack/widgets/_core/app_config.py +266 -0
  267. bootstack/widgets/_core/base.py +461 -0
  268. bootstack/widgets/_core/container.py +334 -0
  269. bootstack/widgets/_core/context.py +35 -0
  270. bootstack/widgets/_core/events.py +130 -0
  271. bootstack/widgets/_core/field_mixin.py +353 -0
  272. bootstack/widgets/_core/icon_image_props.py +72 -0
  273. bootstack/widgets/_core/image_binding.py +93 -0
  274. bootstack/widgets/_core/navmodel.py +345 -0
  275. bootstack/widgets/_core/options.py +210 -0
  276. bootstack/widgets/_core/selection_group.py +130 -0
  277. bootstack/widgets/_core/window_controls.py +96 -0
  278. bootstack/widgets/_core/window_menu.py +246 -0
  279. bootstack/widgets/_impl/__init__.py +1 -0
  280. bootstack/widgets/_impl/_internal/__init__.py +0 -0
  281. bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
  282. bootstack/widgets/_impl/_parts/__init__.py +11 -0
  283. bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
  284. bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
  285. bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
  286. bootstack/widgets/_impl/composites/__init__.py +33 -0
  287. bootstack/widgets/_impl/composites/_dateutils.py +33 -0
  288. bootstack/widgets/_impl/composites/_image_fit.py +105 -0
  289. bootstack/widgets/_impl/composites/accordion.py +381 -0
  290. bootstack/widgets/_impl/composites/avatar.py +191 -0
  291. bootstack/widgets/_impl/composites/buttongroup.py +371 -0
  292. bootstack/widgets/_impl/composites/calendar.py +972 -0
  293. bootstack/widgets/_impl/composites/carousel.py +567 -0
  294. bootstack/widgets/_impl/composites/chart.py +882 -0
  295. bootstack/widgets/_impl/composites/compositeframe.py +298 -0
  296. bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
  297. bootstack/widgets/_impl/composites/dateentry.py +404 -0
  298. bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
  299. bootstack/widgets/_impl/composites/expander.py +515 -0
  300. bootstack/widgets/_impl/composites/field.py +670 -0
  301. bootstack/widgets/_impl/composites/form.py +1066 -0
  302. bootstack/widgets/_impl/composites/gallery.py +551 -0
  303. bootstack/widgets/_impl/composites/list/__init__.py +15 -0
  304. bootstack/widgets/_impl/composites/list/listitem.py +802 -0
  305. bootstack/widgets/_impl/composites/list/listview.py +1433 -0
  306. bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
  307. bootstack/widgets/_impl/composites/menu/model.py +358 -0
  308. bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
  309. bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
  310. bootstack/widgets/_impl/composites/meter.py +860 -0
  311. bootstack/widgets/_impl/composites/numericentry.py +201 -0
  312. bootstack/widgets/_impl/composites/pagestack.py +395 -0
  313. bootstack/widgets/_impl/composites/passwordentry.py +142 -0
  314. bootstack/widgets/_impl/composites/pathentry.py +168 -0
  315. bootstack/widgets/_impl/composites/picture.py +289 -0
  316. bootstack/widgets/_impl/composites/radiogroup.py +511 -0
  317. bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
  318. bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
  319. bootstack/widgets/_impl/composites/scrollview.py +764 -0
  320. bootstack/widgets/_impl/composites/selectbox.py +1026 -0
  321. bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
  322. bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
  323. bootstack/widgets/_impl/composites/shell/layout.py +335 -0
  324. bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
  325. bootstack/widgets/_impl/composites/shell/providers.py +558 -0
  326. bootstack/widgets/_impl/composites/shell/rail.py +117 -0
  327. bootstack/widgets/_impl/composites/shell/shell.py +581 -0
  328. bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
  329. bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
  330. bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
  331. bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
  332. bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
  333. bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
  334. bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
  335. bootstack/widgets/_impl/composites/slider/slider.py +851 -0
  336. bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
  337. bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
  338. bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
  339. bootstack/widgets/_impl/composites/tableview/types.py +169 -0
  340. bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
  341. bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
  342. bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
  343. bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
  344. bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
  345. bootstack/widgets/_impl/composites/textarea/change.py +46 -0
  346. bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
  347. bootstack/widgets/_impl/composites/textarea/core.py +495 -0
  348. bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
  349. bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
  350. bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
  351. bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
  352. bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
  353. bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
  354. bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
  355. bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
  356. bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
  357. bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
  358. bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
  359. bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
  360. bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
  361. bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
  362. bootstack/widgets/_impl/composites/textentry.py +57 -0
  363. bootstack/widgets/_impl/composites/timeentry.py +176 -0
  364. bootstack/widgets/_impl/composites/toast.py +390 -0
  365. bootstack/widgets/_impl/composites/toast_stack.py +156 -0
  366. bootstack/widgets/_impl/composites/togglegroup.py +418 -0
  367. bootstack/widgets/_impl/composites/toolbar.py +608 -0
  368. bootstack/widgets/_impl/composites/tooltip.py +491 -0
  369. bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
  370. bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
  371. bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
  372. bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
  373. bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
  374. bootstack/widgets/_impl/mixins/__init__.py +24 -0
  375. bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
  376. bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
  377. bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
  378. bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
  379. bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
  380. bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
  381. bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
  382. bootstack/widgets/_impl/primitives/__init__.py +49 -0
  383. bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
  384. bootstack/widgets/_impl/primitives/badge.py +45 -0
  385. bootstack/widgets/_impl/primitives/button.py +76 -0
  386. bootstack/widgets/_impl/primitives/card.py +45 -0
  387. bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
  388. bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
  389. bootstack/widgets/_impl/primitives/combobox.py +156 -0
  390. bootstack/widgets/_impl/primitives/entry.py +87 -0
  391. bootstack/widgets/_impl/primitives/flexframe.py +448 -0
  392. bootstack/widgets/_impl/primitives/frame.py +185 -0
  393. bootstack/widgets/_impl/primitives/gridframe.py +546 -0
  394. bootstack/widgets/_impl/primitives/label.py +84 -0
  395. bootstack/widgets/_impl/primitives/labelframe.py +54 -0
  396. bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
  397. bootstack/widgets/_impl/primitives/packframe.py +227 -0
  398. bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
  399. bootstack/widgets/_impl/primitives/progressbar.py +83 -0
  400. bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
  401. bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
  402. bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
  403. bootstack/widgets/_impl/primitives/separator.py +43 -0
  404. bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
  405. bootstack/widgets/_impl/primitives/spinbox.py +95 -0
  406. bootstack/widgets/_impl/primitives/switch.py +44 -0
  407. bootstack/widgets/_impl/primitives/treeview.py +69 -0
  408. bootstack/widgets/app.py +371 -0
  409. bootstack/widgets/appshell.py +1179 -0
  410. bootstack/widgets/avatar.py +140 -0
  411. bootstack/widgets/boolean_controls.py +455 -0
  412. bootstack/widgets/button.py +224 -0
  413. bootstack/widgets/buttongroup.py +239 -0
  414. bootstack/widgets/calendar.py +195 -0
  415. bootstack/widgets/card.py +159 -0
  416. bootstack/widgets/carousel.py +241 -0
  417. bootstack/widgets/chart.py +302 -0
  418. bootstack/widgets/codeeditor.py +675 -0
  419. bootstack/widgets/contextmenu.py +371 -0
  420. bootstack/widgets/datatable.py +688 -0
  421. bootstack/widgets/datefield.py +395 -0
  422. bootstack/widgets/divider.py +60 -0
  423. bootstack/widgets/expander.py +579 -0
  424. bootstack/widgets/form.py +200 -0
  425. bootstack/widgets/gallery.py +250 -0
  426. bootstack/widgets/gauge.py +168 -0
  427. bootstack/widgets/grid.py +121 -0
  428. bootstack/widgets/groupbox.py +162 -0
  429. bootstack/widgets/label.py +229 -0
  430. bootstack/widgets/listview.py +354 -0
  431. bootstack/widgets/menubutton.py +419 -0
  432. bootstack/widgets/numberfield.py +431 -0
  433. bootstack/widgets/pagestack.py +345 -0
  434. bootstack/widgets/passwordfield.py +383 -0
  435. bootstack/widgets/pathfield.py +454 -0
  436. bootstack/widgets/picture.py +251 -0
  437. bootstack/widgets/progressbar.py +106 -0
  438. bootstack/widgets/radio_variants.py +271 -0
  439. bootstack/widgets/radiogroup.py +228 -0
  440. bootstack/widgets/scrollbar.py +64 -0
  441. bootstack/widgets/scrollview.py +186 -0
  442. bootstack/widgets/select.py +302 -0
  443. bootstack/widgets/selectbutton.py +182 -0
  444. bootstack/widgets/sidebar_toggle.py +130 -0
  445. bootstack/widgets/sizegrip.py +42 -0
  446. bootstack/widgets/slider.py +408 -0
  447. bootstack/widgets/spinbox.py +147 -0
  448. bootstack/widgets/spinnerfield.py +413 -0
  449. bootstack/widgets/splash.py +367 -0
  450. bootstack/widgets/splitview.py +482 -0
  451. bootstack/widgets/stacks.py +249 -0
  452. bootstack/widgets/statusbar.py +189 -0
  453. bootstack/widgets/tabs.py +425 -0
  454. bootstack/widgets/textarea.py +459 -0
  455. bootstack/widgets/textfield.py +410 -0
  456. bootstack/widgets/theme_toggle.py +92 -0
  457. bootstack/widgets/timefield.py +340 -0
  458. bootstack/widgets/toast.py +314 -0
  459. bootstack/widgets/togglegroup.py +231 -0
  460. bootstack/widgets/toolbar.py +347 -0
  461. bootstack/widgets/tooltip.py +94 -0
  462. bootstack/widgets/tree.py +648 -0
  463. bootstack/widgets/types.py +305 -0
  464. bootstack/widgets/window.py +307 -0
  465. bootstack-0.1.0.dist-info/METADATA +301 -0
  466. bootstack-0.1.0.dist-info/RECORD +471 -0
  467. bootstack-0.1.0.dist-info/WHEEL +5 -0
  468. bootstack-0.1.0.dist-info/entry_points.txt +2 -0
  469. bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
  470. bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
  471. bootstack-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,643 @@
1
+ """Style builders for SideNav widget components.
2
+
3
+ This module provides navigation-specific styling with selection indicator bars.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Optional
9
+
10
+ from bootstack.style.style_builder_ttk import StyleBuilderTtk
11
+ from bootstack.style.element import Element, ElementImage
12
+ from bootstack.style.utility import recolor_element_image, create_transparent_image, mix_colors
13
+ from bootstack.style.builders.utils import apply_icon_mapping, resolve_icon_spec
14
+ from bootstack.style.builders.toolbutton import (
15
+ toolbutton_layout, button_padding, button_font, icon_size, normalize_button_density
16
+ )
17
+
18
+
19
+ @StyleBuilderTtk.register_builder('default', 'NavigationView.TFrame')
20
+ def build_navigationview_frame_style(b: StyleBuilderTtk, ttk_style: str, accent: Optional[str] = None, **options):
21
+ """Build SideNav frame style for group expanders.
22
+
23
+ Uses standard button assets (no selection indicator needed since
24
+ groups don't get selected, only their child items do).
25
+ """
26
+ accent_token = accent or 'primary'
27
+ surface_token = options.get('surface', 'content')
28
+ density = options.get('density', 'default')
29
+ image_key = f'button_{density}'
30
+
31
+ surface = b.color(surface_token)
32
+ surface_hover = b.color(f'{surface_token}_hover') if b.colors.get(f'{surface_token}_hover') else b.active(surface)
33
+ surface_pressed = b.pressed(surface_hover)
34
+
35
+ disabled = b.disabled()
36
+
37
+ normal_img = recolor_element_image(image_key, surface, surface, surface, surface)
38
+ hover_img = recolor_element_image(image_key, surface_hover, surface_hover, surface, surface)
39
+ pressed_img = recolor_element_image(image_key, surface_pressed, surface_pressed, surface, surface)
40
+ disabled_img = recolor_element_image(image_key, disabled, disabled, surface, surface)
41
+
42
+ b.create_style_element_image(
43
+ ElementImage(
44
+ f'{ttk_style}.border', normal_img.image, sticky="nsew",
45
+ border=normal_img.meta.border, padding=normal_img.meta.border
46
+ ).state_specs(
47
+ [
48
+ ('disabled', disabled_img.image),
49
+ ('pressed', pressed_img.image),
50
+ ('hover', hover_img.image),
51
+ ('', normal_img.image)
52
+ ]))
53
+
54
+ # Create layout to use the image-based border
55
+ b.create_style_layout(
56
+ ttk_style,
57
+ Element(f'{ttk_style}.border', sticky="nsew")
58
+ )
59
+
60
+ b.configure_style(
61
+ ttk_style,
62
+ background=surface,
63
+ relief='flat',
64
+ )
65
+
66
+
67
+ @StyleBuilderTtk.register_builder('default', 'NavigationView.TLabel')
68
+ def build_navigationview_label_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
69
+ """Build SideNav label style with state-aware foreground colors.
70
+
71
+ Matches the ghost Toolbutton foreground color behavior.
72
+ """
73
+ accent_token = accent or 'primary'
74
+ surface_token = options.get('surface', 'content')
75
+
76
+ surface = b.color(surface_token)
77
+ surface_hover = b.color(f'{surface_token}_hover') if b.colors.get(f'{surface_token}_hover') else b.active(surface)
78
+ surface_pressed = b.pressed(surface_hover)
79
+ on_surface = b.on_color(surface)
80
+
81
+ accent_color = b.color(accent_token)
82
+ active = b.subtle(accent_token, surface)
83
+
84
+ on_disabled = b.disabled('text', surface)
85
+
86
+ b.configure_style(
87
+ ttk_style,
88
+ background=surface,
89
+ foreground=on_surface,
90
+ relief='flat',
91
+ font='body'
92
+ )
93
+
94
+ foreground_state_map = [
95
+ ('disabled', on_disabled),
96
+ ('selected', accent_color),
97
+ ('', on_surface)
98
+ ]
99
+
100
+ background_state_map = [
101
+ ('selected', active),
102
+ ('disabled', surface),
103
+ ('pressed !selected', surface_pressed),
104
+ ('hover !selected', surface_hover),
105
+ ('', surface)
106
+ ]
107
+
108
+ state_spec = dict(foreground=foreground_state_map, background=background_state_map)
109
+ state_spec = apply_icon_mapping(b, options, state_spec)
110
+
111
+ b.map_style(ttk_style, **state_spec)
112
+
113
+
114
+ @StyleBuilderTtk.register_builder('navigation', 'Toolbutton')
115
+ def build_navigation_toolbutton_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
116
+ """Build navigation Toolbutton style with selection indicator.
117
+
118
+ Uses nav-button assets with a left-side selection indicator bar
119
+ that shows the accent color when selected.
120
+ """
121
+ accent_token = accent or 'primary'
122
+ surface_token = options.get('surface', 'content')
123
+ density = options.get('density', 'default')
124
+ icon_only = options.get('icon_only', False)
125
+ anchor = options.get('anchor', 'center' if icon_only else 'w')
126
+ image_key = f'navitem_{normalize_button_density(density)}'
127
+
128
+ surface = b.color(surface_token)
129
+ surface_hover = b.color(f'{surface_token}_hover') if b.colors.get(f'{surface_token}_hover') else b.active(surface)
130
+ surface_pressed = b.pressed(surface_hover)
131
+ on_surface = b.on_color(surface)
132
+
133
+ active = b.subtle(accent_token, surface)
134
+ accent_color = b.color(accent_token)
135
+ accent_pressed = b.pressed(active)
136
+
137
+ disabled = b.disabled()
138
+ on_disabled = b.disabled('text', disabled)
139
+
140
+ # Normal states: indicator hidden (same color as button background)
141
+ normal_img = recolor_element_image(image_key, surface, surface, surface, surface)
142
+ hover_img = recolor_element_image(image_key, surface_hover, surface_hover, surface_hover, surface)
143
+ pressed_img = recolor_element_image(image_key, surface_pressed, surface_pressed, surface_pressed, surface)
144
+
145
+ # Selected states: in compact/icon-only mode the indicator matches the fill so it's invisible
146
+ if icon_only:
147
+ selected_img = recolor_element_image(image_key, active, active, active, surface)
148
+ selected_hover_img = recolor_element_image(image_key, active, active, active, surface)
149
+ selected_pressed_img = recolor_element_image(image_key, accent_pressed, accent_pressed, accent_pressed, surface)
150
+ else:
151
+ selected_img = recolor_element_image(image_key, active, active, accent_color, surface)
152
+ selected_hover_img = recolor_element_image(image_key, active, active, accent_color, surface)
153
+ selected_pressed_img = recolor_element_image(image_key, accent_pressed, accent_pressed, accent_color, surface)
154
+
155
+ disabled_img = recolor_element_image(image_key, disabled, disabled, disabled, surface)
156
+
157
+ b.create_style_element_image(
158
+ ElementImage(
159
+ f'{ttk_style}.border', normal_img.image, sticky="nsew",
160
+ border=normal_img.meta.border, padding=normal_img.meta.border
161
+ ).state_specs(
162
+ [
163
+ ('disabled', disabled_img.image),
164
+ ('selected pressed', selected_pressed_img.image),
165
+ ('selected hover', selected_hover_img.image),
166
+ ('selected', selected_img.image),
167
+ ('pressed', pressed_img.image),
168
+ ('hover', hover_img.image),
169
+ ('', normal_img.image)
170
+ ]))
171
+
172
+ b.create_style_layout(
173
+ ttk_style,
174
+ toolbutton_layout(ttk_style),
175
+ )
176
+
177
+ b.configure_style(
178
+ ttk_style,
179
+ background=surface,
180
+ foreground=on_surface,
181
+ relief='flat',
182
+ padding=button_padding(b, icon_only, density),
183
+ anchor=anchor,
184
+ font=button_font(density)
185
+ )
186
+
187
+ state_spec = dict(
188
+ foreground=[
189
+ ('disabled', on_disabled),
190
+ ('selected', accent_color),
191
+ ('', on_surface)
192
+ ],
193
+ )
194
+ state_spec = apply_icon_mapping(b, options, state_spec, icon_size(icon_only, density))
195
+
196
+ b.map_style(ttk_style, **state_spec)
197
+
198
+
199
+ @StyleBuilderTtk.register_builder('rail', 'Toolbutton')
200
+ def build_rail_toolbutton_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
201
+ """Build the workspace-rail item — the VS Code activity-bar treatment.
202
+
203
+ A radio toggle (single-select) rendered icon-only on the chrome surface:
204
+ the glyph is muted when idle and full-strength when selected (or hovered),
205
+ and the `navitem` asset's left indicator bar shows the **foreground** color
206
+ when selected (neutral by default — accent is opt-in, like the buttons). The
207
+ background stays flat in every state (no wash / no fill) — the bar carries the
208
+ selection.
209
+ """
210
+ accent_token = accent # None -> neutral
211
+ surface_token = options.get('surface', 'chrome')
212
+ density = options.get('density', 'default')
213
+ icon_only = options.get('icon_only', True)
214
+ # Labeled rail: a small caption under the icon (compound='top' on the widget)
215
+ # instead of icon-only + tooltips. Dedicated variant, so we just flip the font.
216
+ labeled = options.get('labeled', False)
217
+ image_key = f'navitem_{normalize_button_density(density)}'
218
+
219
+ surface = b.color(surface_token)
220
+ surface_hover = b.color(f'{surface_token}_hover') if b.colors.get(f'{surface_token}_hover') else b.active(surface)
221
+ surface_pressed = b.pressed(surface_hover)
222
+ on_surface = b.on_color(surface)
223
+ muted = b.muted_foreground(surface)
224
+ # Neutral indicator bar by default (the foreground); when an accent is set
225
+ # (`nav_accent`), the bar shows it.
226
+ bar_color = b.color(accent_token) if accent_token else on_surface
227
+ disabled = b.disabled()
228
+ on_disabled = b.disabled('text', disabled)
229
+
230
+ # Flat background throughout; the left bar is hidden (== fill) unless selected.
231
+ normal_img = recolor_element_image(image_key, surface, surface, surface, surface)
232
+ hover_img = recolor_element_image(image_key, surface_hover, surface_hover, surface_hover, surface)
233
+ pressed_img = recolor_element_image(image_key, surface_pressed, surface_pressed, surface_pressed, surface)
234
+ # Selected: foreground bar visible, background still flat (no wash).
235
+ selected_img = recolor_element_image(image_key, surface, surface, bar_color, surface)
236
+ selected_hover_img = recolor_element_image(image_key, surface_hover, surface_hover, bar_color, surface)
237
+ selected_pressed_img = recolor_element_image(image_key, surface_pressed, surface_pressed, bar_color, surface)
238
+ disabled_img = recolor_element_image(image_key, disabled, disabled, surface, surface)
239
+
240
+ b.create_style_element_image(
241
+ ElementImage(
242
+ f'{ttk_style}.border', normal_img.image, sticky="nsew",
243
+ border=normal_img.meta.border, padding=normal_img.meta.border
244
+ ).state_specs(
245
+ [
246
+ ('disabled', disabled_img.image),
247
+ ('selected pressed', selected_pressed_img.image),
248
+ ('selected hover', selected_hover_img.image),
249
+ ('selected', selected_img.image),
250
+ ('pressed', pressed_img.image),
251
+ ('hover', hover_img.image),
252
+ ('', normal_img.image)
253
+ ]))
254
+
255
+ b.create_style_layout(ttk_style, toolbutton_layout(ttk_style))
256
+
257
+ # Rail items are tall, near-square tap targets (VS Code activity bar), not the
258
+ # icon-height of a normal icon-only toolbutton. Generous vertical padding +
259
+ # anchor center gives the larger hit area and vertical rhythm. Labeled rails
260
+ # use a smaller caption font and a touch more room for the stacked label.
261
+ if labeled:
262
+ rail_font = 'caption'
263
+ rail_padding = options.get('padding') or b.scale((2, 8, 2, 8))
264
+ else:
265
+ rail_font = button_font(density)
266
+ rail_padding = options.get('padding') or b.scale((0, 7, 0, 7))
267
+
268
+ b.configure_style(
269
+ ttk_style,
270
+ background=surface,
271
+ foreground=muted,
272
+ relief='flat',
273
+ padding=rail_padding,
274
+ anchor='center',
275
+ font=rail_font,
276
+ )
277
+
278
+ state_spec = dict(
279
+ foreground=[
280
+ ('disabled', on_disabled),
281
+ ('selected', on_surface),
282
+ ('hover', on_surface),
283
+ ('', muted),
284
+ ]
285
+ )
286
+ state_spec = apply_icon_mapping(b, options, state_spec, icon_size(icon_only, density))
287
+
288
+ b.map_style(ttk_style, **state_spec)
289
+
290
+
291
+ # Sidebar nav item geometry. The icon is its OWN layout element so a real spacer
292
+ # can sit between it and the label — ttk gives no control over the compound
293
+ # image<->text gap, so the icon must be separated out (like the radio indicator).
294
+ _NAV_ICON_SIZE = 20
295
+ # Icon-only (compact) buttons read better with a slightly larger glyph and a
296
+ # taller, squarer footprint than the labeled rows.
297
+ _NAV_COMPACT_ICON_SIZE = 24
298
+ _NAV_GAP = 10
299
+ # Nine-patch content inset for nav items. Kept well below the asset border (the
300
+ # slice) so items don't carry the asset's full built-in padding as dead space
301
+ # between buttons.
302
+ _NAV_CONTENT_PAD = 4
303
+
304
+
305
+ def _nav_colors(b, accent_token, options):
306
+ surface_token = options.get('surface', 'card')
307
+ surface = b.color(surface_token)
308
+ surface_hover = b.color(f'{surface_token}_hover') if b.colors.get(f'{surface_token}_hover') else b.active(surface)
309
+ surface_pressed = b.pressed(surface_hover)
310
+ on_surface = b.on_color(surface)
311
+ disabled = b.disabled()
312
+ on_disabled = b.disabled('text', disabled)
313
+ return surface, surface_hover, surface_pressed, on_surface, disabled, on_disabled
314
+
315
+
316
+ def _nav_border_element(b, ttk_style, image_key, *, surface, surface_hover, surface_pressed,
317
+ selected_bg, selected_pressed, disabled):
318
+ """Create a nav item's flat background nine-patch (no indicator bar).
319
+
320
+ Channels are `(white=fill, black, magenta, transparent=surface)`. The edge
321
+ channels (black/magenta) must blend correctly per asset:
322
+
323
+ - the rounded `button` asset (pill) uses black=border / magenta=focus-ring,
324
+ so both must match the **background** (surface) or the rounded edge fails to
325
+ anti-alias and the pill reads square/haloed;
326
+ - the square `navitem` asset (quiet) uses black as part of the fill and
327
+ magenta as the (here hidden) indicator bar, so both must match the **fill**.
328
+
329
+ The 4th channel (surface-behind) is always the surface so rounded corners
330
+ blend into the region.
331
+ """
332
+ rounded = image_key.startswith('button')
333
+
334
+ def img(fill):
335
+ edge = surface if rounded else fill
336
+ return recolor_element_image(image_key, fill, edge, edge, surface)
337
+
338
+ normal_img = img(surface)
339
+ hover_img = img(surface_hover)
340
+ pressed_img = img(surface_pressed)
341
+ selected_img = img(selected_bg)
342
+ selected_hover_img = img(selected_bg)
343
+ selected_pressed_img = img(selected_pressed)
344
+ disabled_img = img(disabled)
345
+ # `border` is the nine-patch slice (for the rounded corners); `padding` is the
346
+ # asset's intended content inset (0 for navitem/card — no built-in dead space).
347
+ # The button's height therefore comes from the configure padding only; spacing
348
+ # BETWEEN items is the container's job (NavPanel pack).
349
+ b.create_style_element_image(
350
+ ElementImage(
351
+ f'{ttk_style}.border', normal_img.image, sticky="nsew",
352
+ border=normal_img.meta.border, padding=normal_img.meta.padding
353
+ ).state_specs(
354
+ [
355
+ ('disabled', disabled_img.image),
356
+ ('selected pressed', selected_pressed_img.image),
357
+ ('selected hover', selected_hover_img.image),
358
+ ('selected', selected_img.image),
359
+ ('pressed', pressed_img.image),
360
+ ('hover', hover_img.image),
361
+ ('', normal_img.image)
362
+ ]))
363
+
364
+
365
+ def _build_nav_expanded(b, ttk_style, *, accent_token, options, image_key, selected_bg, selected_fg):
366
+ """Expanded nav item: icon element + spacer + text label (a real icon/text gap)."""
367
+ density = options.get('density', 'default')
368
+ surface, surface_hover, surface_pressed, on_surface, disabled, on_disabled = _nav_colors(b, accent_token, options)
369
+ # Idle items are slightly muted (a light blend toward the surface) so the
370
+ # selected item — and content headings — stay the brightest text. A gentle
371
+ # mute, not the heavy header mute, so idle rows still sit above the section
372
+ # headers in the 3-tier hierarchy (selected > idle > header).
373
+ idle_fg = mix_colors(on_surface, surface, 0.85)
374
+ _nav_border_element(
375
+ b, ttk_style, image_key, surface=surface, surface_hover=surface_hover,
376
+ surface_pressed=surface_pressed, selected_bg=selected_bg,
377
+ selected_pressed=b.pressed(selected_bg), disabled=disabled,
378
+ )
379
+
380
+ fg_spec = [('disabled', on_disabled), ('selected', selected_fg), ('', idle_fg)]
381
+
382
+ # Icon as its own stateful element (recolored to follow the text foreground).
383
+ icon_spec = resolve_icon_spec(options)
384
+ has_icon = False
385
+ if icon_spec:
386
+ spec = b.normalize_icon_spec(icon_spec, _NAV_ICON_SIZE)
387
+ icon_map = b.map_stateful_icons(spec, fg_spec)
388
+ if icon_map:
389
+ has_icon = True
390
+ b.create_style_element_image(
391
+ ElementImage(f'{ttk_style}.icon', icon_map[-1][1], sticky="").state_specs(icon_map))
392
+
393
+ # Spacer between icon and text; collapses to ~0 in icon-only (the 'alternate'
394
+ # state) so the same layout also serves the compacted item.
395
+ gap = create_transparent_image(_NAV_GAP, 1)
396
+ gap0 = create_transparent_image(1, 1)
397
+ b.create_style_element_image(
398
+ ElementImage(f'{ttk_style}.spacer', gap, sticky="").state_specs([('alternate', gap0), ('', gap)]))
399
+
400
+ row = []
401
+ if has_icon:
402
+ row.append(Element(f'{ttk_style}.icon', side="left", sticky=""))
403
+ row.append(Element(f'{ttk_style}.spacer', side="left", sticky=""))
404
+ row.append(Element('Toolbutton.label', side="left", sticky="nsew"))
405
+ b.create_style_layout(
406
+ ttk_style,
407
+ Element(f'{ttk_style}.border', sticky="nsew").children(
408
+ [Element('Toolbutton.padding', sticky="nsew").children(row)]))
409
+
410
+ b.configure_style(
411
+ ttk_style, background=surface, foreground=idle_fg, relief='flat',
412
+ padding=options.get('padding') or b.scale((10, 8, 10, 8)),
413
+ anchor=options.get('anchor', 'w'), font=button_font(density),
414
+ )
415
+ # Only the text label's foreground here — the icon element carries its own.
416
+ b.map_style(ttk_style, foreground=fg_spec)
417
+
418
+
419
+ def _build_nav_compact(b, ttk_style, *, accent_token, options, image_key, selected_bg, selected_fg):
420
+ """Compact nav item: a single centered icon (icon-only)."""
421
+ density = options.get('density', 'default')
422
+ surface, surface_hover, surface_pressed, on_surface, disabled, on_disabled = _nav_colors(b, accent_token, options)
423
+ idle_fg = mix_colors(on_surface, surface, 0.85) # slight idle mute (see _build_nav_expanded)
424
+ _nav_border_element(
425
+ b, ttk_style, image_key, surface=surface, surface_hover=surface_hover,
426
+ surface_pressed=surface_pressed, selected_bg=selected_bg,
427
+ selected_pressed=b.pressed(selected_bg), disabled=disabled,
428
+ )
429
+ b.create_style_layout(ttk_style, toolbutton_layout(ttk_style))
430
+ b.configure_style(
431
+ ttk_style, background=surface, foreground=idle_fg, relief='flat',
432
+ # No horizontal padding: the compact sidebar is only the rail width, and
433
+ # the icon centers via anchor; a bit more vertical padding makes the
434
+ # icon-only buttons taller/squarer.
435
+ padding=options.get('padding') or b.scale((0, 10, 0, 10)), anchor='center',
436
+ font=button_font(density),
437
+ )
438
+ state_spec = dict(foreground=[('disabled', on_disabled), ('selected', selected_fg), ('', idle_fg)])
439
+ state_spec = apply_icon_mapping(b, options, state_spec, _NAV_COMPACT_ICON_SIZE)
440
+ b.map_style(ttk_style, **state_spec)
441
+
442
+
443
+ def _selection_colors(b, accent_token, options):
444
+ """Selected nav-item (background, foreground), per accent + selection style.
445
+
446
+ Neutral by default (a light active-level wash + neutral fg). With an accent:
447
+ `'ghost'` (default) = a subtle accent wash + **full foreground** text;
448
+ `'solid'` = the solid accent filled + the on-accent (e.g. white) fg — the
449
+ higher-emphasis look. Solid needs an accent; without one falls back to neutral.
450
+
451
+ The ghost foreground is the full foreground (bright on dark, dark on light),
452
+ NOT the accent — accent text is too dim for low-luminance hues (blue) to pop
453
+ consistently. The accent identity instead lives in the wash + the indicator
454
+ bar, which read by hue regardless of luminance, so selection pops uniformly
455
+ across every accent. Idle items are muted, so the bright selected text stands
456
+ out (the prominence is luminance, not color).
457
+ """
458
+ surface = b.color(options.get('surface', 'card'))
459
+ if accent_token:
460
+ if options.get('selection_style') == 'solid':
461
+ fill = b.color(accent_token)
462
+ return fill, b.on_color(fill)
463
+ return b.subtle(accent_token, surface), b.on_color(surface)
464
+ return b.active(surface), b.on_color(surface)
465
+
466
+
467
+ def _quiet_colors(b, accent_token, options):
468
+ return _selection_colors(b, accent_token, options)
469
+
470
+
471
+ def _pill_colors(b, accent_token, options):
472
+ return _selection_colors(b, accent_token, options)
473
+
474
+
475
+ @StyleBuilderTtk.register_builder('nav-quiet', 'Toolbutton')
476
+ def build_nav_quiet_toolbutton_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
477
+ """Quiet sidebar nav item (under a rail): subtle wash, neutral fg, no bar."""
478
+ accent_token = accent # None -> neutral
479
+ image_key = f'navitem_{normalize_button_density(options.get("density", "default"))}'
480
+ sel_bg, sel_fg = _quiet_colors(b, accent_token, options)
481
+ _build_nav_expanded(b, ttk_style, accent_token=accent_token, options=options,
482
+ image_key=image_key, selected_bg=sel_bg, selected_fg=sel_fg)
483
+
484
+
485
+ @StyleBuilderTtk.register_builder('nav-quiet-compact', 'Toolbutton')
486
+ def build_nav_quiet_compact_toolbutton_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
487
+ """Quiet nav item compacted to a centered icon."""
488
+ accent_token = accent # None -> neutral
489
+ image_key = f'navitem_{normalize_button_density(options.get("density", "default"))}'
490
+ sel_bg, sel_fg = _quiet_colors(b, accent_token, options)
491
+ _build_nav_compact(b, ttk_style, accent_token=accent_token, options=options,
492
+ image_key=image_key, selected_bg=sel_bg, selected_fg=sel_fg)
493
+
494
+
495
+ @StyleBuilderTtk.register_builder('nav-pill', 'Toolbutton')
496
+ def build_nav_pill_toolbutton_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
497
+ """Standalone sidebar nav item: a rounded accent pill + accent fg (macOS vibe).
498
+
499
+ The primary nav (no rail): the selected row gets a rounded accent-tinted pill
500
+ (the `card` asset — black/white only, no focus-ring band) with accent
501
+ foreground. Distinct from the quiet under-a-rail treatment (square wash +
502
+ neutral fg).
503
+ """
504
+ accent_token = accent # None -> neutral
505
+ image_key = 'card'
506
+ sel_bg, sel_fg = _pill_colors(b, accent_token, options)
507
+ _build_nav_expanded(b, ttk_style, accent_token=accent_token, options=options,
508
+ image_key=image_key, selected_bg=sel_bg, selected_fg=sel_fg)
509
+
510
+
511
+ @StyleBuilderTtk.register_builder('nav-pill-compact', 'Toolbutton')
512
+ def build_nav_pill_compact_toolbutton_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
513
+ """Standalone nav item compacted to a centered icon (rounded pill on select)."""
514
+ accent_token = accent # None -> neutral
515
+ image_key = 'card'
516
+ sel_bg, sel_fg = _pill_colors(b, accent_token, options)
517
+ _build_nav_compact(b, ttk_style, accent_token=accent_token, options=options,
518
+ image_key=image_key, selected_bg=sel_bg, selected_fg=sel_fg)
519
+
520
+
521
+ @StyleBuilderTtk.register_builder('default', 'NavigationButton.TFrame')
522
+ def build_navigationbutton_frame_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
523
+ """Build NavigationButton frame style with selection indicator.
524
+
525
+ Uses nav-button assets with a left-side selection indicator bar
526
+ that shows the accent color when selected. This is the container
527
+ frame for the composite navigation button.
528
+ """
529
+ accent_token = accent or 'primary'
530
+ surface_token = options.get('surface', 'content')
531
+ density = options.get('density', 'default')
532
+ icon_only = options.get('icon_only', False)
533
+ image_key = f'navitem_{normalize_button_density(density)}'
534
+
535
+ surface = b.color(surface_token)
536
+ surface_hover = b.color(f'{surface_token}_hover') if b.colors.get(f'{surface_token}_hover') else b.active(surface)
537
+ surface_pressed = b.pressed(surface_hover)
538
+
539
+ active = b.subtle(accent_token, surface)
540
+ accent_color = b.color(accent_token)
541
+ accent_pressed = b.pressed(active)
542
+
543
+ disabled = b.disabled()
544
+
545
+ # Normal states: indicator hidden (same color as button background)
546
+ normal_img = recolor_element_image(image_key, surface, surface, surface, surface)
547
+ hover_img = recolor_element_image(image_key, surface_hover, surface_hover, surface_hover, surface)
548
+ pressed_img = recolor_element_image(image_key, surface_pressed, surface_pressed, surface_pressed, surface)
549
+
550
+ # Selected states: in compact/icon-only mode the indicator matches the fill so it's invisible
551
+ if icon_only:
552
+ selected_img = recolor_element_image(image_key, active, active, active, surface)
553
+ selected_hover_img = recolor_element_image(image_key, active, active, active, surface)
554
+ selected_pressed_img = recolor_element_image(image_key, accent_pressed, accent_pressed, accent_pressed, surface)
555
+ else:
556
+ selected_img = recolor_element_image(image_key, active, active, accent_color, surface)
557
+ selected_hover_img = recolor_element_image(image_key, active, active, accent_color, surface)
558
+ selected_pressed_img = recolor_element_image(image_key, accent_pressed, accent_pressed, accent_color, surface)
559
+
560
+ disabled_img = recolor_element_image(image_key, disabled, disabled, disabled, surface)
561
+
562
+ b.create_style_element_image(
563
+ ElementImage(
564
+ f'{ttk_style}.border', normal_img.image, sticky="ew",
565
+ border=normal_img.meta.border
566
+ ).state_specs(
567
+ [
568
+ ('disabled', disabled_img.image),
569
+ ('selected pressed', selected_pressed_img.image),
570
+ ('selected hover', selected_hover_img.image),
571
+ ('selected', selected_img.image),
572
+ ('pressed', pressed_img.image),
573
+ ('hover', hover_img.image),
574
+ ('', normal_img.image)
575
+ ]))
576
+
577
+ # Create layout to use the image-based border
578
+ b.create_style_layout(
579
+ ttk_style,
580
+ Element(f'{ttk_style}.border', sticky="nsew")
581
+ )
582
+
583
+ b.configure_style(
584
+ ttk_style,
585
+ background=surface,
586
+ relief='flat',
587
+ )
588
+
589
+
590
+ @StyleBuilderTtk.register_builder('default', 'NavigationButton.TLabel')
591
+ def build_navigationbutton_label_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
592
+ """Build NavigationButton label style with state-aware colors.
593
+
594
+ Labels inside the navigation button container that respond to
595
+ hover, pressed, and selected states.
596
+ """
597
+ accent_token = accent or 'primary'
598
+ surface_token = options.get('surface', 'content')
599
+ icon_only = options.get('icon_only', False)
600
+ density = options.get('density', 'default')
601
+
602
+ surface = b.color(surface_token)
603
+ surface_hover = b.color(f'{surface_token}_hover') if b.colors.get(f'{surface_token}_hover') else b.active(surface)
604
+ surface_pressed = b.pressed(surface_hover)
605
+ on_surface = b.on_color(surface)
606
+
607
+ accent_color = b.color(accent_token)
608
+ active = b.subtle(accent_token, surface)
609
+ active_pressed = b.pressed(active)
610
+
611
+ on_disabled = b.disabled('text', surface)
612
+
613
+ b.configure_style(
614
+ ttk_style,
615
+ background=surface,
616
+ foreground=on_surface,
617
+ relief='flat',
618
+ font='body'
619
+ )
620
+
621
+ foreground_state_map = [
622
+ ('disabled', on_disabled),
623
+ ('selected', accent_color),
624
+ ('', on_surface)
625
+ ]
626
+
627
+ background_state_map = [
628
+ ('disabled', surface),
629
+ ('selected pressed', active_pressed),
630
+ ('selected', active),
631
+ ('pressed !selected', surface_pressed),
632
+ ('hover !selected', surface_hover),
633
+ ('', surface)
634
+ ]
635
+
636
+ state_spec = dict(foreground=foreground_state_map, background=background_state_map)
637
+
638
+ # Navigation icons use a smaller size than standalone icon buttons
639
+ # to stay visually consistent with context menu popup icons
640
+ nav_icon_size = 20 if icon_only else icon_size(icon_only, density)
641
+ state_spec = apply_icon_mapping(b, options, state_spec, nav_icon_size)
642
+
643
+ b.map_style(ttk_style, **state_spec)
@@ -0,0 +1,15 @@
1
+ """Sizegrip widget style builders.
2
+
3
+ This module contains style builders for ttk.Sizegrip and variants.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from bootstack.style.style_builder_ttk import StyleBuilderTtk
9
+
10
+
11
+ @StyleBuilderTtk.register_builder('default', 'TSizegrip')
12
+ def build_sizegrip_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
13
+ surface_token = options.get('surface', 'content')
14
+ surface = b.color(surface_token)
15
+ b.configure_style(ttk_style, background=surface)