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
bootstack/types.py ADDED
@@ -0,0 +1,39 @@
1
+ """Public type aliases and token Literals for annotating bootstack code.
2
+
3
+ These are the names used in widget signatures — accent/variant/surface tokens,
4
+ layout Literals, density, and the keyword TypedDicts. Import them when you
5
+ annotate your own helpers or widget subclasses::
6
+
7
+ from bootstack.types import AccentToken, WidgetDensity
8
+
9
+ They are re-exported from :mod:`bootstack.widgets.types`.
10
+ """
11
+ from bootstack.widgets.types import (
12
+ BaseWidgetKwargs, StyledKwargs,
13
+ Anchor, BorderMode, CompoundMode, Direction,
14
+ Fill, Justify, Orient, Padding, Relief, Side, Sticky,
15
+ HAlign, VAlign, HArrange, VArrange,
16
+ LayoutKind, AutoFlow, AccordionVariant, Region, ButtonVariant,
17
+ IconPosition, SelectionMode, ExportScope, ExportFormat,
18
+ ToastCorner, SnackbarAlign,
19
+ WidgetState, WidgetDensity, ScrollbarVariant,
20
+ AccentToken, SurfaceToken, WindowStyle,
21
+ ColumnSpec, EditorType, FormOptions,
22
+ Option, OptionDict,
23
+ Icon, IconSpec,
24
+ )
25
+
26
+ __all__ = [
27
+ "BaseWidgetKwargs", "StyledKwargs",
28
+ "Anchor", "BorderMode", "CompoundMode", "Direction",
29
+ "Fill", "Justify", "Orient", "Padding", "Relief", "Side", "Sticky",
30
+ "HAlign", "VAlign", "HArrange", "VArrange",
31
+ "LayoutKind", "AutoFlow", "AccordionVariant", "Region", "ButtonVariant",
32
+ "IconPosition", "SelectionMode", "ExportScope", "ExportFormat",
33
+ "ToastCorner", "SnackbarAlign",
34
+ "WidgetState", "WidgetDensity", "ScrollbarVariant",
35
+ "AccentToken", "SurfaceToken", "WindowStyle",
36
+ "ColumnSpec", "EditorType", "FormOptions",
37
+ "Option", "OptionDict",
38
+ "Icon", "IconSpec",
39
+ ]
@@ -0,0 +1,6 @@
1
+ """Validation rules and results for Field-based input widgets."""
2
+ from .validation_rules import ValidationRule
3
+ from .validation_result import ValidationResult
4
+ from .types import RuleType
5
+
6
+ __all__ = ['ValidationRule', 'ValidationResult', 'RuleType']
@@ -0,0 +1,15 @@
1
+ from typing import Any, Callable, Literal, Optional, TypedDict
2
+
3
+ RuleType = Literal["required", "email", "pattern", "stringLength", "range", "custom", "compare"]
4
+ """A built-in validation rule name."""
5
+
6
+ RuleTriggerType = Literal['key', 'blur', 'always', 'manual']
7
+
8
+
9
+ class ValidationOptions(TypedDict, total=False):
10
+ pattern: str
11
+ message: str
12
+ min: Any
13
+ max: Any
14
+ trigger: Optional[Literal["key", "blur", "always", "manual"]]
15
+ func: Callable[[str], bool]
@@ -0,0 +1,17 @@
1
+ class ValidationResult:
2
+ """The outcome of a single validation check.
3
+
4
+ Attributes:
5
+ is_valid (bool): True if validation passed, False otherwise.
6
+ message (str): Error message when `is_valid` is False; empty string otherwise.
7
+ """
8
+
9
+ def __init__(self, is_valid: bool, message: str = ""):
10
+ """Create a validation result.
11
+
12
+ Args:
13
+ is_valid: True if validation passed.
14
+ message: Error message. Defaults to an empty string.
15
+ """
16
+ self.is_valid = is_valid
17
+ self.message = message
@@ -0,0 +1,205 @@
1
+ import re
2
+ from typing import Callable
3
+
4
+ from bootstack.validation.types import RuleTriggerType, RuleType
5
+ from bootstack.validation.validation_result import ValidationResult
6
+
7
+
8
+ # Rules that operate on a text value; meaningless on a typed (number/date/time)
9
+ # value. The field rejects these at attach time when it does not hold text.
10
+ TEXT_RULES = frozenset({"stringLength", "pattern", "email"})
11
+
12
+ # Rules that operate on an orderable typed value (number, date, or time).
13
+ ORDERED_RULES = frozenset({"range"})
14
+
15
+
16
+ def rule_applies_to_kind(rule_type: str, kind: str) -> bool:
17
+ """Whether a rule type can validate a field holding the given value kind.
18
+
19
+ Args:
20
+ rule_type: The rule name (e.g. `'stringLength'`, `'range'`).
21
+ kind: The field's value kind — `'text'`, `'number'`, `'date'`, or
22
+ `'time'`.
23
+
24
+ Returns:
25
+ `True` if the rule applies. Text rules apply only to text fields;
26
+ `'range'` applies only to ordered (number/date/time) fields; the
27
+ remaining rules (`'required'`, `'compare'`, `'custom'`) apply to any.
28
+ """
29
+ if rule_type in TEXT_RULES:
30
+ return kind == "text"
31
+ if rule_type in ORDERED_RULES:
32
+ return kind in ("number", "date", "time")
33
+ return True
34
+
35
+
36
+ def _as_text(value: object) -> str:
37
+ """Coerce a value to text for the string rules.
38
+
39
+ The string rules (`stringLength`, `pattern`, `email`) operate on text. When
40
+ one is applied to a typed value (a number or date), coerce rather than crash;
41
+ the rule taxonomy rejects that misuse at attach time, this is the last-ditch
42
+ guard. `None` (an empty field) becomes the empty string.
43
+ """
44
+ if isinstance(value, str):
45
+ return value
46
+ return "" if value is None else str(value)
47
+
48
+
49
+ class ValidationRule:
50
+ """A single validation rule that can be applied to a string value.
51
+
52
+ Supports the built-in rule types `'required'`, `'email'`,
53
+ `'stringLength'`, `'pattern'`, `'compare'`, and `'custom'`, and carries a
54
+ trigger policy that controls when the rule is evaluated.
55
+
56
+ Attributes:
57
+ type (RuleType): The validation rule type.
58
+ message (str): Custom error message; if empty a default is generated.
59
+ trigger (RuleTriggerType): When the rule fires — `'always'`, `'key'`,
60
+ `'blur'`, or `'manual'`.
61
+ params (dict): Additional parameters specific to the rule type
62
+ (e.g., `min`/`max` for `'stringLength'`, `pattern` for `'pattern'`,
63
+ `other_field` for `'compare'`, `func` for `'custom'`).
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ rule_type: RuleType,
69
+ message: str = "",
70
+ **kwargs
71
+ ):
72
+ """Create a validation rule.
73
+
74
+ Args:
75
+ rule_type: The type of validation to apply.
76
+ message: Custom error message. If empty, a sensible default is used.
77
+ **kwargs: Rule-specific parameters. Pass `trigger` to override the
78
+ default trigger policy; all other keys are stored in `params`
79
+ (e.g., `min=3, max=20` for `'stringLength'`, `pattern=r'\\d+'`
80
+ for `'pattern'`, `func=callable` for `'custom'`).
81
+ """
82
+ self.type = rule_type
83
+ self.message = message
84
+ self.trigger = kwargs.pop('trigger', self._default_trigger())
85
+ self.params = kwargs
86
+
87
+ def validate(self, value: str) -> ValidationResult:
88
+ """Apply this rule to a value and return the result.
89
+
90
+ Args:
91
+ value: The string value to validate.
92
+
93
+ Returns:
94
+ A ValidationResult with `is_valid=True` on success or `is_valid=False`
95
+ with an error message on failure.
96
+ """
97
+ msg = self.message or self._default_message()
98
+
99
+ if self.type == "required":
100
+ if value is None:
101
+ return ValidationResult(False, msg)
102
+ if isinstance(value, str) and not value.strip():
103
+ return ValidationResult(False, msg)
104
+ # Everything else is valid (non-empty string, number, date, etc.)
105
+ return ValidationResult(True, "")
106
+
107
+ elif self.type == "email":
108
+ if not re.match(r"[^@]+@[^@]+\.[^@]+", _as_text(value)):
109
+ return ValidationResult(False, msg)
110
+ elif self.type == "stringLength":
111
+ min_len = self.params.get("min", 0)
112
+ max_len = self.params.get("max", float("inf"))
113
+ if not (min_len <= len(_as_text(value)) <= max_len):
114
+ return ValidationResult(False, msg)
115
+ elif self.type == "pattern":
116
+ pattern = self.params.get("pattern", "")
117
+ if not re.match(pattern, _as_text(value)):
118
+ return ValidationResult(False, msg)
119
+ elif self.type == "range":
120
+ # Bounds on an ordered value (number/date/time). An empty field is
121
+ # not out of range — use 'required' for presence.
122
+ if value is None or value == "":
123
+ return ValidationResult(True)
124
+ lo = self.params.get("min")
125
+ hi = self.params.get("max")
126
+ try:
127
+ if lo is not None and value < lo:
128
+ return ValidationResult(False, msg)
129
+ if hi is not None and value > hi:
130
+ return ValidationResult(False, msg)
131
+ except TypeError:
132
+ # Incomparable types (e.g. a string bound against a number).
133
+ return ValidationResult(False, msg)
134
+ elif self.type == "compare":
135
+ if value != self._read_other(self.params.get("other_field")):
136
+ return ValidationResult(False, msg)
137
+ elif self.type == "custom":
138
+ func: Callable[[str], bool] = self.params.get("func")
139
+ if func and not func(value):
140
+ return ValidationResult(False, msg)
141
+
142
+ return ValidationResult(True)
143
+
144
+ @staticmethod
145
+ def _read_other(other: object) -> object:
146
+ """Resolve the current value of a `'compare'` rule's `other_field`.
147
+
148
+ Accepts a `Signal` or any zero-argument callable (called to read), a
149
+ field wrapper exposing a `value` property, or a plain literal value.
150
+
151
+ Args:
152
+ other: The `other_field` parameter passed to the rule.
153
+
154
+ Returns:
155
+ The other field's current value, or `None` if `other` is `None`.
156
+ """
157
+ if other is None:
158
+ return None
159
+ if callable(other):
160
+ return other()
161
+ if hasattr(other, "value"):
162
+ return other.value
163
+ return other
164
+
165
+ def _default_message(self) -> str:
166
+ """Return a sensible default error message for this rule type."""
167
+ if self.type == "required":
168
+ return "This field is required."
169
+ elif self.type == "email":
170
+ return "Enter a valid email address."
171
+ elif self.type == "stringLength":
172
+ min_len = self.params.get("min", 0)
173
+ max_len = self.params.get("max", None)
174
+ if max_len is None or max_len == float("inf"):
175
+ return f"Enter at least {min_len} characters."
176
+ return f"Enter between {min_len} and {max_len} characters."
177
+ elif self.type == "pattern":
178
+ return "Value does not match the required pattern."
179
+ elif self.type == "range":
180
+ lo = self.params.get("min")
181
+ hi = self.params.get("max")
182
+ if lo is not None and hi is not None:
183
+ return f"Enter a value between {lo} and {hi}."
184
+ if lo is not None:
185
+ return f"Enter a value of at least {lo}."
186
+ if hi is not None:
187
+ return f"Enter a value of at most {hi}."
188
+ return "Value is out of range."
189
+ elif self.type == "compare":
190
+ return "Values do not match."
191
+ elif self.type == "custom":
192
+ return "Invalid value."
193
+ return "Invalid input."
194
+
195
+ def _default_trigger(self) -> RuleTriggerType:
196
+ """Return the default trigger policy for this rule type."""
197
+ if self.type == "required":
198
+ return "always"
199
+ elif self.type in {"stringLength", "compare", "range"}:
200
+ return "blur"
201
+ elif self.type in {"email", "pattern"}:
202
+ return "always"
203
+ elif self.type in {"custom"}:
204
+ return "manual"
205
+ return "blur"
@@ -0,0 +1,74 @@
1
+ """Widget package for bootstack.
2
+
3
+ Public widgets are lazily importable from this package — e.g.
4
+ `from bootstack.widgets import Button` — mirroring the canonical
5
+ `import bootstack as bs; bs.Button` access. Resolution is lazy (PEP 562)
6
+ so importing the package does not eagerly pull in every widget module.
7
+ """
8
+ from typing import Any
9
+
10
+ # Public widget-area name -> defining submodule (relative to this package).
11
+ # Single source of truth for the runtime namespace below.
12
+ _EXPORTS: dict[str, str] = {
13
+ # Application & windows
14
+ "App": "app", "AppShell": "appshell", "Workbench": "appshell",
15
+ "Window": "window", "Splash": "splash",
16
+ # Layout
17
+ "Row": "stacks", "Column": "stacks", "Spacer": "stacks",
18
+ "Grid": "grid", "Card": "card",
19
+ "GroupBox": "groupbox", "Divider": "divider", "ScrollView": "scrollview",
20
+ "SplitView": "splitview", "SplitPane": "splitview",
21
+ "Accordion": "expander", "AccordionSection": "expander",
22
+ # Actions
23
+ "Button": "button", "ButtonGroup": "buttongroup", "MenuButton": "menubutton",
24
+ "Toolbar": "toolbar", "StatusBar": "statusbar",
25
+ "ContextMenu": "contextmenu", "ContextMenuItem": "contextmenu",
26
+ # Inputs
27
+ "TextField": "textfield", "PasswordField": "passwordfield",
28
+ "NumberField": "numberfield", "PathField": "pathfield",
29
+ "SpinnerField": "spinnerfield", "TextArea": "textarea", "CodeEditor": "codeeditor",
30
+ "DateField": "datefield", "TimeField": "timefield",
31
+ "Slider": "slider", "RangeSlider": "slider",
32
+ "EditFilter": "_impl.composites.textarea.filter",
33
+ # Selection
34
+ "Checkbox": "boolean_controls", "Switch": "boolean_controls",
35
+ "ToggleButton": "boolean_controls", "ThemeToggle": "theme_toggle",
36
+ "ToggleGroup": "togglegroup",
37
+ "Radio": "radio_variants", "RadioToggleButton": "radio_variants",
38
+ "RadioGroup": "radiogroup", "Select": "select", "SelectButton": "selectbutton",
39
+ "Calendar": "calendar",
40
+ # Data display
41
+ "Label": "label", "Badge": "label", "Picture": "picture", "Gallery": "gallery",
42
+ "Carousel": "carousel", "Avatar": "avatar", "ProgressBar": "progressbar",
43
+ "Gauge": "gauge", "ListView": "listview", "DataTable": "datatable",
44
+ "Tree": "tree", "TreeNode": "tree", "Chart": "chart",
45
+ # Navigation
46
+ "PageStack": "pagestack", "StackPage": "pagestack",
47
+ "Tabs": "tabs", "TabPage": "tabs",
48
+ # Overlays
49
+ "Tooltip": "tooltip", "toast": "toast", "Notification": "toast",
50
+ "Snackbar": "toast", "snackbar": "toast",
51
+ # Forms
52
+ "Form": "form", "FormItem": "form", "FieldItem": "form", "GroupItem": "form",
53
+ "TabsItem": "form", "TabItem": "form",
54
+ # Extension base classes (for building custom public widgets)
55
+ "PublicWidgetBase": "_core.base", "PublicContainer": "_core.container",
56
+ }
57
+
58
+ __all__ = sorted(_EXPORTS)
59
+
60
+
61
+ def __getattr__(name: str) -> Any:
62
+ """Lazily import and cache a public widget by name (PEP 562)."""
63
+ module = _EXPORTS.get(name)
64
+ if module is None:
65
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
66
+ import importlib
67
+
68
+ obj = getattr(importlib.import_module(f"bootstack.widgets.{module}"), name)
69
+ globals()[name] = obj # cache so subsequent lookups skip __getattr__
70
+ return obj
71
+
72
+
73
+ def __dir__() -> list[str]:
74
+ return sorted(set(globals()) | set(_EXPORTS))
@@ -0,0 +1,31 @@
1
+ """Public framework internals — extend here, do not import directly from user code."""
2
+ from bootstack.widgets._core.base import PublicWidgetBase
3
+ from bootstack.widgets._core.container import PublicContainer
4
+ from bootstack.widgets._core.context import push_container, pop_container, current_container
5
+ from bootstack.widgets._core.events import register_widget_events, resolve_event
6
+ from bootstack.widgets.types import Event
7
+ from bootstack.errors import BootstackError, UnknownEventError, ParentResolutionError
8
+ from bootstack.widgets._core.field_mixin import FieldAddonMixin
9
+ from bootstack.scheduling import Schedule, Job
10
+ from bootstack.streams import Stream, Handle
11
+ from bootstack.events import Subscription
12
+
13
+ __all__ = [
14
+ "BootstackError",
15
+ "Event",
16
+ "FieldAddonMixin",
17
+ "Handle",
18
+ "Job",
19
+ "ParentResolutionError",
20
+ "PublicContainer",
21
+ "PublicWidgetBase",
22
+ "Schedule",
23
+ "Stream",
24
+ "Subscription",
25
+ "UnknownEventError",
26
+ "current_container",
27
+ "pop_container",
28
+ "push_container",
29
+ "register_widget_events",
30
+ "resolve_event",
31
+ ]
@@ -0,0 +1,266 @@
1
+ """Flat configuration properties shared by `App` and `AppShell`.
2
+
3
+ The public app surface exposes configuration as flat `app.*` properties, so
4
+ that what goes in through the constructor comes back out symmetrically:
5
+ `App(theme=...)` in, `app.theme` out. Settable knobs reflect live where the
6
+ machinery supports a runtime change (theme, locale, title); locale-derived
7
+ formats are read-only.
8
+
9
+ `AppConfigMixin` delegates to the internal runtime `App` returned by
10
+ `_config_app()`, which each public class implements.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING, Any, Callable, Sequence, overload
16
+
17
+ from bootstack.events import Subscription
18
+
19
+ if TYPE_CHECKING:
20
+ from bootstack.streams import Stream
21
+ from bootstack.widgets.types import WindowStyle
22
+
23
+
24
+ class AppConfigMixin:
25
+ """Mixin providing flat configuration properties for the public app.
26
+
27
+ Subclasses must implement `_config_app()` to return the internal runtime
28
+ `App` (a `tkinter.Tk` subclass) whose `settings` holds the resolved
29
+ configuration.
30
+ """
31
+
32
+ def _config_app(self) -> Any:
33
+ """Return the internal runtime `App` backing this public app."""
34
+ raise NotImplementedError
35
+
36
+ @property
37
+ def _settings(self) -> Any:
38
+ return self._config_app().settings
39
+
40
+ # ----- identity -----
41
+
42
+ @property
43
+ def title(self) -> str:
44
+ """The window title bar text (also the app's display name)."""
45
+ return self._config_app().title()
46
+
47
+ @title.setter
48
+ def title(self, value: str) -> None:
49
+ app = self._config_app()
50
+ app.title(value)
51
+ app.settings.app_name = value
52
+
53
+ # The app name (config-directory / taskbar identity) is set via `title=`
54
+ # (which writes both the title bar and `app_name`); there is no separate
55
+ # public `name` property — it was redundant with `title`.
56
+
57
+ # ----- theme -----
58
+
59
+ @property
60
+ def theme(self) -> str:
61
+ """The active theme name. Setting it switches the theme live."""
62
+ from bootstack.style.style import get_theme
63
+ return get_theme()
64
+
65
+ @theme.setter
66
+ def theme(self, value: str) -> None:
67
+ from bootstack.style.style import set_theme
68
+ set_theme(value)
69
+ self._settings.theme = value
70
+
71
+ @property
72
+ def light_theme(self) -> str:
73
+ """Theme used for the light end of system-appearance / `toggle_theme`."""
74
+ return self._settings.light_theme
75
+
76
+ @light_theme.setter
77
+ def light_theme(self, value: str) -> None:
78
+ self._settings.light_theme = value
79
+
80
+ @property
81
+ def dark_theme(self) -> str:
82
+ """Theme used for the dark end of system-appearance / `toggle_theme`."""
83
+ return self._settings.dark_theme
84
+
85
+ @dark_theme.setter
86
+ def dark_theme(self, value: str) -> None:
87
+ self._settings.dark_theme = value
88
+
89
+ @property
90
+ def follow_system_appearance(self) -> bool:
91
+ """Whether the app tracks the OS light/dark appearance (macOS)."""
92
+ return self._settings.follow_system_appearance
93
+
94
+ @follow_system_appearance.setter
95
+ def follow_system_appearance(self, value: bool) -> None:
96
+ self._settings.follow_system_appearance = value
97
+
98
+ # `available_themes` is intentionally construction-only (no runtime property):
99
+ # `App(available_themes=...)` reads as an input ("offer these themes"), but a
100
+ # getter would collide with `get_themes()` (the actual theme-list query) and
101
+ # return `()` by default. Read the exposed list via `get_themes()`.
102
+
103
+ # ----- localization -----
104
+
105
+ @property
106
+ def locale(self) -> str | None:
107
+ """The active locale (e.g. `'en_US'`). Setting it switches locale live."""
108
+ return self._settings.locale
109
+
110
+ @locale.setter
111
+ def locale(self, value: str) -> None:
112
+ from bootstack.i18n.msgcat import MessageCatalog
113
+ MessageCatalog.locale(value)
114
+ self._settings.locale = value
115
+
116
+ # `localize_mode` is construction-only (no runtime property): changing it
117
+ # would only affect widgets created afterward, not the existing UI, so a
118
+ # live setter would be misleading. Pass it to the constructor.
119
+
120
+ @property
121
+ def locale_language(self) -> str | None:
122
+ """Base language code derived from the locale (e.g. `'en'`). Read-only."""
123
+ from bootstack._runtime.app import _language_from_locale
124
+ loc = self._settings.locale
125
+ return _language_from_locale(loc) if loc else None
126
+
127
+ @property
128
+ def locale_date_format(self) -> str | None:
129
+ """Short date pattern for the active locale (e.g. `'M/d/yy'`). Read-only."""
130
+ from bootstack._runtime.app import _safe_date_format
131
+ loc = self._settings.locale
132
+ return _safe_date_format(loc) if loc else None
133
+
134
+ @property
135
+ def locale_time_format(self) -> str | None:
136
+ """Short time pattern for the active locale, e.g. h:mm a. Read-only."""
137
+ from bootstack._runtime.app import _safe_time_format
138
+ loc = self._settings.locale
139
+ return _safe_time_format(loc) if loc else None
140
+
141
+ @property
142
+ def locale_decimal(self) -> str | None:
143
+ """Decimal separator for the active locale (e.g. `'.'`). Read-only."""
144
+ from bootstack._runtime.app import _safe_decimal_symbol
145
+ loc = self._settings.locale
146
+ return _safe_decimal_symbol(loc) if loc else None
147
+
148
+ @property
149
+ def locale_thousands(self) -> str | None:
150
+ """Thousands separator for the active locale (e.g. `','`). Read-only."""
151
+ from bootstack._runtime.app import _safe_group_symbol
152
+ loc = self._settings.locale
153
+ return _safe_group_symbol(loc) if loc else None
154
+
155
+ # ----- platform / window state -----
156
+
157
+ @property
158
+ def window_style(self) -> WindowStyle | str | None:
159
+ """Windows-only window effect, or None to disable."""
160
+ return self._settings.window_style
161
+
162
+ @window_style.setter
163
+ def window_style(self, value: WindowStyle | str | None) -> None:
164
+ app = self._config_app()
165
+ app.settings.window_style = value
166
+ # Best-effort live re-apply (Windows / pywinstyles only).
167
+ try:
168
+ app._window_style = value
169
+ app._window_style_applied = False
170
+ app._apply_window_style()
171
+ except Exception:
172
+ pass
173
+
174
+ # `macos_quit_behavior` and `state_path` are construction-only (no runtime
175
+ # properties): both are set-once startup configuration that a user would not
176
+ # change while the app runs. Pass them to the constructor.
177
+
178
+ @property
179
+ def remember_window_state(self) -> bool:
180
+ """Whether window geometry is saved on close and restored on launch."""
181
+ return self._settings.remember_window_state
182
+
183
+ @remember_window_state.setter
184
+ def remember_window_state(self, value: bool) -> None:
185
+ self._settings.remember_window_state = value
186
+
187
+ # ----- configuration-change events -----
188
+
189
+ def _bind_value_event(
190
+ self,
191
+ sequence: str,
192
+ key: str,
193
+ handler: Callable[[Any], Any] | None,
194
+ ) -> "Stream | Subscription":
195
+ app = self._config_app()
196
+
197
+ def _decode(h: Callable[[Any], Any]) -> Callable[[Any], Any]:
198
+ def _wrapped(raw: Any) -> Any:
199
+ data = getattr(raw, "data", None)
200
+ value = data.get(key) if isinstance(data, dict) else data
201
+ return h(value)
202
+ return _wrapped
203
+
204
+ if handler is None:
205
+ from bootstack.streams import Stream
206
+
207
+ def _source(h: Callable[[Any], Any]) -> Subscription:
208
+ bid = app.bind(sequence, _decode(h), add="+")
209
+ return Subscription(app, sequence, bid)
210
+
211
+ return Stream(app, _source=_source)
212
+ bid = app.bind(sequence, _decode(handler), add="+")
213
+ return Subscription(app, sequence, bid)
214
+
215
+ @overload
216
+ def on_theme_change(self) -> "Stream": ...
217
+ @overload
218
+ def on_theme_change(self, handler: Callable[[str], Any]) -> Subscription: ...
219
+ def on_theme_change(
220
+ self, handler: Callable[[str], Any] | None = None
221
+ ) -> "Stream | Subscription":
222
+ """React to theme changes; the handler receives the new theme name.
223
+
224
+ Fired after the theme is fully rebuilt, so handlers can safely read new
225
+ colors. Useful for persisting the choice, e.g.
226
+ `app.on_theme_change(lambda t: store.update(theme=t))`.
227
+
228
+ Returns:
229
+ `Subscription` (with handler) or `Stream` (without handler).
230
+ """
231
+ return self._bind_value_event("<<BsThemeChanged>>", "theme", handler)
232
+
233
+ @overload
234
+ def on_locale_change(self) -> "Stream": ...
235
+ @overload
236
+ def on_locale_change(self, handler: Callable[[str], Any]) -> Subscription: ...
237
+ def on_locale_change(
238
+ self, handler: Callable[[str], Any] | None = None
239
+ ) -> "Stream | Subscription":
240
+ """React to locale changes; the handler receives the new locale code.
241
+
242
+ Returns:
243
+ `Subscription` (with handler) or `Stream` (without handler).
244
+ """
245
+ return self._bind_value_event("<<LocaleChanged>>", "locale", handler)
246
+
247
+
248
+ # The set of App() constructor kwargs that map to configuration. Used by
249
+ # App.from_store() to tolerantly filter a persisted dict (version skew).
250
+ APP_CONFIG_KWARGS: frozenset[str] = frozenset({
251
+ "title",
252
+ "theme",
253
+ "light_theme",
254
+ "dark_theme",
255
+ "follow_system_appearance",
256
+ "available_themes",
257
+ "locale",
258
+ "localize_mode",
259
+ "window_style",
260
+ "macos_quit_behavior",
261
+ "remember_window_state",
262
+ "state_path",
263
+ })
264
+
265
+
266
+ __all__ = ["AppConfigMixin", "APP_CONFIG_KWARGS"]