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,273 @@
1
+ """A workspace — one sidebar panel + content, filled by one provider.
2
+
3
+ A `Workspace` owns a single navigation provider (chosen lazily and mutually
4
+ exclusive) mounted into its own sidebar-panel frame and content frame. The shell
5
+ holds one workspace per rail icon (and an implicit default workspace for the
6
+ single-tier case). Selection truth lives in the shell's `NavModel`; the workspace
7
+ reports clicks/refreshes back to the shell, which drives the model.
8
+
9
+ The workspace exposes the same content API the shell does — `add_page` /
10
+ `list_nav` / `tree_nav` / `@detail` / headers — so a single-tier app and a
11
+ two-tier workspace are authored identically (the spec's degenerate-case
12
+ unification).
13
+
14
+ The sidebar has three authored shapes: a flat nav list (`add_page`), the same
15
+ list chunked by static `add_header` labels (grouped-static), or a data-bound
16
+ `list_nav` / `tree_nav`. There is no built-in collapsible accordion — a section
17
+ that hides/reveals a sub-list is a content concern; compose `bs.Accordion`
18
+ inside a custom `panel()` if you need one.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Any, Callable
24
+
25
+ from bootstack.widgets._impl.composites.shell.providers import (
26
+ CustomProvider,
27
+ ListNavProvider,
28
+ StaticProvider,
29
+ TreeNavProvider,
30
+ )
31
+
32
+
33
+ class Workspace:
34
+ """One workspace's provider + sidebar-panel/content frames.
35
+
36
+ Args:
37
+ key: Workspace identifier.
38
+ sidebar_panel: The frame this workspace's sidebar provider mounts into.
39
+ content_frame: The frame this workspace's content renders into.
40
+ nav_accent: Accent token for the active nav item.
41
+ on_select: Called `(workspace_key, page_key)` when an item is clicked.
42
+ on_refresh: Called `(workspace_key)` after a data-bound provider rebuilds.
43
+ on_first_page: Called `(workspace_key, page_key)` when the workspace's
44
+ first page is added (so the shell can auto-select it).
45
+ nav_variant: Static-item style variant for the tier (`'nav-pill'`
46
+ standalone, `'nav-quiet'` under a rail).
47
+ nav_surface: Surface token the sidebar items sit on.
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ key: str,
53
+ sidebar_panel: Any,
54
+ content_frame: Any,
55
+ *,
56
+ nav_accent: str,
57
+ on_select: Callable[[str, str], None],
58
+ on_refresh: Callable[[str], None],
59
+ on_first_page: Callable[[str, str], None],
60
+ nav_variant: str = "nav-quiet",
61
+ nav_surface: str = "card",
62
+ nav_selection: str = "ghost",
63
+ ) -> None:
64
+ self._key = key
65
+ self._sidebar = sidebar_panel
66
+ self._content = content_frame
67
+ self._nav_accent = nav_accent
68
+ self._nav_variant = nav_variant
69
+ self._nav_surface = nav_surface
70
+ self._nav_selection = nav_selection
71
+ self._on_select = on_select
72
+ self._on_refresh = on_refresh
73
+ self._on_first_page = on_first_page
74
+ self._provider: Any = None
75
+
76
+ @property
77
+ def key(self) -> str:
78
+ """The workspace identifier."""
79
+ return self._key
80
+
81
+ @property
82
+ def provider(self) -> Any:
83
+ """The workspace's navigation provider (or `None`)."""
84
+ return self._provider
85
+
86
+ @property
87
+ def supports_compact(self) -> bool:
88
+ """Whether this workspace's provider can render icon-only (compact)."""
89
+ return bool(getattr(self._provider, "supports_compact", False))
90
+
91
+ def set_compact(self, compact: bool) -> None:
92
+ """Render the sidebar icon-only (`compact`) or with labels."""
93
+ if self._provider is not None and hasattr(self._provider, "set_compact"):
94
+ self._provider.set_compact(compact)
95
+
96
+ # ----- Provider selection (lazy + mutually exclusive) -----
97
+
98
+ def _ensure_static(self) -> StaticProvider:
99
+ if self._provider is None:
100
+ self._provider = StaticProvider(
101
+ accent=self._nav_accent, variant=self._nav_variant,
102
+ surface=self._nav_surface, selection=self._nav_selection,
103
+ )
104
+ self._mount_provider()
105
+ elif not isinstance(self._provider, StaticProvider):
106
+ raise RuntimeError(
107
+ "cannot mix add_page() with a data-bound provider in one workspace"
108
+ )
109
+ return self._provider
110
+
111
+ def _claim_provider(self) -> None:
112
+ if self._provider is not None:
113
+ raise RuntimeError("this workspace already has a navigation provider")
114
+
115
+ def _mount_provider(self) -> None:
116
+ self._provider.mount(
117
+ self._sidebar,
118
+ self._content,
119
+ on_select=lambda page_key: self._on_select(self._key, page_key),
120
+ on_refresh=lambda: self._on_refresh(self._key),
121
+ )
122
+
123
+ # ----- Static content API -----
124
+
125
+ def page_nav(self, *, selection: str | None = None) -> StaticProvider:
126
+ """Establish the static page-list provider; return it for authoring.
127
+
128
+ Called when the page nav is declared explicitly (so its options have a
129
+ home), before any `add_page`. `selection` overrides the selected-item
130
+ treatment (`'ghost'`/`'solid'`). `'solid'` is the standalone (`nav-pill`)
131
+ emphasis only — under a rail (`nav-quiet`) a solid fill competes with the
132
+ rail, so it is forced to the wash.
133
+ """
134
+ if selection is not None:
135
+ self._nav_selection = selection if self._nav_variant == "nav-pill" else "ghost"
136
+ return self._ensure_static()
137
+
138
+ def add_page(self, key: str, *, text: str = "", icon=None, footer: bool = False) -> Any:
139
+ """Add a nav item and its page; return the page frame."""
140
+ if not key:
141
+ raise ValueError("page key must be non-empty")
142
+ provider = self._ensure_static()
143
+ if key in provider.keys():
144
+ raise ValueError(f"duplicate page key: {key!r}")
145
+ first = not provider.keys()
146
+ page = provider.add_page(key, text=text, icon=icon, footer=footer)
147
+ if first:
148
+ self._on_first_page(self._key, key)
149
+ return page
150
+
151
+ def add_footer_page(self, key: str, *, text: str = "", icon=None) -> Any:
152
+ """Add a nav item pinned to the sidebar footer and its page."""
153
+ return self.add_page(key, text=text, icon=icon, footer=True)
154
+
155
+ def add_header(self, text: str) -> Any:
156
+ """Add a plain section-label header (grouped-static)."""
157
+ return self._ensure_static().add_header(text)
158
+
159
+ def add_divider(self) -> Any:
160
+ """Add a divider to the sidebar."""
161
+ return self._ensure_static().add_divider()
162
+
163
+ # ----- Custom mode -----
164
+
165
+ def panel(self) -> Any:
166
+ """Claim the workspace as a custom panel; return its sidebar container.
167
+
168
+ The workspace enters custom mode: no provider cascade, no compaction.
169
+ Fill the returned container with your own sidebar UI (e.g. a
170
+ `bs.Accordion`) and drive the content region yourself via `content`.
171
+ """
172
+ self._claim_provider()
173
+ self._provider = CustomProvider()
174
+ self._mount_provider()
175
+ return self._provider.container
176
+
177
+ @property
178
+ def content(self) -> Any:
179
+ """The workspace's content region frame (for hand-driven swapping)."""
180
+ return self._content
181
+
182
+ # ----- Data-bound content API -----
183
+
184
+ def list_nav(
185
+ self,
186
+ source: Any,
187
+ *,
188
+ separator: bool = False,
189
+ density: str = "default",
190
+ placeholder: str = "Select an item to view",
191
+ chevron: bool = False,
192
+ ) -> Any:
193
+ """Fill the workspace from a `DataSource` (flat master-detail).
194
+
195
+ Records render their `icon`/`title`/`text` in a recycling `ListView`
196
+ (which inherits the sidebar surface). `separator` draws divider lines
197
+ between rows (default flush); `density` is `'default'`/`'compact'`
198
+ (compact suits one-line items, default suits richer two-line items).
199
+ `placeholder` is the quiet empty-state shown in the content area when the
200
+ source has no records. `chevron` adds a per-row disclosure chevron (off by
201
+ default — use it only when a row visibly drills into something).
202
+ """
203
+ self._claim_provider()
204
+ self._provider = ListNavProvider(
205
+ source, accent=self._nav_accent, separator=separator,
206
+ density=density, placeholder=placeholder, chevron=chevron,
207
+ )
208
+ self._mount_provider()
209
+ return self._provider
210
+
211
+ def tree_nav(
212
+ self,
213
+ *,
214
+ nodes: list | None = None,
215
+ source: Any = None,
216
+ parent_field: str = "parent_id",
217
+ label_field: str = "name",
218
+ icon_field: str = "icon",
219
+ density: str = "default",
220
+ placeholder: str = "Select an item to view",
221
+ ) -> Any:
222
+ """Fill the workspace from a hierarchy (tree master-detail).
223
+
224
+ `density` is `'default'`/`'compact'`. `placeholder` is the quiet
225
+ empty-state shown in the content area until a node is picked (a tree
226
+ opens unselected).
227
+ """
228
+ self._claim_provider()
229
+ self._provider = TreeNavProvider(
230
+ nodes=nodes,
231
+ source=source,
232
+ parent_field=parent_field,
233
+ label_field=label_field,
234
+ icon_field=icon_field,
235
+ accent=self._nav_accent,
236
+ density=density,
237
+ placeholder=placeholder,
238
+ )
239
+ self._mount_provider()
240
+ return self._provider
241
+
242
+ def detail(self, fn: Callable[[dict], Any]) -> Callable[[dict], Any]:
243
+ """Register the detail body for a data-bound provider (decorator)."""
244
+ if self._provider is None or not hasattr(self._provider, "set_detail"):
245
+ raise RuntimeError("detail() requires a data-bound provider (list_nav/tree_nav)")
246
+ self._provider.set_detail(fn)
247
+ if self._provider.keys():
248
+ self._on_first_page(self._key, self._provider.keys()[0])
249
+ return fn
250
+
251
+ # ----- Provider plumbing -----
252
+
253
+ def keys(self) -> tuple[str, ...]:
254
+ """All selectable page/record keys this workspace exposes."""
255
+ return self._provider.keys() if self._provider is not None else ()
256
+
257
+ def show(self, key: str, data: dict | None = None) -> None:
258
+ """Render content for `key` (delegates to the provider)."""
259
+ if self._provider is not None:
260
+ self._provider.show(key, data=data)
261
+
262
+ def select_visual(self, key: str | None) -> None:
263
+ """Reflect the active `key` in the sidebar selection (delegates)."""
264
+ if self._provider is not None:
265
+ self._provider.select_visual(key)
266
+
267
+ def close(self) -> None:
268
+ """Release provider resources (e.g. data-source subscriptions)."""
269
+ if self._provider is not None and hasattr(self._provider, "close"):
270
+ try:
271
+ self._provider.close()
272
+ except Exception:
273
+ pass
@@ -0,0 +1,16 @@
1
+ """Static navigation building blocks shared by the shell's nav panel.
2
+
3
+ The standalone `SideNav` widget was retired with the AppShell rewrite; what
4
+ remains are the two non-interactive primitives the shell's `NavPanel` composes:
5
+
6
+ - SideNavHeader: a non-selectable section label
7
+ - SideNavSeparator: a visual divider between groups
8
+ """
9
+
10
+ from bootstack.widgets._impl.composites.sidenav.header import SideNavHeader
11
+ from bootstack.widgets._impl.composites.sidenav.separator import SideNavSeparator
12
+
13
+ __all__ = [
14
+ 'SideNavHeader',
15
+ 'SideNavSeparator',
16
+ ]
@@ -0,0 +1,81 @@
1
+ """SideNavHeader widget for section labels in navigation menus."""
2
+
3
+ from typing import Any
4
+
5
+ from typing_extensions import TypedDict, Unpack
6
+
7
+ from bootstack.widgets._impl.primitives.frame import Frame
8
+ from bootstack.widgets._impl.primitives.label import Label
9
+ from bootstack.widgets._impl.mixins import configure_delegate
10
+ from bootstack.widgets.types import Master
11
+
12
+
13
+ class SideNavHeaderKwargs(TypedDict, total=False):
14
+ text: str
15
+ # Frame options
16
+ padding: Any
17
+ width: int
18
+ height: int
19
+
20
+
21
+ class SideNavHeader(Frame):
22
+ """A section header for grouping navigation items.
23
+
24
+ SideNavHeader provides a text label to identify groups of related
25
+ navigation items. Unlike SideNavItem, headers are not selectable
26
+ and serve only as visual labels — a quiet, non-interactive section
27
+ marker. Uses the 'label' font token for styling.
28
+
29
+ A header is always a plain label; it does not collapse its items (a
30
+ collapsible sub-list is a content concern — use a `bs.Accordion`).
31
+ """
32
+
33
+ DEFAULT_PADDING = (8, 20, 8, 4)
34
+ DEFAULT_FONT = 'label'
35
+ # Muted, not full-strength: a section header is quiet chrome that names a
36
+ # group — it should recede so the clickable items carry the contrast.
37
+ DEFAULT_ACCENT = 'muted'
38
+
39
+ def __init__(
40
+ self,
41
+ master: Master = None,
42
+ text: str = '',
43
+ **kwargs: Unpack[SideNavHeaderKwargs]
44
+ ):
45
+ """Initialize a SideNavHeader.
46
+
47
+ Args:
48
+ master: Parent widget.
49
+ text: The header text to display.
50
+ **kwargs: Additional arguments passed to Frame.
51
+ """
52
+ self._text = text
53
+
54
+ # Default padding: more top margin for visual separation between sections
55
+ kwargs.setdefault('padding', self.DEFAULT_PADDING)
56
+
57
+ super().__init__(master, **kwargs)
58
+
59
+ # Header label with 'label' font (smaller, bold) and secondary accent.
60
+ self._text_label = Label(
61
+ self,
62
+ text=text,
63
+ font=self.DEFAULT_FONT,
64
+ accent=self.DEFAULT_ACCENT,
65
+ anchor='w',
66
+ )
67
+ self._text_label.pack(fill='x')
68
+
69
+ @property
70
+ def text(self) -> str:
71
+ """Get the header text."""
72
+ return self._text
73
+
74
+ @configure_delegate('text')
75
+ def _delegate_text(self, value: str = None):
76
+ """Configure the header text."""
77
+ if value is None:
78
+ return self._text
79
+ self._text = value
80
+ self._text_label.configure(text=value)
81
+ return None
@@ -0,0 +1,44 @@
1
+ """SideNavSeparator widget for visual grouping in navigation menus."""
2
+
3
+ from typing import Any
4
+
5
+ from typing_extensions import TypedDict, Unpack
6
+
7
+ from bootstack.widgets._impl.primitives.frame import Frame
8
+ from bootstack.widgets._impl.primitives.separator import Separator
9
+ from bootstack.widgets.types import Master
10
+
11
+
12
+ class SideNavSeparatorKwargs(TypedDict, total=False):
13
+ # Frame options
14
+ padding: Any
15
+ width: int
16
+ height: int
17
+
18
+
19
+ class SideNavSeparator(Frame):
20
+ """A visual separator for grouping items in a SideNav.
21
+
22
+ SideNavSeparator provides a horizontal line to visually separate
23
+ groups of navigation items. It is a thin wrapper around the Separator
24
+ primitive with appropriate padding for navigation contexts.
25
+
26
+ """
27
+
28
+ DEFAULT_PADDING = (0, 4, 0, 4)
29
+
30
+ def __init__(self, master: Master = None, **kwargs: Unpack[SideNavSeparatorKwargs]):
31
+ """Initialize a SideNavSeparator.
32
+
33
+ Args:
34
+ master: Parent widget.
35
+ **kwargs: Additional arguments passed to Frame.
36
+ """
37
+ # Default padding for visual separation
38
+ kwargs.setdefault('padding', self.DEFAULT_PADDING)
39
+
40
+ super().__init__(master, **kwargs)
41
+
42
+ # Create horizontal separator
43
+ self._separator = Separator(self, orient='horizontal')
44
+ self._separator.pack(fill='x')
@@ -0,0 +1,7 @@
1
+ """bs.Slider and bs.RangeSlider — themed canvas-based slider widgets."""
2
+ from .slider import Slider
3
+ from .rangeslider import RangeSlider
4
+
5
+ __all__ = [
6
+ "Slider", "RangeSlider",
7
+ ]
@@ -0,0 +1,195 @@
1
+ """Shared constants and PIL helpers for Slider and RangeSlider."""
2
+ from __future__ import annotations
3
+
4
+ from PIL import Image, ImageDraw
5
+ from PIL.ImageTk import PhotoImage
6
+
7
+ # Focus-ring border thickness. Retired (0): the ring was never wired to an
8
+ # actual keyboard-focus style and its frozen color did not track theme changes,
9
+ # so it only ever showed as a stale 1-2px border. Kept as a named constant so
10
+ # the geometry math (`cross_size + HL * 2`) and the highlight kwargs stay valid.
11
+ HL = 0
12
+
13
+ HANDLE_SIZE = 22 # handle diameter (px) — matches slider-handle.png at standard DPI
14
+ TRACK_H = 6 # track height (px)
15
+
16
+ # Value badge geometry
17
+ BADGE_H = 22
18
+ BADGE_PAD_X = 8
19
+ BADGE_GAP = 5 # gap between badge bottom and handle top
20
+ BADGE_FONT_SIZE = 9
21
+
22
+ # Tick geometry
23
+ TICK_GAP = 4
24
+ MAJOR_TICK_H = 8
25
+ MINOR_TICK_H = 4
26
+ LABEL_GAP = 3
27
+ LABEL_FONT_SIZE = 9
28
+
29
+ # Keyboard step sizes
30
+ STEP = 1
31
+ STEP_LARGE = 10
32
+
33
+ # Keyboard focus ring (RangeSlider: a halo around the active handle)
34
+ FOCUS_RING_GAP = 2 # gap between the handle edge and the ring
35
+ FOCUS_RING_W = 2 # ring stroke width
36
+ HALO_PAD = FOCUS_RING_GAP + FOCUS_RING_W # ring overhang beyond the handle,
37
+ # reserved on every side so it can't clip
38
+
39
+
40
+ def _make_focus_ring(diameter: int, color: str, width: int = FOCUS_RING_W) -> PhotoImage:
41
+ """Render a smooth anti-aliased ring (transparent center) as a PhotoImage —
42
+ the keyboard focus halo drawn around the active handle.
43
+
44
+ Drawn as a filled outer disc with the center punched out (an annulus) at 4x
45
+ and LANCZOS-downsampled, which anti-aliases far better than a thin outline
46
+ stroke at the target size.
47
+ """
48
+ scale = 4
49
+ s = diameter * scale
50
+ margin = scale # 1px transparent margin so the outer edge
51
+ # isn't clipped at the image boundary
52
+ img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
53
+ d = ImageDraw.Draw(img)
54
+ d.ellipse([margin, margin, s - 1 - margin, s - 1 - margin], fill=color)
55
+ inset = margin + width * scale # ring thickness = width (post-downsample)
56
+ d.ellipse([inset, inset, s - 1 - inset, s - 1 - inset], fill=(0, 0, 0, 0))
57
+ return PhotoImage(image=img.resize((diameter, diameter), Image.Resampling.LANCZOS))
58
+
59
+
60
+ def _make_badge(width: int, height: int, bg_color: str) -> PhotoImage:
61
+ """Render an anti-aliased pill badge background as a PhotoImage."""
62
+ sw, sh = width * 2, height * 2
63
+ r = sh // 2
64
+ img = Image.new("RGBA", (sw, sh), (0, 0, 0, 0))
65
+ d = ImageDraw.Draw(img)
66
+ d.rounded_rectangle([0, 0, sw - 1, sh - 1], radius=r, fill=bg_color)
67
+ return PhotoImage(image=img.resize((width, height), Image.Resampling.LANCZOS))
68
+
69
+
70
+ def _make_handle(fill_color: str, ring_color: str, border_color: str) -> PhotoImage:
71
+ """Recolor the slider-handle asset and return a PhotoImage.
72
+
73
+ Args:
74
+ fill_color: Accent color for the handle center (magenta channel).
75
+ ring_color: Surface color for the outer ring (white channel).
76
+ border_color: Neutral stroke around the outer edge (black channel).
77
+ """
78
+ from bootstack.style.utility import recolor_element_image
79
+ result = recolor_element_image(
80
+ 'slider_handle',
81
+ white_color=ring_color,
82
+ black_color=border_color,
83
+ magenta_color=fill_color,
84
+ )
85
+ return result.image
86
+
87
+
88
+ def _make_track(
89
+ track_w: int,
90
+ track_h: int,
91
+ fill_start: int,
92
+ fill_end: int,
93
+ trough_color: str,
94
+ fill_color: str,
95
+ *,
96
+ vertical: bool = False,
97
+ ) -> PhotoImage | None:
98
+ """Render a slider track (trough + fill region) as a PhotoImage.
99
+
100
+ When `vertical=True` the image is rotated 90° CCW so the fill region
101
+ appears at the bottom of the returned (tall) image.
102
+ """
103
+ if track_w < 1:
104
+ return None
105
+ # Render at 2× for crisp anti-aliased edges, then LANCZOS-downsample
106
+ scale = 2
107
+ sw, sh = track_w * scale, track_h * scale
108
+ r = sh // 2
109
+ img = Image.new("RGBA", (sw, sh), (0, 0, 0, 0))
110
+ d = ImageDraw.Draw(img)
111
+ d.rounded_rectangle([0, 0, sw - 1, sh - 1], radius=r, fill=trough_color)
112
+ fw = (fill_end - fill_start) * scale
113
+ if fw >= 1:
114
+ d.rounded_rectangle(
115
+ [fill_start * scale, 0, min(fill_end * scale, sw) - 1, sh - 1],
116
+ radius=r, fill=fill_color,
117
+ )
118
+ img = img.resize((track_w, track_h), Image.Resampling.LANCZOS)
119
+ if vertical:
120
+ img = img.rotate(90, expand=True)
121
+ return PhotoImage(image=img)
122
+
123
+
124
+ def get_widget_bg(widget: object) -> str | None:
125
+ """Return a widget's rendered background as a `#rrggbb` hex string.
126
+
127
+ Tries `cget('background')` first (tk widgets), then falls back to the
128
+ ttk Style lookup (ttk widgets). Returns `None` when neither works.
129
+ """
130
+ if widget is None:
131
+ return None
132
+ # tk.Frame, tk.Canvas, etc. — direct option
133
+ try:
134
+ raw = widget.cget('background') # type: ignore[union-attr]
135
+ if raw:
136
+ r, g, b = widget.winfo_rgb(raw) # type: ignore[union-attr]
137
+ return f"#{r // 256:02x}{g // 256:02x}{b // 256:02x}"
138
+ except Exception:
139
+ pass
140
+ # ttk.Frame and bootstack Frame — resolve via style system
141
+ try:
142
+ from tkinter import ttk
143
+ sname = widget.cget('style') or '' # type: ignore[union-attr]
144
+ style = ttk.Style(widget) # type: ignore[arg-type]
145
+ bg = style.lookup(sname, 'background') if sname else None
146
+ if not bg:
147
+ wclass = widget.winfo_class() # type: ignore[union-attr]
148
+ bg = style.lookup(wclass, 'background')
149
+ if bg:
150
+ r, g, b = widget.winfo_rgb(bg) # type: ignore[union-attr]
151
+ return f"#{r // 256:02x}{g // 256:02x}{b // 256:02x}"
152
+ except Exception:
153
+ pass
154
+ return None
155
+
156
+
157
+ def resolve_colors(accent: str, surface: str, bg_override: str | None = None) -> dict[str, str]:
158
+ """Resolve all slider colors from theme tokens.
159
+
160
+ Args:
161
+ accent: Accent token (e.g., `'primary'`, `'success'`).
162
+ surface: Surface token (e.g., `'content'`, `'card'`).
163
+ bg_override: Explicit hex background color (`'#rrggbb'`) to use
164
+ instead of the theme default. Pass the result of
165
+ `get_widget_bg(master)` to make the slider blend with its
166
+ container surface.
167
+
168
+ Returns:
169
+ Dict of resolved hex color strings.
170
+ """
171
+ from bootstack.style.style import get_style
172
+ b = get_style().style_builder
173
+ bg = bg_override or b.color('background')
174
+ accent_hex = b.color(accent)
175
+ border = b.border(bg)
176
+ # A muted tone with guaranteed ≥4.5 contrast against the slider's own surface.
177
+ # (NOT `foreground[muted]`, which computes muted *relative to the foreground*
178
+ # and so lands near the background — faint everywhere, invisible in e.g.
179
+ # solarized-light. `muted_foreground(bg)` is the same value the public
180
+ # `'muted'` token resolves to.)
181
+ muted = b.muted_foreground(bg)
182
+ return {
183
+ 'bg': bg,
184
+ 'trough': border,
185
+ 'fill': accent_hex,
186
+ 'fill_disabled': muted,
187
+ 'handle': accent_hex,
188
+ 'handle_disabled': muted,
189
+ 'ring': bg,
190
+ 'handle_border': border,
191
+ 'badge_bg': b.color('foreground'),
192
+ 'badge_text': b.on_color(b.color('foreground')),
193
+ 'tick': muted,
194
+ 'focus': b.color('foreground'),
195
+ }