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,251 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any, Callable, Literal, overload, TYPE_CHECKING
5
+
6
+ from bootstack.events import ImageErrorEvent, ImageLoadEvent, Event, Subscription
7
+ from bootstack.streams import Stream
8
+ from bootstack.widgets._core.base import PublicWidgetBase
9
+ from bootstack.widgets._core.events import register_widget_events
10
+ from bootstack.widgets._impl.composites.picture import Picture as _InternalPicture
11
+ from bootstack.widgets.types import Anchor, Padding, SurfaceToken
12
+
13
+ if TYPE_CHECKING:
14
+ from bootstack.images import Image
15
+
16
+ ImageFit = Literal["contain", "cover", "fill", "none", "scale-down"]
17
+
18
+
19
+ class Picture(PublicWidgetBase):
20
+ """Displays an image, scaled to fit, with optional animation.
21
+
22
+ `Picture` is the display widget for pictures and media — the counterpart to
23
+ an `Image`, which is only a source handle. Hand it an `Image` (or a file
24
+ path) and it renders the picture into a resizable area, scaling it to fit
25
+ with a chosen policy. Animated GIF and WebP sources play automatically.
26
+
27
+ Unlike showing an image on a `Label`, a `Picture` is sizing-aware: it fills
28
+ the space it is given and re-fits the picture as that space changes, so it
29
+ works in responsive layouts.
30
+
31
+ Args:
32
+ image: The picture to show — an `Image` handle (from `bootstack.images`),
33
+ a file path (`str` or `Path`, opened for you), or `None` for an empty
34
+ frame. Assign the `image` property later to change it.
35
+ fit: How the picture is scaled into the display area. Default
36
+ `'contain'`.
37
+
38
+ - `'contain'` — scale to fit inside, preserving aspect ratio
39
+ (letterboxing any remainder).
40
+ - `'cover'` — scale to fill, preserving aspect ratio (cropping the
41
+ overflow).
42
+ - `'fill'` — stretch to fill, ignoring aspect ratio.
43
+ - `'none'` — show at natural size, no scaling.
44
+ - `'scale-down'` — like `'contain'`, but never enlarge past natural
45
+ size.
46
+ width: Fixed display width in pixels. Omit to size from the container.
47
+ height: Fixed display height in pixels. Omit to size from the container.
48
+ anchor: Where the picture sits within the area when it does not fill it
49
+ (with `'contain'`, `'scale-down'`, or `'none'`). Default `'center'`.
50
+ corner_radius: Rounded-corner radius in pixels. `0` (default) is square.
51
+ autoplay: Start playing an animated source as soon as it is shown.
52
+ Default `True`.
53
+ loop: Loop an animated source. Default `True`.
54
+ surface: Background surface token used to letterbox behind the picture.
55
+ parent: Explicit parent widget. If omitted, the current context-stack
56
+ container is used.
57
+ **kwargs: Layout placement options applied by the parent container
58
+ (e.g. `fill`, `expand`, `row`, `column`). See :doc:`/tasks/layout`.
59
+ """
60
+
61
+ _internal_class = _InternalPicture
62
+
63
+ def __init__(
64
+ self,
65
+ image: "Image | str | Path | None" = None,
66
+ *,
67
+ fit: ImageFit = "contain",
68
+ width: int | None = None,
69
+ height: int | None = None,
70
+ anchor: Anchor = "center",
71
+ corner_radius: int = 0,
72
+ autoplay: bool = True,
73
+ loop: bool = True,
74
+ surface: SurfaceToken | str | None = None,
75
+ padding: Padding | None = None,
76
+ parent: Any = None,
77
+ **kwargs: Any,
78
+ ) -> None:
79
+ self._parent = self._resolve_parent(parent)
80
+ layout_kw = self._split_layout_kwargs(kwargs)
81
+ tk_master = self._parent._child_master() if self._parent else None
82
+
83
+ internal_kwargs: dict[str, Any] = {
84
+ "fit": fit,
85
+ "anchor": anchor,
86
+ "corner_radius": corner_radius,
87
+ "autoplay": autoplay,
88
+ "loop": loop,
89
+ "img_width": width,
90
+ "img_height": height,
91
+ }
92
+ if surface is not None:
93
+ internal_kwargs["surface"] = surface
94
+ if padding is not None:
95
+ internal_kwargs["padding"] = padding
96
+ internal_kwargs.update(kwargs)
97
+
98
+ self._source = self._coerce_image(image)
99
+ self._internal = self._internal_class(
100
+ tk_master, source=self._source, **internal_kwargs
101
+ )
102
+ self._attach_to_parent(layout_kw)
103
+
104
+ @staticmethod
105
+ def _coerce_image(image: "Image | str | Path | None") -> Any:
106
+ """Normalize an `image=` argument to an `Image` handle (or None)."""
107
+ if image is None:
108
+ return None
109
+ from bootstack.images import Image as _ImageHandle
110
+
111
+ if isinstance(image, _ImageHandle):
112
+ return image
113
+ if isinstance(image, (str, Path)):
114
+ return _ImageHandle.open(image)
115
+ try:
116
+ from PIL.Image import Image as _PILImage
117
+
118
+ if isinstance(image, _PILImage):
119
+ return _ImageHandle.from_pil(image)
120
+ except Exception:
121
+ pass
122
+ raise TypeError(
123
+ "Picture image must be an Image handle, a file path, or a PIL image; "
124
+ f"got {type(image).__name__}"
125
+ )
126
+
127
+ # ----- Properties -----
128
+
129
+ @property
130
+ def image(self) -> "Image | None":
131
+ """The displayed image handle, or `None` when empty.
132
+
133
+ Assign an `Image`, a file path, or `None` to change what is shown.
134
+ """
135
+ return self._source
136
+
137
+ @image.setter
138
+ def image(self, value: "Image | str | Path | None") -> None:
139
+ self._source = self._coerce_image(value)
140
+ self._internal.set_source(self._source)
141
+
142
+ @property
143
+ def fit(self) -> ImageFit:
144
+ """The fit policy controlling how the picture is scaled into its area."""
145
+ return self._internal._fit
146
+
147
+ @fit.setter
148
+ def fit(self, value: ImageFit) -> None:
149
+ self._internal.set_fit(value)
150
+
151
+ @property
152
+ def is_playing(self) -> bool:
153
+ """Whether an animated source is currently playing."""
154
+ return self._internal.is_playing
155
+
156
+ # ----- Playback -----
157
+
158
+ def play(self) -> None:
159
+ """Start or resume playback of an animated source.
160
+
161
+ Has no effect on a still image or when already playing.
162
+ """
163
+ self._internal.play()
164
+
165
+ def pause(self) -> None:
166
+ """Pause playback on the current frame.
167
+
168
+ Resume with `play`; the frame stays on screen meanwhile.
169
+ """
170
+ self._internal.pause()
171
+
172
+ def stop(self) -> None:
173
+ """Stop playback and reset to the first frame."""
174
+ self._internal.stop()
175
+
176
+ # ----- Events -----
177
+
178
+ @overload
179
+ def on_load(self) -> Stream: ...
180
+ @overload
181
+ def on_load(self, handler: Callable[[ImageLoadEvent], Any]) -> Subscription: ...
182
+ def on_load(
183
+ self, handler: Callable[[ImageLoadEvent], Any] | None = None
184
+ ) -> Stream | Subscription:
185
+ """Register a callback fired when an image is decoded and displayed.
186
+
187
+ The handler receives an :class:`~bootstack.events.ImageLoadEvent` with
188
+ the picture's natural `width`, `height`, and frame count.
189
+
190
+ Args:
191
+ handler: Called with the load event. Omit to get a composable
192
+ :class:`~bootstack.streams.Stream`.
193
+
194
+ Returns:
195
+ A :class:`~bootstack.events.Subscription` when a handler is given,
196
+ otherwise a :class:`~bootstack.streams.Stream`.
197
+ """
198
+ return self.on("load", handler)
199
+
200
+ @overload
201
+ def on_error(self) -> Stream: ...
202
+ @overload
203
+ def on_error(self, handler: Callable[[ImageErrorEvent], Any]) -> Subscription: ...
204
+ def on_error(
205
+ self, handler: Callable[[ImageErrorEvent], Any] | None = None
206
+ ) -> Stream | Subscription:
207
+ """Register a callback fired when an image fails to load or decode.
208
+
209
+ The handler receives an :class:`~bootstack.events.ImageErrorEvent`
210
+ carrying a human-readable `message`.
211
+
212
+ Args:
213
+ handler: Called with the error event. Omit to get a composable
214
+ :class:`~bootstack.streams.Stream`.
215
+
216
+ Returns:
217
+ A :class:`~bootstack.events.Subscription` when a handler is given,
218
+ otherwise a :class:`~bootstack.streams.Stream`.
219
+ """
220
+ return self.on("error", handler)
221
+
222
+ @overload
223
+ def on_click(self) -> Stream: ...
224
+ @overload
225
+ def on_click(self, handler: Callable[[Event], Any]) -> Subscription: ...
226
+ def on_click(
227
+ self, handler: Callable[[Event], Any] | None = None
228
+ ) -> Stream | Subscription:
229
+ """Register a callback for clicks on the picture.
230
+
231
+ The handler receives a curated :class:`~bootstack.events.Event`.
232
+
233
+ Args:
234
+ handler: Called with the click `Event`. Omit to get a composable
235
+ :class:`~bootstack.streams.Stream`.
236
+
237
+ Returns:
238
+ A :class:`~bootstack.events.Subscription` when a handler is given,
239
+ otherwise a :class:`~bootstack.streams.Stream`.
240
+ """
241
+ return self.on("click", handler)
242
+
243
+
244
+ register_widget_events(
245
+ Picture,
246
+ {
247
+ "load": "<<PictureLoad>>",
248
+ "error": "<<PictureError>>",
249
+ "click": "<<PictureClick>>",
250
+ },
251
+ )
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal, TYPE_CHECKING
4
+
5
+ from bootstack.widgets._impl.primitives.progressbar import Progressbar as _InternalProgressbar
6
+ from bootstack.widgets._core.base import PublicWidgetBase
7
+ from bootstack.widgets._core.events import register_widget_events
8
+ from bootstack.widgets.types import AccentToken, Orient
9
+
10
+ if TYPE_CHECKING:
11
+ from bootstack.signals import Signal
12
+
13
+
14
+ class ProgressBar(PublicWidgetBase):
15
+ """A progress indicator bar.
16
+
17
+ Displays determinate progress (a fixed percentage fill) or indeterminate
18
+ progress (a looping animation) for operations whose duration is unknown.
19
+
20
+ Args:
21
+ value: Initial progress value. Defaults to `0`.
22
+ max_value: Value that represents 100% progress. Defaults to `100`.
23
+ mode: `'determinate'` shows a fixed fill proportional to
24
+ `value / max_value`; `'indeterminate'` runs a looping animation.
25
+ Default `'determinate'`.
26
+ orient: Track orientation. Default `'horizontal'`.
27
+ signal: Reactive `Signal` two-way bound to `value`.
28
+ accent: Color intent token for the bar. Defaults to the theme's default
29
+ color.
30
+ variant: Style variant. `'thin'` reduces the bar height.
31
+ parent: Override the context-stack parent.
32
+ **kwargs: Layout placement options applied by the parent container —
33
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
34
+ See :doc:`/tasks/layout`.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ value: float = 0,
40
+ *,
41
+ max_value: float = 100,
42
+ mode: Literal["determinate", "indeterminate"] = "determinate",
43
+ orient: Orient = "horizontal",
44
+ signal: Signal | None = None,
45
+ accent: AccentToken | str | None = None,
46
+ variant: Literal["thin"] | None = None,
47
+ parent: Any = None,
48
+ **kwargs: Any,
49
+ ) -> None:
50
+ self._parent = self._resolve_parent(parent)
51
+ layout_kw = self._split_layout_kwargs(kwargs)
52
+
53
+ tk_master = self._parent._child_master() if self._parent else None
54
+
55
+ internal_kwargs: dict[str, Any] = {
56
+ "value": value,
57
+ "maximum": max_value,
58
+ "mode": mode,
59
+ "orient": orient,
60
+ }
61
+ if signal is not None:
62
+ internal_kwargs["signal"] = signal
63
+ if accent is not None:
64
+ internal_kwargs["accent"] = accent
65
+ if variant is not None:
66
+ internal_kwargs["variant"] = variant
67
+
68
+ self._internal = _InternalProgressbar(tk_master, **internal_kwargs)
69
+ self._attach_to_parent(layout_kw)
70
+
71
+ # ----- Properties -----
72
+
73
+ @property
74
+ def value(self) -> float:
75
+ """The current progress value."""
76
+ return self._internal.get()
77
+
78
+ @value.setter
79
+ def value(self, v: float) -> None:
80
+ self._internal.set(v)
81
+
82
+ @property
83
+ def max_value(self) -> float:
84
+ """The value that represents 100% progress."""
85
+ return float(self._internal.cget("maximum"))
86
+
87
+ @max_value.setter
88
+ def max_value(self, v: float) -> None:
89
+ self._internal.configure(maximum=float(v))
90
+
91
+ # ----- Methods -----
92
+
93
+ def start(self, interval: int = 50) -> None:
94
+ """Start indeterminate animation. `interval` is the step delay in ms."""
95
+ self._internal.start(interval)
96
+
97
+ def stop(self) -> None:
98
+ """Stop indeterminate animation."""
99
+ self._internal.stop()
100
+
101
+ def step(self, amount: float = 1.0) -> None:
102
+ """Advance the value by `amount`."""
103
+ self._internal.step(amount)
104
+
105
+
106
+ register_widget_events(ProgressBar, {})
@@ -0,0 +1,271 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import overload, Any, Callable, Literal, TYPE_CHECKING
4
+
5
+ from bootstack.widgets._impl.primitives.radiobutton import RadioButton as _InternalRadioButton
6
+ from bootstack.widgets._impl.primitives.radiotoggle import RadioToggle as _InternalRadioToggle
7
+ from bootstack.widgets._core.base import PublicWidgetBase
8
+ from bootstack.widgets._core.events import register_widget_events
9
+ from bootstack.events import Subscription
10
+ from bootstack.streams import Stream
11
+ from bootstack.widgets.types import AccentToken, Event, WidgetDensity, IconPosition
12
+
13
+ if TYPE_CHECKING:
14
+ from bootstack.signals import Signal
15
+
16
+ _RADIO_EVENTS: dict[str, str] = {
17
+ "change": "<<Change>>",
18
+ "select": "<<Select>>",
19
+ }
20
+
21
+
22
+ class _RadioBase(PublicWidgetBase):
23
+ """Shared base for Radio and RadioToggleButton."""
24
+
25
+ _internal_class: type
26
+
27
+ def __init__(
28
+ self,
29
+ label: str = "",
30
+ value: Any = None,
31
+ *,
32
+ signal: "Signal | None" = None,
33
+ text_signal: "Signal | None" = None,
34
+ on_change: Callable[[], Any] | None = None,
35
+ icon: str | None = None,
36
+ selected_icon: str | None = None,
37
+ unselected_icon: str | None = None,
38
+ icon_only: bool = False,
39
+ show_indicator: bool = True,
40
+ icon_position: IconPosition | None = None,
41
+ disabled: bool = False,
42
+ accent: AccentToken | str | None = None,
43
+ localize: bool | Literal['auto'] | None = None,
44
+ _internal_options: dict[str, Any] | None = None,
45
+ parent: Any = None,
46
+ **kwargs: Any,
47
+ ) -> None:
48
+ self._parent = self._resolve_parent(parent)
49
+ layout_kw = self._split_layout_kwargs(kwargs)
50
+ if kwargs:
51
+ raise TypeError(
52
+ f"{type(self).__name__}() got unexpected keyword argument(s): "
53
+ f"{', '.join(sorted(kwargs))}"
54
+ )
55
+
56
+ tk_master = self._parent._child_master() if self._parent else None
57
+
58
+ _ctor_callback = on_change
59
+
60
+ if icon is not None and not label:
61
+ icon_only = True
62
+
63
+ internal_kwargs: dict[str, Any] = {}
64
+ if label:
65
+ internal_kwargs["text"] = label
66
+ if value is not None:
67
+ internal_kwargs["value"] = value
68
+ if signal is not None:
69
+ internal_kwargs["signal"] = signal
70
+ if text_signal is not None:
71
+ internal_kwargs["textsignal"] = text_signal
72
+ if icon is not None:
73
+ internal_kwargs["icon"] = icon
74
+ if selected_icon is not None:
75
+ internal_kwargs["on_icon"] = selected_icon
76
+ if unselected_icon is not None:
77
+ internal_kwargs["off_icon"] = unselected_icon
78
+ if icon_only:
79
+ internal_kwargs["icon_only"] = True
80
+ if not show_indicator:
81
+ internal_kwargs["show_indicator"] = False
82
+ if icon_position is not None:
83
+ internal_kwargs["compound"] = icon_position
84
+ if disabled:
85
+ internal_kwargs["state"] = "disabled"
86
+ if accent is not None:
87
+ internal_kwargs["accent"] = accent
88
+ if localize is not None:
89
+ internal_kwargs["localize"] = localize
90
+ # Per-subclass styling options (e.g. density — RadioToggleButton only).
91
+ if _internal_options:
92
+ internal_kwargs.update(_internal_options)
93
+
94
+ self._internal = self._internal_class(tk_master, **internal_kwargs)
95
+
96
+ def _command():
97
+ self._internal.event_generate("<<Change>>")
98
+ self._internal.event_generate("<<Select>>")
99
+ if _ctor_callback is not None:
100
+ _ctor_callback()
101
+
102
+ self._internal.configure(command=_command)
103
+ self._attach_to_parent(layout_kw)
104
+
105
+ # ----- Properties -----
106
+
107
+ @property
108
+ def selected(self) -> bool:
109
+ """True if this radio is the currently selected option in its group."""
110
+ return self._internal.get() == self._internal.cget("value")
111
+
112
+ @property
113
+ def disabled(self) -> bool:
114
+ """Whether the control is disabled (non-interactive and greyed out)."""
115
+ return str(self._internal.cget("state")) == "disabled"
116
+
117
+ @disabled.setter
118
+ def disabled(self, v: bool) -> None:
119
+ self._internal.configure(state="disabled" if v else "normal")
120
+
121
+ # ----- Methods -----
122
+
123
+ def select(self) -> None:
124
+ """Programmatically select this radio button."""
125
+ self._internal.invoke()
126
+
127
+ # ----- Event shorthands -----
128
+
129
+ @overload
130
+ def on_change(self) -> Stream: ...
131
+ @overload
132
+ def on_change(self, handler: Callable[[Event], Any]) -> Subscription: ...
133
+ def on_change(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
134
+ """Register a callback fired when this radio is selected.
135
+
136
+ Args:
137
+ handler: Called with the :class:`~bootstack.events.Event`. Omit to
138
+ get a composable :class:`~bootstack.streams.Stream` instead.
139
+
140
+ Returns:
141
+ A cancellable :class:`~bootstack.events.Subscription` when a
142
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
143
+ """
144
+ return self.on("change", handler)
145
+
146
+ @overload
147
+ def on_select(self) -> Stream: ...
148
+ @overload
149
+ def on_select(self, handler: Callable[[Event], Any]) -> Subscription: ...
150
+ def on_select(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
151
+ """Register a callback fired when this radio is selected.
152
+
153
+ Args:
154
+ handler: Called with the :class:`~bootstack.events.Event`. 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("select", handler)
162
+
163
+
164
+ class Radio(_RadioBase):
165
+ """A single radio button — one selectable option in a mutually-exclusive group.
166
+
167
+ Radio buttons in a group share a `signal`; clicking one sets the signal
168
+ to the radio's `value`.
169
+
170
+ Usage::
171
+
172
+ sig = bs.Signal("a")
173
+ bs.Radio("Option A", "a", signal=sig)
174
+ bs.Radio("Option B", "b", signal=sig)
175
+
176
+ Args:
177
+ label: Text displayed beside the radio indicator.
178
+ value: The value written to `signal` when this radio is selected.
179
+ signal: Shared `Signal` that holds the group's current value.
180
+ text_signal: Reactive `Signal` linked to the label text.
181
+ on_change: Callback fired when this radio is selected.
182
+ icon: Icon shown for all states.
183
+ selected_icon: Icon shown when this radio is selected.
184
+ unselected_icon: Icon shown when this radio is not selected.
185
+ icon_only: If True, removes extra padding reserved for the label.
186
+ Inferred automatically when `icon=` is set and no label is provided.
187
+ show_indicator: If False, hides the circular indicator.
188
+ icon_position: Position of the icon relative to the text.
189
+ disabled: If True, widget is non-interactive.
190
+ accent: Accent token applied to the indicator.
191
+ localize: Whether the label is translated through the catalog — `True`,
192
+ `False`, or `'auto'`. Defaults to the app's `localize_mode`. Set
193
+ `False` to keep a proper noun untranslated.
194
+ parent: Override the context-stack parent.
195
+ **kwargs: Layout placement options applied by the parent container —
196
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
197
+ See :doc:`/tasks/layout`.
198
+ """
199
+ _internal_class = _InternalRadioButton
200
+
201
+
202
+ class RadioToggleButton(_RadioBase):
203
+ """A radio button that renders as a toggle button — toolbar/segmented-control style.
204
+
205
+ Behaves identically to `Radio` but renders as a depressed button when
206
+ selected rather than showing the circular indicator.
207
+
208
+ Args:
209
+ label: Text displayed on the button.
210
+ value: The value written to `signal` when this button is selected.
211
+ signal: Shared `Signal` that holds the group's current value.
212
+ text_signal: Reactive `Signal` linked to the label text.
213
+ on_change: Callback fired when this button is selected.
214
+ icon: Icon shown for all states.
215
+ selected_icon: Icon shown when selected.
216
+ unselected_icon: Icon shown when not selected.
217
+ icon_only: If True, removes extra label padding. Inferred
218
+ automatically when `icon=` is set and no label is provided.
219
+ disabled: If True, widget is non-interactive.
220
+ accent: Accent token applied to the button when selected.
221
+ density: Widget density.
222
+ localize: Whether the label is translated through the catalog — `True`,
223
+ `False`, or `'auto'`. Defaults to the app's `localize_mode`. Set
224
+ `False` to keep a proper noun untranslated.
225
+ parent: Override the context-stack parent.
226
+ **kwargs: Layout placement options applied by the parent container —
227
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
228
+ See :doc:`/tasks/layout`.
229
+ """
230
+ _internal_class = _InternalRadioToggle
231
+
232
+ def __init__(
233
+ self,
234
+ label: str = "",
235
+ value: Any = None,
236
+ *,
237
+ signal: "Signal | None" = None,
238
+ text_signal: "Signal | None" = None,
239
+ on_change: Callable[[], Any] | None = None,
240
+ icon: str | None = None,
241
+ selected_icon: str | None = None,
242
+ unselected_icon: str | None = None,
243
+ icon_only: bool = False,
244
+ disabled: bool = False,
245
+ accent: AccentToken | str | None = None,
246
+ density: WidgetDensity | None = None,
247
+ localize: bool | Literal['auto'] | None = None,
248
+ parent: Any = None,
249
+ **kwargs: Any,
250
+ ) -> None:
251
+ super().__init__(
252
+ label,
253
+ value,
254
+ signal=signal,
255
+ text_signal=text_signal,
256
+ on_change=on_change,
257
+ icon=icon,
258
+ selected_icon=selected_icon,
259
+ unselected_icon=unselected_icon,
260
+ icon_only=icon_only,
261
+ disabled=disabled,
262
+ accent=accent,
263
+ localize=localize,
264
+ _internal_options=({"density": density} if density is not None else None),
265
+ parent=parent,
266
+ **kwargs,
267
+ )
268
+
269
+
270
+ register_widget_events(Radio, _RADIO_EVENTS)
271
+ register_widget_events(RadioToggleButton, _RADIO_EVENTS)