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,47 @@
|
|
|
1
|
+
"""Internal dialog implementations.
|
|
2
|
+
|
|
3
|
+
The Bootstrap-styled dialog building blocks behind the public dialog API. The
|
|
4
|
+
curated public surface — the `alert` / `confirm` / `ask_*` verbs and the
|
|
5
|
+
`Dialog` / `FormDialog` / `FontDialog` / `ColorChooserDialog` /
|
|
6
|
+
`FilterDialog` classes — lives in `bootstack.dialogs`; import from there, not
|
|
7
|
+
from this package.
|
|
8
|
+
|
|
9
|
+
Modules here:
|
|
10
|
+
|
|
11
|
+
- `message` (MessageBox/MessageDialog): info/warning/error/question prompts
|
|
12
|
+
- `query` (QueryBox/QueryDialog): collect input (string, number, date, item, …)
|
|
13
|
+
- `colorchooser`: color picker
|
|
14
|
+
- `datedialog`: calendar date picker
|
|
15
|
+
- `fontdialog`: font selection
|
|
16
|
+
- `filterdialog`: multi-select filter dialog with search
|
|
17
|
+
- `dialog`: the base `Dialog` / `DialogButton`
|
|
18
|
+
"""
|
|
19
|
+
from .colorchooser import (
|
|
20
|
+
ColorChooser,
|
|
21
|
+
ColorChooserDialog,
|
|
22
|
+
)
|
|
23
|
+
from .dialog import Dialog, DialogButton
|
|
24
|
+
from .filterdialog import FilterDialog
|
|
25
|
+
from .formdialog import FormDialog
|
|
26
|
+
from .message import MessageDialog, MessageBox
|
|
27
|
+
from .query import QueryDialog, QueryBox
|
|
28
|
+
from .datedialog import DateDialog
|
|
29
|
+
from .fontdialog import FontDialog
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
# Base / core dialogs
|
|
33
|
+
"Dialog",
|
|
34
|
+
"DialogButton",
|
|
35
|
+
"FilterDialog",
|
|
36
|
+
"FormDialog",
|
|
37
|
+
"MessageDialog",
|
|
38
|
+
"QueryDialog",
|
|
39
|
+
"DateDialog",
|
|
40
|
+
"FontDialog",
|
|
41
|
+
# Facades
|
|
42
|
+
"MessageBox",
|
|
43
|
+
"QueryBox",
|
|
44
|
+
# Color tools
|
|
45
|
+
"ColorChooser",
|
|
46
|
+
"ColorChooserDialog",
|
|
47
|
+
]
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
"""Color chooser widget for bootstack dialogs.
|
|
2
|
+
|
|
3
|
+
This module provides a comprehensive color selection widget with multiple
|
|
4
|
+
selection methods including RGB sliders, HSL controls, standard color palette,
|
|
5
|
+
and hex input.
|
|
6
|
+
"""
|
|
7
|
+
import tkinter
|
|
8
|
+
from collections import namedtuple
|
|
9
|
+
from tkinter import Canvas, IntVar, StringVar
|
|
10
|
+
from types import SimpleNamespace
|
|
11
|
+
from typing import Any, Callable, List, NamedTuple, Optional, Tuple
|
|
12
|
+
|
|
13
|
+
from PIL import ImageColor
|
|
14
|
+
|
|
15
|
+
from bootstack.constants import *
|
|
16
|
+
from bootstack._core import colorutils
|
|
17
|
+
from bootstack._core.colorutils import HEX, HSL, HUE, LUM, RGB, SAT
|
|
18
|
+
from bootstack.dialogs._impl.dialog import Dialog
|
|
19
|
+
from bootstack.i18n import MessageCatalog
|
|
20
|
+
from bootstack._runtime import utility
|
|
21
|
+
from bootstack.style.style import get_style, get_theme_color
|
|
22
|
+
from bootstack.widgets._impl.composites.tabs.tabview import TabView
|
|
23
|
+
from bootstack.widgets._impl.primitives import Button, Entry, Frame, Label, Spinbox
|
|
24
|
+
|
|
25
|
+
# Theme-color families shown on the "Themed" tab, in display order. Each is a
|
|
26
|
+
# semantic color the active theme exposes as a full light-to-dark band; `gray`
|
|
27
|
+
# is the neutral ramp for surface/text picks. (`secondary` is intentionally
|
|
28
|
+
# omitted — it is a flat token with no band.)
|
|
29
|
+
THEMED_FAMILIES: Tuple[str, ...] = (
|
|
30
|
+
"primary", "success", "info", "warning", "danger", "gray",
|
|
31
|
+
)
|
|
32
|
+
# Band stops sampled per family, light to dark — rendered as one gradient row.
|
|
33
|
+
THEMED_SHADES: Tuple[int, ...] = (
|
|
34
|
+
50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
ttk = SimpleNamespace(
|
|
38
|
+
Button=Button,
|
|
39
|
+
Canvas=Canvas,
|
|
40
|
+
Entry=Entry,
|
|
41
|
+
Frame=Frame,
|
|
42
|
+
IntVar=IntVar,
|
|
43
|
+
Label=Label,
|
|
44
|
+
Spinbox=Spinbox,
|
|
45
|
+
StringVar=StringVar,
|
|
46
|
+
use_style=get_style,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
ColorValues = namedtuple('ColorValues', 'h s l r g b hex')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ColorChoice(NamedTuple):
|
|
53
|
+
"""A color selected from `ColorChooserDialog` or `ask_color`."""
|
|
54
|
+
|
|
55
|
+
rgb: Tuple[int, int, int]
|
|
56
|
+
"""The color as an `(r, g, b)` tuple, each component 0–255."""
|
|
57
|
+
hsl: Tuple[int, int, int]
|
|
58
|
+
"""The color as an `(h, s, l)` tuple — hue 0–360, saturation and luminance 0–100."""
|
|
59
|
+
hex: str
|
|
60
|
+
"""The color as a lowercase hex string, e.g. `'#ff0000'`."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ColorChooser(ttk.Frame):
|
|
64
|
+
"""Color chooser widget — hue/saturation spectrum with luminance slider."""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self, master: Optional[tkinter.Misc], initial_color: Optional[str] = None,
|
|
68
|
+
padding: Optional[int] = None) -> None:
|
|
69
|
+
"""Create a color chooser widget.
|
|
70
|
+
|
|
71
|
+
Shows a hue/saturation spectrum with a luminance slider, RGB/HSL/hex
|
|
72
|
+
numeric inputs, and a live color preview.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
master: Parent widget.
|
|
76
|
+
initial_color: Initial color string; defaults to theme background.
|
|
77
|
+
padding: Padding around the chooser.
|
|
78
|
+
"""
|
|
79
|
+
super().__init__(master, padding=padding)
|
|
80
|
+
self.tframe = ttk.Frame(self, padding=5)
|
|
81
|
+
self.tframe.pack(fill=X)
|
|
82
|
+
self.bframe = ttk.Frame(self, padding=(5, 0, 5, 5))
|
|
83
|
+
self.bframe.pack(fill=X)
|
|
84
|
+
|
|
85
|
+
self.notebook = TabView(self.tframe)
|
|
86
|
+
self.notebook.pack(fill=BOTH)
|
|
87
|
+
|
|
88
|
+
self.style = ttk.use_style()
|
|
89
|
+
self.colors = self.style.colors
|
|
90
|
+
fallback_bg = (
|
|
91
|
+
self.colors.get("bg")
|
|
92
|
+
if isinstance(self.colors, dict)
|
|
93
|
+
else getattr(self.colors, "bg", None)
|
|
94
|
+
) or "#ffffff"
|
|
95
|
+
self.initial_color = initial_color or fallback_bg
|
|
96
|
+
|
|
97
|
+
# color variables
|
|
98
|
+
r, g, b = ImageColor.getrgb(self.initial_color)
|
|
99
|
+
h, s, l = colorutils.color_to_hsl((r, g, b), RGB)
|
|
100
|
+
hx = colorutils.color_to_hex((r, g, b), RGB)
|
|
101
|
+
|
|
102
|
+
self.hue = ttk.IntVar(value=h)
|
|
103
|
+
self.sat = ttk.IntVar(value=s)
|
|
104
|
+
self.lum = ttk.IntVar(value=l)
|
|
105
|
+
self.red = ttk.IntVar(value=r)
|
|
106
|
+
self.grn = ttk.IntVar(value=g)
|
|
107
|
+
self.blu = ttk.IntVar(value=b)
|
|
108
|
+
self.hex = ttk.StringVar(value=hx)
|
|
109
|
+
|
|
110
|
+
# widget sizes (adjusted by widget scaling)
|
|
111
|
+
self.spectrum_height = utility.scale_size(self, 240)
|
|
112
|
+
self.spectrum_width = utility.scale_size(self, 530)
|
|
113
|
+
self.spectrum_point = utility.scale_size(self, 12)
|
|
114
|
+
|
|
115
|
+
# build widgets — the themed swatches and the spectrum each get a tab
|
|
116
|
+
# (themed first); the luminance scale, preview, and numeric inputs stay
|
|
117
|
+
# shared below both tabs (so switching tabs doesn't shift the layout).
|
|
118
|
+
themed_page = self.notebook.add('themed', text='color.themed')
|
|
119
|
+
self.themed_swatches = self.create_swatches(themed_page)
|
|
120
|
+
self.themed_swatches.pack(fill=BOTH, expand=YES)
|
|
121
|
+
|
|
122
|
+
custom_page = self.notebook.add('custom', text='color.custom')
|
|
123
|
+
self.color_spectrum = self.create_spectrum(custom_page)
|
|
124
|
+
self.color_spectrum.pack(fill=X, side=TOP)
|
|
125
|
+
|
|
126
|
+
self.luminance_scale = self.create_luminance_scale(self.tframe)
|
|
127
|
+
self.luminance_scale.pack(fill=X)
|
|
128
|
+
|
|
129
|
+
preview_frame = self.create_preview(self.bframe)
|
|
130
|
+
preview_frame.pack(side=LEFT, fill=BOTH, expand=YES, padx=(0, 5))
|
|
131
|
+
self.color_entries = self.create_value_inputs(self.bframe)
|
|
132
|
+
self.color_entries.pack(side=RIGHT)
|
|
133
|
+
|
|
134
|
+
self.create_spectrum_indicator()
|
|
135
|
+
self.create_luminance_indicator()
|
|
136
|
+
|
|
137
|
+
def create_spectrum(self, master: tkinter.Misc) -> ttk.Canvas:
|
|
138
|
+
"""Create the color spectrum canvas"""
|
|
139
|
+
# canvas and point dimensions
|
|
140
|
+
width = self.spectrum_width
|
|
141
|
+
height = self.spectrum_height
|
|
142
|
+
xf = yf = self.spectrum_point
|
|
143
|
+
|
|
144
|
+
# create canvas widget and binding
|
|
145
|
+
canvas = ttk.Canvas(master, width=width, height=height, cursor='tcross')
|
|
146
|
+
canvas.bind("<B1-Motion>", self.on_spectrum_interaction, add="+")
|
|
147
|
+
canvas.bind("<Button-1>", self.on_spectrum_interaction, add="+")
|
|
148
|
+
|
|
149
|
+
# add color points
|
|
150
|
+
for x, colorx in enumerate(range(0, width, xf)):
|
|
151
|
+
for y, colory in enumerate(range(0, height, yf)):
|
|
152
|
+
values = self.color_from_coords(colorx, colory)
|
|
153
|
+
fill = values.hex
|
|
154
|
+
bbox = [x * xf, y * yf, (x * xf) + xf, (y * yf) + yf]
|
|
155
|
+
canvas.create_rectangle(*bbox, fill=fill, width=0)
|
|
156
|
+
return canvas
|
|
157
|
+
|
|
158
|
+
def create_spectrum_indicator(self) -> None:
|
|
159
|
+
"""Create a square indicator that displays in the position of
|
|
160
|
+
the selected color"""
|
|
161
|
+
s = utility.scale_size(self, 10)
|
|
162
|
+
width = utility.scale_size(self, 2)
|
|
163
|
+
values = self.get_variables()
|
|
164
|
+
x1, y1 = self.coords_from_color(values.hex)
|
|
165
|
+
tag = ['spectrum-indicator']
|
|
166
|
+
self.color_spectrum.create_rectangle(
|
|
167
|
+
x1, y1, x1 + s, y1 + s, width=width, tags=[tag])
|
|
168
|
+
self.color_spectrum.tag_lower('spectrum-indicator')
|
|
169
|
+
|
|
170
|
+
# widget builder methods
|
|
171
|
+
def create_swatches(self, master: tkinter.Misc) -> ttk.Frame:
|
|
172
|
+
"""Create a grid of the active theme's color bands.
|
|
173
|
+
|
|
174
|
+
Each row is one semantic color family rendered as a light-to-dark
|
|
175
|
+
gradient of its band stops; clicking any swatch loads that exact
|
|
176
|
+
on-theme color into the chooser.
|
|
177
|
+
"""
|
|
178
|
+
gap = utility.scale_size(self, 1)
|
|
179
|
+
n_cols = len(THEMED_SHADES)
|
|
180
|
+
n_rows = len(THEMED_FAMILIES)
|
|
181
|
+
|
|
182
|
+
# Distribute leftover pixels so the grid sums to EXACTLY the spectrum
|
|
183
|
+
# width/height — otherwise integer rounding leaves the themed page a few
|
|
184
|
+
# pixels off and switching tabs shifts the layout.
|
|
185
|
+
avail_w = self.spectrum_width - gap * (n_cols - 1)
|
|
186
|
+
base_w, extra_w = divmod(avail_w, n_cols)
|
|
187
|
+
col_widths = [base_w + (1 if j < extra_w else 0) for j in range(n_cols)]
|
|
188
|
+
avail_h = self.spectrum_height - gap * (n_rows - 1)
|
|
189
|
+
base_h, extra_h = divmod(avail_h, n_rows)
|
|
190
|
+
|
|
191
|
+
container = ttk.Frame(master)
|
|
192
|
+
for i, family in enumerate(THEMED_FAMILIES):
|
|
193
|
+
row_height = base_h + (1 if i < extra_h else 0)
|
|
194
|
+
row_frame = ttk.Frame(container)
|
|
195
|
+
for j, shade in enumerate(THEMED_SHADES):
|
|
196
|
+
token = f"{family}[{shade}]"
|
|
197
|
+
hexcolor = get_theme_color(token)
|
|
198
|
+
swatch = ttk.Frame(
|
|
199
|
+
row_frame,
|
|
200
|
+
surface=token,
|
|
201
|
+
width=col_widths[j],
|
|
202
|
+
height=row_height,
|
|
203
|
+
)
|
|
204
|
+
swatch.bind(
|
|
205
|
+
'<Button-1>',
|
|
206
|
+
lambda _e, hx=hexcolor: self.on_press_swatch(hx),
|
|
207
|
+
add="+",
|
|
208
|
+
)
|
|
209
|
+
# Left-pad only, so the inter-swatch gap is exactly `gap` (a
|
|
210
|
+
# symmetric padx would double it and widen the page).
|
|
211
|
+
swatch.pack(side=LEFT, padx=(gap if j > 0 else 0, 0))
|
|
212
|
+
row_frame.pack(fill=X, pady=(gap if i > 0 else 0, 0))
|
|
213
|
+
return container
|
|
214
|
+
|
|
215
|
+
def create_preview(self, master: tkinter.Misc) -> ttk.Frame:
|
|
216
|
+
"""Create the preview swatches for the original and new colors."""
|
|
217
|
+
container = ttk.Frame(master)
|
|
218
|
+
constrast_fg = colorutils.contrast_color(color=self.initial_color, model='hex')
|
|
219
|
+
|
|
220
|
+
# Each swatch is a single ttk.Label: unlike most ttk widgets, Label
|
|
221
|
+
# honors the background/foreground instance options directly, so the label
|
|
222
|
+
# itself is the color fill with its caption on top — no native tk widget
|
|
223
|
+
# and no autostyle coupling, and live updates stay an O(1) configure().
|
|
224
|
+
old = ttk.Label(
|
|
225
|
+
container,
|
|
226
|
+
text=MessageCatalog.translate("color.current"),
|
|
227
|
+
background=self.initial_color,
|
|
228
|
+
foreground=constrast_fg,
|
|
229
|
+
anchor=NW,
|
|
230
|
+
width=7,
|
|
231
|
+
padding=6,
|
|
232
|
+
)
|
|
233
|
+
old.pack(side=LEFT, fill=BOTH, expand=YES, padx=(0, 2))
|
|
234
|
+
|
|
235
|
+
self.preview = ttk.Label(
|
|
236
|
+
container,
|
|
237
|
+
text=MessageCatalog.translate("color.new"),
|
|
238
|
+
background=self.initial_color,
|
|
239
|
+
foreground=constrast_fg,
|
|
240
|
+
anchor=NW,
|
|
241
|
+
width=7,
|
|
242
|
+
padding=6,
|
|
243
|
+
)
|
|
244
|
+
self.preview.pack(side=LEFT, fill=BOTH, expand=YES, padx=(2, 0))
|
|
245
|
+
|
|
246
|
+
return container
|
|
247
|
+
|
|
248
|
+
def create_value_inputs(self, master: tkinter.Misc) -> ttk.Frame:
|
|
249
|
+
"""Create color value input widgets"""
|
|
250
|
+
container = ttk.Frame(master)
|
|
251
|
+
for x in range(4):
|
|
252
|
+
container.columnconfigure(x, weight=1)
|
|
253
|
+
|
|
254
|
+
# value labels - use semantic keys with built-in localization
|
|
255
|
+
lbl_cnf = {'master': container, 'anchor': E}
|
|
256
|
+
ttk.Label(**lbl_cnf, text='color.hue:').grid(row=0, column=0, sticky=E)
|
|
257
|
+
ttk.Label(**lbl_cnf, text='color.sat:').grid(row=1, column=0, sticky=E)
|
|
258
|
+
ttk.Label(**lbl_cnf, text='color.lum:').grid(row=2, column=0, sticky=E)
|
|
259
|
+
ttk.Label(**lbl_cnf, text='color.hex:').grid(row=3, column=0, sticky=E)
|
|
260
|
+
ttk.Label(**lbl_cnf, text='color.red:').grid(row=0, column=2, sticky=E)
|
|
261
|
+
ttk.Label(**lbl_cnf, text='color.green:').grid(row=1, column=2, sticky=E)
|
|
262
|
+
ttk.Label(**lbl_cnf, text='color.blue:').grid(row=2, column=2, sticky=E)
|
|
263
|
+
|
|
264
|
+
# value spinners and entry widgets
|
|
265
|
+
rgb_cnf = {'master': container, 'from_': 0, 'to': 255, 'width': 3}
|
|
266
|
+
sl_cnf = {'master': container, 'from_': 0, 'to': 100, 'width': 3}
|
|
267
|
+
hue_cnf = {'master': container, 'from_': 0, 'to': 360, 'width': 3}
|
|
268
|
+
sb_hue = ttk.Spinbox(**hue_cnf, textvariable=self.hue)
|
|
269
|
+
sb_hue.grid(row=0, column=1, padx=4, pady=2, sticky=EW)
|
|
270
|
+
sb_sat = ttk.Spinbox(**sl_cnf, textvariable=self.sat)
|
|
271
|
+
sb_sat.grid(row=1, column=1, padx=4, pady=2, sticky=EW)
|
|
272
|
+
sb_lum = ttk.Spinbox(**sl_cnf, textvariable=self.lum)
|
|
273
|
+
sb_lum.grid(row=2, column=1, padx=4, pady=2, sticky=EW)
|
|
274
|
+
sb_red = ttk.Spinbox(**rgb_cnf, textvariable=self.red)
|
|
275
|
+
sb_red.grid(row=0, column=3, padx=4, pady=2, sticky=EW)
|
|
276
|
+
sb_grn = ttk.Spinbox(**rgb_cnf, textvariable=self.grn)
|
|
277
|
+
sb_grn.grid(row=1, column=3, padx=4, pady=2, sticky=EW)
|
|
278
|
+
sb_blu = ttk.Spinbox(**rgb_cnf, textvariable=self.blu)
|
|
279
|
+
sb_blu.grid(row=2, column=3, padx=4, pady=2, sticky=EW)
|
|
280
|
+
ent_hex = ttk.Entry(container, textvariable=self.hex)
|
|
281
|
+
ent_hex.grid(row=3, column=1, padx=4, columnspan=3, pady=2, sticky=EW)
|
|
282
|
+
|
|
283
|
+
# event binding for updating colors on value change
|
|
284
|
+
for sb in [sb_hue, sb_sat, sb_lum]:
|
|
285
|
+
for sequence in ['<<Increment>>', '<<Decrement>>', '<Return>', '<KP_Enter>']:
|
|
286
|
+
sb.bind(
|
|
287
|
+
sequence=sequence,
|
|
288
|
+
func=lambda _, w=sb: self.on_entry_value_change(w, HSL),
|
|
289
|
+
add="+"
|
|
290
|
+
)
|
|
291
|
+
for sb in [sb_red, sb_grn, sb_blu]:
|
|
292
|
+
for sequence in ['<<Increment>>', '<<Decrement>>', '<Return>', '<KP_Enter>']:
|
|
293
|
+
sb.bind(
|
|
294
|
+
sequence=sequence,
|
|
295
|
+
func=lambda _, w=sb: self.on_entry_value_change(w, RGB),
|
|
296
|
+
add="+"
|
|
297
|
+
)
|
|
298
|
+
for sequence in ['<Return>', '<KP_Enter>']:
|
|
299
|
+
ent_hex.bind(
|
|
300
|
+
sequence=sequence,
|
|
301
|
+
func=lambda _, w=ent_hex: self.on_entry_value_change(w, HEX),
|
|
302
|
+
add="+"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return container
|
|
306
|
+
|
|
307
|
+
def create_luminance_scale(self, master: tkinter.Misc) -> ttk.Canvas:
|
|
308
|
+
"""Create the color luminance canvas"""
|
|
309
|
+
# widget dimensions
|
|
310
|
+
height = xf = self.spectrum_point
|
|
311
|
+
width = self.spectrum_width
|
|
312
|
+
|
|
313
|
+
values = self.get_variables()
|
|
314
|
+
canvas = ttk.Canvas(master, height=height, width=width)
|
|
315
|
+
|
|
316
|
+
# add color points to scale
|
|
317
|
+
for x, l in enumerate(range(0, width, xf)):
|
|
318
|
+
lum = l / width * LUM
|
|
319
|
+
fill = colorutils.update_hsl_value(
|
|
320
|
+
color=values.hex,
|
|
321
|
+
lum=lum,
|
|
322
|
+
inmodel='hex',
|
|
323
|
+
outmodel='hex'
|
|
324
|
+
)
|
|
325
|
+
bbox = [x * xf, 0, (x * xf) + xf, height]
|
|
326
|
+
tag = f'color{x}'
|
|
327
|
+
canvas.create_rectangle(*bbox, fill=fill, width=0, tags=[tag])
|
|
328
|
+
canvas.bind("<B1-Motion>", self.on_luminance_interaction, add="+")
|
|
329
|
+
canvas.bind("<Button-1>", self.on_luminance_interaction, add="+")
|
|
330
|
+
return canvas
|
|
331
|
+
|
|
332
|
+
def create_luminance_indicator(self) -> None:
|
|
333
|
+
"""Create an indicator that displays in the position of the
|
|
334
|
+
luminance value"""
|
|
335
|
+
lum = 50
|
|
336
|
+
x1 = int(lum / LUM * self.spectrum_width) - \
|
|
337
|
+
((self.spectrum_point - 2) // 2)
|
|
338
|
+
y1 = 0
|
|
339
|
+
x2 = x1 + self.spectrum_point
|
|
340
|
+
y2 = self.spectrum_point - 3
|
|
341
|
+
tag = 'luminance-indicator'
|
|
342
|
+
bbox = [x1, y1, x2, y2]
|
|
343
|
+
self.luminance_scale.create_rectangle(
|
|
344
|
+
*bbox, fill='white', outline='black', tags=[tag])
|
|
345
|
+
self.luminance_scale.tag_lower(tag)
|
|
346
|
+
|
|
347
|
+
def coords_from_color(self, hexcolor: str) -> Tuple[float, float]:
|
|
348
|
+
"""Get the coordinates on the color spectrum from the color value"""
|
|
349
|
+
h, s, _ = colorutils.color_to_hsl(hexcolor)
|
|
350
|
+
x = (h / HUE) * self.spectrum_width
|
|
351
|
+
y = (1 - (s / SAT)) * self.spectrum_height
|
|
352
|
+
return x, y
|
|
353
|
+
|
|
354
|
+
def color_from_coords(self, x: int, y: int):
|
|
355
|
+
"""Get the color value from the mouse position in the color spectrum"""
|
|
356
|
+
HEIGHT = self.spectrum_height
|
|
357
|
+
WIDTH = self.spectrum_width
|
|
358
|
+
h = int(min(HUE, max(0, (HUE / WIDTH) * x)))
|
|
359
|
+
s = int(min(SAT, max(0, SAT - ((SAT / HEIGHT) * y))))
|
|
360
|
+
l = 50
|
|
361
|
+
hx = colorutils.color_to_hex([h, s, l], 'hsl')
|
|
362
|
+
r, g, b = colorutils.color_to_rgb(hx)
|
|
363
|
+
return ColorValues(h, s, l, r, g, b, hx)
|
|
364
|
+
|
|
365
|
+
def set_variables(self, h: int, s: int, l: int, r: int, g: int, b: int, hx: str) -> None:
|
|
366
|
+
"""Update the color value variables"""
|
|
367
|
+
self.hue.set(h)
|
|
368
|
+
self.sat.set(s)
|
|
369
|
+
self.lum.set(l)
|
|
370
|
+
self.red.set(r)
|
|
371
|
+
self.grn.set(g)
|
|
372
|
+
self.blu.set(b)
|
|
373
|
+
self.hex.set(hx)
|
|
374
|
+
|
|
375
|
+
def get_variables(self):
|
|
376
|
+
"""Get the values of all color models and return a tuple of color values"""
|
|
377
|
+
h = self.hue.get()
|
|
378
|
+
s = self.sat.get()
|
|
379
|
+
l = self.lum.get()
|
|
380
|
+
r = self.red.get()
|
|
381
|
+
g = self.grn.get()
|
|
382
|
+
b = self.blu.get()
|
|
383
|
+
hx = self.hex.get()
|
|
384
|
+
return ColorValues(h, s, l, r, g, b, hx)
|
|
385
|
+
|
|
386
|
+
def update_preview(self) -> None:
|
|
387
|
+
"""Update the color in the preview frame"""
|
|
388
|
+
hx = self.hex.get()
|
|
389
|
+
fg = colorutils.contrast_color(color=hx, model='hex')
|
|
390
|
+
self.preview.configure(background=hx, foreground=fg)
|
|
391
|
+
|
|
392
|
+
def update_luminance_scale(self) -> None:
|
|
393
|
+
"""Update the luminance scale with the change in hue and saturation"""
|
|
394
|
+
values = self.get_variables()
|
|
395
|
+
width = self.spectrum_width
|
|
396
|
+
xf = self.spectrum_point
|
|
397
|
+
for x, l in enumerate(range(0, width, xf)):
|
|
398
|
+
lum = l / width * LUM
|
|
399
|
+
fill = colorutils.update_hsl_value(
|
|
400
|
+
color=values.hex,
|
|
401
|
+
lum=lum,
|
|
402
|
+
inmodel='hex',
|
|
403
|
+
outmodel='hex'
|
|
404
|
+
)
|
|
405
|
+
tag = f'color{x}'
|
|
406
|
+
self.luminance_scale.itemconfig(tag, fill=fill)
|
|
407
|
+
|
|
408
|
+
def update_luminance_indicator(self) -> None:
|
|
409
|
+
"""Update the position of the luminance indicator"""
|
|
410
|
+
lum = self.lum.get()
|
|
411
|
+
x = int(lum / LUM * self.spectrum_width) - \
|
|
412
|
+
((self.spectrum_point - 2) // 2)
|
|
413
|
+
self.luminance_scale.moveto('luminance-indicator', x, 0)
|
|
414
|
+
self.luminance_scale.tag_raise('luminance-indicator')
|
|
415
|
+
|
|
416
|
+
def update_spectrum_indicator(self) -> None:
|
|
417
|
+
"""Move the spectrum indicator to a new location"""
|
|
418
|
+
values = self.get_variables()
|
|
419
|
+
x, y = self.coords_from_color(values.hex)
|
|
420
|
+
self.color_spectrum.moveto('spectrum-indicator', x, y)
|
|
421
|
+
self.color_spectrum.tag_raise('spectrum-indicator')
|
|
422
|
+
color = colorutils.contrast_color(values.hex, 'hex')
|
|
423
|
+
self.color_spectrum.itemconfig('spectrum-indicator', outline=color)
|
|
424
|
+
|
|
425
|
+
# color events
|
|
426
|
+
def sync_color_values(self, model) -> None:
|
|
427
|
+
"""Sync all color model variables when one changes."""
|
|
428
|
+
values = self.get_variables()
|
|
429
|
+
if model == HEX:
|
|
430
|
+
hx = values.hex
|
|
431
|
+
r, g, b = colorutils.color_to_rgb(hx)
|
|
432
|
+
h, s, l = colorutils.color_to_hsl(hx)
|
|
433
|
+
elif model == RGB:
|
|
434
|
+
r, g, b = values.r, values.g, values.b
|
|
435
|
+
h, s, l = colorutils.color_to_hsl([r, g, b], 'rgb')
|
|
436
|
+
hx = colorutils.color_to_hex([r, g, b])
|
|
437
|
+
elif model == HSL:
|
|
438
|
+
h, s, l = values.h, values.s, values.l
|
|
439
|
+
r, g, b = colorutils.color_to_rgb([h, s, l], 'hsl')
|
|
440
|
+
hx = colorutils.color_to_hex([h, s, l], 'hsl')
|
|
441
|
+
self.set_variables(h, s, l, r, g, b, hx)
|
|
442
|
+
self.update_preview()
|
|
443
|
+
self.update_luminance_indicator()
|
|
444
|
+
|
|
445
|
+
def on_entry_value_change(self, widget: ttk.Spinbox, model: Any) -> None:
|
|
446
|
+
"""Update widget colors when a color value input changes"""
|
|
447
|
+
is_valid = widget.validate()
|
|
448
|
+
if is_valid:
|
|
449
|
+
self.sync_color_values(model)
|
|
450
|
+
self.update_luminance_scale()
|
|
451
|
+
self.update_spectrum_indicator()
|
|
452
|
+
|
|
453
|
+
def on_press_swatch(self, hexcolor: str) -> None:
|
|
454
|
+
"""Update widget colors when a themed swatch is clicked."""
|
|
455
|
+
self.hex.set(hexcolor)
|
|
456
|
+
self.sync_color_values(HEX)
|
|
457
|
+
self.update_luminance_scale()
|
|
458
|
+
self.update_spectrum_indicator()
|
|
459
|
+
|
|
460
|
+
def on_spectrum_interaction(self, event: tkinter.Event) -> None:
|
|
461
|
+
"""Update widget colors when the color spectrum canvas is pressed"""
|
|
462
|
+
values = self.color_from_coords(event.x, event.y)
|
|
463
|
+
self.hue.set(values.h)
|
|
464
|
+
self.sat.set(values.s)
|
|
465
|
+
self.lum.set(values.l)
|
|
466
|
+
self.sync_color_values(HSL)
|
|
467
|
+
self.update_luminance_scale()
|
|
468
|
+
self.update_spectrum_indicator()
|
|
469
|
+
|
|
470
|
+
def on_luminance_interaction(self, event: tkinter.Event) -> None:
|
|
471
|
+
"""Update widget colors when the luminance scale is pressed"""
|
|
472
|
+
l = max(0, min(LUM, int((event.x / self.spectrum_width) * LUM)))
|
|
473
|
+
self.lum.set(l)
|
|
474
|
+
self.sync_color_values(HSL)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class ColorChooserDialog:
|
|
478
|
+
"""Dialog wrapper for the ColorChooser widget.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
master: Parent widget used for positioning and event binding.
|
|
482
|
+
title: Dialog window title (localized).
|
|
483
|
+
initial_color: Initial color shown in the chooser; defaults to theme background.
|
|
484
|
+
"""
|
|
485
|
+
|
|
486
|
+
def __init__(
|
|
487
|
+
self,
|
|
488
|
+
master: Optional[tkinter.Misc] = None,
|
|
489
|
+
title: str = "color.chooser",
|
|
490
|
+
initial_color: Optional[str] = None,
|
|
491
|
+
) -> None:
|
|
492
|
+
self._master = master
|
|
493
|
+
self._title = title
|
|
494
|
+
self._initial_color = initial_color
|
|
495
|
+
self.result: Optional[ColorChoice] = None
|
|
496
|
+
self._emitted_result = False
|
|
497
|
+
|
|
498
|
+
self._dialog = Dialog(
|
|
499
|
+
parent=master,
|
|
500
|
+
title=self._title,
|
|
501
|
+
content_builder=self._build_content,
|
|
502
|
+
_raw_content=True,
|
|
503
|
+
footer_builder=self._build_footer,
|
|
504
|
+
)
|
|
505
|
+
self._chooser: Optional[ColorChooser] = None
|
|
506
|
+
|
|
507
|
+
# builders -----------------------------------------------------------------
|
|
508
|
+
def _build_content(self, master: tkinter.Widget) -> None:
|
|
509
|
+
self._chooser = ColorChooser(master, self._initial_color)
|
|
510
|
+
self._chooser.pack(fill=BOTH, expand=YES)
|
|
511
|
+
|
|
512
|
+
def _build_footer(self, master: tkinter.Widget) -> None:
|
|
513
|
+
ok = ttk.Button(
|
|
514
|
+
master,
|
|
515
|
+
accent='primary',
|
|
516
|
+
text='button.ok',
|
|
517
|
+
command=self._on_ok,
|
|
518
|
+
)
|
|
519
|
+
ok.pack(side=RIGHT)
|
|
520
|
+
|
|
521
|
+
cancel = ttk.Button(
|
|
522
|
+
master,
|
|
523
|
+
accent='default',
|
|
524
|
+
text='button.cancel',
|
|
525
|
+
command=self._on_cancel,
|
|
526
|
+
)
|
|
527
|
+
cancel.pack(side=RIGHT)
|
|
528
|
+
|
|
529
|
+
# callbacks ----------------------------------------------------------------
|
|
530
|
+
def _on_ok(self) -> None:
|
|
531
|
+
if self._chooser:
|
|
532
|
+
values = self._chooser.get_variables()
|
|
533
|
+
self.result = ColorChoice(
|
|
534
|
+
rgb=(values.r, values.g, values.b),
|
|
535
|
+
hsl=(values.h, values.s, values.l),
|
|
536
|
+
hex=values.hex
|
|
537
|
+
)
|
|
538
|
+
self._emit_result(confirmed=True)
|
|
539
|
+
if self._dialog.toplevel:
|
|
540
|
+
self._dialog.toplevel.after_idle(self._dialog.toplevel.destroy)
|
|
541
|
+
|
|
542
|
+
def _on_cancel(self) -> None:
|
|
543
|
+
self.result = None
|
|
544
|
+
self._emit_result(confirmed=False)
|
|
545
|
+
if self._dialog.toplevel:
|
|
546
|
+
self._dialog.toplevel.after_idle(self._dialog.toplevel.destroy)
|
|
547
|
+
|
|
548
|
+
# API ----------------------------------------------------------------------
|
|
549
|
+
def show(self, position: Optional[Tuple[int, int]] = None, modal: bool = True) -> None:
|
|
550
|
+
"""Display the dialog."""
|
|
551
|
+
self.result = None
|
|
552
|
+
self._emitted_result = False
|
|
553
|
+
self._dialog.show(position=position, modal=modal)
|
|
554
|
+
if not self._emitted_result:
|
|
555
|
+
self._emit_result(confirmed=self.result is not None)
|
|
556
|
+
|
|
557
|
+
def on_dialog_result(self, callback: Callable[[Any], None]) -> Optional[str]:
|
|
558
|
+
"""Bind a callback fired when the dialog produces a result."""
|
|
559
|
+
target = self._master or self._dialog.toplevel
|
|
560
|
+
if target is None:
|
|
561
|
+
return None
|
|
562
|
+
|
|
563
|
+
def handler(event):
|
|
564
|
+
callback(getattr(event, "data", None))
|
|
565
|
+
|
|
566
|
+
return target.bind("<<DialogResult>>", handler, add="+")
|
|
567
|
+
|
|
568
|
+
def off_dialog_result(self, funcid: str) -> None:
|
|
569
|
+
"""Unbind a previously bound dialog result callback."""
|
|
570
|
+
target = self._master or self._dialog.toplevel
|
|
571
|
+
if target is None:
|
|
572
|
+
return
|
|
573
|
+
target.unbind("<<DialogResult>>", funcid)
|
|
574
|
+
|
|
575
|
+
# helpers ------------------------------------------------------------------
|
|
576
|
+
def _emit_result(self, confirmed: bool) -> None:
|
|
577
|
+
payload = {"result": self.result, "confirmed": confirmed}
|
|
578
|
+
target = self._master or self._dialog.toplevel
|
|
579
|
+
if not target:
|
|
580
|
+
return
|
|
581
|
+
try:
|
|
582
|
+
target.event_generate("<<DialogResult>>", data=payload)
|
|
583
|
+
except Exception:
|
|
584
|
+
try:
|
|
585
|
+
target.event_generate("<<DialogResult>>")
|
|
586
|
+
except Exception:
|
|
587
|
+
pass
|
|
588
|
+
self._emitted_result = True
|