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,523 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import platform
5
+ import re
6
+ from dataclasses import dataclass
7
+ from tkinter import Misc, font as tkfont
8
+ from tkinter.font import Font as TkFont
9
+ from typing import Any, Literal, NamedTuple
10
+
11
+
12
+ # =============================================================================
13
+ # Token model
14
+ # =============================================================================
15
+
16
+ class FontSpec(NamedTuple):
17
+ font: str
18
+ size: int
19
+ weight: Literal["normal", "bold"]
20
+ underline: bool = False
21
+ slant: Literal["roman", "italic"] = "roman"
22
+ overstrike: bool = False
23
+
24
+
25
+ class FontTokenNames:
26
+ # Base families
27
+ caption = "caption"
28
+ label = "label"
29
+ body_sm = "body-sm"
30
+ body = "body"
31
+ body_lg = "body-lg"
32
+ body_xl = "body-xl"
33
+
34
+ heading_sm = "heading-sm"
35
+ heading_md = "heading-md"
36
+ heading_lg = "heading-lg"
37
+ heading_xl = "heading-xl"
38
+
39
+ display_lg = "display-lg"
40
+ display_xl = "display-xl"
41
+
42
+ code = "code"
43
+ hyperlink = "hyperlink"
44
+
45
+
46
+ class FontTokens(NamedTuple):
47
+ caption: FontSpec
48
+ label: FontSpec
49
+ body_sm: FontSpec
50
+ body: FontSpec
51
+ body_lg: FontSpec
52
+ body_xl: FontSpec
53
+
54
+ heading_sm: FontSpec
55
+ heading_md: FontSpec
56
+ heading_lg: FontSpec
57
+ heading_xl: FontSpec
58
+
59
+ display_lg: FontSpec
60
+ display_xl: FontSpec
61
+
62
+ code: FontSpec
63
+ hyperlink: FontSpec
64
+
65
+
66
+ # =============================================================================
67
+ # Defaults
68
+ # =============================================================================
69
+
70
+ def _clamp_base_size(base_size: int) -> int:
71
+ # keep sizes sane; your existing file had a similar constraint
72
+ return max(8, min(int(base_size), 14))
73
+
74
+
75
+ def build_desktop_tokens(
76
+ *,
77
+ family: str | None = None,
78
+ mono_family: str | None = None,
79
+ base_size: int = 11,
80
+ ) -> FontTokens:
81
+ """
82
+ Construct default typography tokens tuned for desktop UI.
83
+ """
84
+ base = _clamp_base_size(base_size)
85
+
86
+ system = platform.system().lower()
87
+ if system == "windows":
88
+ default_ui = "Segoe UI"
89
+ default_mono = "Cascadia Mono"
90
+ elif system == "darwin":
91
+ default_ui = "SF Pro Text"
92
+ default_mono = "Menlo"
93
+ # Tk's pt-to-pixel conversion uses the active scaling factor.
94
+ # macOS defaults to 1.0 (72 DPI baseline) vs ~1.334 on Win/Linux,
95
+ # so the same point size renders ~25% smaller on Mac. Bump the
96
+ # base to keep visual parity across platforms and align with the
97
+ # macOS HIG default control text size (13pt).
98
+ base = _clamp_base_size(base + 2)
99
+ else:
100
+ default_ui = "DejaVu Sans"
101
+ default_mono = "DejaVu Sans Mono"
102
+
103
+ ui = family or default_ui
104
+ mono = mono_family or default_mono
105
+
106
+ # Scale helpers
107
+ def s(delta: int) -> int:
108
+ return base + delta
109
+
110
+ return FontTokens(
111
+ caption=FontSpec(ui, s(-2), "normal"),
112
+ label=FontSpec(ui, s(-2), "bold"),
113
+ body_sm=FontSpec(ui, s(-1), "normal"),
114
+ body=FontSpec(ui, s(0), "normal"),
115
+ body_lg=FontSpec(ui, s(1), "normal"),
116
+ body_xl=FontSpec(ui, s(2), "normal"),
117
+
118
+ heading_sm=FontSpec(ui, s(1), "bold"), # 12pt — just above body
119
+ heading_md=FontSpec(ui, s(2), "bold"), # 13pt
120
+ heading_lg=FontSpec(ui, s(4), "bold"), # 15pt
121
+ heading_xl=FontSpec(ui, s(7), "bold"), # 18pt
122
+
123
+ display_lg=FontSpec(ui, s(11), "bold"), # 22pt
124
+ display_xl=FontSpec(ui, s(17), "bold"), # 28pt
125
+
126
+ code=FontSpec(mono, s(0), "normal"),
127
+ hyperlink=FontSpec(ui, s(0), "normal", underline=True),
128
+ )
129
+
130
+
131
+ DEFAULT_FONT_TOKENS = build_desktop_tokens()
132
+
133
+ FALLBACK_FONT = "TkDefaultFont"
134
+ FALLBACK_MONO = "TkFixedFont"
135
+
136
+
137
+ TK_FONT_OVERRIDES = {
138
+ # Make Tk defaults align with desktop expectations / your token system
139
+ "TkDefaultFont": FontTokenNames.body,
140
+ "TkTextFont": FontTokenNames.body,
141
+ "TkHeadingFont": FontTokenNames.heading_md,
142
+ "TkCaptionFont": FontTokenNames.caption,
143
+ "TkFixedFont": FontTokenNames.code,
144
+ }
145
+
146
+
147
+ # =============================================================================
148
+ # Typography registry (named fonts)
149
+ # =============================================================================
150
+
151
+ class Typography:
152
+ """
153
+ Named-font registry for tokenized typography.
154
+
155
+ - Call Typography.init(root) once.
156
+ - Token fonts become named Tk fonts (e.g. "body", "heading-lg"...)
157
+ - Tk defaults are overridden (TkDefaultFont, etc.) to match tokens.
158
+ """
159
+
160
+ _root: Misc | None = None
161
+ _fonts: FontTokens = DEFAULT_FONT_TOKENS
162
+ _named_fonts: dict[str, TkFont] = {}
163
+
164
+ @classmethod
165
+ def initialize(cls, root: Misc | None = None) -> None:
166
+ if root is not None:
167
+ cls._root = root
168
+ cls._ensure_named_token_fonts()
169
+ cls._override_tk_fonts()
170
+
171
+ @classmethod
172
+ def reset(cls) -> None:
173
+ """Forget the Tk-bound named-font cache and root reference.
174
+
175
+ The cached named fonts (`body`, `heading-lg`, ...) and the derived
176
+ fonts (`body[bold]`, ...) are `TkFont`s bound to the root that created
177
+ them; once that root is destroyed they are stale, and their finalizers
178
+ would later run against a foreign interpreter. Clearing them lets the
179
+ next `initialize()` rebuild against a fresh root. Called from
180
+ App.destroy so a subsequent App starts clean.
181
+ """
182
+ cls._root = None
183
+ cls._named_fonts = {}
184
+ _DERIVED_FONTS.clear()
185
+
186
+ @classmethod
187
+ def use_fonts(cls, fonts: FontTokens) -> None:
188
+ cls._fonts = fonts
189
+ cls._ensure_named_token_fonts()
190
+ cls._override_tk_fonts()
191
+
192
+ @classmethod
193
+ def set_global_family(cls, family: str, *, mono_family: str | None = None) -> None:
194
+ """
195
+ Update all font tokens to use a single family.
196
+
197
+ The monospace `code` token keeps its own family unless `mono_family`
198
+ is given.
199
+ """
200
+ updated = {}
201
+ for name, spec in cls._fonts._asdict().items():
202
+ if name == "code":
203
+ updated[name] = spec._replace(font=mono_family) if mono_family else spec
204
+ else:
205
+ updated[name] = spec._replace(font=family)
206
+ cls.use_fonts(FontTokens(**updated))
207
+
208
+ @classmethod
209
+ def get_token(cls, name: str) -> FontSpec:
210
+ """
211
+ Return the FontSpec for a token name.
212
+ """
213
+ # allow passing "body-sm" etc.
214
+ # map token string to field name
215
+ field = name.replace("-", "_")
216
+ if hasattr(cls._fonts, field):
217
+ return getattr(cls._fonts, field)
218
+ raise KeyError(f"Unknown font token: {name}")
219
+
220
+ @classmethod
221
+ def token_names(cls) -> set[str]:
222
+ return {
223
+ v for k, v in FontTokenNames.__dict__.items()
224
+ if not k.startswith("_") and isinstance(v, str)
225
+ }
226
+
227
+ @classmethod
228
+ def get_named_token_font(cls, token_name: str) -> TkFont:
229
+ """
230
+ Return the named Tk font for a token.
231
+ """
232
+ if token_name not in cls._named_fonts:
233
+ cls._ensure_named_token_fonts()
234
+ # If it's still missing, try to resolve as Tk named font directly:
235
+ return cls._named_fonts.get(token_name) or TkFont(name=token_name, exists=True)
236
+
237
+ @classmethod
238
+ def update_font_token(cls, name: str, **kwargs: Any) -> None:
239
+ """
240
+ Replace the given token with updated fields (NamedTuple-safe).
241
+ """
242
+ field = name.replace("-", "_")
243
+ if not hasattr(cls._fonts, field):
244
+ raise KeyError(f"Unknown font token: {name}")
245
+
246
+ old: FontSpec = getattr(cls._fonts, field)
247
+ new = old._replace(**kwargs)
248
+ # rebuild the FontTokens instance
249
+ data = cls._fonts._asdict()
250
+ data[field] = new
251
+ cls.use_fonts(FontTokens(**data))
252
+
253
+ @classmethod
254
+ def _ensure_named_token_fonts(cls) -> None:
255
+ """
256
+ Create/update named Tk fonts for every token.
257
+ """
258
+ for token_name, spec in cls._iter_tokens():
259
+ family = spec.font
260
+
261
+ if token_name in tkfont.names():
262
+ f = tkfont.nametofont(token_name)
263
+ f.configure(
264
+ family=family,
265
+ size=spec.size,
266
+ weight=spec.weight,
267
+ slant=spec.slant,
268
+ underline=bool(spec.underline),
269
+ overstrike=bool(spec.overstrike),
270
+ )
271
+ else:
272
+ f = TkFont(
273
+ name=token_name,
274
+ family=family,
275
+ size=spec.size,
276
+ weight=spec.weight,
277
+ slant=spec.slant,
278
+ underline=bool(spec.underline),
279
+ overstrike=bool(spec.overstrike),
280
+ )
281
+ cls._named_fonts[token_name] = f
282
+
283
+ @classmethod
284
+ def _override_tk_fonts(cls) -> None:
285
+ """
286
+ Override Tk’s default named fonts to align with tokens.
287
+ """
288
+ for tk_name, token_name in TK_FONT_OVERRIDES.items():
289
+ spec = cls.get_token(token_name)
290
+ family = spec.font
291
+
292
+ if tk_name in tkfont.names():
293
+ tkfont.nametofont(tk_name).configure(
294
+ family=family,
295
+ size=spec.size,
296
+ weight=spec.weight,
297
+ slant=spec.slant,
298
+ )
299
+
300
+ @classmethod
301
+ def _iter_tokens(cls):
302
+ d = cls._fonts._asdict()
303
+ for field_name, spec in d.items():
304
+ token_name = field_name.replace("_", "-")
305
+ yield token_name, spec
306
+
307
+
308
+ # =============================================================================
309
+ # Public Font abstraction (token + modifiers -> named Tk font)
310
+ # =============================================================================
311
+
312
+ _BRACKET_RE = re.compile(r"\[([^]]+)]")
313
+
314
+
315
+ @dataclass(frozen=True)
316
+ class FontModifierSpec:
317
+ token: str
318
+ size: int | None = None # absolute override
319
+ size_delta: int = 0 # "+1"/"-1" after token
320
+ weight: str | None = None # "bold"|"normal"
321
+ slant: str | None = None # "italic"|"roman"
322
+ underline: bool | None = None
323
+ overstrike: bool | None = None
324
+
325
+ def key(self) -> tuple:
326
+ return (
327
+ self.token,
328
+ self.size,
329
+ self.size_delta,
330
+ self.weight,
331
+ self.slant,
332
+ self.underline,
333
+ self.overstrike,
334
+ )
335
+
336
+
337
+ _DERIVED_FONTS: dict[tuple, TkFont] = {}
338
+
339
+
340
+ def _hash(parts: Any) -> str:
341
+ return hashlib.md5(repr(parts).encode("utf-8")).hexdigest()[:10]
342
+
343
+
344
+ def parse_font(value: str, *, default_token: str = FontTokenNames.body) -> FontModifierSpec:
345
+ """
346
+ Parse:
347
+ "body[bold]"
348
+ "heading-lg[+1][italic][underline]"
349
+ "[16][bold]" -> default token + size/mods
350
+
351
+ Brackets can contain:
352
+ - size delta: +1, -2 (relative to token base size)
353
+ - absolute size: 16, 16px
354
+ - modifiers: bold, normal, italic, roman, underline, overstrike
355
+ - comma/space separated lists: [bold, italic]
356
+ """
357
+ s = (value or "").strip()
358
+ if not s:
359
+ return FontModifierSpec(default_token)
360
+
361
+ head = s.split("[", 1)[0].strip()
362
+ mods = _BRACKET_RE.findall(s)
363
+
364
+ token = head if head else default_token
365
+
366
+ size: int | None = None
367
+ size_delta = 0
368
+ weight: str | None = None
369
+ slant: str | None = None
370
+ underline: bool | None = None
371
+ overstrike: bool | None = None
372
+
373
+ for raw in mods:
374
+ part = raw.strip()
375
+ if not part:
376
+ continue
377
+
378
+ bits = [b.strip().lower() for b in re.split(r"[,\s]+", part) if b.strip()]
379
+ for b in bits:
380
+ if re.match(r"^[+-]\d+$", b):
381
+ size_delta = int(b)
382
+ elif b.endswith("px") and b[:-2].isdigit():
383
+ size = -int(b[:-2])
384
+ elif b.isdigit():
385
+ size = int(b)
386
+ elif b in ("bold", "normal"):
387
+ weight = b
388
+ elif b in ("italic", "roman"):
389
+ slant = b
390
+ elif b == "underline":
391
+ underline = True
392
+ elif b in ("no-underline", "nounderline"):
393
+ underline = False
394
+ elif b in ("overstrike", "strike", "strikethrough"):
395
+ overstrike = True
396
+ elif b in ("no-overstrike", "no-strike", "nostrike"):
397
+ overstrike = False
398
+
399
+ return FontModifierSpec(
400
+ token=token,
401
+ size=size,
402
+ size_delta=size_delta,
403
+ weight=weight,
404
+ slant=slant,
405
+ underline=underline,
406
+ overstrike=overstrike,
407
+ )
408
+
409
+
410
+ def resolve_modifier_font(spec: FontModifierSpec) -> TkFont:
411
+ """
412
+ Resolve a modifier spec to a named, cached TkFont.
413
+ """
414
+ key = spec.key()
415
+ cached = _DERIVED_FONTS.get(key)
416
+ if cached is not None:
417
+ return cached
418
+
419
+ # If token exists in our system, prefer it; otherwise treat as Tk named font
420
+ try:
421
+ base = Typography.get_named_token_font(spec.token)
422
+ except Exception:
423
+ base = TkFont(name=spec.token, exists=True)
424
+
425
+ actual = base.actual()
426
+
427
+ cfg: dict[str, Any] = {
428
+ "family": actual.get("family"),
429
+ "size": actual.get("size"),
430
+ "weight": actual.get("weight", "normal"),
431
+ "slant": actual.get("slant", "roman"),
432
+ "underline": int(actual.get("underline", 0) or 0),
433
+ "overstrike": int(actual.get("overstrike", 0) or 0),
434
+ }
435
+
436
+ if spec.size is not None:
437
+ cfg["size"] = spec.size
438
+ if spec.size_delta:
439
+ cfg["size"] = int(cfg.get("size") or 0) + spec.size_delta
440
+
441
+ if spec.weight is not None:
442
+ cfg["weight"] = spec.weight
443
+ if spec.slant is not None:
444
+ cfg["slant"] = spec.slant
445
+ if spec.underline is not None:
446
+ cfg["underline"] = 1 if spec.underline else 0
447
+ if spec.overstrike is not None:
448
+ cfg["overstrike"] = 1 if spec.overstrike else 0
449
+
450
+ # If nothing changed, return base
451
+ if (
452
+ spec.size is None
453
+ and spec.size_delta == 0
454
+ and spec.weight is None
455
+ and spec.slant is None
456
+ and spec.underline is None
457
+ and spec.overstrike is None
458
+ ):
459
+ _DERIVED_FONTS[key] = base
460
+ return base
461
+
462
+ name = f"bootstack.font.{_hash(key)}"
463
+ try:
464
+ f = TkFont(name=name, exists=True)
465
+ f.configure(**cfg)
466
+ except Exception:
467
+ f = TkFont(name=name, **cfg)
468
+
469
+ _DERIVED_FONTS[key] = f
470
+ return f
471
+
472
+
473
+ class Font:
474
+ """
475
+ Resolves a font token string to a live Tk font object.
476
+
477
+ Accepts the same token syntax as the `font=` argument on any widget:
478
+ Font("body[bold]")
479
+ Font("heading-lg[+1][italic]")
480
+ Font("[16][bold]") # default token + size/mods
481
+
482
+ Use it for text measurement or to pass a resolved font to a raw Tk widget.
483
+
484
+ Args:
485
+ value: Font token string (e.g. `'body[bold]'`, `'heading-md[italic]'`).
486
+ default_token: Token to use when `value` has no token prefix. Defaults to `'body'`.
487
+ """
488
+
489
+ __slots__ = ("spec", "_tkfont")
490
+
491
+ def __init__(self, value: str, *, default_token: str = FontTokenNames.body):
492
+ self.spec = parse_font(value, default_token=default_token)
493
+ self._tkfont: TkFont | None = None
494
+
495
+ @property
496
+ def tkfont(self) -> TkFont:
497
+ if self._tkfont is None:
498
+ self._tkfont = resolve_modifier_font(self.spec)
499
+ return self._tkfont
500
+
501
+ @property
502
+ def name(self) -> str:
503
+ return str(self.tkfont)
504
+
505
+ def measure(self, text: str) -> int:
506
+ return int(self.tkfont.measure(text))
507
+
508
+ def metrics(self, *args, **kwargs):
509
+ return self.tkfont.metrics(*args, **kwargs)
510
+
511
+ def actual(self, *args, **kwargs):
512
+ return self.tkfont.actual(*args, **kwargs)
513
+
514
+ def __str__(self) -> str:
515
+ return self.name
516
+
517
+
518
+ def get_font(value: str, *, default_token: str = FontTokenNames.body) -> Font:
519
+ """
520
+ Convenience factory:
521
+ get_font("body[bold]")
522
+ """
523
+ return Font(value, default_token=default_token)