bootstack 0.1.0a1__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 (419) hide show
  1. bootstack/__init__.py +249 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/api/__init__.py +127 -0
  4. bootstack/api/app.py +30 -0
  5. bootstack/api/constants.py +3 -0
  6. bootstack/api/data.py +23 -0
  7. bootstack/api/dialogs.py +44 -0
  8. bootstack/api/i18n.py +17 -0
  9. bootstack/api/localization.py +16 -0
  10. bootstack/api/menu.py +7 -0
  11. bootstack/api/style.py +23 -0
  12. bootstack/api/utils.py +24 -0
  13. bootstack/api/widgets.py +137 -0
  14. bootstack/assets/__init__.py +24 -0
  15. bootstack/assets/bootstack-transparent.png +0 -0
  16. bootstack/assets/bootstack.ico +0 -0
  17. bootstack/assets/bootstack.png +0 -0
  18. bootstack/assets/elements/__init__.py +0 -0
  19. bootstack/assets/elements/badge-pill.png +0 -0
  20. bootstack/assets/elements/badge-square.png +0 -0
  21. bootstack/assets/elements/border.png +0 -0
  22. bootstack/assets/elements/button-compact.png +0 -0
  23. bootstack/assets/elements/button-default.png +0 -0
  24. bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
  25. bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
  26. bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
  27. bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
  28. bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
  29. bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
  30. bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
  31. bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
  32. bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
  33. bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
  34. bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
  35. bootstack/assets/elements/button-group-vertical-center-default.png +0 -0
  36. bootstack/assets/elements/checkbox-checked.png +0 -0
  37. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  38. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  39. bootstack/assets/elements/field.png +0 -0
  40. bootstack/assets/elements/input-after-compact.png +0 -0
  41. bootstack/assets/elements/input-after-default.png +0 -0
  42. bootstack/assets/elements/input-before-compact.png +0 -0
  43. bootstack/assets/elements/input-before-default.png +0 -0
  44. bootstack/assets/elements/input-compact.png +0 -0
  45. bootstack/assets/elements/input-default.png +0 -0
  46. bootstack/assets/elements/list-item-separated.png +0 -0
  47. bootstack/assets/elements/list-item.png +0 -0
  48. bootstack/assets/elements/manifest.toml +480 -0
  49. bootstack/assets/elements/menu-item.png +0 -0
  50. bootstack/assets/elements/nav-button-compact.png +0 -0
  51. bootstack/assets/elements/nav-button-default.png +0 -0
  52. bootstack/assets/elements/nav-icon-button-compact.png +0 -0
  53. bootstack/assets/elements/nav-icon-button-default.png +0 -0
  54. bootstack/assets/elements/notebook-client-border.png +0 -0
  55. bootstack/assets/elements/notebook-tab-active.png +0 -0
  56. bootstack/assets/elements/notebook-tab-bar.png +0 -0
  57. bootstack/assets/elements/notebook-tab-normal.png +0 -0
  58. bootstack/assets/elements/notebook-tab-pill.png +0 -0
  59. bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
  60. bootstack/assets/elements/progress-bar-solid.png +0 -0
  61. bootstack/assets/elements/progress-bar-thin.png +0 -0
  62. bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
  63. bootstack/assets/elements/radio-selected.png +0 -0
  64. bootstack/assets/elements/radio-unselected.png +0 -0
  65. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  66. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  67. bootstack/assets/elements/slider-handle-focus.png +0 -0
  68. bootstack/assets/elements/slider-handle.png +0 -0
  69. bootstack/assets/elements/slider-track-horizontal.png +0 -0
  70. bootstack/assets/elements/slider-track-vertical.png +0 -0
  71. bootstack/assets/elements/switch-off.png +0 -0
  72. bootstack/assets/elements/switch-on.png +0 -0
  73. bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
  74. bootstack/assets/elements/tabs-bar-vertical.png +0 -0
  75. bootstack/assets/elements/tabs-pill.png +0 -0
  76. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  77. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
  78. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
  79. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  80. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
  81. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  82. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
  83. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  84. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
  85. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  86. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
  87. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  88. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
  89. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  90. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
  91. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  92. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
  93. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  94. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
  95. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  96. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
  97. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  98. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
  99. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  100. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
  101. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  102. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
  103. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  104. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
  105. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  106. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
  107. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  108. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
  109. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  110. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
  111. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  112. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
  113. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  114. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
  115. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  116. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
  117. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  118. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
  119. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  120. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
  121. bootstack/assets/themes/__init__.py +0 -0
  122. bootstack/assets/themes/amber-dark.json +32 -0
  123. bootstack/assets/themes/amber-light.json +32 -0
  124. bootstack/assets/themes/aurora-dark.json +32 -0
  125. bootstack/assets/themes/aurora-light.json +32 -0
  126. bootstack/assets/themes/bootstrap-dark.json +32 -0
  127. bootstack/assets/themes/bootstrap-light.json +32 -0
  128. bootstack/assets/themes/classic-dark.json +32 -0
  129. bootstack/assets/themes/classic-light.json +32 -0
  130. bootstack/assets/themes/docs-dark.json +32 -0
  131. bootstack/assets/themes/docs-light.json +32 -0
  132. bootstack/assets/themes/forest-dark.json +32 -0
  133. bootstack/assets/themes/forest-light.json +32 -0
  134. bootstack/assets/themes/ocean-dark.json +32 -0
  135. bootstack/assets/themes/ocean-light.json +32 -0
  136. bootstack/assets/themes/rose-dark.json +32 -0
  137. bootstack/assets/themes/rose-light.json +32 -0
  138. bootstack/assets/widgets/__init__.py +0 -0
  139. bootstack/assets/widgets/badge-default.png +0 -0
  140. bootstack/assets/widgets/badge-pill.png +0 -0
  141. bootstack/assets/widgets/border.png +0 -0
  142. bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
  143. bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
  144. bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
  145. bootstack/assets/widgets/button-group-vertical-after.png +0 -0
  146. bootstack/assets/widgets/button-group-vertical-before.png +0 -0
  147. bootstack/assets/widgets/button-group-vertical-center.png +0 -0
  148. bootstack/assets/widgets/button.png +0 -0
  149. bootstack/assets/widgets/checkbox-checked.png +0 -0
  150. bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
  151. bootstack/assets/widgets/checkbox-unchecked.png +0 -0
  152. bootstack/assets/widgets/field.png +0 -0
  153. bootstack/assets/widgets/icon-button.png +0 -0
  154. bootstack/assets/widgets/input-inner.png +0 -0
  155. bootstack/assets/widgets/input-prefix.png +0 -0
  156. bootstack/assets/widgets/input-suffix.png +0 -0
  157. bootstack/assets/widgets/input.png +0 -0
  158. bootstack/assets/widgets/list-item-focus.png +0 -0
  159. bootstack/assets/widgets/list-item-separated.png +0 -0
  160. bootstack/assets/widgets/menu-item-separated.png +0 -0
  161. bootstack/assets/widgets/notebook-client-border.png +0 -0
  162. bootstack/assets/widgets/notebook-pill-active.png +0 -0
  163. bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
  164. bootstack/assets/widgets/notebook-tab-active.png +0 -0
  165. bootstack/assets/widgets/notebook-tab-border.png +0 -0
  166. bootstack/assets/widgets/notebook-tab-normal.png +0 -0
  167. bootstack/assets/widgets/notebook-underline.png +0 -0
  168. bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
  169. bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
  170. bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
  171. bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
  172. bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
  173. bootstack/assets/widgets/progress-trough-vertical.png +0 -0
  174. bootstack/assets/widgets/radio-selected.png +0 -0
  175. bootstack/assets/widgets/radio-unselected.png +0 -0
  176. bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
  177. bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
  178. bootstack/assets/widgets/separator-horizontal.png +0 -0
  179. bootstack/assets/widgets/separator-vertical.png +0 -0
  180. bootstack/assets/widgets/slider-handle-focus.png +0 -0
  181. bootstack/assets/widgets/slider-handle.png +0 -0
  182. bootstack/assets/widgets/slider-track-horizontal.png +0 -0
  183. bootstack/assets/widgets/slider-track-vertical.png +0 -0
  184. bootstack/assets/widgets/switch-off.png +0 -0
  185. bootstack/assets/widgets/switch-on.png +0 -0
  186. bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
  187. bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
  188. bootstack/assets/widgets/tabs-pill.png +0 -0
  189. bootstack/cli/__init__.py +124 -0
  190. bootstack/cli/__main__.py +6 -0
  191. bootstack/cli/add.py +439 -0
  192. bootstack/cli/build.py +115 -0
  193. bootstack/cli/config.py +287 -0
  194. bootstack/cli/demo.py +1267 -0
  195. bootstack/cli/doctor.py +195 -0
  196. bootstack/cli/list_cmd.py +71 -0
  197. bootstack/cli/promote.py +120 -0
  198. bootstack/cli/pyinstaller.py +246 -0
  199. bootstack/cli/run.py +99 -0
  200. bootstack/cli/start.py +105 -0
  201. bootstack/cli/templates/__init__.py +861 -0
  202. bootstack/constants.py +325 -0
  203. bootstack/core/__init__.py +34 -0
  204. bootstack/core/capabilities/__init__.py +45 -0
  205. bootstack/core/capabilities/after.py +103 -0
  206. bootstack/core/capabilities/bind.py +154 -0
  207. bootstack/core/capabilities/bindtags.py +112 -0
  208. bootstack/core/capabilities/busy.py +61 -0
  209. bootstack/core/capabilities/clipboard.py +88 -0
  210. bootstack/core/capabilities/focus.py +118 -0
  211. bootstack/core/capabilities/grab.py +65 -0
  212. bootstack/core/capabilities/grid.py +188 -0
  213. bootstack/core/capabilities/localization.py +231 -0
  214. bootstack/core/capabilities/pack.py +119 -0
  215. bootstack/core/capabilities/place.py +92 -0
  216. bootstack/core/capabilities/selection.py +136 -0
  217. bootstack/core/capabilities/signals.py +242 -0
  218. bootstack/core/capabilities/winfo.py +315 -0
  219. bootstack/core/colorutils.py +234 -0
  220. bootstack/core/exceptions.py +95 -0
  221. bootstack/core/images.py +283 -0
  222. bootstack/core/localization/README.md +90 -0
  223. bootstack/core/localization/__init__.py +13 -0
  224. bootstack/core/localization/intl_format.py +580 -0
  225. bootstack/core/localization/msgcat.py +425 -0
  226. bootstack/core/localization/specs.py +143 -0
  227. bootstack/core/mixins/__init__.py +1 -0
  228. bootstack/core/mixins/ttk_state.py +35 -0
  229. bootstack/core/mixins/widget.py +132 -0
  230. bootstack/core/publisher.py +147 -0
  231. bootstack/core/signals/README.md +112 -0
  232. bootstack/core/signals/__init__.py +8 -0
  233. bootstack/core/signals/integration.py +100 -0
  234. bootstack/core/signals/signal.py +317 -0
  235. bootstack/core/signals/types.py +4 -0
  236. bootstack/core/validation/__init__.py +5 -0
  237. bootstack/core/validation/types.py +13 -0
  238. bootstack/core/validation/validation_result.py +17 -0
  239. bootstack/core/validation/validation_rules.py +112 -0
  240. bootstack/core/variables.py +62 -0
  241. bootstack/datasource/README.md +607 -0
  242. bootstack/datasource/__init__.py +51 -0
  243. bootstack/datasource/base.py +474 -0
  244. bootstack/datasource/file_source.py +541 -0
  245. bootstack/datasource/memory_source.py +482 -0
  246. bootstack/datasource/sqlite_source.py +453 -0
  247. bootstack/datasource/types.py +259 -0
  248. bootstack/dialogs/__init__.py +56 -0
  249. bootstack/dialogs/colorchooser.py +674 -0
  250. bootstack/dialogs/colordropper.py +257 -0
  251. bootstack/dialogs/datedialog.py +404 -0
  252. bootstack/dialogs/dialog.py +514 -0
  253. bootstack/dialogs/filterdialog.py +358 -0
  254. bootstack/dialogs/fontdialog.py +339 -0
  255. bootstack/dialogs/formdialog.py +541 -0
  256. bootstack/dialogs/message.py +489 -0
  257. bootstack/dialogs/query.py +561 -0
  258. bootstack/py.typed +1 -0
  259. bootstack/runtime/__init__.py +3 -0
  260. bootstack/runtime/app.py +879 -0
  261. bootstack/runtime/base_window.py +786 -0
  262. bootstack/runtime/events.py +399 -0
  263. bootstack/runtime/menu.py +510 -0
  264. bootstack/runtime/shortcuts.py +423 -0
  265. bootstack/runtime/tk_patch.py +31 -0
  266. bootstack/runtime/toplevel.py +131 -0
  267. bootstack/runtime/utility.py +371 -0
  268. bootstack/runtime/visual_focus.py +228 -0
  269. bootstack/runtime/window_utilities.py +1043 -0
  270. bootstack/style/__init__.py +5498 -0
  271. bootstack/style/bootstyle.py +507 -0
  272. bootstack/style/bootstyle_builder_base.py +752 -0
  273. bootstack/style/bootstyle_builder_mixed.py +93 -0
  274. bootstack/style/bootstyle_builder_tk.py +109 -0
  275. bootstack/style/bootstyle_builder_ttk.py +354 -0
  276. bootstack/style/builders/__init__.py +51 -0
  277. bootstack/style/builders/badge.py +44 -0
  278. bootstack/style/builders/button.py +453 -0
  279. bootstack/style/builders/buttongroup.py +344 -0
  280. bootstack/style/builders/calendar.py +271 -0
  281. bootstack/style/builders/checkbutton.py +95 -0
  282. bootstack/style/builders/combobox.py +112 -0
  283. bootstack/style/builders/contextmenu.py +268 -0
  284. bootstack/style/builders/entry.py +83 -0
  285. bootstack/style/builders/expander.py +171 -0
  286. bootstack/style/builders/field.py +312 -0
  287. bootstack/style/builders/frame.py +27 -0
  288. bootstack/style/builders/label.py +28 -0
  289. bootstack/style/builders/labelframe.py +41 -0
  290. bootstack/style/builders/listview.py +267 -0
  291. bootstack/style/builders/menubar.py +74 -0
  292. bootstack/style/builders/menubutton.py +408 -0
  293. bootstack/style/builders/notebook.py +316 -0
  294. bootstack/style/builders/panedwindow.py +25 -0
  295. bootstack/style/builders/progressbar.py +71 -0
  296. bootstack/style/builders/radiobutton.py +68 -0
  297. bootstack/style/builders/scale.py +66 -0
  298. bootstack/style/builders/scrollbar.py +360 -0
  299. bootstack/style/builders/separator.py +45 -0
  300. bootstack/style/builders/sidenav.py +313 -0
  301. bootstack/style/builders/sizegrip.py +15 -0
  302. bootstack/style/builders/spinbox.py +119 -0
  303. bootstack/style/builders/switch.py +67 -0
  304. bootstack/style/builders/tabitem.py +205 -0
  305. bootstack/style/builders/toolbutton.py +260 -0
  306. bootstack/style/builders/tooltip.py +26 -0
  307. bootstack/style/builders/treeview.py +269 -0
  308. bootstack/style/builders/utils.py +404 -0
  309. bootstack/style/builders_tk/__init__.py +16 -0
  310. bootstack/style/builders_tk/defaults.py +229 -0
  311. bootstack/style/element.py +173 -0
  312. bootstack/style/style.py +499 -0
  313. bootstack/style/theme_provider.py +449 -0
  314. bootstack/style/tk_patch.py +5 -0
  315. bootstack/style/token_maps.py +42 -0
  316. bootstack/style/types.py +32 -0
  317. bootstack/style/typography.py +527 -0
  318. bootstack/style/utility.py +696 -0
  319. bootstack/themes/__init__.py +12 -0
  320. bootstack/themes/standard.py +415 -0
  321. bootstack/themes/user.py +45 -0
  322. bootstack/widgets/__init__.py +53 -0
  323. bootstack/widgets/composites/__init__.py +38 -0
  324. bootstack/widgets/composites/accordion.py +385 -0
  325. bootstack/widgets/composites/appshell.py +445 -0
  326. bootstack/widgets/composites/buttongroup.py +391 -0
  327. bootstack/widgets/composites/calendar.py +914 -0
  328. bootstack/widgets/composites/compositeframe.py +282 -0
  329. bootstack/widgets/composites/contextmenu.py +1754 -0
  330. bootstack/widgets/composites/dateentry.py +261 -0
  331. bootstack/widgets/composites/dropdownbutton.py +190 -0
  332. bootstack/widgets/composites/expander.py +508 -0
  333. bootstack/widgets/composites/field.py +448 -0
  334. bootstack/widgets/composites/floodgauge.py +434 -0
  335. bootstack/widgets/composites/form.py +983 -0
  336. bootstack/widgets/composites/labeledscale.py +209 -0
  337. bootstack/widgets/composites/list/__init__.py +15 -0
  338. bootstack/widgets/composites/list/listitem.py +733 -0
  339. bootstack/widgets/composites/list/listview.py +1507 -0
  340. bootstack/widgets/composites/menubar.py +303 -0
  341. bootstack/widgets/composites/meter.py +882 -0
  342. bootstack/widgets/composites/numericentry.py +183 -0
  343. bootstack/widgets/composites/pagestack.py +330 -0
  344. bootstack/widgets/composites/passwordentry.py +149 -0
  345. bootstack/widgets/composites/pathentry.py +223 -0
  346. bootstack/widgets/composites/radiogroup.py +466 -0
  347. bootstack/widgets/composites/scrolledtext.py +388 -0
  348. bootstack/widgets/composites/scrolledtext.pyi +186 -0
  349. bootstack/widgets/composites/scrollview.py +675 -0
  350. bootstack/widgets/composites/selectbox.py +544 -0
  351. bootstack/widgets/composites/sidenav/__init__.py +24 -0
  352. bootstack/widgets/composites/sidenav/group.py +485 -0
  353. bootstack/widgets/composites/sidenav/header.py +83 -0
  354. bootstack/widgets/composites/sidenav/item.py +413 -0
  355. bootstack/widgets/composites/sidenav/separator.py +51 -0
  356. bootstack/widgets/composites/sidenav/view.py +919 -0
  357. bootstack/widgets/composites/spinnerentry.py +232 -0
  358. bootstack/widgets/composites/tableview/__init__.py +5 -0
  359. bootstack/widgets/composites/tableview/tableview.py +2254 -0
  360. bootstack/widgets/composites/tableview/types.py +169 -0
  361. bootstack/widgets/composites/tabs/__init__.py +6 -0
  362. bootstack/widgets/composites/tabs/tabitem.py +372 -0
  363. bootstack/widgets/composites/tabs/tabs.py +478 -0
  364. bootstack/widgets/composites/tabs/tabview.py +352 -0
  365. bootstack/widgets/composites/textentry.py +90 -0
  366. bootstack/widgets/composites/timeentry.py +189 -0
  367. bootstack/widgets/composites/toast.py +364 -0
  368. bootstack/widgets/composites/togglegroup.py +382 -0
  369. bootstack/widgets/composites/toolbar.py +393 -0
  370. bootstack/widgets/composites/tooltip.py +404 -0
  371. bootstack/widgets/internal/__init__.py +0 -0
  372. bootstack/widgets/internal/wrapper_base.py +304 -0
  373. bootstack/widgets/mixins/__init__.py +25 -0
  374. bootstack/widgets/mixins/configure_mixin.py +186 -0
  375. bootstack/widgets/mixins/entry_mixin.py +70 -0
  376. bootstack/widgets/mixins/font_mixin.py +346 -0
  377. bootstack/widgets/mixins/icon_mixin.py +38 -0
  378. bootstack/widgets/mixins/localization_mixin.py +255 -0
  379. bootstack/widgets/mixins/signal_mixin.py +272 -0
  380. bootstack/widgets/mixins/validation_mixin.py +204 -0
  381. bootstack/widgets/parts/__init__.py +11 -0
  382. bootstack/widgets/parts/numberentry_part.py +345 -0
  383. bootstack/widgets/parts/spinnerentry_part.py +394 -0
  384. bootstack/widgets/parts/textentry_part.py +344 -0
  385. bootstack/widgets/primitives/__init__.py +55 -0
  386. bootstack/widgets/primitives/badge.py +44 -0
  387. bootstack/widgets/primitives/button.py +89 -0
  388. bootstack/widgets/primitives/card.py +66 -0
  389. bootstack/widgets/primitives/checkbutton.py +124 -0
  390. bootstack/widgets/primitives/checktoggle.py +53 -0
  391. bootstack/widgets/primitives/combobox.py +165 -0
  392. bootstack/widgets/primitives/entry.py +98 -0
  393. bootstack/widgets/primitives/frame.py +206 -0
  394. bootstack/widgets/primitives/gridframe.py +479 -0
  395. bootstack/widgets/primitives/label.py +95 -0
  396. bootstack/widgets/primitives/labelframe.py +63 -0
  397. bootstack/widgets/primitives/menubutton.py +118 -0
  398. bootstack/widgets/primitives/notebook.py +551 -0
  399. bootstack/widgets/primitives/optionmenu.py +248 -0
  400. bootstack/widgets/primitives/packframe.py +228 -0
  401. bootstack/widgets/primitives/panedwindow.py +58 -0
  402. bootstack/widgets/primitives/progressbar.py +95 -0
  403. bootstack/widgets/primitives/radiobutton.py +115 -0
  404. bootstack/widgets/primitives/radiotoggle.py +50 -0
  405. bootstack/widgets/primitives/scale.py +85 -0
  406. bootstack/widgets/primitives/scrollbar.py +56 -0
  407. bootstack/widgets/primitives/separator.py +56 -0
  408. bootstack/widgets/primitives/sizegrip.py +47 -0
  409. bootstack/widgets/primitives/spinbox.py +91 -0
  410. bootstack/widgets/primitives/switch.py +41 -0
  411. bootstack/widgets/primitives/treeview.py +77 -0
  412. bootstack/widgets/types.py +20 -0
  413. bootstack-0.1.0a1.dist-info/METADATA +196 -0
  414. bootstack-0.1.0a1.dist-info/RECORD +419 -0
  415. bootstack-0.1.0a1.dist-info/WHEEL +5 -0
  416. bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
  417. bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
  418. bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
  419. bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,183 @@
