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,228 @@
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.radiogroup import RadioGroup as _InternalRadioGroup
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
14
+
15
+ if TYPE_CHECKING:
16
+ from bootstack.signals import Signal
17
+
18
+ _RADIOGROUP_EVENTS: dict[str, str] = {
19
+ "change": "<<Change>>",
20
+ }
21
+
22
+
23
+ class RadioGroup(SelectionGroupMixin, PublicWidgetBase):
24
+ """A group of mutually exclusive radio buttons.
25
+
26
+ Exactly one option can be selected at a time. Options are supplied at
27
+ construction via `options=` and can be added or removed at runtime
28
+ using `add()` and `remove()`.
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. `["S", "M", "L"]` or
34
+ `[("Small", "s"), ("Medium", "m"), ("Large", "l")]`. A dict option
35
+ may 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': 'star', 'value': 's'}` — renders as an icon-only button.
40
+ signal: Reactive `Signal` holding the selected value. When
41
+ provided, `value=` is ignored — seed the Signal directly.
42
+ value: Initially selected value. Ignored when `signal=` is passed.
43
+ orient: Layout direction. Default `'horizontal'`.
44
+ title: Optional label rendered above the button group.
45
+ accent: Accent token applied to all buttons.
46
+ disabled: If `True`, all buttons are non-interactive and dimmed.
47
+ Defaults to `False`.
48
+ localize: Whether option labels (and the `title`) are translated through
49
+ the catalog — `True`, `False`, or `'auto'` (translate when a
50
+ translation is registered, otherwise show the literal). Defaults to
51
+ the app's `localize_mode`. Set `False` to keep proper nouns
52
+ untranslated; override a single option with its `localize` key.
53
+ parent: Explicit parent widget. If omitted, the current
54
+ context-stack container is used.
55
+ **kwargs: Layout placement options applied by the parent container —
56
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
57
+ See :doc:`/tasks/layout`.
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ options: list[Option] | None = None,
63
+ *,
64
+ signal: "Signal | None" = None,
65
+ value: Any = None,
66
+ orient: Orient = "horizontal",
67
+ title: str | None = None,
68
+ accent: AccentToken | str | None = None,
69
+ disabled: bool = False,
70
+ localize: bool | Literal['auto'] | None = None,
71
+ parent: Any = None,
72
+ **kwargs: Any,
73
+ ) -> None:
74
+ self._localize = localize
75
+ self._parent = self._resolve_parent(parent)
76
+ layout_kw = self._split_layout_kwargs(kwargs)
77
+
78
+ tk_master = self._parent._child_master() if self._parent else None
79
+
80
+ internal_kwargs: dict[str, Any] = {"orient": orient}
81
+ if signal is not None:
82
+ internal_kwargs["signal"] = signal
83
+ if value is not None:
84
+ internal_kwargs["value"] = value
85
+ if title is not None:
86
+ internal_kwargs["text"] = title
87
+ if accent is not None:
88
+ internal_kwargs["accent"] = accent
89
+ if disabled:
90
+ internal_kwargs["state"] = "disabled"
91
+ if localize is not None:
92
+ internal_kwargs["localize"] = localize
93
+
94
+ self._internal = _InternalRadioGroup(tk_master, **internal_kwargs)
95
+
96
+ # Populate options passed at construction; keep the records (the data
97
+ # bag) so `selection` can return each option's full dict.
98
+ records = normalize_options(options)
99
+ self._set_option_records(records)
100
+ for record in records:
101
+ icon, disabled = option_display(record)
102
+ add_kwargs, _ = self._option_render(icon, disabled, record.text)
103
+ item_localize = self._resolve_localize(record)
104
+ if item_localize is not None:
105
+ add_kwargs["localize"] = item_localize
106
+ self._internal.add(text=record.text, value=record.value, **add_kwargs)
107
+
108
+ # Trace the signal so <<Change>> fires on the internal Frame whenever
109
+ # the value changes — this lets on_change() use standard Subscription binding.
110
+ self._prev_value = self._internal.signal()
111
+
112
+ def _on_value_change(new_value: Any) -> None:
113
+ try:
114
+ prev, self._prev_value = self._prev_value, new_value
115
+ self._internal.event_generate(
116
+ "<<Change>>", data=ChangeEvent(value=new_value, prev_value=prev)
117
+ )
118
+ except tkinter.TclError:
119
+ pass
120
+
121
+ self._trace_id = self._internal.signal.subscribe(_on_value_change)
122
+ self._attach_to_parent(layout_kw)
123
+
124
+ # ----- Properties -----
125
+
126
+ @property
127
+ def value(self) -> Any:
128
+ """The currently selected value, or `None` if nothing is selected."""
129
+ return self._internal.value
130
+
131
+ @value.setter
132
+ def value(self, v: Any) -> None:
133
+ self._internal.value = v
134
+
135
+ @property
136
+ def text(self) -> str | None:
137
+ """The label of the selected option, or `None` if nothing is selected.
138
+
139
+ Read-only; the display complement of `value` (the selection's value).
140
+ """
141
+ return self._internal.text_for(self._internal.value)
142
+
143
+ @property
144
+ def selection(self) -> dict | None:
145
+ """The selected option as a full record dict — the data bag — or `None`.
146
+
147
+ `{'text': ..., 'value': ..., ...any extra keys}`, indexed by key like any
148
+ record. Read-only.
149
+ """
150
+ return self._record_dict_for(self._internal.value)
151
+
152
+ @property
153
+ def disabled(self) -> bool:
154
+ """Whether all buttons in the group are non-interactive."""
155
+ return self._internal._state == "disabled"
156
+
157
+ @disabled.setter
158
+ def disabled(self, v: bool) -> None:
159
+ self._internal.configure(state="disabled" if v else "normal")
160
+
161
+ @property
162
+ def title(self) -> str | None:
163
+ """The label rendered above the group. Assigning to it updates it live.
164
+
165
+ Set to `None` (or `''`) to remove the label entirely.
166
+ """
167
+ return self._internal.cget("text") or None
168
+
169
+ @title.setter
170
+ def title(self, value: str | None) -> None:
171
+ self._internal.configure(text=value or "")
172
+
173
+ # ----- Methods -----
174
+
175
+ def add(
176
+ self,
177
+ label: str,
178
+ value: Any | None = None,
179
+ *,
180
+ icon: Any = None,
181
+ disabled: bool = False,
182
+ localize: bool | Literal['auto'] | None = None,
183
+ **kwargs: Any,
184
+ ) -> None:
185
+ """Add a radio button to the group at runtime.
186
+
187
+ Args:
188
+ label: Display text for the button. Pass `''` with an `icon` for an
189
+ icon-only button.
190
+ value: Value assigned when this button is selected. Defaults to `label`.
191
+ icon: Optional icon spec rendered beside the label (alone when
192
+ `label` is blank).
193
+ disabled: If `True`, the option is dimmed and cannot be selected.
194
+ localize: Translation mode for this option's label, overriding the
195
+ group's `localize=`. Defaults to the group setting.
196
+ """
197
+ resolved = value if value is not None else label
198
+ add_kwargs, extras = self._option_render(icon, disabled, label)
199
+ item_localize = self._resolve_localize(override=localize)
200
+ if item_localize is not None:
201
+ add_kwargs["localize"] = item_localize
202
+ if localize is not None:
203
+ extras["localize"] = localize
204
+ self._internal.add(text=label, value=resolved, **add_kwargs, **kwargs)
205
+ self._add_option_record(label, resolved, extras)
206
+
207
+ # ----- Event shorthands -----
208
+
209
+ @overload
210
+ def on_change(self) -> Stream: ...
211
+ @overload
212
+ def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
213
+ def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
214
+ """Register a callback fired whenever the selection changes.
215
+
216
+ Args:
217
+ handler: Called with a :class:`~bootstack.events.ChangeEvent`; the
218
+ selected value is also available via `group.value`. Omit to get
219
+ a composable :class:`~bootstack.streams.Stream` instead.
220
+
221
+ Returns:
222
+ A cancellable :class:`~bootstack.events.Subscription` when a
223
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
224
+ """
225
+ return self.on("change", handler)
226
+
227
+
228
+ register_widget_events(RadioGroup, _RADIOGROUP_EVENTS)
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable
4
+
5
+ from bootstack.widgets._impl.primitives.scrollbar import Scrollbar as _InternalScrollbar
6
+ from bootstack.widgets._core.base import PublicWidgetBase
7
+ from bootstack.widgets.types import AccentToken, Orient, ScrollbarVariant
8
+
9
+
10
+ class Scrollbar(PublicWidgetBase):
11
+ """A themed scrollbar — horizontal or vertical.
12
+
13
+ Most users will never instantiate `Scrollbar` directly; scrollable
14
+ containers wire scrollbars automatically. Use this when you need an
15
+ explicit, manually managed scrollbar.
16
+
17
+ Args:
18
+ orient: `'vertical'` (default) or `'horizontal'`.
19
+ on_scroll: Callback invoked on scroll actions. Receives the scroll
20
+ arguments — `(fraction,)` for a move, or `(first, last)` for a drag.
21
+ accent: Accent token for styling.
22
+ variant: Scrollbar style — `'default'` (standard rounded bar) or
23
+ `'thin'` (slim square bar).
24
+ parent: Override the context-stack parent.
25
+ **kwargs: Layout placement options applied by the parent container —
26
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
27
+ See :doc:`/tasks/layout`.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ orient: Orient = "vertical",
33
+ *,
34
+ on_scroll: Callable | None = None,
35
+ accent: AccentToken | str | None = None,
36
+ variant: ScrollbarVariant = "default",
37
+ parent: Any = None,
38
+ **kwargs: Any,
39
+ ) -> None:
40
+ self._parent = self._resolve_parent(parent)
41
+ layout_kw = self._split_layout_kwargs(kwargs)
42
+
43
+ tk_master = self._parent._child_master() if self._parent else None
44
+
45
+ internal_kwargs: dict[str, Any] = {"orient": orient}
46
+ if on_scroll is not None:
47
+ internal_kwargs["command"] = on_scroll
48
+ if accent is not None:
49
+ internal_kwargs["accent"] = accent
50
+ if variant is not None:
51
+ internal_kwargs["variant"] = variant
52
+ # **kwargs is layout-only (split out above) — not forwarded to the internal.
53
+
54
+ self._internal = _InternalScrollbar(tk_master, **internal_kwargs)
55
+ self._attach_to_parent(layout_kw)
56
+
57
+ @property
58
+ def position(self) -> tuple[float, float]:
59
+ """Current scroll position as a `(first, last)` fraction pair."""
60
+ return self._internal.get()
61
+
62
+ @position.setter
63
+ def position(self, value: tuple[float, float]) -> None:
64
+ self._internal.set(*value)
@@ -0,0 +1,186 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import overload, Any, Callable, Literal
4
+
5
+ from bootstack.widgets._impl.composites.scrollview import ScrollView as _InternalScrollView
6
+ from bootstack.widgets._impl.primitives.flexframe import FlexFrame
7
+ from bootstack.widgets._core.base import adapt_handler
8
+ from bootstack.widgets._core.container import FlexContainer
9
+ from bootstack.events import ScrollEvent, Subscription
10
+ from bootstack.streams import Stream
11
+ from bootstack.widgets.types import Padding, ScrollbarVariant
12
+
13
+
14
+ class ScrollView(FlexContainer):
15
+ """A canvas-based scrollable container.
16
+
17
+ Place children inside the context block; they are laid out vertically
18
+ inside the scrollable area. Mouse-wheel scrolling is automatically
19
+ enabled for all descendants.
20
+
21
+ Args:
22
+ scroll_direction: Which axis to scroll. Defaults to `'both'`.
23
+ scrollbar_visibility: When scrollbars appear — `'always'` (default),
24
+ `'never'`, `'hover'` (appears on mouse enter), or `'scroll'`
25
+ (auto-hides after `autohide_delay` ms of inactivity).
26
+ scrollbar_variant: Scrollbar style — `'default'` (standard rounded bar)
27
+ or `'thin'` (slim square bar).
28
+ autohide_delay: Milliseconds before scrollbars hide in `'scroll'` mode.
29
+ Defaults to `1000`.
30
+ height: Fixed height of the viewport in pixels. When set, the
31
+ canvas is pinned to this height regardless of content size.
32
+ Required for vertical scrolling unless the parent already
33
+ constrains the height. Defaults to `None`.
34
+ width: Fixed width of the viewport in pixels. Pins the canvas
35
+ width, similar to `height=`. Defaults to `None`.
36
+ show_border: Draw a 1 px border around the ScrollView frame.
37
+ Defaults to `False`.
38
+ padding: Space in pixels between the border and the canvas.
39
+ Defaults to `None` (no padding).
40
+ parent: Override the context-stack parent.
41
+ **kwargs: Layout placement options applied by the parent container —
42
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
43
+ See :doc:`/tasks/layout`.
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ *,
49
+ scroll_direction: Literal["vertical", "horizontal", "both"] = "both",
50
+ scrollbar_visibility: Literal["always", "never", "hover", "scroll"] = "always",
51
+ scrollbar_variant: ScrollbarVariant = "default",
52
+ autohide_delay: int = 1000,
53
+ height: int | None = None,
54
+ width: int | None = None,
55
+ show_border: bool = False,
56
+ padding: Padding | None = None,
57
+ parent: Any = None,
58
+ **kwargs: Any,
59
+ ) -> None:
60
+ self._parent = self._resolve_parent(parent)
61
+ layout_kw = self._split_layout_kwargs(kwargs)
62
+ tk_master = self._parent._child_master() if self._parent else None
63
+
64
+ internal_kwargs: dict[str, Any] = {
65
+ "scroll_direction": scroll_direction,
66
+ "scrollbar_visibility": scrollbar_visibility,
67
+ "scrollbar_variant": scrollbar_variant,
68
+ "autohide_delay": autohide_delay,
69
+ }
70
+ if height is not None:
71
+ internal_kwargs["height"] = height
72
+ if width is not None:
73
+ internal_kwargs["width"] = width
74
+ if show_border:
75
+ internal_kwargs["show_border"] = True
76
+ if padding is not None:
77
+ internal_kwargs["padding"] = padding
78
+
79
+ self._internal = _InternalScrollView(tk_master, **internal_kwargs)
80
+ # The scrollable content is a vertical flex frame inside the canvas, so
81
+ # children flow with the Row/Column vocabulary (grow / horizontal /
82
+ # vertical). It stretches children across the width by default — a scroll
83
+ # area fills its viewport width and scrolls overflow vertically — so
84
+ # content does not sit narrow against the left edge.
85
+ self._content_frame = FlexFrame(
86
+ self._internal.canvas, direction="vertical", horizontal_items="stretch"
87
+ )
88
+ self._internal.add(self._content_frame)
89
+ self._attach_to_parent(layout_kw)
90
+
91
+ @property
92
+ def _flex_frame(self) -> Any:
93
+ return self._content_frame
94
+
95
+ # ----- Scrolling -----
96
+
97
+ def enable_scrolling(self) -> None:
98
+ """Enable mouse-wheel scrolling on the canvas and all children."""
99
+ self._internal.enable_scrolling()
100
+
101
+ def disable_scrolling(self) -> None:
102
+ """Disable mouse-wheel scrolling on the canvas and all children."""
103
+ self._internal.disable_scrolling()
104
+
105
+ def refresh_bindings(self) -> None:
106
+ """Refresh scroll bindings after dynamically adding many children."""
107
+ self._internal.refresh_bindings()
108
+
109
+ def scroll_to_top(self) -> None:
110
+ """Scroll to the top of the content."""
111
+ self._internal.yview_moveto(0.0)
112
+
113
+ def scroll_to_bottom(self) -> None:
114
+ """Scroll to the bottom of the content."""
115
+ self._internal.yview_moveto(1.0)
116
+
117
+ def scroll_to_left(self) -> None:
118
+ """Scroll to the left edge of the content."""
119
+ self._internal.xview_moveto(0.0)
120
+
121
+ def scroll_to_right(self) -> None:
122
+ """Scroll to the right edge of the content."""
123
+ self._internal.xview_moveto(1.0)
124
+
125
+ def yview_moveto(self, fraction: float) -> None:
126
+ """Scroll to a vertical position.
127
+
128
+ Args:
129
+ fraction: 0.0 (top) to 1.0 (bottom).
130
+ """
131
+ self._internal.yview_moveto(fraction)
132
+
133
+ def xview_moveto(self, fraction: float) -> None:
134
+ """Scroll to a horizontal position.
135
+
136
+ Args:
137
+ fraction: 0.0 (left) to 1.0 (right).
138
+ """
139
+ self._internal.xview_moveto(fraction)
140
+
141
+ @property
142
+ def scroll_position(self) -> tuple[float, float]:
143
+ """Current scroll position as `(y, x)` fractions.
144
+
145
+ Each value is the fraction of the content that lies above (`y`) or to the
146
+ left (`x`) of the viewport's top-left corner. `(0.0, 0.0)` means scrolled
147
+ fully to the top-left. As you scroll toward the end the value grows toward
148
+ `1.0` but does not reach it while content fills the viewport (it tops out
149
+ at roughly `1.0` minus the visible fraction) — only an axis whose content
150
+ is shorter than the viewport reads `0.0`. Read this inside an `on_scroll`
151
+ handler to get the new position:
152
+
153
+ sv.on_scroll(lambda e: print(e.y, e.x))
154
+ """
155
+ return self._internal.scroll_position
156
+
157
+ # ----- Events -----
158
+
159
+ @overload
160
+ def on_scroll(self) -> Stream: ...
161
+ @overload
162
+ def on_scroll(self, handler: Callable[[ScrollEvent], Any]) -> Subscription: ...
163
+ def on_scroll(self, handler: Callable[[ScrollEvent], Any] | None = None) -> Stream | Subscription:
164
+ """Register a callback fired whenever the viewport position changes.
165
+
166
+ Fires on mouse-wheel scrolls, programmatic `scroll_to_*` / `yview_moveto`
167
+ calls, and keyboard scrolling (arrow keys / Page Up / Page Down / Home /
168
+ End when the canvas has focus from a click).
169
+
170
+ Args:
171
+ handler: Called with a :class:`~bootstack.events.ScrollEvent` carrying
172
+ `y` (vertical fraction, 0.0–1.0) and `x` (horizontal fraction).
173
+ Omit to get a composable :class:`~bootstack.streams.Stream`.
174
+
175
+ Returns:
176
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
177
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
178
+ """
179
+ sequence = "<<BsScroll>>"
180
+ if handler is None:
181
+ def _source(h):
182
+ bid = self._internal.bind(sequence, adapt_handler(h), add="+")
183
+ return Subscription(self._internal, sequence, bid)
184
+ return Stream(self._internal, _source=_source)
185
+ bid = self._internal.bind(sequence, adapt_handler(handler), add="+")
186
+ return Subscription(self._internal, sequence, bid)