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
bootstack/cli/dev.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""bootstack dev command - Run an application with hot reload.
|
|
2
|
+
|
|
3
|
+
PROVISIONAL — the dev workflow ships in 0.1.0 but is carved out of the API
|
|
4
|
+
freeze and may change in a minor release.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import threading
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from bootstack.cli.config import TtkbConfig, find_config
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def add_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
20
|
+
"""Add the 'dev' subcommand parser."""
|
|
21
|
+
parser = subparsers.add_parser(
|
|
22
|
+
"dev",
|
|
23
|
+
help="Run the application with hot reload",
|
|
24
|
+
description=(
|
|
25
|
+
"Run the application with live reload. Saving a source file updates "
|
|
26
|
+
"the running app. A module-level `with bs.App()` reloads in place "
|
|
27
|
+
"(state preserved); anything else falls back to a process restart."
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"path",
|
|
32
|
+
nargs="?",
|
|
33
|
+
default=None,
|
|
34
|
+
help="Path to entry point or directory with bootstack.toml (default: current directory)",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--restart",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Always reload by restarting the process, even for a module-level app",
|
|
40
|
+
)
|
|
41
|
+
parser.set_defaults(func=run_dev)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_dev(args: argparse.Namespace) -> None:
|
|
45
|
+
"""Execute the dev command."""
|
|
46
|
+
resolved = _resolve_entry(args.path)
|
|
47
|
+
if resolved is None:
|
|
48
|
+
return
|
|
49
|
+
entry_point, project_root = resolved
|
|
50
|
+
|
|
51
|
+
if not entry_point.exists():
|
|
52
|
+
print(f"Error: Entry point '{entry_point}' does not exist.")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
display_path = entry_point.relative_to(project_root)
|
|
57
|
+
except ValueError:
|
|
58
|
+
display_path = entry_point
|
|
59
|
+
print(f"Starting {display_path} with hot reload...")
|
|
60
|
+
print("(Save a file to reload. Press Ctrl+C to stop.)")
|
|
61
|
+
print()
|
|
62
|
+
|
|
63
|
+
env = dict(os.environ)
|
|
64
|
+
env["BOOTSTACK_DEV"] = "1"
|
|
65
|
+
env["BOOTSTACK_DEV_MODE"] = "restart" if args.restart else "inprocess"
|
|
66
|
+
|
|
67
|
+
src_dir = project_root / "src"
|
|
68
|
+
if src_dir.exists():
|
|
69
|
+
python_path = env.get("PYTHONPATH", "")
|
|
70
|
+
env["PYTHONPATH"] = (
|
|
71
|
+
f"{src_dir}{os.pathsep}{python_path}" if python_path else str(src_dir)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Supervise the app here rather than letting it os.execv itself on restart:
|
|
75
|
+
# on Windows execv does not replace the process in place — it spawns a new
|
|
76
|
+
# PID and terminates the caller, which would drop this supervisor after the
|
|
77
|
+
# first reload and orphan the app. The child re-launches by exiting with
|
|
78
|
+
# DEV_RESTART_EXIT_CODE; looping subprocess.run is portable and re-establishes
|
|
79
|
+
# cwd + env on every relaunch.
|
|
80
|
+
from bootstack.dev._env import DEV_RESTART_EXIT_CODE
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
while True:
|
|
84
|
+
result = subprocess.run(
|
|
85
|
+
[sys.executable, str(entry_point.resolve())],
|
|
86
|
+
cwd=str(project_root),
|
|
87
|
+
env=env,
|
|
88
|
+
)
|
|
89
|
+
if result.returncode == DEV_RESTART_EXIT_CODE:
|
|
90
|
+
continue # a valid edit asked for a clean restart
|
|
91
|
+
if result.returncode == 0:
|
|
92
|
+
sys.exit(0) # the app window was closed normally
|
|
93
|
+
# The app crashed (e.g. an edit raised at import). Don't end the
|
|
94
|
+
# session — stay alive and relaunch when the file is fixed and
|
|
95
|
+
# saved. This is the restart-mode counterpart to the in-process
|
|
96
|
+
# error banner's fix-and-save recovery; the child's own watcher
|
|
97
|
+
# died with it, so the supervisor watches in the meantime.
|
|
98
|
+
print(
|
|
99
|
+
f"\n[bootstack dev] app exited with code {result.returncode} - "
|
|
100
|
+
"waiting for a change to retry (Ctrl+C to stop)...",
|
|
101
|
+
flush=True,
|
|
102
|
+
)
|
|
103
|
+
_wait_for_change(str(project_root))
|
|
104
|
+
except KeyboardInterrupt:
|
|
105
|
+
print("\nStopped.")
|
|
106
|
+
sys.exit(0)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _wait_for_change(root: str) -> None:
|
|
110
|
+
"""Block until a watched source file under `root` changes (or Ctrl+C).
|
|
111
|
+
|
|
112
|
+
Used by the restart supervisor to stay alive after the app crashes: rather
|
|
113
|
+
than ending the dev session, it waits for the next save and lets the loop
|
|
114
|
+
relaunch.
|
|
115
|
+
"""
|
|
116
|
+
from bootstack.dev._watcher import PollWatcher
|
|
117
|
+
|
|
118
|
+
changed = threading.Event()
|
|
119
|
+
watcher = PollWatcher(root, lambda _paths: changed.set())
|
|
120
|
+
watcher.start()
|
|
121
|
+
try:
|
|
122
|
+
# Short timed waits keep Ctrl+C responsive on Windows, where a bare
|
|
123
|
+
# Event.wait() with no timeout can swallow the interrupt.
|
|
124
|
+
while not changed.wait(0.3):
|
|
125
|
+
pass
|
|
126
|
+
finally:
|
|
127
|
+
watcher.stop()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _resolve_entry(path: str | None) -> tuple[Path, Path] | None:
|
|
131
|
+
"""Resolve (entry_point, project_root) from a path or bootstack.toml."""
|
|
132
|
+
if path:
|
|
133
|
+
p = Path(path)
|
|
134
|
+
if p.is_file() and p.suffix == ".py":
|
|
135
|
+
return p, p.parent
|
|
136
|
+
if p.is_dir():
|
|
137
|
+
config_path = p / "bootstack.toml"
|
|
138
|
+
if not config_path.exists():
|
|
139
|
+
print(f"Error: No bootstack.toml found in '{p}'")
|
|
140
|
+
return None
|
|
141
|
+
config = TtkbConfig.load(config_path)
|
|
142
|
+
return p / config.app.entry, p
|
|
143
|
+
print(f"Error: '{path}' is not a valid file or directory")
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
config_path = find_config()
|
|
147
|
+
if config_path is None:
|
|
148
|
+
print("Error: No bootstack.toml found in current directory or parents.")
|
|
149
|
+
print("Pass a path: bootstack dev app.py")
|
|
150
|
+
return None
|
|
151
|
+
config = TtkbConfig.load(config_path)
|
|
152
|
+
project_root = config_path.parent
|
|
153
|
+
return project_root / config.app.entry, project_root
|
bootstack/cli/doctor.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""bootstack doctor command - Validate project structure and environment."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import platform
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from bootstack.cli.config import TtkbConfig, find_config
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_OK = " [OK] "
|
|
14
|
+
_WARN = " [WARN] "
|
|
15
|
+
_FAIL = " [FAIL] "
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def add_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
19
|
+
"""Add the 'doctor' subcommand parser."""
|
|
20
|
+
parser = subparsers.add_parser(
|
|
21
|
+
"doctor",
|
|
22
|
+
help="Diagnose project and environment health",
|
|
23
|
+
description=(
|
|
24
|
+
"Validate bootstack.toml, check the project layout, and report "
|
|
25
|
+
"environment versions relevant to building and running."
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
parser.set_defaults(func=run_doctor)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_doctor(args: argparse.Namespace) -> None:
|
|
32
|
+
"""Run the diagnostic checks."""
|
|
33
|
+
failures = 0
|
|
34
|
+
warnings = 0
|
|
35
|
+
|
|
36
|
+
# ---- Environment -------------------------------------------------------
|
|
37
|
+
print("Environment:")
|
|
38
|
+
print(f"{_OK}Python {platform.python_version()} ({sys.executable})")
|
|
39
|
+
|
|
40
|
+
tk_version, tk_error = _probe_tk()
|
|
41
|
+
if tk_version:
|
|
42
|
+
print(f"{_OK}Tcl/Tk {tk_version}")
|
|
43
|
+
else:
|
|
44
|
+
print(f"{_FAIL}Tcl/Tk: {tk_error}")
|
|
45
|
+
failures += 1
|
|
46
|
+
|
|
47
|
+
ttkb_version = _probe_bootstack()
|
|
48
|
+
print(f"{_OK}bootstack {ttkb_version}")
|
|
49
|
+
|
|
50
|
+
print()
|
|
51
|
+
|
|
52
|
+
# ---- Project -----------------------------------------------------------
|
|
53
|
+
config_path = find_config()
|
|
54
|
+
if config_path is None:
|
|
55
|
+
print("Project:")
|
|
56
|
+
print(f"{_WARN}No bootstack.toml found in current directory or parents.")
|
|
57
|
+
print(" (Run 'bootstack start <name>' to create a project, or 'cd' into one.)")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
project_root = config_path.parent
|
|
61
|
+
print(f"Project: {project_root}")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
config = TtkbConfig.load(config_path)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
print(f"{_FAIL}bootstack.toml failed to parse: {e}")
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
print(f"{_OK}bootstack.toml parses ([app] name={config.app.name!r}, template={config.app.template!r})")
|
|
69
|
+
|
|
70
|
+
# Entry point exists
|
|
71
|
+
entry_path = project_root / config.app.entry
|
|
72
|
+
if entry_path.exists():
|
|
73
|
+
print(f"{_OK}entry point: {config.app.entry}")
|
|
74
|
+
else:
|
|
75
|
+
print(f"{_FAIL}entry point missing: {config.app.entry}")
|
|
76
|
+
failures += 1
|
|
77
|
+
|
|
78
|
+
# Layout matches template
|
|
79
|
+
if entry_path.parts and "src" in entry_path.parts:
|
|
80
|
+
idx = list(entry_path.parts).index("src")
|
|
81
|
+
if idx + 1 < len(entry_path.parts):
|
|
82
|
+
module_dir = project_root.joinpath(*entry_path.parts[: idx + 2])
|
|
83
|
+
failures += _check_template_layout(config.app.template, module_dir)
|
|
84
|
+
else:
|
|
85
|
+
# Non-src layout: skip the views/pages check
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
# Packaging — always reported. If [build] exists we run the full check;
|
|
89
|
+
# otherwise we still flag a missing PyInstaller as a heads-up so users
|
|
90
|
+
# discover it before they reach 'bootstack build'.
|
|
91
|
+
if config.build is not None:
|
|
92
|
+
warnings += _check_build(config, project_root)
|
|
93
|
+
else:
|
|
94
|
+
warnings += _check_packaging_readiness()
|
|
95
|
+
|
|
96
|
+
# ---- Summary -----------------------------------------------------------
|
|
97
|
+
print()
|
|
98
|
+
if failures:
|
|
99
|
+
print(f"{failures} failure(s), {warnings} warning(s).")
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
if warnings:
|
|
102
|
+
print(f"All checks passed with {warnings} warning(s).")
|
|
103
|
+
else:
|
|
104
|
+
print("All checks passed.")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _check_template_layout(template: str, module_dir: Path) -> int:
|
|
108
|
+
"""Verify the project's component directory matches its template type.
|
|
109
|
+
|
|
110
|
+
Returns the number of failures recorded.
|
|
111
|
+
"""
|
|
112
|
+
expected = "pages" if template == "appshell" else "views"
|
|
113
|
+
other = "views" if template == "appshell" else "pages"
|
|
114
|
+
|
|
115
|
+
expected_dir = module_dir / expected
|
|
116
|
+
other_dir = module_dir / other
|
|
117
|
+
|
|
118
|
+
failures = 0
|
|
119
|
+
if expected_dir.exists():
|
|
120
|
+
print(f"{_OK}layout: found {expected}/ (matches template={template!r})")
|
|
121
|
+
else:
|
|
122
|
+
print(f"{_FAIL}layout: expected {expected_dir.relative_to(module_dir.parent.parent)}/ for template={template!r}")
|
|
123
|
+
failures += 1
|
|
124
|
+
|
|
125
|
+
if other_dir.exists():
|
|
126
|
+
print(f"{_WARN}layout: unexpected {other}/ directory for template={template!r}")
|
|
127
|
+
return failures
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _check_packaging_readiness() -> int:
|
|
131
|
+
"""Report packaging-tool availability for un-promoted projects.
|
|
132
|
+
|
|
133
|
+
Never fails — just warns if PyInstaller is missing so users know
|
|
134
|
+
they have an install ahead of them before 'bootstack build' will work.
|
|
135
|
+
Returns the number of warnings.
|
|
136
|
+
"""
|
|
137
|
+
print()
|
|
138
|
+
print("Packaging (project not yet promoted):")
|
|
139
|
+
try:
|
|
140
|
+
import PyInstaller # noqa: F401
|
|
141
|
+
print(f"{_OK}PyInstaller is available (run 'bootstack promote --pyinstaller' to enable builds)")
|
|
142
|
+
return 0
|
|
143
|
+
except ImportError:
|
|
144
|
+
print(f"{_WARN}PyInstaller is not installed")
|
|
145
|
+
print(" Install it before 'bootstack build': pip install pyinstaller")
|
|
146
|
+
return 1
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _check_build(config: TtkbConfig, project_root: Path) -> int:
|
|
150
|
+
"""Verify build-time prerequisites. Returns the number of warnings."""
|
|
151
|
+
warnings = 0
|
|
152
|
+
backend = config.build.backend if config.build else None
|
|
153
|
+
print()
|
|
154
|
+
print(f"Build (backend={backend!r}):")
|
|
155
|
+
|
|
156
|
+
if backend == "pyinstaller":
|
|
157
|
+
spec = project_root / "build" / "pyinstaller" / "app.spec"
|
|
158
|
+
if spec.exists():
|
|
159
|
+
print(f"{_OK}spec file: {spec.relative_to(project_root)}")
|
|
160
|
+
else:
|
|
161
|
+
print(f"{_WARN}spec file missing — run 'bootstack promote --pyinstaller --force'")
|
|
162
|
+
warnings += 1
|
|
163
|
+
try:
|
|
164
|
+
import PyInstaller # noqa: F401
|
|
165
|
+
print(f"{_OK}PyInstaller is importable")
|
|
166
|
+
except ImportError:
|
|
167
|
+
print(f"{_WARN}PyInstaller is not installed (pip install pyinstaller)")
|
|
168
|
+
warnings += 1
|
|
169
|
+
else:
|
|
170
|
+
print(f"{_WARN}unknown build backend: {backend!r}")
|
|
171
|
+
warnings += 1
|
|
172
|
+
|
|
173
|
+
return warnings
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _probe_tk() -> tuple[str | None, str | None]:
|
|
177
|
+
"""Return (version, None) on success, (None, error_message) on failure.
|
|
178
|
+
|
|
179
|
+
Uses `_tkinter` module constants directly so the probe does not have
|
|
180
|
+
to instantiate a Tk interpreter (bootstack monkey-patches
|
|
181
|
+
`Tk.__init__` to require an App, which would trip the probe).
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
import _tkinter
|
|
185
|
+
return f"{_tkinter.TCL_VERSION} / Tk {_tkinter.TK_VERSION}", None
|
|
186
|
+
except Exception as e:
|
|
187
|
+
return None, str(e)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _probe_bootstack() -> str:
|
|
191
|
+
try:
|
|
192
|
+
import bootstack
|
|
193
|
+
return getattr(bootstack, "__version__", "(unknown)")
|
|
194
|
+
except Exception:
|
|
195
|
+
return "(import failed)"
|
bootstack/cli/icons.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Icons command — launches the Bootstrap Icons browser."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def add_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
8
|
+
parser = subparsers.add_parser(
|
|
9
|
+
"icons",
|
|
10
|
+
help="Browse Bootstrap Icons available in bootstack",
|
|
11
|
+
description=(
|
|
12
|
+
"Launch the Bootstrap Icons browser.\n\n"
|
|
13
|
+
"Displays all Bootstrap Icons bundled with bootstack. "
|
|
14
|
+
"Click any icon to copy its name to the clipboard — the "
|
|
15
|
+
"copied name works directly as icon= on any bootstack widget."
|
|
16
|
+
),
|
|
17
|
+
)
|
|
18
|
+
parser.set_defaults(func=lambda args: run_icons())
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_DEFAULT_STATUS = (
|
|
22
|
+
'Click an icon to copy its name · use as icon="name" on any widget'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_icons() -> None:
|
|
27
|
+
import bootstack as bs
|
|
28
|
+
from bootstack.clipboard import set_clipboard
|
|
29
|
+
from bootstack.data import MemoryDataSource, col
|
|
30
|
+
from bootstack.images import get_icon, list_icons
|
|
31
|
+
|
|
32
|
+
names = list_icons()
|
|
33
|
+
|
|
34
|
+
with bs.App(title="Bootstrap Icons", size=(990, 720), gap=0) as app:
|
|
35
|
+
# Each record carries a theme-following Image. Gallery recycles its tiles,
|
|
36
|
+
# so the thumbnails render lazily as they scroll into view and recolor
|
|
37
|
+
# with the theme on their own — no manual batching or repaint needed.
|
|
38
|
+
records = [
|
|
39
|
+
{"id": i, "name": name, "icon": get_icon(name, size=32)}
|
|
40
|
+
for i, name in enumerate(names)
|
|
41
|
+
]
|
|
42
|
+
source = MemoryDataSource()
|
|
43
|
+
source.load(records)
|
|
44
|
+
|
|
45
|
+
count = bs.Signal("")
|
|
46
|
+
status = bs.Signal(_DEFAULT_STATUS)
|
|
47
|
+
|
|
48
|
+
def update_count() -> None:
|
|
49
|
+
count.set(f"{source.count:,} of {len(names):,} icons")
|
|
50
|
+
|
|
51
|
+
with bs.Row(padding=(12, 8), gap=10, horizontal="stretch", vertical_items="center"):
|
|
52
|
+
search = bs.TextField(placeholder="Search icons", grow=True)
|
|
53
|
+
search.insert_addon("label", "before", icon="search")
|
|
54
|
+
bs.Label(textsignal=count, font="caption", accent="muted")
|
|
55
|
+
|
|
56
|
+
bs.Divider()
|
|
57
|
+
|
|
58
|
+
gallery = bs.Gallery(
|
|
59
|
+
data_source=source,
|
|
60
|
+
image_field="icon",
|
|
61
|
+
caption_field="name",
|
|
62
|
+
columns="auto",
|
|
63
|
+
tile_size=(72, 40),
|
|
64
|
+
fit="none", # show the 32px glyph at native size — crisp, no upscale
|
|
65
|
+
gap=6,
|
|
66
|
+
selection_mode="single",
|
|
67
|
+
grow=True,
|
|
68
|
+
horizontal="stretch",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
bs.Divider()
|
|
72
|
+
|
|
73
|
+
with bs.Row(padding=(12, 4), horizontal="stretch", surface="chrome"):
|
|
74
|
+
bs.Label(textsignal=status, font="caption", accent="muted")
|
|
75
|
+
|
|
76
|
+
def on_search(event) -> None:
|
|
77
|
+
query = event.text.strip().lower()
|
|
78
|
+
source.where(col("name").contains(query) if query else None)
|
|
79
|
+
gallery.reload()
|
|
80
|
+
update_count()
|
|
81
|
+
|
|
82
|
+
# Live, as-you-type filtering — debounced so a fast typist isn't reloaded
|
|
83
|
+
# on every keystroke.
|
|
84
|
+
search.on_input().debounce(120).listen(on_search)
|
|
85
|
+
|
|
86
|
+
def on_pick(record: dict) -> None:
|
|
87
|
+
name = record["name"]
|
|
88
|
+
set_clipboard(name)
|
|
89
|
+
status.set(f'Copied "{name}" — ready to paste as icon="{name}"')
|
|
90
|
+
|
|
91
|
+
gallery.on_item_click(on_pick)
|
|
92
|
+
|
|
93
|
+
update_count()
|
|
94
|
+
|
|
95
|
+
app.run()
|
|
96
|
+
|
|
97
|
+
if __name__ == '__main__':
|
|
98
|
+
run_icons()
|
bootstack/cli/promote.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""bootstack promote command - Upgrade project to packaging-ready."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from bootstack.cli.config import (
|
|
9
|
+
BUILD_CONFIG_TEMPLATE,
|
|
10
|
+
TtkbConfig,
|
|
11
|
+
find_config,
|
|
12
|
+
)
|
|
13
|
+
from bootstack.cli.pyinstaller import generate_spec
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
17
|
+
"""Add the 'promote' subcommand parser."""
|
|
18
|
+
parser = subparsers.add_parser(
|
|
19
|
+
"promote",
|
|
20
|
+
help="Upgrade project to packaging-ready",
|
|
21
|
+
description="Add build configuration and generate build files for distribution.",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--pyinstaller",
|
|
25
|
+
action="store_true",
|
|
26
|
+
help="Enable PyInstaller build support",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--force",
|
|
30
|
+
action="store_true",
|
|
31
|
+
help="Overwrite existing build configuration",
|
|
32
|
+
)
|
|
33
|
+
parser.set_defaults(func=run_promote)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run_promote(args: argparse.Namespace) -> None:
|
|
37
|
+
"""Execute the promote command."""
|
|
38
|
+
if not args.pyinstaller:
|
|
39
|
+
print("Error: Please specify a build backend.")
|
|
40
|
+
print(" bootstack promote --pyinstaller")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Find project root
|
|
44
|
+
config_path = find_config()
|
|
45
|
+
if config_path is None:
|
|
46
|
+
print("Error: No bootstack.toml found in current directory or parents.")
|
|
47
|
+
print("Run 'bootstack start <appname>' to create a new project first.")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
project_root = config_path.parent
|
|
51
|
+
config = TtkbConfig.load(config_path)
|
|
52
|
+
|
|
53
|
+
# Check if already promoted
|
|
54
|
+
if config.build is not None and not args.force:
|
|
55
|
+
print("Project already has build configuration.")
|
|
56
|
+
print("Use --force to overwrite.")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
print(f"Promoting project '{config.app.name}' for PyInstaller...")
|
|
60
|
+
|
|
61
|
+
# Update bootstack.toml with build section
|
|
62
|
+
_add_build_section(config_path)
|
|
63
|
+
|
|
64
|
+
# Create build directory
|
|
65
|
+
build_dir = project_root / "build" / "pyinstaller"
|
|
66
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
|
|
68
|
+
# Reload config to get build settings
|
|
69
|
+
config = TtkbConfig.load(config_path)
|
|
70
|
+
|
|
71
|
+
# Generate .spec file
|
|
72
|
+
spec_path = build_dir / "app.spec"
|
|
73
|
+
generate_spec(config, project_root, spec_path)
|
|
74
|
+
|
|
75
|
+
print()
|
|
76
|
+
print("Project promoted successfully!")
|
|
77
|
+
print()
|
|
78
|
+
print("Generated files:")
|
|
79
|
+
print(f" - {spec_path.relative_to(project_root)}")
|
|
80
|
+
print()
|
|
81
|
+
print("Updated:")
|
|
82
|
+
print(f" - bootstack.toml (added [build] section)")
|
|
83
|
+
print()
|
|
84
|
+
|
|
85
|
+
# Warn early if PyInstaller isn't on this interpreter — `bootstack build`
|
|
86
|
+
# needs it but the promote step itself only writes config.
|
|
87
|
+
try:
|
|
88
|
+
import PyInstaller # noqa: F401
|
|
89
|
+
except ImportError:
|
|
90
|
+
print("Note: PyInstaller is not installed in this environment.")
|
|
91
|
+
print(" Install it before running 'bootstack build':")
|
|
92
|
+
print(" pip install pyinstaller")
|
|
93
|
+
print()
|
|
94
|
+
|
|
95
|
+
print("Next steps:")
|
|
96
|
+
print(" 1. (Optional) Edit bootstack.toml [build] section")
|
|
97
|
+
print(" 2. Run 'bootstack build' to create executable")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _add_build_section(config_path: Path) -> None:
|
|
101
|
+
"""Add [build] section to existing bootstack.toml."""
|
|
102
|
+
content = config_path.read_text(encoding="utf-8")
|
|
103
|
+
|
|
104
|
+
# Check if [build] section already exists
|
|
105
|
+
if "[build]" in content:
|
|
106
|
+
# Remove existing build section and everything after
|
|
107
|
+
lines = content.split("\n")
|
|
108
|
+
new_lines = []
|
|
109
|
+
skip = False
|
|
110
|
+
for line in lines:
|
|
111
|
+
if line.strip().startswith("[build"):
|
|
112
|
+
skip = True
|
|
113
|
+
if not skip:
|
|
114
|
+
new_lines.append(line)
|
|
115
|
+
content = "\n".join(new_lines).rstrip() + "\n"
|
|
116
|
+
|
|
117
|
+
# Append build section
|
|
118
|
+
content += BUILD_CONFIG_TEMPLATE
|
|
119
|
+
|
|
120
|
+
config_path.write_text(content, encoding="utf-8")
|