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.
- bootstack/__init__.py +157 -0
- bootstack/__main__.py +5 -0
- bootstack/_core/__init__.py +21 -0
- bootstack/_core/capabilities/__init__.py +45 -0
- bootstack/_core/capabilities/after.py +103 -0
- bootstack/_core/capabilities/bind.py +192 -0
- bootstack/_core/capabilities/bindtags.py +112 -0
- bootstack/_core/capabilities/busy.py +72 -0
- bootstack/_core/capabilities/clipboard.py +89 -0
- bootstack/_core/capabilities/focus.py +118 -0
- bootstack/_core/capabilities/grab.py +65 -0
- bootstack/_core/capabilities/grid.py +211 -0
- bootstack/_core/capabilities/localization.py +231 -0
- bootstack/_core/capabilities/pack.py +140 -0
- bootstack/_core/capabilities/place.py +113 -0
- bootstack/_core/capabilities/selection.py +136 -0
- bootstack/_core/capabilities/signals.py +244 -0
- bootstack/_core/capabilities/winfo.py +315 -0
- bootstack/_core/colorutils.py +234 -0
- bootstack/_core/exceptions.py +25 -0
- bootstack/_core/images.py +463 -0
- bootstack/_core/mixins/__init__.py +1 -0
- bootstack/_core/mixins/ttk_state.py +35 -0
- bootstack/_core/mixins/widget.py +132 -0
- bootstack/_core/paths.py +49 -0
- bootstack/_core/publisher.py +149 -0
- bootstack/_core/variables.py +62 -0
- bootstack/_runtime/__init__.py +3 -0
- bootstack/_runtime/app.py +930 -0
- bootstack/_runtime/base_window.py +945 -0
- bootstack/_runtime/events.py +399 -0
- bootstack/_runtime/shortcuts.py +496 -0
- bootstack/_runtime/tk_patch.py +43 -0
- bootstack/_runtime/toplevel.py +276 -0
- bootstack/_runtime/utility.py +457 -0
- bootstack/_runtime/visual_focus.py +240 -0
- bootstack/_runtime/window_utilities.py +1123 -0
- bootstack/assets/__init__.py +21 -0
- bootstack/assets/bootstack.ico +0 -0
- bootstack/assets/bootstack.png +0 -0
- bootstack/assets/elements/__init__.py +0 -0
- bootstack/assets/elements/badge-pill.png +0 -0
- bootstack/assets/elements/badge-square.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
- bootstack/assets/elements/card.png +0 -0
- bootstack/assets/elements/checkbox-checked.png +0 -0
- bootstack/assets/elements/checkbox-indeterminate.png +0 -0
- bootstack/assets/elements/checkbox-unchecked.png +0 -0
- bootstack/assets/elements/field.png +0 -0
- bootstack/assets/elements/input-addon-compact.png +0 -0
- bootstack/assets/elements/input-addon-default.png +0 -0
- bootstack/assets/elements/input-compact.png +0 -0
- bootstack/assets/elements/input-default.png +0 -0
- bootstack/assets/elements/list-item-separated.png +0 -0
- bootstack/assets/elements/list-item.png +0 -0
- bootstack/assets/elements/listrow-compact.png +0 -0
- bootstack/assets/elements/listrow-default.png +0 -0
- bootstack/assets/elements/manifest.toml +361 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/navitem-compact.png +0 -0
- bootstack/assets/elements/navitem-default.png +0 -0
- bootstack/assets/elements/progressbar-h-compact.png +0 -0
- bootstack/assets/elements/progressbar-h-default.png +0 -0
- bootstack/assets/elements/progressbar-v-compact.png +0 -0
- bootstack/assets/elements/progressbar-v-default.png +0 -0
- bootstack/assets/elements/radiobutton.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-h.png +0 -0
- bootstack/assets/elements/slider-track-v.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tab-h.png +0 -0
- bootstack/assets/elements/tab-v.png +0 -0
- bootstack/assets/icons/bootstrap.ttf +0 -0
- bootstack/assets/icons/glyphmap.json +2080 -0
- bootstack/assets/icons/icon_metrics.json +12470 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
- bootstack/cli/__init__.py +133 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +395 -0
- bootstack/cli/appicon.py +285 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +313 -0
- bootstack/cli/demo.py +564 -0
- bootstack/cli/dev.py +153 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/icons.py +98 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +268 -0
- bootstack/cli/run.py +95 -0
- bootstack/cli/start.py +117 -0
- bootstack/cli/templates/__init__.py +931 -0
- bootstack/clipboard.py +48 -0
- bootstack/constants.py +318 -0
- bootstack/data/README.md +615 -0
- bootstack/data/__init__.py +78 -0
- bootstack/data/_observable.py +276 -0
- bootstack/data/base.py +780 -0
- bootstack/data/file_source.py +367 -0
- bootstack/data/memory_source.py +450 -0
- bootstack/data/query.py +367 -0
- bootstack/data/readers.py +289 -0
- bootstack/data/sqlite_source.py +869 -0
- bootstack/data/types.py +354 -0
- bootstack/data/writers.py +232 -0
- bootstack/dev/__init__.py +36 -0
- bootstack/dev/_capture.py +141 -0
- bootstack/dev/_env.py +43 -0
- bootstack/dev/_registry.py +140 -0
- bootstack/dev/_reloader.py +351 -0
- bootstack/dev/_reset.py +50 -0
- bootstack/dev/_watcher.py +91 -0
- bootstack/dialogs/__init__.py +948 -0
- bootstack/dialogs/_impl/__init__.py +47 -0
- bootstack/dialogs/_impl/colorchooser.py +588 -0
- bootstack/dialogs/_impl/datedialog.py +450 -0
- bootstack/dialogs/_impl/dialog.py +594 -0
- bootstack/dialogs/_impl/filterdialog.py +358 -0
- bootstack/dialogs/_impl/fontdialog.py +364 -0
- bootstack/dialogs/_impl/formdialog.py +564 -0
- bootstack/dialogs/_impl/message.py +486 -0
- bootstack/dialogs/_impl/query.py +570 -0
- bootstack/errors.py +67 -0
- bootstack/events/__init__.py +111 -0
- bootstack/events/_event.py +135 -0
- bootstack/events/_payloads.py +539 -0
- bootstack/events/_subscription.py +38 -0
- bootstack/i18n/README.md +77 -0
- bootstack/i18n/__init__.py +23 -0
- bootstack/i18n/catalog.py +121 -0
- bootstack/i18n/intl_format.py +584 -0
- bootstack/i18n/msgcat.py +425 -0
- bootstack/i18n/specs.py +156 -0
- bootstack/images.py +563 -0
- bootstack/py.typed +1 -0
- bootstack/scheduling/__init__.py +11 -0
- bootstack/scheduling/_schedule.py +218 -0
- bootstack/shortcuts.py +21 -0
- bootstack/signals/README.md +98 -0
- bootstack/signals/__init__.py +10 -0
- bootstack/signals/integration.py +100 -0
- bootstack/signals/signal.py +353 -0
- bootstack/signals/types.py +6 -0
- bootstack/store.py +286 -0
- bootstack/streams/__init__.py +12 -0
- bootstack/streams/_stream.py +321 -0
- bootstack/style/__init__.py +38 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +46 -0
- bootstack/style/builders/button.py +339 -0
- bootstack/style/builders/buttongroup.py +311 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +110 -0
- bootstack/style/builders/combobox.py +113 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +82 -0
- bootstack/style/builders/expander.py +148 -0
- bootstack/style/builders/field.py +335 -0
- bootstack/style/builders/frame.py +50 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +34 -0
- bootstack/style/builders/listview.py +369 -0
- bootstack/style/builders/menubar.py +91 -0
- bootstack/style/builders/menubutton.py +359 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +67 -0
- bootstack/style/builders/radiobutton.py +99 -0
- bootstack/style/builders/scale.py +64 -0
- bootstack/style/builders/scrollbar.py +225 -0
- bootstack/style/builders/separator.py +49 -0
- bootstack/style/builders/sidenav.py +643 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +70 -0
- bootstack/style/builders/tabitem.py +204 -0
- bootstack/style/builders/togglegroup.py +294 -0
- bootstack/style/builders/toolbutton.py +275 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +193 -0
- bootstack/style/builders/utils.py +455 -0
- bootstack/style/builders_tk/__init__.py +16 -0
- bootstack/style/builders_tk/defaults.py +229 -0
- bootstack/style/element.py +173 -0
- bootstack/style/fonts.py +123 -0
- bootstack/style/style.py +609 -0
- bootstack/style/style_builder_base.py +716 -0
- bootstack/style/style_builder_mixed.py +93 -0
- bootstack/style/style_builder_tk.py +109 -0
- bootstack/style/style_builder_ttk.py +353 -0
- bootstack/style/style_resolver.py +447 -0
- bootstack/style/theme.py +245 -0
- bootstack/style/theme_provider.py +471 -0
- bootstack/style/themes/__init__.py +128 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +41 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +523 -0
- bootstack/style/utility.py +746 -0
- bootstack/types.py +39 -0
- bootstack/validation/__init__.py +6 -0
- bootstack/validation/types.py +15 -0
- bootstack/validation/validation_result.py +17 -0
- bootstack/validation/validation_rules.py +205 -0
- bootstack/widgets/__init__.py +74 -0
- bootstack/widgets/_core/__init__.py +31 -0
- bootstack/widgets/_core/app_config.py +266 -0
- bootstack/widgets/_core/base.py +461 -0
- bootstack/widgets/_core/container.py +334 -0
- bootstack/widgets/_core/context.py +35 -0
- bootstack/widgets/_core/events.py +130 -0
- bootstack/widgets/_core/field_mixin.py +353 -0
- bootstack/widgets/_core/icon_image_props.py +72 -0
- bootstack/widgets/_core/image_binding.py +93 -0
- bootstack/widgets/_core/navmodel.py +345 -0
- bootstack/widgets/_core/options.py +210 -0
- bootstack/widgets/_core/selection_group.py +130 -0
- bootstack/widgets/_core/window_controls.py +96 -0
- bootstack/widgets/_core/window_menu.py +246 -0
- bootstack/widgets/_impl/__init__.py +1 -0
- bootstack/widgets/_impl/_internal/__init__.py +0 -0
- bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
- bootstack/widgets/_impl/_parts/__init__.py +11 -0
- bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
- bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
- bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
- bootstack/widgets/_impl/composites/__init__.py +33 -0
- bootstack/widgets/_impl/composites/_dateutils.py +33 -0
- bootstack/widgets/_impl/composites/_image_fit.py +105 -0
- bootstack/widgets/_impl/composites/accordion.py +381 -0
- bootstack/widgets/_impl/composites/avatar.py +191 -0
- bootstack/widgets/_impl/composites/buttongroup.py +371 -0
- bootstack/widgets/_impl/composites/calendar.py +972 -0
- bootstack/widgets/_impl/composites/carousel.py +567 -0
- bootstack/widgets/_impl/composites/chart.py +882 -0
- bootstack/widgets/_impl/composites/compositeframe.py +298 -0
- bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
- bootstack/widgets/_impl/composites/dateentry.py +404 -0
- bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
- bootstack/widgets/_impl/composites/expander.py +515 -0
- bootstack/widgets/_impl/composites/field.py +670 -0
- bootstack/widgets/_impl/composites/form.py +1066 -0
- bootstack/widgets/_impl/composites/gallery.py +551 -0
- bootstack/widgets/_impl/composites/list/__init__.py +15 -0
- bootstack/widgets/_impl/composites/list/listitem.py +802 -0
- bootstack/widgets/_impl/composites/list/listview.py +1433 -0
- bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
- bootstack/widgets/_impl/composites/menu/model.py +358 -0
- bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
- bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
- bootstack/widgets/_impl/composites/meter.py +860 -0
- bootstack/widgets/_impl/composites/numericentry.py +201 -0
- bootstack/widgets/_impl/composites/pagestack.py +395 -0
- bootstack/widgets/_impl/composites/passwordentry.py +142 -0
- bootstack/widgets/_impl/composites/pathentry.py +168 -0
- bootstack/widgets/_impl/composites/picture.py +289 -0
- bootstack/widgets/_impl/composites/radiogroup.py +511 -0
- bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
- bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/_impl/composites/scrollview.py +764 -0
- bootstack/widgets/_impl/composites/selectbox.py +1026 -0
- bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
- bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
- bootstack/widgets/_impl/composites/shell/layout.py +335 -0
- bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
- bootstack/widgets/_impl/composites/shell/providers.py +558 -0
- bootstack/widgets/_impl/composites/shell/rail.py +117 -0
- bootstack/widgets/_impl/composites/shell/shell.py +581 -0
- bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
- bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
- bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
- bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
- bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
- bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
- bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
- bootstack/widgets/_impl/composites/slider/slider.py +851 -0
- bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
- bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
- bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
- bootstack/widgets/_impl/composites/tableview/types.py +169 -0
- bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
- bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
- bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
- bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
- bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
- bootstack/widgets/_impl/composites/textarea/change.py +46 -0
- bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
- bootstack/widgets/_impl/composites/textarea/core.py +495 -0
- bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
- bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
- bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
- bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
- bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
- bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
- bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
- bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
- bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
- bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
- bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
- bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
- bootstack/widgets/_impl/composites/textentry.py +57 -0
- bootstack/widgets/_impl/composites/timeentry.py +176 -0
- bootstack/widgets/_impl/composites/toast.py +390 -0
- bootstack/widgets/_impl/composites/toast_stack.py +156 -0
- bootstack/widgets/_impl/composites/togglegroup.py +418 -0
- bootstack/widgets/_impl/composites/toolbar.py +608 -0
- bootstack/widgets/_impl/composites/tooltip.py +491 -0
- bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
- bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
- bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
- bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
- bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
- bootstack/widgets/_impl/mixins/__init__.py +24 -0
- bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
- bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
- bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
- bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
- bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
- bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
- bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
- bootstack/widgets/_impl/primitives/__init__.py +49 -0
- bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
- bootstack/widgets/_impl/primitives/badge.py +45 -0
- bootstack/widgets/_impl/primitives/button.py +76 -0
- bootstack/widgets/_impl/primitives/card.py +45 -0
- bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
- bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
- bootstack/widgets/_impl/primitives/combobox.py +156 -0
- bootstack/widgets/_impl/primitives/entry.py +87 -0
- bootstack/widgets/_impl/primitives/flexframe.py +448 -0
- bootstack/widgets/_impl/primitives/frame.py +185 -0
- bootstack/widgets/_impl/primitives/gridframe.py +546 -0
- bootstack/widgets/_impl/primitives/label.py +84 -0
- bootstack/widgets/_impl/primitives/labelframe.py +54 -0
- bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
- bootstack/widgets/_impl/primitives/packframe.py +227 -0
- bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
- bootstack/widgets/_impl/primitives/progressbar.py +83 -0
- bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
- bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
- bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
- bootstack/widgets/_impl/primitives/separator.py +43 -0
- bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
- bootstack/widgets/_impl/primitives/spinbox.py +95 -0
- bootstack/widgets/_impl/primitives/switch.py +44 -0
- bootstack/widgets/_impl/primitives/treeview.py +69 -0
- bootstack/widgets/app.py +371 -0
- bootstack/widgets/appshell.py +1179 -0
- bootstack/widgets/avatar.py +140 -0
- bootstack/widgets/boolean_controls.py +455 -0
- bootstack/widgets/button.py +224 -0
- bootstack/widgets/buttongroup.py +239 -0
- bootstack/widgets/calendar.py +195 -0
- bootstack/widgets/card.py +159 -0
- bootstack/widgets/carousel.py +241 -0
- bootstack/widgets/chart.py +302 -0
- bootstack/widgets/codeeditor.py +675 -0
- bootstack/widgets/contextmenu.py +371 -0
- bootstack/widgets/datatable.py +688 -0
- bootstack/widgets/datefield.py +395 -0
- bootstack/widgets/divider.py +60 -0
- bootstack/widgets/expander.py +579 -0
- bootstack/widgets/form.py +200 -0
- bootstack/widgets/gallery.py +250 -0
- bootstack/widgets/gauge.py +168 -0
- bootstack/widgets/grid.py +121 -0
- bootstack/widgets/groupbox.py +162 -0
- bootstack/widgets/label.py +229 -0
- bootstack/widgets/listview.py +354 -0
- bootstack/widgets/menubutton.py +419 -0
- bootstack/widgets/numberfield.py +431 -0
- bootstack/widgets/pagestack.py +345 -0
- bootstack/widgets/passwordfield.py +383 -0
- bootstack/widgets/pathfield.py +454 -0
- bootstack/widgets/picture.py +251 -0
- bootstack/widgets/progressbar.py +106 -0
- bootstack/widgets/radio_variants.py +271 -0
- bootstack/widgets/radiogroup.py +228 -0
- bootstack/widgets/scrollbar.py +64 -0
- bootstack/widgets/scrollview.py +186 -0
- bootstack/widgets/select.py +302 -0
- bootstack/widgets/selectbutton.py +182 -0
- bootstack/widgets/sidebar_toggle.py +130 -0
- bootstack/widgets/sizegrip.py +42 -0
- bootstack/widgets/slider.py +408 -0
- bootstack/widgets/spinbox.py +147 -0
- bootstack/widgets/spinnerfield.py +413 -0
- bootstack/widgets/splash.py +367 -0
- bootstack/widgets/splitview.py +482 -0
- bootstack/widgets/stacks.py +249 -0
- bootstack/widgets/statusbar.py +189 -0
- bootstack/widgets/tabs.py +425 -0
- bootstack/widgets/textarea.py +459 -0
- bootstack/widgets/textfield.py +410 -0
- bootstack/widgets/theme_toggle.py +92 -0
- bootstack/widgets/timefield.py +340 -0
- bootstack/widgets/toast.py +314 -0
- bootstack/widgets/togglegroup.py +231 -0
- bootstack/widgets/toolbar.py +347 -0
- bootstack/widgets/tooltip.py +94 -0
- bootstack/widgets/tree.py +648 -0
- bootstack/widgets/types.py +305 -0
- bootstack/widgets/window.py +307 -0
- bootstack-0.1.0.dist-info/METADATA +301 -0
- bootstack-0.1.0.dist-info/RECORD +471 -0
- bootstack-0.1.0.dist-info/WHEEL +5 -0
- bootstack-0.1.0.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
- bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from bootstack._runtime.app import get_app_settings
|
|
4
|
+
from bootstack._core.exceptions import ThemeError
|
|
5
|
+
from bootstack.style.utility import (
|
|
6
|
+
shade_color, tint_color, color_to_hsl, hsl_to_hex, best_foreground
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
_registered_themes = {}
|
|
10
|
+
_current_theme = None
|
|
11
|
+
|
|
12
|
+
# Weights for generating color spectrum tints (toward white) and shades (toward black)
|
|
13
|
+
# Each weight represents how much of the original color to retain when mixing
|
|
14
|
+
# Full 50-step increments from 50-450 (tints) and 550-950 (shades)
|
|
15
|
+
# Tint (toward white) / shade (toward black) weights per 50-step stop. The named
|
|
16
|
+
# 100-step stops match Bootstrap 5.3's scale exactly — 100/200/300 = tint
|
|
17
|
+
# 80/60/40, 400 = tint 20, 600/700/800/900 = shade 20/40/60/80 — so `[100]`/
|
|
18
|
+
# `[200]`/`[800]` equal Bootstrap's bg-subtle/border-subtle/text-emphasis. The
|
|
19
|
+
# half-steps (150/250/…) interpolate linearly between the Bootstrap anchors.
|
|
20
|
+
TINT_WEIGHTS = {
|
|
21
|
+
50: 0.90,
|
|
22
|
+
100: 0.80,
|
|
23
|
+
150: 0.70,
|
|
24
|
+
200: 0.60,
|
|
25
|
+
250: 0.50,
|
|
26
|
+
300: 0.40,
|
|
27
|
+
350: 0.30,
|
|
28
|
+
400: 0.20,
|
|
29
|
+
450: 0.10,
|
|
30
|
+
}
|
|
31
|
+
SHADE_WEIGHTS = {
|
|
32
|
+
550: 0.10,
|
|
33
|
+
600: 0.20,
|
|
34
|
+
650: 0.30,
|
|
35
|
+
700: 0.40,
|
|
36
|
+
750: 0.50,
|
|
37
|
+
800: 0.60,
|
|
38
|
+
850: 0.70,
|
|
39
|
+
900: 0.80,
|
|
40
|
+
950: 0.90,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def register_theme(name, data):
|
|
45
|
+
"""Register a resolved theme schema dict under `name`.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
name: Canonical theme name used by `set_theme`.
|
|
49
|
+
data: Resolved theme dict (name, display_name, mode, foreground,
|
|
50
|
+
background, white, black, shades, semantic).
|
|
51
|
+
"""
|
|
52
|
+
_registered_themes[name] = data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_theme(name):
|
|
56
|
+
"""Return a registered theme by name.
|
|
57
|
+
|
|
58
|
+
If the theme is not currently registered this will re-run
|
|
59
|
+
`load_system_themes` once to pick up any newly added themes before
|
|
60
|
+
failing.
|
|
61
|
+
"""
|
|
62
|
+
if name in _registered_themes:
|
|
63
|
+
return _registered_themes[name]
|
|
64
|
+
|
|
65
|
+
# Lazy fallback: configuration (e.g., load_all_themes/include_legacy_themes)
|
|
66
|
+
# may have changed after the provider singleton was first initialized.
|
|
67
|
+
# Re-run the loader once to pick up any additional themes.
|
|
68
|
+
try:
|
|
69
|
+
load_system_themes()
|
|
70
|
+
if name in _registered_themes:
|
|
71
|
+
return _registered_themes[name]
|
|
72
|
+
except Exception:
|
|
73
|
+
# If anything goes wrong here, fall through to the ThemeError.
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# Build a helpful error message listing available themes
|
|
77
|
+
available = sorted(
|
|
78
|
+
{
|
|
79
|
+
data.get("name")
|
|
80
|
+
for data in _registered_themes.values()
|
|
81
|
+
if isinstance(data, dict) and data.get("name")
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
available_str = ", ".join(available) if available else "<none>"
|
|
85
|
+
raise ThemeError(
|
|
86
|
+
f"Theme '{name}' is not registered. "
|
|
87
|
+
f"Registered themes: {available_str}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_system_themes():
|
|
92
|
+
"""Register the built-in themes and the `light`/`dark` aliases.
|
|
93
|
+
|
|
94
|
+
Built-in themes are plain `Theme` instances (no JSON assets). The themes
|
|
95
|
+
matching the app's configured `light_theme`/`dark_theme` are also registered
|
|
96
|
+
under the `'light'` and `'dark'` aliases. Themes may be registered before an
|
|
97
|
+
App exists (e.g. installing a custom theme with `base=` at module level), so
|
|
98
|
+
this falls back to the default light/dark theme names when there is no app.
|
|
99
|
+
"""
|
|
100
|
+
from bootstack.style.themes import install_builtin_themes
|
|
101
|
+
|
|
102
|
+
install_builtin_themes()
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
app_settings = get_app_settings()
|
|
106
|
+
dark_theme_name = app_settings.dark_theme
|
|
107
|
+
light_theme_name = app_settings.light_theme
|
|
108
|
+
except Exception:
|
|
109
|
+
dark_theme_name = "bootstrap-dark"
|
|
110
|
+
light_theme_name = "bootstrap-light"
|
|
111
|
+
|
|
112
|
+
if dark_theme_name in _registered_themes:
|
|
113
|
+
_registered_themes['dark'] = _registered_themes[dark_theme_name]
|
|
114
|
+
if light_theme_name in _registered_themes:
|
|
115
|
+
_registered_themes['light'] = _registered_themes[light_theme_name]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def color_spectrum(token, value):
|
|
119
|
+
"""Generate a color spectrum with 50-step increments from 50-950.
|
|
120
|
+
|
|
121
|
+
Creates tints (lighter) and shades (darker) of the base color:
|
|
122
|
+
- 50-450: Tints toward white (50 is lightest)
|
|
123
|
+
- 500: Base color
|
|
124
|
+
- 550-950: Shades toward black (950 is darkest)
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
token: The color token name (e.g., 'gray', 'blue')
|
|
128
|
+
value: The base hex color value
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Dict mapping spectrum names to hex colors
|
|
132
|
+
"""
|
|
133
|
+
result = {}
|
|
134
|
+
|
|
135
|
+
# Generate tints (50-450)
|
|
136
|
+
for stop, weight in TINT_WEIGHTS.items():
|
|
137
|
+
result[f'{token}[{stop}]'] = tint_color(value, weight)
|
|
138
|
+
|
|
139
|
+
# Base color (500)
|
|
140
|
+
result[f'{token}[500]'] = value
|
|
141
|
+
|
|
142
|
+
# Generate shades (550-950)
|
|
143
|
+
for stop, weight in SHADE_WEIGHTS.items():
|
|
144
|
+
result[f'{token}[{stop}]'] = shade_color(value, weight)
|
|
145
|
+
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ThemeProvider:
|
|
150
|
+
"""Theme data provider with singleton access and helpers.
|
|
151
|
+
|
|
152
|
+
Mirrors the pattern used by Style/use_style():
|
|
153
|
+
- `ThemeProvider()` returns the global instance (singleton)
|
|
154
|
+
- `use_theme(name)` returns/initializes the singleton with optional theme
|
|
155
|
+
- `ThemeProvider.instance(name)` remains for backward compatibility
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
# Class-level global singleton instance
|
|
159
|
+
_instance: ThemeProvider | None = None
|
|
160
|
+
|
|
161
|
+
def __new__(cls, *args, **kwargs):
|
|
162
|
+
"""Ensure ThemeProvider() always returns the global singleton instance."""
|
|
163
|
+
if cls._instance is None:
|
|
164
|
+
cls._instance = super().__new__(cls)
|
|
165
|
+
return cls._instance
|
|
166
|
+
|
|
167
|
+
def __init__(self, name: str = "dark"):
|
|
168
|
+
# Prevent reinitialization on subsequent ThemeProvider() calls
|
|
169
|
+
if getattr(self, "_initialized", False):
|
|
170
|
+
if name and name != self.name:
|
|
171
|
+
self.use(name)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
self._theme = {}
|
|
175
|
+
self._colors = {}
|
|
176
|
+
load_system_themes()
|
|
177
|
+
|
|
178
|
+
from bootstack.style.typography import Typography
|
|
179
|
+
Typography.initialize()
|
|
180
|
+
|
|
181
|
+
self.use(name)
|
|
182
|
+
self._initialized = True
|
|
183
|
+
|
|
184
|
+
# ----- Theme metadata helpers -------------------------------------------------
|
|
185
|
+
|
|
186
|
+
def list_themes(self) -> list[dict[str, str]]:
|
|
187
|
+
"""Return a list of available themes with names and display names.
|
|
188
|
+
|
|
189
|
+
The result is a list of dictionaries in the form:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
{"name": "bootstrap-light", "display_name": "Bootstrap Light"}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Aliases such as `\"light\"` and `\"dark\"` are not included; only the
|
|
196
|
+
canonical theme entries loaded into the provider are returned.
|
|
197
|
+
"""
|
|
198
|
+
themes: list[dict[str, str]] = []
|
|
199
|
+
seen: set[str] = set()
|
|
200
|
+
|
|
201
|
+
for key, data in _registered_themes.items():
|
|
202
|
+
# Skip alias keys that point at an existing theme object
|
|
203
|
+
name = data.get("name")
|
|
204
|
+
if not name:
|
|
205
|
+
continue
|
|
206
|
+
if key != name and name in _registered_themes:
|
|
207
|
+
# This is an alias like 'light' or 'dark'
|
|
208
|
+
continue
|
|
209
|
+
if name in seen:
|
|
210
|
+
continue
|
|
211
|
+
seen.add(name)
|
|
212
|
+
themes.append(
|
|
213
|
+
{
|
|
214
|
+
"name": name,
|
|
215
|
+
"display_name": data.get("display_name", name),
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# If the application has declared a specific set/order of themes
|
|
220
|
+
# to expose, filter and order by that list.
|
|
221
|
+
select_themes = get_app_settings().available_themes
|
|
222
|
+
if select_themes:
|
|
223
|
+
by_name = {t["name"]: t for t in themes}
|
|
224
|
+
ordered: list[dict[str, str]] = []
|
|
225
|
+
for name in select_themes:
|
|
226
|
+
t = by_name.get(name)
|
|
227
|
+
if t is not None:
|
|
228
|
+
ordered.append(t)
|
|
229
|
+
return ordered
|
|
230
|
+
|
|
231
|
+
# Otherwise, sort for stable UI ordering (by display name, then name)
|
|
232
|
+
themes.sort(key=lambda t: (t["display_name"].lower(), t["name"].lower()))
|
|
233
|
+
return themes
|
|
234
|
+
|
|
235
|
+
def use(self, name):
|
|
236
|
+
self._theme = get_theme(name)
|
|
237
|
+
self.build_theme_colors()
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def raw(self):
|
|
241
|
+
"""Return the raw source dictionary"""
|
|
242
|
+
return self._theme
|
|
243
|
+
|
|
244
|
+
def build_theme_colors(self):
|
|
245
|
+
colors = {}
|
|
246
|
+
colors.update(
|
|
247
|
+
foreground=self.raw.get('foreground'),
|
|
248
|
+
background=self.raw.get('background'),
|
|
249
|
+
white=self.raw.get('white'),
|
|
250
|
+
black=self.raw.get('black'),
|
|
251
|
+
**self._shades,
|
|
252
|
+
)
|
|
253
|
+
# add shaded spectrum
|
|
254
|
+
for color, value in self._shades.items():
|
|
255
|
+
colors.update(**color_spectrum(color, value))
|
|
256
|
+
|
|
257
|
+
# semantic tokens
|
|
258
|
+
neutral_tokens = {
|
|
259
|
+
"foreground",
|
|
260
|
+
"muted",
|
|
261
|
+
"muted_alt",
|
|
262
|
+
"subtle",
|
|
263
|
+
"border",
|
|
264
|
+
"border_subtle",
|
|
265
|
+
}
|
|
266
|
+
for token, value in self._semantic.items():
|
|
267
|
+
# Neutral roles are derived elsewhere from the top-level
|
|
268
|
+
# foreground/background and should not be overridden here.
|
|
269
|
+
if token in neutral_tokens:
|
|
270
|
+
continue
|
|
271
|
+
colors[token] = colors[value]
|
|
272
|
+
|
|
273
|
+
# Per-semantic subtle / border / emphasis tokens (Bootstrap 5.3 model),
|
|
274
|
+
# sourced from the accent's hue-FAMILY scale stops so they stay vibrant
|
|
275
|
+
# regardless of how dark the semantic solid is. Light: bg-subtle=[100],
|
|
276
|
+
# border-subtle=[200], text-emphasis=[800]; dark uses the dark end.
|
|
277
|
+
# border-subtle + text-emphasis are fixed accent shades (light: [200]/
|
|
278
|
+
# [800]; dark: [700]/[300]). The bg-subtle WASH is NOT precomputed here —
|
|
279
|
+
# it is surface-relative (a blend of the accent anchor into whatever
|
|
280
|
+
# surface it sits on), computed in `StyleBuilder.subtle()`.
|
|
281
|
+
import re as _re
|
|
282
|
+
_stop_re = _re.compile(r'^([a-z]+)\[\d+\]$')
|
|
283
|
+
_is_dark = self.mode == 'dark'
|
|
284
|
+
_stops = ('700', '300') if _is_dark else ('200', '800')
|
|
285
|
+
for token, value in self._semantic.items():
|
|
286
|
+
if token in neutral_tokens:
|
|
287
|
+
continue
|
|
288
|
+
m = _stop_re.match(str(value))
|
|
289
|
+
if m is None:
|
|
290
|
+
continue
|
|
291
|
+
family = m.group(1)
|
|
292
|
+
for suffix, stop in zip(('border_subtle', 'emphasis'), _stops):
|
|
293
|
+
colors[f'{token}_{suffix}'] = colors.get(f'{family}[{stop}]')
|
|
294
|
+
|
|
295
|
+
# Surface tokens - semantic ramps for container backgrounds
|
|
296
|
+
self._build_surface_tokens(colors)
|
|
297
|
+
|
|
298
|
+
# Neutral text tokens — pre-computed for direct use as accent tokens
|
|
299
|
+
from bootstack.style.utility import muted_foreground
|
|
300
|
+
colors['muted'] = muted_foreground(colors['background'])
|
|
301
|
+
|
|
302
|
+
self._colors.clear()
|
|
303
|
+
self._colors.update(**colors)
|
|
304
|
+
|
|
305
|
+
def _build_surface_tokens(self, colors: dict):
|
|
306
|
+
"""Build semantic surface tokens for container backgrounds.
|
|
307
|
+
|
|
308
|
+
Surface tokens provide deterministic, theme-defined backgrounds
|
|
309
|
+
that don't rely on "background +1 math" for elevation. The hue
|
|
310
|
+
and saturation are derived from the theme's background color to
|
|
311
|
+
ensure consistent tinting across all surfaces.
|
|
312
|
+
|
|
313
|
+
Token families:
|
|
314
|
+
- chrome: UI shell (sidebars, toolbars, navigation)
|
|
315
|
+
- content: Main content area background (= theme background)
|
|
316
|
+
- card: Elevated content (cards, panels)
|
|
317
|
+
- overlay: Floating elements (menus, dialogs, tooltips)
|
|
318
|
+
- input: Form control backgrounds
|
|
319
|
+
"""
|
|
320
|
+
from bootstack.style.utility import darken_color, lighten_color
|
|
321
|
+
is_dark = self.mode == 'dark'
|
|
322
|
+
bg = colors['background']
|
|
323
|
+
fg = colors['foreground']
|
|
324
|
+
|
|
325
|
+
# Derive surfaces by darkening/lightening the BACKGROUND — this preserves
|
|
326
|
+
# the theme's hue AND saturation at every level (rebuilding at a target
|
|
327
|
+
# lightness with capped saturation washed the tint out and could invert
|
|
328
|
+
# the content/sidebar hierarchy). `content` is always the background;
|
|
329
|
+
# `chrome` is the recessed shell tier (darkest in both modes).
|
|
330
|
+
def dk(amount: float) -> str:
|
|
331
|
+
return darken_color(bg, amount)
|
|
332
|
+
|
|
333
|
+
def lt(amount: float) -> str:
|
|
334
|
+
return lighten_color(bg, amount)
|
|
335
|
+
|
|
336
|
+
# Helpers for the muted-foreground + hover tokens below (a tinted gray at
|
|
337
|
+
# a target lightness; the main surfaces use dk/lt above).
|
|
338
|
+
hue, sat, bg_lightness = color_to_hsl(bg, model='hex')
|
|
339
|
+
tint_sat = min(sat, 25) if sat >= 5 else 0
|
|
340
|
+
|
|
341
|
+
def tinted_surface(lightness: float) -> str:
|
|
342
|
+
if tint_sat == 0:
|
|
343
|
+
return hsl_to_hex(0, 0, lightness)
|
|
344
|
+
return hsl_to_hex(hue, tint_sat, lightness)
|
|
345
|
+
|
|
346
|
+
if is_dark:
|
|
347
|
+
# Elevation lightens off the dark bg; chrome darkens (recessed).
|
|
348
|
+
surfaces = {
|
|
349
|
+
'chrome': dk(0.40),
|
|
350
|
+
'content': bg,
|
|
351
|
+
'raised': lt(0.05),
|
|
352
|
+
'card': lt(0.09),
|
|
353
|
+
'card_raised': lt(0.15),
|
|
354
|
+
'overlay': lt(0.12),
|
|
355
|
+
'input': lt(0.03),
|
|
356
|
+
}
|
|
357
|
+
colors['stroke'] = lt(0.22)
|
|
358
|
+
colors['stroke_subtle'] = lt(0.10)
|
|
359
|
+
else:
|
|
360
|
+
# Surfaces darken off the light bg; content stays the lightest.
|
|
361
|
+
surfaces = {
|
|
362
|
+
'chrome': dk(0.10),
|
|
363
|
+
'content': bg,
|
|
364
|
+
'raised': dk(0.025),
|
|
365
|
+
'card': dk(0.05),
|
|
366
|
+
'card_raised': dk(0.085),
|
|
367
|
+
'overlay': dk(0.025),
|
|
368
|
+
'input': bg,
|
|
369
|
+
}
|
|
370
|
+
colors['stroke'] = dk(0.14)
|
|
371
|
+
colors['stroke_subtle'] = dk(0.06)
|
|
372
|
+
|
|
373
|
+
# Per-theme surface overrides (escape hatch for cases the derivation
|
|
374
|
+
# can't produce well, e.g. a very-dark or very-light background).
|
|
375
|
+
override = self.raw.get('surfaces') if isinstance(self.raw, dict) else None
|
|
376
|
+
if override:
|
|
377
|
+
for name, value in override.items():
|
|
378
|
+
if name == 'stroke' or name == 'stroke_subtle':
|
|
379
|
+
colors[name] = value
|
|
380
|
+
else:
|
|
381
|
+
surfaces[name] = value
|
|
382
|
+
|
|
383
|
+
# Add surfaces to colors
|
|
384
|
+
for name, value in surfaces.items():
|
|
385
|
+
colors[name] = value
|
|
386
|
+
|
|
387
|
+
# Pre-compute foreground colors for each surface
|
|
388
|
+
candidates = [fg, '#ffffff', '#000000']
|
|
389
|
+
for name, value in surfaces.items():
|
|
390
|
+
colors[f'on_{name}'] = best_foreground(value, candidates)
|
|
391
|
+
|
|
392
|
+
# Secondary/muted foreground for each surface (reduced contrast)
|
|
393
|
+
for name, value in surfaces.items():
|
|
394
|
+
# Generate a muted foreground by mixing fg with the surface
|
|
395
|
+
on_color = colors[f'on_{name}']
|
|
396
|
+
if on_color in ('#ffffff', '#FFFFFF'):
|
|
397
|
+
colors[f'on_{name}_secondary'] = tinted_surface(65) if is_dark else tinted_surface(45)
|
|
398
|
+
else:
|
|
399
|
+
colors[f'on_{name}_secondary'] = tinted_surface(35) if is_dark else tinted_surface(55)
|
|
400
|
+
|
|
401
|
+
# Hover states for each surface (subtle highlight)
|
|
402
|
+
for name in surfaces.keys():
|
|
403
|
+
if name == 'content':
|
|
404
|
+
# Content hover: slightly elevated
|
|
405
|
+
hover_l = min(bg_lightness + 5, 25) if is_dark else max(bg_lightness - 5, 90)
|
|
406
|
+
else:
|
|
407
|
+
# Other surfaces: shift toward content
|
|
408
|
+
surface_h, surface_s, surface_l = color_to_hsl(colors[name], model='hex')
|
|
409
|
+
hover_l = min(surface_l + 5, 30) if is_dark else max(surface_l - 5, 85)
|
|
410
|
+
colors[f'{name}_hover'] = tinted_surface(hover_l)
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def name(self):
|
|
414
|
+
"""The name of the theme"""
|
|
415
|
+
return self.raw.get('name')
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def display_name(self):
|
|
419
|
+
"""The display name of the theme"""
|
|
420
|
+
return self.raw.get('display_name')
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def mode(self):
|
|
424
|
+
"""Returns the color mode 'light' or 'dark'"""
|
|
425
|
+
return self.raw.get('mode')
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def colors(self):
|
|
429
|
+
return self._colors
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def typography(self):
|
|
433
|
+
"""Returns the current typography configuration as FontTokens"""
|
|
434
|
+
from bootstack.style.typography import Typography
|
|
435
|
+
return Typography.all()
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def _shades(self):
|
|
439
|
+
return self.raw.get('shades')
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def _semantic(self):
|
|
443
|
+
return self.raw.get('semantic')
|
|
444
|
+
|
|
445
|
+
def __repr__(self):
|
|
446
|
+
"""Return a string representation of the current theme"""
|
|
447
|
+
return f"<Theme name={self.name} mode={self.mode}>"
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def use_theme(name: str = None) -> ThemeProvider:
|
|
451
|
+
"""Return the global ThemeProvider singleton instance.
|
|
452
|
+
|
|
453
|
+
Convenience helper that mirrors `use_style()` so callers can obtain
|
|
454
|
+
the current ThemeProvider without handling singleton state.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
name: Optional theme name to switch to, or None to return current instance.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Global ThemeProvider instance.
|
|
461
|
+
"""
|
|
462
|
+
# If instance doesn't exist yet, create it with default theme
|
|
463
|
+
if ThemeProvider._instance is None:
|
|
464
|
+
return ThemeProvider(name or "dark")
|
|
465
|
+
|
|
466
|
+
# If name is provided and different, switch themes
|
|
467
|
+
if name is not None and name != ThemeProvider._instance.name:
|
|
468
|
+
ThemeProvider._instance.use(name)
|
|
469
|
+
|
|
470
|
+
# Return the singleton
|
|
471
|
+
return ThemeProvider._instance
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Built-in color theme families, defined as `Theme` instances.
|
|
2
|
+
|
|
3
|
+
Each family declares its semantic accent colors (the `[500]` ramp anchors) plus a
|
|
4
|
+
light and dark background block; `install()` generates and registers both
|
|
5
|
+
`<name>-light` and `<name>-dark` variants. The framework picks the per-mode ramp
|
|
6
|
+
step for solids, washes, borders, and emphasis — there is no per-mode shade
|
|
7
|
+
boilerplate. Installed into the registry by `install_builtin_themes`, called from
|
|
8
|
+
the theme provider.
|
|
9
|
+
|
|
10
|
+
The non-bootstrap families adapt well-known, open-licensed designer palettes
|
|
11
|
+
(Nord, Solarized, Catppuccin, Gruvbox, Dracula, Tokyo Night, One, Everforest) to
|
|
12
|
+
the framework's semantic roles.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from bootstack.style.theme import Theme
|
|
17
|
+
|
|
18
|
+
# --- Bootstrap (the neutral reference) -------------------------------------
|
|
19
|
+
BOOTSTRAP = Theme(
|
|
20
|
+
name="bootstrap",
|
|
21
|
+
primary="#0d6efd", success="#198754", info="#0dcaf0", warning="#ffc107", danger="#dc3545",
|
|
22
|
+
light=dict(background="#ffffff", foreground="#212529"),
|
|
23
|
+
dark=dict(background="#212529", foreground="#f8f9fa"),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# --- PyData (professional teal) --------------------------------------------
|
|
27
|
+
PYDATA = Theme(
|
|
28
|
+
name="pydata",
|
|
29
|
+
display_name="PyData",
|
|
30
|
+
primary="#0a7d91", success="#198754", info="#0dcaf0", warning="#ffc107", danger="#dc3545",
|
|
31
|
+
secondary="#8045e5",
|
|
32
|
+
neutral="#677384",
|
|
33
|
+
light=dict(background="#ffffff", foreground="#222832"),
|
|
34
|
+
dark=dict(background="#14181e", foreground="#ced6dd"),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# --- Nord (cool arctic) -----------------------------------------------------
|
|
38
|
+
NORD = Theme(
|
|
39
|
+
name="nord",
|
|
40
|
+
primary="#5e81ac", success="#a3be8c", info="#88c0d0", warning="#ebcb8b", danger="#bf616a",
|
|
41
|
+
secondary="#b48ead",
|
|
42
|
+
neutral="#4c566a",
|
|
43
|
+
light=dict(background="#eceff4", foreground="#2e3440"),
|
|
44
|
+
dark=dict(background="#2e3440", foreground="#eceff4"),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# --- Solarized (precision classic) -----------------------------------------
|
|
48
|
+
SOLARIZED = Theme(
|
|
49
|
+
name="solarized",
|
|
50
|
+
primary="#268bd2", success="#859900", info="#2aa198", warning="#b58900", danger="#dc322f",
|
|
51
|
+
secondary="#6c71c4",
|
|
52
|
+
neutral="#839496",
|
|
53
|
+
light=dict(background="#f6f1e9", foreground="#586e75"),
|
|
54
|
+
dark=dict(background="#002b36", foreground="#93a1a1"),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# --- Catppuccin (soft pastel) ----------------------------------------------
|
|
58
|
+
CATPPUCCIN = Theme(
|
|
59
|
+
name="catppuccin",
|
|
60
|
+
primary="#8839ef", success="#40a02b", info="#179299", warning="#df8e1d", danger="#d20f39",
|
|
61
|
+
secondary="#ea76cb",
|
|
62
|
+
neutral="#8c8fa1",
|
|
63
|
+
light=dict(background="#eff1f5", foreground="#4c4f69"),
|
|
64
|
+
dark=dict(background="#1e1e2e", foreground="#cdd6f4"),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# --- Gruvbox (warm retro) ---------------------------------------------------
|
|
68
|
+
GRUVBOX = Theme(
|
|
69
|
+
name="gruvbox",
|
|
70
|
+
primary="#458588", success="#98971a", info="#689d6a", warning="#d79921", danger="#cc241d",
|
|
71
|
+
secondary="#d65d0e",
|
|
72
|
+
neutral="#928374",
|
|
73
|
+
light=dict(background="#f2ede9", foreground="#3c3836"),
|
|
74
|
+
dark=dict(background="#282828", foreground="#ebdbb2"),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# --- Dracula (vibrant) ------------------------------------------------------
|
|
78
|
+
DRACULA = Theme(
|
|
79
|
+
name="dracula",
|
|
80
|
+
primary="#bd93f9", success="#50fa7b", info="#8be9fd", warning="#ffb86c", danger="#ff5555",
|
|
81
|
+
secondary="#ff79c6",
|
|
82
|
+
neutral="#6272a4",
|
|
83
|
+
light=dict(background="#f8f8f2", foreground="#282a36"),
|
|
84
|
+
dark=dict(background="#282a36", foreground="#f8f8f2"),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# --- Tokyo Night (deep blue/purple) ----------------------------------------
|
|
88
|
+
TOKYO_NIGHT = Theme(
|
|
89
|
+
name="tokyo-night",
|
|
90
|
+
display_name="Tokyo Night",
|
|
91
|
+
primary="#7aa2f7", success="#9ece6a", info="#7dcfff", warning="#e0af68", danger="#f7768e",
|
|
92
|
+
secondary="#bb9af7",
|
|
93
|
+
neutral="#565f89",
|
|
94
|
+
light=dict(background="#e1e2e7", foreground="#343b58"),
|
|
95
|
+
dark=dict(background="#1a1b26", foreground="#c0caf5"),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# --- One (Atom, clean neutral) ---------------------------------------------
|
|
99
|
+
ONE = Theme(
|
|
100
|
+
name="one",
|
|
101
|
+
primary="#4078f2", success="#50a14f", info="#0184bc", warning="#c18401", danger="#e45649",
|
|
102
|
+
secondary="#a626a4",
|
|
103
|
+
neutral="#a0a1a7",
|
|
104
|
+
light=dict(background="#fafafa", foreground="#383a42"),
|
|
105
|
+
dark=dict(background="#282c34", foreground="#abb2bf"),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# --- Everforest (green-leaning, soft) --------------------------------------
|
|
109
|
+
EVERFOREST = Theme(
|
|
110
|
+
name="everforest",
|
|
111
|
+
primary="#3a94c5", success="#8da101", info="#35a77c", warning="#dfa000", danger="#f85552",
|
|
112
|
+
secondary="#df69ba",
|
|
113
|
+
neutral="#939f91",
|
|
114
|
+
light=dict(background="#edf3ed", foreground="#5c6a72"),
|
|
115
|
+
dark=dict(background="#2d353b", foreground="#d3c6aa"),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
#: All built-in theme families, in display order.
|
|
119
|
+
BUILTIN_THEMES = [
|
|
120
|
+
BOOTSTRAP, PYDATA, NORD, SOLARIZED, CATPPUCCIN,
|
|
121
|
+
GRUVBOX, DRACULA, TOKYO_NIGHT, ONE, EVERFOREST,
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def install_builtin_themes() -> None:
|
|
126
|
+
"""Register every built-in theme variant into the theme registry (idempotent)."""
|
|
127
|
+
for theme in BUILTIN_THEMES:
|
|
128
|
+
theme.install()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
COLOR_TOKENS = {
|
|
4
|
+
'primary', 'secondary', 'info', 'success', 'warning', 'danger',
|
|
5
|
+
'foreground', 'background', 'white', 'black',
|
|
6
|
+
'muted',
|
|
7
|
+
'blue', 'indigo', 'purple', 'red', 'orange',
|
|
8
|
+
'yellow', 'green', 'teal', 'cyan', 'gray',
|
|
9
|
+
'border'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
WIDGET_CLASS_MAP = {
|
|
13
|
+
'badge': 'TBadge',
|
|
14
|
+
'button': 'TButton',
|
|
15
|
+
'label': 'TLabel',
|
|
16
|
+
'entry': 'TEntry',
|
|
17
|
+
'frame': 'TFrame',
|
|
18
|
+
'labelframe': 'TLabelframe',
|
|
19
|
+
'progressbar': 'TProgressbar',
|
|
20
|
+
'scale': 'TScale',
|
|
21
|
+
'scrollbar': 'TScrollbar',
|
|
22
|
+
'checkbutton': 'TCheckbutton',
|
|
23
|
+
'radiobutton': 'TRadiobutton',
|
|
24
|
+
'combobox': 'TCombobox',
|
|
25
|
+
'treeview': 'Treeview',
|
|
26
|
+
'separator': 'TSeparator',
|
|
27
|
+
'sizegrip': 'TSizegrip',
|
|
28
|
+
'panedwindow': 'TPanedwindow',
|
|
29
|
+
'spinbox': 'TSpinbox',
|
|
30
|
+
'menubutton': 'TMenubutton',
|
|
31
|
+
'field': 'TField',
|
|
32
|
+
'toolbutton': 'Toolbutton',
|
|
33
|
+
'tooltip': 'Tooltip',
|
|
34
|
+
'calendar': 'TCalendar',
|
|
35
|
+
'buttongroup': 'ButtonGroup'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
WIDGET_NAME_MAP = {v: k for k, v in WIDGET_CLASS_MAP.items()}
|
|
39
|
+
CONTAINER_CLASSES = {'TFrame'}
|
|
40
|
+
ORIENT_CLASSES = {'TProgressbar', 'TScale', 'TScrollbar', 'TPanedwindow', 'TSeparator'}
|
|
41
|
+
ICON_CLASSES = {'TLabel', 'TButton', 'TCheckbutton', 'TRadiobutton', 'TMenubutton'}
|
bootstack/style/types.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from tkinter.font import Font
|
|
2
|
+
from typing import Literal, Union
|
|
3
|
+
|
|
4
|
+
ColorShade = Literal[100, 200, 300, 400, 500, 600, 700, 800, 900]
|
|
5
|
+
ColorMode = Union[Literal['light', 'dark'], str]
|
|
6
|
+
ColorModel = Literal['hex', 'hsl', 'rgb']
|
|
7
|
+
|
|
8
|
+
# === Color Types ===
|
|
9
|
+
|
|
10
|
+
# supports subtle variants, e.g., 'primary[subtle]'
|
|
11
|
+
SemanticColor = Literal['primary', 'success', 'warning', 'danger']
|
|
12
|
+
UtilityColor = Literal['foreground', 'background']
|
|
13
|
+
|
|
14
|
+
# supports shade variants 100-900, e.g. `blue[300]`
|
|
15
|
+
ShadeColor = Literal['blue', 'indigo', 'purple', 'red', 'orange', 'yellow', 'green', 'teal', 'cyan', 'gray']
|
|
16
|
+
|
|
17
|
+
# === Color Tokens ===
|
|
18
|
+
|
|
19
|
+
SurfaceColor = Union[SemanticColor, str]
|
|
20
|
+
ForegroundColor = Union[SemanticColor, ShadeColor, UtilityColor, str]
|
|
21
|
+
ThemeColor = Union[SemanticColor, ShadeColor, UtilityColor, str]
|
|
22
|
+
SeparatorColor = Union[Literal['border'], SemanticColor]
|
|
23
|
+
BorderColor = Union[Literal['border'], SemanticColor, ShadeColor, str]
|
|
24
|
+
|
|
25
|
+
# === Font Tokens ====
|
|
26
|
+
|
|
27
|
+
BootstrapFontType = Literal[
|
|
28
|
+
'label', 'body', 'body-sm', 'body-lg', 'body-xl', 'caption',
|
|
29
|
+
'display-xl', 'display-lg', 'heading-xl', 'heading-lg', 'heading-md', 'code'
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
TypographyToken = Union[BootstrapFontType, str, Font]
|