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,594 @@
1
+ """Core dialog base class for bootstack dialogs.
2
+
3
+ This module provides the base `Dialog` class using a builder pattern
4
+ for creating flexible, customizable dialogs with composition-based content
5
+ and footer builders.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ import tkinter
12
+ from tkinter import Widget
13
+ from typing import Any, Callable, Iterable, Literal, Mapping, Optional, Tuple, TypedDict, Union
14
+
15
+ from bootstack.widgets._core.base import PublicWidgetBase
16
+ from bootstack.widgets._impl.primitives.button import Button as _Button
17
+ from bootstack.widgets._impl.primitives.frame import Frame as _Frame
18
+ from bootstack.widgets._impl.primitives.separator import Separator as _Separator
19
+ from bootstack.widgets.types import Master, AccentToken, ButtonVariant, Padding, SurfaceToken, WindowStyle
20
+ from bootstack._runtime.toplevel import Toplevel
21
+ from bootstack._runtime.window_utilities import AnchorPoint, WindowPositioning
22
+
23
+ # --- Types -----------------------------------------------------------------
24
+
25
+ # Called to fill the dialog body. The dialog content area is a public container
26
+ # pushed as the active parent, so the builder can create widgets directly (no
27
+ # `parent=`). It is also passed the content container, so a builder that wants an
28
+ # explicit handle can declare a single parameter (`def build(content): ...`).
29
+ ContentBuilder = Callable[..., None]
30
+ FooterBuilder = Callable[[Widget], None]
31
+
32
+ # Where a dialog can anchor itself. A public widget anchors to that widget; the
33
+ # string forms anchor to the screen, the cursor, or the parent window.
34
+ AnchorTarget = Union["PublicWidgetBase", Literal["screen", "cursor", "parent"]]
35
+
36
+
37
+ def _resolve_anchor_target(anchor_to: Any) -> Any:
38
+ """Unwrap a public widget to its underlying tk widget for positioning.
39
+
40
+ A public bootstack widget is a plain object holding `_internal`, not a tk
41
+ widget — so positioning (which reads `winfo_*`) needs the internal handle.
42
+ String forms (`'screen'`/`'cursor'`/`'parent'`) and `None` pass through.
43
+ """
44
+ internal = getattr(anchor_to, "_internal", None)
45
+ return internal if internal is not None else anchor_to
46
+
47
+
48
+ def _call_builder(builder: Callable[..., None], content: Any) -> None:
49
+ """Call a content builder, passing the content container only if it takes one.
50
+
51
+ Supports both the idiomatic parent-free form (`def build(): ...`, run inside
52
+ the content container's context) and an explicit form (`def build(content):
53
+ ...`). Falls back to passing the argument if the signature can't be read.
54
+ """
55
+ import inspect
56
+
57
+ try:
58
+ params = inspect.signature(builder).parameters.values()
59
+ takes_arg = any(
60
+ p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD, p.VAR_POSITIONAL)
61
+ for p in params
62
+ )
63
+ except (TypeError, ValueError):
64
+ takes_arg = True
65
+ builder(content) if takes_arg else builder()
66
+
67
+ ButtonRole = Literal["primary", "secondary", "danger", "cancel"]
68
+ DialogMode = Literal["modal", "popover", "sheet"]
69
+
70
+
71
+ @dataclass
72
+ class DialogButton:
73
+ """Specification for a dialog button."""
74
+
75
+ text: str
76
+ """Button label text displayed to the user."""
77
+
78
+ role: ButtonRole = "secondary"
79
+ """Role that determines the button's styling and keyboard behavior. One of
80
+ `'primary'` (main action, triggered by Enter when `default=True`),
81
+ `'secondary'` (neutral action, no keyboard shortcut), `'danger'` (destructive
82
+ action, not focused by default), or `'cancel'` (triggered by Escape)."""
83
+
84
+ result: Any | None = None # value assigned to dialog.result
85
+ """Value assigned to `dialog.result` when clicked."""
86
+
87
+ closes: bool = True # close dialog after click
88
+ """Whether the button closes the dialog when clicked."""
89
+
90
+ default: bool = False # default button (Enter)
91
+ """Whether this is the default button (focused, triggered by Enter)."""
92
+
93
+ command: Callable[[Dialog], None] | None = None
94
+ """Callback invoked when clicked."""
95
+
96
+ accent: AccentToken | str | None = None
97
+ """Accent token for styling (e.g. `'primary'`, `'danger'`)."""
98
+
99
+ variant: ButtonVariant | str | None = None
100
+ """Button style variant — `'default'`, `'solid'`, `'outline'`, or `'ghost'`."""
101
+
102
+ icon: str | dict[str, Any] | None = None # passed straight to _Button(icon=...)
103
+ """Optional icon specification for the button."""
104
+
105
+
106
+ ButtonSpec = Union[DialogButton, Mapping[str, Any]]
107
+
108
+
109
+ class ShowOptions(TypedDict, total=False):
110
+ """Options for showing the dialog window.
111
+
112
+ Attributes:
113
+ position (tuple[int, int] | None): Optional (x, y) coordinates.
114
+ modal (bool | None): Override the mode's default modality.
115
+ anchor_to (PublicWidgetBase | str | None): Positioning target widget or string.
116
+ anchor_point (AnchorPoint): Point on the anchor target.
117
+ window_point (AnchorPoint): Point on the dialog window.
118
+ offset (tuple[int, int]): Additional (x, y) offset in pixels.
119
+ auto_flip (bool | str): Smart positioning to keep window on screen.
120
+ """
121
+ position: Optional[Tuple[int, int]]
122
+ modal: Optional[bool]
123
+ anchor_to: Optional[AnchorTarget]
124
+ anchor_point: AnchorPoint
125
+ window_point: AnchorPoint
126
+ offset: Tuple[int, int]
127
+ auto_flip: Union[bool, Literal['vertical', 'horizontal']]
128
+
129
+
130
+ # --- Dialog ----------------------------------------------------------------
131
+
132
+ class Dialog:
133
+ """A flexible dialog window using the builder pattern.
134
+
135
+ Dialog provides a composition-based approach to creating modal and non-modal
136
+ dialogs with customizable content, buttons, and behavior. Instead of requiring
137
+ inheritance, you provide callback functions to build the dialog content and
138
+ optionally the footer.
139
+
140
+ The dialog manages window creation, positioning, button handling, and keyboard
141
+ shortcuts automatically.
142
+
143
+ Attributes:
144
+ result: The value returned by the dialog after closing.
145
+ Set automatically when a button with a result value is clicked.
146
+ Defaults to None.
147
+
148
+ Args:
149
+ title: Dialog window title. Defaults to `'bootstack'`.
150
+ content_builder: Callback to build the dialog body. The content area is a
151
+ public container set as the active parent, so the callback creates
152
+ widgets directly — no `parent=` needed, like an `App` body. Declare a
153
+ single parameter (`def build(content): ...`) to also receive the
154
+ content container. If `None`, the dialog has no body area.
155
+ footer_builder: Callback to build a fully custom footer. Replaces the
156
+ standard button row when provided.
157
+ buttons: List of `DialogButton` (or equivalent dicts) for the footer.
158
+ Ignored when `footer_builder` is given. Buttons are displayed
159
+ right-to-left — the first entry appears rightmost.
160
+ min_size: Minimum window size as `(width, height)` in pixels.
161
+ max_size: Maximum window size as `(width, height)` in pixels.
162
+ resizable: `(width, height)` booleans controlling resize. Default
163
+ `(False, False)`.
164
+ alert: Play the system alert sound when the dialog is shown. Default
165
+ `False`.
166
+ mode: Interaction mode. `'modal'` (default) blocks the parent;
167
+ `'popover'` closes on focus loss; `'sheet'` renders as a Cocoa
168
+ sheet on macOS and falls back to modal elsewhere.
169
+ undecorated: Remove OS window decorations. Useful for popover-style
170
+ dialogs. Default `False`.
171
+ window_style: Windows-only pywinstyles effect (`'mica'`, `'acrylic'`,
172
+ `'aero'`, etc.). Defaults to the app's window style setting.
173
+ on_close: Callback fired when the dialog is closed by any means (button
174
+ click, X button, Escape, or focus loss in popover mode). Receives
175
+ no arguments. Useful for non-modal dialogs where `show()` does
176
+ not block.
177
+ padding: Space in pixels between the dialog body edge and its content.
178
+ Defaults to `20`.
179
+ gap: Space in pixels between stacked content widgets. Defaults to `8`.
180
+ parent: Parent window. Defaults to the active root window.
181
+ """
182
+
183
+ def __init__(
184
+ self,
185
+ *,
186
+ title: str = "bootstack",
187
+ content_builder: Optional[ContentBuilder] = None,
188
+ footer_builder: Optional[FooterBuilder] = None,
189
+ buttons: Iterable[ButtonSpec] | None = None,
190
+ min_size: tuple[int, int] | None = None,
191
+ max_size: tuple[int, int] | None = None,
192
+ resizable: tuple[bool, bool] | None = (False, False),
193
+ alert: bool = False,
194
+ mode: DialogMode = "modal",
195
+ undecorated: bool = False,
196
+ window_style: WindowStyle | str | None = None,
197
+ on_close: Callable[[], Any] | None = None,
198
+ surface: SurfaceToken | str | None = None,
199
+ padding: Padding = 20,
200
+ gap: int = 8,
201
+ parent: Master = None,
202
+ _raw_content: bool = False,
203
+ ):
204
+ import tkinter
205
+ self._master = parent if parent else tkinter._default_root
206
+ self._title = title
207
+ self._content_builder = content_builder
208
+ self._footer_builder = footer_builder
209
+ self._buttons: list[DialogButton] = self._normalize_buttons(buttons)
210
+
211
+ self._minsize = min_size
212
+ self._maxsize = max_size
213
+ self._resizable = resizable
214
+ self._alert = alert
215
+ self._mode = mode
216
+ self._undecorated = undecorated
217
+ self._window_style = window_style
218
+ self._on_close = on_close
219
+ self._surface = surface
220
+ self._content_padding = padding
221
+ self._content_gap = gap
222
+ # Internal builders (the built-in verbs, FormDialog, …) render with raw
223
+ # primitives and want the bare content frame; public content_builders get
224
+ # the parent-free public container.
225
+ self._raw_content = _raw_content
226
+
227
+ self._toplevel: Toplevel | None = None
228
+ self._content: _Frame | None = None
229
+ self._content_area: Any = None
230
+ self._footer: _Frame | None = None
231
+ self._border_frame: _Frame | None = None
232
+
233
+ self.result: Any = None
234
+
235
+ # --------------------------------------------------------------- API
236
+
237
+ def show(
238
+ self,
239
+ *,
240
+ position: Optional[Tuple[int, int]] = None,
241
+ modal: Optional[bool] = None,
242
+ anchor_to: Optional[AnchorTarget] = None,
243
+ anchor_point: AnchorPoint = 'center',
244
+ window_point: AnchorPoint = 'center',
245
+ offset: Tuple[int, int] = (0, 0),
246
+ auto_flip: Union[bool, Literal['vertical', 'horizontal']] = False
247
+ ):
248
+ """Create and show the dialog with flexible positioning options.
249
+
250
+ Args:
251
+ position: Optional (x, y) coordinates to position the dialog.
252
+ If provided, takes precedence over anchor-based positioning.
253
+ modal: Override the mode's default modality. When `None`, follows the
254
+ mode — `"modal"` grabs focus and waits for the dialog to close;
255
+ `"popover"` waits without grabbing.
256
+ anchor_to: Positioning target. A widget (e.g. a `bs.Button`) anchors to
257
+ that widget; `"screen"` anchors to the screen edges/corners;
258
+ `"cursor"` anchors to the mouse cursor; `"parent"` anchors to the
259
+ parent window; and `None` (default) centers on the parent.
260
+ anchor_point: Point on the anchor target (n, s, e, w, ne, nw, se, sw, center).
261
+ Default 'center'.
262
+ window_point: Point on the dialog window (n, s, e, w, ne, nw, se, sw, center).
263
+ Default 'center'.
264
+ offset: Additional (x, y) offset in pixels from the anchor position.
265
+ auto_flip: Smart positioning to keep window on screen.
266
+ - False: No flipping (default)
267
+ - True: Flip both vertically and horizontally as needed
268
+ - 'vertical': Only flip up/down
269
+ - 'horizontal': Only flip left/right
270
+
271
+ Positioning Logic:
272
+ 1. If position is provided: Use explicit coordinates
273
+ 2. If anchor_to is provided: Use anchor-based positioning
274
+ 3. Default: Center on parent window
275
+ """
276
+ if modal is None:
277
+ modal = self._mode in ("modal", "sheet")
278
+
279
+ self.result = None
280
+ self._create_toplevel(modal=modal)
281
+ self._build_footer()
282
+ self._build_content()
283
+ self._position_dialog(
284
+ position=position,
285
+ anchor_to=_resolve_anchor_target(anchor_to),
286
+ anchor_point=anchor_point,
287
+ window_point=window_point,
288
+ offset=offset,
289
+ auto_flip=auto_flip
290
+ )
291
+
292
+ if self._alert:
293
+ self._toplevel.bell()
294
+
295
+ if self._mode == "popover":
296
+ self._toplevel.bind("<FocusOut>", self._on_focus_out, add="+")
297
+
298
+ if modal:
299
+ # Sheets are inherently modal to their parent on Aqua via the
300
+ # sheet window class; calling grab_set on top of that is fine
301
+ # but unnecessary. Plain modal mode still uses grab to block
302
+ # interaction with the parent on platforms without a sheet.
303
+ if self._mode in ("modal", "sheet"):
304
+ self._toplevel.grab_set()
305
+ self._master.wait_window(self._toplevel)
306
+
307
+ @property
308
+ def toplevel(self) -> Toplevel | None:
309
+ """Read-only access to the underlying toplevel window."""
310
+ return self._toplevel
311
+
312
+ # --------------------------------------------------------------- Internals
313
+
314
+ def _normalize_buttons(
315
+ self,
316
+ buttons: Iterable[ButtonSpec] | None,
317
+ ) -> list[DialogButton]:
318
+ if not buttons:
319
+ return []
320
+
321
+ normalized: list[DialogButton] = []
322
+ for b in buttons:
323
+ if isinstance(b, DialogButton):
324
+ normalized.append(b)
325
+ else:
326
+ # assume mapping/dict
327
+ try:
328
+ normalized.append(DialogButton(**b)) # type: ignore[arg-type]
329
+ except TypeError as exc:
330
+ raise ValueError(
331
+ f"Invalid button mapping {b!r}: {exc}"
332
+ ) from exc
333
+ return normalized
334
+
335
+ def _create_toplevel(self, modal: bool = True):
336
+ # Pass transient to Toplevel so it's set before window_style is applied
337
+ # (required for mica effect to work on Windows)
338
+ self._toplevel = Toplevel(
339
+ master=self._master,
340
+ window_style=self._window_style,
341
+ transient=self._master if modal else None
342
+ )
343
+ self._toplevel.title(self._title)
344
+ self._toplevel.protocol("WM_DELETE_WINDOW", self._on_close_request)
345
+ if self._on_close:
346
+ self._toplevel.bind("<Destroy>", self._on_toplevel_destroy)
347
+
348
+ try:
349
+ self._toplevel.withdraw()
350
+ except Exception:
351
+ pass
352
+
353
+ # Sheet mode: on Aqua, apply the Cocoa 'sheet' window class so the
354
+ # dialog renders chromeless and tied to its parent. Must be set
355
+ # before the window is mapped, hence here while still withdrawn.
356
+ # On non-Aqua, sheet mode is treated as plain modal — there's no
357
+ # cross-platform equivalent of a Cocoa sheet.
358
+ if self._mode == "sheet" and getattr(self._toplevel, 'winsys', None) == 'aqua':
359
+ try:
360
+ self._toplevel.tk.call(
361
+ '::tk::unsupported::MacWindowStyle', 'style',
362
+ self._toplevel, 'sheet', 'none',
363
+ )
364
+ except tkinter.TclError:
365
+ pass
366
+
367
+ if self._minsize:
368
+ self._toplevel.minsize(*self._minsize)
369
+ if self._maxsize:
370
+ self._toplevel.maxsize(*self._maxsize)
371
+ if self._resizable is not None:
372
+ self._toplevel.resizable(*self._resizable)
373
+
374
+ if self._undecorated:
375
+ self._toplevel.overrideredirect(True)
376
+ self._border_frame = _Frame(self._toplevel, show_border=True, padding=2)
377
+ self._border_frame.pack(fill='both', expand=True)
378
+
379
+ def _build_content(self):
380
+ parent = self._border_frame if self._undecorated else self._toplevel
381
+ padding = 2 if self._undecorated else 0
382
+ kw = {"padding": padding}
383
+ if self._surface:
384
+ kw["surface"] = self._surface
385
+ self._content = _Frame(parent, **kw)
386
+
387
+ if self._undecorated:
388
+ self._content.pack(fill="both", side="top", expand=False)
389
+ else:
390
+ self._content.pack(fill="both", side="top", expand=True)
391
+
392
+ if self._content_builder:
393
+ if self._raw_content:
394
+ # Internal builders render with raw primitives into the frame.
395
+ self._content_builder(self._content)
396
+ return
397
+
398
+ from bootstack.widgets.stacks import Column
399
+
400
+ # Public content area the builder fills — set as the active parent so
401
+ # the body needs no `parent=`, like an App body. It is hosted in the
402
+ # content frame via the raw-Tk-parent bridge in `_resolve_parent`.
403
+ self._content_area = Column(
404
+ parent=self._content,
405
+ padding=self._content_padding,
406
+ gap=self._content_gap,
407
+ )
408
+ with self._content_area:
409
+ _call_builder(self._content_builder, self._content_area)
410
+
411
+ def _build_footer(self):
412
+ parent = self._border_frame if self._undecorated else self._toplevel
413
+ footer_padding = 6 if self._undecorated else 4
414
+
415
+ footer_kw = {"padding": footer_padding}
416
+ if self._surface:
417
+ footer_kw["surface"] = self._surface
418
+
419
+ if self._footer_builder:
420
+ self._footer = _Frame(parent, **footer_kw)
421
+ self._footer.pack(side="bottom", fill="x")
422
+ _Separator(parent, orient="horizontal").pack(side="bottom", fill="x")
423
+ self._footer_builder(self._footer)
424
+ return
425
+
426
+ if not self._buttons:
427
+ return
428
+
429
+ self._footer = _Frame(parent, **footer_kw)
430
+ self._footer.pack(side="bottom", fill="x")
431
+ _Separator(parent, orient="horizontal").pack(side="bottom", fill="x")
432
+
433
+ self._create_standard_buttons(self._footer)
434
+
435
+ def _create_standard_buttons(self, parent: Widget):
436
+ """Create standardized footer buttons from self._buttons.
437
+
438
+ Buttons are packed right-to-left so first button appears rightmost.
439
+ """
440
+ default_button: _Button | None = None
441
+ cancel_button: _Button | None = None
442
+
443
+ for spec in reversed(self._buttons):
444
+ # Get accent/variant from spec or derive from role
445
+ if spec.accent or spec.variant:
446
+ accent, variant = spec.accent, spec.variant
447
+ else:
448
+ accent, variant = self._style_for_role(spec.role)
449
+
450
+ def make_command(s: DialogButton):
451
+ def cmd():
452
+ if s.command:
453
+ s.command(self)
454
+ if s.result is not None:
455
+ self.result = s.result
456
+ if s.closes and self._toplevel:
457
+ self._toplevel.destroy()
458
+
459
+ return cmd
460
+
461
+ btn = _Button(
462
+ parent,
463
+ text=spec.text,
464
+ accent=accent,
465
+ variant=variant,
466
+ command=make_command(spec),
467
+ icon=spec.icon,
468
+ compound="left" if spec.icon else "text",
469
+ )
470
+ btn.pack(side="right")
471
+
472
+ if spec.default and default_button is None:
473
+ default_button = btn
474
+ if spec.role == "cancel" and cancel_button is None:
475
+ cancel_button = btn
476
+
477
+ if self._toplevel is None:
478
+ return
479
+
480
+ if default_button is not None:
481
+ default_button.focus_set()
482
+ self._toplevel.bind("<Return>", lambda e, b=default_button: b.invoke())
483
+
484
+ if cancel_button is not None:
485
+ self._toplevel.bind("<Escape>", lambda e, b=cancel_button: b.invoke())
486
+ else:
487
+ self._toplevel.bind("<Escape>", lambda e: self._toplevel.destroy())
488
+
489
+ def _position_dialog(
490
+ self,
491
+ position: Optional[Tuple[int, int]] = None,
492
+ anchor_to: Optional[Union[Widget, Literal["screen", "cursor", "parent"]]] = None,
493
+ anchor_point: AnchorPoint = 'center',
494
+ window_point: AnchorPoint = 'center',
495
+ offset: Tuple[int, int] = (0, 0),
496
+ auto_flip: Union[bool, Literal['vertical', 'horizontal']] = False
497
+ ) -> None:
498
+ """Position the dialog window using consolidated positioning logic.
499
+
500
+ Positioning logic:
501
+ 1. If position is provided: Use explicit coordinates
502
+ 2. If anchor_to is provided: Use anchor-based positioning
503
+ 3. Default: Center on parent
504
+ """
505
+ if not self._toplevel:
506
+ return
507
+
508
+ # Priority 1: Explicit position coordinates
509
+ if position is not None:
510
+ x, y = position
511
+ x, y = WindowPositioning.ensure_on_screen(self._toplevel, int(x), int(y))
512
+ self._toplevel.geometry(f"+{x}+{y}")
513
+
514
+ # Priority 2: Anchor-based positioning
515
+ elif anchor_to is not None:
516
+ WindowPositioning.position_anchored(
517
+ window=self._toplevel,
518
+ anchor_to=anchor_to,
519
+ parent=self._master,
520
+ anchor_point=anchor_point,
521
+ window_point=window_point,
522
+ offset=offset,
523
+ auto_flip=auto_flip,
524
+ ensure_visible=True
525
+ )
526
+
527
+ # Priority 3: Default - center on parent
528
+ else:
529
+ WindowPositioning.position_window(
530
+ window=self._toplevel,
531
+ position=None,
532
+ parent=self._master,
533
+ center_on_parent=True,
534
+ ensure_visible=True
535
+ )
536
+
537
+ # Apply window style while still withdrawn, right before showing.
538
+ # The update() call is here so pywinstyles can attach to a fully
539
+ # realized HWND on Windows; on Aqua (and X11) it serves no purpose
540
+ # and can hang indefinitely flushing children's pending events
541
+ # (e.g. FontDialog's Treeview with hundreds of tag-configure font
542
+ # calls), so gate it on the platform that actually needs it.
543
+ self._toplevel.update_idletasks()
544
+ self._toplevel._apply_window_style()
545
+ if getattr(self._toplevel, 'winsys', None) == 'win32':
546
+ self._toplevel.update()
547
+ self._toplevel.deiconify()
548
+
549
+ # Second centering pass for default positioning (handles dynamic sizing)
550
+ if position is None and anchor_to is None:
551
+ try:
552
+ x, y = WindowPositioning.center_on_parent(self._toplevel, self._master)
553
+ x, y = WindowPositioning.ensure_on_screen(self._toplevel, x, y)
554
+ self._toplevel.geometry(f"+{x}+{y}")
555
+ except Exception:
556
+ pass
557
+
558
+ # --------------------------------------------------------------- Event Handlers
559
+
560
+ def _on_toplevel_destroy(self, event) -> None:
561
+ if event.widget is self._toplevel and self._on_close:
562
+ self._on_close()
563
+
564
+ def _on_focus_out(self, _event):
565
+ """For popover mode: close when focus leaves the dialog."""
566
+ if self._mode != "popover" or not self._toplevel:
567
+ return
568
+
569
+ new_focus = self._toplevel.focus_get()
570
+
571
+ if new_focus is None:
572
+ self._toplevel.destroy()
573
+ return
574
+
575
+ if not str(new_focus).startswith(str(self._toplevel)):
576
+ self._toplevel.destroy()
577
+
578
+ def _on_close_request(self):
579
+ if self._toplevel:
580
+ self._toplevel.destroy()
581
+
582
+ # --------------------------------------------------------------- Helpers
583
+
584
+ def _style_for_role(self, role: ButtonRole) -> tuple[str | None, str | None]:
585
+ """Return (accent, variant) tuple for a button role."""
586
+ if role == "primary":
587
+ return ("primary", None)
588
+ if role == "secondary":
589
+ return ("default", None)
590
+ if role == "danger":
591
+ return ("danger", None)
592
+ if role == "cancel":
593
+ return ("default", "outline")
594
+ return ("default", None)