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,448 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter as tk
4
+ from typing import Any
5
+
6
+ from typing_extensions import Unpack
7
+
8
+ from bootstack.widgets._impl.primitives.frame import Frame, FrameKwargs
9
+ from bootstack.widgets._impl.mixins.configure_mixin import configure_delegate
10
+ from bootstack.widgets.types import Master
11
+
12
+
13
+ def _alive(widget: tk.Widget) -> bool:
14
+ """True if `widget` still exists (Tk `destroy()` leaves dead references)."""
15
+ try:
16
+ return bool(widget.winfo_exists())
17
+ except tk.TclError:
18
+ return False
19
+
20
+
21
+ class FlexFrame(Frame):
22
+ """A grid-backed 1-D flex container (the engine behind Row and Column).
23
+
24
+ Lays children along a single main axis using the Tk *grid* geometry
25
+ manager. Unlike pack, grid can express whole-group main-axis distribution
26
+ (`justify`) without phantom spacer widgets — an empty track with a weight
27
+ claims space on its own. That makes true weighted growth, correct
28
+ `space-around`, and a `Spacer` element all expressible on one engine.
29
+
30
+ Children are added through `add_child`, which appends to an ordered managed
31
+ list and re-plans the whole layout (`_relayout`). Re-planning on every add
32
+ is required because `justify`'s phantom tracks and the content-track indices
33
+ shift as children come and go — the same approach `GridFrame` uses.
34
+
35
+ Layout model (HStack == one row, N columns shown; Column transposes):
36
+
37
+ - The single cross track always carries `weight=1` so `align`
38
+ (start/center/end/stretch) has room to position a child even when the
39
+ container is larger than its content.
40
+ - `justify` injects phantom weighted tracks *between/around* the content
41
+ cells (no widget) — only when nothing grows (grow defeats justify).
42
+ - `grow` / `grow_items` / `weights=` put weight on the *content* tracks.
43
+ - `Spacer` children occupy a real content track whose weight (flexible) or
44
+ `minsize` (fixed) is taken from the spacer.
45
+ - Gap is synthesized with edge-aware leading `padx`/`pady`, skipping the
46
+ phantom and spacer breaks so it never doubles up around them.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ master: Master = None,
52
+ *,
53
+ direction: str = "horizontal",
54
+ horizontal_items: str = "left",
55
+ vertical_items: str = "top",
56
+ grow_items: bool = False,
57
+ weights: list[int] | None = None,
58
+ gap: int = 0,
59
+ propagate: bool | None = None,
60
+ **kwargs: Unpack[FrameKwargs],
61
+ ) -> None:
62
+ """Create a FlexFrame.
63
+
64
+ The public layout vocabulary is screen-axis based: `horizontal_items` and
65
+ `vertical_items` set how children sit on the x and y axes (values are edge
66
+ names — `left`/`right`/`top`/`bottom`/`center`/`stretch`, plus `space-*`
67
+ for the stacking axis). Internally the frame resolves which screen axis is
68
+ the *main* (stacking) axis and which is the *cross* axis from `direction`.
69
+
70
+ Args:
71
+ master: Parent widget. If None, uses the default root window.
72
+ direction: Main axis — `'horizontal'` (a Row) or `'vertical'`
73
+ (a Column). Defaults to `'horizontal'`.
74
+ horizontal_items: How children are positioned on the x axis. For a
75
+ Row (horizontal stacks) this arranges the group
76
+ (`left`/`center`/`right`/`space-*`); for a Column it aligns each
77
+ child (`left`/`center`/`right`/`stretch`). Defaults to `'left'`.
78
+ vertical_items: How children are positioned on the y axis. For a
79
+ Column (vertical stacks) this arranges the group
80
+ (`top`/`center`/`bottom`/`space-*`); for a Row it aligns each
81
+ child (`top`/`center`/`bottom`/`stretch`). Defaults to `'top'`.
82
+ grow_items: When True, every content child grows equally (uniform)
83
+ to share the main axis. Defaults to False.
84
+ weights: Positional shorthand for per-child `grow` —
85
+ `weights[i]` is the flex-grow weight of content child `i`
86
+ (share of leftover main-axis space, not a fixed size). Overrides
87
+ `grow_items` and per-child `grow`; missing trailing entries are
88
+ 0. Defaults to None.
89
+ gap: Spacing in pixels between adjacent content children. Defaults
90
+ to 0.
91
+ propagate: When False, the frame does not resize to fit its
92
+ children. Defaults to None (Tk default).
93
+
94
+ Other Parameters:
95
+ **kwargs: Additional keyword arguments forwarded to `Frame`.
96
+ """
97
+ super().__init__(master, **kwargs)
98
+ self._direction = direction
99
+ self._horizontal_items = horizontal_items
100
+ self._vertical_items = vertical_items
101
+ self._grow_items = grow_items
102
+ self._weights = weights
103
+ self._gap = gap
104
+ # Ordered (widget, opts) entries. opts may carry: grow, horizontal,
105
+ # vertical, padx, pady (resolved margins), and spacer metadata
106
+ # (_spacer, _spacer_size, _spacer_weight).
107
+ self._managed: list[tuple[tk.Widget, dict[str, Any]]] = []
108
+ # Highest main-axis track index configured on the previous relayout,
109
+ # so we can zero stale tracks when the plan shrinks.
110
+ self._used_main_tracks = 0
111
+ # Count of managed children carrying `grow` or spacer metadata. Lets the
112
+ # append fast-path decide in O(1) whether the stack is "pure" (see
113
+ # `_can_append_in_place`) without rescanning every add.
114
+ self._flex_child_count = 0
115
+ if propagate is not None:
116
+ self.grid_propagate(propagate)
117
+
118
+ # ----- direction helpers -------------------------------------------------
119
+
120
+ @property
121
+ def _row_dir(self) -> bool:
122
+ """True when the main axis is horizontal (columns vary)."""
123
+ return self._direction in ("horizontal", "row")
124
+
125
+ def _main_distribution(self) -> str:
126
+ """Normalize the stacking-axis value to start/center/end/space-* tokens."""
127
+ if self._row_dir:
128
+ value = self._horizontal_items
129
+ start, end = "left", "right"
130
+ else:
131
+ value = self._vertical_items
132
+ start, end = "top", "bottom"
133
+ if value == start:
134
+ return "start"
135
+ if value == end:
136
+ return "end"
137
+ return value # center, space-between, space-around, space-evenly
138
+
139
+ def _cross_default(self) -> str:
140
+ """The cross-axis (non-stacking) container alignment value."""
141
+ return self._vertical_items if self._row_dir else self._horizontal_items
142
+
143
+ def _cross_sticky(self, value: str) -> str:
144
+ """Map a cross-axis edge value to Tk sticky chars for this direction."""
145
+ if value == "stretch":
146
+ return "ns" if self._row_dir else "ew"
147
+ if self._row_dir: # cross axis is vertical
148
+ if value == "top":
149
+ return "n"
150
+ if value == "bottom":
151
+ return "s"
152
+ else: # cross axis is horizontal
153
+ if value == "left":
154
+ return "w"
155
+ if value == "right":
156
+ return "e"
157
+ return "" # center
158
+
159
+ def index_of(self, widget: tk.Widget) -> int:
160
+ """Return the managed index of `widget`, or -1 if not managed."""
161
+ for i, (w, _) in enumerate(self._managed):
162
+ if w is widget:
163
+ return i
164
+ return -1
165
+
166
+ # ----- child management --------------------------------------------------
167
+
168
+ def add_child(
169
+ self, widget: tk.Widget, opts: dict[str, Any], index: int | None = None
170
+ ) -> None:
171
+ """Add `widget` to the flow (at `index`, else appended) and re-plan.
172
+
173
+ A plain append onto a "pure" start-aligned stack grids only the new
174
+ child (the fast path) instead of re-planning every sibling, so building
175
+ an N-child stack is O(N) rather than O(N^2). Anything that can shift a
176
+ sibling's track — a mid-list insert, a distribution mode, or a
177
+ grow/spacer child — takes the full `_relayout`.
178
+ """
179
+ entry = (widget, dict(opts))
180
+ appending = index is None or index >= len(self._managed)
181
+ new_is_flex = bool(opts.get("grow")) or bool(opts.get("_spacer"))
182
+ # Decide the fast path BEFORE the count reflects this child (the check
183
+ # asks whether the EXISTING stack is pure).
184
+ fast = appending and not new_is_flex and self._can_append_in_place()
185
+ if appending:
186
+ self._managed.append(entry)
187
+ else:
188
+ self._managed.insert(max(index, 0), entry)
189
+ if new_is_flex:
190
+ self._flex_child_count += 1
191
+ if fast:
192
+ self._grid_appended_plain(widget, opts)
193
+ else:
194
+ self._relayout()
195
+
196
+ def remove_child(self, widget: tk.Widget) -> None:
197
+ """Remove `widget` from the flow (grid-forget it) and re-plan."""
198
+ idx = self.index_of(widget)
199
+ if idx < 0:
200
+ return
201
+ _, removed_opts = self._managed[idx]
202
+ if removed_opts.get("grow") or removed_opts.get("_spacer"):
203
+ self._flex_child_count -= 1
204
+ try:
205
+ tk.Grid.forget(widget)
206
+ except tk.TclError:
207
+ pass
208
+ self._managed.pop(idx)
209
+ self._relayout()
210
+
211
+ def _can_append_in_place(self) -> bool:
212
+ """True when appending a plain child cannot move any existing sibling.
213
+
214
+ Holds only for a start-aligned stack with no container-level growth and
215
+ no grow/spacer children: every child then occupies one consecutive
216
+ track, so a new child simply takes the next one. Any distribution mode
217
+ (`grow`, `weights`, `grow_items`, a `Spacer`, or a `space-*`/`center`/
218
+ `end` justify) interleaves phantom tracks or shifts weights and needs a
219
+ full re-plan. O(1) — `_flex_child_count` avoids a per-add scan.
220
+ """
221
+ return (
222
+ self._flex_child_count == 0
223
+ and not self._grow_items
224
+ and self._weights is None
225
+ and self._main_distribution() == "start"
226
+ )
227
+
228
+ def _grid_appended_plain(self, widget: tk.Widget, opts: dict[str, Any]) -> None:
229
+ """Grid a single just-appended child, leaving its siblings in place.
230
+
231
+ Valid only under `_can_append_in_place`. Mirrors the content-child arm
232
+ of `_relayout` for the pure case: one track per child, no growth, cross
233
+ sticky from the child's alignment, and a leading gap after the first.
234
+ """
235
+ row_dir = self._row_dir
236
+ idx = len(self._managed) - 1 # one track per child in the pure case
237
+ cross_cfg = self.grid_rowconfigure if row_dir else self.grid_columnconfigure
238
+ main_cfg = self.grid_columnconfigure if row_dir else self.grid_rowconfigure
239
+ cross_cfg(0, weight=1) # cross track fills so alignment has room
240
+ main_cfg(idx, weight=0)
241
+
242
+ cross_key = "vertical" if row_dir else "horizontal"
243
+ cross_val = opts.get(cross_key) or self._cross_default()
244
+ sticky = self._cross_sticky(cross_val)
245
+
246
+ main_axis = "padx" if row_dir else "pady"
247
+ cross_axis = "pady" if row_dir else "padx"
248
+ lead = self._gap if idx > 0 else 0
249
+ trail = 0
250
+ margin_main = opts.get(main_axis)
251
+ if isinstance(margin_main, tuple):
252
+ lead += margin_main[0]
253
+ trail = margin_main[1]
254
+ elif margin_main is not None:
255
+ lead += margin_main
256
+ pad: dict[str, Any] = {}
257
+ if lead or trail:
258
+ pad[main_axis] = (lead, trail)
259
+ margin_cross = opts.get(cross_axis)
260
+ if margin_cross is not None:
261
+ pad[cross_axis] = margin_cross
262
+
263
+ self._grid_cell(widget, idx, sticky, pad)
264
+ self._used_main_tracks = max(self._used_main_tracks, idx)
265
+
266
+ # ----- configuration delegates -------------------------------------------
267
+
268
+ @configure_delegate('gap')
269
+ def _delegate_gap(self, value=None) -> int:
270
+ """Get or set the gap between children."""
271
+ if value is None:
272
+ return self._gap
273
+ self._gap = value
274
+ self._relayout()
275
+
276
+ @configure_delegate('horizontal_items')
277
+ def _delegate_horizontal_items(self, value=None) -> str:
278
+ """Get or set how children are positioned on the x axis."""
279
+ if value is None:
280
+ return self._horizontal_items
281
+ self._horizontal_items = value
282
+ self._relayout()
283
+
284
+ @configure_delegate('vertical_items')
285
+ def _delegate_vertical_items(self, value=None) -> str:
286
+ """Get or set how children are positioned on the y axis."""
287
+ if value is None:
288
+ return self._vertical_items
289
+ self._vertical_items = value
290
+ self._relayout()
291
+
292
+ # ----- the planner -------------------------------------------------------
293
+
294
+ def _grid_cell(
295
+ self, widget: tk.Widget, idx: int, sticky: str, pad: dict[str, Any]
296
+ ) -> None:
297
+ """Place `widget` into main-track `idx` on the single cross track."""
298
+ opts: dict[str, Any] = {"in_": self, "sticky": sticky, **pad}
299
+ if self._row_dir:
300
+ opts.update(row=0, column=idx)
301
+ else:
302
+ opts.update(column=0, row=idx)
303
+ tk.Grid.configure(widget, **opts)
304
+
305
+ def _relayout(self) -> None:
306
+ """Re-plan and re-grid every managed child from scratch."""
307
+ row_dir = self._row_dir
308
+ main_cfg = self.grid_columnconfigure if row_dir else self.grid_rowconfigure
309
+ cross_cfg = self.grid_rowconfigure if row_dir else self.grid_columnconfigure
310
+
311
+ # Reposition surviving children IN PLACE — re-gridding an already-gridded
312
+ # widget (grid_configure) moves it without an unmap/remap, so we must NOT
313
+ # grid_forget them here: that would fire spurious <Unmap>/<Map> (and thus
314
+ # on_detach/on_attach) on every sibling whenever one child is added or
315
+ # removed. A genuinely removed child is grid-forgotten by remove_child
316
+ # before this runs, so it is already out of self._managed.
317
+ #
318
+ # Zero the tracks the previous plan used so a now-shorter plan leaves no
319
+ # stale weights/minsizes/uniform groups (configuring a track does not
320
+ # touch the widgets in it).
321
+ for i in range(self._used_main_tracks + 1):
322
+ main_cfg(i, weight=0, minsize=0, uniform="")
323
+ self._used_main_tracks = 0
324
+
325
+ # Drop children destroyed out from under us: Tk's destroy() does not route
326
+ # through remove_child, so a dead child lingers in _managed and re-gridding
327
+ # it below would raise "bad window path name". (GridFrame guards the same.)
328
+ if any(not _alive(w) for w, _ in self._managed):
329
+ self._managed = [e for e in self._managed if _alive(e[0])]
330
+ self._flex_child_count = sum(
331
+ 1 for _, o in self._managed if o.get("grow") or o.get("_spacer")
332
+ )
333
+
334
+ items = self._managed
335
+ if not items:
336
+ return
337
+
338
+ # The cross track always fills so align has room to work.
339
+ cross_cfg(0, weight=1)
340
+
341
+ j = self._main_distribution()
342
+ per_child_grow = any(
343
+ o.get("grow") for _, o in items if not o.get("_spacer")
344
+ )
345
+ has_spacer = any(o.get("_spacer") for _, o in items)
346
+ any_grow = (
347
+ self._grow_items
348
+ or per_child_grow
349
+ or self._weights is not None
350
+ or has_spacer
351
+ )
352
+ # justify only engages when nothing competes for the main axis.
353
+ use_just = (not any_grow) and j != "start"
354
+ uniform_grow = self._grow_items and not per_child_grow and self._weights is None
355
+
356
+ # Build a plan of phantom breaks ('p', weight) and content ('c', w, o).
357
+ plan: list[tuple] = []
358
+ if use_just and j in ("end", "center", "space-evenly", "space-around"):
359
+ plan.append(("p", 1))
360
+ for i, (w, o) in enumerate(items):
361
+ if i > 0 and use_just:
362
+ if j in ("space-between", "space-evenly"):
363
+ plan.append(("p", 1))
364
+ elif j == "space-around":
365
+ plan.append(("p", 2))
366
+ plan.append(("c", w, o))
367
+ if use_just and j in ("center", "space-evenly", "space-around"):
368
+ plan.append(("p", 1))
369
+
370
+ idx = 0
371
+ content_seen = 0
372
+ prev_content = False
373
+ for entry in plan:
374
+ if entry[0] == "p":
375
+ main_cfg(idx, weight=entry[1])
376
+ idx += 1
377
+ prev_content = False
378
+ continue
379
+
380
+ _, widget, o = entry
381
+
382
+ # Spacer: a real child whose track carries the slack/fixed size.
383
+ if o.get("_spacer"):
384
+ size = o.get("_spacer_size")
385
+ if size is not None:
386
+ main_cfg(idx, weight=0, minsize=size)
387
+ else:
388
+ main_cfg(idx, weight=o.get("_spacer_weight", 1))
389
+ self._grid_cell(widget, idx, "", {})
390
+ idx += 1
391
+ prev_content = False
392
+ continue
393
+
394
+ # Content child: resolve its main-axis weight. `grow` is bool | int —
395
+ # grow=True means weight 1, grow=N sets the relative weight. Coerce
396
+ # bool to int so Tk's columnconfigure never sees a bool.
397
+ weight = o.get("grow")
398
+ if isinstance(weight, bool):
399
+ weight = 1 if weight else 0
400
+ if weight is None and self._grow_items:
401
+ weight = 1
402
+ if self._weights is not None:
403
+ weight = (
404
+ int(self._weights[content_seen])
405
+ if content_seen < len(self._weights)
406
+ else 0
407
+ )
408
+ weight = int(weight or 0)
409
+ grows = weight > 0
410
+ if uniform_grow:
411
+ main_cfg(idx, weight=weight, uniform="flex")
412
+ else:
413
+ main_cfg(idx, weight=weight)
414
+
415
+ # Cross-axis sticky from the child's cross alignment (+ main-axis
416
+ # stretch when growing). The cross axis is vertical for a Row and
417
+ # horizontal for a Column, so the relevant per-child key flips.
418
+ cross_key = "vertical" if row_dir else "horizontal"
419
+ cross_val = o.get(cross_key) or self._cross_default()
420
+ sticky = ""
421
+ if grows:
422
+ sticky += "ew" if row_dir else "ns"
423
+ sticky += self._cross_sticky(cross_val)
424
+
425
+ # Gap (leading, between content only) + per-child margins.
426
+ main_axis = "padx" if row_dir else "pady"
427
+ cross_axis = "pady" if row_dir else "padx"
428
+ lead = self._gap if prev_content else 0
429
+ trail = 0
430
+ margin_main = o.get(main_axis)
431
+ if isinstance(margin_main, tuple):
432
+ lead += margin_main[0]
433
+ trail = margin_main[1]
434
+ elif margin_main is not None:
435
+ lead += margin_main
436
+ pad: dict[str, Any] = {}
437
+ if lead or trail:
438
+ pad[main_axis] = (lead, trail)
439
+ margin_cross = o.get(cross_axis)
440
+ if margin_cross is not None:
441
+ pad[cross_axis] = margin_cross
442
+
443
+ self._grid_cell(widget, idx, sticky, pad)
444
+ idx += 1
445
+ content_seen += 1
446
+ prev_content = True
447
+
448
+ self._used_main_tracks = max(idx - 1, 0)
@@ -0,0 +1,185 @@
1
+ from __future__ import annotations
2
+
3
+ from tkinter import ttk
4
+ from typing import Any
5
+
6
+ from typing_extensions import Unpack
7
+
8
+ from bootstack._core.mixins.ttk_state import TtkStateMixin
9
+ from bootstack._core.mixins.widget import WidgetCapabilitiesMixin
10
+ from bootstack.widgets._impl._internal.wrapper_base import TTKWrapperBase
11
+ from bootstack.widgets.types import Master, StyledKwargs
12
+ from bootstack.style.style import get_style
13
+ from ..mixins import configure_delegate
14
+
15
+
16
+ class FrameKwargs(StyledKwargs, total=False):
17
+ # Standard ttk.Frame options
18
+ padding: Any
19
+ width: int
20
+ height: int
21
+
22
+ # bootstack-specific extensions
23
+ input_background: str
24
+ show_border: bool
25
+
26
+
27
+ class Frame(TTKWrapperBase, WidgetCapabilitiesMixin, TtkStateMixin, ttk.Frame):
28
+ """bootstack wrapper for `ttk.Frame` with themed styling support."""
29
+
30
+ _ttk_base = ttk.Frame
31
+
32
+ def __init__(self, master: Master = None, **kwargs: Unpack[FrameKwargs]) -> None:
33
+ """Create a themed bootstack Frame.
34
+
35
+ Args:
36
+ master: Parent widget. If None, uses the default root window.
37
+
38
+ Other Parameters:
39
+ padding: Extra padding inside the frame.
40
+ relief: Border style.
41
+ borderwidth: Border width.
42
+ width: Requested width in pixels.
43
+ height: Requested height in pixels.
44
+ takefocus: Widget accepts focus during keyboard traversal.
45
+ style: Explicit ttk style name (overrides accent/variant).
46
+ accent: Accent token for styling, e.g. 'primary', 'secondary', 'success'.
47
+ variant: Style variant (if applicable).
48
+ surface: Optional surface token; otherwise inherited.
49
+ input_background: Surface token used as the fill color for all input
50
+ widgets (Entry, Combobox, Spinbox, Field) inside this container. Cascades
51
+ to descendants the same way `surface` does. Input foreground, border,
52
+ and focus-ring colors are all derived from this fill so contrast is always
53
+ correct. Defaults to `'content'` (the app background), which keeps
54
+ inputs visually distinct regardless of the container surface. Override
55
+ with any surface token (e.g. `'card'`) to match the container.
56
+ show_border: Draw a border around the frame.
57
+ style_options: Optional dict forwarded to the style builder.
58
+ """
59
+ existing = kwargs.pop('style_options', {})
60
+ captured = self._capture_style_options(['show_border'], kwargs)
61
+ kwargs['style_options'] = {**existing, **captured}
62
+ super().__init__(master, **kwargs)
63
+
64
+ # A canvas-painting Frame subclass (Meter, Slider, …) recolors on a theme
65
+ # change by defining `_bs_apply_theme(self)`; the unified theme walk in
66
+ # `bootstack.style.style` calls it. There is no per-widget subscription or
67
+ # `<Map>` deferral — visibility is resolved by the walk at apply time.
68
+
69
+ def configure_style_options(self, value=None, **kwargs):
70
+ """Set style options and refresh descendant surfaces if needed."""
71
+ old_surface = getattr(self, "_surface", "background")
72
+ old_input_bg = getattr(self, "_input_background", None)
73
+ result = super().configure_style_options(value, **kwargs)
74
+ if value is None:
75
+ if "surface" in kwargs:
76
+ new_surface = getattr(self, "_surface", "background")
77
+ if old_surface != new_surface:
78
+ self.rebuild_style()
79
+ self._refresh_descendant_surfaces(old_surface, new_surface)
80
+ if "input_background" in kwargs:
81
+ new_input_bg = getattr(self, "_input_background", None)
82
+ if old_input_bg != new_input_bg:
83
+ self._refresh_descendant_input_backgrounds(old_input_bg, new_input_bg)
84
+ return result
85
+
86
+ def _refresh_descendant_surfaces(self, old_surface: str, new_surface: str) -> None:
87
+ if old_surface == new_surface:
88
+ return
89
+
90
+ style = get_style()
91
+ builder_tk = style._get_tk_builder()
92
+
93
+ for child in self._iter_descendants():
94
+ try:
95
+ child_surface = getattr(child, "_surface", None)
96
+ except Exception:
97
+ child_surface = None
98
+
99
+ explicit_surface = None
100
+ try:
101
+ explicit_surface = getattr(child, "_style_options", {}).get("surface")
102
+ except Exception:
103
+ explicit_surface = None
104
+
105
+ if explicit_surface and explicit_surface != old_surface:
106
+ continue
107
+
108
+ if child_surface != old_surface:
109
+ continue
110
+
111
+ try:
112
+ setattr(child, "_surface", new_surface)
113
+ except Exception:
114
+ continue
115
+
116
+ if hasattr(child, "rebuild_style"):
117
+ try:
118
+ child.rebuild_style()
119
+ except Exception:
120
+ pass
121
+ else:
122
+ try:
123
+ builder_tk.call_builder(child, surface=new_surface)
124
+ except Exception:
125
+ pass
126
+
127
+ def _refresh_descendant_input_backgrounds(self, old_bg: str | None, new_bg: str | None) -> None:
128
+ if old_bg == new_bg:
129
+ return
130
+
131
+ style = get_style()
132
+ builder_tk = style._get_tk_builder()
133
+
134
+ for child in self._iter_descendants():
135
+ try:
136
+ child_input_bg = getattr(child, "_input_background", None)
137
+ except Exception:
138
+ child_input_bg = None
139
+
140
+ explicit_input_bg = None
141
+ try:
142
+ explicit_input_bg = getattr(child, "_style_options", {}).get("input_background")
143
+ except Exception:
144
+ explicit_input_bg = None
145
+
146
+ if explicit_input_bg and explicit_input_bg != old_bg:
147
+ continue
148
+
149
+ if child_input_bg != old_bg:
150
+ continue
151
+
152
+ try:
153
+ setattr(child, "_input_background", new_bg)
154
+ except Exception:
155
+ continue
156
+
157
+ if hasattr(child, "rebuild_style"):
158
+ try:
159
+ child.rebuild_style()
160
+ except Exception:
161
+ pass
162
+ else:
163
+ try:
164
+ builder_tk.call_builder(child, input_background=new_bg)
165
+ except Exception:
166
+ pass
167
+
168
+ def _iter_descendants(self):
169
+ stack = list(self.winfo_children())
170
+ while stack:
171
+ widget = stack.pop()
172
+ yield widget
173
+ try:
174
+ stack.extend(widget.winfo_children())
175
+ except Exception:
176
+ pass
177
+
178
+ @configure_delegate('show_border')
179
+ def _delegate_show_border(self, value=None):
180
+ """Get or set whether to draw a border."""
181
+ if value is not None:
182
+ return self.configure_style_options('show_border')
183
+ else:
184
+ self.configure_style_options(show_border=True)
185
+ return self.rebuild_style()