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,231 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter
4
+ from typing import overload, Any, Callable, Literal, TYPE_CHECKING
5
+
6
+ from bootstack.widgets._impl.composites.togglegroup import ToggleGroup as _InternalToggleGroup
7
+ from bootstack.widgets._core.base import PublicWidgetBase
8
+ from bootstack.widgets._core.selection_group import SelectionGroupMixin
9
+ from bootstack.widgets._core.options import normalize_options, option_display
10
+ from bootstack.widgets._core.events import register_widget_events
11
+ from bootstack.events import Subscription, ChangeEvent
12
+ from bootstack.streams import Stream
13
+ from bootstack.widgets.types import AccentToken, Event, Option, Orient, ButtonVariant
14
+
15
+ if TYPE_CHECKING:
16
+ from bootstack.signals import Signal
17
+
18
+ _TOGGLEGROUP_EVENTS: dict[str, str] = {
19
+ "change": "<<Change>>",
20
+ }
21
+
22
+
23
+ class ToggleGroup(SelectionGroupMixin, PublicWidgetBase):
24
+ """A group of toggle buttons — single-select or multi-select.
25
+
26
+ Options are supplied at construction via `options=` and can be added
27
+ at runtime with `add()`. In `'single'` mode exactly one button is
28
+ active at a time; in `'multi'` mode any combination can be active.
29
+
30
+ Args:
31
+ options: Choices for the group. Each item is a plain string (text and
32
+ value are the same), a `(text, value)` tuple, or a
33
+ `{'text': ..., 'value': ...}` dict — e.g. `["Grid", "List"]` or
34
+ `[("Grid view", "grid"), ("List view", "list")]`. A dict option may
35
+ also carry `'icon'` (a glyph rendered beside the label) and
36
+ `'disabled'` (when `True` the option is dimmed and cannot be
37
+ selected); any other keys ride along as carried data on `selection`.
38
+ An option with an `'icon'` and no text — e.g.
39
+ `{'icon': 'grid', 'value': 'grid'}` — renders as an icon-only button.
40
+ mode: Selection behavior. `'single'` (default) enforces mutual
41
+ exclusivity like a radio group; `'multi'` allows any number
42
+ of buttons to be active simultaneously.
43
+ signal: Reactive `Signal` holding the selected value(s). In
44
+ `'single'` mode the signal value is a `str`; in `'multi'`
45
+ mode it is a `set[str]`. When provided, `value=` is ignored
46
+ — seed the Signal directly.
47
+ value: Initial selection. A `str` for `'single'` mode or a
48
+ `set[str]` for `'multi'` mode. Ignored when `signal=` is
49
+ passed.
50
+ orient: Layout direction. Default `'horizontal'`.
51
+ accent: Accent token applied to all buttons.
52
+ variant: Button style variant. Default `'solid'`.
53
+ disabled: If `True`, all buttons are non-interactive and dimmed.
54
+ Defaults to `False`.
55
+ localize: Whether option labels are translated through the catalog —
56
+ `True`, `False`, or `'auto'` (translate when a translation is
57
+ registered, otherwise show the literal). Defaults to the app's
58
+ `localize_mode`. Set `False` to keep proper nouns untranslated;
59
+ override a single option with its `localize` key.
60
+ parent: Explicit parent widget. If omitted, the current
61
+ context-stack container is used.
62
+ **kwargs: Layout placement options applied by the parent container —
63
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
64
+ See :doc:`/tasks/layout`.
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ options: list[Option] | None = None,
70
+ *,
71
+ mode: Literal["single", "multi"] = "single",
72
+ signal: "Signal | None" = None,
73
+ value: Any = None,
74
+ orient: Orient = "horizontal",
75
+ accent: AccentToken | str | None = None,
76
+ variant: ButtonVariant = "default",
77
+ disabled: bool = False,
78
+ localize: bool | Literal['auto'] | None = None,
79
+ parent: Any = None,
80
+ **kwargs: Any,
81
+ ) -> None:
82
+ self._localize = localize
83
+ self._parent = self._resolve_parent(parent)
84
+ layout_kw = self._split_layout_kwargs(kwargs)
85
+
86
+ tk_master = self._parent._child_master() if self._parent else None
87
+
88
+ internal_kwargs: dict[str, Any] = {
89
+ "mode": mode,
90
+ "orient": orient,
91
+ }
92
+ if signal is not None:
93
+ internal_kwargs["signal"] = signal
94
+ if value is not None:
95
+ internal_kwargs["value"] = value
96
+ if accent is not None:
97
+ internal_kwargs["accent"] = accent
98
+ if variant is not None:
99
+ internal_kwargs["variant"] = variant
100
+ if disabled:
101
+ internal_kwargs["state"] = "disabled"
102
+
103
+ self._internal = _InternalToggleGroup(tk_master, **internal_kwargs)
104
+
105
+ # Populate options passed at construction; keep the records (the data
106
+ # bag) so `selection` can return each option's full dict.
107
+ records = normalize_options(options)
108
+ self._set_option_records(records)
109
+ for record in records:
110
+ icon, disabled = option_display(record)
111
+ add_kwargs, _ = self._option_render(icon, disabled, record.text)
112
+ item_localize = self._resolve_localize(record)
113
+ if item_localize is not None:
114
+ add_kwargs["localize"] = item_localize
115
+ self._internal.add(text=record.text, value=record.value, **add_kwargs)
116
+
117
+ # Trace signal → <<Change>> for consistent Subscription-based on_change().
118
+ self._prev_value = self._internal.signal()
119
+
120
+ def _on_value_change(new_value: Any) -> None:
121
+ try:
122
+ prev, self._prev_value = self._prev_value, new_value
123
+ self._internal.event_generate(
124
+ "<<Change>>", data=ChangeEvent(value=new_value, prev_value=prev)
125
+ )
126
+ except tkinter.TclError:
127
+ pass
128
+
129
+ self._trace_id = self._internal.signal.subscribe(_on_value_change)
130
+ self._attach_to_parent(layout_kw)
131
+
132
+ # ----- Properties -----
133
+
134
+ @property
135
+ def value(self) -> Any:
136
+ """The current selection. A `str` in single mode, `set[str]` in multi mode."""
137
+ return self._internal.get()
138
+
139
+ @value.setter
140
+ def value(self, v: Any) -> None:
141
+ self._internal.set(v)
142
+
143
+ @property
144
+ def text(self) -> str | set[str] | None:
145
+ """The label(s) of the selected option(s) — the display complement of `value`.
146
+
147
+ In single mode, the selected option's label (or `None` if nothing is
148
+ selected). In multi mode, a `set` of the selected options' labels.
149
+ Read-only.
150
+ """
151
+ value = self._internal.get()
152
+ if isinstance(value, set):
153
+ return {t for v in value if (t := self._internal.text_for(v)) is not None}
154
+ return self._internal.text_for(value)
155
+
156
+ @property
157
+ def selection(self) -> dict | list[dict] | None:
158
+ """The selected option(s) as full record dicts — the data bag.
159
+
160
+ In single mode, the selected option's `{text, value, ...extras}` dict
161
+ (or `None`). In multi mode, a `list` of those dicts. Read-only.
162
+ """
163
+ value = self._internal.get()
164
+ if isinstance(value, set):
165
+ return [d for v in value if (d := self._record_dict_for(v)) is not None]
166
+ return self._record_dict_for(value)
167
+
168
+ @property
169
+ def disabled(self) -> bool:
170
+ """Whether all buttons in the group are non-interactive."""
171
+ return self._internal._state == "disabled"
172
+
173
+ @disabled.setter
174
+ def disabled(self, v: bool) -> None:
175
+ self._internal.configure(state="disabled" if v else "normal")
176
+
177
+ # ----- Methods -----
178
+
179
+ def add(
180
+ self,
181
+ label: str,
182
+ value: Any | None = None,
183
+ *,
184
+ icon: Any = None,
185
+ disabled: bool = False,
186
+ localize: bool | Literal['auto'] | None = None,
187
+ **kwargs: Any,
188
+ ) -> None:
189
+ """Add a toggle button at runtime.
190
+
191
+ Args:
192
+ label: Display text. Pass `''` with an `icon` for an icon-only button.
193
+ value: Value this button represents. Defaults to `label`.
194
+ icon: Optional icon spec rendered beside the label (alone when
195
+ `label` is blank).
196
+ disabled: If `True`, the option is dimmed and cannot be selected.
197
+ localize: Translation mode for this option's label, overriding the
198
+ group's `localize=`. Defaults to the group setting.
199
+ """
200
+ resolved = value if value is not None else label
201
+ add_kwargs, extras = self._option_render(icon, disabled, label)
202
+ item_localize = self._resolve_localize(override=localize)
203
+ if item_localize is not None:
204
+ add_kwargs["localize"] = item_localize
205
+ if localize is not None:
206
+ extras["localize"] = localize
207
+ self._internal.add(text=label, value=resolved, **add_kwargs, **kwargs)
208
+ self._add_option_record(label, resolved, extras)
209
+
210
+ # ----- Event shorthands -----
211
+
212
+ @overload
213
+ def on_change(self) -> Stream: ...
214
+ @overload
215
+ def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
216
+ def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
217
+ """Register a callback fired whenever the selection changes.
218
+
219
+ Args:
220
+ handler: Called with a :class:`~bootstack.events.ChangeEvent`; the
221
+ current value is also available via `group.value`. Omit to get
222
+ a composable :class:`~bootstack.streams.Stream` instead.
223
+
224
+ Returns:
225
+ A cancellable :class:`~bootstack.events.Subscription` when a
226
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
227
+ """
228
+ return self.on("change", handler)
229
+
230
+
231
+ register_widget_events(ToggleGroup, _TOGGLEGROUP_EVENTS)
@@ -0,0 +1,347 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Callable
4
+
5
+ from bootstack.errors import BootstackError
6
+ from bootstack.widgets._impl.composites.toolbar import Toolbar as _InternalToolbar
7
+ from bootstack.widgets._core.base import PublicWidgetBase
8
+ from bootstack.widgets.types import AccentToken, WidgetDensity, SurfaceToken, ButtonVariant
9
+
10
+ if TYPE_CHECKING:
11
+ from bootstack.widgets._impl.composites.menu.model import MenuGroup
12
+ from bootstack.widgets.button import Button
13
+ from bootstack.widgets.label import Label
14
+
15
+
16
+ class Toolbar(PublicWidgetBase):
17
+ """A horizontal strip of buttons, labels, and other widgets.
18
+
19
+ The toolbar holds an app's commands — buttons, a search box, a theme toggle.
20
+ Items are added left-to-right via `add_button()`, `add_label()`,
21
+ `add_divider()`, `add_spacer()`, and `add_widget()`. Call `add_spacer()` to
22
+ push subsequent items to the right side.
23
+
24
+ Use this widget directly to build a standalone toolbar or a custom titlebar.
25
+
26
+ Args:
27
+ button_variant: Default variant applied to every button added via
28
+ `add_button()`. Default `'ghost'`.
29
+ density: Size of toolbar items. Default `'default'`.
30
+ surface: Background surface token. Defaults to `None` — the toolbar
31
+ inherits the base content surface (it blends into its parent). Pass
32
+ `'chrome'` (or another token) to tint the bar explicitly.
33
+ padding: Inner padding in pixels — an int (all sides) or a
34
+ `(horizontal, vertical)` tuple. Defaults to `3` for default density
35
+ and `(3, 1)` for compact.
36
+ show_border: If `True`, draws a border around the toolbar frame.
37
+ Default `False`.
38
+ show_window_controls: If `True`, adds minimize, maximize, and close
39
+ buttons to the right side. Default `False`.
40
+ draggable: If `True`, clicking and dragging the toolbar repositions
41
+ the window. Automatically enabled when `show_window_controls=True`.
42
+ Default `False`.
43
+ parent: Override the context-stack parent.
44
+ **kwargs: Layout placement options applied by the parent container —
45
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
46
+ See :doc:`/tasks/layout`.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ *,
52
+ button_variant: ButtonVariant = "ghost",
53
+ density: WidgetDensity = "default",
54
+ surface: SurfaceToken | str | None = None,
55
+ padding: int | tuple[int, int] | None = None,
56
+ show_border: bool = False,
57
+ show_window_controls: bool = False,
58
+ draggable: bool = False,
59
+ parent: Any = None,
60
+ _toolbar: Any = None,
61
+ _host: Any = None,
62
+ **kwargs: Any,
63
+ ) -> None:
64
+ # The chrome host (App/Window/AppShell) that owns this bar, when built via
65
+ # `host.add_toolbar()`. Lets shell-aware conveniences (add_sidebar_toggle)
66
+ # reach the sidebar; None for a standalone bar.
67
+ self._host = _host
68
+ if _toolbar is not None:
69
+ # Adoption: wrap a pre-built internal toolbar (e.g. an AppShell's
70
+ # titlebar band, which already carries its window controls + drag).
71
+ self._internal = _toolbar
72
+ self._parent = None
73
+ return
74
+
75
+ self._parent = self._resolve_parent(parent)
76
+ layout_kw = self._split_layout_kwargs(kwargs)
77
+ tk_master = self._parent._child_master() if self._parent else None
78
+
79
+ internal_kwargs: dict[str, Any] = {
80
+ "button_variant": button_variant,
81
+ "density": density,
82
+ "show_window_controls": show_window_controls,
83
+ "draggable": draggable,
84
+ "show_border": show_border,
85
+ }
86
+ if surface is not None:
87
+ internal_kwargs["surface"] = surface
88
+ if padding is not None:
89
+ internal_kwargs["padding"] = padding
90
+ internal_kwargs.update(kwargs)
91
+
92
+ self._internal = _InternalToolbar(tk_master, **internal_kwargs)
93
+ self._attach_to_parent(layout_kw)
94
+
95
+ # ----- Properties -----
96
+
97
+ @property
98
+ def density(self) -> WidgetDensity:
99
+ """Current density setting for toolbar items."""
100
+ return self._internal.density
101
+
102
+ @property
103
+ def button_variant(self) -> str:
104
+ """Default variant applied to buttons added via `add_button()`."""
105
+ return self._internal._button_variant
106
+
107
+ # ----- Content -----
108
+
109
+ def add_button(
110
+ self,
111
+ label: str | None = None,
112
+ *,
113
+ icon: str | None = None,
114
+ on_click: Callable[[], Any] | None = None,
115
+ accent: AccentToken | str | None = None,
116
+ variant: ButtonVariant | None = None,
117
+ **kwargs: Any,
118
+ ) -> "Button":
119
+ """Add a button to the toolbar and return it for later control.
120
+
121
+ When both `label` and `icon` are given, the button shows text and icon
122
+ side by side. When only `icon` is given, the button is icon-only.
123
+
124
+ Args:
125
+ label: Button label text.
126
+ icon: Icon name. When provided without `label`, renders icon-only.
127
+ on_click: Callback fired when the button is clicked.
128
+ accent: Color intent override.
129
+ variant: Variant override. When omitted, falls back to the toolbar's
130
+ `button_variant` (default `'ghost'`).
131
+
132
+ Returns:
133
+ The :class:`~bootstack.Button` — use its live properties to control
134
+ it later, e.g. ``btn.disabled = True`` or ``btn.text = 'Saving…'``.
135
+ """
136
+ from bootstack.widgets.button import Button
137
+
138
+ kw: dict[str, Any] = {
139
+ "text": label or "",
140
+ "variant": variant or self.button_variant,
141
+ "density": self.density,
142
+ "surface": self._internal.surface,
143
+ }
144
+ if icon is not None:
145
+ kw["icon"] = icon
146
+ if on_click is not None:
147
+ kw["on_click"] = on_click
148
+ if accent is not None:
149
+ kw["accent"] = accent
150
+ kw.update(kwargs) # explicit kwargs (incl. text=) win over the defaults
151
+ return Button(parent=self, **kw)
152
+
153
+ def add_menu(self, text: str, *, key: str | None = None) -> "MenuGroup":
154
+ """Add a dropdown menu (File / Edit / …) as a toolbar item.
155
+
156
+ A menu is just another toolbar item. The returned builder is a context
157
+ manager, so the natural form reads::
158
+
159
+ with toolbar.add_menu("File") as file:
160
+ file.add_action("Open", shortcut="Mod+O", on_click=open_file)
161
+ file.add_divider()
162
+ file.add_action("Quit", shortcut="Mod+Q", on_click=app.close)
163
+
164
+ On Windows/Linux the menu renders as an in-window dropdown; on macOS it
165
+ is bridged to the native global menu bar (when the toolbar is part of a
166
+ window's chrome).
167
+
168
+ Args:
169
+ text: The menu's label (e.g. `'File'`).
170
+ key: Optional stable identifier; defaults to `text`.
171
+
172
+ Returns:
173
+ The menu's `MenuGroup` builder (`add_action` / `add_check` /
174
+ `add_radio` / `add_divider`; usable as a context manager).
175
+ """
176
+ return self._internal.add_menu(text, key=key)
177
+
178
+ def add_label(
179
+ self,
180
+ text: str | None = None,
181
+ *,
182
+ icon: str | None = None,
183
+ font: str | None = None,
184
+ **kwargs: Any,
185
+ ) -> "Label":
186
+ """Add a non-interactive label to the toolbar and return it.
187
+
188
+ Args:
189
+ text: Label text.
190
+ icon: Icon name displayed alongside the text.
191
+ font: Font token, e.g. `'heading-md'` or `'caption'`.
192
+
193
+ Returns:
194
+ The :class:`~bootstack.Label` — use its live properties to update it
195
+ later, e.g. ``lbl.text = 'Updated'``.
196
+ """
197
+ from bootstack.widgets.label import Label
198
+
199
+ kw: dict[str, Any] = {"text": text or "", "surface": self._internal.surface}
200
+ if icon is not None:
201
+ kw["icon"] = icon
202
+ if font is not None:
203
+ kw["font"] = font
204
+ kw.update(kwargs)
205
+ lbl = Label(parent=self, **kw)
206
+ # On a draggable bar (e.g. a titlebar) the label doubles as a drag handle.
207
+ self._internal._attach_drag(lbl._internal)
208
+ return lbl
209
+
210
+ def add_divider(self, length: int = 16) -> None:
211
+ """Add a vertical divider.
212
+
213
+ Args:
214
+ length: Divider height in pixels. Defaults to `16`.
215
+ """
216
+ self._internal.add_separator(length=length)
217
+
218
+ def add_spacer(self) -> None:
219
+ """Add a flexible spacer that pushes subsequent items to the right."""
220
+ self._internal.add_spacer()
221
+
222
+ def add_widget(self, widget_cls: Any, **kwargs: Any) -> Any:
223
+ """Build a widget on the toolbar from its class.
224
+
225
+ Pass a widget **class** — the bar builds it, applying its own `density`
226
+ and `surface` (for any the class accepts) so the widget matches the rest
227
+ of the bar, and forwarding `kwargs` to the constructor (overriding those
228
+ defaults):
229
+
230
+ bar.add_widget(bs.ThemeToggle)
231
+ bar.add_widget(bs.TextField, placeholder="Search", width=24)
232
+
233
+ To add a widget you have already built yourself, parent it onto the bar
234
+ directly — it attaches automatically and keeps whatever you configured:
235
+
236
+ bs.MyCustomWidget(parent=bar)
237
+
238
+ Args:
239
+ widget_cls: The widget class to build on the bar.
240
+ **kwargs: Constructor arguments for the widget.
241
+
242
+ Returns:
243
+ The new widget instance.
244
+ """
245
+ self._apply_bar_defaults(widget_cls, kwargs)
246
+ return widget_cls(parent=self, **kwargs)
247
+
248
+ def _apply_bar_defaults(self, widget_cls: type, kwargs: dict[str, Any]) -> None:
249
+ """Default `density`/`surface` from the bar onto a class being built, but
250
+ only for parameters the widget actually accepts (and not if the caller
251
+ already passed them).
252
+
253
+ A widget is treated as accepting `density`/`surface` only when it declares
254
+ that parameter by name. A bare `**kwargs` catch-all does NOT count: in the
255
+ public layer `**kwargs` is the layout-placement passthrough, and several
256
+ widgets use it to *reject* unknown keys — injecting into those raises a
257
+ `TypeError`, and injecting into ones that ignore it is a silent no-op.
258
+ """
259
+ import inspect
260
+
261
+ try:
262
+ params = inspect.signature(widget_cls).parameters
263
+ except (TypeError, ValueError):
264
+ return
265
+ if "density" in params:
266
+ kwargs.setdefault("density", self._internal.density)
267
+ if "surface" in params:
268
+ kwargs.setdefault("surface", self._internal.surface)
269
+
270
+ # ----- Container protocol (so `parent=toolbar` works) -----
271
+
272
+ def _child_master(self) -> Any:
273
+ return self._internal.content
274
+
275
+ def guide_layout(self, child: Any, **layout_kw: Any) -> None:
276
+ # A widget created with `parent=toolbar` is packed into the bar.
277
+ self._internal.add_widget(child._internal)
278
+
279
+ def __enter__(self) -> "Toolbar":
280
+ # A *scoping* context manager (like `menubar.add_menu`): `with
281
+ # window.add_toolbar() as tb:` reads naturally and hands back the handle,
282
+ # but it does NOT capture bare widgets — fill the bar with `add_button` /
283
+ # `add_menu` / `add_widget(Class, ...)` so each item inherits the bar's
284
+ # density and surface (a constructed-then-attached widget cannot).
285
+ return self
286
+
287
+ def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
288
+ return None
289
+
290
+ @property
291
+ def content(self) -> Any:
292
+ """The bar's content frame — parent custom widgets here."""
293
+ return self._internal.content
294
+
295
+ def add_theme_toggle(self, **kwargs: Any) -> Any:
296
+ """Add a `ThemeToggle` — a sun/moon button that flips the theme.
297
+
298
+ Args:
299
+ **kwargs: Forwarded to `ThemeToggle` — e.g. `variant`, `density`,
300
+ `accent`, `light_icon`, `dark_icon`.
301
+
302
+ Returns:
303
+ The created `ThemeToggle`.
304
+ """
305
+ from bootstack.widgets.theme_toggle import ThemeToggle
306
+
307
+ self._apply_bar_defaults(ThemeToggle, kwargs)
308
+ return ThemeToggle(parent=self, **kwargs)
309
+
310
+ def add_sidebar_toggle(self, **kwargs: Any) -> Any:
311
+ """Add a hamburger button that collapses/expands the AppShell sidebar.
312
+
313
+ Only meaningful on a toolbar built into an `AppShell` (via
314
+ `shell.add_toolbar()`); raises on an `App`/`Window` toolbar, which has no
315
+ sidebar. Place it wherever you like in the bar.
316
+
317
+ Args:
318
+ **kwargs: Forwarded to the toggle button — e.g. `collapse`
319
+ (`'hidden'`/`'compact'`), `icon`, `expand_icon`, `collapse_icon`,
320
+ `variant`, `density`, `accent`.
321
+
322
+ Returns:
323
+ The created sidebar-toggle button.
324
+ """
325
+ from bootstack.widgets.sidebar_toggle import SidebarToggle
326
+
327
+ shell = self._sidebar_host()
328
+ self._apply_bar_defaults(SidebarToggle, kwargs)
329
+ return SidebarToggle(shell=shell, parent=self, **kwargs)
330
+
331
+ def _sidebar_host(self) -> Any:
332
+ """Return the owning `AppShell`, or raise if this bar has no sidebar.
333
+
334
+ Restricted to `AppShell` — the single-tier sidebar host. A `Workbench`
335
+ (two-tier, rail + per-workspace sidebars) inherits the shell sidebar
336
+ methods but is intentionally not supported here.
337
+ """
338
+ from bootstack.widgets.appshell import AppShell
339
+
340
+ host = getattr(self, "_host", None)
341
+ if not isinstance(host, AppShell):
342
+ raise BootstackError(
343
+ "add_sidebar_toggle() requires a toolbar built on an AppShell "
344
+ "(via shell.add_toolbar()); it is not supported on a Workbench or "
345
+ "a plain App/Window."
346
+ )
347
+ return host
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal
4
+
5
+ from bootstack.widgets._impl.composites.tooltip import ToolTip as _InternalToolTip
6
+ from bootstack.widgets._core.base import PublicWidgetBase
7
+ from bootstack.widgets.types import AccentToken, Anchor, Justify
8
+
9
+
10
+ class Tooltip:
11
+ """A hover tooltip attached to a target widget.
12
+
13
+ The tooltip appears after `delay` milliseconds when the mouse enters the
14
+ target and disappears when the mouse leaves or clicks. Positioning follows
15
+ the mouse cursor by default; pass `anchor_point` and `window_point` to
16
+ pin the tooltip to a specific edge of the widget instead.
17
+
18
+ Args:
19
+ target: Widget to attach the tooltip to. Accepts any bootstack widget.
20
+ When the target is a container, the tooltip shows while hovering
21
+ anywhere inside it, including over its children present at attach
22
+ time. For children added later, call `refresh_bindings()`.
23
+ text: Tooltip text content. Defaults to `''`.
24
+ delay: Milliseconds before the tooltip appears on mouse enter. Defaults
25
+ to `250`.
26
+ accent: Semantic color accent. Defaults to the theme's elevated
27
+ background color.
28
+ wrap_width: Maximum text width in pixels before wrapping. Defaults to
29
+ `None` (auto-scaled to approximately 300 px).
30
+ justify: Text alignment inside the tooltip. Defaults to `'left'`.
31
+ anchor_point: Anchor on the *target widget* the tooltip attaches to.
32
+ Defaults to `None` (tooltip follows the mouse).
33
+ window_point: Anchor on the *tooltip window* aligned to `anchor_point`.
34
+ Defaults to the opposite of `anchor_point` when it is set.
35
+ auto_flip: Keep the tooltip fully on screen. `True` flips both axes,
36
+ `False` disables flipping, `'vertical'` or `'horizontal'` restricts
37
+ flipping to one axis. Defaults to `True`.
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ target: PublicWidgetBase | Any,
43
+ text: str = "",
44
+ *,
45
+ delay: int = 250,
46
+ accent: AccentToken | str | None = None,
47
+ wrap_width: int | None = None,
48
+ justify: Justify = "left",
49
+ anchor_point: Anchor | None = None,
50
+ window_point: Anchor | None = None,
51
+ auto_flip: bool | Literal["vertical", "horizontal"] = True,
52
+ ) -> None:
53
+ tk_widget = target.tk if isinstance(target, PublicWidgetBase) else target
54
+
55
+ internal_kwargs: dict[str, Any] = {
56
+ "text": text,
57
+ "delay": delay,
58
+ "justify": justify,
59
+ "auto_flip": auto_flip,
60
+ }
61
+ if accent is not None:
62
+ internal_kwargs["accent"] = accent
63
+ if wrap_width is not None:
64
+ internal_kwargs["wraplength"] = wrap_width
65
+ if anchor_point is not None:
66
+ internal_kwargs["anchor_point"] = anchor_point
67
+ if window_point is not None:
68
+ internal_kwargs["window_point"] = window_point
69
+
70
+ self._internal = _InternalToolTip(tk_widget, **internal_kwargs)
71
+
72
+ def refresh_bindings(self) -> None:
73
+ """Re-cover a container target's subtree after adding children to it.
74
+
75
+ A tooltip on a container covers the children present when it was
76
+ created. Add more children later and call this so hovering them shows
77
+ the tip too. Safe to call repeatedly — already-covered children are
78
+ left untouched.
79
+ """
80
+ self._internal.refresh_bindings()
81
+
82
+ @property
83
+ def text(self) -> str:
84
+ """The tooltip text. Assign to change it — a visible tooltip updates
85
+ immediately, and the next hover shows the new text."""
86
+ return self._internal._text
87
+
88
+ @text.setter
89
+ def text(self, value: str) -> None:
90
+ self._internal.text = value
91
+
92
+ def destroy(self) -> None:
93
+ """Remove the tooltip and unbind all event handlers."""
94
+ self._internal.destroy()