1
+ """Numeric entry field widget with spin buttons.
2
+
3
+ Provides a specialized entry field for numeric input with increment/decrement
4
+ buttons and keyboard/mouse wheel support.
5
+ """
6
+
7
+ from typing import Any
8
+ from tkinter import TclError
9
+ from typing_extensions import Unpack
10
+
11
+ from bootstack.widgets.primitives.button import Button
12
+ from bootstack.widgets.composites.field import Field, FieldOptions
13
+ from bootstack.widgets.mixins import configure_delegate
14
+ from bootstack.widgets.types import Master
15
+
16
+
17
+ class NumericEntry(Field):
18
+ """A numeric entry field widget with increment/decrement spin buttons.
19
+
20
+ Extends Field to provide numeric input with spin buttons, bounds validation,
21
+ keyboard stepping (Up/Down arrows), mouse wheel support, and optional wrapping.
22
+
23
+ !!! note "Events"
24
+
25
+ - `<<Increment>>`: Fired when increment is requested (before step occurs).
26
+ - `<<Decrement>>`: Fired when decrement is requested (before step occurs).
27
+ - `<<Change>>`: Fired when value changes after commit.
28
+ - `<<Input>>`: Fired on each keystroke.
29
+ - `<<Valid>>`: Fired when validation passes.
30
+ - `<<Invalid>>`: Fired when validation fails.
31
+
32
+ Attributes:
33
+ entry_widget (NumberEntryPart): The underlying entry widget.
34
+ label_widget (Label): The label widget.
35
+ message_widget (Label): The message label widget.
36
+ addons (dict[str, Widget]): Dictionary of inserted addon widgets by name.
37
+ variable (Variable): Tkinter Variable linked to entry text.
38
+ signal (Signal): Signal object for reactive updates.
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ master: Master = None,
44
+ value: int | float = 0,
45
+ label: str = None,
46
+ message: str = None,
47
+ show_spin_buttons: bool = True,
48
+ minvalue: int | float | None = None,
49
+ maxvalue: int | float | None = None,
50
+ increment: int | float = 1,
51
+ **kwargs: Unpack[FieldOptions]
52
+ ):
53
+ """Initialize a NumericEntry widget.
54
+
55
+ Creates a numeric entry field with optional label, validation, bounds
56
+ constraints, and increment/decrement spin buttons. The widget supports
57
+ keyboard stepping (Up/Down arrows), mouse wheel interaction, and optional
58
+ value wrapping at boundaries.
59
+
60
+ Args:
61
+ master: Parent widget. If None, uses the default root window.
62
+ value (int | float): Initial numeric value to display.
63
+ label (str): Optional label text to display above the entry field.
64
+ If `required=True`, an asterisk (*) is automatically appended.
65
+ message (str): Optional message text to display below the entry field.
66
+ Used for hints or help text. Replaced by validation errors when
67
+ validation fails.
68
+ show_spin_buttons (bool): If True, displays increment/decrement buttons
69
+ (plus and minus icons) to the right of the entry.
70
+ minvalue (int | float): Minimum allowed value (inclusive). Values below
71
+ this will be clamped or wrapped depending on the wrap setting.
72
+ maxvalue (int | float): Maximum allowed value (inclusive). Values above
73
+ this will be clamped or wrapped depending on the wrap setting.
74
+ increment (int | float): Step size for increment/decrement operations.
75
+
76
+ Other Parameters:
77
+ wrap (bool): If True, values wrap around at min/max boundaries.
78
+ value_format (str): Number format specification for IntlFormatter.
79
+ Examples: `'decimal'`, `'percent'`, `'currency'`, `'#,##0.00'`.
80
+ locale (str): Locale identifier for number formatting (e.g., `'en_US'`).
81
+ required (bool): If True, field cannot be empty.
82
+ accent (str): Accent token for the focus ring and active border.
83
+ bootstyle (str): DEPRECATED - Use `accent` instead.
84
+ allow_blank (bool): If True, empty input is allowed (sets value to None).
85
+ cursor (str): Cursor style when hovering.
86
+ exportselection (bool): Export selection to clipboard.
87
+ font (str): Font for text display.
88
+ foreground (str): Text color.
89
+ initial_focus (bool): If True, widget receives focus on creation.
90
+ justify (str): Text alignment (`'left'`, `'center'`, `'right'`).
91
+ show_message (bool): If True, displays message area.
92
+ padding (int | tuple): Padding around entry widget.
93
+ takefocus (bool): If True, widget accepts Tab focus.
94
+ textvariable (Variable): Tkinter Variable to link with text.
95
+ textsignal (Signal): Signal object for reactive updates.
96
+ width (int): Width in characters.
97
+ xscrollcommand (Callable): Callback for horizontal scrolling.
98
+ """
99
+ super().__init__(
100
+ master, value=value, label=label, message=message, minvalue=minvalue, maxvalue=maxvalue, kind="numeric",
101
+ increment=increment, **kwargs)
102
+
103
+ self._show_spin_buttons = show_spin_buttons
104
+
105
+ # passthrough methods
106
+ self.on_increment = self.entry_widget.on_increment
107
+ self.off_increment = self.entry_widget.off_increment
108
+ self.on_decrement = self.entry_widget.on_decrement
109
+ self.off_decrement = self.entry_widget.off_decrement
110
+ self.step = self.entry_widget.step
111
+
112
+ # pack info
113
+ self._increment_pack_info = {}
114
+ self._decrement_pack_info = {}
115
+
116
+ # buttons
117
+ self.insert_addon(
118
+ Button, position="after", name="decrement", icon="dash", command=self.decrement, icon_only=True)
119
+ self.insert_addon(
120
+ Button, position="after", name="increment", icon="plus", command=self.increment, icon_only=True)
121
+ self._delegate_show_spin_buttons(show_spin_buttons)
122
+
123
+ self.entry_widget.bind('<<StateChanged>>', self._on_entry_state_changed, add=True)
124
+ self._update_spin_button_states()
125
+
126
+ @property
127
+ def increment_widget(self):
128
+ """Get the increment spin button widget."""
129
+ return self.addons['increment']
130
+
131
+ @property
132
+ def decrement_widget(self):
133
+ """Get the decrement spin button widget."""
134
+ return self.addons['decrement']
135
+
136
+ def increment(self):
137
+ """Increment the numeric value by one step."""
138
+ if not self._entry_is_interactive():
139
+ return
140
+ self.entry_widget.event_generate("<<Increment>>")
141
+
142
+ def decrement(self):
143
+ """Decrement the numeric value by one step."""
144
+ if not self._entry_is_interactive():
145
+ return
146
+ self.entry_widget.event_generate("<<Decrement>>")
147
+
148
+ def _entry_is_interactive(self) -> bool:
149
+ """Return True if the entry widget is not disabled or readonly."""
150
+ state = self.entry_widget.state()
151
+ return 'disabled' not in state and 'readonly' not in state
152
+
153
+ def _on_entry_state_changed(self, _: Any = None):
154
+ """Sync spin buttons whenever the entry state mutates."""
155
+ self._update_spin_button_states()
156
+
157
+ def _update_spin_button_states(self):
158
+ """Enable or disable spin buttons based on entry interactivity."""
159
+ state_value = 'disabled' if not self._entry_is_interactive() else '!disabled'
160
+ for control in (self.increment_widget, self.decrement_widget):
161
+ try:
162
+ control.configure(state=state_value)
163
+ except TclError:
164
+ pass
165
+
166
+ @configure_delegate('show_spin_buttons')
167
+ def _delegate_show_spin_buttons(self, value: bool = None):
168
+ """Get or set the visibility of spin buttons."""
169
+ if value is None:
170
+ return self._show_spin_buttons
171
+ else:
172
+ self._show_spin_buttons = value
173
+ if value:
174
+ if not self.increment_widget.winfo_ismapped():
175
+ self.increment_widget.pack(**self._increment_pack_info)
176
+ if not self.decrement_widget.winfo_ismapped():
177
+ self.decrement_widget.pack(**self._decrement_pack_info)
178
+ else:
179
+ self._increment_pack_info = self.increment_widget.pack_info()
180
+ self._decrement_pack_info = self.decrement_widget.pack_info()
181
+ self.increment_widget.pack_forget()
182
+ self.decrement_widget.pack_forget()
183
+ return None
@@ -0,0 +1,330 @@
1
+ import tkinter
2
+ from collections.abc import ValuesView
3
+ from typing import Any, Callable
4
+
5
+ from typing_extensions import TypedDict, Unpack
6
+
7
+ from bootstack.widgets.primitives import Frame
8
+ from bootstack.widgets.types import Master
9
+ from bootstack.core import NavigationError
10
+
11
+
12
+ class PageOptions(TypedDict, total=False):
13
+ padding: Any
14
+ width: int
15
+ height: int
16
+ style: str
17
+ cursor: str
18
+ show_border: bool
19
+ accent: str
20
+ variant: str
21
+ surface: str
22
+ style_options: dict[str, Any]
23
+
24
+
25
+ class PageStackKwargs(TypedDict, total=False):
26
+ takefocus: bool
27
+ width: int
28
+ height: int
29
+ padding: Any
30
+
31
+
32
+ class PageStack(Frame):
33
+ """A navigation container widget for managing multiple pages with history support.
34
+
35
+ PageStack provides a stack-based navigation system where only one page is
36
+ visible at a time. It maintains a navigation history, allowing users to move
37
+ backward and forward through pages similar to a web browser.
38
+
39
+ !!! note "Events"
40
+
41
+ - `<<PageUnmount>>`: Triggered when the current page is hidden.
42
+ - `<<PageWillMount>>`: Triggered before a new page is displayed.
43
+ - `<<PageMount>>`: Triggered after a new page is displayed.
44
+ - `<<PageChange>>`: Triggered after page navigation completes.
45
+
46
+ All events provide `event.data` with keys: `page`, `prev_page`, `prev_data`,
47
+ `nav` ('push', 'back', 'forward'), `index`, `length`, `can_back`,
48
+ `can_forward`.
49
+ """
50
+
51
+ def __init__(self, master: Master = None, **kwargs: Unpack[PageStackKwargs]):
52
+ """Initialize a new PageStack instance.
53
+
54
+ Creates an empty PageStack with no pages and no navigation history.
55
+ The widget inherits from Frame and supports all standard Frame
56
+ configuration options.
57
+
58
+ Args:
59
+ master: Parent widget. If None, uses the default root window.
60
+
61
+ Other Parameters:
62
+ takefocus (bool): If True, the widget can receive keyboard focus.
63
+ width (int): Width of the PageStack in pixels.
64
+ height (int): Height of the PageStack in pixels.
65
+ padding (int | tuple): Padding around the PageStack (can be a single value
66
+ or tuple of (left, top, right, bottom)).
67
+
68
+ Note:
69
+ Pages must be added using add() before navigation can occur.
70
+ The PageStack starts with an empty history and no current page.
71
+ """
72
+ super().__init__(master, **kwargs)
73
+ self._pages: dict[str, tkinter.Widget] = {}
74
+ self._current: str | None = None
75
+ self._history: list[tuple[str, dict]] = []
76
+ self._index: int = -1
77
+
78
+ def add(self, key: str, page: tkinter.Widget = None, **kwargs) -> tkinter.Widget:
79
+ """Add a page to the stack, optionally creating a Frame.
80
+
81
+ Args:
82
+ key (str): Unique identifier for the page (required for navigation).
83
+ page (Widget | None): The widget to add. If None, creates a Frame.
84
+ **kwargs: When page is None, these are passed to Frame (e.g., padding, color, variant).
85
+
86
+ Returns:
87
+ Widget: The page widget (passed or created Frame).
88
+
89
+ Raises:
90
+ NavigationError: If a page with the given key already exists.
91
+ ValueError: If key is an empty string.
92
+ """
93
+ if not key:
94
+ raise ValueError("Page key cannot be an empty string")
95
+ if key in self._pages:
96
+ raise NavigationError(f"Page {key} already exists")
97
+
98
+ if page is None:
99
+ page = Frame(self, **kwargs)
100
+
101
+ self._pages[key] = page
102
+ page.pack(fill='both', expand=True)
103
+ page.pack_forget()
104
+ return page
105
+
106
+ def remove(self, key: str) -> None:
107
+ """Remove a page from the stack and destroy its widget.
108
+
109
+ Args:
110
+ key: The identifier of the page to remove
111
+
112
+ Note:
113
+ If the removed page is currently displayed, the current page
114
+ will be set to None without navigating to another page.
115
+ """
116
+ if key in self._pages:
117
+ page = self._pages.pop(key)
118
+ page.destroy()
119
+ if self._current == key:
120
+ self._current = None
121
+
122
+ def navigate(
123
+ self,
124
+ key: str,
125
+ data: dict | None = None,
126
+ replace: bool = False,
127
+ _nav: str = 'push',
128
+ _prev: tuple[str, dict] | None = None
129
+ ) -> None:
130
+ """Navigate to the page with the given key.
131
+
132
+ This method handles page transitions, manages navigation history,
133
+ and triggers lifecycle events (`<<PageUnmount>>`, `<<PageWillMount>>`,
134
+ `<<PageMount>>`, `<<PageChange>>`).
135
+
136
+ Args:
137
+ key: The identifier of the page to navigate to
138
+ data: Optional data to pass to the page and include in event payloads
139
+ replace: If True, replace the current history entry instead of adding a new one
140
+ _nav: Internal parameter indicating navigation type ('push', 'back', 'forward')
141
+ _prev: Internal parameter for preserving previous page state
142
+
143
+ Raises:
144
+ NavigationError: If the page with the given key does not exist
145
+ ValueError: If key is an empty string
146
+
147
+ Note:
148
+ Event payloads include: page, prev_page, prev_data, nav, index,
149
+ length, can_back, and can_forward.
150
+ """
151
+ if not key:
152
+ raise ValueError("Page key cannot be an empty string")
153
+ if key not in self._pages:
154
+ raise NavigationError(f"Page {key} does not exist")
155
+
156
+ # Normalize data to empty dict if None
157
+ if data is None:
158
+ data = {}
159
+
160
+ # Snapshot "previous" BEFORE mutating history
161
+ if _prev is None:
162
+ prev_key = self._current
163
+ prev_data = self._history[self._index][1] if self._index >= 0 else {}
164
+ else:
165
+ prev_key, prev_data = _prev
166
+
167
+ # Mutate history
168
+ if replace and 0 <= self._index < len(self._history):
169
+ self._history[self._index] = (key, data)
170
+ else:
171
+ if self._index < len(self._history) - 1:
172
+ self._history = self._history[:self._index + 1]
173
+ self._history.append((key, data))
174
+ self._index += 1
175
+
176
+ # Unmount previous page
177
+ if self._current is not None:
178
+ self._pages[self._current].event_generate('<<PageUnmount>>', when="tail")
179
+ self._pages[self._current].pack_forget()
180
+
181
+ # Normalized payload (self-contained snapshot)
182
+ payload = dict(data)
183
+ payload.update(
184
+ {
185
+ "page": key,
186
+ "prev_page": prev_key,
187
+ "prev_data": prev_data,
188
+ "nav": _nav,
189
+ "index": self._index,
190
+ "length": len(self._history),
191
+ "can_back": self._index > 0,
192
+ "can_forward": self._index < len(self._history) - 1,
193
+ }
194
+ )
195
+
196
+ # Mount and notify
197
+ page = self._pages[key]
198
+ page.event_generate('<<PageWillMount>>', data=payload, when="tail")
199
+ page.pack(fill='both', expand=True)
200
+ self._current = key
201
+ self.event_generate('<<PageMount>>', data=payload, when="tail")
202
+ self.event_generate('<<PageChange>>', data=payload, when="tail")
203
+
204
+ def back(self) -> None:
205
+ """Navigate to the previous page in the navigation history.
206
+
207
+ This method moves backward in the history stack if possible.
208
+ Does nothing if already at the first page.
209
+ """
210
+ if self._index > 0:
211
+ # Snapshot BEFORE changing index
212
+ prev = (self._current, self._history[self._index][1] if self._index >= 0 else {})
213
+ self._index -= 1
214
+ key, data = self._history[self._index]
215
+ # use replace=True to avoid pushing a new entry
216
+ # pass _prev to preserve the correct 'previous' snapshot
217
+ self.navigate(key, data=data, replace=True, _nav='back', _prev=prev)
218
+
219
+ def forward(self) -> None:
220
+ """Navigate to the next page in the navigation history.
221
+
222
+ This method moves forward in the history stack if possible.
223
+ Does nothing if already at the most recent page.
224
+ """
225
+ if self._index < len(self._history) - 1:
226
+ prev = (self._current, self._history[self._index][1] if self._index >= 0 else {})
227
+ self._index += 1
228
+ key, data = self._history[self._index]
229
+ self.navigate(key, data=data, replace=True, _nav='forward', _prev=prev)
230
+
231
+ def can_back(self) -> bool:
232
+ """Check if backward navigation is possible.
233
+
234
+ Returns:
235
+ True if there is a previous page in the history to navigate back to,
236
+ False otherwise.
237
+ """
238
+ return self._index > 0
239
+
240
+ def can_forward(self) -> bool:
241
+ """Check if forward navigation is possible.
242
+
243
+ Returns:
244
+ True if there is a next page in the history to navigate forward to,
245
+ False otherwise.
246
+ """
247
+ return self._index < len(self._history) - 1
248
+
249
+ def current(self) -> tuple[str, dict] | None:
250
+ """Return the current page key and its navigation data.
251
+
252
+ Returns:
253
+ A tuple of (page_key, data_dict) if a page is currently displayed,
254
+ None if no page is currently displayed.
255
+ """
256
+ if self._current is None:
257
+ return None
258
+ return self._current, (self._history[self._index][1] if self._index >= 0 else {})
259
+
260
+ def item(self, key: str) -> tkinter.Widget:
261
+ """Get a page by its key.
262
+
263
+ Args:
264
+ key: The identifier of the page to retrieve.
265
+
266
+ Returns:
267
+ The page widget.
268
+
269
+ Raises:
270
+ KeyError: If no page with the given key exists.
271
+ ValueError: If key is an empty string.
272
+ """
273
+ if not key:
274
+ raise ValueError("Page key cannot be an empty string")
275
+ if key not in self._pages:
276
+ raise KeyError(f"No page with key '{key}'")
277
+ return self._pages[key]
278
+
279
+ def items(self) -> tuple[tkinter.Widget, ...]:
280
+ """Get all page widgets in the stack.
281
+
282
+ Returns:
283
+ A tuple of all page widgets managed by this PageStack.
284
+ """
285
+ return tuple(self._pages.values())
286
+
287
+ def keys(self) -> tuple[str, ...]:
288
+ """Get all page keys.
289
+
290
+ Returns:
291
+ A tuple of all page keys in the stack.
292
+ """
293
+ return tuple(self._pages.keys())
294
+
295
+ def configure_item(self, key: str, option: Any = None, **kwargs: Any) -> Any:
296
+ """Query or configure the page configuration.
297
+
298
+ Args:
299
+ key: The identifier of the page to configure.
300
+ option: Optional configuration option to query.
301
+ **kwargs: Configuration options to set on the page widget.
302
+
303
+ Returns:
304
+ If option is provided, returns the value of that option.
305
+ Otherwise returns the result of configure() if kwargs are provided.
306
+
307
+ Raises:
308
+ KeyError: If no page with the given key exists.
309
+ ValueError: If key is an empty string.
310
+ """
311
+ if not key:
312
+ raise ValueError("Page key cannot be an empty string")
313
+ if key not in self._pages:
314
+ raise KeyError(f"No page with key '{key}'")
315
+ if option is not None:
316
+ return self._pages[key].cget(option)
317
+ else:
318
+ return self._pages[key].configure(**kwargs)
319
+
320
+ def on_page_changed(self, callback: Callable) -> str:
321
+ """Bind to `<<PageChange>>`. Callback receives `event.data` with navigation info.
322
+
323
+ Returns:
324
+ Binding identifier for use with off_page_changed().
325
+ """
326
+ return self.bind('<<PageChange>>', callback, add="+")
327
+
328
+ def off_page_changed(self, bind_id: str | None = None) -> None:
329
+ """Unbind from `<<PageChange>>`."""
330
+ self.unbind("<<PageChange>>", bind_id)
@@ -0,0 +1,149 @@
1
+ """Password entry field widget with visibility toggle.
2
+
3
+ Provides a specialized text entry field for password input with masked characters
4
+ and an optional visibility toggle button.
5
+ """
6
+
7
+ from bootstack.widgets.primitives.button import Button
8
+ from bootstack.widgets.composites.field import Field, FieldOptions
9
+ from bootstack.widgets.mixins.configure_mixin import configure_delegate
10
+ from bootstack.widgets.types import Master
11
+ from typing_extensions import Unpack
12
+
13
+
14
+ class PasswordEntry(Field):
15
+ """A password entry field widget with masked input and visibility toggle.
16
+
17
+ PasswordEntry extends the Field widget to provide password-specific functionality,
18
+ including character masking and a toggle button to temporarily reveal the password.
19
+ The widget automatically inserts a visibility toggle button (eye icon) that shows
20
+ the password while pressed and hides it when released.
21
+
22
+ !!! note "Events"
23
+
24
+ - `<<Input>>`: Triggered on each keystroke.
25
+ - `<<Change>>`: Triggered when value changes after commit.
26
+ - `<<Valid>>`: Triggered when validation passes.
27
+ - `<<Invalid>>`: Triggered when validation fails.
28
+ - `<<Validate>>`: Triggered after any validation.
29
+
30
+ Attributes:
31
+ entry_widget (TextEntryPart): The underlying text entry widget.
32
+ label_widget (Label): The label widget above the entry.
33
+ message_widget (Label): The message label widget below the entry.
34
+ addons (dict[str, Widget]): Dictionary of inserted addon widgets by name.
35
+ variable (Variable): Tkinter Variable linked to entry text.
36
+ signal (Signal): Signal object for reactive updates.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ master: Master = None,
42
+ value: str = None,
43
+ label: str = None,
44
+ message: str = None,
45
+ show_visibility_toggle: bool = True,
46
+ **kwargs: Unpack[FieldOptions]):
47
+ """Initialize a PasswordEntry widget.
48
+
49
+ Creates a password entry field with character masking and an optional
50
+ visibility toggle button. The widget automatically masks input characters
51
+ (default: '•') and provides a button to temporarily reveal the password
52
+ while pressed.
53
+
54
+ Args:
55
+ master: Parent widget. If None, uses the default root window.
56
+ value: Initial password value to display (masked). Default is None.
57
+ label: Optional label text to display above the entry field.
58
+ If required=True, an asterisk (*) is automatically appended.
59
+ message: Optional message text to display below the entry field.
60
+ Used for hints or help text. Replaced by validation errors when
61
+ validation fails.
62
+ show_visibility_toggle: If True, displays the visibility toggle button
63
+ (eye icon) that reveals the password while pressed. Default is True.
64
+ Can be changed at runtime via `configure(show_visibility_toggle=...)`.
65
+
66
+ Other Parameters:
67
+ show (str): Character to mask password input. Default is '•'.
68
+ required (bool): If True, field cannot be empty.
69
+ accent (str): Accent token for the focus ring and active border.
70
+ bootstyle (str): DEPRECATED - Use `accent` instead.
71
+ allow_blank (bool): Allow empty input.
72
+ cursor (str): Cursor style when hovering.
73
+ font (str): Font for text display.
74
+ foreground (str): Text color.
75
+ initial_focus (bool): If True, widget receives focus on creation.
76
+ justify (str): Text alignment.
77
+ show_message (bool): If True, displays message area.
78
+ padding (str): Padding around entry widget.
79
+ takefocus (bool): If True, widget accepts Tab focus.
80
+ textvariable (Variable): Tkinter Variable to link with text.
81
+ textsignal (Signal): Signal object for reactive updates.
82
+ width (int): Width in characters.
83
+
84
+ Note:
85
+ The visibility toggle button uses a press-and-hold interaction.
86
+ The password is only visible while the button is actively pressed,
87
+ providing a secure way to verify input without leaving it exposed.
88
+ """
89
+ # set default mask if not provided
90
+ self._show_indicator = kwargs.get('show', '•')
91
+ kwargs.setdefault('show', self._show_indicator)
92
+ super().__init__(master, value=value, label=label, message=message, **kwargs)
93
+
94
+ # configuration
95
+ self._show_visibility_toggle = show_visibility_toggle
96
+ self._show_visibility_pack = {}
97
+
98
+ self.insert_addon(
99
+ Button,
100
+ position="after",
101
+ name="visibility",
102
+ icon={"name": "eye", "state": [("pressed", "eye-slash")]},
103
+ compound="image",
104
+ icon_only=True
105
+ )
106
+ addon = self.addons['visibility']
107
+ addon.bind('<ButtonPress>', self._show_password, add=True)
108
+ addon.bind('<ButtonRelease>', self._hide_password, add=True)
109
+
110
+ # Apply initial visibility setting
111
+ if not show_visibility_toggle:
112
+ self._apply_visibility_toggle(False)
113
+
114
+ @property
115
+ def _visibility_toggle(self):
116
+ """Get the visibility toggle button widget."""
117
+ return self.addons['visibility']
118
+
119
+ def _show_password(self, _):
120
+ """Reveal the password by removing character masking."""
121
+ self.entry_widget['show'] = ''
122
+
123
+ def _hide_password(self, _):
124
+ """Hide the password by restoring character masking."""
125
+ self.entry_widget['show'] = self._show_indicator
126
+
127
+ # ------ Configuration Delegates ------
128
+
129
+ @configure_delegate('show_visibility_toggle')
130
+ def _delegate_show_visibility_toggle(self, value=None):
131
+ if value is None:
132
+ return self._show_visibility_toggle
133
+ else:
134
+ self._apply_visibility_toggle(value)
135
+ return None
136
+
137
+ def _apply_visibility_toggle(self, value: bool):
138
+ """Show or hide the visibility toggle button.
139
+
140
+ Args:
141
+ value: If True, show the toggle button. If False, hide it.
142
+ """
143
+ self._show_visibility_toggle = value
144
+ if value and not self._visibility_toggle.winfo_ismapped():
145
+ self._visibility_toggle.pack(**self._show_visibility_pack)
146
+ elif not value and self._visibility_toggle.winfo_ismapped():
147
+ self._show_visibility_pack = self._visibility_toggle.pack_info()
148
+ self._visibility_toggle.pack_forget()
149
+