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,482 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter
4
+ from typing import Any, Callable, overload
5
+
6
+ from bootstack.widgets._impl.primitives.panedwindow import PanedWindow as _InternalPanedWindow
7
+ from bootstack.widgets._impl.primitives.frame import Frame as _InternalFrame
8
+ from bootstack.widgets._impl.primitives.flexframe import FlexFrame
9
+ from bootstack.widgets._impl.primitives.gridframe import GridFrame
10
+ from bootstack.widgets._core.base import PublicWidgetBase, adapt_handler
11
+ from bootstack.widgets._core.container import (
12
+ GRID_KEYS, grid_sticky, place_flex_child, _reject_legacy_child_kwargs,
13
+ _expand_margin,
14
+ )
15
+ from bootstack.widgets._core.context import push_container, pop_container
16
+ from bootstack.events import SashMoveEvent, Subscription
17
+ from bootstack.streams import Stream
18
+ from bootstack.widgets.types import (
19
+ AccentToken, SurfaceToken, Padding, LayoutKind, AutoFlow, Orient,
20
+ )
21
+
22
+
23
+ class SplitPane:
24
+ """A handle for one split pane — both a layout context and a live controller.
25
+
26
+ Returned by `SplitView.add()` and by `SplitView.item()` / `panes`. Use it as a
27
+ `with` block to place child widgets inside the pane, and read or set `key` /
28
+ `weight` or call `remove()` to inspect, resize, or drop the pane afterward.
29
+
30
+ As a context manager it accepts the same layout kwargs as the standalone
31
+ layout containers — `layout=`, `gap=`, `horizontal_items=`, `vertical_items=`,
32
+ `grow_items=`, `columns=`, `rows=`, `auto_flow=`.
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ frame: tkinter.Widget,
38
+ *,
39
+ key: str,
40
+ paned: _InternalPanedWindow,
41
+ owner: "SplitView",
42
+ layout: LayoutKind = "column",
43
+ padding: Padding | None = None,
44
+ gap: int = 0,
45
+ horizontal_items: str | None = None,
46
+ vertical_items: str | None = None,
47
+ grow_items: bool = False,
48
+ columns: int | list[int | str] | None = None,
49
+ rows: int | list[int | str] | None = None,
50
+ auto_flow: AutoFlow = "row",
51
+ ) -> None:
52
+ self._frame = frame
53
+ self._key = key
54
+ self._paned = paned
55
+ self._owner = owner
56
+ self._layout = layout
57
+
58
+ if horizontal_items is None:
59
+ horizontal_items = (
60
+ "stretch" if layout == "grid"
61
+ else "center" if layout == "column" else "left"
62
+ )
63
+ if vertical_items is None:
64
+ vertical_items = (
65
+ "stretch" if layout == "grid"
66
+ else "center" if layout == "row" else "top"
67
+ )
68
+
69
+ if layout in ("column", "row"):
70
+ self._layout_frame = FlexFrame(
71
+ frame,
72
+ direction="vertical" if layout == "column" else "horizontal",
73
+ padding=padding,
74
+ gap=gap,
75
+ horizontal_items=horizontal_items,
76
+ vertical_items=vertical_items,
77
+ grow_items=grow_items,
78
+ )
79
+ elif layout == "grid":
80
+ self._layout_frame = GridFrame(
81
+ frame,
82
+ columns=columns,
83
+ rows=rows,
84
+ padding=padding,
85
+ gap=gap,
86
+ auto_flow=auto_flow,
87
+ )
88
+ else:
89
+ raise ValueError(
90
+ f"SplitPane layout must be 'column', 'row', or 'grid', got {layout!r}"
91
+ )
92
+
93
+ self._horizontal_items = horizontal_items
94
+ self._vertical_items = vertical_items
95
+ self._layout_frame.pack(fill="both", expand=True)
96
+
97
+ def _child_master(self) -> tkinter.Widget:
98
+ return self._layout_frame
99
+
100
+ def guide_layout(self, child: PublicWidgetBase, **layout_kw: Any) -> None:
101
+ if self._layout == "grid":
102
+ _reject_legacy_child_kwargs(layout_kw, "SplitPane")
103
+ _expand_margin(layout_kw)
104
+ options = {k: v for k, v in layout_kw.items() if k in GRID_KEYS}
105
+ h = layout_kw.get("horizontal") or self._horizontal_items
106
+ v = layout_kw.get("vertical") or self._vertical_items
107
+ options["sticky"] = grid_sticky(h, v)
108
+ child._internal.grid(in_=self._child_master(), **options)
109
+ return
110
+ place_flex_child(self._layout_frame, child, layout_kw, "SplitPane")
111
+
112
+ # ----- Pane identity, weight, and removal -----
113
+
114
+ @property
115
+ def key(self) -> str:
116
+ """The pane's unique key, used with `SplitView.item()` / `remove()`."""
117
+ return self._key
118
+
119
+ @property
120
+ def weight(self) -> int:
121
+ """Relative resize weight — panes with a higher weight take proportionally
122
+ more space when the container grows. A pane with `weight=0` keeps its size
123
+ and does not grow (a fixed pane). Assigning a new value resizes live.
124
+ """
125
+ return int(self._paned.pane(self._frame, "weight"))
126
+
127
+ @weight.setter
128
+ def weight(self, value: int) -> None:
129
+ self._paned.pane(self._frame, weight=value)
130
+
131
+ def remove(self) -> None:
132
+ """Remove this pane and its content from the split view."""
133
+ self._owner.remove(self._key)
134
+
135
+ def __enter__(self) -> "SplitPane":
136
+ push_container(self)
137
+ return self
138
+
139
+ def __exit__(self, exc_type, exc, tb) -> None:
140
+ pop_container(self)
141
+
142
+
143
+ class SplitView(PublicWidgetBase):
144
+ """A resizable split container with panes separated by draggable sashes.
145
+
146
+ Add panes with `.add()` and place children inside each pane using the
147
+ returned `SplitPane` handle. Sashes between panes can be dragged at runtime
148
+ to resize them. Panes are addressable by key — enumerate them with `panes`,
149
+ look one up with `item()`, place a new one next to another with
150
+ `add(before=)` / `add(after=)`, reorder with `move()`, and drop one with
151
+ `remove()`.
152
+
153
+ Args:
154
+ orient: Pane arrangement — `'horizontal'` (side-by-side) or
155
+ `'vertical'` (stacked top-to-bottom). Defaults to `'horizontal'`.
156
+ padding: Space in pixels between the outer border and the pane area.
157
+ A single value applies to all sides; `(x, y)` sets the horizontal
158
+ and vertical amounts. Defaults to `None`.
159
+ accent: Color intent token applied to the sash. When omitted, the
160
+ sash uses the theme's default color.
161
+ surface: Surface token for the pane background.
162
+ sash_thickness: Width (horizontal split) or height (vertical split)
163
+ of the draggable sash in pixels. Defaults to the theme's
164
+ standard sash size (typically 6 px).
165
+ width: Requested width in pixels.
166
+ height: Requested height in pixels.
167
+ parent: Override the context-stack parent.
168
+ **kwargs: Layout placement options applied by the parent container —
169
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
170
+ See :doc:`/tasks/layout`.
171
+ """
172
+
173
+ def __init__(
174
+ self,
175
+ orient: Orient = "horizontal",
176
+ *,
177
+ padding: Padding | None = None,
178
+ accent: AccentToken | str | None = None,
179
+ surface: SurfaceToken | str | None = None,
180
+ sash_thickness: int | None = None,
181
+ width: int | None = None,
182
+ height: int | None = None,
183
+ parent: Any = None,
184
+ **kwargs: Any,
185
+ ) -> None:
186
+ self._parent = self._resolve_parent(parent)
187
+ layout_kw = self._split_layout_kwargs(kwargs)
188
+
189
+ pw_kwargs: dict[str, Any] = {"orient": orient}
190
+ if padding is not None:
191
+ pw_kwargs["padding"] = padding
192
+ if accent is not None:
193
+ pw_kwargs["accent"] = accent
194
+ if surface is not None:
195
+ pw_kwargs["surface"] = surface
196
+ if width is not None:
197
+ pw_kwargs["width"] = width
198
+ if height is not None:
199
+ pw_kwargs["height"] = height
200
+ if sash_thickness is not None:
201
+ pw_kwargs["style_options"] = {"sash_thickness": sash_thickness}
202
+
203
+ self._panes: dict[str, SplitPane] = {}
204
+ self._pane_counter = 0
205
+ self._dragging_sash: int | None = None
206
+
207
+ tk_master = self._parent._child_master() if self._parent else None
208
+ self._internal = _InternalPanedWindow(tk_master, **pw_kwargs)
209
+ self._setup_sash_bindings()
210
+ self._attach_to_parent(layout_kw)
211
+
212
+ # ----- Pane management -----
213
+
214
+ def add(
215
+ self,
216
+ key: str | None = None,
217
+ *,
218
+ before: str | None = None,
219
+ after: str | None = None,
220
+ weight: int = 1,
221
+ layout: LayoutKind = "column",
222
+ padding: Padding | None = None,
223
+ gap: int = 0,
224
+ horizontal_items: str | None = None,
225
+ vertical_items: str | None = None,
226
+ grow_items: bool = False,
227
+ columns: int | list[int | str] | None = None,
228
+ rows: int | list[int | str] | None = None,
229
+ auto_flow: AutoFlow = "row",
230
+ ) -> SplitPane:
231
+ """Add a pane and return a handle for placing its children and controlling it.
232
+
233
+ Panes are appended in order by default. Pass `before=` or `after=` (the
234
+ key of an existing pane) to place the new pane next to another one.
235
+
236
+ Args:
237
+ key: Unique identifier for the pane, used with `item()`, `move()`,
238
+ and `remove()`. Auto-generated when omitted.
239
+ before: Place the new pane immediately before the pane with this key.
240
+ after: Place the new pane immediately after the pane with this key.
241
+ Mutually exclusive with `before`; omit both to append at the end.
242
+ weight: Relative size weight when the container is resized. Panes
243
+ with a higher weight take proportionally more space. Settable
244
+ afterward via the pane's `weight` property. Defaults to `1`.
245
+ layout: Internal pane layout. Defaults to `'column'`.
246
+ padding: Space in pixels inside the pane border. Defaults to `None`.
247
+ gap: Space in pixels between children. Defaults to `0`.
248
+ horizontal_items: How children sit on the horizontal axis — edge
249
+ values `'left'`/`'center'`/`'right'`/`'stretch'`, plus `'space-*'`
250
+ when horizontal is the stacking axis. Defaults to `'stretch'` in grid mode,
251
+ `'center'` in a column and `'left'` in a row.
252
+ vertical_items: How children sit on the vertical axis — edge values
253
+ `'top'`/`'center'`/`'bottom'`/`'stretch'`, plus `'space-*'` when
254
+ vertical is the stacking axis. Defaults to `'stretch'` in grid mode,
255
+ `'center'` in a row and `'top'` in a column.
256
+ grow_items: For `'column'`/`'row'`, when `True` every child grows
257
+ equally to share the main axis. Defaults to `False`.
258
+ columns: Column definitions for `'grid'` layout. An integer sets
259
+ the number of equal-weight columns; a list sets per-column
260
+ weights or sizes (e.g. `[1, 2, 'auto', '120px']`).
261
+ rows: Row definitions for `'grid'` layout, same format as `columns`.
262
+ auto_flow: Grid auto-flow direction. Defaults to `'row'`.
263
+
264
+ Returns:
265
+ `SplitPane` — use as a context manager to place children, and as a
266
+ handle to read/set `weight` or `remove()` the pane.
267
+
268
+ Raises:
269
+ KeyError: If `before`/`after` names a pane that does not exist.
270
+ ValueError: If both `before` and `after` are given.
271
+ """
272
+ index = self._resolve_placement(before, after)
273
+ return self._create_pane(
274
+ index=index, key=key, weight=weight,
275
+ layout=layout, padding=padding, gap=gap,
276
+ horizontal_items=horizontal_items, vertical_items=vertical_items,
277
+ grow_items=grow_items,
278
+ columns=columns, rows=rows, auto_flow=auto_flow,
279
+ )
280
+
281
+ def move(self, key: str, *, before: str | None = None, after: str | None = None) -> None:
282
+ """Move an existing pane next to another pane.
283
+
284
+ Args:
285
+ key: The key of the pane to move.
286
+ before: Move it immediately before the pane with this key.
287
+ after: Move it immediately after the pane with this key. Pass
288
+ exactly one of `before` or `after`.
289
+
290
+ Raises:
291
+ KeyError: If `key`, `before`, or `after` names a pane that does
292
+ not exist.
293
+ ValueError: If not exactly one of `before`/`after` is given, or if
294
+ the anchor is the pane being moved.
295
+ """
296
+ if key not in self._panes:
297
+ raise KeyError(f"no pane with key {key!r}")
298
+ if (before is None) == (after is None):
299
+ raise ValueError("pass exactly one of before= or after=")
300
+ anchor = before if before is not None else after
301
+ if anchor == key:
302
+ raise ValueError(f"cannot move pane {key!r} relative to itself")
303
+ # The target index is computed against the order with the moved pane
304
+ # removed, which is how ttk's insert(pos, existing) re-seats it.
305
+ order = [k for k in self._panes if k != key]
306
+ if anchor not in order:
307
+ raise KeyError(f"no pane with key {anchor!r}")
308
+ pos = order.index(anchor)
309
+ index = pos if before is not None else pos + 1
310
+ self._internal.insert(index, self._panes[key]._frame)
311
+ self._resync_order()
312
+
313
+ def _resolve_placement(self, before: str | None, after: str | None) -> int | None:
314
+ """Translate a `before=`/`after=` key into an insertion index for a new pane.
315
+
316
+ Returns `None` to append (the default, and the case where the anchor is
317
+ the last pane — ttk cannot target the slot past the final pane).
318
+ """
319
+ if before is not None and after is not None:
320
+ raise ValueError("pass only one of before= or after=, not both")
321
+ if before is None and after is None:
322
+ return None
323
+ anchor = before if before is not None else after
324
+ order = list(self._panes)
325
+ if anchor not in order:
326
+ raise KeyError(f"no pane with key {anchor!r}")
327
+ pos = order.index(anchor)
328
+ index = pos if before is not None else pos + 1
329
+ return index if index < len(order) else None
330
+
331
+ def _create_pane(self, *, index: int | None, key: str | None, weight: int, **layout_kw: Any) -> SplitPane:
332
+ if key is None:
333
+ key = f"pane_{self._pane_counter}"
334
+ self._pane_counter += 1
335
+ if key in self._panes:
336
+ raise ValueError(f"A pane with the key {key!r} already exists.")
337
+
338
+ frame = _InternalFrame(self._internal)
339
+ if index is None:
340
+ self._internal.add(frame, weight=weight)
341
+ else:
342
+ self._internal.insert(index, frame, weight=weight)
343
+ pane = SplitPane(frame, key=key, paned=self._internal, owner=self, **layout_kw)
344
+ self._panes[key] = pane
345
+ if index is not None:
346
+ self._resync_order()
347
+ return pane
348
+
349
+ def _resync_order(self) -> None:
350
+ """Reorder `_panes` to match the live pane order after an insert/move."""
351
+ by_frame = {str(p._frame): (k, p) for k, p in self._panes.items()}
352
+ self._panes = dict(
353
+ by_frame[fid] for fid in self._internal.panes() if fid in by_frame
354
+ )
355
+
356
+ @property
357
+ def panes(self) -> tuple[SplitPane, ...]:
358
+ """All panes in left-to-right (or top-to-bottom) order."""
359
+ return tuple(self._panes.values())
360
+
361
+ def __len__(self) -> int:
362
+ return len(self._panes)
363
+
364
+ def keys(self) -> tuple[str, ...]:
365
+ """All pane keys in order."""
366
+ return tuple(self._panes)
367
+
368
+ def item(self, key: str) -> SplitPane:
369
+ """Return the pane handle for `key`.
370
+
371
+ Args:
372
+ key: Pane key.
373
+
374
+ Returns:
375
+ The `SplitPane` for that key — read/set its `weight` or `remove()` it.
376
+ """
377
+ return self._panes[key]
378
+
379
+ def remove(self, key: str) -> None:
380
+ """Remove a pane and destroy its content.
381
+
382
+ Args:
383
+ key: The key assigned when the pane was added.
384
+ """
385
+ pane = self._panes.pop(key)
386
+ # `WidgetCapabilitiesMixin.forget()` shadows `ttk.Panedwindow.forget(pane)`
387
+ # with a zero-arg method, so invoke the underlying Tk command directly.
388
+ self._internal.tk.call(str(self._internal), "forget", pane._frame)
389
+ pane._frame.destroy()
390
+
391
+ # ----- Sash control -----
392
+
393
+ @property
394
+ def sash_positions(self) -> list[int]:
395
+ """Current position of every sash in pixels, in order."""
396
+ n = len(self._internal.panes())
397
+ return [self._internal.sashpos(i) for i in range(max(0, n - 1))]
398
+
399
+ def sash_position(self, index: int, position: int | None = None) -> int | None:
400
+ """Get or set the position of a sash.
401
+
402
+ Args:
403
+ index: Zero-based sash index. A split view with `n` panes has
404
+ `n - 1` sashes.
405
+ position: New position in pixels. If `None`, returns the
406
+ current position.
407
+
408
+ Returns:
409
+ Current sash position in pixels when `position` is `None`.
410
+
411
+ Raises:
412
+ IndexError: If `index` does not refer to an existing sash.
413
+ """
414
+ n_sashes = max(0, len(self._internal.panes()) - 1)
415
+ if not 0 <= index < n_sashes:
416
+ if n_sashes == 0:
417
+ raise IndexError(
418
+ f"sash index {index} out of range: this split view has no "
419
+ f"sashes yet (add at least two panes)"
420
+ )
421
+ raise IndexError(
422
+ f"sash index {index} out of range: valid sash indices are "
423
+ f"0..{n_sashes - 1}"
424
+ )
425
+ if position is None:
426
+ return self._internal.sashpos(index)
427
+ self._internal.sashpos(index, position)
428
+ return None
429
+
430
+ def _setup_sash_bindings(self) -> None:
431
+ pw = self._internal
432
+ pw.bind("<Button-1>", self._on_sash_press, add="+")
433
+ pw.bind("<ButtonRelease-1>", self._on_sash_release, add="+")
434
+
435
+ def _on_sash_press(self, event: Any) -> None:
436
+ result = self._internal.identify(event.x, event.y)
437
+ if result != "":
438
+ try:
439
+ self._dragging_sash = int(result)
440
+ except (ValueError, TypeError):
441
+ self._dragging_sash = None
442
+
443
+ def _on_sash_release(self, event: Any) -> None:
444
+ idx = self._dragging_sash
445
+ self._dragging_sash = None
446
+ if idx is None:
447
+ return
448
+ try:
449
+ pos = self._internal.sashpos(idx)
450
+ except Exception:
451
+ return
452
+ self._internal.event_generate(
453
+ "<<BsSashMoved>>", data=SashMoveEvent(index=idx, position=pos)
454
+ )
455
+
456
+ # ----- Events -----
457
+
458
+ @overload
459
+ def on_sash_moved(self) -> Stream: ...
460
+ @overload
461
+ def on_sash_moved(self, handler: Callable[[SashMoveEvent], Any]) -> Subscription: ...
462
+ def on_sash_moved(self, handler: Callable[[SashMoveEvent], Any] | None = None) -> Stream | Subscription:
463
+ """Register a callback fired when a sash drag is released.
464
+
465
+ Args:
466
+ handler: Called with a :class:`~bootstack.events.SashMoveEvent`
467
+ carrying `index` (zero-based sash index) and `position` (pixel
468
+ offset from the leading edge). Omit to get a composable
469
+ :class:`~bootstack.streams.Stream`.
470
+
471
+ Returns:
472
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
473
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
474
+ """
475
+ sequence = "<<BsSashMoved>>"
476
+ if handler is None:
477
+ def _source(h):
478
+ bid = self._internal.bind(sequence, adapt_handler(h), add="+")
479
+ return Subscription(self._internal, sequence, bid)
480
+ return Stream(self._internal, _source=_source)
481
+ bid = self._internal.bind(sequence, adapt_handler(handler), add="+")
482
+ return Subscription(self._internal, sequence, bid)