bootstack 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. bootstack/__init__.py +157 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/_core/__init__.py +21 -0
  4. bootstack/_core/capabilities/__init__.py +45 -0
  5. bootstack/_core/capabilities/after.py +103 -0
  6. bootstack/_core/capabilities/bind.py +192 -0
  7. bootstack/_core/capabilities/bindtags.py +112 -0
  8. bootstack/_core/capabilities/busy.py +72 -0
  9. bootstack/_core/capabilities/clipboard.py +89 -0
  10. bootstack/_core/capabilities/focus.py +118 -0
  11. bootstack/_core/capabilities/grab.py +65 -0
  12. bootstack/_core/capabilities/grid.py +211 -0
  13. bootstack/_core/capabilities/localization.py +231 -0
  14. bootstack/_core/capabilities/pack.py +140 -0
  15. bootstack/_core/capabilities/place.py +113 -0
  16. bootstack/_core/capabilities/selection.py +136 -0
  17. bootstack/_core/capabilities/signals.py +244 -0
  18. bootstack/_core/capabilities/winfo.py +315 -0
  19. bootstack/_core/colorutils.py +234 -0
  20. bootstack/_core/exceptions.py +25 -0
  21. bootstack/_core/images.py +463 -0
  22. bootstack/_core/mixins/__init__.py +1 -0
  23. bootstack/_core/mixins/ttk_state.py +35 -0
  24. bootstack/_core/mixins/widget.py +132 -0
  25. bootstack/_core/paths.py +49 -0
  26. bootstack/_core/publisher.py +149 -0
  27. bootstack/_core/variables.py +62 -0
  28. bootstack/_runtime/__init__.py +3 -0
  29. bootstack/_runtime/app.py +930 -0
  30. bootstack/_runtime/base_window.py +945 -0
  31. bootstack/_runtime/events.py +399 -0
  32. bootstack/_runtime/shortcuts.py +496 -0
  33. bootstack/_runtime/tk_patch.py +43 -0
  34. bootstack/_runtime/toplevel.py +276 -0
  35. bootstack/_runtime/utility.py +457 -0
  36. bootstack/_runtime/visual_focus.py +240 -0
  37. bootstack/_runtime/window_utilities.py +1123 -0
  38. bootstack/assets/__init__.py +21 -0
  39. bootstack/assets/bootstack.ico +0 -0
  40. bootstack/assets/bootstack.png +0 -0
  41. bootstack/assets/elements/__init__.py +0 -0
  42. bootstack/assets/elements/badge-pill.png +0 -0
  43. bootstack/assets/elements/badge-square.png +0 -0
  44. bootstack/assets/elements/button-compact.png +0 -0
  45. bootstack/assets/elements/button-default.png +0 -0
  46. bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
  47. bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
  48. bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
  49. bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
  50. bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
  51. bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
  52. bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
  53. bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
  54. bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
  55. bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
  56. bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
  57. bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
  58. bootstack/assets/elements/card.png +0 -0
  59. bootstack/assets/elements/checkbox-checked.png +0 -0
  60. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  61. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  62. bootstack/assets/elements/field.png +0 -0
  63. bootstack/assets/elements/input-addon-compact.png +0 -0
  64. bootstack/assets/elements/input-addon-default.png +0 -0
  65. bootstack/assets/elements/input-compact.png +0 -0
  66. bootstack/assets/elements/input-default.png +0 -0
  67. bootstack/assets/elements/list-item-separated.png +0 -0
  68. bootstack/assets/elements/list-item.png +0 -0
  69. bootstack/assets/elements/listrow-compact.png +0 -0
  70. bootstack/assets/elements/listrow-default.png +0 -0
  71. bootstack/assets/elements/manifest.toml +361 -0
  72. bootstack/assets/elements/menu-item.png +0 -0
  73. bootstack/assets/elements/navitem-compact.png +0 -0
  74. bootstack/assets/elements/navitem-default.png +0 -0
  75. bootstack/assets/elements/progressbar-h-compact.png +0 -0
  76. bootstack/assets/elements/progressbar-h-default.png +0 -0
  77. bootstack/assets/elements/progressbar-v-compact.png +0 -0
  78. bootstack/assets/elements/progressbar-v-default.png +0 -0
  79. bootstack/assets/elements/radiobutton.png +0 -0
  80. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  81. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  82. bootstack/assets/elements/slider-handle.png +0 -0
  83. bootstack/assets/elements/slider-track-h.png +0 -0
  84. bootstack/assets/elements/slider-track-v.png +0 -0
  85. bootstack/assets/elements/switch-off.png +0 -0
  86. bootstack/assets/elements/switch-on.png +0 -0
  87. bootstack/assets/elements/tab-h.png +0 -0
  88. bootstack/assets/elements/tab-v.png +0 -0
  89. bootstack/assets/icons/bootstrap.ttf +0 -0
  90. bootstack/assets/icons/glyphmap.json +2080 -0
  91. bootstack/assets/icons/icon_metrics.json +12470 -0
  92. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  93. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
  94. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
  95. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
  96. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  97. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
  98. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  99. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
  100. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  101. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
  102. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  103. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
  104. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  105. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
  106. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  107. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
  108. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  109. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
  110. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  111. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
  112. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  113. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
  114. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  115. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
  116. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  117. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
  118. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  119. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
  120. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  121. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
  122. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  123. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
  124. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  125. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
  126. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  127. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
  128. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  129. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
  130. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  131. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
  132. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  133. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
  134. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  135. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
  136. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  137. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
  138. bootstack/cli/__init__.py +133 -0
  139. bootstack/cli/__main__.py +6 -0
  140. bootstack/cli/add.py +395 -0
  141. bootstack/cli/appicon.py +285 -0
  142. bootstack/cli/build.py +115 -0
  143. bootstack/cli/config.py +313 -0
  144. bootstack/cli/demo.py +564 -0
  145. bootstack/cli/dev.py +153 -0
  146. bootstack/cli/doctor.py +195 -0
  147. bootstack/cli/icons.py +98 -0
  148. bootstack/cli/promote.py +120 -0
  149. bootstack/cli/pyinstaller.py +268 -0
  150. bootstack/cli/run.py +95 -0
  151. bootstack/cli/start.py +117 -0
  152. bootstack/cli/templates/__init__.py +931 -0
  153. bootstack/clipboard.py +48 -0
  154. bootstack/constants.py +318 -0
  155. bootstack/data/README.md +615 -0
  156. bootstack/data/__init__.py +78 -0
  157. bootstack/data/_observable.py +276 -0
  158. bootstack/data/base.py +780 -0
  159. bootstack/data/file_source.py +367 -0
  160. bootstack/data/memory_source.py +450 -0
  161. bootstack/data/query.py +367 -0
  162. bootstack/data/readers.py +289 -0
  163. bootstack/data/sqlite_source.py +869 -0
  164. bootstack/data/types.py +354 -0
  165. bootstack/data/writers.py +232 -0
  166. bootstack/dev/__init__.py +36 -0
  167. bootstack/dev/_capture.py +141 -0
  168. bootstack/dev/_env.py +43 -0
  169. bootstack/dev/_registry.py +140 -0
  170. bootstack/dev/_reloader.py +351 -0
  171. bootstack/dev/_reset.py +50 -0
  172. bootstack/dev/_watcher.py +91 -0
  173. bootstack/dialogs/__init__.py +948 -0
  174. bootstack/dialogs/_impl/__init__.py +47 -0
  175. bootstack/dialogs/_impl/colorchooser.py +588 -0
  176. bootstack/dialogs/_impl/datedialog.py +450 -0
  177. bootstack/dialogs/_impl/dialog.py +594 -0
  178. bootstack/dialogs/_impl/filterdialog.py +358 -0
  179. bootstack/dialogs/_impl/fontdialog.py +364 -0
  180. bootstack/dialogs/_impl/formdialog.py +564 -0
  181. bootstack/dialogs/_impl/message.py +486 -0
  182. bootstack/dialogs/_impl/query.py +570 -0
  183. bootstack/errors.py +67 -0
  184. bootstack/events/__init__.py +111 -0
  185. bootstack/events/_event.py +135 -0
  186. bootstack/events/_payloads.py +539 -0
  187. bootstack/events/_subscription.py +38 -0
  188. bootstack/i18n/README.md +77 -0
  189. bootstack/i18n/__init__.py +23 -0
  190. bootstack/i18n/catalog.py +121 -0
  191. bootstack/i18n/intl_format.py +584 -0
  192. bootstack/i18n/msgcat.py +425 -0
  193. bootstack/i18n/specs.py +156 -0
  194. bootstack/images.py +563 -0
  195. bootstack/py.typed +1 -0
  196. bootstack/scheduling/__init__.py +11 -0
  197. bootstack/scheduling/_schedule.py +218 -0
  198. bootstack/shortcuts.py +21 -0
  199. bootstack/signals/README.md +98 -0
  200. bootstack/signals/__init__.py +10 -0
  201. bootstack/signals/integration.py +100 -0
  202. bootstack/signals/signal.py +353 -0
  203. bootstack/signals/types.py +6 -0
  204. bootstack/store.py +286 -0
  205. bootstack/streams/__init__.py +12 -0
  206. bootstack/streams/_stream.py +321 -0
  207. bootstack/style/__init__.py +38 -0
  208. bootstack/style/builders/__init__.py +51 -0
  209. bootstack/style/builders/badge.py +46 -0
  210. bootstack/style/builders/button.py +339 -0
  211. bootstack/style/builders/buttongroup.py +311 -0
  212. bootstack/style/builders/calendar.py +271 -0
  213. bootstack/style/builders/checkbutton.py +110 -0
  214. bootstack/style/builders/combobox.py +113 -0
  215. bootstack/style/builders/contextmenu.py +268 -0
  216. bootstack/style/builders/entry.py +82 -0
  217. bootstack/style/builders/expander.py +148 -0
  218. bootstack/style/builders/field.py +335 -0
  219. bootstack/style/builders/frame.py +50 -0
  220. bootstack/style/builders/label.py +28 -0
  221. bootstack/style/builders/labelframe.py +34 -0
  222. bootstack/style/builders/listview.py +369 -0
  223. bootstack/style/builders/menubar.py +91 -0
  224. bootstack/style/builders/menubutton.py +359 -0
  225. bootstack/style/builders/panedwindow.py +25 -0
  226. bootstack/style/builders/progressbar.py +67 -0
  227. bootstack/style/builders/radiobutton.py +99 -0
  228. bootstack/style/builders/scale.py +64 -0
  229. bootstack/style/builders/scrollbar.py +225 -0
  230. bootstack/style/builders/separator.py +49 -0
  231. bootstack/style/builders/sidenav.py +643 -0
  232. bootstack/style/builders/sizegrip.py +15 -0
  233. bootstack/style/builders/spinbox.py +119 -0
  234. bootstack/style/builders/switch.py +70 -0
  235. bootstack/style/builders/tabitem.py +204 -0
  236. bootstack/style/builders/togglegroup.py +294 -0
  237. bootstack/style/builders/toolbutton.py +275 -0
  238. bootstack/style/builders/tooltip.py +26 -0
  239. bootstack/style/builders/treeview.py +193 -0
  240. bootstack/style/builders/utils.py +455 -0
  241. bootstack/style/builders_tk/__init__.py +16 -0
  242. bootstack/style/builders_tk/defaults.py +229 -0
  243. bootstack/style/element.py +173 -0
  244. bootstack/style/fonts.py +123 -0
  245. bootstack/style/style.py +609 -0
  246. bootstack/style/style_builder_base.py +716 -0
  247. bootstack/style/style_builder_mixed.py +93 -0
  248. bootstack/style/style_builder_tk.py +109 -0
  249. bootstack/style/style_builder_ttk.py +353 -0
  250. bootstack/style/style_resolver.py +447 -0
  251. bootstack/style/theme.py +245 -0
  252. bootstack/style/theme_provider.py +471 -0
  253. bootstack/style/themes/__init__.py +128 -0
  254. bootstack/style/tk_patch.py +5 -0
  255. bootstack/style/token_maps.py +41 -0
  256. bootstack/style/types.py +32 -0
  257. bootstack/style/typography.py +523 -0
  258. bootstack/style/utility.py +746 -0
  259. bootstack/types.py +39 -0
  260. bootstack/validation/__init__.py +6 -0
  261. bootstack/validation/types.py +15 -0
  262. bootstack/validation/validation_result.py +17 -0
  263. bootstack/validation/validation_rules.py +205 -0
  264. bootstack/widgets/__init__.py +74 -0
  265. bootstack/widgets/_core/__init__.py +31 -0
  266. bootstack/widgets/_core/app_config.py +266 -0
  267. bootstack/widgets/_core/base.py +461 -0
  268. bootstack/widgets/_core/container.py +334 -0
  269. bootstack/widgets/_core/context.py +35 -0
  270. bootstack/widgets/_core/events.py +130 -0
  271. bootstack/widgets/_core/field_mixin.py +353 -0
  272. bootstack/widgets/_core/icon_image_props.py +72 -0
  273. bootstack/widgets/_core/image_binding.py +93 -0
  274. bootstack/widgets/_core/navmodel.py +345 -0
  275. bootstack/widgets/_core/options.py +210 -0
  276. bootstack/widgets/_core/selection_group.py +130 -0
  277. bootstack/widgets/_core/window_controls.py +96 -0
  278. bootstack/widgets/_core/window_menu.py +246 -0
  279. bootstack/widgets/_impl/__init__.py +1 -0
  280. bootstack/widgets/_impl/_internal/__init__.py +0 -0
  281. bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
  282. bootstack/widgets/_impl/_parts/__init__.py +11 -0
  283. bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
  284. bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
  285. bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
  286. bootstack/widgets/_impl/composites/__init__.py +33 -0
  287. bootstack/widgets/_impl/composites/_dateutils.py +33 -0
  288. bootstack/widgets/_impl/composites/_image_fit.py +105 -0
  289. bootstack/widgets/_impl/composites/accordion.py +381 -0
  290. bootstack/widgets/_impl/composites/avatar.py +191 -0
  291. bootstack/widgets/_impl/composites/buttongroup.py +371 -0
  292. bootstack/widgets/_impl/composites/calendar.py +972 -0
  293. bootstack/widgets/_impl/composites/carousel.py +567 -0
  294. bootstack/widgets/_impl/composites/chart.py +882 -0
  295. bootstack/widgets/_impl/composites/compositeframe.py +298 -0
  296. bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
  297. bootstack/widgets/_impl/composites/dateentry.py +404 -0
  298. bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
  299. bootstack/widgets/_impl/composites/expander.py +515 -0
  300. bootstack/widgets/_impl/composites/field.py +670 -0
  301. bootstack/widgets/_impl/composites/form.py +1066 -0
  302. bootstack/widgets/_impl/composites/gallery.py +551 -0
  303. bootstack/widgets/_impl/composites/list/__init__.py +15 -0
  304. bootstack/widgets/_impl/composites/list/listitem.py +802 -0
  305. bootstack/widgets/_impl/composites/list/listview.py +1433 -0
  306. bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
  307. bootstack/widgets/_impl/composites/menu/model.py +358 -0
  308. bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
  309. bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
  310. bootstack/widgets/_impl/composites/meter.py +860 -0
  311. bootstack/widgets/_impl/composites/numericentry.py +201 -0
  312. bootstack/widgets/_impl/composites/pagestack.py +395 -0
  313. bootstack/widgets/_impl/composites/passwordentry.py +142 -0
  314. bootstack/widgets/_impl/composites/pathentry.py +168 -0
  315. bootstack/widgets/_impl/composites/picture.py +289 -0
  316. bootstack/widgets/_impl/composites/radiogroup.py +511 -0
  317. bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
  318. bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
  319. bootstack/widgets/_impl/composites/scrollview.py +764 -0
  320. bootstack/widgets/_impl/composites/selectbox.py +1026 -0
  321. bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
  322. bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
  323. bootstack/widgets/_impl/composites/shell/layout.py +335 -0
  324. bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
  325. bootstack/widgets/_impl/composites/shell/providers.py +558 -0
  326. bootstack/widgets/_impl/composites/shell/rail.py +117 -0
  327. bootstack/widgets/_impl/composites/shell/shell.py +581 -0
  328. bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
  329. bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
  330. bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
  331. bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
  332. bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
  333. bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
  334. bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
  335. bootstack/widgets/_impl/composites/slider/slider.py +851 -0
  336. bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
  337. bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
  338. bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
  339. bootstack/widgets/_impl/composites/tableview/types.py +169 -0
  340. bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
  341. bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
  342. bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
  343. bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
  344. bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
  345. bootstack/widgets/_impl/composites/textarea/change.py +46 -0
  346. bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
  347. bootstack/widgets/_impl/composites/textarea/core.py +495 -0
  348. bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
  349. bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
  350. bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
  351. bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
  352. bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
  353. bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
  354. bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
  355. bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
  356. bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
  357. bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
  358. bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
  359. bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
  360. bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
  361. bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
  362. bootstack/widgets/_impl/composites/textentry.py +57 -0
  363. bootstack/widgets/_impl/composites/timeentry.py +176 -0
  364. bootstack/widgets/_impl/composites/toast.py +390 -0
  365. bootstack/widgets/_impl/composites/toast_stack.py +156 -0
  366. bootstack/widgets/_impl/composites/togglegroup.py +418 -0
  367. bootstack/widgets/_impl/composites/toolbar.py +608 -0
  368. bootstack/widgets/_impl/composites/tooltip.py +491 -0
  369. bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
  370. bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
  371. bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
  372. bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
  373. bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
  374. bootstack/widgets/_impl/mixins/__init__.py +24 -0
  375. bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
  376. bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
  377. bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
  378. bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
  379. bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
  380. bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
  381. bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
  382. bootstack/widgets/_impl/primitives/__init__.py +49 -0
  383. bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
  384. bootstack/widgets/_impl/primitives/badge.py +45 -0
  385. bootstack/widgets/_impl/primitives/button.py +76 -0
  386. bootstack/widgets/_impl/primitives/card.py +45 -0
  387. bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
  388. bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
  389. bootstack/widgets/_impl/primitives/combobox.py +156 -0
  390. bootstack/widgets/_impl/primitives/entry.py +87 -0
  391. bootstack/widgets/_impl/primitives/flexframe.py +448 -0
  392. bootstack/widgets/_impl/primitives/frame.py +185 -0
  393. bootstack/widgets/_impl/primitives/gridframe.py +546 -0
  394. bootstack/widgets/_impl/primitives/label.py +84 -0
  395. bootstack/widgets/_impl/primitives/labelframe.py +54 -0
  396. bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
  397. bootstack/widgets/_impl/primitives/packframe.py +227 -0
  398. bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
  399. bootstack/widgets/_impl/primitives/progressbar.py +83 -0
  400. bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
  401. bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
  402. bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
  403. bootstack/widgets/_impl/primitives/separator.py +43 -0
  404. bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
  405. bootstack/widgets/_impl/primitives/spinbox.py +95 -0
  406. bootstack/widgets/_impl/primitives/switch.py +44 -0
  407. bootstack/widgets/_impl/primitives/treeview.py +69 -0
  408. bootstack/widgets/app.py +371 -0
  409. bootstack/widgets/appshell.py +1179 -0
  410. bootstack/widgets/avatar.py +140 -0
  411. bootstack/widgets/boolean_controls.py +455 -0
  412. bootstack/widgets/button.py +224 -0
  413. bootstack/widgets/buttongroup.py +239 -0
  414. bootstack/widgets/calendar.py +195 -0
  415. bootstack/widgets/card.py +159 -0
  416. bootstack/widgets/carousel.py +241 -0
  417. bootstack/widgets/chart.py +302 -0
  418. bootstack/widgets/codeeditor.py +675 -0
  419. bootstack/widgets/contextmenu.py +371 -0
  420. bootstack/widgets/datatable.py +688 -0
  421. bootstack/widgets/datefield.py +395 -0
  422. bootstack/widgets/divider.py +60 -0
  423. bootstack/widgets/expander.py +579 -0
  424. bootstack/widgets/form.py +200 -0
  425. bootstack/widgets/gallery.py +250 -0
  426. bootstack/widgets/gauge.py +168 -0
  427. bootstack/widgets/grid.py +121 -0
  428. bootstack/widgets/groupbox.py +162 -0
  429. bootstack/widgets/label.py +229 -0
  430. bootstack/widgets/listview.py +354 -0
  431. bootstack/widgets/menubutton.py +419 -0
  432. bootstack/widgets/numberfield.py +431 -0
  433. bootstack/widgets/pagestack.py +345 -0
  434. bootstack/widgets/passwordfield.py +383 -0
  435. bootstack/widgets/pathfield.py +454 -0
  436. bootstack/widgets/picture.py +251 -0
  437. bootstack/widgets/progressbar.py +106 -0
  438. bootstack/widgets/radio_variants.py +271 -0
  439. bootstack/widgets/radiogroup.py +228 -0
  440. bootstack/widgets/scrollbar.py +64 -0
  441. bootstack/widgets/scrollview.py +186 -0
  442. bootstack/widgets/select.py +302 -0
  443. bootstack/widgets/selectbutton.py +182 -0
  444. bootstack/widgets/sidebar_toggle.py +130 -0
  445. bootstack/widgets/sizegrip.py +42 -0
  446. bootstack/widgets/slider.py +408 -0
  447. bootstack/widgets/spinbox.py +147 -0
  448. bootstack/widgets/spinnerfield.py +413 -0
  449. bootstack/widgets/splash.py +367 -0
  450. bootstack/widgets/splitview.py +482 -0
  451. bootstack/widgets/stacks.py +249 -0
  452. bootstack/widgets/statusbar.py +189 -0
  453. bootstack/widgets/tabs.py +425 -0
  454. bootstack/widgets/textarea.py +459 -0
  455. bootstack/widgets/textfield.py +410 -0
  456. bootstack/widgets/theme_toggle.py +92 -0
  457. bootstack/widgets/timefield.py +340 -0
  458. bootstack/widgets/toast.py +314 -0
  459. bootstack/widgets/togglegroup.py +231 -0
  460. bootstack/widgets/toolbar.py +347 -0
  461. bootstack/widgets/tooltip.py +94 -0
  462. bootstack/widgets/tree.py +648 -0
  463. bootstack/widgets/types.py +305 -0
  464. bootstack/widgets/window.py +307 -0
  465. bootstack-0.1.0.dist-info/METADATA +301 -0
  466. bootstack-0.1.0.dist-info/RECORD +471 -0
  467. bootstack-0.1.0.dist-info/WHEEL +5 -0
  468. bootstack-0.1.0.dist-info/entry_points.txt +2 -0
  469. bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
  470. bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
  471. bootstack-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,47 @@
