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,546 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter as tk
4
+ from typing import Any, Literal
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, Sticky
11
+
12
+ AutoFlow = Literal["row", "column", "row-dense", "column-dense", "none"]
13
+ Gap = int | tuple[int, int]
14
+
15
+
16
+ def _parse_size(value: int | str) -> tuple[int, int]:
17
+ """Parse size value into (weight, minsize) tuple."""
18
+ if isinstance(value, int):
19
+ return value, 0
20
+ if isinstance(value, str):
21
+ if value == "auto":
22
+ return 0, 0
23
+ if value.endswith("px"):
24
+ try:
25
+ return 0, int(value[:-2])
26
+ except ValueError:
27
+ return 0, 0
28
+ return 0, 0
29
+
30
+
31
+ class GridFrame(Frame):
32
+ """A Frame with simplified grid-based layout management and auto-placement.
33
+
34
+ GridFrame extends the bootstack Frame with automatic grid-based
35
+ layout management, including support for row/column definitions,
36
+ gap spacing, auto-placement, and default sticky behavior.
37
+
38
+ Children gridded into this frame automatically receive the frame's
39
+ default layout options. Simply use the standard `grid()` method
40
+ on child widgets — no special `add()` method is needed.
41
+
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ master: Master = None,
47
+ *,
48
+ rows: int | list[int | str] | None = None,
49
+ columns: int | list[int | str] | None = None,
50
+ gap: Gap = 0,
51
+ sticky_items: Sticky | None = None,
52
+ propagate: bool | None = None,
53
+ auto_flow: AutoFlow = "row",
54
+ **kwargs: Unpack[FrameKwargs],
55
+ ) -> None:
56
+ """Create a GridFrame with automatic grid-based layout.
57
+
58
+ Args:
59
+ master: Parent widget. If None, uses the default root window.
60
+ rows: Number of rows as an `int`, or a list of size specs where each
61
+ spec is an `int` weight or a string such as `'auto'` or `'100px'`.
62
+ columns: Number of columns or a list of size specs (same format as `rows`).
63
+ gap: Spacing between cells. An `int` applies the same gap in both
64
+ directions; a `(col_gap, row_gap)` tuple sets them independently.
65
+ sticky_items: Default `sticky` value applied to all children when
66
+ they call `grid()`. If None, no default sticky is applied.
67
+ propagate: If False, the frame will not resize to fit its contents.
68
+ Defaults to None (Tk default behaviour).
69
+ auto_flow: Auto-placement mode — `'row'` (default), `'column'`,
70
+ `'row-dense'`, `'column-dense'`, or `'none'`.
71
+
72
+ Other Parameters:
73
+ **kwargs: Additional keyword arguments forwarded to `Frame`.
74
+ """
75
+ super().__init__(master, **kwargs)
76
+
77
+ self._gap = self._normalize_gap(gap)
78
+ self._default_sticky = sticky_items
79
+ self._auto_flow = auto_flow
80
+
81
+ # Track managed widgets: list of (widget, user_options, computed_position)
82
+ # computed_position is (row, col, rowspan, colspan)
83
+ self._managed: list[tuple[tk.Widget, dict[str, Any], tuple[int, int, int, int]]] = []
84
+
85
+ # Track occupied cells for auto-placement
86
+ self._occupied: set[tuple[int, int]] = set()
87
+
88
+ # Track widgets hidden via grid_remove (still managed, just hidden)
89
+ self._removed: set[tk.Widget] = set()
90
+
91
+ # Set when a managed child is destroyed out-of-band (Tk destroy()
92
+ # bypasses grid_forget). Gates the O(N) prune scan so a pure build —
93
+ # adds with no destroys — never pays for it, keeping construction O(N).
94
+ self._needs_prune = False
95
+
96
+ # Auto-placement cursor
97
+ self._next_row = 0
98
+ self._next_col = 0
99
+
100
+ # Parse and store row/column definitions
101
+ self._row_defs: list[tuple[int, int]] = []
102
+ self._col_defs: list[tuple[int, int]] = []
103
+
104
+ if isinstance(rows, int):
105
+ self._row_defs = [(1, 0)] * rows
106
+ elif isinstance(rows, list):
107
+ self._row_defs = [_parse_size(r) for r in rows]
108
+
109
+ if isinstance(columns, int):
110
+ self._col_defs = [(1, 0)] * columns
111
+ elif isinstance(columns, list):
112
+ self._col_defs = [_parse_size(c) for c in columns]
113
+
114
+ # Apply initial row/column configuration.
115
+ # Weighted columns use a uniform group keyed by weight so that all
116
+ # equal-weight columns are exactly the same width regardless of their
117
+ # children's minimum sizes. Auto/fixed-px columns are not grouped.
118
+ for i, (weight, minsize) in enumerate(self._row_defs):
119
+ self.rowconfigure(i, weight=weight, minsize=minsize)
120
+ for i, (weight, minsize) in enumerate(self._col_defs):
121
+ kw: dict[str, Any] = {"weight": weight, "minsize": minsize}
122
+ if weight > 0:
123
+ kw["uniform"] = f"col_w{weight}"
124
+ self.columnconfigure(i, **kw)
125
+
126
+ if propagate is not None:
127
+ self.grid_propagate(propagate)
128
+
129
+ @staticmethod
130
+ def _normalize_gap(gap: Gap) -> tuple[int, int]:
131
+ """Normalize gap to (column_gap, row_gap) tuple."""
132
+ if isinstance(gap, int):
133
+ return gap, gap
134
+ return gap
135
+
136
+ @property
137
+ def _column_gap(self) -> int:
138
+ """Get the column gap."""
139
+ return self._gap[0]
140
+
141
+ @property
142
+ def _row_gap(self) -> int:
143
+ """Get the row gap."""
144
+ return self._gap[1]
145
+
146
+ @configure_delegate('gap')
147
+ def _delegate_gap(self, value=None) -> tuple[int, int]:
148
+ """Get or set the gap between cells."""
149
+ if value is None:
150
+ return self._gap
151
+ self._gap = self._normalize_gap(value)
152
+ # Regrid all widgets with new gap
153
+ self._regrid_all()
154
+
155
+ @property
156
+ def _num_columns(self) -> int:
157
+ """Number of defined columns, or a large default for auto-placement."""
158
+ return len(self._col_defs) if self._col_defs else 100
159
+
160
+ @property
161
+ def _num_rows(self) -> int:
162
+ """Number of defined rows, or a large default for auto-placement."""
163
+ return len(self._row_defs) if self._row_defs else 100
164
+
165
+ def _is_area_free(self, row: int, col: int, rowspan: int, colspan: int) -> bool:
166
+ """Check if a rectangular area is free."""
167
+ return all(
168
+ (row + dr, col + dc) not in self._occupied
169
+ for dr in range(rowspan)
170
+ for dc in range(colspan)
171
+ )
172
+
173
+ def _occupy_area(self, row: int, col: int, rowspan: int, colspan: int) -> None:
174
+ """Mark a rectangular area as occupied."""
175
+ for dr in range(rowspan):
176
+ for dc in range(colspan):
177
+ self._occupied.add((row + dr, col + dc))
178
+
179
+ def _free_area(self, row: int, col: int, rowspan: int, colspan: int) -> None:
180
+ """Mark a rectangular area as free."""
181
+ for dr in range(rowspan):
182
+ for dc in range(colspan):
183
+ self._occupied.discard((row + dr, col + dc))
184
+
185
+ def _prune_destroyed(self) -> None:
186
+ """Drop managed widgets that have been destroyed, freeing their cells.
187
+
188
+ Tk's `destroy()` does not route through `grid_forget()`, so without this
189
+ the auto-flow cursor and the occupied-cell set would keep stale entries
190
+ for gone widgets — causing re-added children to fall through to the
191
+ `(0, 0)` fallback and stack on top of one another.
192
+ """
193
+ survivors = []
194
+ pruned = False
195
+ for entry in self._managed:
196
+ widget = entry[0]
197
+ try:
198
+ alive = bool(widget.winfo_exists())
199
+ except Exception:
200
+ alive = False
201
+ if alive:
202
+ survivors.append(entry)
203
+ else:
204
+ self._free_area(*entry[2])
205
+ self._removed.discard(widget)
206
+ pruned = True
207
+ if pruned:
208
+ self._managed = survivors
209
+ # Restart the flow cursor; `_find_next_position` skips cells still
210
+ # occupied by survivors, so it lands on the first genuinely free one.
211
+ self._next_row = 0
212
+ self._next_col = 0
213
+
214
+ def _find_next_position(self, rowspan: int = 1, colspan: int = 1) -> tuple[int, int]:
215
+ """Find the next available position using auto-flow rules."""
216
+ if self._auto_flow == "none":
217
+ return 0, 0
218
+
219
+ max_cols = self._num_columns
220
+ max_rows = self._num_rows
221
+
222
+ if "dense" in self._auto_flow:
223
+ # Dense packing: search from (0,0)
224
+ if self._auto_flow == "row-dense":
225
+ for r in range(max_rows):
226
+ for c in range(max_cols - colspan + 1):
227
+ if self._is_area_free(r, c, rowspan, colspan):
228
+ return r, c
229
+ else: # column-dense
230
+ for c in range(max_cols):
231
+ for r in range(max_rows - rowspan + 1):
232
+ if self._is_area_free(r, c, rowspan, colspan):
233
+ return r, c
234
+ else:
235
+ # Normal flow: continue from cursor
236
+ if self._auto_flow == "row":
237
+ r, c = self._next_row, self._next_col
238
+ while r < max_rows:
239
+ while c <= max_cols - colspan:
240
+ if self._is_area_free(r, c, rowspan, colspan):
241
+ # Update cursor for next placement
242
+ next_c = c + colspan
243
+ if self._col_defs and next_c >= len(self._col_defs):
244
+ self._next_row = r + 1
245
+ self._next_col = 0
246
+ else:
247
+ self._next_row = r
248
+ self._next_col = next_c
249
+ return r, c
250
+ c += 1
251
+ r += 1
252
+ c = 0
253
+ else: # column
254
+ r, c = self._next_row, self._next_col
255
+ while c < max_cols:
256
+ while r <= max_rows - rowspan:
257
+ if self._is_area_free(r, c, rowspan, colspan):
258
+ # Update cursor for next placement
259
+ next_r = r + rowspan
260
+ if self._row_defs and next_r >= len(self._row_defs):
261
+ self._next_col = c + 1
262
+ self._next_row = 0
263
+ else:
264
+ self._next_col = c
265
+ self._next_row = next_r
266
+ return r, c
267
+ r += 1
268
+ c += 1
269
+ r = 0
270
+
271
+ return 0, 0 # Fallback
272
+
273
+ def _compute_gap_padding(
274
+ self, row: int, col: int, user_padx: Any = None, user_pady: Any = None
275
+ ) -> dict[str, Any]:
276
+ """Compute padding that includes gap for non-first rows/columns."""
277
+ result: dict[str, Any] = {}
278
+
279
+ # Handle column gap (padx)
280
+ if col > 0 and self._column_gap:
281
+ gap_padx = (self._column_gap, 0)
282
+ if user_padx is not None:
283
+ result["padx"] = self._merge_padding(gap_padx, user_padx)
284
+ else:
285
+ result["padx"] = gap_padx
286
+ elif user_padx is not None:
287
+ result["padx"] = user_padx
288
+
289
+ # Handle row gap (pady)
290
+ if row > 0 and self._row_gap:
291
+ gap_pady = (self._row_gap, 0)
292
+ if user_pady is not None:
293
+ result["pady"] = self._merge_padding(gap_pady, user_pady)
294
+ else:
295
+ result["pady"] = gap_pady
296
+ elif user_pady is not None:
297
+ result["pady"] = user_pady
298
+
299
+ return result
300
+
301
+ @staticmethod
302
+ def _merge_padding(
303
+ gap_pad: tuple[int, int], user_pad: Any
304
+ ) -> tuple[int, int]:
305
+ """Merge gap padding with user padding."""
306
+ if isinstance(user_pad, int):
307
+ return (gap_pad[0] + user_pad, user_pad)
308
+ elif isinstance(user_pad, (tuple, list)) and len(user_pad) == 2:
309
+ return (gap_pad[0] + user_pad[0], user_pad[1])
310
+ return gap_pad
311
+
312
+ def _build_options(
313
+ self,
314
+ row: int,
315
+ col: int,
316
+ rowspan: int,
317
+ colspan: int,
318
+ user_options: dict[str, Any],
319
+ ) -> dict[str, Any]:
320
+ """Build final grid options."""
321
+ options: dict[str, Any] = {
322
+ "in_": self,
323
+ "row": row,
324
+ "column": col,
325
+ }
326
+
327
+ if rowspan > 1:
328
+ options["rowspan"] = rowspan
329
+ if colspan > 1:
330
+ options["columnspan"] = colspan
331
+
332
+ # Apply gap padding
333
+ gap_padding = self._compute_gap_padding(
334
+ row, col,
335
+ user_options.get("padx"),
336
+ user_options.get("pady")
337
+ )
338
+ options.update(gap_padding)
339
+
340
+ # Apply default sticky
341
+ if self._default_sticky and "sticky" not in user_options:
342
+ options["sticky"] = self._default_sticky
343
+
344
+ # User options override (except padx/pady which we handled)
345
+ for key, value in user_options.items():
346
+ if key not in ("padx", "pady", "row", "column"):
347
+ options[key] = value
348
+
349
+ return options
350
+
351
+ def _regrid_all(self) -> None:
352
+ """Remove and re-grid all widgets."""
353
+ # Ungrid all (except those in _removed which are already hidden)
354
+ for widget, _, _ in self._managed:
355
+ if widget not in self._removed:
356
+ tk.Grid.forget(widget)
357
+
358
+ # Clear and rebuild occupied set
359
+ self._occupied.clear()
360
+ self._next_row = 0
361
+ self._next_col = 0
362
+
363
+ # Regrid in order, recalculating positions
364
+ new_managed: list[tuple[tk.Widget, dict[str, Any], tuple[int, int, int, int]]] = []
365
+
366
+ for widget, user_options, _ in self._managed:
367
+ rowspan = int(user_options.get("rowspan", 1))
368
+ colspan = int(user_options.get("columnspan", 1))
369
+
370
+ # Check if user specified explicit position
371
+ explicit_row = user_options.get("row")
372
+ explicit_col = user_options.get("column")
373
+
374
+ if explicit_row is not None and explicit_col is not None:
375
+ row, col = int(explicit_row), int(explicit_col)
376
+ else:
377
+ row, col = self._find_next_position(rowspan, colspan)
378
+
379
+ self._occupy_area(row, col, rowspan, colspan)
380
+ new_managed.append((widget, user_options, (row, col, rowspan, colspan)))
381
+
382
+ # Only configure visible widgets (not those in _removed)
383
+ if widget not in self._removed:
384
+ options = self._build_options(row, col, rowspan, colspan, user_options)
385
+ tk.Grid.configure(widget, **options)
386
+
387
+ self._managed = new_managed
388
+
389
+ def _find_widget_index(self, widget: tk.Widget) -> int:
390
+ """Find index of widget in managed list, return -1 if not found."""
391
+ for i, (w, _, _) in enumerate(self._managed):
392
+ if w is widget:
393
+ return i
394
+ return -1
395
+
396
+ # -------------------------------------------------------------------------
397
+ # Hook methods called by GridMixin
398
+ # -------------------------------------------------------------------------
399
+
400
+ def _on_child_grid(self, widget: tk.Widget, **options: Any) -> None:
401
+ """Hook called when a child widget calls grid().
402
+
403
+ Applies frame defaults, handles gap spacing, auto-placement, and tracks the widget.
404
+ """
405
+ # Reclaim cells from any children destroyed since the last placement so
406
+ # auto-flow positions this widget correctly (Tk destroy() bypasses
407
+ # grid_forget()). Only scan when a destroy actually flagged it.
408
+ if self._needs_prune:
409
+ self._prune_destroyed()
410
+ self._needs_prune = False
411
+
412
+ # Check if widget was hidden via grid_remove and is being restored
413
+ if widget in self._removed and not options:
414
+ # Restore widget to its original position
415
+ self._removed.discard(widget)
416
+ idx = self._find_widget_index(widget)
417
+ if idx >= 0:
418
+ _, user_options, (row, col, rowspan, colspan) = self._managed[idx]
419
+ grid_options = self._build_options(row, col, rowspan, colspan, user_options)
420
+ tk.Grid.configure(widget, **grid_options)
421
+ return
422
+
423
+ # Check if widget is already managed (reconfigure case)
424
+ existing_idx = self._find_widget_index(widget)
425
+
426
+ rowspan = int(options.get("rowspan", 1))
427
+ colspan = int(options.get("columnspan", 1))
428
+
429
+ # Check if user specified explicit position
430
+ explicit_row = options.get("row")
431
+ explicit_col = options.get("column")
432
+
433
+ if existing_idx >= 0:
434
+ # Widget already managed - update its options and regrid all
435
+ _, _, old_pos = self._managed[existing_idx]
436
+ self._free_area(*old_pos)
437
+ self._managed[existing_idx] = (widget, options, (0, 0, 1, 1)) # temp position
438
+ self._regrid_all()
439
+ else:
440
+ # New widget
441
+ if explicit_row is not None and explicit_col is not None:
442
+ row, col = int(explicit_row), int(explicit_col)
443
+ else:
444
+ row, col = self._find_next_position(rowspan, colspan)
445
+
446
+ self._occupy_area(row, col, rowspan, colspan)
447
+ # Rows that weren't pre-defined via `rows=` keep Tk's default weight
448
+ # of 0 — matching undefined columns — so an auto-flow grid stays
449
+ # content-sized instead of dividing its parent's height among its
450
+ # rows. Pass `rows=[1, ...]` to make a row stretch.
451
+ grid_options = self._build_options(row, col, rowspan, colspan, options)
452
+ tk.Grid.configure(widget, **grid_options)
453
+ self._managed.append((widget, options, (row, col, rowspan, colspan)))
454
+ # Flag a prune when this child is destroyed out-of-band, so the
455
+ # next placement reclaims its cell without scanning on every add.
456
+ widget.bind(
457
+ "<Destroy>",
458
+ lambda e, w=widget: self._flag_prune(e, w),
459
+ add="+",
460
+ )
461
+
462
+ def _flag_prune(self, event: tk.Event, widget: tk.Widget) -> None:
463
+ """Mark the frame for a prune when one of its managed children dies."""
464
+ if event.widget is widget:
465
+ self._needs_prune = True
466
+
467
+ def _on_child_grid_forget(self, widget: tk.Widget) -> None:
468
+ """Hook called when a child widget calls grid_forget().
469
+
470
+ Removes widget from tracking and frees its occupied area.
471
+ """
472
+ idx = self._find_widget_index(widget)
473
+ if idx < 0:
474
+ # Not managed by us, just forget it normally
475
+ tk.Grid.forget(widget)
476
+ return
477
+
478
+ _, _, (row, col, rowspan, colspan) = self._managed[idx]
479
+ tk.Grid.forget(widget)
480
+ self._free_area(row, col, rowspan, colspan)
481
+ self._managed.pop(idx)
482
+ self._removed.discard(widget) # Clean up if it was in removed set
483
+
484
+ def _on_child_grid_remove(self, widget: tk.Widget) -> None:
485
+ """Hook called when a child widget calls grid_remove().
486
+
487
+ Hides widget but keeps its configuration for later restore via grid().
488
+ The widget's position is preserved so it can be restored with grid().
489
+ """
490
+ idx = self._find_widget_index(widget)
491
+ if idx < 0:
492
+ # Not managed by us, just remove it normally
493
+ tk.Grid.grid_remove(widget)
494
+ return
495
+
496
+ # Hide the widget but keep it in _managed for position restoration
497
+ tk.Grid.grid_remove(widget)
498
+ self._removed.add(widget)
499
+
500
+ # -------------------------------------------------------------------------
501
+ # Public configuration methods
502
+ # -------------------------------------------------------------------------
503
+
504
+ def configure_row(
505
+ self,
506
+ index: int,
507
+ weight: int = 1,
508
+ minsize: int | None = None,
509
+ pad: int | None = None,
510
+ ) -> None:
511
+ """Configure a row's weight and minimum size.
512
+
513
+ Args:
514
+ index: Row index to configure.
515
+ weight: Stretch weight. Defaults to 1.
516
+ minsize: Minimum height in pixels.
517
+ pad: Extra padding around the row.
518
+ """
519
+ kwargs: dict[str, Any] = {"weight": weight}
520
+ if minsize is not None:
521
+ kwargs["minsize"] = minsize
522
+ if pad is not None:
523
+ kwargs["pad"] = pad
524
+ self.rowconfigure(index, **kwargs)
525
+
526
+ def configure_column(
527
+ self,
528
+ index: int,
529
+ weight: int = 1,
530
+ minsize: int | None = None,
531
+ pad: int | None = None,
532
+ ) -> None:
533
+ """Configure a column's weight and minimum size.
534
+
535
+ Args:
536
+ index: Column index to configure.
537
+ weight: Stretch weight. Defaults to 1.
538
+ minsize: Minimum width in pixels.
539
+ pad: Extra padding around the column.
540
+ """
541
+ kwargs: dict[str, Any] = {"weight": weight}
542
+ if minsize is not None:
543
+ kwargs["minsize"] = minsize
544
+ if pad is not None:
545
+ kwargs["pad"] = pad
546
+ self.columnconfigure(index, **kwargs)
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from tkinter import ttk
4
+ from typing import Any, Literal, TYPE_CHECKING
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._impl.mixins import IconMixin, LocalizationMixin, TextSignalMixin
12
+ from bootstack.widgets.types import Master, StyledKwargs, Anchor, Justify, Relief, CompoundMode, WidgetState
13
+
14
+ if TYPE_CHECKING:
15
+ from bootstack.signals import Signal
16
+
17
+
18
+ class LabelKwargs(StyledKwargs, total=False):
19
+ # Standard ttk.Label options
20
+ text: Any
21
+ image: Any
22
+ icon: Any
23
+ icon_only: bool
24
+ compound: CompoundMode
25
+ anchor: Anchor
26
+ justify: Justify
27
+ padding: Any
28
+ width: int
29
+ wraplength: Any
30
+ font: Any
31
+ foreground: str
32
+ background: str
33
+ relief: Relief
34
+ localize: bool | Literal['auto']
35
+ value_format: dict | str
36
+ state: WidgetState
37
+ format_spec: str | dict
38
+ textvariable: Any
39
+ textsignal: Signal[str]
40
+
41
+ # bootstack-specific extensions
42
+
43
+
44
+ class Label(LocalizationMixin, TextSignalMixin, IconMixin, TTKWrapperBase, WidgetCapabilitiesMixin, TtkStateMixin, ttk.Label):
45
+ """bootstack wrapper for `ttk.Label` with themed styling and icon support."""
46
+
47
+ _ttk_base = ttk.Label
48
+
49
+ def __init__(self, master: Master = None, **kwargs: Unpack[LabelKwargs]) -> None:
50
+ """Create a themed bootstack Label.
51
+
52
+ Args:
53
+ master: Parent widget. If None, uses the default root window.
54
+
55
+ Other Parameters:
56
+ text: Text to display in the label.
57
+ textvariable: Tk variable linked to the label text.
58
+ See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
59
+ textsignal: Reactive Signal linked to the label text (auto-synced with textvariable).
60
+ image: Image to display.
61
+ icon: Theme-aware icon spec handled by the style system.
62
+ icon_only: If True, removes the additional padding reserved for label text.
63
+ compound: Placement of the image relative to text.
64
+ anchor: Alignment of the label's content within its area.
65
+ justify: How to justify multiple lines of text.
66
+ localize: Determines the widget's localization mode.
67
+ value_format: Format specification for the label value.
68
+ padding: Extra space around the label content.
69
+ width: Width of the label in characters.
70
+ wraplength: Maximum width before wrapping text.
71
+ font: Font for text.
72
+ foreground: Text color.
73
+ background: Background color.
74
+ relief: Border style.
75
+ state: Widget state.
76
+ takefocus: Whether the widget participates in focus traversal.
77
+ style: Explicit ttk style name (overrides accent/variant).
78
+ accent: Accent token for styling, e.g. 'primary', 'danger', 'success'.
79
+ variant: Style variant, e.g. 'default', 'inverse'.
80
+ surface: Optional surface token; otherwise inherited.
81
+ style_options: Optional dict forwarded to the style builder.
82
+ """
83
+ kwargs.update(style_options=self._capture_style_options(['icon_only', 'icon', 'density'], kwargs))
84
+ super().__init__(master, **kwargs)
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from tkinter import ttk
4
+ from typing import Any, Literal
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._impl.mixins import LocalizationMixin
12
+ from bootstack.widgets.types import Master, StyledKwargs, Relief
13
+
14
+
15
+ class LabelFrameKwargs(StyledKwargs, total=False):
16
+ # Standard ttk.Labelframe options
17
+ text: Any
18
+ labelanchor: Any
19
+ padding: Any
20
+ relief: Relief
21
+ borderwidth: Any
22
+ width: int
23
+ height: int
24
+
25
+ # bootstack-specific extensions
26
+ localize: bool | Literal['auto']
27
+
28
+
29
+ class LabelFrame(LocalizationMixin, TTKWrapperBase, WidgetCapabilitiesMixin, TtkStateMixin, ttk.LabelFrame):
30
+ """bootstack wrapper for `ttk.Labelframe` with themed styling support."""
31
+
32
+ _ttk_base = ttk.Labelframe
33
+
34
+ def __init__(self, master: Master = None, **kwargs: Unpack[LabelFrameKwargs]) -> None:
35
+ """Create a themed bootstack Labelframe.
36
+
37
+ Args:
38
+ master: Parent widget. If None, uses the default root window.
39
+
40
+ Other Parameters:
41
+ text: Text for the embedded label.
42
+ labelanchor: Position of the label relative to the frame.
43
+ padding: Extra internal padding.
44
+ relief: Border style.
45
+ borderwidth: Border width.
46
+ width: Requested width in pixels.
47
+ height: Requested height in pixels.
48
+ style: Explicit ttk style name (overrides accent/variant).
49
+ accent: Accent token for styling, e.g. 'primary', 'secondary'.
50
+ surface: Optional surface token; otherwise inherited.
51
+ style_options: Optional dict forwarded to the style builder.
52
+ localize: Determines the widget's localization mode.
53
+ """
54
+ super().__init__(master, **kwargs)