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,674 @@
1
+ """Color chooser widget for bootstack dialogs.
2
+
3
+ This module provides a comprehensive color selection widget with multiple
4
+ selection methods including RGB sliders, HSL controls, standard color palette,
5
+ hex input, and screen color picker (color dropper).
6
+ """
7
+ import tkinter
8
+ from collections import namedtuple
9
+ from tkinter import Canvas, IntVar, StringVar
10
+ from tkinter import Frame as tkFrame, Label as tkLabel
11
+ from types import SimpleNamespace
12
+ from typing import Any, Callable, List, Optional, Tuple
13
+
14
+ from PIL import ImageColor
15
+
16
+ from bootstack.constants import *
17
+ from bootstack.core import colorutils
18
+ from bootstack.core.colorutils import HEX, HSL, HUE, LUM, RGB, SAT
19
+ from bootstack.core.localization import MessageCatalog
20
+ from bootstack.runtime import utility
21
+ from bootstack.style.style import get_style
22
+ from bootstack.widgets.composites.tooltip import ToolTip
23
+ from bootstack.widgets.primitives import Button, Entry, Frame, Label, Notebook, Spinbox
24
+ # from bootstack.validation import add_range_validation, add_validation, validator
25
+ from .colordropper import ColorDropperDialog
26
+
27
+ ttk = SimpleNamespace(
28
+ Button=Button,
29
+ Canvas=Canvas,
30
+ Entry=Entry,
31
+ Frame=Frame,
32
+ IntVar=IntVar,
33
+ Label=Label,
34
+ Notebook=Notebook,
35
+ Spinbox=Spinbox,
36
+ StringVar=StringVar,
37
+ use_style=get_style,
38
+ )
39
+
40
+ STD_SHADES: List[float] = [0.9, 0.8, 0.7, 0.4, 0.3]
41
+ STD_COLORS: List[str] = [
42
+ '#FF0000', '#FFC000', '#FFFF00', '#00B050',
43
+ '#0070C0', '#7030A0', '#FFFFFF', '#000000'
44
+ ]
45
+
46
+ ColorValues = namedtuple('ColorValues', 'h s l r g b hex')
47
+ ColorChoice = namedtuple('ColorChoice', 'rgb hsl hex')
48
+
49
+ PEN = '✛'
50
+
51
+
52
+ # @validator
53
+ # def validate_color(event: Any) -> bool:
54
+ # try:
55
+ # ImageColor.getrgb(event.postchangetext)
56
+ # return True
57
+ # except:
58
+ # return False
59
+
60
+
61
+ class ColorChooser(ttk.Frame):
62
+ """Color chooser widget with multiple selection modes."""
63
+
64
+ def __init__(
65
+ self, master: Optional[tkinter.Misc], initial_color: Optional[str] = None,
66
+ padding: Optional[int] = None) -> None:
67
+ """Create a color chooser widget.
68
+
69
+ The chooser offers:
70
+ - Advanced tab: spectrum with hue/saturation and luminance slider.
71
+ - Themed tab: swatches for theme colors and shades.
72
+ - Standard tab: common colors and shades.
73
+
74
+ Includes RGB/HSL/Hex inputs, live preview, and optional dropper.
75
+
76
+ Args:
77
+ master: Parent widget.
78
+ initial_color: Initial color string; defaults to theme background.
79
+ padding: Padding around the chooser.
80
+ """
81
+ super().__init__(master, padding=padding)
82
+ self.tframe = ttk.Frame(self, padding=5)
83
+ self.tframe.pack(fill=X)
84
+ self.bframe = ttk.Frame(self, padding=(5, 0, 5, 5))
85
+ self.bframe.pack(fill=X)
86
+
87
+ self.notebook = ttk.Notebook(self.tframe)
88
+ self.notebook.pack(fill=BOTH)
89
+
90
+ self.style = ttk.use_style()
91
+ self.colors = self.style.colors
92
+ fallback_bg = (
93
+ self.colors.get("bg")
94
+ if isinstance(self.colors, dict)
95
+ else getattr(self.colors, "bg", None)
96
+ ) or "#ffffff"
97
+ self.initial_color = initial_color or fallback_bg
98
+
99
+ # color variables
100
+ r, g, b = ImageColor.getrgb(self.initial_color)
101
+ h, s, l = colorutils.color_to_hsl((r, g, b), RGB)
102
+ hx = colorutils.color_to_hex((r, g, b), RGB)
103
+
104
+ self.hue = ttk.IntVar(value=h)
105
+ self.sat = ttk.IntVar(value=s)
106
+ self.lum = ttk.IntVar(value=l)
107
+ self.red = ttk.IntVar(value=r)
108
+ self.grn = ttk.IntVar(value=g)
109
+ self.blu = ttk.IntVar(value=b)
110
+ self.hex = ttk.StringVar(value=hx)
111
+
112
+ # widget sizes (adjusted by widget scaling)
113
+ self.spectrum_height = utility.scale_size(self, 240)
114
+ self.spectrum_width = utility.scale_size(self, 530) # looks better on Mac OS
115
+ # self.spectrum_width = utility.scale_size(self, 480)
116
+ self.spectrum_point = utility.scale_size(self, 12)
117
+
118
+ # build widgets
119
+ spectrum_frame = ttk.Frame(self.notebook)
120
+ self.color_spectrum = self.create_spectrum(spectrum_frame)
121
+ self.color_spectrum.pack(fill=X, side=TOP)
122
+ self.luminance_scale = self.create_luminance_scale(self.tframe)
123
+ self.luminance_scale.pack(fill=X)
124
+ self.notebook.add(spectrum_frame, text='color.advanced')
125
+
126
+ palette_keys = ("primary", "secondary", "success", "info", "warning", "danger", "light", "dark")
127
+ themed_colors = [
128
+ (self.colors.get(c) if isinstance(self.colors, dict) else getattr(self.colors, c, None))
129
+ for c in palette_keys
130
+ ]
131
+ themed_colors = [c or "#ffffff" for c in themed_colors]
132
+ self.themed_swatches = self.create_swatches(
133
+ self.notebook, themed_colors)
134
+ self.standard_swatches = self.create_swatches(
135
+ self.notebook, STD_COLORS)
136
+ self.notebook.add(self.themed_swatches, text='color.themed')
137
+ self.notebook.add(self.standard_swatches, text='color.standard')
138
+ preview_frame = self.create_preview(self.bframe)
139
+ preview_frame.pack(side=LEFT, fill=BOTH, expand=YES, padx=(0, 5))
140
+ self.color_entries = self.create_value_inputs(self.bframe)
141
+ self.color_entries.pack(side=RIGHT)
142
+
143
+ self.create_spectrum_indicator()
144
+ self.create_luminance_indicator()
145
+
146
+ def create_spectrum(self, master: tkinter.Misc) -> ttk.Canvas:
147
+ """Create the color spectrum canvas"""
148
+ # canvas and point dimensions
149
+ width = self.spectrum_width
150
+ height = self.spectrum_height
151
+ xf = yf = self.spectrum_point
152
+
153
+ # create canvas widget and binding
154
+ canvas = ttk.Canvas(master, width=width, height=height, cursor='tcross')
155
+ canvas.bind("<B1-Motion>", self.on_spectrum_interaction, add="+")
156
+ canvas.bind("<Button-1>", self.on_spectrum_interaction, add="+")
157
+
158
+ # add color points
159
+ for x, colorx in enumerate(range(0, width, xf)):
160
+ for y, colory in enumerate(range(0, height, yf)):
161
+ values = self.color_from_coords(colorx, colory)
162
+ fill = values.hex
163
+ bbox = [x * xf, y * yf, (x * xf) + xf, (y * yf) + yf]
164
+ canvas.create_rectangle(*bbox, fill=fill, width=0)
165
+ return canvas
166
+
167
+ def create_spectrum_indicator(self) -> None:
168
+ """Create a square indicator that displays in the position of
169
+ the selected color"""
170
+ s = utility.scale_size(self, 10)
171
+ width = utility.scale_size(self, 2)
172
+ values = self.get_variables()
173
+ x1, y1 = self.coords_from_color(values.hex)
174
+ colorutils.contrast_color(values.hex, 'hex')
175
+ tag = ['spectrum-indicator']
176
+ self.color_spectrum.create_rectangle(
177
+ x1, y1, x1 + s, y1 + s, width=width, tags=[tag])
178
+ self.color_spectrum.tag_lower('spectrum-indicator')
179
+
180
+ # widget builder methods
181
+ def create_swatches(self, master: tkinter.Misc, colors: List[str]) -> ttk.Frame:
182
+ """Create a grid of color swatches"""
183
+ box_padx = 2
184
+ box_pady = 0
185
+ padx_total = (box_padx * 15)
186
+ box_width = int((self.spectrum_width - padx_total)) / len(STD_COLORS)
187
+ box_height = int((self.spectrum_height - box_pady) / (len(STD_SHADES) + 1))
188
+ container = ttk.Frame(master)
189
+
190
+ # create color combinations
191
+ color_rows = [colors]
192
+ last_col = len(colors) - 1
193
+ for l in STD_SHADES:
194
+ lum = int(l * LUM)
195
+ row = []
196
+ for color in colors:
197
+ color = colorutils.update_hsl_value(
198
+ color=color,
199
+ lum=lum,
200
+ inmodel='hex',
201
+ outmodel='hex'
202
+ )
203
+ row.append(color)
204
+ color_rows.append(row)
205
+
206
+ # themed colors - regular colors
207
+ for row in color_rows:
208
+ row_frame = ttk.Frame(container)
209
+ for j, color in enumerate(row):
210
+ swatch = tkFrame(
211
+ master=row_frame,
212
+ bg=color,
213
+ width=box_width,
214
+ height=box_height,
215
+ autostyle=False
216
+ )
217
+ swatch.bind('<Button-1>', self.on_press_swatch)
218
+ if j == 0:
219
+ swatch.pack(side=LEFT, padx=(0, box_padx))
220
+ elif j == last_col:
221
+ swatch.pack(side=LEFT, padx=(box_padx, 0))
222
+ else:
223
+ swatch.pack(side=LEFT, padx=box_padx)
224
+ row_frame.pack(fill=X, expand=YES)
225
+
226
+ return container
227
+
228
+ def create_preview(self, master: tkinter.Misc) -> ttk.Frame:
229
+ """Create the preview frame for original and new colors"""
230
+ ng_style = self.notebook.cget('style')
231
+ # set the border color to match the notebook border color
232
+ border_color = self.style.lookup(ng_style, 'bordercolor') or "#000000"
233
+ container = ttk.Frame(master)
234
+
235
+ # the frame and label for the original color (current)
236
+ old = tkFrame(
237
+ master=container,
238
+ relief=FLAT,
239
+ bd=2,
240
+ highlightthickness=1,
241
+ highlightbackground=border_color,
242
+ bg=self.initial_color,
243
+ autostyle=False
244
+ )
245
+ old.pack(side=LEFT, fill=BOTH, expand=YES, padx=(0, 2))
246
+ constrast_fg = colorutils.contrast_color(
247
+ color=self.initial_color,
248
+ model='hex',
249
+ )
250
+ tkLabel(
251
+ master=old,
252
+ text=MessageCatalog.translate("color.current"),
253
+ background=self.initial_color,
254
+ foreground=constrast_fg,
255
+ autostyle=False,
256
+ width=7
257
+ ).pack(anchor=NW)
258
+
259
+ # the frame and label for the new color
260
+ self.preview = tkFrame(
261
+ master=container,
262
+ relief=FLAT,
263
+ bd=2,
264
+ highlightthickness=1,
265
+ highlightbackground=border_color,
266
+ bg=self.initial_color,
267
+ autostyle=False
268
+ )
269
+ self.preview.pack(side=LEFT, fill=BOTH, expand=YES, padx=(2, 0))
270
+ self.preview_lbl = tkLabel(
271
+ master=self.preview,
272
+ text=MessageCatalog.translate("color.new"),
273
+ background=self.initial_color,
274
+ foreground=constrast_fg,
275
+ autostyle=False,
276
+ width=7
277
+ )
278
+ self.preview_lbl.pack(anchor=NW)
279
+
280
+ return container
281
+
282
+ def create_value_inputs(self, master: tkinter.Misc) -> ttk.Frame:
283
+ """Create color value input widgets"""
284
+ container = ttk.Frame(master)
285
+ for x in range(4):
286
+ container.columnconfigure(x, weight=1)
287
+
288
+ # value labels - use semantic keys with built-in localization
289
+ lbl_cnf = {'master': container, 'anchor': E}
290
+ ttk.Label(**lbl_cnf, text='color.hue:').grid(row=0, column=0, sticky=E)
291
+ ttk.Label(**lbl_cnf, text='color.sat:').grid(row=1, column=0, sticky=E)
292
+ ttk.Label(**lbl_cnf, text='color.lum:').grid(row=2, column=0, sticky=E)
293
+ ttk.Label(**lbl_cnf, text='color.hex:').grid(row=3, column=0, sticky=E)
294
+ ttk.Label(**lbl_cnf, text='color.red:').grid(row=0, column=2, sticky=E)
295
+ ttk.Label(**lbl_cnf, text='color.green:').grid(row=1, column=2, sticky=E)
296
+ ttk.Label(**lbl_cnf, text='color.blue:').grid(row=2, column=2, sticky=E)
297
+
298
+ # value spinners and entry widgets
299
+ rgb_cnf = {'master': container, 'from_': 0, 'to': 255, 'width': 3}
300
+ sl_cnf = {'master': container, 'from_': 0, 'to': 100, 'width': 3}
301
+ hue_cnf = {'master': container, 'from_': 0, 'to': 360, 'width': 3}
302
+ sb_hue = ttk.Spinbox(**hue_cnf, textvariable=self.hue)
303
+ sb_hue.grid(row=0, column=1, padx=4, pady=2, sticky=EW)
304
+ sb_sat = ttk.Spinbox(**sl_cnf, textvariable=self.sat)
305
+ sb_sat.grid(row=1, column=1, padx=4, pady=2, sticky=EW)
306
+ sb_lum = ttk.Spinbox(**sl_cnf, textvariable=self.lum)
307
+ sb_lum.grid(row=2, column=1, padx=4, pady=2, sticky=EW)
308
+ sb_red = ttk.Spinbox(**rgb_cnf, textvariable=self.red)
309
+ sb_red.grid(row=0, column=3, padx=4, pady=2, sticky=EW)
310
+ sb_grn = ttk.Spinbox(**rgb_cnf, textvariable=self.grn)
311
+ sb_grn.grid(row=1, column=3, padx=4, pady=2, sticky=EW)
312
+ sb_blu = ttk.Spinbox(**rgb_cnf, textvariable=self.blu)
313
+ sb_blu.grid(row=2, column=3, padx=4, pady=2, sticky=EW)
314
+ ent_hex = ttk.Entry(container, textvariable=self.hex)
315
+ ent_hex.grid(row=3, column=1, padx=4, columnspan=3, pady=2, sticky=EW)
316
+
317
+ # event binding for updating colors on value change
318
+ for sb in [sb_hue, sb_sat, sb_lum]:
319
+ for sequence in ['<<Increment>>', '<<Decrement>>', '<Return>', '<KP_Enter>']:
320
+ sb.bind(
321
+ sequence=sequence,
322
+ func=lambda _, w=sb: self.on_entry_value_change(
323
+ w, HSL),
324
+ add="+"
325
+ )
326
+ for sb in [sb_red, sb_grn, sb_blu]:
327
+ for sequence in ['<<Increment>>', '<<Decrement>>', '<Return>', '<KP_Enter>']:
328
+ sb.bind(
329
+ sequence=sequence,
330
+ func=lambda _, w=sb: self.on_entry_value_change(
331
+ w, RGB),
332
+ add="+"
333
+ )
334
+ for sequence in ['<Return>', '<KP_Enter>']:
335
+ ent_hex.bind(
336
+ sequence=sequence,
337
+ func=lambda _, w=ent_hex: self.on_entry_value_change(
338
+ w, HEX),
339
+ add="+"
340
+ )
341
+
342
+ return container
343
+
344
+ def create_luminance_scale(self, master: tkinter.Misc) -> ttk.Canvas:
345
+ """Create the color luminance canvas"""
346
+ # widget dimensions
347
+ height = xf = self.spectrum_point
348
+ width = self.spectrum_width
349
+
350
+ values = self.get_variables()
351
+ canvas = ttk.Canvas(master, height=height, width=width)
352
+
353
+ # add color points to scale
354
+ for x, l in enumerate(range(0, width, xf)):
355
+ lum = l / width * LUM
356
+ fill = colorutils.update_hsl_value(
357
+ color=values.hex,
358
+ lum=lum,
359
+ inmodel='hex',
360
+ outmodel='hex'
361
+ )
362
+ bbox = [x * xf, 0, (x * xf) + xf, height]
363
+ tag = f'color{x}'
364
+ canvas.create_rectangle(*bbox, fill=fill, width=0, tags=[tag])
365
+ canvas.bind("<B1-Motion>", self.on_luminance_interaction, add="+")
366
+ canvas.bind("<Button-1>", self.on_luminance_interaction, add="+")
367
+ return canvas
368
+
369
+ def create_luminance_indicator(self) -> None:
370
+ """Create an indicator that displays in the position of the
371
+ luminance value"""
372
+ lum = 50
373
+ x1 = int(lum / LUM * self.spectrum_width) - \
374
+ ((self.spectrum_point - 2) // 2)
375
+ y1 = 0
376
+ x2 = x1 + self.spectrum_point
377
+ y2 = self.spectrum_point - 3
378
+ tag = 'luminance-indicator'
379
+ bbox = [x1, y1, x2, y2]
380
+ self.luminance_scale.create_rectangle(
381
+ *bbox, fill='white', outline='black', tags=[tag])
382
+ self.luminance_scale.tag_lower(tag)
383
+
384
+ def coords_from_color(self, hexcolor: str) -> Tuple[float, float]:
385
+ """Get the coordinates on the color spectrum from the color
386
+ value"""
387
+ h, s, _ = colorutils.color_to_hsl(hexcolor)
388
+ x = (h / HUE) * self.spectrum_width
389
+ y = (1 - (s / SAT)) * self.spectrum_height
390
+ return x, y
391
+
392
+ def color_from_coords(self, x: int, y: int):
393
+ """Get the color value from the mouse position in the color
394
+ spectrum"""
395
+ HEIGHT = self.spectrum_height
396
+ WIDTH = self.spectrum_width
397
+ h = int(min(HUE, max(0, (HUE / WIDTH) * x)))
398
+ s = int(min(SAT, max(0, SAT - ((SAT / HEIGHT) * y))))
399
+ l = 50
400
+ hx = colorutils.color_to_hex([h, s, l], 'hsl')
401
+ r, g, b = colorutils.color_to_rgb(hx)
402
+ return ColorValues(h, s, l, r, g, b, hx)
403
+
404
+ def set_variables(self, h: int, s: int, l: int, r: int, g: int, b: int, hx: str) -> None:
405
+ """Update the color value variables"""
406
+ self.hue.set(h)
407
+ self.sat.set(s)
408
+ self.lum.set(l)
409
+ self.red.set(r)
410
+ self.grn.set(g)
411
+ self.blu.set(b)
412
+ self.hex.set(hx)
413
+
414
+ def get_variables(self):
415
+ """Get the values of all color models and return a
416
+ tuple of color values"""
417
+ h = self.hue.get()
418
+ s = self.sat.get()
419
+ l = self.lum.get()
420
+ r = self.red.get()
421
+ g = self.grn.get()
422
+ b = self.blu.get()
423
+ hx = self.hex.get()
424
+ return ColorValues(h, s, l, r, g, b, hx)
425
+
426
+ def update_preview(self) -> None:
427
+ """Update the color in the preview frame"""
428
+ hx = self.hex.get()
429
+ fg = colorutils.contrast_color(
430
+ color=hx,
431
+ model='hex',
432
+ )
433
+ self.preview.configure(bg=hx)
434
+ self.preview_lbl.configure(bg=hx, fg=fg)
435
+
436
+ def update_luminance_scale(self) -> None:
437
+ """Update the luminance scale with the change in hue and saturation"""
438
+ values = self.get_variables()
439
+ width = self.spectrum_width
440
+ xf = self.spectrum_point
441
+ for x, l in enumerate(range(0, width, xf)):
442
+ lum = l / width * LUM
443
+ fill = colorutils.update_hsl_value(
444
+ color=values.hex,
445
+ lum=lum,
446
+ inmodel='hex',
447
+ outmodel='hex'
448
+ )
449
+ tag = f'color{x}'
450
+ self.luminance_scale.itemconfig(tag, fill=fill)
451
+
452
+ def update_luminance_indicator(self) -> None:
453
+ """Update the position of the luminance indicator"""
454
+ lum = self.lum.get()
455
+ x = int(lum / LUM * self.spectrum_width) - \
456
+ ((self.spectrum_point - 2) // 2)
457
+ self.luminance_scale.moveto('luminance-indicator', x, 0)
458
+ self.luminance_scale.tag_raise('luminance-indicator')
459
+
460
+ def update_spectrum_indicator(self) -> None:
461
+ """Move the spectrum indicator to a new location"""
462
+ values = self.get_variables()
463
+ x, y = self.coords_from_color(values.hex)
464
+ # move to the new color location
465
+ self.color_spectrum.moveto('spectrum-indicator', x, y)
466
+ self.color_spectrum.tag_raise('spectrum-indicator')
467
+ # adjust the outline color based on contrast of background
468
+ color = colorutils.contrast_color(values.hex, 'hex')
469
+ self.color_spectrum.itemconfig('spectrum-indicator', outline=color)
470
+
471
+ # color events
472
+ def sync_color_values(self, model):
473
+ """Callback for when a color value changes. A change in one
474
+ value will automatically update the other values so that all
475
+ color models remain in sync."""
476
+ values = self.get_variables()
477
+ if model == HEX:
478
+ hx = values.hex
479
+ r, g, b = colorutils.color_to_rgb(hx)
480
+ h, s, l = colorutils.color_to_hsl(hx)
481
+ elif model == RGB:
482
+ r, g, b = values.r, values.g, values.b
483
+ h, s, l = colorutils.color_to_hsl([r, g, b], 'rgb')
484
+ hx = colorutils.color_to_hex([r, g, b])
485
+ elif model == HSL:
486
+ h, s, l = values.h, values.s, values.l
487
+ r, g, b = colorutils.color_to_rgb([h, s, l], 'hsl')
488
+ hx = colorutils.color_to_hex([h, s, l], 'hsl')
489
+ self.set_variables(h, s, l, r, g, b, hx)
490
+ self.update_preview()
491
+ self.update_luminance_indicator()
492
+
493
+ def on_entry_value_change(self, widget: ttk.Spinbox, model: Any) -> None:
494
+ """Update the widget colors when the color value input is
495
+ changed"""
496
+ is_valid = widget.validate()
497
+ if is_valid:
498
+ self.sync_color_values(model)
499
+ self.update_luminance_scale()
500
+ self.update_spectrum_indicator()
501
+
502
+ def on_press_swatch(self, event: tkinter.Event) -> None:
503
+ """Update the widget colors when a color swatch is clicked."""
504
+ button: tkFrame = self.nametowidget(event.widget)
505
+ color = button.cget('background')
506
+ self.hex.set(color)
507
+ self.sync_color_values(HEX)
508
+ self.update_luminance_scale()
509
+ self.update_spectrum_indicator()
510
+
511
+ def on_spectrum_interaction(self, event: tkinter.Event) -> None:
512
+ """Update the widget colors when the color spectrum canvas is
513
+ pressed"""
514
+ values = self.color_from_coords(event.x, event.y)
515
+ self.hue.set(values.h)
516
+ self.sat.set(values.s)
517
+ self.lum.set(values.l)
518
+ self.sync_color_values(HSL)
519
+ self.update_luminance_scale()
520
+ self.update_spectrum_indicator()
521
+
522
+ def on_luminance_interaction(self, event: tkinter.Event) -> None:
523
+ """Update the widget colors when the color luminance scale is
524
+ pressed"""
525
+ l = max(0, min(LUM, int((event.x / self.spectrum_width) * LUM)))
526
+ self.lum.set(l)
527
+ self.sync_color_values(HSL)
528
+
529
+
530
+ from bootstack.dialogs.dialog import Dialog
531
+
532
+
533
+ class ColorChooserDialog:
534
+ """Dialog wrapper for the ColorChooser widget.
535
+
536
+ Args:
537
+ master: Parent widget used for positioning and event binding.
538
+ title: Dialog window title (localized).
539
+ initial_color: Initial color shown in the chooser; defaults to theme background.
540
+
541
+ !!! note "Events"
542
+
543
+ `<<DialogResult>>`: Fired when the dialog is closed.
544
+ Provides `event.data` with keys: `result` (ColorChoice|None), `confirmed` (bool).
545
+ """
546
+
547
+ def __init__(
548
+ self,
549
+ master: Optional[tkinter.Misc] = None,
550
+ title: str = "color.chooser",
551
+ initial_color: Optional[str] = None,
552
+ ) -> None:
553
+ self._master = master
554
+ # Title is now automatically localized by BaseWindow._setup_window
555
+ self._title = title
556
+ self._initial_color = initial_color
557
+ self.result: Optional[ColorChoice] = None
558
+ self._emitted_result = False
559
+
560
+ self._dropper = ColorDropperDialog()
561
+ self._dropper.result.trace_add('write', self._trace_dropper_color)
562
+
563
+ self._dialog = Dialog(
564
+ master=master,
565
+ title=self._title,
566
+ content_builder=self._build_content,
567
+ footer_builder=self._build_footer,
568
+ )
569
+ self._chooser: Optional[ColorChooser] = None
570
+
571
+ # builders -----------------------------------------------------------------
572
+ def _build_content(self, master: tkinter.Widget) -> None:
573
+ self._chooser = ColorChooser(master, self._initial_color)
574
+ self._chooser.pack(fill=BOTH, expand=YES)
575
+
576
+ def _build_footer(self, master: tkinter.Widget) -> None:
577
+ # color dropper (not supported on macOS)
578
+ winsys = ""
579
+ try:
580
+ winsys = master.tk.call("tk", "windowingsystem")
581
+ except Exception:
582
+ winsys = ""
583
+ if winsys != 'aqua':
584
+ dropper = ttk.Label(master, text=PEN, font=('-size 16'))
585
+ ToolTip(dropper, 'color.dropper')
586
+ dropper.pack(side=LEFT, padx=(0, 4))
587
+ dropper.bind("<Button-1>", self._on_show_color_dropper)
588
+
589
+ ok = ttk.Button(
590
+ master,
591
+ accent=PRIMARY,
592
+ text='button.ok',
593
+ command=self._on_ok,
594
+ )
595
+ ok.pack(side=RIGHT)
596
+
597
+ cancel = ttk.Button(
598
+ master,
599
+ accent=SECONDARY,
600
+ text='button.cancel',
601
+ command=self._on_cancel,
602
+ )
603
+ cancel.pack(side=RIGHT)
604
+
605
+ # callbacks ----------------------------------------------------------------
606
+ def _on_show_color_dropper(self, _: tkinter.Event) -> None:
607
+ self._dropper.show()
608
+
609
+ def _trace_dropper_color(self, *_: Any) -> None:
610
+ values = self._dropper.result.get()
611
+ if self._chooser:
612
+ self._chooser.hex.set(values[2])
613
+ self._chooser.sync_color_values('hex')
614
+
615
+ def _on_ok(self) -> None:
616
+ if self._chooser:
617
+ values = self._chooser.get_variables()
618
+ self.result = ColorChoice(
619
+ rgb=(values.r, values.g, values.b),
620
+ hsl=(values.h, values.s, values.l),
621
+ hex=values.hex
622
+ )
623
+ self._emit_result(confirmed=True)
624
+ if self._dialog.toplevel:
625
+ self._dialog.toplevel.after_idle(self._dialog.toplevel.destroy)
626
+
627
+ def _on_cancel(self) -> None:
628
+ self.result = None
629
+ self._emit_result(confirmed=False)
630
+ if self._dialog.toplevel:
631
+ self._dialog.toplevel.after_idle(self._dialog.toplevel.destroy)
632
+
633
+ # API ----------------------------------------------------------------------
634
+ def show(self, position: Optional[tuple[int, int]] = None, modal: bool = True) -> None:
635
+ """Display the dialog."""
636
+ self.result = None
637
+ self._emitted_result = False
638
+ self._dialog.show(position=position, modal=modal)
639
+ # Ensure result event fires even if consumers prefer post-show access
640
+ if not self._emitted_result:
641
+ self._emit_result(confirmed=self.result is not None)
642
+
643
+ def on_dialog_result(self, callback: Callable[[Any], None]) -> Optional[str]:
644
+ """Bind a callback fired when the dialog produces a result."""
645
+ target = self._master or self._dialog.toplevel
646
+ if target is None:
647
+ return None
648
+
649
+ def handler(event):
650
+ callback(getattr(event, "data", None))
651
+
652
+ return target.bind("<<DialogResult>>", handler, add="+")
653
+
654
+ def off_dialog_result(self, funcid: str) -> None:
655
+ """Unbind a previously bound dialog result callback."""
656
+ target = self._master or self._dialog.toplevel
657
+ if target is None:
658
+ return
659
+ target.unbind("<<DialogResult>>", funcid)
660
+
661
+ # helpers ------------------------------------------------------------------
662
+ def _emit_result(self, confirmed: bool) -> None:
663
+ payload = {"result": self.result, "confirmed": confirmed}
664
+ target = self._master or self._dialog.toplevel
665
+ if not target:
666
+ return
667
+ try:
668
+ target.event_generate("<<DialogResult>>", data=payload)
669
+ except Exception:
670
+ try:
671
+ target.event_generate("<<DialogResult>>")
672
+ except Exception:
673
+ pass
674
+ self._emitted_result = True