1
+ """Internal dialog implementations.
2
+
3
+ The Bootstrap-styled dialog building blocks behind the public dialog API. The
4
+ curated public surface — the `alert` / `confirm` / `ask_*` verbs and the
5
+ `Dialog` / `FormDialog` / `FontDialog` / `ColorChooserDialog` /
6
+ `FilterDialog` classes — lives in `bootstack.dialogs`; import from there, not
7
+ from this package.
8
+
9
+ Modules here:
10
+
11
+ - `message` (MessageBox/MessageDialog): info/warning/error/question prompts
12
+ - `query` (QueryBox/QueryDialog): collect input (string, number, date, item, …)
13
+ - `colorchooser`: color picker
14
+ - `datedialog`: calendar date picker
15
+ - `fontdialog`: font selection
16
+ - `filterdialog`: multi-select filter dialog with search
17
+ - `dialog`: the base `Dialog` / `DialogButton`
18
+ """
19
+ from .colorchooser import (
20
+ ColorChooser,
21
+ ColorChooserDialog,
22
+ )
23
+ from .dialog import Dialog, DialogButton
24
+ from .filterdialog import FilterDialog
25
+ from .formdialog import FormDialog
26
+ from .message import MessageDialog, MessageBox
27
+ from .query import QueryDialog, QueryBox
28
+ from .datedialog import DateDialog
29
+ from .fontdialog import FontDialog
30
+
31
+ __all__ = [
32
+ # Base / core dialogs
33
+ "Dialog",
34
+ "DialogButton",
35
+ "FilterDialog",
36
+ "FormDialog",
37
+ "MessageDialog",
38
+ "QueryDialog",
39
+ "DateDialog",
40
+ "FontDialog",
41
+ # Facades
42
+ "MessageBox",
43
+ "QueryBox",
44
+ # Color tools
45
+ "ColorChooser",
46
+ "ColorChooserDialog",
47
+ ]
@@ -0,0 +1,588 @@
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
+ and hex input.
6
+ """
7
+ import tkinter
8
+ from collections import namedtuple
9
+ from tkinter import Canvas, IntVar, StringVar
10
+ from types import SimpleNamespace
11
+ from typing import Any, Callable, List, NamedTuple, Optional, Tuple
12
+
13
+ from PIL import ImageColor
14
+
15
+ from bootstack.constants import *
16
+ from bootstack._core import colorutils
17
+ from bootstack._core.colorutils import HEX, HSL, HUE, LUM, RGB, SAT
18
+ from bootstack.dialogs._impl.dialog import Dialog
19
+ from bootstack.i18n import MessageCatalog
20
+ from bootstack._runtime import utility
21
+ from bootstack.style.style import get_style, get_theme_color
22
+ from bootstack.widgets._impl.composites.tabs.tabview import TabView
23
+ from bootstack.widgets._impl.primitives import Button, Entry, Frame, Label, Spinbox
24
+
25
+ # Theme-color families shown on the "Themed" tab, in display order. Each is a
26
+ # semantic color the active theme exposes as a full light-to-dark band; `gray`
27
+ # is the neutral ramp for surface/text picks. (`secondary` is intentionally
28
+ # omitted — it is a flat token with no band.)
29
+ THEMED_FAMILIES: Tuple[str, ...] = (
30
+ "primary", "success", "info", "warning", "danger", "gray",
31
+ )
32
+ # Band stops sampled per family, light to dark — rendered as one gradient row.
33
+ THEMED_SHADES: Tuple[int, ...] = (
34
+ 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950,
35
+ )
36
+
37
+ ttk = SimpleNamespace(
38
+ Button=Button,
39
+ Canvas=Canvas,
40
+ Entry=Entry,
41
+ Frame=Frame,
42
+ IntVar=IntVar,
43
+ Label=Label,
44
+ Spinbox=Spinbox,
45
+ StringVar=StringVar,
46
+ use_style=get_style,
47
+ )
48
+
49
+ ColorValues = namedtuple('ColorValues', 'h s l r g b hex')
50
+
51
+
52
+ class ColorChoice(NamedTuple):
53
+ """A color selected from `ColorChooserDialog` or `ask_color`."""
54
+
55
+ rgb: Tuple[int, int, int]
56
+ """The color as an `(r, g, b)` tuple, each component 0–255."""
57
+ hsl: Tuple[int, int, int]
58
+ """The color as an `(h, s, l)` tuple — hue 0–360, saturation and luminance 0–100."""
59
+ hex: str
60
+ """The color as a lowercase hex string, e.g. `'#ff0000'`."""
61
+
62
+
63
+ class ColorChooser(ttk.Frame):
64
+ """Color chooser widget — hue/saturation spectrum with luminance slider."""
65
+
66
+ def __init__(
67
+ self, master: Optional[tkinter.Misc], initial_color: Optional[str] = None,
68
+ padding: Optional[int] = None) -> None:
69
+ """Create a color chooser widget.
70
+
71
+ Shows a hue/saturation spectrum with a luminance slider, RGB/HSL/hex
72
+ numeric inputs, and a live color preview.
73
+
74
+ Args:
75
+ master: Parent widget.
76
+ initial_color: Initial color string; defaults to theme background.
77
+ padding: Padding around the chooser.
78
+ """
79
+ super().__init__(master, padding=padding)
80
+ self.tframe = ttk.Frame(self, padding=5)
81
+ self.tframe.pack(fill=X)
82
+ self.bframe = ttk.Frame(self, padding=(5, 0, 5, 5))
83
+ self.bframe.pack(fill=X)
84
+
85
+ self.notebook = TabView(self.tframe)
86
+ self.notebook.pack(fill=BOTH)
87
+
88
+ self.style = ttk.use_style()
89
+ self.colors = self.style.colors
90
+ fallback_bg = (
91
+ self.colors.get("bg")
92
+ if isinstance(self.colors, dict)
93
+ else getattr(self.colors, "bg", None)
94
+ ) or "#ffffff"
95
+ self.initial_color = initial_color or fallback_bg
96
+
97
+ # color variables
98
+ r, g, b = ImageColor.getrgb(self.initial_color)
99
+ h, s, l = colorutils.color_to_hsl((r, g, b), RGB)
100
+ hx = colorutils.color_to_hex((r, g, b), RGB)
101
+
102
+ self.hue = ttk.IntVar(value=h)
103
+ self.sat = ttk.IntVar(value=s)
104
+ self.lum = ttk.IntVar(value=l)
105
+ self.red = ttk.IntVar(value=r)
106
+ self.grn = ttk.IntVar(value=g)
107
+ self.blu = ttk.IntVar(value=b)
108
+ self.hex = ttk.StringVar(value=hx)
109
+
110
+ # widget sizes (adjusted by widget scaling)
111
+ self.spectrum_height = utility.scale_size(self, 240)
112
+ self.spectrum_width = utility.scale_size(self, 530)
113
+ self.spectrum_point = utility.scale_size(self, 12)
114
+
115
+ # build widgets — the themed swatches and the spectrum each get a tab
116
+ # (themed first); the luminance scale, preview, and numeric inputs stay
117
+ # shared below both tabs (so switching tabs doesn't shift the layout).
118
+ themed_page = self.notebook.add('themed', text='color.themed')
119
+ self.themed_swatches = self.create_swatches(themed_page)
120
+ self.themed_swatches.pack(fill=BOTH, expand=YES)
121
+
122
+ custom_page = self.notebook.add('custom', text='color.custom')
123
+ self.color_spectrum = self.create_spectrum(custom_page)
124
+ self.color_spectrum.pack(fill=X, side=TOP)
125
+
126
+ self.luminance_scale = self.create_luminance_scale(self.tframe)
127
+ self.luminance_scale.pack(fill=X)
128
+
129
+ preview_frame = self.create_preview(self.bframe)
130
+ preview_frame.pack(side=LEFT, fill=BOTH, expand=YES, padx=(0, 5))
131
+ self.color_entries = self.create_value_inputs(self.bframe)
132
+ self.color_entries.pack(side=RIGHT)
133
+
134
+ self.create_spectrum_indicator()
135
+ self.create_luminance_indicator()
136
+
137
+ def create_spectrum(self, master: tkinter.Misc) -> ttk.Canvas:
138
+ """Create the color spectrum canvas"""
139
+ # canvas and point dimensions
140
+ width = self.spectrum_width
141
+ height = self.spectrum_height
142
+ xf = yf = self.spectrum_point
143
+
144
+ # create canvas widget and binding
145
+ canvas = ttk.Canvas(master, width=width, height=height, cursor='tcross')
146
+ canvas.bind("<B1-Motion>", self.on_spectrum_interaction, add="+")
147
+ canvas.bind("<Button-1>", self.on_spectrum_interaction, add="+")
148
+
149
+ # add color points
150
+ for x, colorx in enumerate(range(0, width, xf)):
151
+ for y, colory in enumerate(range(0, height, yf)):
152
+ values = self.color_from_coords(colorx, colory)
153
+ fill = values.hex
154
+ bbox = [x * xf, y * yf, (x * xf) + xf, (y * yf) + yf]
155
+ canvas.create_rectangle(*bbox, fill=fill, width=0)
156
+ return canvas
157
+
158
+ def create_spectrum_indicator(self) -> None:
159
+ """Create a square indicator that displays in the position of
160
+ the selected color"""
161
+ s = utility.scale_size(self, 10)
162
+ width = utility.scale_size(self, 2)
163
+ values = self.get_variables()
164
+ x1, y1 = self.coords_from_color(values.hex)
165
+ tag = ['spectrum-indicator']
166
+ self.color_spectrum.create_rectangle(
167
+ x1, y1, x1 + s, y1 + s, width=width, tags=[tag])
168
+ self.color_spectrum.tag_lower('spectrum-indicator')
169
+
170
+ # widget builder methods
171
+ def create_swatches(self, master: tkinter.Misc) -> ttk.Frame:
172
+ """Create a grid of the active theme's color bands.
173
+
174
+ Each row is one semantic color family rendered as a light-to-dark
175
+ gradient of its band stops; clicking any swatch loads that exact
176
+ on-theme color into the chooser.
177
+ """
178
+ gap = utility.scale_size(self, 1)
179
+ n_cols = len(THEMED_SHADES)
180
+ n_rows = len(THEMED_FAMILIES)
181
+
182
+ # Distribute leftover pixels so the grid sums to EXACTLY the spectrum
183
+ # width/height — otherwise integer rounding leaves the themed page a few
184
+ # pixels off and switching tabs shifts the layout.
185
+ avail_w = self.spectrum_width - gap * (n_cols - 1)
186
+ base_w, extra_w = divmod(avail_w, n_cols)
187
+ col_widths = [base_w + (1 if j < extra_w else 0) for j in range(n_cols)]
188
+ avail_h = self.spectrum_height - gap * (n_rows - 1)
189
+ base_h, extra_h = divmod(avail_h, n_rows)
190
+
191
+ container = ttk.Frame(master)
192
+ for i, family in enumerate(THEMED_FAMILIES):
193
+ row_height = base_h + (1 if i < extra_h else 0)
194
+ row_frame = ttk.Frame(container)
195
+ for j, shade in enumerate(THEMED_SHADES):
196
+ token = f"{family}[{shade}]"
197
+ hexcolor = get_theme_color(token)
198
+ swatch = ttk.Frame(
199
+ row_frame,
200
+ surface=token,
201
+ width=col_widths[j],
202
+ height=row_height,
203
+ )
204
+ swatch.bind(
205
+ '<Button-1>',
206
+ lambda _e, hx=hexcolor: self.on_press_swatch(hx),
207
+ add="+",
208
+ )
209
+ # Left-pad only, so the inter-swatch gap is exactly `gap` (a
210
+ # symmetric padx would double it and widen the page).
211
+ swatch.pack(side=LEFT, padx=(gap if j > 0 else 0, 0))
212
+ row_frame.pack(fill=X, pady=(gap if i > 0 else 0, 0))
213
+ return container
214
+
215
+ def create_preview(self, master: tkinter.Misc) -> ttk.Frame:
216
+ """Create the preview swatches for the original and new colors."""
217
+ container = ttk.Frame(master)
218
+ constrast_fg = colorutils.contrast_color(color=self.initial_color, model='hex')
219
+
220
+ # Each swatch is a single ttk.Label: unlike most ttk widgets, Label
221
+ # honors the background/foreground instance options directly, so the label
222
+ # itself is the color fill with its caption on top — no native tk widget
223
+ # and no autostyle coupling, and live updates stay an O(1) configure().
224
+ old = ttk.Label(
225
+ container,
226
+ text=MessageCatalog.translate("color.current"),
227
+ background=self.initial_color,
228
+ foreground=constrast_fg,
229
+ anchor=NW,
230
+ width=7,
231
+ padding=6,
232
+ )
233
+ old.pack(side=LEFT, fill=BOTH, expand=YES, padx=(0, 2))
234
+
235
+ self.preview = ttk.Label(
236
+ container,
237
+ text=MessageCatalog.translate("color.new"),
238
+ background=self.initial_color,
239
+ foreground=constrast_fg,
240
+ anchor=NW,
241
+ width=7,
242
+ padding=6,
243
+ )
244
+ self.preview.pack(side=LEFT, fill=BOTH, expand=YES, padx=(2, 0))
245
+
246
+ return container
247
+
248
+ def create_value_inputs(self, master: tkinter.Misc) -> ttk.Frame:
249
+ """Create color value input widgets"""
250
+ container = ttk.Frame(master)
251
+ for x in range(4):
252
+ container.columnconfigure(x, weight=1)
253
+
254
+ # value labels - use semantic keys with built-in localization
255
+ lbl_cnf = {'master': container, 'anchor': E}
256
+ ttk.Label(**lbl_cnf, text='color.hue:').grid(row=0, column=0, sticky=E)
257
+ ttk.Label(**lbl_cnf, text='color.sat:').grid(row=1, column=0, sticky=E)
258
+ ttk.Label(**lbl_cnf, text='color.lum:').grid(row=2, column=0, sticky=E)
259
+ ttk.Label(**lbl_cnf, text='color.hex:').grid(row=3, column=0, sticky=E)
260
+ ttk.Label(**lbl_cnf, text='color.red:').grid(row=0, column=2, sticky=E)
261
+ ttk.Label(**lbl_cnf, text='color.green:').grid(row=1, column=2, sticky=E)
262
+ ttk.Label(**lbl_cnf, text='color.blue:').grid(row=2, column=2, sticky=E)
263
+
264
+ # value spinners and entry widgets
265
+ rgb_cnf = {'master': container, 'from_': 0, 'to': 255, 'width': 3}
266
+ sl_cnf = {'master': container, 'from_': 0, 'to': 100, 'width': 3}
267
+ hue_cnf = {'master': container, 'from_': 0, 'to': 360, 'width': 3}
268
+ sb_hue = ttk.Spinbox(**hue_cnf, textvariable=self.hue)
269
+ sb_hue.grid(row=0, column=1, padx=4, pady=2, sticky=EW)
270
+ sb_sat = ttk.Spinbox(**sl_cnf, textvariable=self.sat)
271
+ sb_sat.grid(row=1, column=1, padx=4, pady=2, sticky=EW)
272
+ sb_lum = ttk.Spinbox(**sl_cnf, textvariable=self.lum)
273
+ sb_lum.grid(row=2, column=1, padx=4, pady=2, sticky=EW)
274
+ sb_red = ttk.Spinbox(**rgb_cnf, textvariable=self.red)
275
+ sb_red.grid(row=0, column=3, padx=4, pady=2, sticky=EW)
276
+ sb_grn = ttk.Spinbox(**rgb_cnf, textvariable=self.grn)
277
+ sb_grn.grid(row=1, column=3, padx=4, pady=2, sticky=EW)
278
+ sb_blu = ttk.Spinbox(**rgb_cnf, textvariable=self.blu)
279
+ sb_blu.grid(row=2, column=3, padx=4, pady=2, sticky=EW)
280
+ ent_hex = ttk.Entry(container, textvariable=self.hex)
281
+ ent_hex.grid(row=3, column=1, padx=4, columnspan=3, pady=2, sticky=EW)
282
+
283
+ # event binding for updating colors on value change
284
+ for sb in [sb_hue, sb_sat, sb_lum]:
285
+ for sequence in ['<<Increment>>', '<<Decrement>>', '<Return>', '<KP_Enter>']:
286
+ sb.bind(
287
+ sequence=sequence,
288
+ func=lambda _, w=sb: self.on_entry_value_change(w, HSL),
289
+ add="+"
290
+ )
291
+ for sb in [sb_red, sb_grn, sb_blu]:
292
+ for sequence in ['<<Increment>>', '<<Decrement>>', '<Return>', '<KP_Enter>']:
293
+ sb.bind(
294
+ sequence=sequence,
295
+ func=lambda _, w=sb: self.on_entry_value_change(w, RGB),
296
+ add="+"
297
+ )
298
+ for sequence in ['<Return>', '<KP_Enter>']:
299
+ ent_hex.bind(
300
+ sequence=sequence,
301
+ func=lambda _, w=ent_hex: self.on_entry_value_change(w, HEX),
302
+ add="+"
303
+ )
304
+
305
+ return container
306
+
307
+ def create_luminance_scale(self, master: tkinter.Misc) -> ttk.Canvas:
308
+ """Create the color luminance canvas"""
309
+ # widget dimensions
310
+ height = xf = self.spectrum_point
311
+ width = self.spectrum_width
312
+
313
+ values = self.get_variables()
314
+ canvas = ttk.Canvas(master, height=height, width=width)
315
+
316
+ # add color points to scale
317
+ for x, l in enumerate(range(0, width, xf)):
318
+ lum = l / width * LUM
319
+ fill = colorutils.update_hsl_value(
320
+ color=values.hex,
321
+ lum=lum,
322
+ inmodel='hex',
323
+ outmodel='hex'
324
+ )
325
+ bbox = [x * xf, 0, (x * xf) + xf, height]
326
+ tag = f'color{x}'
327
+ canvas.create_rectangle(*bbox, fill=fill, width=0, tags=[tag])
328
+ canvas.bind("<B1-Motion>", self.on_luminance_interaction, add="+")
329
+ canvas.bind("<Button-1>", self.on_luminance_interaction, add="+")
330
+ return canvas
331
+
332
+ def create_luminance_indicator(self) -> None:
333
+ """Create an indicator that displays in the position of the
334
+ luminance value"""
335
+ lum = 50
336
+ x1 = int(lum / LUM * self.spectrum_width) - \
337
+ ((self.spectrum_point - 2) // 2)
338
+ y1 = 0
339
+ x2 = x1 + self.spectrum_point
340
+ y2 = self.spectrum_point - 3
341
+ tag = 'luminance-indicator'
342
+ bbox = [x1, y1, x2, y2]
343
+ self.luminance_scale.create_rectangle(
344
+ *bbox, fill='white', outline='black', tags=[tag])
345
+ self.luminance_scale.tag_lower(tag)
346
+
347
+ def coords_from_color(self, hexcolor: str) -> Tuple[float, float]:
348
+ """Get the coordinates on the color spectrum from the color value"""
349
+ h, s, _ = colorutils.color_to_hsl(hexcolor)
350
+ x = (h / HUE) * self.spectrum_width
351
+ y = (1 - (s / SAT)) * self.spectrum_height
352
+ return x, y
353
+
354
+ def color_from_coords(self, x: int, y: int):
355
+ """Get the color value from the mouse position in the color spectrum"""
356
+ HEIGHT = self.spectrum_height
357
+ WIDTH = self.spectrum_width
358
+ h = int(min(HUE, max(0, (HUE / WIDTH) * x)))
359
+ s = int(min(SAT, max(0, SAT - ((SAT / HEIGHT) * y))))
360
+ l = 50
361
+ hx = colorutils.color_to_hex([h, s, l], 'hsl')
362
+ r, g, b = colorutils.color_to_rgb(hx)
363
+ return ColorValues(h, s, l, r, g, b, hx)
364
+
365
+ def set_variables(self, h: int, s: int, l: int, r: int, g: int, b: int, hx: str) -> None:
366
+ """Update the color value variables"""
367
+ self.hue.set(h)
368
+ self.sat.set(s)
369
+ self.lum.set(l)
370
+ self.red.set(r)
371
+ self.grn.set(g)
372
+ self.blu.set(b)
373
+ self.hex.set(hx)
374
+
375
+ def get_variables(self):
376
+ """Get the values of all color models and return a tuple of color values"""
377
+ h = self.hue.get()
378
+ s = self.sat.get()
379
+ l = self.lum.get()
380
+ r = self.red.get()
381
+ g = self.grn.get()
382
+ b = self.blu.get()
383
+ hx = self.hex.get()
384
+ return ColorValues(h, s, l, r, g, b, hx)
385
+
386
+ def update_preview(self) -> None:
387
+ """Update the color in the preview frame"""
388
+ hx = self.hex.get()
389
+ fg = colorutils.contrast_color(color=hx, model='hex')
390
+ self.preview.configure(background=hx, foreground=fg)
391
+
392
+ def update_luminance_scale(self) -> None:
393
+ """Update the luminance scale with the change in hue and saturation"""
394
+ values = self.get_variables()
395
+ width = self.spectrum_width
396
+ xf = self.spectrum_point
397
+ for x, l in enumerate(range(0, width, xf)):
398
+ lum = l / width * LUM
399
+ fill = colorutils.update_hsl_value(
400
+ color=values.hex,
401
+ lum=lum,
402
+ inmodel='hex',
403
+ outmodel='hex'
404
+ )
405
+ tag = f'color{x}'
406
+ self.luminance_scale.itemconfig(tag, fill=fill)
407
+
408
+ def update_luminance_indicator(self) -> None:
409
+ """Update the position of the luminance indicator"""
410
+ lum = self.lum.get()
411
+ x = int(lum / LUM * self.spectrum_width) - \
412
+ ((self.spectrum_point - 2) // 2)
413
+ self.luminance_scale.moveto('luminance-indicator', x, 0)
414
+ self.luminance_scale.tag_raise('luminance-indicator')
415
+
416
+ def update_spectrum_indicator(self) -> None:
417
+ """Move the spectrum indicator to a new location"""
418
+ values = self.get_variables()
419
+ x, y = self.coords_from_color(values.hex)
420
+ self.color_spectrum.moveto('spectrum-indicator', x, y)
421
+ self.color_spectrum.tag_raise('spectrum-indicator')
422
+ color = colorutils.contrast_color(values.hex, 'hex')
423
+ self.color_spectrum.itemconfig('spectrum-indicator', outline=color)
424
+
425
+ # color events
426
+ def sync_color_values(self, model) -> None:
427
+ """Sync all color model variables when one changes."""
428
+ values = self.get_variables()
429
+ if model == HEX:
430
+ hx = values.hex
431
+ r, g, b = colorutils.color_to_rgb(hx)
432
+ h, s, l = colorutils.color_to_hsl(hx)
433
+ elif model == RGB:
434
+ r, g, b = values.r, values.g, values.b
435
+ h, s, l = colorutils.color_to_hsl([r, g, b], 'rgb')
436
+ hx = colorutils.color_to_hex([r, g, b])
437
+ elif model == HSL:
438
+ h, s, l = values.h, values.s, values.l
439
+ r, g, b = colorutils.color_to_rgb([h, s, l], 'hsl')
440
+ hx = colorutils.color_to_hex([h, s, l], 'hsl')
441
+ self.set_variables(h, s, l, r, g, b, hx)
442
+ self.update_preview()
443
+ self.update_luminance_indicator()
444
+
445
+ def on_entry_value_change(self, widget: ttk.Spinbox, model: Any) -> None:
446
+ """Update widget colors when a color value input changes"""
447
+ is_valid = widget.validate()
448
+ if is_valid:
449
+ self.sync_color_values(model)
450
+ self.update_luminance_scale()
451
+ self.update_spectrum_indicator()
452
+
453
+ def on_press_swatch(self, hexcolor: str) -> None:
454
+ """Update widget colors when a themed swatch is clicked."""
455
+ self.hex.set(hexcolor)
456
+ self.sync_color_values(HEX)
457
+ self.update_luminance_scale()
458
+ self.update_spectrum_indicator()
459
+
460
+ def on_spectrum_interaction(self, event: tkinter.Event) -> None:
461
+ """Update widget colors when the color spectrum canvas is pressed"""
462
+ values = self.color_from_coords(event.x, event.y)
463
+ self.hue.set(values.h)
464
+ self.sat.set(values.s)
465
+ self.lum.set(values.l)
466
+ self.sync_color_values(HSL)
467
+ self.update_luminance_scale()
468
+ self.update_spectrum_indicator()
469
+
470
+ def on_luminance_interaction(self, event: tkinter.Event) -> None:
471
+ """Update widget colors when the luminance scale is pressed"""
472
+ l = max(0, min(LUM, int((event.x / self.spectrum_width) * LUM)))
473
+ self.lum.set(l)
474
+ self.sync_color_values(HSL)
475
+
476
+
477
+ class ColorChooserDialog:
478
+ """Dialog wrapper for the ColorChooser widget.
479
+
480
+ Args:
481
+ master: Parent widget used for positioning and event binding.
482
+ title: Dialog window title (localized).
483
+ initial_color: Initial color shown in the chooser; defaults to theme background.
484
+ """
485
+
486
+ def __init__(
487
+ self,
488
+ master: Optional[tkinter.Misc] = None,
489
+ title: str = "color.chooser",
490
+ initial_color: Optional[str] = None,
491
+ ) -> None:
492
+ self._master = master
493
+ self._title = title
494
+ self._initial_color = initial_color
495
+ self.result: Optional[ColorChoice] = None
496
+ self._emitted_result = False
497
+
498
+ self._dialog = Dialog(
499
+ parent=master,
500
+ title=self._title,
501
+ content_builder=self._build_content,
502
+ _raw_content=True,
503
+ footer_builder=self._build_footer,
504
+ )
505
+ self._chooser: Optional[ColorChooser] = None
506
+
507
+ # builders -----------------------------------------------------------------
508
+ def _build_content(self, master: tkinter.Widget) -> None:
509
+ self._chooser = ColorChooser(master, self._initial_color)
510
+ self._chooser.pack(fill=BOTH, expand=YES)
511
+
512
+ def _build_footer(self, master: tkinter.Widget) -> None:
513
+ ok = ttk.Button(
514
+ master,
515
+ accent='primary',
516
+ text='button.ok',
517
+ command=self._on_ok,
518
+ )
519
+ ok.pack(side=RIGHT)
520
+
521
+ cancel = ttk.Button(
522
+ master,
523
+ accent='default',
524
+ text='button.cancel',
525
+ command=self._on_cancel,
526
+ )
527
+ cancel.pack(side=RIGHT)
528
+
529
+ # callbacks ----------------------------------------------------------------
530
+ def _on_ok(self) -> None:
531
+ if self._chooser:
532
+ values = self._chooser.get_variables()
533
+ self.result = ColorChoice(
534
+ rgb=(values.r, values.g, values.b),
535
+ hsl=(values.h, values.s, values.l),
536
+ hex=values.hex
537
+ )
538
+ self._emit_result(confirmed=True)
539
+ if self._dialog.toplevel:
540
+ self._dialog.toplevel.after_idle(self._dialog.toplevel.destroy)
541
+
542
+ def _on_cancel(self) -> None:
543
+ self.result = None
544
+ self._emit_result(confirmed=False)
545
+ if self._dialog.toplevel:
546
+ self._dialog.toplevel.after_idle(self._dialog.toplevel.destroy)
547
+
548
+ # API ----------------------------------------------------------------------
549
+ def show(self, position: Optional[Tuple[int, int]] = None, modal: bool = True) -> None:
550
+ """Display the dialog."""
551
+ self.result = None
552
+ self._emitted_result = False
553
+ self._dialog.show(position=position, modal=modal)
554
+ if not self._emitted_result:
555
+ self._emit_result(confirmed=self.result is not None)
556
+
557
+ def on_dialog_result(self, callback: Callable[[Any], None]) -> Optional[str]:
558
+ """Bind a callback fired when the dialog produces a result."""
559
+ target = self._master or self._dialog.toplevel
560
+ if target is None:
561
+ return None
562
+
563
+ def handler(event):
564
+ callback(getattr(event, "data", None))
565
+
566
+ return target.bind("<<DialogResult>>", handler, add="+")
567
+
568
+ def off_dialog_result(self, funcid: str) -> None:
569
+ """Unbind a previously bound dialog result callback."""
570
+ target = self._master or self._dialog.toplevel
571
+ if target is None:
572
+ return
573
+ target.unbind("<<DialogResult>>", funcid)
574
+
575
+ # helpers ------------------------------------------------------------------
576
+ def _emit_result(self, confirmed: bool) -> None:
577
+ payload = {"result": self.result, "confirmed": confirmed}
578
+ target = self._master or self._dialog.toplevel
579
+ if not target:
580
+ return
581
+ try:
582
+ target.event_generate("<<DialogResult>>", data=payload)
583
+ except Exception:
584
+ try:
585
+ target.event_generate("<<DialogResult>>")
586
+ except Exception:
587
+ pass
588
+ self._emitted_result = True