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,369 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from bootstack.style.style_builder_ttk import StyleBuilderTtk
6
+ from bootstack.style.element import Element, ElementImage
7
+ from bootstack.style.utility import recolor_element_image
8
+ from bootstack.style.builders.utils import (
9
+ normalize_button_density,
10
+ button_font,
11
+ apply_icon_mapping,
12
+ )
13
+
14
+
15
+ def _selection_wash(b: StyleBuilderTtk, accent: str, background: str, options: dict) -> str:
16
+ """Row selection background — NEUTRAL by default; accent only when opted in.
17
+
18
+ Governs the row's background wash only. The selection control's glyph still
19
+ follows the accent (it reads as the accent indicator regardless). `wash=False`
20
+ drops the wash entirely (selection shown via the control); `accent_selection`
21
+ requests the old accent-tinted wash.
22
+ """
23
+ if not options.get('wash', True):
24
+ return background
25
+ if options.get('accent_selection', False):
26
+ return b.subtle(accent or 'primary', background)
27
+ return b.active(background)
28
+
29
+
30
+ @StyleBuilderTtk.register_builder('container', 'ListView.TFrame')
31
+ def build_list_container_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
32
+ """List container frame style - no hover state (only items should have hover)."""
33
+ surface_token = options.get('surface', 'content')
34
+ background = b.color(surface_token)
35
+ b.configure_style(ttk_style, background=background, relief='flat')
36
+
37
+
38
+ @StyleBuilderTtk.register_builder('list', 'ListView.TFrame')
39
+ def build_list_frame_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
40
+ """List internal frame style - has state mapping to sync with parent ListItem."""
41
+ hoverable = options.get('hoverable', True)
42
+ accent_token = accent or 'primary'
43
+ surface_token = options.get('surface', 'content')
44
+ base_token = surface_token.split('[')[0]
45
+ background = b.color(surface_token)
46
+ pressed = b.pressed(background)
47
+ selected = _selection_wash(b, accent, background, options)
48
+ b.configure_style(ttk_style, background=background, relief='flat')
49
+
50
+ # No hover wash and no 'focus' wash: rows show selection only (keyboard focus
51
+ # rides the row image's bar). Row hover was removed — it competed with the
52
+ # stripe and selection washes (several subtle colors at once) and is gone in
53
+ # DataTable too, so this keeps the data widgets consistent.
54
+ background_state_map = [
55
+ ('selected', selected),
56
+ ('', background)
57
+ ]
58
+
59
+ b.map_style(ttk_style, background=[x for x in background_state_map if x is not None])
60
+
61
+
62
+ @StyleBuilderTtk.register_builder('item', 'ListView.TFrame')
63
+ def build_list_item_default_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
64
+ build_list_item_style(b, ttk_style, accent, 'list-item', **options)
65
+
66
+
67
+ @StyleBuilderTtk.register_builder('separated_item', 'ListView.TFrame')
68
+ def build_list_item_separated_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
69
+ build_list_item_style(b, ttk_style, accent, 'list-item-separated', **options)
70
+
71
+
72
+ def build_list_item_style(
73
+ b: StyleBuilderTtk,
74
+ ttk_style: str,
75
+ accent: Optional[str] = None,
76
+ variant: str = 'item',
77
+ **options
78
+ ):
79
+ # TODO add density option
80
+
81
+ hoverable = options.get('hoverable', True)
82
+ surface_token = options.get('surface', 'content')
83
+ base_token = surface_token.split('[')[0]
84
+ accent_token = accent or 'primary'
85
+
86
+ background = b.color(surface_token)
87
+ pressed = b.pressed(background)
88
+ selected = _selection_wash(b, accent, background, options)
89
+
90
+ # Use separated image for separated variant, otherwise use standard list_item
91
+ is_separated = 'separated' in variant
92
+ image_key = 'listrow_default'
93
+ # An explicit `separator` color overrides the derived border — used to draw
94
+ # the divider in the stripe color so striped+separated rows read as one tone.
95
+ separator = options.get('separator')
96
+ if is_separated:
97
+ border_normal = b.color(separator) if separator else b.border(background)
98
+ else:
99
+ border_normal = background
100
+
101
+ def _bd(fill):
102
+ # Separated rows draw a real bottom separator (border_normal); otherwise
103
+ # the border tracks the fill so it stays invisible in every state. Without
104
+ # this, hover/selected rows showed a stray 1px frame (the border channel
105
+ # kept the base background while the fill changed).
106
+ return border_normal if is_separated else fill
107
+
108
+ # The magenta channel of the row image is the focus ring; paint it the
109
+ # foreground color but ONLY on the 'background focus' state (keyboard focus
110
+ # via visual_focus — see _runtime/visual_focus.py). Mouse focus has 'focus'
111
+ # but not 'background', so it shows no ring. Other (non-focus) images paint
112
+ # the ring channel = fill so it stays invisible.
113
+ focus_ring = b.color('foreground')
114
+
115
+ normal_img = recolor_element_image(image_key, background, _bd(background), background, None, border_normal)
116
+ # No left selection bar: the indicator channels match the selected fill so it
117
+ # stays invisible (selection reads via the row wash + accent checkbox, aligned
118
+ # with DataTable).
119
+ selected_img = recolor_element_image(image_key, selected, _bd(selected), selected, None, selected)
120
+
121
+ # Keyboard-focus bar images (bar on the current fill). Row hover was removed
122
+ # (it competed with stripe/selection washes), so there is no hover variant —
123
+ # the bar only needs a normal and a selected fill.
124
+ focus_ring_img = recolor_element_image(image_key, background, _bd(background), focus_ring, None, border_normal)
125
+ selected_focus_img = recolor_element_image(image_key, selected, _bd(selected), focus_ring, None, selected)
126
+
127
+ image_state_specs = [
128
+ ('selected background focus', selected_focus_img.image),
129
+ ('selected', selected_img.image),
130
+ ('background focus', focus_ring_img.image),
131
+ ('', normal_img.image),
132
+ ]
133
+
134
+ b.create_style_element_image(
135
+ ElementImage(f'{ttk_style}.border', normal_img.image, sticky='nsew', border=normal_img.meta.border).state_specs(
136
+ [x for x in image_state_specs if x is not None]
137
+ )
138
+ )
139
+
140
+ b.create_style_layout(
141
+ ttk_style,
142
+ Element(f'{ttk_style}.border', sticky='nsew').children(
143
+ [
144
+ Element(f'{ttk_style}.padding', sticky='')
145
+ ]
146
+ )
147
+ )
148
+ b.configure_style(ttk_style, background=background, relief='flat')
149
+
150
+
151
+ @StyleBuilderTtk.register_builder('list', 'ListView.TButton')
152
+ def build_list_item_button_style(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
153
+ hoverable = options.get('hoverable', True)
154
+ surface_token = options.get('surface', 'content')
155
+ base_token = surface_token.split('[')[0]
156
+ density = normalize_button_density(options.get('density', 'default'))
157
+
158
+ background = b.color(surface_token)
159
+ pressed = b.pressed(background)
160
+ selected = _selection_wash(b, accent, background, options)
161
+
162
+ b.create_style_layout(
163
+ ttk_style,
164
+ Element('Label.border', sticky='nsew').children([
165
+ Element('Label.padding', sticky='nsew').children([
166
+ Element('Label.label', sticky='nsew')
167
+ ])
168
+ ])
169
+ )
170
+
171
+ # Selection + momentary press only — NO hover and NO plain-'focus' highlight.
172
+ # 'selected' keeps the button fill matching a selected row; 'pressed' gives a
173
+ # brief click feedback on the chevron. Row hover was removed (it competed with
174
+ # the stripe/selection washes and never cleared cleanly across recycling).
175
+ background_state_spec = [
176
+ ('selected', selected),
177
+ ('pressed', pressed),
178
+ ]
179
+
180
+ b.configure_style(ttk_style, background=background, padding=0, relief='flat', stipple='gray12', font=button_font(density))
181
+ b.map_style(
182
+ ttk_style,
183
+ foreground=[],
184
+ background=[x for x in background_state_spec if x is not None]
185
+ )
186
+
187
+
188
+ @StyleBuilderTtk.register_builder('radio', 'ListView.TLabel')
189
+ def build_list_item_radio_label(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
190
+ build_list_item_label(b, ttk_style, accent, 'list-radio', **options)
191
+
192
+
193
+ @StyleBuilderTtk.register_builder('check', 'ListView.TLabel')
194
+ def build_list_item_check_label(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
195
+ build_list_item_label(b, ttk_style, accent, 'list-checkbox', **options)
196
+
197
+
198
+ @StyleBuilderTtk.register_builder('list', 'ListView.TLabel')
199
+ def build_list_item_default_label(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
200
+ build_list_item_label(b, ttk_style, accent, 'list', **options)
201
+
202
+
203
+ def _list_icon_size(b: StyleBuilderTtk, density: str) -> int:
204
+ """Get icon size for list items based on density.
205
+
206
+ Args:
207
+ b: The style builder instance.
208
+ density: The density ('default' or 'compact').
209
+
210
+ Returns:
211
+ Scaled icon size in pixels.
212
+ """
213
+ return 16 if density == 'compact' else 18
214
+
215
+
216
+ @StyleBuilderTtk.register_builder('icon', 'ListView.TButton')
217
+ @StyleBuilderTtk.register_builder('icon', 'ListView.TLabel')
218
+ def build_list_icon(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
219
+ hoverable = options.get('hoverable', True)
220
+ surface_token = options.get('surface', 'content')
221
+ base_token = surface_token.split('[')[0]
222
+ density = normalize_button_density(options.get('density', 'default'))
223
+
224
+ background = b.color(surface_token)
225
+ pressed = b.pressed(background)
226
+ selected = _selection_wash(b, accent, background, options)
227
+ on_background = b.on_color(background)
228
+ on_selected = b.on_color(selected)
229
+ on_disabled = b.disabled('text', background)
230
+
231
+ # Create layout (remove focus border)
232
+ b.create_style_layout(
233
+ ttk_style,
234
+ Element('Label.border', sticky='nsew').children([
235
+ Element('Label.padding', sticky='nsew').children([
236
+ Element('Label.label', sticky='nsew')
237
+ ])
238
+ ])
239
+ )
240
+
241
+ # Configure style
242
+ b.configure_style(
243
+ ttk_style,
244
+ background=background,
245
+ foreground=on_background,
246
+ padding=0,
247
+ relief='flat',
248
+ stipple='gray12',
249
+ font=button_font(density),
250
+ )
251
+
252
+ foreground_state_spec = [
253
+ ('disabled', on_disabled),
254
+ ('selected pressed', on_selected),
255
+ ('selected', on_selected),
256
+ ('', on_background)
257
+ ]
258
+
259
+ # Selection only — no hover, no 'focus' wash. Row hover was removed (it
260
+ # competed with the stripe/selection washes); the icon/chevron fill just
261
+ # tracks the row's selected state so it stays consistent.
262
+ background_state_spec = [
263
+ ('selected', selected),
264
+ ]
265
+
266
+ # Prepare state spec
267
+ state_spec = dict(
268
+ foreground=[x for x in foreground_state_spec if x is not None],
269
+ background=[x for x in background_state_spec if x is not None]
270
+ )
271
+
272
+ # Apply icon mapping if icon is provided - use density-aware icon size
273
+ icon_size = _list_icon_size(b, density)
274
+ state_spec = apply_icon_mapping(b, options, state_spec, icon_size)
275
+ b.map_style(ttk_style, **state_spec)
276
+
277
+
278
+ @StyleBuilderTtk.register_builder('selection', 'ListView.TLabel')
279
+ def build_list_selection_icon(b: StyleBuilderTtk, ttk_style: str, accent: str = None, **options):
280
+ """Selection checkbox/radio icon for list rows.
281
+
282
+ Mirrors the DataTable selection markers: the glyph is a muted outline when
283
+ unchecked and accent-filled when checked, so selection reads the same across
284
+ both data-bound widgets (unlike a generic row icon, which stays foreground).
285
+ """
286
+ hoverable = options.get('hoverable', True)
287
+ surface_token = options.get('surface', 'content')
288
+ base_token = surface_token.split('[')[0]
289
+ density = normalize_button_density(options.get('density', 'default'))
290
+
291
+ background = b.color(surface_token)
292
+ pressed = b.pressed(background)
293
+ # Neutral row wash by default; the GLYPH foreground below stays accent so the
294
+ # checked control is the accent indicator regardless of the wash.
295
+ selected = _selection_wash(b, accent, background, options)
296
+ accent_color = b.color(accent or 'primary')
297
+ muted = b.color('muted')
298
+ on_disabled = b.disabled('text', background)
299
+
300
+ b.create_style_layout(
301
+ ttk_style,
302
+ Element('Label.border', sticky='nsew').children([
303
+ Element('Label.padding', sticky='nsew').children([
304
+ Element('Label.label', sticky='nsew')
305
+ ])
306
+ ])
307
+ )
308
+
309
+ b.configure_style(
310
+ ttk_style,
311
+ background=background,
312
+ foreground=muted,
313
+ padding=0,
314
+ relief='flat',
315
+ stipple='gray12',
316
+ font=button_font(density),
317
+ )
318
+
319
+ foreground_state_spec = [
320
+ ('disabled', on_disabled),
321
+ ('selected', accent_color), # accent-filled when checked
322
+ ('alternate', accent_color), # partially-selected parent (mixed) -> accent dash
323
+ ('', muted), # muted outline when unchecked
324
+ ]
325
+ # Selection only — no hover, no 'focus' wash (row hover was removed).
326
+ background_state_spec = [
327
+ ('selected', selected),
328
+ ]
329
+ state_spec = dict(
330
+ foreground=[x for x in foreground_state_spec if x is not None],
331
+ background=[x for x in background_state_spec if x is not None],
332
+ )
333
+ # Fixed crisp size across densities so the control doesn't shrink/pixelate
334
+ # in compact mode (used by both ListView and Tree selection markers).
335
+ state_spec = apply_icon_mapping(b, options, state_spec, 18)
336
+ b.map_style(ttk_style, **state_spec)
337
+
338
+
339
+ def build_list_item_label(b: StyleBuilderTtk, ttk_style: str, accent: str = None, variant: str = None, **options):
340
+ """
341
+
342
+ Style Options
343
+ * surface
344
+ * hoverable
345
+ * foreground
346
+ """
347
+ hoverable = options.get('hoverable', True)
348
+ surface_token = options.get('surface', 'content')
349
+ base_token = surface_token.split('[')[0]
350
+ foreground_token = options.get('foreground', None)
351
+
352
+ background = b.color(surface_token)
353
+ pressed = b.pressed(background)
354
+ selected = _selection_wash(b, accent, background, options)
355
+ on_background = b.color(foreground_token) if foreground_token else b.on_color(background)
356
+ on_selected = b.on_color(selected)
357
+
358
+ # Selection only — no hover wash, no 'focus' wash (keyboard focus = row bar).
359
+ # Row hover was removed (competed with stripe/selection washes).
360
+ background_state_spec = [
361
+ ('selected', selected),
362
+ ]
363
+
364
+ b.configure_style(ttk_style, background=background, foreground=on_background)
365
+ b.map_style(
366
+ ttk_style,
367
+ background=[x for x in background_state_spec if x is not None],
368
+ foreground=[('selected', on_selected)]
369
+ )
@@ -0,0 +1,91 @@
1
+ """Menubutton widget style builders.
2
+
3
+ This module contains style builders for ttk.Menubutton widgets and variants.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from bootstack.style.style_builder_ttk import StyleBuilderTtk
9
+ from bootstack.style.element import Element, ElementImage
10
+ from bootstack.style.utility import recolor_element_image
11
+ from bootstack.style.builders.utils import (
12
+ button_font,
13
+ button_height,
14
+ button_padding,
15
+ normalize_button_density,
16
+ )
17
+
18
+
19
+ @StyleBuilderTtk.register_builder('menubar-item', 'TMenubutton')
20
+ @StyleBuilderTtk.register_builder('menubar-item', 'TButton')
21
+ def build_menubar_item(b: StyleBuilderTtk, ttk_style: str, color: str = None, **options):
22
+ # Default to the base content surface (so a menu trigger blends into a
23
+ # content-surfaced toolbar). The window menu bar passes surface='chrome'
24
+ # explicitly, so the chrome strip is unaffected. Note `'content'` is dropped
25
+ # before it reaches here, so a content toolbar arrives with no surface option
26
+ # and lands on this default.
27
+ surface_token = options.get('surface', 'content')
28
+ # Follow the host toolbar's density (sizing the trigger image, padding, and
29
+ # font like a button of the same density) so menu triggers match the buttons
30
+ # beside them — rather than being locked to a single compact size.
31
+ density = options.get('density', 'default')
32
+ icon_only = options.get('icon_only', False)
33
+
34
+ surface = b.color(surface_token)
35
+ active = b.active(surface)
36
+ pressed = b.pressed(surface)
37
+
38
+ foreground_normal = b.on_color(surface)
39
+ foreground_disabled = b.disabled('text', surface)
40
+
41
+ # The menu-bar look is a flat button — the normal state paints the surface on
42
+ # all four channels (no visible border); hover / pressed reveal the wash.
43
+ image_key = f'button_{normalize_button_density(density)}'
44
+ normal_img = recolor_element_image(image_key, surface, surface, surface, surface)
45
+ pressed_img = recolor_element_image(image_key, pressed, pressed, surface, surface)
46
+ active_img = recolor_element_image(image_key, active, active, surface, surface)
47
+
48
+ # button element
49
+ b.create_style_element_image(
50
+ ElementImage(f'{ttk_style}.Button.border', normal_img.image, sticky="nsew",
51
+ border=normal_img.meta.border, padding=normal_img.meta.border,
52
+ height=button_height(b, density)).state_specs([
53
+ ('background focus pressed', pressed_img.image),
54
+ ('background focus hover', active_img.image),
55
+ ('background focus', pressed_img.image),
56
+ ('pressed', pressed_img.image),
57
+ ('hover', active_img.image)
58
+ ]))
59
+
60
+ b.create_style_layout(
61
+ ttk_style, Element(f"{ttk_style}.Button.border", sticky="nsew").children([
62
+ Element("Button.padding", sticky="nsew").children([
63
+ Element("Button.label", sticky="nsew")
64
+ ])
65
+ ]))
66
+
67
+ b.configure_style(ttk_style, font=button_font(density), background=surface,
68
+ foreground=foreground_normal,
69
+ padding=button_padding(b, icon_only, density))
70
+
71
+ state_spec = dict(
72
+ foreground=[
73
+ ('disabled', foreground_disabled),
74
+ ('background focus', foreground_normal),
75
+ ('', foreground_normal)
76
+ ]
77
+ )
78
+
79
+ state_spec = _apply_icon_mapping(b, options, state_spec)
80
+ b.map_style(ttk_style, **state_spec)
81
+
82
+
83
+ def _apply_icon_mapping(b: StyleBuilderTtk, options: dict, state_spec: dict) -> dict:
84
+ """Apply icon mapping if an icon is provided in options."""
85
+ icon = options.get('icon')
86
+ if icon is None:
87
+ return state_spec
88
+
89
+ icon = b.normalize_icon_spec(icon, 20)
90
+ state_spec['image'] = b.map_stateful_icons(icon, state_spec['foreground'])
91
+ return state_spec