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,340 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import tkinter
5
+ from typing import overload, Any, Callable, TYPE_CHECKING
6
+
7
+ from bootstack.widgets._impl.composites.timeentry import TimeEntry as _InternalTimeEntry
8
+ from bootstack.widgets._core.base import PublicWidgetBase, adapt_handler
9
+ from bootstack.widgets._core.events import resolve_event, register_widget_events
10
+ from bootstack.widgets._core.field_mixin import FieldAddonMixin, ValueSignalMixin
11
+ from bootstack.events import ChangeEvent, Subscription, ValidationEvent
12
+ from bootstack.streams import Stream
13
+ from bootstack.widgets.textfield import _INNER_ENTRY_SEQUENCES
14
+ from bootstack.widgets.types import AccentToken, Event, WidgetDensity
15
+
16
+ if TYPE_CHECKING:
17
+ from bootstack.signals import Signal
18
+
19
+ _TIME_FIELD_EVENTS: dict[str, str] = {
20
+ "change": "<<Change>>",
21
+ "validate": "<<Validate>>",
22
+ "valid": "<<Valid>>",
23
+ "invalid": "<<Invalid>>",
24
+ "submit": "<Return>",
25
+ "focus": "<FocusIn>",
26
+ "blur": "<FocusOut>",
27
+ }
28
+
29
+
30
+ class TimeField(ValueSignalMixin, FieldAddonMixin, PublicWidgetBase):
31
+ """A time-input field with a searchable dropdown of time intervals.
32
+
33
+ Displays a formatted time value and shows a dropdown list of times at
34
+ the specified `interval`. The user can type a custom time or pick from
35
+ the list.
36
+
37
+ Args:
38
+ value: Initial time value — a `datetime.time` object or a time
39
+ string (e.g. `'14:30'` or `'2:30 PM'`). Empty by default; pass a
40
+ value to seed a starting time.
41
+ value_format: Format applied to the displayed time — a named preset
42
+ (e.g. `'shortTime'`, `'longTime'`) or a custom pattern (e.g.
43
+ `'HH:mm'`, `'h:mm a'`). Default `'shortTime'`. See
44
+ :ref:`format specs <value-formats>`.
45
+ interval: Minute interval for dropdown entries. Default `30`.
46
+ min_time: Earliest time shown in the dropdown.
47
+ max_time: Latest time shown in the dropdown.
48
+ label: Label displayed above the field.
49
+ message: Hint text displayed below the field.
50
+ signal: Reactive `Signal` two-way bound to the field's `time` value (not
51
+ its text). When given, it seeds the initial value. This is the usual
52
+ way to bind a time field.
53
+ required: If `True`, field cannot be left empty.
54
+ disabled: If `True`, field is non-interactive.
55
+ read_only: If `True`, free-text entry is blocked; user must pick
56
+ from the dropdown.
57
+ width: Width in character cells.
58
+ accent: Accent color applied to the focus ring. Default `'primary'`.
59
+ density: Widget density.
60
+ parent: Override the context-stack parent.
61
+ **kwargs: Layout placement options applied by the parent container —
62
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
63
+ See :doc:`/tasks/layout`.
64
+ """
65
+
66
+ _VALIDATION_KIND = "time"
67
+
68
+ def __init__(
69
+ self,
70
+ value: datetime.time | str | None = None,
71
+ *,
72
+ signal: "Signal | None" = None,
73
+ value_format: str = "shortTime",
74
+ interval: int = 30,
75
+ min_time: datetime.time | str | None = None,
76
+ max_time: datetime.time | str | None = None,
77
+ label: str | None = None,
78
+ message: str | None = None,
79
+ required: bool = False,
80
+ disabled: bool = False,
81
+ read_only: bool = False,
82
+ width: int | None = None,
83
+ accent: AccentToken | str | None = None,
84
+ density: WidgetDensity | None = None,
85
+ parent: Any = None,
86
+ **kwargs: Any,
87
+ ) -> None:
88
+ self._parent = self._resolve_parent(parent)
89
+ layout_kw = self._split_layout_kwargs(kwargs)
90
+ if "textsignal" in kwargs:
91
+ raise TypeError(
92
+ "TimeField does not accept 'textsignal=' — a time field binds its "
93
+ "time value. Use signal= with a time-typed Signal "
94
+ "(e.g. Signal(time(9, 0)))."
95
+ )
96
+ tk_master = self._parent._child_master() if self._parent else None
97
+
98
+ internal_kwargs: dict[str, Any] = {
99
+ "value_format": value_format,
100
+ "interval": interval,
101
+ }
102
+ if value is not None:
103
+ internal_kwargs["value"] = value
104
+ if min_time is not None:
105
+ internal_kwargs["min_time"] = min_time
106
+ if max_time is not None:
107
+ internal_kwargs["max_time"] = max_time
108
+ if label is not None:
109
+ internal_kwargs["label"] = label
110
+ if message is not None:
111
+ internal_kwargs["message"] = message
112
+ if required:
113
+ internal_kwargs["required"] = True
114
+ if disabled:
115
+ internal_kwargs["state"] = "disabled"
116
+ elif read_only:
117
+ internal_kwargs["state"] = "readonly"
118
+ if width is not None:
119
+ internal_kwargs["width"] = width
120
+ if accent is not None:
121
+ internal_kwargs["accent"] = accent
122
+ if density is not None:
123
+ internal_kwargs["density"] = density
124
+
125
+ self._internal = _InternalTimeEntry(tk_master, **internal_kwargs)
126
+ self._attach_to_parent(layout_kw)
127
+
128
+ if signal is not None:
129
+ self._bind_value_signal(signal)
130
+
131
+ # ----- Event routing -----
132
+
133
+ def _entry_widget(self) -> tkinter.Misc:
134
+ return self._internal._entry
135
+
136
+ @overload
137
+ def on(self, event: str) -> Stream: ...
138
+ @overload
139
+ def on(self, event: str, handler: Callable[[Event], Any]) -> Subscription: ...
140
+ def on(self, event: str, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
141
+ """Register a callback for an event by name.
142
+
143
+ A generic, string-keyed escape hatch — prefer the typed `on_*`
144
+ shorthands (e.g. `on_change`), which carry the precise payload type.
145
+ Called with no handler, returns a composable `Stream`; with a handler,
146
+ binds it and returns a `Subscription`.
147
+
148
+ Args:
149
+ event: Event name (for example `'change'` or `'focus'`).
150
+ handler: Called with the event payload. Omit to get a composable
151
+ :class:`~bootstack.streams.Stream` instead.
152
+
153
+ Returns:
154
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
155
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
156
+ """
157
+ sequence = resolve_event(self, str(event))
158
+ widget = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
159
+ if handler is None:
160
+ from bootstack.streams import Stream as _Stream
161
+ def _source(h):
162
+ _w = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
163
+ _bid = _w.bind(sequence, adapt_handler(h), add="+")
164
+ return Subscription(_w, sequence, _bid)
165
+ return _Stream(self._internal, _source=_source)
166
+ bind_id = widget.bind(sequence, adapt_handler(handler), add="+")
167
+ return Subscription(widget, sequence, bind_id)
168
+
169
+ # ----- Properties -----
170
+
171
+ @property
172
+ def value(self) -> "datetime.time | None":
173
+ """Current time value, or `None` if the field is empty."""
174
+ return self._internal.value
175
+
176
+ @value.setter
177
+ def value(self, v: "datetime.time | str | None") -> None:
178
+ self._internal.value = v
179
+ self._sync_value_set(self._internal.value)
180
+
181
+ @property
182
+ def disabled(self) -> bool:
183
+ """Whether the field is disabled (non-interactive and greyed out)."""
184
+ return self._internal._entry.instate(("disabled",))
185
+
186
+ @disabled.setter
187
+ def disabled(self, v: bool) -> None:
188
+ self._internal.configure(state="disabled" if v else "normal")
189
+
190
+ @property
191
+ def read_only(self) -> bool:
192
+ """Whether the field is read-only (selectable but not editable)."""
193
+ return self._internal._entry.instate(("readonly",))
194
+
195
+ @read_only.setter
196
+ def read_only(self, v: bool) -> None:
197
+ self._internal.configure(state="readonly" if v else "normal")
198
+
199
+ # ----- Methods -----
200
+
201
+ def validate(self) -> bool:
202
+ """Run validation rules against the current value.
203
+
204
+ Returns:
205
+ `True` if all rules pass, `False` otherwise.
206
+ """
207
+ entry = self._internal._entry
208
+ return entry.validate(entry._get_validation_value(), trigger="manual")
209
+
210
+ def focus(self) -> None:
211
+ """Give keyboard focus to this field."""
212
+ self._entry_widget().focus_set()
213
+
214
+ def clear(self) -> None:
215
+ """Clear the field, setting the value to `None`."""
216
+ self._internal.value = None
217
+
218
+ # ----- Event shorthands -----
219
+
220
+ @overload
221
+ def on_change(self) -> Stream: ...
222
+ @overload
223
+ def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
224
+ def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
225
+ """Register a callback fired when the time value changes.
226
+
227
+ Args:
228
+ handler: Called with a :class:`~bootstack.events.ChangeEvent`. Omit to
229
+ get a composable :class:`~bootstack.streams.Stream` instead.
230
+
231
+ Returns:
232
+ A cancellable :class:`~bootstack.events.Subscription` when a
233
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
234
+ """
235
+ return self.on("change", handler)
236
+
237
+ @overload
238
+ def on_submit(self) -> Stream: ...
239
+ @overload
240
+ def on_submit(self, handler: Callable[[Event], Any]) -> Subscription: ...
241
+ def on_submit(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
242
+ """Register a callback fired when the user presses Return to confirm input.
243
+
244
+ Args:
245
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
246
+ get a composable :class:`~bootstack.streams.Stream` instead.
247
+
248
+ Returns:
249
+ A cancellable :class:`~bootstack.events.Subscription` when a
250
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
251
+ """
252
+ return self.on("submit", handler)
253
+
254
+ @overload
255
+ def on_valid(self) -> Stream: ...
256
+ @overload
257
+ def on_valid(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
258
+ def on_valid(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
259
+ """Register a callback fired when validation passes.
260
+
261
+ Args:
262
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
263
+ get a composable :class:`~bootstack.streams.Stream` instead.
264
+
265
+ Returns:
266
+ A cancellable :class:`~bootstack.events.Subscription` when a
267
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
268
+ """
269
+ return self.on("valid", handler)
270
+
271
+ @overload
272
+ def on_invalid(self) -> Stream: ...
273
+ @overload
274
+ def on_invalid(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
275
+ def on_invalid(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
276
+ """Register a callback fired when validation fails.
277
+
278
+ Args:
279
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
280
+ get a composable :class:`~bootstack.streams.Stream` instead.
281
+
282
+ Returns:
283
+ A cancellable :class:`~bootstack.events.Subscription` when a
284
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
285
+ """
286
+ return self.on("invalid", handler)
287
+
288
+ @overload
289
+ def on_validate(self) -> Stream: ...
290
+ @overload
291
+ def on_validate(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
292
+ def on_validate(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
293
+ """Register a callback fired when a validation check runs.
294
+
295
+ Args:
296
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
297
+ get a composable :class:`~bootstack.streams.Stream` instead.
298
+
299
+ Returns:
300
+ A cancellable :class:`~bootstack.events.Subscription` when a
301
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
302
+ """
303
+ return self.on("validate", handler)
304
+
305
+ @overload
306
+ def on_focus(self) -> Stream: ...
307
+ @overload
308
+ def on_focus(self, handler: Callable[[Event], Any]) -> Subscription: ...
309
+ def on_focus(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
310
+ """Register a callback fired when the field gains keyboard focus.
311
+
312
+ Args:
313
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
314
+ get a composable :class:`~bootstack.streams.Stream` instead.
315
+
316
+ Returns:
317
+ A cancellable :class:`~bootstack.events.Subscription` when a
318
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
319
+ """
320
+ return self.on("focus", handler)
321
+
322
+ @overload
323
+ def on_blur(self) -> Stream: ...
324
+ @overload
325
+ def on_blur(self, handler: Callable[[Event], Any]) -> Subscription: ...
326
+ def on_blur(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
327
+ """Register a callback fired when the field loses keyboard focus.
328
+
329
+ Args:
330
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
331
+ get a composable :class:`~bootstack.streams.Stream` instead.
332
+
333
+ Returns:
334
+ A cancellable :class:`~bootstack.events.Subscription` when a
335
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
336
+ """
337
+ return self.on("blur", handler)
338
+
339
+
340
+ register_widget_events(TimeField, _TIME_FIELD_EVENTS)
@@ -0,0 +1,314 @@
1
+ """Transient message surfaces: toast, notification, and snackbar.
2
+
3
+ Three opinionated, single-purpose facades over the shared internal toast engine:
4
+
5
+ - `toast()` — a passive, auto-dismissing message that floats in a screen corner
6
+ and stacks. Fire-and-forget.
7
+ - `Notification` — a persistent corner message with a close button; the user
8
+ decides when it goes away. Stacks alongside toasts.
9
+ - `Snackbar` / `snackbar()` — a transient message bound to the app window's
10
+ bottom edge with an optional single action (e.g. *Undo*). Only one shows at a
11
+ time; a new one replaces it.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Callable
16
+
17
+ from bootstack.widgets._impl.composites.toast import Toast as _InternalToast
18
+ from bootstack.widgets._impl.composites.toast_stack import get_toast_manager
19
+ from bootstack.widgets.types import AccentToken, Icon, ToastCorner, SnackbarAlign
20
+
21
+ _TOAST_DEFAULT_DURATION = 4000
22
+ _SNACKBAR_DEFAULT_DURATION = 5000
23
+
24
+
25
+ def _windowing_system() -> str:
26
+ """The active Tk windowing system (`'win32'`, `'aqua'`, or `'x11'`)."""
27
+ from bootstack._runtime.app import get_default_root
28
+
29
+ root = get_default_root()
30
+ if root is not None:
31
+ try:
32
+ return root.tk.call("tk", "windowingsystem")
33
+ except Exception:
34
+ pass
35
+ return "win32"
36
+
37
+
38
+ def _resolve_corner(corner: ToastCorner | None, *, persistent: bool) -> ToastCorner:
39
+ """Resolve the default corner for a toast or notification.
40
+
41
+ Notifications default to the top-right everywhere. Passive toasts default to
42
+ the bottom-right on Windows and macOS, and the top-right on Linux (X11),
43
+ matching each platform's native notification placement.
44
+ """
45
+ if corner is not None:
46
+ return corner
47
+ if persistent:
48
+ return "top-right"
49
+ return "bottom-right" if _windowing_system() in ("win32", "aqua") else "top-right"
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # toast() — passive, auto-dismiss, stacks in a screen corner
54
+ # ---------------------------------------------------------------------------
55
+
56
+ def toast(
57
+ message: str,
58
+ *,
59
+ title: str | None = None,
60
+ corner: ToastCorner | None = None,
61
+ duration: int = _TOAST_DEFAULT_DURATION,
62
+ accent: AccentToken | str | None = None,
63
+ icon: Icon | None = None,
64
+ on_dismiss: Callable[[], Any] | None = None,
65
+ ) -> None:
66
+ """Show a passive toast that dismisses itself, and return immediately.
67
+
68
+ A toast is the lightest notification — "the app telling you something" with
69
+ no demand for a response. It floats in a corner of the monitor the app
70
+ window is on, stacks below any toasts already there, and fades after
71
+ `duration`. For a message the user must dismiss, use `Notification`; for one
72
+ tied to the window with an action, use `Snackbar`.
73
+
74
+ Args:
75
+ message: Body text of the toast.
76
+ title: Optional header shown above the message.
77
+ corner: Screen corner to anchor to — `'top-left'`, `'top-right'`,
78
+ `'bottom-left'`, or `'bottom-right'`. Defaults to the bottom-right
79
+ (top-right on Linux).
80
+ duration: Time on screen in milliseconds before it auto-dismisses.
81
+ Defaults to `4000`.
82
+ accent: Semantic color accent (e.g. `'success'`, `'danger'`). Defaults
83
+ to a neutral surface.
84
+ icon: Optional Bootstrap Icons name (e.g. `'check-circle'`) or an icon
85
+ spec dict with `name`, `size`, and `color`, shown before the message.
86
+ No icon is shown unless given.
87
+ on_dismiss: Called with no arguments when the toast dismisses.
88
+ """
89
+ manager = get_toast_manager()
90
+ resolved = _resolve_corner(corner, persistent=False)
91
+
92
+ engine = _InternalToast(
93
+ title=title,
94
+ message=message,
95
+ icon=icon,
96
+ accent=accent,
97
+ duration=duration,
98
+ show_close_button=False,
99
+ place=manager.corner_placer(resolved),
100
+ )
101
+
102
+ def _on_dismissed(_data: Any) -> None:
103
+ manager.remove(resolved, engine._toplevel)
104
+ if on_dismiss is not None:
105
+ on_dismiss()
106
+
107
+ engine.configure(on_dismissed=_on_dismissed)
108
+ engine.show()
109
+
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # Notification — persistent, close button, stacks in a screen corner
113
+ # ---------------------------------------------------------------------------
114
+
115
+ class Notification:
116
+ """A persistent corner message the user closes themselves.
117
+
118
+ Like a toast, a notification floats in a screen corner and stacks — but it
119
+ does not auto-dismiss. It carries a close button and stays until the user
120
+ closes it or your code calls `dismiss()`. Use it for information that should
121
+ persist (a finished background job, an available update) rather than flash
122
+ by. Build it, then call `show()`.
123
+
124
+ Notifications are message-only — for a response, use a `Snackbar` action or
125
+ a dialog.
126
+
127
+ Args:
128
+ title: Header text — the notification's headline.
129
+ message: Body text shown below the title.
130
+ detail: Small muted metadata shown in the header (e.g. `'just now'`).
131
+ corner: Screen corner to anchor to — `'top-left'`, `'top-right'`,
132
+ `'bottom-left'`, or `'bottom-right'`. Defaults to the top-right.
133
+ accent: Semantic color accent. Defaults to a neutral surface.
134
+ icon: Optional Bootstrap Icons name or an icon spec dict, shown before
135
+ the title. No icon is shown unless given.
136
+ on_dismiss: Called with no arguments when the notification closes.
137
+ """
138
+
139
+ def __init__(
140
+ self,
141
+ title: str | None = None,
142
+ *,
143
+ message: str | None = None,
144
+ detail: str | None = None,
145
+ corner: ToastCorner | None = None,
146
+ accent: AccentToken | str | None = None,
147
+ icon: Icon | None = None,
148
+ on_dismiss: Callable[[], Any] | None = None,
149
+ ) -> None:
150
+ self._title = title
151
+ self._message = message
152
+ self._detail = detail
153
+ self._corner = corner
154
+ self._accent = accent
155
+ self._icon = icon
156
+ self._on_dismiss = on_dismiss
157
+ self._engine: _InternalToast | None = None
158
+
159
+ def show(self) -> "Notification":
160
+ """Display the notification. Returns `self` for chaining."""
161
+ manager = get_toast_manager()
162
+ resolved = _resolve_corner(self._corner, persistent=True)
163
+
164
+ engine = _InternalToast(
165
+ title=self._title,
166
+ message=self._message,
167
+ memo=self._detail,
168
+ icon=self._icon,
169
+ accent=self._accent,
170
+ duration=None,
171
+ show_close_button=True,
172
+ place=manager.corner_placer(resolved),
173
+ )
174
+
175
+ def _on_dismissed(_data: Any) -> None:
176
+ manager.remove(resolved, engine._toplevel)
177
+ if self._on_dismiss is not None:
178
+ self._on_dismiss()
179
+
180
+ engine.configure(on_dismissed=_on_dismissed)
181
+ self._engine = engine
182
+ engine.show()
183
+ return self
184
+
185
+ def dismiss(self) -> None:
186
+ """Close the notification immediately."""
187
+ if self._engine is not None:
188
+ self._engine.hide()
189
+
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # Snackbar — transient, one action, anchored to the window bottom edge
193
+ # ---------------------------------------------------------------------------
194
+
195
+ class Snackbar:
196
+ """A transient message at the app window's bottom edge with one action.
197
+
198
+ A snackbar belongs to the app, not the screen: it slides up from the bottom
199
+ of the window and moves with it. It is for brief, in-context feedback that
200
+ may offer a single response — the classic *"Message archived. [Undo]"*. Only
201
+ one snackbar shows at a time; showing another replaces it. Build it, then
202
+ call `show()`.
203
+
204
+ Args:
205
+ message: Body text of the snackbar.
206
+ action: Label for the optional action button (e.g. `'Undo'`). Omit for
207
+ a message with no action.
208
+ on_action: Called with no arguments when the action button is pressed.
209
+ The snackbar dismisses afterward.
210
+ align: Horizontal placement along the window's bottom edge — `'left'`,
211
+ `'center'`, or `'right'`. Defaults to `'center'`.
212
+ duration: Time on screen in milliseconds before it auto-dismisses.
213
+ Defaults to `5000`.
214
+ accent: Semantic color accent for the action button (the surface stays
215
+ neutral — severity coloring belongs to `toast`/`Notification`).
216
+ Defaults to `'primary'`. No effect without an `action`.
217
+ on_dismiss: Called with no arguments when the snackbar dismisses (whether
218
+ by timeout, the action, or `dismiss()`).
219
+ """
220
+
221
+ def __init__(
222
+ self,
223
+ message: str,
224
+ *,
225
+ action: str | None = None,
226
+ on_action: Callable[[], Any] | None = None,
227
+ align: SnackbarAlign = "center",
228
+ duration: int = _SNACKBAR_DEFAULT_DURATION,
229
+ accent: AccentToken | str | None = None,
230
+ on_dismiss: Callable[[], Any] | None = None,
231
+ ) -> None:
232
+ self._message = message
233
+ self._action = action
234
+ self._on_action = on_action
235
+ self._align = align
236
+ self._duration = duration
237
+ self._accent = accent
238
+ self._on_dismiss = on_dismiss
239
+ self._engine: _InternalToast | None = None
240
+
241
+ def show(self) -> "Snackbar":
242
+ """Display the snackbar, replacing any current one. Returns `self`."""
243
+ manager = get_toast_manager()
244
+
245
+ buttons = None
246
+ if self._action is not None:
247
+ buttons = [{
248
+ "text": self._action,
249
+ "command": self._on_action,
250
+ "variant": "ghost",
251
+ "accent": self._accent or "primary",
252
+ }]
253
+
254
+ # Neutral surface (no accent) and no icon — a snackbar is in-app action
255
+ # feedback, not a severity message. The accent rides only on the action.
256
+ engine = _InternalToast(
257
+ message=self._message,
258
+ duration=self._duration,
259
+ show_close_button=False,
260
+ buttons=buttons,
261
+ place=manager.snackbar_placer(self._align),
262
+ )
263
+
264
+ def _on_dismissed(_data: Any) -> None:
265
+ manager.clear_snackbar(engine._toplevel)
266
+ if self._on_dismiss is not None:
267
+ self._on_dismiss()
268
+
269
+ engine.configure(on_dismissed=_on_dismissed)
270
+ self._engine = engine
271
+ engine.show()
272
+ return self
273
+
274
+ def dismiss(self) -> None:
275
+ """Dismiss the snackbar immediately."""
276
+ if self._engine is not None:
277
+ self._engine.hide()
278
+
279
+
280
+ def snackbar(
281
+ message: str,
282
+ *,
283
+ action: str | None = None,
284
+ on_action: Callable[[], Any] | None = None,
285
+ align: SnackbarAlign = "center",
286
+ duration: int = _SNACKBAR_DEFAULT_DURATION,
287
+ accent: AccentToken | str | None = None,
288
+ on_dismiss: Callable[[], Any] | None = None,
289
+ ) -> None:
290
+ """Show a snackbar at the window's bottom edge, and return immediately.
291
+
292
+ The one-line form of `Snackbar` — see it for the full description. Use the
293
+ class when you need a handle to `dismiss()` early.
294
+
295
+ Args:
296
+ message: Body text of the snackbar.
297
+ action: Label for the optional action button (e.g. `'Undo'`).
298
+ on_action: Called with no arguments when the action is pressed.
299
+ align: Bottom-edge alignment — `'left'`, `'center'`, or `'right'`.
300
+ Defaults to `'center'`.
301
+ duration: Time on screen in milliseconds. Defaults to `5000`.
302
+ accent: Semantic color accent for the action button (the surface stays
303
+ neutral). No effect without an `action`.
304
+ on_dismiss: Called with no arguments when the snackbar dismisses.
305
+ """
306
+ Snackbar(
307
+ message,
308
+ action=action,
309
+ on_action=on_action,
310
+ align=align,
311
+ duration=duration,
312
+ accent=accent,
313
+ on_dismiss=on_dismiss,
314
+ ).show()