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,200 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Callable, Mapping, Sequence, overload
4
+
5
+ from bootstack.constants import DEFAULT_MIN_COL_WIDTH
6
+ from bootstack.widgets._impl.composites.form import Form as _InternalForm
7
+ from bootstack.widgets._impl.composites.form import (
8
+ FormItem, FieldItem, GroupItem, TabsItem, TabItem,
9
+ )
10
+ from bootstack.widgets._core.base import PublicWidgetBase
11
+ from bootstack.widgets._core.events import register_widget_events
12
+ from bootstack.streams import Stream
13
+ from bootstack.events import Subscription
14
+ from bootstack.signals import Signal
15
+ from bootstack.widgets.types import AccentToken
16
+
17
+ if TYPE_CHECKING:
18
+ from bootstack.dialogs import DialogButton
19
+
20
+
21
+ class Form(PublicWidgetBase):
22
+ """Data-entry form built from data or explicit field definitions.
23
+
24
+ Args:
25
+ data: Initial data dict. Keys become field names; types are inferred
26
+ when `items` is not provided.
27
+ items: Explicit form layout as `FieldItem`/`GroupItem`/`TabsItem`
28
+ instances or equivalent dicts.
29
+ col_count: Number of top-level columns. Default `1`.
30
+ min_col_width: Minimum column width in pixels.
31
+ on_data_change: Callback invoked with the updated data dict on each
32
+ field change. Equivalent to calling `on_data_change()` after
33
+ construction.
34
+ width: Fixed form width in pixels.
35
+ height: Fixed form height in pixels.
36
+ accent: Accent token for the form container.
37
+ buttons: Footer buttons — strings, `DialogButton` instances, or dicts.
38
+ parent: Override the context-stack parent.
39
+ **kwargs: Layout placement options applied by the parent container —
40
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
41
+ See :doc:`/tasks/layout`.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ *,
47
+ data: dict[str, Any] | None = None,
48
+ items: Sequence[FormItem | Mapping[str, Any]] | None = None,
49
+ col_count: int = 1,
50
+ min_col_width: int = DEFAULT_MIN_COL_WIDTH,
51
+ on_data_change: Callable[[dict[str, Any]], Any] | None = None,
52
+ width: int | None = None,
53
+ height: int | None = None,
54
+ accent: AccentToken | str | None = None,
55
+ buttons: Sequence[str | DialogButton | dict[str, Any]] | None = None,
56
+ parent: Any = None,
57
+ **kwargs: Any,
58
+ ) -> None:
59
+ self._parent = self._resolve_parent(parent)
60
+ layout_kw = self._split_layout_kwargs(kwargs)
61
+ tk_master = self._parent._child_master() if self._parent else None
62
+
63
+ kw: dict[str, Any] = {"col_count": col_count, "min_col_width": min_col_width}
64
+ if data is not None:
65
+ kw["data"] = data
66
+ if items is not None:
67
+ kw["items"] = items
68
+ if on_data_change is not None:
69
+ kw["on_data_change"] = on_data_change
70
+ if width is not None:
71
+ kw["width"] = width
72
+ if height is not None:
73
+ kw["height"] = height
74
+ if accent is not None:
75
+ kw["accent"] = accent
76
+ if buttons is not None:
77
+ kw["buttons"] = buttons
78
+
79
+ self._internal = _InternalForm(tk_master, **kw)
80
+ self._attach_to_parent(layout_kw)
81
+
82
+ # ----- Value API -----
83
+
84
+ @property
85
+ def value(self) -> dict[str, Any]:
86
+ """All field values as a dictionary."""
87
+ return self._internal.value
88
+
89
+ @value.setter
90
+ def value(self, values: Mapping[str, Any]) -> None:
91
+ self._internal.value = values
92
+
93
+ @property
94
+ def data(self) -> dict[str, Any]:
95
+ """Current form data dict."""
96
+ return self._internal.data
97
+
98
+ @property
99
+ def result(self) -> Any:
100
+ """Value set by button commands. None until a button is pressed."""
101
+ return self._internal.result
102
+
103
+ def get(self) -> dict[str, Any]:
104
+ """Return all field values as a dictionary."""
105
+ return self._internal.get()
106
+
107
+ def set(self, values: Mapping[str, Any]) -> None:
108
+ """Set multiple field values from a dictionary."""
109
+ self._internal.set(values)
110
+
111
+ def validate(self) -> bool:
112
+ """Run validation rules; returns True if all fields pass."""
113
+ return self._internal.validate()
114
+
115
+ @property
116
+ def valid(self) -> Signal:
117
+ """Reactive `Signal[bool]` — `True` when every field passes validation.
118
+
119
+ AND-ed over the fields' own `valid` signals and recomputed whenever any
120
+ field's validity changes, so a submit button can react to it directly::
121
+
122
+ form = bs.Form(items=[...])
123
+ submit = bs.Button("Save", on_click=save)
124
+
125
+ def gate(ok):
126
+ submit.disabled = not ok
127
+
128
+ form.valid.subscribe(gate)
129
+
130
+ A field is valid until proven otherwise, so this reads `True` before any
131
+ rule has run — call `validate()` to force a full pass (for example, to
132
+ gate the submit button's initial state).
133
+ """
134
+ return self._internal.valid
135
+
136
+ @property
137
+ def errors(self) -> dict[str, str]:
138
+ """Current validation errors keyed by field key; empty when all valid.
139
+
140
+ Read live from the fields' `error` signals, so it always reflects the
141
+ latest validation run.
142
+ """
143
+ return self._internal.errors
144
+
145
+ # ----- Field access -----
146
+
147
+ def field(self, key: str) -> Any:
148
+ """Return the Field widget for the given key."""
149
+ return self._internal.field(key)
150
+
151
+ def fields(self) -> tuple:
152
+ """Return all field widgets in insertion order."""
153
+ return self._internal.fields()
154
+
155
+ def keys(self) -> tuple[str, ...]:
156
+ """Return all field keys in insertion order."""
157
+ return self._internal.keys()
158
+
159
+ def get_field_value(self, key: str) -> Any:
160
+ """Return the current value of the named field."""
161
+ return self._internal.get_field_value(key)
162
+
163
+ def set_field_value(self, key: str, value: Any) -> None:
164
+ """Set the value of the named field."""
165
+ self._internal.set_field_value(key, value)
166
+
167
+ # ----- Signal access -----
168
+
169
+ def field_signal(self, key: str) -> Any:
170
+ """Return the Signal for the named field value."""
171
+ return self._internal.field_signal(key)
172
+
173
+ def field_textsignal(self, key: str) -> Any:
174
+ """Return the text Signal for the named field."""
175
+ return self._internal.field_textsignal(key)
176
+
177
+ # ----- Events -----
178
+
179
+ @overload
180
+ def on_data_change(self) -> Stream: ...
181
+ @overload
182
+ def on_data_change(self, handler: Callable[[dict[str, Any]], Any]) -> Subscription: ...
183
+ def on_data_change(self, handler: Callable[[dict[str, Any]], Any] | None = None) -> Stream | Subscription:
184
+ """Register a callback fired whenever any field value changes.
185
+
186
+ The handler receives the current form data as a dict. Called with no
187
+ handler, returns a composable `Stream`.
188
+
189
+ Args:
190
+ handler: Called with the updated data dict on each field change.
191
+
192
+ Returns:
193
+ `Subscription` (with handler) or `Stream` (without handler).
194
+ """
195
+ if handler is None:
196
+ return self.on("data_change")
197
+ return self.on("data_change", lambda _e: handler(self.value))
198
+
199
+
200
+ register_widget_events(Form, {"data_change": "<<BsDataChange>>"})
@@ -0,0 +1,250 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Literal, overload, TYPE_CHECKING
4
+
5
+ from bootstack.events import Event, Subscription
6
+ from bootstack.streams import Stream
7
+ from bootstack.widgets._core.base import PublicWidgetBase
8
+ from bootstack.widgets._core.events import register_widget_events
9
+ from bootstack.widgets._impl.composites.gallery import Gallery as _InternalGallery
10
+ from bootstack.widgets.types import AccentToken, SelectionMode, SurfaceToken, ScrollbarVariant
11
+
12
+ if TYPE_CHECKING:
13
+ from bootstack.data.types import DataSourceProtocol
14
+
15
+ ImageFit = Literal["contain", "cover", "fill", "none", "scale-down"]
16
+
17
+
18
+ class Gallery(PublicWidgetBase):
19
+ """A scrollable, selectable grid of image thumbnails.
20
+
21
+ `Gallery` displays a collection of image records as a reflowing grid of
22
+ thumbnails, recycling tiles so it stays efficient for large collections. It
23
+ is record-native — populate it with `items=` (a list of dicts) or a
24
+ `data_source=`, and read the chosen tile(s) back through `selection`, the
25
+ same model `ListView`, `DataTable`, and `Tree` share.
26
+
27
+ Each record names the image to show through `image_field` (an `Image`, a file
28
+ path, or a PIL image) and, optionally, a caption through `caption_field`.
29
+ Other keys ride along in the record bag and are returned by `selection` and
30
+ events.
31
+
32
+ Args:
33
+ items: Initial list of record dicts.
34
+ data_source: A data source for database- or API-backed data. Any object
35
+ implementing the data-source protocol is accepted.
36
+ image_field: Record key holding the image to display in each tile — an
37
+ `Image` handle, a file path, or a PIL image. Default `'image'`.
38
+ caption_field: Record key for the caption shown under each thumbnail.
39
+ Default `None` (no captions).
40
+ columns: Number of columns, or `'auto'` (default) to reflow the grid to
41
+ fit the available width as it resizes.
42
+ tile_size: Thumbnail size as `(width, height)` in pixels. Default
43
+ `(160, 160)`.
44
+ fit: How each image is scaled into its tile — `'contain'`, `'cover'`
45
+ (default), `'fill'`, `'none'`, or `'scale-down'`. See `Picture`.
46
+ corner_radius: Rounded-corner radius for thumbnails, in pixels. Default
47
+ `0`.
48
+ gap: Spacing between tiles in pixels. Default `8`.
49
+ selection_mode: Tile selection behavior — `'single'`, `'multi'`, or
50
+ `'none'` (default). Selected tiles show an accent highlight ring.
51
+ scrollbar_variant: Scrollbar style — `'thin'` (default) for a slim bar,
52
+ or `'default'` for the standard rounded bar.
53
+ rows: Number of tile rows used to derive the gallery's minimum height
54
+ when it sits in a container that doesn't impose one (a scroll view,
55
+ an auto-fit window). Default `2`. `fill`/`expand` still grow the grid
56
+ past this floor and reflow more rows in.
57
+ accent: Color intent token for the selection ring. Defaults to the
58
+ theme's primary color.
59
+ surface: Background surface token shown behind the tiles.
60
+ parent: Override the context-stack parent.
61
+ **kwargs: Layout placement options applied by the parent container —
62
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
63
+ See :doc:`/tasks/layout`.
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ *,
69
+ items: list[dict] | None = None,
70
+ data_source: "DataSourceProtocol | None" = None,
71
+ image_field: str = "image",
72
+ caption_field: str | None = None,
73
+ columns: "int | Literal['auto']" = "auto",
74
+ tile_size: tuple[int, int] = (160, 160),
75
+ fit: ImageFit = "cover",
76
+ corner_radius: int = 0,
77
+ gap: int = 8,
78
+ selection_mode: SelectionMode = "none",
79
+ scrollbar_variant: ScrollbarVariant = "thin",
80
+ rows: int = 2,
81
+ accent: AccentToken | str | None = None,
82
+ surface: SurfaceToken | str | None = None,
83
+ parent: Any = None,
84
+ **kwargs: Any,
85
+ ) -> None:
86
+ self._parent = self._resolve_parent(parent)
87
+ self._selection_mode = selection_mode
88
+ layout_kw = self._split_layout_kwargs(kwargs)
89
+ tk_master = self._parent._child_master() if self._parent else None
90
+
91
+ internal_kwargs: dict[str, Any] = {
92
+ "image_field": image_field,
93
+ "caption_field": caption_field,
94
+ "columns": columns,
95
+ "tile_size": tile_size,
96
+ "fit": fit,
97
+ "corner_radius": corner_radius,
98
+ "gap": gap,
99
+ "selection_mode": selection_mode,
100
+ "scrollbar_variant": scrollbar_variant,
101
+ "rows": rows,
102
+ }
103
+ if items is not None:
104
+ internal_kwargs["items"] = items
105
+ if data_source is not None:
106
+ internal_kwargs["datasource"] = data_source
107
+ if accent is not None:
108
+ internal_kwargs["accent"] = accent
109
+ if surface is not None:
110
+ internal_kwargs["surface"] = surface
111
+
112
+ self._internal = _InternalGallery(tk_master, **internal_kwargs)
113
+ self._internal.pack_propagate(False)
114
+ self._attach_to_parent(layout_kw)
115
+
116
+ # ----- Selection -----
117
+
118
+ @property
119
+ def selection(self) -> dict | list[dict] | None:
120
+ """The selected record(s) — the data bag.
121
+
122
+ In `'single'` mode, the selected record `dict` (or `None`). In `'multi'`
123
+ mode, a `list` of record dicts (empty when nothing is selected). Each
124
+ record carries its non-displayed fields too. Read-only.
125
+ """
126
+ rows = self._internal.get_selected()
127
+ if self._selection_mode == "multi":
128
+ return rows
129
+ return rows[0] if rows else None
130
+
131
+ def select_items(self, record_ids: list) -> None:
132
+ """Select tiles by record `id`.
133
+
134
+ In `'single'` mode this replaces the current selection; in `'multi'`
135
+ mode the items are added to it.
136
+
137
+ Args:
138
+ record_ids: Record ids of the tiles to select.
139
+ """
140
+ self._internal.select_items(record_ids)
141
+
142
+ def deselect_items(self, record_ids: list) -> None:
143
+ """Remove the given tiles from the selection by record `id`.
144
+
145
+ Args:
146
+ record_ids: Record ids to deselect.
147
+ """
148
+ self._internal.deselect_items(record_ids)
149
+
150
+ def select_all(self) -> None:
151
+ """Select all tiles. Only effective when `selection_mode='multi'`."""
152
+ self._internal.select_all()
153
+
154
+ def clear_selection(self) -> None:
155
+ """Deselect all tiles."""
156
+ self._internal.clear_selection()
157
+
158
+ # ----- Scrolling -----
159
+
160
+ def scroll_to_top(self) -> None:
161
+ """Scroll to the first tile."""
162
+ self._internal.scroll_to_top()
163
+
164
+ def scroll_to_bottom(self) -> None:
165
+ """Scroll to the last tile."""
166
+ self._internal.scroll_to_bottom()
167
+
168
+ def reload(self) -> None:
169
+ """Reload from the data source and refresh the grid."""
170
+ self._internal.reload()
171
+
172
+ # ----- Properties -----
173
+
174
+ @property
175
+ def data_source(self) -> "DataSourceProtocol":
176
+ """The underlying data source instance."""
177
+ return self._internal.get_datasource()
178
+
179
+ # ----- Events -----
180
+
181
+ @overload
182
+ def on_item_click(self) -> Stream: ...
183
+ @overload
184
+ def on_item_click(self, handler: Callable[[dict[str, Any]], Any]) -> Subscription: ...
185
+ def on_item_click(
186
+ self, handler: Callable[[dict[str, Any]], Any] | None = None
187
+ ) -> Stream | Subscription:
188
+ """Fired when a tile is clicked.
189
+
190
+ Args:
191
+ handler: Called with the record `dict` — read fields with
192
+ `e["field"]`. Omit to get a composable
193
+ :class:`~bootstack.streams.Stream`.
194
+
195
+ Returns:
196
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
197
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
198
+ """
199
+ return self.on("item_click", handler)
200
+
201
+ @overload
202
+ def on_item_activate(self) -> Stream: ...
203
+ @overload
204
+ def on_item_activate(self, handler: Callable[[dict[str, Any]], Any]) -> Subscription: ...
205
+ def on_item_activate(
206
+ self, handler: Callable[[dict[str, Any]], Any] | None = None
207
+ ) -> Stream | Subscription:
208
+ """Fired when a tile is activated (double-clicked).
209
+
210
+ The natural hook for opening a full-size viewer or detail page for the
211
+ chosen record.
212
+
213
+ Args:
214
+ handler: Called with the record `dict`. Omit to get a composable
215
+ :class:`~bootstack.streams.Stream`.
216
+
217
+ Returns:
218
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
219
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
220
+ """
221
+ return self.on("item_activate", handler)
222
+
223
+ @overload
224
+ def on_select(self) -> Stream: ...
225
+ @overload
226
+ def on_select(self, handler: Callable[[Event], Any]) -> Subscription: ...
227
+ def on_select(
228
+ self, handler: Callable[[Event], Any] | None = None
229
+ ) -> Stream | Subscription:
230
+ """Fired when the selection changes.
231
+
232
+ Args:
233
+ handler: Called with the curated :class:`~bootstack.events.Event`;
234
+ read `selection` for the current selection. Omit to get a
235
+ composable :class:`~bootstack.streams.Stream`.
236
+
237
+ Returns:
238
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
239
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
240
+ """
241
+ return self.on("select", handler)
242
+
243
+
244
+ _GALLERY_EVENTS: dict[str, str] = {
245
+ "item_click": "<<ItemClick>>",
246
+ "item_activate": "<<ItemActivate>>",
247
+ "select": "<<SelectionChange>>",
248
+ }
249
+
250
+ register_widget_events(Gallery, _GALLERY_EVENTS)
@@ -0,0 +1,168 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, overload, Any, Callable
4
+
5
+ from bootstack.widgets._impl.composites.meter import Meter as _InternalMeter
6
+ from bootstack.widgets._core.base import PublicWidgetBase
7
+ from bootstack.widgets._core.events import register_widget_events
8
+ from bootstack.events import SliderEvent, Subscription
9
+ from bootstack.streams import Stream
10
+ from bootstack.widgets.types import AccentToken, Event, SurfaceToken
11
+
12
+
13
+ class Gauge(PublicWidgetBase):
14
+ """A circular gauge for displaying a value within a range.
15
+
16
+ Renders a circular arc gauge with an optional center text display. Supports
17
+ full-circle and semicircle layouts, solid and segmented arcs, and an
18
+ interactive drag mode for value input.
19
+
20
+ Args:
21
+ value: Initial value. Defaults to `0`.
22
+ min_value: Minimum of the range. Defaults to `0`.
23
+ max_value: Maximum of the range. Defaults to `100`.
24
+ subtitle: Secondary label displayed below the value text.
25
+ value_prefix: Text prepended to the displayed value (e.g. `'$'`).
26
+ value_suffix: Text appended to the displayed value (e.g. `'%'`).
27
+ value_template: Python format string applied to the value (e.g.
28
+ `'{:.0f}'`, `'{:.1f}'`). Pair with `value_prefix`/`value_suffix` for
29
+ units. Default `'{:.0f}'` (integer display). For locale-aware
30
+ formatting, format the value yourself and pass it via `value=`.
31
+ size: Diameter of the gauge in pixels. Defaults to `200`.
32
+ thickness: Width of the arc stroke in pixels. Defaults to `10`.
33
+ variant: Shape variant. `'full'` (default) draws a complete 360° ring;
34
+ `'semi'` draws a half-circle arc along the bottom.
35
+ segment_width: Width of each dash segment for a dashed arc style.
36
+ `0` (default) draws a solid arc.
37
+ interactive: If `True`, the user can click or drag the arc to change
38
+ the value. Defaults to `False`.
39
+ step: Value increment per drag step in interactive mode. Defaults to `1`.
40
+ show_text: If `False`, hides the center value text. Defaults to `True`.
41
+ accent: Color intent token for the arc. Defaults to the theme's default
42
+ color.
43
+ surface: Surface token for the gauge background, so it matches the
44
+ container it sits on (e.g. `'card'` inside a `Card`). Defaults to the
45
+ window background.
46
+ parent: Override the context-stack parent.
47
+ **kwargs: Layout placement options applied by the parent container —
48
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
49
+ See :doc:`/tasks/layout`.
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ value: int | float = 0,
55
+ *,
56
+ min_value: int | float = 0,
57
+ max_value: int | float = 100,
58
+ subtitle: str | None = None,
59
+ value_prefix: str | None = None,
60
+ value_suffix: str | None = None,
61
+ value_template: str = "{:.0f}",
62
+ size: int = 200,
63
+ thickness: int = 10,
64
+ variant: Literal["full", "semi"] = "full",
65
+ segment_width: int = 0,
66
+ interactive: bool = False,
67
+ step: int | float = 1,
68
+ show_text: bool = True,
69
+ accent: AccentToken | str | None = None,
70
+ surface: SurfaceToken | str | None = None,
71
+ parent: Any = None,
72
+ **kwargs: Any,
73
+ ) -> None:
74
+ self._parent = self._resolve_parent(parent)
75
+ layout_kw = self._split_layout_kwargs(kwargs)
76
+
77
+ tk_master = self._parent._child_master() if self._parent else None
78
+
79
+ internal_kwargs: dict[str, Any] = {
80
+ "value": value,
81
+ "minvalue": min_value,
82
+ "maxvalue": max_value,
83
+ "value_format": value_template,
84
+ "size": size,
85
+ "thickness": thickness,
86
+ "meter_type": variant,
87
+ "segment_width": segment_width,
88
+ "interactive": interactive,
89
+ "step_size": step,
90
+ "show_text": show_text,
91
+ }
92
+ if subtitle is not None:
93
+ internal_kwargs["subtitle"] = subtitle
94
+ if value_prefix is not None:
95
+ internal_kwargs["value_prefix"] = value_prefix
96
+ if value_suffix is not None:
97
+ internal_kwargs["value_suffix"] = value_suffix
98
+ if accent is not None:
99
+ internal_kwargs["accent"] = accent
100
+ if surface is not None:
101
+ internal_kwargs["surface"] = surface
102
+
103
+ self._internal = _InternalMeter(tk_master, **internal_kwargs)
104
+ self._attach_to_parent(layout_kw)
105
+
106
+ # ----- Properties -----
107
+
108
+ @property
109
+ def value(self) -> int | float:
110
+ """The current gauge value."""
111
+ return self._internal.value
112
+
113
+ @value.setter
114
+ def value(self, v: int | float) -> None:
115
+ self._internal.value = v
116
+
117
+ @property
118
+ def subtitle(self) -> str:
119
+ """The secondary label shown below the value text."""
120
+ return self._internal.subtitle
121
+
122
+ @subtitle.setter
123
+ def subtitle(self, v: str) -> None:
124
+ self._internal.subtitle = v
125
+
126
+ @property
127
+ def min_value(self) -> int | float:
128
+ """The low end of the range. Assigning to it rescales the gauge live."""
129
+ return self._internal.cget("minvalue")
130
+
131
+ @min_value.setter
132
+ def min_value(self, v: int | float) -> None:
133
+ self._internal.configure(minvalue=v)
134
+
135
+ @property
136
+ def max_value(self) -> int | float:
137
+ """The high end of the range. Assigning to it rescales the gauge live."""
138
+ return self._internal.cget("maxvalue")
139
+
140
+ @max_value.setter
141
+ def max_value(self, v: int | float) -> None:
142
+ self._internal.configure(maxvalue=v)
143
+
144
+ # ----- Event shorthands -----
145
+
146
+ @overload
147
+ def on_change(self) -> Stream: ...
148
+ @overload
149
+ def on_change(self, handler: Callable[[SliderEvent], Any]) -> Subscription: ...
150
+ def on_change(self, handler: Callable[[SliderEvent], Any] | None = None) -> Stream | Subscription:
151
+ """Register a callback fired when the gauge value changes.
152
+
153
+ Args:
154
+ handler: Called with a :class:`~bootstack.events.SliderEvent`. Omit to
155
+ get a composable :class:`~bootstack.streams.Stream` instead.
156
+
157
+ Returns:
158
+ A cancellable :class:`~bootstack.events.Subscription` when a
159
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
160
+ """
161
+ return self.on("change", handler)
162
+
163
+
164
+ _GAUGE_EVENTS: dict[str, str] = {
165
+ "change": "<<Change>>",
166
+ }
167
+
168
+ register_widget_events(Gauge, _GAUGE_EVENTS)