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,390 @@
1
+
2
+ from bootstack._runtime.toplevel import Toplevel
3
+ from typing import Any, Callable, Sequence
4
+
5
+ from typing_extensions import TypedDict, Unpack
6
+
7
+ from bootstack._runtime.window_utilities import WindowPositioning
8
+
9
+
10
+ class IconSpec(TypedDict, total=False):
11
+ """Icon configuration for toast."""
12
+ name: str
13
+ size: int | None
14
+ color: str | None
15
+
16
+
17
+ from bootstack.widgets._impl.primitives.frame import Frame as _Frame
18
+ from bootstack.widgets._impl.primitives.label import Label as _Label
19
+ from bootstack.widgets._impl.primitives.button import Button as _Button
20
+
21
+ class ToastConfig(TypedDict, total=False):
22
+ """Configuration options for Toast widget."""
23
+ title: str | None
24
+ icon: str | IconSpec | None
25
+ message: str | None
26
+ memo: str | None
27
+ duration: int | None
28
+ buttons: Sequence[dict[str, Any]] | None
29
+ show_close_button: bool
30
+ accent: str | None
31
+ position: str | None
32
+ alert: bool
33
+ on_dismissed: Callable[[Any], Any] | None
34
+
35
+
36
+ class Toast:
37
+ """A notification toast widget that displays temporary messages.
38
+
39
+ Toast notifications appear in a small window, typically in the corner of the screen,
40
+ and can display a title, message, icon, buttons, and optional metadata. They can be
41
+ configured to auto-dismiss after a duration or remain visible until manually closed.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ *,
47
+ title: str | None = None,
48
+ icon: str | IconSpec | None = None,
49
+ message: str | None = None,
50
+ memo: str | None = None,
51
+ duration: int | None = None,
52
+ buttons: Sequence[dict[str, Any]] | None = None,
53
+ show_close_button: bool = True,
54
+ accent: str | None = None,
55
+ position: str | None = None,
56
+ alert: bool = False,
57
+ on_dismissed: Callable[[Any], Any] | None = None,
58
+ place: Callable[[Any], None] | None = None,
59
+ ) -> None:
60
+ """Initialize a Toast notification.
61
+
62
+ Args:
63
+ title: Title text. Displayed with a larger font when no message is
64
+ provided; otherwise appears as the header with message below.
65
+ icon: Icon name string or `IconSpec` dict with `name`, `size`, and
66
+ `color` keys.
67
+ message: Main message text. Displayed in header when no title; shown
68
+ below the title separator when both are provided.
69
+ memo: Small metadata text in the header (e.g., "5 mins ago"). Muted styling.
70
+ duration: Auto-dismiss delay in milliseconds. If None, stays until closed.
71
+ buttons: Sequence of button option dicts. Each dict accepts any bootstack
72
+ Button kwargs. Button press triggers `on_dismissed` before closing.
73
+ show_close_button: Whether to show the close (×) button. Default True.
74
+ accent: Accent token for the container (e.g., 'primary', 'danger'). If
75
+ None, uses the default background.
76
+ position: Tkinter geometry string (e.g., '-25-75'). If None, uses
77
+ platform defaults (bottom-right on Windows/macOS, top-right on X11).
78
+ alert: If True, plays a system bell when shown.
79
+ on_dismissed: Callback invoked on dismiss. Receives the button options
80
+ dict when dismissed via a button, or None otherwise.
81
+ """
82
+ self._config_keys = {'title', 'icon', 'message', 'memo', 'duration', 'buttons', 'show_close_button',
83
+ 'accent', 'position', 'alert', 'on_dismissed'}
84
+
85
+ # initialized configuration
86
+ self._title = title
87
+ self._icon = icon
88
+ self._message = message
89
+ self._memo = memo
90
+ self._duration = duration
91
+ self._buttons = buttons
92
+ self._show_close_button = show_close_button
93
+ self._accent = accent
94
+ self._position = position
95
+ self._alert = alert
96
+ self._on_dismissed = on_dismissed
97
+ # Optional positioning hook: a callable given the built (off-screen,
98
+ # measured) Toplevel that sets its final geometry. Lets a facade own
99
+ # corner/window placement and stacking; bypasses the default logic.
100
+ self._place = place
101
+
102
+ # top level widget
103
+ self._toplevel: Toplevel | None = None
104
+
105
+ def __setitem__(self, key: str, value: Any) -> None:
106
+ """Set a configuration option using dictionary syntax."""
107
+ self.configure(**{key: value})
108
+
109
+ def __getitem__(self, key: str) -> Any:
110
+ """Get a configuration option using dictionary syntax."""
111
+ return self.cget(key)
112
+
113
+ def _handle_on_dismissed(self, data: Any = None) -> None:
114
+ """Invoke the on_dismissed callback if configured."""
115
+ if self._on_dismissed:
116
+ self._on_dismissed(data)
117
+
118
+ def configure(
119
+ self,
120
+ option: str | None = None,
121
+ **kwargs: Unpack[ToastConfig]
122
+ ) -> tuple[str, str, str, None, Any] | None:
123
+ """Configure toast options.
124
+
125
+ Args:
126
+ option: If provided, returns the configuration for this option.
127
+ **kwargs: Configuration options to set.
128
+
129
+ Returns:
130
+ Configuration tuple if option provided, otherwise None.
131
+ """
132
+ if option is not None:
133
+ if option in self._config_keys:
134
+ attr = f"_{option}"
135
+ value = getattr(self, attr)
136
+ # returns in the expected tkinter.configure format
137
+ return option, option, option.capitalize(), None, value
138
+ else:
139
+ raise AttributeError(f"'{option}' is not a valid option")
140
+
141
+ if kwargs:
142
+ for key, value in kwargs.items():
143
+ if key in self._config_keys:
144
+ attr = f"_{key}"
145
+ setattr(self, attr, value)
146
+ else:
147
+ raise AttributeError(f"'{key}' is not a valid option")
148
+ return None
149
+
150
+ def cget(self, option: str) -> Any:
151
+ """Get the value of a configuration option.
152
+
153
+ Args:
154
+ option: The configuration option name.
155
+
156
+ Returns:
157
+ The value of the configuration option.
158
+ """
159
+ if option in self._config_keys:
160
+ attr = f"_{option}"
161
+ return getattr(self, attr)
162
+ else:
163
+ raise AttributeError(f"'{option}' is not a valid option")
164
+
165
+ def show(self, merge: bool = True, **options: Unpack[ToastConfig]) -> None:
166
+ """Display the toast.
167
+
168
+ If options are provided, they are merged with the existing toast configuration. If you do not want this
169
+ behavior, set the merge flag to False, or create a new toast instance.
170
+
171
+ Args:
172
+ merge: If True, merge options with existing configuration. If False, clear existing options first.
173
+ **options: Configuration options to set before showing.
174
+ """
175
+ if not merge:
176
+ self._clear_options()
177
+
178
+ self.configure(**options)
179
+ self._build_toast()
180
+ if self._toplevel:
181
+ self._toplevel.deiconify()
182
+
183
+ def _clear_options(self) -> None:
184
+ """Clear all configuration options to their default values."""
185
+ self._title = None
186
+ self._icon = None
187
+ self._message = None
188
+ self._memo = None
189
+ self._duration = None
190
+ self._buttons = None
191
+ self._show_close_button = True
192
+ self._accent = None
193
+ self._position = None
194
+ self._alert = False
195
+ self._on_dismissed = None
196
+
197
+ def hide(self) -> None:
198
+ """Hide the toast and trigger the on_dismissed callback."""
199
+ if self._toplevel:
200
+ self._toplevel.destroy()
201
+ self._handle_on_dismissed(None)
202
+
203
+ def destroy(self) -> None:
204
+ """Destroy the toast widget and cleanup resources."""
205
+ if hasattr(self, '_toplevel') and self._toplevel:
206
+ self._toplevel.destroy()
207
+ self._toplevel = None
208
+
209
+ def _build_toast(self) -> None:
210
+ # ----- Configuration Options -------
211
+
212
+ has_title = self._title is not None
213
+ has_title_and_message = has_title and self._message is not None
214
+ resolved_title_font = "label" if has_title else "body"
215
+ subtle_accent = f"{self._accent}[subtle]" if self._accent else None
216
+ # Text uses the accent's *emphasis* shade — a dark, readable, accent-
217
+ # tinted color (matching Bootstrap's text-emphasis), not the bright base
218
+ # accent, which is low-contrast and harsh as text on the subtle wash.
219
+ # No accent -> None, so the label falls back to the on-surface foreground.
220
+ text_accent = f"{self._accent}_emphasis" if self._accent else None
221
+ # Icon is opt-in (no forced default): a toast is its message, optionally
222
+ # preceded by an icon. Frameworks don't agree on a default, so render one
223
+ # only when asked.
224
+ icon_spec = None
225
+ if self._icon:
226
+ icon_spec = self._icon if isinstance(self._icon, dict) else {"name": self._icon, "size": 20}
227
+
228
+ # ------ Toplevel setup ------
229
+
230
+ # bootstack.Toplevel applies MacWindowStyle 'help none' on Aqua
231
+ # via windowtype='tooltip', so toasts render chromeless on Mac
232
+ # (overrideredirect alone is silently skipped on Aqua per the
233
+ # project's BaseWindow guard). Win/Linux keep the overrideredirect
234
+ # path. Topmost + alpha are passed as kwargs so the Toplevel sets
235
+ # them at the right moment in its setup pipeline.
236
+ top = Toplevel(
237
+ overrideredirect=True,
238
+ topmost=True,
239
+ alpha=0.97,
240
+ windowtype='tooltip',
241
+ )
242
+ top.minsize(400, 30)
243
+
244
+ # ------ Toast Layout ------
245
+
246
+ # The container carries the subtle accent wash; the border stays neutral
247
+ # (no accent) so the card has a quiet edge rather than a bright outline.
248
+ container = _Frame(top, padding=4, show_border=True,
249
+ surface=subtle_accent or 'overlay')
250
+ container.pack(fill='both', expand=True)
251
+
252
+ # Tight gap below the title so it reads as one unit with the message;
253
+ # the breathing room lives on the outside (container padding + message
254
+ # bottom). A lone title (no message) keeps a balanced bottom instead.
255
+ header_bottom = 8 if not has_title_and_message else 2
256
+ header = _Frame(container, padding=(8, 8, 8, header_bottom))
257
+ header.pack(side='top', fill='x')
258
+
259
+ # icon — the emphasis shade, like the text, so it stays legible on the
260
+ # subtle wash (the bright base accent — e.g. warning's yellow — washes
261
+ # out against its own pale tint).
262
+ if icon_spec:
263
+ _Label(header, icon=icon_spec, accent=text_accent).pack(side='left', padx=(0, 8))
264
+
265
+ # title — emphasis (dark, readable accent-tinted) text
266
+ _Label(
267
+ header,
268
+ text=self._title if has_title else self._message,
269
+ font=resolved_title_font,
270
+ wraplength=380,
271
+ justify='left',
272
+ accent=text_accent,
273
+ ).pack(side='left', fill='x')
274
+
275
+ # close — a clickable x icon, not a Button: a compact button still floors
276
+ # ~12px above the title row and would inflate the header. A Label icon
277
+ # matches the title/icon height, so the row stays content-sized.
278
+ if self._show_close_button:
279
+ close = _Label(header, icon={"name": "x-lg", "size": 14}, accent="secondary")
280
+ close.configure(cursor="hand2")
281
+ close.bind("<Button-1>", lambda _e: self.hide())
282
+ close.pack(side='right', padx=(4, 0))
283
+
284
+ # memo — muted metadata (no extra vertical padding; the header owns it)
285
+ if self._memo:
286
+ _Label(
287
+ header,
288
+ text=self._memo,
289
+ font="caption",
290
+ accent="secondary",
291
+ ).pack(side='right', padx=(0, 4 if self._show_close_button else 12))
292
+
293
+ # message — emphasis text
294
+ if has_title_and_message:
295
+ _Label(
296
+ container,
297
+ text=self._message,
298
+ wraplength=400,
299
+ justify='left',
300
+ accent=text_accent,
301
+ ).pack(side='top', fill='x', pady=(0, 8), padx=8)
302
+
303
+ # buttons
304
+ if self._buttons:
305
+ def execute_command(options: dict[str, Any], fn: Callable[[], None] | None = None) -> Callable[[], None]:
306
+ def inner() -> None:
307
+ if fn:
308
+ fn()
309
+ self._handle_on_dismissed(options)
310
+ top.destroy()
311
+
312
+ return inner
313
+
314
+ button_frame = _Frame(container)
315
+ button_frame.pack(side='top', anchor='e', padx=4, pady=(0, 4))
316
+
317
+ for i, button_options in enumerate(self._buttons):
318
+ func = button_options.get('command', None)
319
+ button_opts = {k: v for k, v in button_options.items() if k != 'command'}
320
+ _Button(
321
+ button_frame,
322
+ **button_opts,
323
+ command=execute_command(button_options, func)
324
+ ).grid(column=i, row=0, padx=(4 if i > 0 else 0, 0))
325
+
326
+ # ------ Positioning -------
327
+
328
+ # Deiconify off-screen first so the geometry manager runs a full
329
+ # layout pass on a mapped window. winfo_height() returns 1 on a
330
+ # withdrawn window, causing the toast to be placed with ~0 height
331
+ # and therefore positioned flush against the screen edge.
332
+ top.deiconify()
333
+ top.geometry(
334
+ f"+{top.winfo_screenwidth() * 2}+{top.winfo_screenheight() * 2}"
335
+ )
336
+ top.update_idletasks()
337
+
338
+ # A facade-supplied placement hook owns corner/window positioning and
339
+ # stacking; it runs while the window is still off-screen, so there is no
340
+ # reposition flash.
341
+ if self._place is not None:
342
+ self._place(top)
343
+ # Apply positioning using WindowPositioning utilities
344
+ elif self._position:
345
+ # Support legacy geometry strings (e.g., "-25-75")
346
+ if self._position.startswith(('+', '-')):
347
+ # Legacy geometry string - use directly
348
+ top.geometry(self._position)
349
+ else:
350
+ # New positioning - assume it's a corner specification
351
+ # (future enhancement: could support more complex positioning)
352
+ top.geometry(self._position)
353
+ else:
354
+ # Default positioning based on platform
355
+ winsys = top.tk.call('tk', 'windowingsystem')
356
+ if winsys in ['win32', 'aqua']:
357
+ # Bottom-right corner
358
+ WindowPositioning.position_anchored(
359
+ window=top,
360
+ anchor_to="screen",
361
+ parent=None,
362
+ anchor_point="se",
363
+ window_point="se",
364
+ offset=(-25, -75),
365
+ auto_flip=False,
366
+ ensure_visible=True
367
+ )
368
+ else:
369
+ # Top-right corner for X11
370
+ WindowPositioning.position_anchored(
371
+ window=top,
372
+ anchor_to="screen",
373
+ parent=None,
374
+ anchor_point="ne",
375
+ window_point="ne",
376
+ offset=(-25, 25),
377
+ auto_flip=False,
378
+ ensure_visible=True
379
+ )
380
+
381
+ # ------ Other setup -------
382
+
383
+ if self._duration:
384
+ top.after(self._duration, self.hide)
385
+
386
+ if self._alert:
387
+ top.bell()
388
+
389
+ self._toplevel = top
390
+
@@ -0,0 +1,156 @@
1
+ """Positioning and stacking for the toast family.
2
+
3
+ The public `toast()` / `Notification` / `Snackbar` facades share one process-wide
4
+ manager that owns *where* a transient window lands and how several of them stack:
5
+
6
+ - **Corner stacks** (toast, notification) float in a corner of the monitor the
7
+ app window currently sits on, newest nearest the corner, older ones offset
8
+ away. The stack re-flows when any member dismisses.
9
+ - **Snackbar** anchors to the app window's own bottom edge, aligned left,
10
+ center, or right. Only one snackbar shows at a time; a new one replaces it.
11
+
12
+ Geometry is computed by hand (no reliance on the engine's default placement) and
13
+ applied through the engine's `place=` hook, so positioning happens while the
14
+ window is still off-screen — no reposition flash.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import tkinter
19
+ from typing import Literal
20
+
21
+ from bootstack._runtime.app import get_default_root
22
+ from bootstack._runtime.window_utilities import WindowPositioning
23
+
24
+ Corner = Literal["top-left", "top-right", "bottom-left", "bottom-right"]
25
+ Align = Literal["left", "center", "right"]
26
+
27
+ CORNERS: tuple[Corner, ...] = ("top-left", "top-right", "bottom-left", "bottom-right")
28
+
29
+ _MARGIN = 24 # px gap from the monitor / window edge
30
+ _GAP = 12 # px gap between stacked windows
31
+
32
+
33
+ def _alive(top: tkinter.Toplevel) -> bool:
34
+ """Whether a toast Toplevel still exists."""
35
+ try:
36
+ return bool(top.winfo_exists())
37
+ except tkinter.TclError:
38
+ return False
39
+
40
+
41
+ def _monitor_rect() -> tuple[int, int, int, int]:
42
+ """`(x, y, w, h)` usable work area of the monitor the app window is on.
43
+
44
+ On macOS this is the visible frame (menu bar + Dock excluded) so a corner
45
+ toast never lands under the Dock; elsewhere it is the full monitor bounds.
46
+ Falls back to the full screen, then a fixed default.
47
+ """
48
+ root = get_default_root()
49
+ if root is not None:
50
+ try:
51
+ root.update_idletasks()
52
+ cx = root.winfo_rootx() + root.winfo_width() // 2
53
+ cy = root.winfo_rooty() + root.winfo_height() // 2
54
+ rect = WindowPositioning._get_monitor_at_point(cx, cy)
55
+ if rect is None:
56
+ rect = (0, 0, root.winfo_screenwidth(), root.winfo_screenheight())
57
+ return WindowPositioning.get_work_area(root, rect)
58
+ except tkinter.TclError:
59
+ pass
60
+ return (0, 0, 1920, 1080)
61
+
62
+
63
+ class _ToastManager:
64
+ """Process-wide owner of corner stacks and the single active snackbar."""
65
+
66
+ def __init__(self) -> None:
67
+ self._corners: dict[Corner, list[tkinter.Toplevel]] = {c: [] for c in CORNERS}
68
+ self._snackbar: tkinter.Toplevel | None = None
69
+
70
+ # ----- corner stacks (toast / notification) --------------------------
71
+
72
+ def corner_placer(self, corner: Corner):
73
+ """Return a `place(top)` hook that slots `top` into `corner`'s stack."""
74
+ def _place(top: tkinter.Toplevel) -> None:
75
+ self._corners[corner].insert(0, top) # newest nearest the corner
76
+ self._reflow(corner)
77
+ return _place
78
+
79
+ def remove(self, corner: Corner, top: tkinter.Toplevel) -> None:
80
+ """Drop `top` from its stack (on dismiss) and re-flow the rest."""
81
+ stack = self._corners.get(corner)
82
+ if stack is None:
83
+ return
84
+ self._corners[corner] = [t for t in stack if t is not top]
85
+ self._reflow(corner)
86
+
87
+ def _reflow(self, corner: Corner) -> None:
88
+ """Re-position every live window in a corner stack, in order."""
89
+ mx, my, mw, mh = _monitor_rect()
90
+ at_top = "top" in corner
91
+ at_right = "right" in corner
92
+ offset = _MARGIN
93
+ live: list[tkinter.Toplevel] = []
94
+ for top in self._corners[corner]:
95
+ if not _alive(top):
96
+ continue
97
+ live.append(top)
98
+ try:
99
+ top.update_idletasks()
100
+ w, h = top.winfo_width(), top.winfo_height()
101
+ x = (mx + mw - w - _MARGIN) if at_right else (mx + _MARGIN)
102
+ y = (my + offset) if at_top else (my + mh - h - offset)
103
+ top.geometry(f"+{int(x)}+{int(y)}")
104
+ offset += h + _GAP
105
+ except tkinter.TclError:
106
+ pass
107
+ self._corners[corner] = live
108
+
109
+ # ----- snackbar (single, app-window bottom edge) ---------------------
110
+
111
+ def snackbar_placer(self, align: Align):
112
+ """Return a `place(top)` hook that anchors `top` to the window bottom."""
113
+ def _place(top: tkinter.Toplevel) -> None:
114
+ if self._snackbar is not None and _alive(self._snackbar) and self._snackbar is not top:
115
+ try:
116
+ self._snackbar.destroy()
117
+ except tkinter.TclError:
118
+ pass
119
+ self._snackbar = top
120
+ self._position_snackbar(top, align)
121
+ return _place
122
+
123
+ def clear_snackbar(self, top: tkinter.Toplevel) -> None:
124
+ """Forget the active snackbar if it is `top` (on dismiss)."""
125
+ if self._snackbar is top:
126
+ self._snackbar = None
127
+
128
+ def _position_snackbar(self, top: tkinter.Toplevel, align: Align) -> None:
129
+ root = get_default_root()
130
+ if root is None:
131
+ return
132
+ try:
133
+ root.update_idletasks()
134
+ top.update_idletasks()
135
+ rx, ry = root.winfo_rootx(), root.winfo_rooty()
136
+ rw, rh = root.winfo_width(), root.winfo_height()
137
+ w, h = top.winfo_width(), top.winfo_height()
138
+ y = ry + rh - h - _MARGIN
139
+ if align == "left":
140
+ x = rx + _MARGIN
141
+ elif align == "right":
142
+ x = rx + rw - w - _MARGIN
143
+ else: # center
144
+ x = rx + (rw - w) // 2
145
+ top.geometry(f"+{int(x)}+{int(y)}")
146
+ except tkinter.TclError:
147
+ pass
148
+
149
+
150
+ # One manager for the whole process — toasts are app-global.
151
+ _manager = _ToastManager()
152
+
153
+
154
+ def get_toast_manager() -> _ToastManager:
155
+ """Return the process-wide toast manager."""
156
+ return _manager