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,96 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable
4
+
5
+
6
+ class WindowControlsMixin:
7
+ """Window lifecycle and show-state controls shared by top-level windows.
8
+
9
+ Mixed into `App`, `AppShell`, and `Window` so every top-level window exposes
10
+ the same close and show-state surface.
11
+ """
12
+
13
+ _internal: Any
14
+
15
+ def close(self) -> None:
16
+ """Close the window and exit its event loop.
17
+
18
+ For the application window this quits the loop started by `run()` — the
19
+ natural action for a "Quit" command. It does not run the `on_close` veto
20
+ handlers (those guard the user clicking the window's close button), so a
21
+ programmatic `close()` always proceeds.
22
+ """
23
+ self._internal.close()
24
+
25
+ def show(self) -> None:
26
+ """Show the window — typically to reveal it again after `hide()`.
27
+
28
+ For the application window, `run()` already shows it and starts the
29
+ event loop; reach for `show()` to bring a hidden window back.
30
+ """
31
+ self._internal.show()
32
+
33
+ def hide(self) -> None:
34
+ """Hide the window without destroying it.
35
+
36
+ The window is unmapped (and usually removed from the taskbar). Bring it
37
+ back with `show()`.
38
+ """
39
+ self._internal.hide()
40
+
41
+ def minimize(self) -> None:
42
+ """Minimize the window to the taskbar or dock."""
43
+ self._internal.minimize()
44
+
45
+ def maximize(self) -> None:
46
+ """Maximize the window where the platform supports it."""
47
+ self._internal.maximize()
48
+
49
+ def set_fullscreen(self, value: bool = True) -> None:
50
+ """Enter or leave fullscreen mode where supported.
51
+
52
+ Args:
53
+ value: `True` to enter fullscreen, `False` to exit. Default `True`.
54
+ """
55
+ self._internal.set_fullscreen(value)
56
+
57
+ def set_topmost(self, value: bool = True) -> None:
58
+ """Pin the window above all others, or release it.
59
+
60
+ Args:
61
+ value: `True` to keep the window above others, `False` to release.
62
+ Default `True`.
63
+ """
64
+ self._internal.set_topmost(value)
65
+
66
+ def on_close(self, handler: Callable[[], bool | None]) -> None:
67
+ """Register a handler invoked when the user clicks the window's close button.
68
+
69
+ Handlers run in registration order; return `False` from one to veto the
70
+ close (the window stays open), or `None`/`True` to allow it. This guards
71
+ the close-button gesture only — it is not run by the programmatic
72
+ `close()`.
73
+
74
+ Args:
75
+ handler: Called with no arguments on a close request. Return `False`
76
+ to cancel the close.
77
+ """
78
+ self._internal.add_close_handler(handler)
79
+
80
+ def add_close_handler(self, handler: Callable[[], bool | None]) -> None:
81
+ """Register a veto-able close handler — an alias for `on_close()`.
82
+
83
+ Args:
84
+ handler: Called with no arguments on a close request. Return `False`
85
+ to cancel the close.
86
+ """
87
+ self._internal.add_close_handler(handler)
88
+
89
+ def remove_close_handler(self, handler: Callable[[], bool | None]) -> None:
90
+ """Remove a close handler previously registered with `on_close()`.
91
+
92
+ Args:
93
+ handler: The same callable passed to `on_close()` or
94
+ `add_close_handler()`. No-op if it was not registered.
95
+ """
96
+ self._internal.remove_close_handler(handler)
@@ -0,0 +1,246 @@
1
+ """The stacked-toolbar chrome host for top-level windows.
2
+
3
+ `ChromeHostMixin` gives `App` / `Window` / `AppShell` an `add_toolbar()` factory:
4
+ the window's top region is a vertical stack of `Toolbar`s, each holding buttons,
5
+ labels, widgets, and menus (`toolbar.add_menu(...)`). A menu is just a toolbar
6
+ item — it renders as an in-window dropdown on Windows/Linux and bridges to the
7
+ native global menu bar on macOS (aggregated across every bridged chrome toolbar).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ # Softness for a chrome divider, matching the shell's region dividers: the stroke
14
+ # blends close to its surface for a quiet hairline rather than the default
15
+ # separator border (which blends harder toward the semantic border color).
16
+ _CHROME_DIVIDER_BORDER_STRENGTH = 0.90
17
+
18
+
19
+ class _ToolbarStackContainer:
20
+ """Public-container shim so an `add_toolbar()` toolbar packs into the host's
21
+ vertical chrome stack (full-width, top-to-bottom in creation order)."""
22
+
23
+ _auto_place = True
24
+
25
+ def __init__(self, frame: Any) -> None:
26
+ self._frame = frame
27
+
28
+ def _child_master(self) -> Any:
29
+ return self._frame
30
+
31
+ def guide_layout(self, child: Any, **layout_kw: Any) -> None:
32
+ # Toolbars stack full-width in creation order; the stack owns the layout.
33
+ child._internal.pack(side="top", fill="x")
34
+
35
+
36
+ class ChromeHostMixin:
37
+ """`add_toolbar()` — the window's top region is a stack of toolbars.
38
+
39
+ Each `add_toolbar()` appends a full-width `Toolbar` band; menus live on the
40
+ toolbars (`toolbar.add_menu(...)`). On macOS the bridged toolbars' menus are
41
+ aggregated into the native global menu bar; on Windows/Linux they render as
42
+ in-window dropdowns. `AppShell` overrides `_ensure_toolbar_stack()` to mount
43
+ the stack into its own region layout.
44
+ """
45
+
46
+ def _menu_root(self) -> Any:
47
+ """Tk root/toplevel for the native menubar (`['menu']`) + shortcuts."""
48
+ return getattr(self, "_internal", None)
49
+
50
+ # ----- toolbar stack -----
51
+
52
+ def add_toolbar(
53
+ self, *, divider: bool = False, use_macos_menus: bool = True, **toolbar_kwargs: Any
54
+ ) -> Any:
55
+ """Append a `Toolbar` to the window's top chrome stack and return it.
56
+
57
+ Toolbars stack full-width, top-to-bottom, in the order added — fill each
58
+ with buttons, labels, widgets, and menus (`toolbar.add_menu(...)`). The
59
+ returned `Toolbar` is a context manager, so the natural form reads::
60
+
61
+ with window.add_toolbar() as tb:
62
+ tb.add_menu("File")
63
+ tb.add_spacer()
64
+ tb.add_theme_toggle()
65
+
66
+ Args:
67
+ divider: Draw a hairline beneath this toolbar (default `False`).
68
+ use_macos_menus: On macOS, bridge this toolbar's menus to the native
69
+ global menu bar (in-window dropdowns hide). Default `True`; set
70
+ `False` to keep its menus in-window even on macOS. No effect on
71
+ Windows/Linux.
72
+ **toolbar_kwargs: Forwarded to `Toolbar` — e.g. `surface`, `density`,
73
+ `button_variant`, `show_window_controls`, `draggable`. Window
74
+ chrome defaults `surface='chrome'` and `density='compact'` (a tight
75
+ strip); pass `density='default'` for a roomier bar.
76
+
77
+ Returns:
78
+ The `Toolbar` for this layer.
79
+ """
80
+ from bootstack.widgets._impl.primitives import Separator
81
+ from bootstack.widgets.toolbar import Toolbar
82
+
83
+ stack = self._ensure_toolbar_stack()
84
+ toolbar_kwargs.setdefault("surface", "chrome")
85
+ # Window chrome reads as a tight strip (menu bars, title bars, command
86
+ # bars are all compact by convention) — default to compact, overridable.
87
+ toolbar_kwargs.setdefault("density", "compact")
88
+ tb = Toolbar(parent=_ToolbarStackContainer(stack), _host=self, **toolbar_kwargs)
89
+ if divider:
90
+ # A bare full-width hairline — no margin. The bar above provides any
91
+ # spacing through its own (chrome) padding, so nothing of the stack
92
+ # surface leaks below the line when this is the last band before the
93
+ # content (the window's content surface starts right after the line).
94
+ Separator(
95
+ stack, orient="horizontal", surface=toolbar_kwargs["surface"],
96
+ border_strength=_CHROME_DIVIDER_BORDER_STRENGTH,
97
+ ).pack(side="top", fill="x")
98
+ self._register_chrome_toolbar(tb, use_macos_menus)
99
+ return tb
100
+
101
+ def _ensure_default_titlebar(self) -> None:
102
+ """Give a borderless window a movable, closeable title bar.
103
+
104
+ Batteries-included chrome for an `undecorated` window: when the OS title
105
+ bar is gone and the author added no chrome toolbar of their own, inject a
106
+ single `add_toolbar(show_window_controls=True)` band (min/max/close on the
107
+ right, draggable, double-click to maximize) with the window title as a
108
+ left label — so a borderless window is never stranded.
109
+
110
+ No-op when window controls are turned off (`window_controls=False`), when
111
+ the window is decorated, on macOS (where `overrideredirect` is
112
+ force-disabled), or when the author already built chrome with
113
+ `add_toolbar()`. Idempotent — the author-chrome guard makes a second call
114
+ a no-op. Called from the window's show/run path, after authoring.
115
+ """
116
+ import sys
117
+
118
+ if not getattr(self, "_window_controls", True):
119
+ return
120
+ if not getattr(self, "_undecorated", False):
121
+ return
122
+ if sys.platform == "darwin":
123
+ return
124
+ if getattr(self, "_chrome_toolbars", None):
125
+ return # the author owns the chrome
126
+ tb = self.add_toolbar(show_window_controls=True)
127
+ root = self._menu_root()
128
+ title = ""
129
+ if root is not None:
130
+ try:
131
+ title = root.title()
132
+ except Exception:
133
+ title = ""
134
+ if title:
135
+ tb.add_label(title)
136
+
137
+ def _ensure_toolbar_stack(self) -> Any:
138
+ stack = getattr(self, "_toolbar_stack", None)
139
+ if stack is not None:
140
+ return stack
141
+ from bootstack.widgets._impl.primitives.packframe import PackFrame
142
+
143
+ # The stack carries the chrome surface so any gap between stacked bars
144
+ # (e.g. a divider's margin) reads as chrome rather than the window's
145
+ # content surface. Chrome-surfaced toolbars sit seamlessly on it.
146
+ stack = PackFrame(self._toolbar_stack_parent(), direction="vertical", surface="chrome")
147
+ self._mount_toolbar_stack(stack)
148
+ self._toolbar_stack = stack
149
+ return stack
150
+
151
+ def _toolbar_stack_parent(self) -> Any:
152
+ """Widget the chrome stack is built under (overridable by the host)."""
153
+ return self._menu_root()
154
+
155
+ def _mount_toolbar_stack(self, stack: Any) -> None:
156
+ """Pack the chrome stack at the top of the window, above the content."""
157
+ content = getattr(self, "_content_frame", None)
158
+ pack_kw: dict[str, Any] = {"side": "top", "fill": "x"}
159
+ if content is not None:
160
+ pack_kw["before"] = content
161
+ stack.pack(**pack_kw)
162
+
163
+ # ----- macOS native menu bridge -----
164
+
165
+ def _register_chrome_toolbar(self, tb: Any, use_macos_menus: bool) -> None:
166
+ """Track a chrome toolbar and, on macOS, wire its menus into the native
167
+ global menu bar (aggregated across all bridged toolbars)."""
168
+ toolbars = getattr(self, "_chrome_toolbars", None)
169
+ if toolbars is None:
170
+ toolbars = self._chrome_toolbars = []
171
+ toolbars.append((tb, use_macos_menus))
172
+ if use_macos_menus and self._menus_are_native():
173
+ tb._internal._on_menu_change = self._schedule_native_menu_rebuild
174
+ self._schedule_native_menu_rebuild()
175
+
176
+ def _menus_are_native(self) -> bool:
177
+ """Whether menus render in the native global bar (macOS / aqua)."""
178
+ root = self._menu_root()
179
+ try:
180
+ return root.tk.call("tk", "windowingsystem") == "aqua"
181
+ except Exception:
182
+ return False
183
+
184
+ def _aggregate_native_menu_model(self) -> Any:
185
+ """Build one `MenuModel` from every bridged chrome toolbar's menus, in
186
+ stack order then left-to-right within each toolbar. Also hides the
187
+ in-window triggers for bridged toolbars (the native bar shows them)."""
188
+ from bootstack.widgets._impl.composites.menu.model import MenuModel
189
+
190
+ agg = MenuModel()
191
+ for tb, bridge in getattr(self, "_chrome_toolbars", []):
192
+ if not bridge:
193
+ continue
194
+ model = tb._internal.menu_model
195
+ if model is None:
196
+ continue
197
+ agg.groups.extend(model.groups)
198
+ for trigger in tb._internal._menu_triggers.values():
199
+ try:
200
+ trigger.pack_forget()
201
+ except Exception:
202
+ pass
203
+ return agg
204
+
205
+ def _schedule_native_menu_rebuild(self) -> None:
206
+ root = self._menu_root()
207
+ pending = getattr(self, "_native_menu_pending", None)
208
+ if root is None:
209
+ self._rebuild_native_menu()
210
+ return
211
+ if pending is not None:
212
+ try:
213
+ root.after_cancel(pending)
214
+ except Exception:
215
+ pass
216
+ try:
217
+ self._native_menu_pending = root.after_idle(self._rebuild_native_menu)
218
+ except Exception:
219
+ self._rebuild_native_menu()
220
+
221
+ def _cancel_native_menu_pending(self) -> None:
222
+ """Cancel a scheduled native-menu rebuild and clear the handle.
223
+
224
+ Used on teardown / dev-reload reset: an `after_idle` rebuild scheduled
225
+ just before a reset would otherwise fire against freshly-rebuilt chrome.
226
+ """
227
+ pending = getattr(self, "_native_menu_pending", None)
228
+ if pending is not None:
229
+ root = self._menu_root()
230
+ if root is not None:
231
+ try:
232
+ root.after_cancel(pending)
233
+ except Exception:
234
+ pass
235
+ self._native_menu_pending = None
236
+
237
+ def _rebuild_native_menu(self) -> None:
238
+ from bootstack.widgets._impl.composites.menu.render_native import NativeMenuBar
239
+
240
+ self._native_menu_pending = None
241
+ model = self._aggregate_native_menu_model()
242
+ renderer = getattr(self, "_native_menu_renderer", None)
243
+ if renderer is None:
244
+ self._native_menu_renderer = NativeMenuBar(self._menu_root(), model)
245
+ else:
246
+ renderer.set_model(model)
@@ -0,0 +1 @@
1
+ """Internal widget implementation — do not import directly from user code."""
File without changes
@@ -0,0 +1,307 @@
1
+ """Shared base for ttk wrapper widgets with style and config delegation.
2
+
3
+ This base class wires up:
4
+ - Constructor: delegates to StyleResolver's constructor wrapper for init-time style
5
+ setup, including surface color inheritance and default variants.
6
+ - Configure: routes keys annotated via @configure_delegate to their handlers,
7
+ then forwards remaining options to the underlying ttk widget.
8
+ - Index access: `w['key']` and `w['key'] = value` work for delegated keys and
9
+ for `style` queries.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+ from bootstack.style.style_resolver import (
17
+ StyleResolver,
18
+ extract_accent_from_style,
19
+ extract_variant_from_style,
20
+ )
21
+ from bootstack.style.token_maps import CONTAINER_CLASSES, ORIENT_CLASSES
22
+ from bootstack.widgets._impl.mixins.configure_mixin import (
23
+ ConfigureDelegationMixin,
24
+ configure_delegate,
25
+ )
26
+ from bootstack.widgets._impl.mixins.font_mixin import FontMixin
27
+
28
+
29
+ class TTKWrapperBase(FontMixin, ConfigureDelegationMixin):
30
+ """Base class for all ttk wrapper widgets.
31
+
32
+ Subclasses must set `_ttk_base` to the underlying ttk class and inherit
33
+ from that ttk class as well (MRO: WrapperBase, ttk.Class).
34
+
35
+ Includes FontMixin for font modifier syntax support.
36
+ """
37
+
38
+ _ttk_base: type
39
+
40
+ def __init__(self, *args: Any, **kwargs: Any) -> None: # type: ignore[override]
41
+ """Initialize the wrapper, applying font modifier syntax and StyleResolver setup."""
42
+ font_value = self._init_font_mixin(kwargs)
43
+ init_wrapper = StyleResolver.override_ttk_widget_constructor(self._ttk_base.__init__) # type: ignore[attr-defined]
44
+ init_wrapper(self, *args, **kwargs)
45
+ if font_value is not None:
46
+ self._delegate_font(font_value)
47
+
48
+ def configure(self, cnf: Any | None = None, **kwargs: Any): # type: ignore[override]
49
+ """Configure widget options, routing delegated keys before forwarding to ttk."""
50
+ # First, route custom keys via delegation
51
+ if kwargs:
52
+ for _k in list(kwargs.keys()):
53
+ if _k in self._configure_delegate_map:
54
+ if self._config_delegate_set(_k, kwargs[_k]):
55
+ kwargs.pop(_k, None)
56
+
57
+ # Getter path for delegated keys
58
+ if isinstance(cnf, str) and cnf in self._configure_delegate_map:
59
+ handled, value = self._config_delegate_get(cnf)
60
+ if handled:
61
+ return value
62
+
63
+ # Forward remaining options directly to ttk
64
+ return self._ttk_base.configure(self, cnf, **kwargs) # type: ignore[misc]
65
+
66
+ # tk alias
67
+ config = configure
68
+
69
+ # Indexing support: delegate custom keys; handle style queries
70
+ def __setitem__(self, key: str, value: Any) -> None: # noqa: D401
71
+ if key in getattr(self, "_configure_delegate_map", {}):
72
+ self._config_delegate_set(key, value)
73
+ return None
74
+ if key == "style" and getattr(self, "__class__", None).__name__ != "OptionMenu":
75
+ return self.configure(**{key: value})
76
+ return self._ttk_base.__setitem__(self, key, value) # type: ignore[misc]
77
+
78
+ def __getitem__(self, key: str) -> Any: # noqa: D401
79
+ if key in getattr(self, "_configure_delegate_map", {}):
80
+ handled, value = self._config_delegate_get(key)
81
+ if handled:
82
+ return value
83
+ if key == "style" and getattr(self, "__class__", None).__name__ != "OptionMenu":
84
+ return self.configure(cnf=key)
85
+ return self._ttk_base.__getitem__(self, key) # type: ignore[misc]
86
+
87
+ def rebuild_style(self):
88
+ """Recreate the widget's current style with updated style options.
89
+
90
+ This is useful when runtime style_options change (e.g., showing/hiding
91
+ dropdown chevrons) and you need the builder to regenerate assets.
92
+
93
+ Generates a new style name with the correct hash based on the new
94
+ style_options, then applies it to the widget.
95
+
96
+ NOTE: Style options are updated via the `style_options()` method.
97
+ """
98
+ # Use stored _ttk_class if available (for custom style class like ButtonGroup)
99
+ # Otherwise fall back to actual widget class
100
+ widget_class = getattr(self, '_ttk_class', None) or self.winfo_class()
101
+
102
+ style_options = getattr(self, '_style_options', {})
103
+
104
+ # Use stored accent/variant if available
105
+ accent = getattr(self, '_accent', None)
106
+ variant = getattr(self, '_variant', None)
107
+
108
+ # Fall back to extraction from style if not stored
109
+ if accent is None or variant is None:
110
+ current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
111
+ if current_style:
112
+ if accent is None:
113
+ accent = extract_accent_from_style(current_style, default=None)
114
+ if variant is None:
115
+ variant = extract_variant_from_style(current_style, widget_class)
116
+
117
+ # Preserve surface and orientation in style_options
118
+ if not style_options:
119
+ style_options = {}
120
+ surface = getattr(self, "_surface", None)
121
+ if surface and surface != "content":
122
+ style_options["surface"] = surface
123
+ if widget_class in ORIENT_CLASSES:
124
+ try:
125
+ style_options["orient"] = str(self.cget("orient"))
126
+ except Exception:
127
+ pass
128
+
129
+ # Generate NEW style name with NEW hash based on new options
130
+ ttk_style = StyleResolver.create_ttk_style(
131
+ widget_class=widget_class,
132
+ style_options=style_options or None,
133
+ accent=accent,
134
+ variant=variant,
135
+ )
136
+
137
+ # Apply the new style
138
+ return self._ttk_base.configure(self, style=ttk_style) # type: ignore[misc]
139
+
140
+ # ----- Built-in delegated handlers -----
141
+ @configure_delegate("accent")
142
+ def _delegate_accent(self, value: Any = None):
143
+ """Get or set the accent token for this widget.
144
+
145
+ - Query: returns the current accent token or None
146
+ - Set: updates the widget style with the new accent, preserving variant
147
+ """
148
+ # Query path
149
+ if value is None:
150
+ # Try stored value first
151
+ stored = getattr(self, '_accent', None)
152
+ if stored:
153
+ return stored
154
+ # Fall back to extracting from current style
155
+ current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
156
+ if not current_style:
157
+ return None
158
+ return extract_accent_from_style(current_style, default=None)
159
+
160
+ # Set path - use stored _ttk_class if available
161
+ widget_class = getattr(self, '_ttk_class', None) or self.winfo_class()
162
+ current_variant = getattr(self, '_variant', None)
163
+ if current_variant is None:
164
+ current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
165
+ if current_style:
166
+ current_variant = extract_variant_from_style(current_style, widget_class)
167
+
168
+ # Coerce 'default' to None — means "no accent, use widget's neutral style"
169
+ if value == 'default':
170
+ value = None
171
+
172
+ # Store the new accent
173
+ setattr(self, '_accent', value)
174
+
175
+ # Use stored style_options
176
+ style_options = getattr(self, '_style_options', {}).copy()
177
+
178
+ surface = getattr(self, "_surface", None)
179
+ if surface and surface != "content":
180
+ style_options["surface"] = surface
181
+
182
+ if widget_class in ORIENT_CLASSES:
183
+ try:
184
+ style_options["orient"] = str(self.cget("orient"))
185
+ except Exception:
186
+ pass
187
+
188
+ if widget_class in CONTAINER_CLASSES:
189
+ style_options["surface"] = value
190
+ setattr(self, "_surface", value)
191
+
192
+ ttk_style = StyleResolver.create_ttk_style(
193
+ widget_class=widget_class,
194
+ accent=value,
195
+ variant=current_variant,
196
+ style_options=style_options or None,
197
+ )
198
+ return self._ttk_base.configure(self, style=ttk_style) # type: ignore[misc]
199
+
200
+ @configure_delegate("variant")
201
+ def _delegate_variant(self, value: Any = None):
202
+ """Get or set the variant for this widget.
203
+
204
+ - Query: returns the current variant name or None
205
+ - Set: updates the widget style with the new variant, preserving accent
206
+
207
+ Note: If the variant is not valid for this widget type, a StyleBuilderError
208
+ will be raised.
209
+ """
210
+ # Query path
211
+ if value is None:
212
+ # Try stored value first
213
+ stored = getattr(self, '_variant', None)
214
+ if stored:
215
+ return stored
216
+ # Fall back to extracting from current style
217
+ current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
218
+ if not current_style:
219
+ return None
220
+ widget_class = getattr(self, '_ttk_class', None) or self.winfo_class()
221
+ return extract_variant_from_style(current_style, widget_class)
222
+
223
+ # Set path - use stored _ttk_class if available
224
+ widget_class = getattr(self, '_ttk_class', None) or self.winfo_class()
225
+ current_accent = getattr(self, '_accent', None)
226
+ if current_accent is None:
227
+ current_style = self._ttk_base.cget(self, "style") # type: ignore[misc]
228
+ if current_style:
229
+ current_accent = extract_accent_from_style(current_style, default=None)
230
+
231
+ # Store the new variant
232
+ setattr(self, '_variant', value)
233
+
234
+ # Use stored style_options
235
+ style_options = getattr(self, '_style_options', {}).copy()
236
+
237
+ surface = getattr(self, "_surface", None)
238
+ if surface and surface != "content":
239
+ style_options["surface"] = surface
240
+
241
+ if widget_class in ORIENT_CLASSES:
242
+ try:
243
+ style_options["orient"] = str(self.cget("orient"))
244
+ except Exception:
245
+ pass
246
+
247
+ ttk_style = StyleResolver.create_ttk_style(
248
+ widget_class=widget_class,
249
+ accent=current_accent,
250
+ variant=value,
251
+ style_options=style_options or None,
252
+ )
253
+ return self._ttk_base.configure(self, style=ttk_style) # type: ignore[misc]
254
+
255
+ def configure_style_options(self, value=None, **kwargs):
256
+ """Get or set the widget style options if handled by the widget's style builder.
257
+
258
+ Special cascading keys:
259
+ surface: Updates `_surface` on this widget and (via Frame subclass) triggers
260
+ `_refresh_descendant_surfaces` to propagate the change to children.
261
+ input_background: Updates `_input_background` on this widget and (via Frame
262
+ subclass) triggers `_refresh_descendant_input_backgrounds` to propagate
263
+ the change to all descendant input widgets.
264
+ """
265
+ options = getattr(self, "_style_options", {})
266
+ if value is None:
267
+ options.update(**kwargs)
268
+ setattr(self, "_style_options", options)
269
+ if "surface" in kwargs:
270
+ surface = kwargs.get("surface")
271
+ setattr(self, "_surface", surface or "content")
272
+ if "input_background" in kwargs:
273
+ setattr(self, "_input_background", kwargs.get("input_background"))
274
+ return None
275
+ else:
276
+ return options.get(value, None)
277
+
278
+ def _capture_style_options(self, options: list[str] = None, source: Any = None):
279
+ """Extract named kwargs into a style-options dict before passing to super.
280
+
281
+ Call this before `super().__init__` to capture style-related kwargs that
282
+ are exposed as explicit parameters rather than via `style_options=`. Also
283
+ merges any `style_options` dict already in `source`.
284
+
285
+ Args:
286
+ options: Names of kwargs to extract from `source`.
287
+ source: The kwargs dict to extract from (mutated in-place).
288
+
289
+ Returns:
290
+ A dict of captured style options, e.g. `{'icon_only': True, 'icon': 'house'}`.
291
+ """
292
+ if source is None:
293
+ return {}
294
+
295
+ style_options = source.pop("style_options", {})
296
+
297
+ captured = {}
298
+ for option in options:
299
+ if option in source:
300
+ captured[option] = source.pop(option)
301
+ if captured:
302
+ style_options.update(**captured)
303
+
304
+ return style_options
305
+
306
+
307
+ __all__ = ["TTKWrapperBase"]
@@ -0,0 +1,11 @@
1
+ """Widget parts module for composite widgets."""
2
+
3
+ from bootstack.widgets._impl._parts.textentry_part import TextEntryPart
4
+ from bootstack.widgets._impl._parts.numberentry_part import NumberEntryPart
5
+ from bootstack.widgets._impl._parts.spinnerentry_part import SpinnerEntryPart
6
+
7
+ __all__ = [
8
+ 'TextEntryPart',
9
+ 'NumberEntryPart',
10
+ 'SpinnerEntryPart',
11
+ ]