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,371 @@
1
+ """Utility functions for bootstack.
2
+
3
+ This module provides various utility functions for common tasks in
4
+ ttksbootstrap applications, including high-DPI support, screen geometry
5
+ calculations, and color manipulations.
6
+
7
+ Functions:
8
+ enable_high_dpi_awareness: Enable high-DPI scaling on Windows/Linux
9
+ detect_scale_factor: Detect the appropriate scale factor for the display
10
+ scale_size: Scale a size value for high-DPI displays
11
+ get_desktop_geometry: Get the screen dimensions
12
+ get_asset_path: Get the path to an asset file
13
+
14
+ Example:
15
+ ```python
16
+ from bootstack.utility import enable_high_dpi_awareness
17
+ import bootstack as bs
18
+
19
+ # Enable high-DPI before creating window
20
+ enable_high_dpi_awareness()
21
+
22
+ root = bs.Window()
23
+ root.mainloop()
24
+ ```
25
+ """
26
+
27
+
28
+ def _platform_baseline() -> float:
29
+ """Return the default Tk scaling baseline for the current platform.
30
+
31
+ Tk scaling is measured in pixels-per-point (72 points per inch).
32
+ - Windows/Linux default DPI is 96 → baseline = 96/72 ≈ 1.334
33
+ - macOS default DPI is 72 → baseline = 72/72 = 1.0
34
+ (Retina pixel-doubling is handled by the OS, not Tk scaling)
35
+ """
36
+ import platform
37
+ if platform.system() == 'Darwin':
38
+ return 1.0
39
+ return 1.33398982438864281 # 96 DPI / 72
40
+
41
+
42
+ class _ScalingState:
43
+ """Internal class to store global scaling state."""
44
+ _scale_factor: float = 1.0
45
+ _baseline: float = _platform_baseline()
46
+
47
+ @classmethod
48
+ def set_scale_factor(cls, factor: float):
49
+ """Set the global scale factor."""
50
+ cls._scale_factor = factor
51
+
52
+ @classmethod
53
+ def get_scale_factor(cls) -> float:
54
+ """Get the current global scale factor."""
55
+ return cls._scale_factor
56
+
57
+ @classmethod
58
+ def get_ui_scale(cls) -> float:
59
+ """Get the UI scale factor (relative to baseline)."""
60
+ return cls._scale_factor / cls._baseline
61
+
62
+ @classmethod
63
+ def get_image_scale(cls, source_resolution: float = 2.0) -> float:
64
+ """Get the scale factor for images.
65
+
66
+ Args:
67
+ source_resolution: The resolution multiplier of source images.
68
+ For example, 2.0 means images are 2x resolution.
69
+
70
+ Returns:
71
+ The scale factor to apply to images.
72
+ """
73
+ return cls.get_ui_scale() / source_resolution
74
+
75
+
76
+ def enable_high_dpi_awareness(root=None, scaling=None):
77
+ """Enable high dpi awareness.
78
+
79
+ **Windows OS**
80
+ Call the method BEFORE creating the `Tk` object. No parameters
81
+ required. After the root is created, call again with the root
82
+ parameter to apply detected scaling.
83
+
84
+ **Linux OS**
85
+ Must provided the `root` and `scaling` parameters. Call the method
86
+ AFTER creating the `Tk` object. A number between 1.6 and 2.0 is
87
+ usually suffient to scale for high-dpi screen.
88
+
89
+ !!! warning
90
+ If the `root` argument is provided, then `scaling` must also
91
+ be provided. Otherwise, there is no effect.
92
+
93
+ Parameters:
94
+
95
+ root (tk.Tk):
96
+ The root widget
97
+
98
+ scaling (float or 'auto'):
99
+ Sets and queries the current scaling factor used by Tk to
100
+ convert between physical units (for example, points,
101
+ inches, or millimeters) and pixels. The number argument is
102
+ a floating point number that specifies the number of pixels
103
+ per point on window's display. If the window argument is
104
+ omitted, it defaults to the main window. If the number
105
+ argument is omitted, the current value of the scaling
106
+ factor is returned.
107
+
108
+ If set to 'auto', the scale factor will be detected
109
+ automatically from the system DPI settings.
110
+
111
+ A "point" is a unit of measurement equal to 1/72 inch. A
112
+ scaling factor of 1.0 corresponds to 1 pixel per point,
113
+ which is equivalent to a standard 72 dpi monitor. A scaling
114
+ factor of 1.25 would mean 1.25 pixels per point, which is
115
+ the setting for a 90 dpi monitor; setting the scaling factor
116
+ to 1.25 on a 72 dpi monitor would cause everything in the
117
+ application to be displayed 1.25 times as large as normal.
118
+ The initial value for the scaling factor is set when the
119
+ application starts, based on properties of the installed
120
+ monitor, but it can be changed at any time. Measurements
121
+ made after the scaling factor is changed will use the new
122
+ scaling factor, but it is undefined whether existing
123
+ widgets will resize themselves dynamically to accommodate
124
+ the new scaling factor.
125
+
126
+ Returns:
127
+
128
+ float:
129
+ The scaling factor that was applied, or None if no scaling
130
+ was applied.
131
+ """
132
+ import platform
133
+
134
+ # Enable DPI awareness on Windows
135
+ try:
136
+ from ctypes import windll
137
+ # Use shcore for better DPI awareness (Windows 8.1+)
138
+ try:
139
+ windll.shcore.SetProcessDpiAwareness(1)
140
+ except:
141
+ # Fallback to older API
142
+ windll.user32.SetProcessDPIAware()
143
+ except:
144
+ pass
145
+
146
+ # Apply scaling if root is provided
147
+ if root:
148
+ # Auto-detect scaling if requested
149
+ if scaling == 'auto':
150
+ scaling = detect_scale_factor(root)
151
+
152
+ if scaling:
153
+ try:
154
+ root.tk.call('tk', 'scaling', scaling)
155
+ # Store the scale factor globally
156
+ _ScalingState.set_scale_factor(scaling)
157
+ return scaling
158
+ except:
159
+ pass
160
+
161
+ return None
162
+
163
+
164
+ def detect_scale_factor(root):
165
+ """Detect the appropriate scale factor for the display.
166
+
167
+ This function attempts to detect the system DPI scaling and return
168
+ an appropriate scale factor for Tk. The scale factor is measured in
169
+ "pixels per point" (72 points per inch).
170
+
171
+ Parameters:
172
+
173
+ root (tk.Tk):
174
+ The root window (must be created before calling)
175
+
176
+ Returns:
177
+
178
+ float:
179
+ The detected scale factor in pixels per point
180
+ (e.g., 1.333 for 96 DPI, 2.0 for 144 DPI)
181
+ """
182
+ import platform
183
+
184
+ system = platform.system()
185
+
186
+ try:
187
+ if system == 'Windows':
188
+ # Try to get scale factor from Windows (Windows 8.1+)
189
+ try:
190
+ from ctypes import windll
191
+ # GetScaleFactorForDevice returns percentage (100, 125, 150, 200, etc.)
192
+ scale_percent = windll.shcore.GetScaleFactorForDevice(0)
193
+ # Convert to DPI: 100% = 96 DPI, 125% = 120 DPI, etc.
194
+ dpi = (96 * scale_percent) / 100
195
+ # Convert to tk scaling (pixels per point, 72 points per inch)
196
+ scale_factor = dpi / 72
197
+ return scale_factor
198
+ except:
199
+ pass
200
+
201
+ # Fallback: detect from current DPI
202
+ # This works on Linux, Mac, and older Windows
203
+ current_dpi = root.winfo_fpixels('1i')
204
+ # Tk scaling is pixels per point (72 points per inch)
205
+ scale_factor = current_dpi / 72
206
+ return scale_factor
207
+
208
+ except:
209
+ # If all else fails, return default for 96 DPI
210
+ return 96 / 72 # 1.333...
211
+
212
+
213
+ def get_image_name(image):
214
+ """Extract and return the tcl/tk image name from a PhotoImage
215
+ object.
216
+
217
+ Parameters:
218
+
219
+ image (ImageTk.PhotoImage):
220
+ A photoimage object.
221
+
222
+ Returns:
223
+
224
+ str:
225
+ The tcl/tk name of the photoimage object.
226
+ """
227
+ return image._PhotoImage__photo.name
228
+
229
+
230
+ def scale_size(widget=None, size=None):
231
+ """Scale the size based on the scaling factor of tkinter.
232
+ This is used most frequently to adjust the assets for
233
+ image-based widget layouts and padding values.
234
+
235
+ Can be called in two ways:
236
+ 1. scale_size(widget, size) - Legacy mode, calculates from widget
237
+ 2. scale_size(size) - Uses global scaling state
238
+
239
+ Parameters:
240
+
241
+ widget (Widget, optional):
242
+ The widget object. If provided, scaling is calculated from
243
+ the widget's tk instance. If None, uses global scaling state.
244
+
245
+ size (Union[int, List, Tuple]):
246
+ A single integer or an iterable of integers
247
+
248
+ Returns:
249
+
250
+ Union[int, List]:
251
+ An integer or list of integers representing the new size.
252
+
253
+ Examples:
254
+
255
+ >>> # Using widget (legacy mode)
256
+ >>> scaled = scale_size(my_widget, 10)
257
+
258
+ >>> # Using global state (new mode)
259
+ >>> scaled = scale_size(10)
260
+ """
261
+ # Handle both calling conventions
262
+ if widget is not None and size is None:
263
+ # Called as scale_size(size) - widget is actually the size
264
+ size = widget
265
+ factor = _ScalingState.get_ui_scale()
266
+ elif widget is not None and size is not None:
267
+ # Called as scale_size(widget, size) - legacy mode
268
+ BASELINE = _ScalingState._baseline
269
+ scaling = widget.tk.call('tk', 'scaling')
270
+ factor = scaling / BASELINE
271
+ else:
272
+ # Called as scale_size(size=size) - use global state
273
+ factor = _ScalingState.get_ui_scale()
274
+
275
+ if isinstance(size, int):
276
+ return int(size * factor)
277
+ elif isinstance(size, tuple) or isinstance(size, list):
278
+ return [int(x * factor) for x in size]
279
+ else:
280
+ return size
281
+
282
+
283
+ # --- Debug helpers ---------------------------------------------------------
284
+ def _debug_enabled() -> bool:
285
+ """Return True if debug logging is enabled.
286
+
287
+ Controlled via the environment variable `TTKBOOTSTRAP_DEBUG`.
288
+ Accepts: "1", "true", "yes" (case-insensitive) as truthy values.
289
+ """
290
+ import os
291
+ return str(os.environ.get("TTKBOOTSTRAP_DEBUG", "")).lower() in {"1", "true", "yes"}
292
+
293
+
294
+ def debug_log_exception(message: str = "") -> None:
295
+ """Print the current exception traceback if debug is enabled.
296
+
297
+ Args:
298
+ message: Optional context message to print before the traceback.
299
+ """
300
+ if not _debug_enabled():
301
+ return
302
+ try:
303
+ import traceback
304
+ if message:
305
+ print(f"TTKBootstrap DEBUG: {message}")
306
+ traceback.print_exc()
307
+ except Exception:
308
+ # Never raise from debug logging
309
+ pass
310
+
311
+
312
+ def center_on_parent(win, parent=None):
313
+ """Center `win` on parent or over its master if not given"""
314
+ win.update_idletasks() # ensure geometry
315
+ if parent is None:
316
+ parent = getattr(win, 'master', None) or win # root if no parent
317
+
318
+ # parent geometry
319
+ parent.update_idletasks()
320
+ px, py = parent.winfo_rootx(), parent.winfo_rooty()
321
+ pw, ph = parent.winfo_width(), parent.winfo_height()
322
+ if pw <= 1 or ph <= 1:
323
+ # not yet realized, fallback to requested size
324
+ pw, ph = parent.winfo_reqwidth(), parent.winfo_reqheight()
325
+
326
+ # window geometry
327
+ ww = win.winfo_width() or win.winfo_reqwidth()
328
+ wh = win.winfo_height() or win.winfo_reqheight()
329
+
330
+ x = px + (pw - ww) // 2
331
+ y = py + (ph - wh) // 2
332
+ win.geometry(f"{ww}x{wh}+{x}+{y}")
333
+
334
+
335
+ def bind_right_click(widget, handler, add: str | bool = '+'):
336
+ """Bind a right-click handler portably across Tk windowing systems.
337
+
338
+ On Win/Linux right-click maps to `<Button-3>`. On macOS Tk maps the
339
+ right mouse button (and two-finger trackpad click) to `<Button-2>`,
340
+ and Mac users also expect Ctrl+click as a context-menu trigger. This
341
+ helper binds the appropriate event(s) for the current platform so
342
+ callers don't have to repeat the platform check.
343
+
344
+ Args:
345
+ widget: Any Tk widget with a `.bind` method and a `.tk` attribute.
346
+ handler: Event handler callable (or Tcl command string).
347
+ add: Passed through to `bind`. Defaults to `'+'` so the helper
348
+ never silently replaces an existing binding.
349
+ """
350
+ widget.bind('<Button-3>', handler, add=add)
351
+ try:
352
+ winsys = widget.tk.call('tk', 'windowingsystem')
353
+ except Exception:
354
+ winsys = None
355
+ if winsys == 'aqua':
356
+ widget.bind('<Button-2>', handler, add=add)
357
+ widget.bind('<Control-Button-1>', handler, add=add)
358
+
359
+
360
+ def clamp(value, min_val, max_val):
361
+ """Return a value that is bounded by a minimum and maximum.
362
+
363
+ Args:
364
+ value: The value to evaluate.
365
+ min_val: The minimum allowed value.
366
+ max_val: The maximum allowed value.
367
+
368
+ Returns:
369
+ The value, constrained between `min_val` and `max_val`.
370
+ """
371
+ return min(max(value, min_val), max_val)
@@ -0,0 +1,228 @@
1
+ """Visual focus management for bootstack.
2
+
3
+ This module provides keyboard vs mouse focus distinction by leveraging
4
+ the TTK 'background' state as a "keyboard focus" indicator. This enables
5
+ style maps to show focus rings only for keyboard navigation (Tab), not
6
+ for mouse clicks.
7
+
8
+ The approach:
9
+ - Tab key press → sets 'background' state on newly focused widget
10
+ - FocusOut → removes 'background' state
11
+ - Mouse clicks never add 'background', so click focus is distinguishable
12
+
13
+ Style maps can then use:
14
+ ('background focus', ring_color) # Keyboard focus - show ring
15
+ ('focus', '') # Mouse focus - no ring
16
+
17
+ This matches the CSS :focus-visible behavior that modern browsers implement.
18
+
19
+ Programmatic focus:
20
+ The focus_set() and focus_force() methods accept a `visual_focus` parameter.
21
+ When True, the focus ring is shown as if the widget was focused via keyboard:
22
+
23
+ widget.focus_set(visual_focus=True) # Shows focus ring
24
+
25
+ Note:
26
+ The 'background' TTK state is normally used to indicate an inactive
27
+ window. Since this is rarely styled in practice, it's repurposed here
28
+ for keyboard focus tracking.
29
+ """
30
+
31
+ import tkinter as tk
32
+ from tkinter import TclError
33
+ from typing import Optional
34
+
35
+ _installed = False
36
+ _root_ref: Optional[tk.Misc] = None
37
+
38
+ # Store original focus methods
39
+ _original_focus_set = tk.Misc.focus_set
40
+ _original_focus_force = tk.Misc.focus_force
41
+
42
+
43
+ def _patched_focus_set(self, *, visual_focus: bool = False) -> None:
44
+ """Enhanced focus_set that optionally shows visual focus ring.
45
+
46
+ Args:
47
+ visual_focus: If True, show focus as if focused via keyboard. Default is False.
48
+
49
+ Examples:
50
+ ```python
51
+ # Normal programmatic focus (no ring)
52
+ entry.focus_set()
53
+
54
+ # Focus with visible ring (e.g., after validation error)
55
+ entry.focus_set(visual_focus=True)
56
+ ```
57
+ """
58
+ _original_focus_set(self)
59
+ if visual_focus:
60
+ try:
61
+ self.state(['background'])
62
+ except (TclError, AttributeError):
63
+ pass
64
+
65
+
66
+ def _patched_focus_force(self, *, visual_focus: bool = False) -> None:
67
+ """Enhanced focus_force that optionally shows visual focus ring.
68
+
69
+ Args:
70
+ visual_focus: If True, show focus as if focused via keyboard. Default is False.
71
+
72
+ Examples:
73
+ ```python
74
+ # Normal forced focus (no ring)
75
+ entry.focus_force()
76
+
77
+ # Forced focus with visible ring
78
+ entry.focus_force(visual_focus=True)
79
+ ```
80
+ """
81
+ _original_focus_force(self)
82
+ if visual_focus:
83
+ try:
84
+ self.state(['background'])
85
+ except (TclError, AttributeError):
86
+ pass
87
+
88
+
89
+ def _on_tab_focus(event: tk.Event) -> None:
90
+ """Handle Tab key press by marking the newly focused widget.
91
+
92
+ Uses after_idle to wait for focus to actually move before
93
+ querying focus_get() and setting the background state.
94
+ """
95
+ root = event.widget.winfo_toplevel()
96
+
97
+ def set_keyboard_focus_state():
98
+ widget = root.focus_get()
99
+ if widget is None:
100
+ return
101
+ try:
102
+ widget.state(['background'])
103
+ except (TclError, AttributeError):
104
+ pass # Widget doesn't support state (non-TTK)
105
+
106
+ root.after_idle(set_keyboard_focus_state)
107
+
108
+
109
+ def _on_focus_out(event: tk.Event) -> None:
110
+ """Clear the keyboard focus indicator when widget loses focus."""
111
+ try:
112
+ event.widget.state(['!background'])
113
+ except (TclError, AttributeError):
114
+ pass # Widget doesn't support state (non-TTK)
115
+
116
+
117
+ def install_visual_focus(root: tk.Misc = None) -> None:
118
+ """Install keyboard focus tracking for the application.
119
+
120
+ This function sets up global event bindings that track whether
121
+ focus was acquired via keyboard (Tab) or mouse click, enabling
122
+ style maps to show focus rings only for keyboard navigation.
123
+
124
+ Args:
125
+ root: Optional root widget. If not provided, bindings are
126
+ set up to work with any root via bind_class on Tk.
127
+
128
+ Note:
129
+ This is called automatically when bootstack is imported.
130
+ You typically don't need to call this manually.
131
+
132
+ Examples:
133
+ Style builders can use the 'background' state to distinguish:
134
+
135
+ ```python
136
+ b.map_style(ttk_style,
137
+ focuscolor=[
138
+ ('background focus', ring_color), # Keyboard focus
139
+ ('focus', ''), # Mouse focus
140
+ ('', ''),
141
+ ]
142
+ )
143
+ ```
144
+ """
145
+ global _installed, _root_ref
146
+
147
+ if _installed:
148
+ return
149
+
150
+ # Patch focus_set and focus_force to support visual_focus parameter
151
+ tk.Misc.focus_set = _patched_focus_set
152
+ tk.Misc.focus_force = _patched_focus_force
153
+
154
+ # Bind Tab key globally - works even before any root exists
155
+ # by using bind_class on the base Tk class
156
+ tk.Tk.bind_all = _bind_all_with_focus
157
+
158
+ _installed = True
159
+
160
+
161
+ def _bind_all_with_focus(self, sequence=None, func=None, add=None):
162
+ """Wrapper for bind_all that installs focus tracking on first call."""
163
+ global _root_ref
164
+
165
+ # Install our bindings on first bind_all call (when root exists)
166
+ if _root_ref is None:
167
+ _root_ref = self
168
+ # Bind Tab on root window - the after_idle callback will set state
169
+ # on whatever widget receives focus (forward or backward)
170
+ self.bind('<Tab>', _on_tab_focus, '+')
171
+ # Clear background state on focus out (needs bind_all for all widgets)
172
+ self.bind_all('<FocusOut>', _on_focus_out, '+')
173
+
174
+ # Call original bind_all
175
+ return tk.Misc.bind_all(self, sequence, func, add)
176
+
177
+
178
+ def uninstall_visual_focus() -> None:
179
+ """Remove keyboard focus tracking bindings.
180
+
181
+ This restores the original behavior where focus state doesn't
182
+ distinguish between keyboard and mouse focus.
183
+
184
+ Note:
185
+ After calling this, style maps using 'background focus' will
186
+ no longer show focus rings for keyboard navigation.
187
+ """
188
+ global _installed, _root_ref
189
+
190
+ if not _installed:
191
+ return
192
+
193
+ # Restore original focus methods
194
+ tk.Misc.focus_set = _original_focus_set
195
+ tk.Misc.focus_force = _original_focus_force
196
+
197
+ if _root_ref is not None:
198
+ try:
199
+ _root_ref.unbind('<Tab>')
200
+ _root_ref.unbind_all('<FocusOut>')
201
+ except TclError:
202
+ pass # Root may have been destroyed
203
+
204
+ _root_ref = None
205
+ _installed = False
206
+
207
+
208
+ def is_keyboard_focus(widget: tk.Misc) -> bool:
209
+ """Check if a widget currently has keyboard-initiated focus.
210
+
211
+ Args:
212
+ widget: The widget to check.
213
+
214
+ Returns:
215
+ True if the widget has focus AND was focused via keyboard.
216
+ """
217
+ try:
218
+ state = widget.state()
219
+ return 'focus' in state and 'background' in state
220
+ except (TclError, AttributeError):
221
+ return False
222
+
223
+
224
+ __all__ = [
225
+ 'install_visual_focus',
226
+ 'uninstall_visual_focus',
227
+ 'is_keyboard_focus',
228
+ ]