bootstack 0.1.0a1__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 +249 -0
- bootstack/__main__.py +5 -0
- bootstack/api/__init__.py +127 -0
- bootstack/api/app.py +30 -0
- bootstack/api/constants.py +3 -0
- bootstack/api/data.py +23 -0
- bootstack/api/dialogs.py +44 -0
- bootstack/api/i18n.py +17 -0
- bootstack/api/localization.py +16 -0
- bootstack/api/menu.py +7 -0
- bootstack/api/style.py +23 -0
- bootstack/api/utils.py +24 -0
- bootstack/api/widgets.py +137 -0
- bootstack/assets/__init__.py +24 -0
- bootstack/assets/bootstack-transparent.png +0 -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/border.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-default.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-after-compact.png +0 -0
- bootstack/assets/elements/input-after-default.png +0 -0
- bootstack/assets/elements/input-before-compact.png +0 -0
- bootstack/assets/elements/input-before-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/manifest.toml +480 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/nav-button-compact.png +0 -0
- bootstack/assets/elements/nav-button-default.png +0 -0
- bootstack/assets/elements/nav-icon-button-compact.png +0 -0
- bootstack/assets/elements/nav-icon-button-default.png +0 -0
- bootstack/assets/elements/notebook-client-border.png +0 -0
- bootstack/assets/elements/notebook-tab-active.png +0 -0
- bootstack/assets/elements/notebook-tab-bar.png +0 -0
- bootstack/assets/elements/notebook-tab-normal.png +0 -0
- bootstack/assets/elements/notebook-tab-pill.png +0 -0
- bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/elements/progress-bar-solid.png +0 -0
- bootstack/assets/elements/progress-bar-thin.png +0 -0
- bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/elements/radio-selected.png +0 -0
- bootstack/assets/elements/radio-unselected.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle-focus.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-horizontal.png +0 -0
- bootstack/assets/elements/slider-track-vertical.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
- bootstack/assets/elements/tabs-bar-vertical.png +0 -0
- bootstack/assets/elements/tabs-pill.png +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/themes/__init__.py +0 -0
- bootstack/assets/themes/amber-dark.json +32 -0
- bootstack/assets/themes/amber-light.json +32 -0
- bootstack/assets/themes/aurora-dark.json +32 -0
- bootstack/assets/themes/aurora-light.json +32 -0
- bootstack/assets/themes/bootstrap-dark.json +32 -0
- bootstack/assets/themes/bootstrap-light.json +32 -0
- bootstack/assets/themes/classic-dark.json +32 -0
- bootstack/assets/themes/classic-light.json +32 -0
- bootstack/assets/themes/docs-dark.json +32 -0
- bootstack/assets/themes/docs-light.json +32 -0
- bootstack/assets/themes/forest-dark.json +32 -0
- bootstack/assets/themes/forest-light.json +32 -0
- bootstack/assets/themes/ocean-dark.json +32 -0
- bootstack/assets/themes/ocean-light.json +32 -0
- bootstack/assets/themes/rose-dark.json +32 -0
- bootstack/assets/themes/rose-light.json +32 -0
- bootstack/assets/widgets/__init__.py +0 -0
- bootstack/assets/widgets/badge-default.png +0 -0
- bootstack/assets/widgets/badge-pill.png +0 -0
- bootstack/assets/widgets/border.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
- bootstack/assets/widgets/button-group-vertical-after.png +0 -0
- bootstack/assets/widgets/button-group-vertical-before.png +0 -0
- bootstack/assets/widgets/button-group-vertical-center.png +0 -0
- bootstack/assets/widgets/button.png +0 -0
- bootstack/assets/widgets/checkbox-checked.png +0 -0
- bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
- bootstack/assets/widgets/checkbox-unchecked.png +0 -0
- bootstack/assets/widgets/field.png +0 -0
- bootstack/assets/widgets/icon-button.png +0 -0
- bootstack/assets/widgets/input-inner.png +0 -0
- bootstack/assets/widgets/input-prefix.png +0 -0
- bootstack/assets/widgets/input-suffix.png +0 -0
- bootstack/assets/widgets/input.png +0 -0
- bootstack/assets/widgets/list-item-focus.png +0 -0
- bootstack/assets/widgets/list-item-separated.png +0 -0
- bootstack/assets/widgets/menu-item-separated.png +0 -0
- bootstack/assets/widgets/notebook-client-border.png +0 -0
- bootstack/assets/widgets/notebook-pill-active.png +0 -0
- bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
- bootstack/assets/widgets/notebook-tab-active.png +0 -0
- bootstack/assets/widgets/notebook-tab-border.png +0 -0
- bootstack/assets/widgets/notebook-tab-normal.png +0 -0
- bootstack/assets/widgets/notebook-underline.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
- bootstack/assets/widgets/progress-trough-vertical.png +0 -0
- bootstack/assets/widgets/radio-selected.png +0 -0
- bootstack/assets/widgets/radio-unselected.png +0 -0
- bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
- bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
- bootstack/assets/widgets/separator-horizontal.png +0 -0
- bootstack/assets/widgets/separator-vertical.png +0 -0
- bootstack/assets/widgets/slider-handle-focus.png +0 -0
- bootstack/assets/widgets/slider-handle.png +0 -0
- bootstack/assets/widgets/slider-track-horizontal.png +0 -0
- bootstack/assets/widgets/slider-track-vertical.png +0 -0
- bootstack/assets/widgets/switch-off.png +0 -0
- bootstack/assets/widgets/switch-on.png +0 -0
- bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
- bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
- bootstack/assets/widgets/tabs-pill.png +0 -0
- bootstack/cli/__init__.py +124 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +439 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +287 -0
- bootstack/cli/demo.py +1267 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/list_cmd.py +71 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +246 -0
- bootstack/cli/run.py +99 -0
- bootstack/cli/start.py +105 -0
- bootstack/cli/templates/__init__.py +861 -0
- bootstack/constants.py +325 -0
- bootstack/core/__init__.py +34 -0
- bootstack/core/capabilities/__init__.py +45 -0
- bootstack/core/capabilities/after.py +103 -0
- bootstack/core/capabilities/bind.py +154 -0
- bootstack/core/capabilities/bindtags.py +112 -0
- bootstack/core/capabilities/busy.py +61 -0
- bootstack/core/capabilities/clipboard.py +88 -0
- bootstack/core/capabilities/focus.py +118 -0
- bootstack/core/capabilities/grab.py +65 -0
- bootstack/core/capabilities/grid.py +188 -0
- bootstack/core/capabilities/localization.py +231 -0
- bootstack/core/capabilities/pack.py +119 -0
- bootstack/core/capabilities/place.py +92 -0
- bootstack/core/capabilities/selection.py +136 -0
- bootstack/core/capabilities/signals.py +242 -0
- bootstack/core/capabilities/winfo.py +315 -0
- bootstack/core/colorutils.py +234 -0
- bootstack/core/exceptions.py +95 -0
- bootstack/core/images.py +283 -0
- bootstack/core/localization/README.md +90 -0
- bootstack/core/localization/__init__.py +13 -0
- bootstack/core/localization/intl_format.py +580 -0
- bootstack/core/localization/msgcat.py +425 -0
- bootstack/core/localization/specs.py +143 -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/publisher.py +147 -0
- bootstack/core/signals/README.md +112 -0
- bootstack/core/signals/__init__.py +8 -0
- bootstack/core/signals/integration.py +100 -0
- bootstack/core/signals/signal.py +317 -0
- bootstack/core/signals/types.py +4 -0
- bootstack/core/validation/__init__.py +5 -0
- bootstack/core/validation/types.py +13 -0
- bootstack/core/validation/validation_result.py +17 -0
- bootstack/core/validation/validation_rules.py +112 -0
- bootstack/core/variables.py +62 -0
- bootstack/datasource/README.md +607 -0
- bootstack/datasource/__init__.py +51 -0
- bootstack/datasource/base.py +474 -0
- bootstack/datasource/file_source.py +541 -0
- bootstack/datasource/memory_source.py +482 -0
- bootstack/datasource/sqlite_source.py +453 -0
- bootstack/datasource/types.py +259 -0
- bootstack/dialogs/__init__.py +56 -0
- bootstack/dialogs/colorchooser.py +674 -0
- bootstack/dialogs/colordropper.py +257 -0
- bootstack/dialogs/datedialog.py +404 -0
- bootstack/dialogs/dialog.py +514 -0
- bootstack/dialogs/filterdialog.py +358 -0
- bootstack/dialogs/fontdialog.py +339 -0
- bootstack/dialogs/formdialog.py +541 -0
- bootstack/dialogs/message.py +489 -0
- bootstack/dialogs/query.py +561 -0
- bootstack/py.typed +1 -0
- bootstack/runtime/__init__.py +3 -0
- bootstack/runtime/app.py +879 -0
- bootstack/runtime/base_window.py +786 -0
- bootstack/runtime/events.py +399 -0
- bootstack/runtime/menu.py +510 -0
- bootstack/runtime/shortcuts.py +423 -0
- bootstack/runtime/tk_patch.py +31 -0
- bootstack/runtime/toplevel.py +131 -0
- bootstack/runtime/utility.py +371 -0
- bootstack/runtime/visual_focus.py +228 -0
- bootstack/runtime/window_utilities.py +1043 -0
- bootstack/style/__init__.py +5498 -0
- bootstack/style/bootstyle.py +507 -0
- bootstack/style/bootstyle_builder_base.py +752 -0
- bootstack/style/bootstyle_builder_mixed.py +93 -0
- bootstack/style/bootstyle_builder_tk.py +109 -0
- bootstack/style/bootstyle_builder_ttk.py +354 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +44 -0
- bootstack/style/builders/button.py +453 -0
- bootstack/style/builders/buttongroup.py +344 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +95 -0
- bootstack/style/builders/combobox.py +112 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +83 -0
- bootstack/style/builders/expander.py +171 -0
- bootstack/style/builders/field.py +312 -0
- bootstack/style/builders/frame.py +27 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +41 -0
- bootstack/style/builders/listview.py +267 -0
- bootstack/style/builders/menubar.py +74 -0
- bootstack/style/builders/menubutton.py +408 -0
- bootstack/style/builders/notebook.py +316 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +71 -0
- bootstack/style/builders/radiobutton.py +68 -0
- bootstack/style/builders/scale.py +66 -0
- bootstack/style/builders/scrollbar.py +360 -0
- bootstack/style/builders/separator.py +45 -0
- bootstack/style/builders/sidenav.py +313 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +67 -0
- bootstack/style/builders/tabitem.py +205 -0
- bootstack/style/builders/toolbutton.py +260 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +269 -0
- bootstack/style/builders/utils.py +404 -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/style.py +499 -0
- bootstack/style/theme_provider.py +449 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +42 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +527 -0
- bootstack/style/utility.py +696 -0
- bootstack/themes/__init__.py +12 -0
- bootstack/themes/standard.py +415 -0
- bootstack/themes/user.py +45 -0
- bootstack/widgets/__init__.py +53 -0
- bootstack/widgets/composites/__init__.py +38 -0
- bootstack/widgets/composites/accordion.py +385 -0
- bootstack/widgets/composites/appshell.py +445 -0
- bootstack/widgets/composites/buttongroup.py +391 -0
- bootstack/widgets/composites/calendar.py +914 -0
- bootstack/widgets/composites/compositeframe.py +282 -0
- bootstack/widgets/composites/contextmenu.py +1754 -0
- bootstack/widgets/composites/dateentry.py +261 -0
- bootstack/widgets/composites/dropdownbutton.py +190 -0
- bootstack/widgets/composites/expander.py +508 -0
- bootstack/widgets/composites/field.py +448 -0
- bootstack/widgets/composites/floodgauge.py +434 -0
- bootstack/widgets/composites/form.py +983 -0
- bootstack/widgets/composites/labeledscale.py +209 -0
- bootstack/widgets/composites/list/__init__.py +15 -0
- bootstack/widgets/composites/list/listitem.py +733 -0
- bootstack/widgets/composites/list/listview.py +1507 -0
- bootstack/widgets/composites/menubar.py +303 -0
- bootstack/widgets/composites/meter.py +882 -0
- bootstack/widgets/composites/numericentry.py +183 -0
- bootstack/widgets/composites/pagestack.py +330 -0
- bootstack/widgets/composites/passwordentry.py +149 -0
- bootstack/widgets/composites/pathentry.py +223 -0
- bootstack/widgets/composites/radiogroup.py +466 -0
- bootstack/widgets/composites/scrolledtext.py +388 -0
- bootstack/widgets/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/composites/scrollview.py +675 -0
- bootstack/widgets/composites/selectbox.py +544 -0
- bootstack/widgets/composites/sidenav/__init__.py +24 -0
- bootstack/widgets/composites/sidenav/group.py +485 -0
- bootstack/widgets/composites/sidenav/header.py +83 -0
- bootstack/widgets/composites/sidenav/item.py +413 -0
- bootstack/widgets/composites/sidenav/separator.py +51 -0
- bootstack/widgets/composites/sidenav/view.py +919 -0
- bootstack/widgets/composites/spinnerentry.py +232 -0
- bootstack/widgets/composites/tableview/__init__.py +5 -0
- bootstack/widgets/composites/tableview/tableview.py +2254 -0
- bootstack/widgets/composites/tableview/types.py +169 -0
- bootstack/widgets/composites/tabs/__init__.py +6 -0
- bootstack/widgets/composites/tabs/tabitem.py +372 -0
- bootstack/widgets/composites/tabs/tabs.py +478 -0
- bootstack/widgets/composites/tabs/tabview.py +352 -0
- bootstack/widgets/composites/textentry.py +90 -0
- bootstack/widgets/composites/timeentry.py +189 -0
- bootstack/widgets/composites/toast.py +364 -0
- bootstack/widgets/composites/togglegroup.py +382 -0
- bootstack/widgets/composites/toolbar.py +393 -0
- bootstack/widgets/composites/tooltip.py +404 -0
- bootstack/widgets/internal/__init__.py +0 -0
- bootstack/widgets/internal/wrapper_base.py +304 -0
- bootstack/widgets/mixins/__init__.py +25 -0
- bootstack/widgets/mixins/configure_mixin.py +186 -0
- bootstack/widgets/mixins/entry_mixin.py +70 -0
- bootstack/widgets/mixins/font_mixin.py +346 -0
- bootstack/widgets/mixins/icon_mixin.py +38 -0
- bootstack/widgets/mixins/localization_mixin.py +255 -0
- bootstack/widgets/mixins/signal_mixin.py +272 -0
- bootstack/widgets/mixins/validation_mixin.py +204 -0
- bootstack/widgets/parts/__init__.py +11 -0
- bootstack/widgets/parts/numberentry_part.py +345 -0
- bootstack/widgets/parts/spinnerentry_part.py +394 -0
- bootstack/widgets/parts/textentry_part.py +344 -0
- bootstack/widgets/primitives/__init__.py +55 -0
- bootstack/widgets/primitives/badge.py +44 -0
- bootstack/widgets/primitives/button.py +89 -0
- bootstack/widgets/primitives/card.py +66 -0
- bootstack/widgets/primitives/checkbutton.py +124 -0
- bootstack/widgets/primitives/checktoggle.py +53 -0
- bootstack/widgets/primitives/combobox.py +165 -0
- bootstack/widgets/primitives/entry.py +98 -0
- bootstack/widgets/primitives/frame.py +206 -0
- bootstack/widgets/primitives/gridframe.py +479 -0
- bootstack/widgets/primitives/label.py +95 -0
- bootstack/widgets/primitives/labelframe.py +63 -0
- bootstack/widgets/primitives/menubutton.py +118 -0
- bootstack/widgets/primitives/notebook.py +551 -0
- bootstack/widgets/primitives/optionmenu.py +248 -0
- bootstack/widgets/primitives/packframe.py +228 -0
- bootstack/widgets/primitives/panedwindow.py +58 -0
- bootstack/widgets/primitives/progressbar.py +95 -0
- bootstack/widgets/primitives/radiobutton.py +115 -0
- bootstack/widgets/primitives/radiotoggle.py +50 -0
- bootstack/widgets/primitives/scale.py +85 -0
- bootstack/widgets/primitives/scrollbar.py +56 -0
- bootstack/widgets/primitives/separator.py +56 -0
- bootstack/widgets/primitives/sizegrip.py +47 -0
- bootstack/widgets/primitives/spinbox.py +91 -0
- bootstack/widgets/primitives/switch.py +41 -0
- bootstack/widgets/primitives/treeview.py +77 -0
- bootstack/widgets/types.py +20 -0
- bootstack-0.1.0a1.dist-info/METADATA +196 -0
- bootstack-0.1.0a1.dist-info/RECORD +419 -0
- bootstack-0.1.0a1.dist-info/WHEEL +5 -0
- bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
- bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,674 @@
|
|
|
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
|
+
hex input, and screen color picker (color dropper).
|
|
6
|
+
"""
|
|
7
|
+
import tkinter
|
|
8
|
+
from collections import namedtuple
|
|
9
|
+
from tkinter import Canvas, IntVar, StringVar
|
|
10
|
+
from tkinter import Frame as tkFrame, Label as tkLabel
|
|
11
|
+
from types import SimpleNamespace
|
|
12
|
+
from typing import Any, Callable, List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
from PIL import ImageColor
|
|
15
|
+
|
|
16
|
+
from bootstack.constants import *
|
|
17
|
+
from bootstack.core import colorutils
|
|
18
|
+
from bootstack.core.colorutils import HEX, HSL, HUE, LUM, RGB, SAT
|
|
19
|
+
from bootstack.core.localization import MessageCatalog
|
|
20
|
+
from bootstack.runtime import utility
|
|
21
|
+
from bootstack.style.style import get_style
|
|
22
|
+
from bootstack.widgets.composites.tooltip import ToolTip
|
|
23
|
+
from bootstack.widgets.primitives import Button, Entry, Frame, Label, Notebook, Spinbox
|
|
24
|
+
# from bootstack.validation import add_range_validation, add_validation, validator
|
|
25
|
+
from .colordropper import ColorDropperDialog
|
|
26
|
+
|
|
27
|
+
ttk = SimpleNamespace(
|
|
28
|
+
Button=Button,
|
|
29
|
+
Canvas=Canvas,
|
|
30
|
+
Entry=Entry,
|
|
31
|
+
Frame=Frame,
|
|
32
|
+
IntVar=IntVar,
|
|
33
|
+
Label=Label,
|
|
34
|
+
Notebook=Notebook,
|
|
35
|
+
Spinbox=Spinbox,
|
|
36
|
+
StringVar=StringVar,
|
|
37
|
+
use_style=get_style,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
STD_SHADES: List[float] = [0.9, 0.8, 0.7, 0.4, 0.3]
|
|
41
|
+
STD_COLORS: List[str] = [
|
|
42
|
+
'#FF0000', '#FFC000', '#FFFF00', '#00B050',
|
|
43
|
+
'#0070C0', '#7030A0', '#FFFFFF', '#000000'
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
ColorValues = namedtuple('ColorValues', 'h s l r g b hex')
|
|
47
|
+
ColorChoice = namedtuple('ColorChoice', 'rgb hsl hex')
|
|
48
|
+
|
|
49
|
+
PEN = '✛'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# @validator
|
|
53
|
+
# def validate_color(event: Any) -> bool:
|
|
54
|
+
# try:
|
|
55
|
+
# ImageColor.getrgb(event.postchangetext)
|
|
56
|
+
# return True
|
|
57
|
+
# except:
|
|
58
|
+
# return False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ColorChooser(ttk.Frame):
|
|
62
|
+
"""Color chooser widget with multiple selection modes."""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self, master: Optional[tkinter.Misc], initial_color: Optional[str] = None,
|
|
66
|
+
padding: Optional[int] = None) -> None:
|
|
67
|
+
"""Create a color chooser widget.
|
|
68
|
+
|
|
69
|
+
The chooser offers:
|
|
70
|
+
- Advanced tab: spectrum with hue/saturation and luminance slider.
|
|
71
|
+
- Themed tab: swatches for theme colors and shades.
|
|
72
|
+
- Standard tab: common colors and shades.
|
|
73
|
+
|
|
74
|
+
Includes RGB/HSL/Hex inputs, live preview, and optional dropper.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
master: Parent widget.
|
|
78
|
+
initial_color: Initial color string; defaults to theme background.
|
|
79
|
+
padding: Padding around the chooser.
|
|
80
|
+
"""
|
|
81
|
+
super().__init__(master, padding=padding)
|
|
82
|
+
self.tframe = ttk.Frame(self, padding=5)
|
|
83
|
+
self.tframe.pack(fill=X)
|
|
84
|
+
self.bframe = ttk.Frame(self, padding=(5, 0, 5, 5))
|
|
85
|
+
self.bframe.pack(fill=X)
|
|
86
|
+
|
|
87
|
+
self.notebook = ttk.Notebook(self.tframe)
|
|
88
|
+
self.notebook.pack(fill=BOTH)
|
|
89
|
+
|
|
90
|
+
self.style = ttk.use_style()
|
|
91
|
+
self.colors = self.style.colors
|
|
92
|
+
fallback_bg = (
|
|
93
|
+
self.colors.get("bg")
|
|
94
|
+
if isinstance(self.colors, dict)
|
|
95
|
+
else getattr(self.colors, "bg", None)
|
|
96
|
+
) or "#ffffff"
|
|
97
|
+
self.initial_color = initial_color or fallback_bg
|
|
98
|
+
|
|
99
|
+
# color variables
|
|
100
|
+
r, g, b = ImageColor.getrgb(self.initial_color)
|
|
101
|
+
h, s, l = colorutils.color_to_hsl((r, g, b), RGB)
|
|
102
|
+
hx = colorutils.color_to_hex((r, g, b), RGB)
|
|
103
|
+
|
|
104
|
+
self.hue = ttk.IntVar(value=h)
|
|
105
|
+
self.sat = ttk.IntVar(value=s)
|
|
106
|
+
self.lum = ttk.IntVar(value=l)
|
|
107
|
+
self.red = ttk.IntVar(value=r)
|
|
108
|
+
self.grn = ttk.IntVar(value=g)
|
|
109
|
+
self.blu = ttk.IntVar(value=b)
|
|
110
|
+
self.hex = ttk.StringVar(value=hx)
|
|
111
|
+
|
|
112
|
+
# widget sizes (adjusted by widget scaling)
|
|
113
|
+
self.spectrum_height = utility.scale_size(self, 240)
|
|
114
|
+
self.spectrum_width = utility.scale_size(self, 530) # looks better on Mac OS
|
|
115
|
+
# self.spectrum_width = utility.scale_size(self, 480)
|
|
116
|
+
self.spectrum_point = utility.scale_size(self, 12)
|
|
117
|
+
|
|
118
|
+
# build widgets
|
|
119
|
+
spectrum_frame = ttk.Frame(self.notebook)
|
|
120
|
+
self.color_spectrum = self.create_spectrum(spectrum_frame)
|
|
121
|
+
self.color_spectrum.pack(fill=X, side=TOP)
|
|
122
|
+
self.luminance_scale = self.create_luminance_scale(self.tframe)
|
|
123
|
+
self.luminance_scale.pack(fill=X)
|
|
124
|
+
self.notebook.add(spectrum_frame, text='color.advanced')
|
|
125
|
+
|
|
126
|
+
palette_keys = ("primary", "secondary", "success", "info", "warning", "danger", "light", "dark")
|
|
127
|
+
themed_colors = [
|
|
128
|
+
(self.colors.get(c) if isinstance(self.colors, dict) else getattr(self.colors, c, None))
|
|
129
|
+
for c in palette_keys
|
|
130
|
+
]
|
|
131
|
+
themed_colors = [c or "#ffffff" for c in themed_colors]
|
|
132
|
+
self.themed_swatches = self.create_swatches(
|
|
133
|
+
self.notebook, themed_colors)
|
|
134
|
+
self.standard_swatches = self.create_swatches(
|
|
135
|
+
self.notebook, STD_COLORS)
|
|
136
|
+
self.notebook.add(self.themed_swatches, text='color.themed')
|
|
137
|
+
self.notebook.add(self.standard_swatches, text='color.standard')
|
|
138
|
+
preview_frame = self.create_preview(self.bframe)
|
|
139
|
+
preview_frame.pack(side=LEFT, fill=BOTH, expand=YES, padx=(0, 5))
|
|
140
|
+
self.color_entries = self.create_value_inputs(self.bframe)
|
|
141
|
+
self.color_entries.pack(side=RIGHT)
|
|
142
|
+
|
|
143
|
+
self.create_spectrum_indicator()
|
|
144
|
+
self.create_luminance_indicator()
|
|
145
|
+
|
|
146
|
+
def create_spectrum(self, master: tkinter.Misc) -> ttk.Canvas:
|
|
147
|
+
"""Create the color spectrum canvas"""
|
|
148
|
+
# canvas and point dimensions
|
|
149
|
+
width = self.spectrum_width
|
|
150
|
+
height = self.spectrum_height
|
|
151
|
+
xf = yf = self.spectrum_point
|
|
152
|
+
|
|
153
|
+
# create canvas widget and binding
|
|
154
|
+
canvas = ttk.Canvas(master, width=width, height=height, cursor='tcross')
|
|
155
|
+
canvas.bind("<B1-Motion>", self.on_spectrum_interaction, add="+")
|
|
156
|
+
canvas.bind("<Button-1>", self.on_spectrum_interaction, add="+")
|
|
157
|
+
|
|
158
|
+
# add color points
|
|
159
|
+
for x, colorx in enumerate(range(0, width, xf)):
|
|
160
|
+
for y, colory in enumerate(range(0, height, yf)):
|
|
161
|
+
values = self.color_from_coords(colorx, colory)
|
|
162
|
+
fill = values.hex
|
|
163
|
+
bbox = [x * xf, y * yf, (x * xf) + xf, (y * yf) + yf]
|
|
164
|
+
canvas.create_rectangle(*bbox, fill=fill, width=0)
|
|
165
|
+
return canvas
|
|
166
|
+
|
|
167
|
+
def create_spectrum_indicator(self) -> None:
|
|
168
|
+
"""Create a square indicator that displays in the position of
|
|
169
|
+
the selected color"""
|
|
170
|
+
s = utility.scale_size(self, 10)
|
|
171
|
+
width = utility.scale_size(self, 2)
|
|
172
|
+
values = self.get_variables()
|
|
173
|
+
x1, y1 = self.coords_from_color(values.hex)
|
|
174
|
+
colorutils.contrast_color(values.hex, 'hex')
|
|
175
|
+
tag = ['spectrum-indicator']
|
|
176
|
+
self.color_spectrum.create_rectangle(
|
|
177
|
+
x1, y1, x1 + s, y1 + s, width=width, tags=[tag])
|
|
178
|
+
self.color_spectrum.tag_lower('spectrum-indicator')
|
|
179
|
+
|
|
180
|
+
# widget builder methods
|
|
181
|
+
def create_swatches(self, master: tkinter.Misc, colors: List[str]) -> ttk.Frame:
|
|
182
|
+
"""Create a grid of color swatches"""
|
|
183
|
+
box_padx = 2
|
|
184
|
+
box_pady = 0
|
|
185
|
+
padx_total = (box_padx * 15)
|
|
186
|
+
box_width = int((self.spectrum_width - padx_total)) / len(STD_COLORS)
|
|
187
|
+
box_height = int((self.spectrum_height - box_pady) / (len(STD_SHADES) + 1))
|
|
188
|
+
container = ttk.Frame(master)
|
|
189
|
+
|
|
190
|
+
# create color combinations
|
|
191
|
+
color_rows = [colors]
|
|
192
|
+
last_col = len(colors) - 1
|
|
193
|
+
for l in STD_SHADES:
|
|
194
|
+
lum = int(l * LUM)
|
|
195
|
+
row = []
|
|
196
|
+
for color in colors:
|
|
197
|
+
color = colorutils.update_hsl_value(
|
|
198
|
+
color=color,
|
|
199
|
+
lum=lum,
|
|
200
|
+
inmodel='hex',
|
|
201
|
+
outmodel='hex'
|
|
202
|
+
)
|
|
203
|
+
row.append(color)
|
|
204
|
+
color_rows.append(row)
|
|
205
|
+
|
|
206
|
+
# themed colors - regular colors
|
|
207
|
+
for row in color_rows:
|
|
208
|
+
row_frame = ttk.Frame(container)
|
|
209
|
+
for j, color in enumerate(row):
|
|
210
|
+
swatch = tkFrame(
|
|
211
|
+
master=row_frame,
|
|
212
|
+
bg=color,
|
|
213
|
+
width=box_width,
|
|
214
|
+
height=box_height,
|
|
215
|
+
autostyle=False
|
|
216
|
+
)
|
|
217
|
+
swatch.bind('<Button-1>', self.on_press_swatch)
|
|
218
|
+
if j == 0:
|
|
219
|
+
swatch.pack(side=LEFT, padx=(0, box_padx))
|
|
220
|
+
elif j == last_col:
|
|
221
|
+
swatch.pack(side=LEFT, padx=(box_padx, 0))
|
|
222
|
+
else:
|
|
223
|
+
swatch.pack(side=LEFT, padx=box_padx)
|
|
224
|
+
row_frame.pack(fill=X, expand=YES)
|
|
225
|
+
|
|
226
|
+
return container
|
|
227
|
+
|
|
228
|
+
def create_preview(self, master: tkinter.Misc) -> ttk.Frame:
|
|
229
|
+
"""Create the preview frame for original and new colors"""
|
|
230
|
+
ng_style = self.notebook.cget('style')
|
|
231
|
+
# set the border color to match the notebook border color
|
|
232
|
+
border_color = self.style.lookup(ng_style, 'bordercolor') or "#000000"
|
|
233
|
+
container = ttk.Frame(master)
|
|
234
|
+
|
|
235
|
+
# the frame and label for the original color (current)
|
|
236
|
+
old = tkFrame(
|
|
237
|
+
master=container,
|
|
238
|
+
relief=FLAT,
|
|
239
|
+
bd=2,
|
|
240
|
+
highlightthickness=1,
|
|
241
|
+
highlightbackground=border_color,
|
|
242
|
+
bg=self.initial_color,
|
|
243
|
+
autostyle=False
|
|
244
|
+
)
|
|
245
|
+
old.pack(side=LEFT, fill=BOTH, expand=YES, padx=(0, 2))
|
|
246
|
+
constrast_fg = colorutils.contrast_color(
|
|
247
|
+
color=self.initial_color,
|
|
248
|
+
model='hex',
|
|
249
|
+
)
|
|
250
|
+
tkLabel(
|
|
251
|
+
master=old,
|
|
252
|
+
text=MessageCatalog.translate("color.current"),
|
|
253
|
+
background=self.initial_color,
|
|
254
|
+
foreground=constrast_fg,
|
|
255
|
+
autostyle=False,
|
|
256
|
+
width=7
|
|
257
|
+
).pack(anchor=NW)
|
|
258
|
+
|
|
259
|
+
# the frame and label for the new color
|
|
260
|
+
self.preview = tkFrame(
|
|
261
|
+
master=container,
|
|
262
|
+
relief=FLAT,
|
|
263
|
+
bd=2,
|
|
264
|
+
highlightthickness=1,
|
|
265
|
+
highlightbackground=border_color,
|
|
266
|
+
bg=self.initial_color,
|
|
267
|
+
autostyle=False
|
|
268
|
+
)
|
|
269
|
+
self.preview.pack(side=LEFT, fill=BOTH, expand=YES, padx=(2, 0))
|
|
270
|
+
self.preview_lbl = tkLabel(
|
|
271
|
+
master=self.preview,
|
|
272
|
+
text=MessageCatalog.translate("color.new"),
|
|
273
|
+
background=self.initial_color,
|
|
274
|
+
foreground=constrast_fg,
|
|
275
|
+
autostyle=False,
|
|
276
|
+
width=7
|
|
277
|
+
)
|
|
278
|
+
self.preview_lbl.pack(anchor=NW)
|
|
279
|
+
|
|
280
|
+
return container
|
|
281
|
+
|
|
282
|
+
def create_value_inputs(self, master: tkinter.Misc) -> ttk.Frame:
|
|
283
|
+
"""Create color value input widgets"""
|
|
284
|
+
container = ttk.Frame(master)
|
|
285
|
+
for x in range(4):
|
|
286
|
+
container.columnconfigure(x, weight=1)
|
|
287
|
+
|
|
288
|
+
# value labels - use semantic keys with built-in localization
|
|
289
|
+
lbl_cnf = {'master': container, 'anchor': E}
|
|
290
|
+
ttk.Label(**lbl_cnf, text='color.hue:').grid(row=0, column=0, sticky=E)
|
|
291
|
+
ttk.Label(**lbl_cnf, text='color.sat:').grid(row=1, column=0, sticky=E)
|
|
292
|
+
ttk.Label(**lbl_cnf, text='color.lum:').grid(row=2, column=0, sticky=E)
|
|
293
|
+
ttk.Label(**lbl_cnf, text='color.hex:').grid(row=3, column=0, sticky=E)
|
|
294
|
+
ttk.Label(**lbl_cnf, text='color.red:').grid(row=0, column=2, sticky=E)
|
|
295
|
+
ttk.Label(**lbl_cnf, text='color.green:').grid(row=1, column=2, sticky=E)
|
|
296
|
+
ttk.Label(**lbl_cnf, text='color.blue:').grid(row=2, column=2, sticky=E)
|
|
297
|
+
|
|
298
|
+
# value spinners and entry widgets
|
|
299
|
+
rgb_cnf = {'master': container, 'from_': 0, 'to': 255, 'width': 3}
|
|
300
|
+
sl_cnf = {'master': container, 'from_': 0, 'to': 100, 'width': 3}
|
|
301
|
+
hue_cnf = {'master': container, 'from_': 0, 'to': 360, 'width': 3}
|
|
302
|
+
sb_hue = ttk.Spinbox(**hue_cnf, textvariable=self.hue)
|
|
303
|
+
sb_hue.grid(row=0, column=1, padx=4, pady=2, sticky=EW)
|
|
304
|
+
sb_sat = ttk.Spinbox(**sl_cnf, textvariable=self.sat)
|
|
305
|
+
sb_sat.grid(row=1, column=1, padx=4, pady=2, sticky=EW)
|
|
306
|
+
sb_lum = ttk.Spinbox(**sl_cnf, textvariable=self.lum)
|
|
307
|
+
sb_lum.grid(row=2, column=1, padx=4, pady=2, sticky=EW)
|
|
308
|
+
sb_red = ttk.Spinbox(**rgb_cnf, textvariable=self.red)
|
|
309
|
+
sb_red.grid(row=0, column=3, padx=4, pady=2, sticky=EW)
|
|
310
|
+
sb_grn = ttk.Spinbox(**rgb_cnf, textvariable=self.grn)
|
|
311
|
+
sb_grn.grid(row=1, column=3, padx=4, pady=2, sticky=EW)
|
|
312
|
+
sb_blu = ttk.Spinbox(**rgb_cnf, textvariable=self.blu)
|
|
313
|
+
sb_blu.grid(row=2, column=3, padx=4, pady=2, sticky=EW)
|
|
314
|
+
ent_hex = ttk.Entry(container, textvariable=self.hex)
|
|
315
|
+
ent_hex.grid(row=3, column=1, padx=4, columnspan=3, pady=2, sticky=EW)
|
|
316
|
+
|
|
317
|
+
# event binding for updating colors on value change
|
|
318
|
+
for sb in [sb_hue, sb_sat, sb_lum]:
|
|
319
|
+
for sequence in ['<<Increment>>', '<<Decrement>>', '<Return>', '<KP_Enter>']:
|
|
320
|
+
sb.bind(
|
|
321
|
+
sequence=sequence,
|
|
322
|
+
func=lambda _, w=sb: self.on_entry_value_change(
|
|
323
|
+
w, HSL),
|
|
324
|
+
add="+"
|
|
325
|
+
)
|
|
326
|
+
for sb in [sb_red, sb_grn, sb_blu]:
|
|
327
|
+
for sequence in ['<<Increment>>', '<<Decrement>>', '<Return>', '<KP_Enter>']:
|
|
328
|
+
sb.bind(
|
|
329
|
+
sequence=sequence,
|
|
330
|
+
func=lambda _, w=sb: self.on_entry_value_change(
|
|
331
|
+
w, RGB),
|
|
332
|
+
add="+"
|
|
333
|
+
)
|
|
334
|
+
for sequence in ['<Return>', '<KP_Enter>']:
|
|
335
|
+
ent_hex.bind(
|
|
336
|
+
sequence=sequence,
|
|
337
|
+
func=lambda _, w=ent_hex: self.on_entry_value_change(
|
|
338
|
+
w, HEX),
|
|
339
|
+
add="+"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return container
|
|
343
|
+
|
|
344
|
+
def create_luminance_scale(self, master: tkinter.Misc) -> ttk.Canvas:
|
|
345
|
+
"""Create the color luminance canvas"""
|
|
346
|
+
# widget dimensions
|
|
347
|
+
height = xf = self.spectrum_point
|
|
348
|
+
width = self.spectrum_width
|
|
349
|
+
|
|
350
|
+
values = self.get_variables()
|
|
351
|
+
canvas = ttk.Canvas(master, height=height, width=width)
|
|
352
|
+
|
|
353
|
+
# add color points to scale
|
|
354
|
+
for x, l in enumerate(range(0, width, xf)):
|
|
355
|
+
lum = l / width * LUM
|
|
356
|
+
fill = colorutils.update_hsl_value(
|
|
357
|
+
color=values.hex,
|
|
358
|
+
lum=lum,
|
|
359
|
+
inmodel='hex',
|
|
360
|
+
outmodel='hex'
|
|
361
|
+
)
|
|
362
|
+
bbox = [x * xf, 0, (x * xf) + xf, height]
|
|
363
|
+
tag = f'color{x}'
|
|
364
|
+
canvas.create_rectangle(*bbox, fill=fill, width=0, tags=[tag])
|
|
365
|
+
canvas.bind("<B1-Motion>", self.on_luminance_interaction, add="+")
|
|
366
|
+
canvas.bind("<Button-1>", self.on_luminance_interaction, add="+")
|
|
367
|
+
return canvas
|
|
368
|
+
|
|
369
|
+
def create_luminance_indicator(self) -> None:
|
|
370
|
+
"""Create an indicator that displays in the position of the
|
|
371
|
+
luminance value"""
|
|
372
|
+
lum = 50
|
|
373
|
+
x1 = int(lum / LUM * self.spectrum_width) - \
|
|
374
|
+
((self.spectrum_point - 2) // 2)
|
|
375
|
+
y1 = 0
|
|
376
|
+
x2 = x1 + self.spectrum_point
|
|
377
|
+
y2 = self.spectrum_point - 3
|
|
378
|
+
tag = 'luminance-indicator'
|
|
379
|
+
bbox = [x1, y1, x2, y2]
|
|
380
|
+
self.luminance_scale.create_rectangle(
|
|
381
|
+
*bbox, fill='white', outline='black', tags=[tag])
|
|
382
|
+
self.luminance_scale.tag_lower(tag)
|
|
383
|
+
|
|
384
|
+
def coords_from_color(self, hexcolor: str) -> Tuple[float, float]:
|
|
385
|
+
"""Get the coordinates on the color spectrum from the color
|
|
386
|
+
value"""
|
|
387
|
+
h, s, _ = colorutils.color_to_hsl(hexcolor)
|
|
388
|
+
x = (h / HUE) * self.spectrum_width
|
|
389
|
+
y = (1 - (s / SAT)) * self.spectrum_height
|
|
390
|
+
return x, y
|
|
391
|
+
|
|
392
|
+
def color_from_coords(self, x: int, y: int):
|
|
393
|
+
"""Get the color value from the mouse position in the color
|
|
394
|
+
spectrum"""
|
|
395
|
+
HEIGHT = self.spectrum_height
|
|
396
|
+
WIDTH = self.spectrum_width
|
|
397
|
+
h = int(min(HUE, max(0, (HUE / WIDTH) * x)))
|
|
398
|
+
s = int(min(SAT, max(0, SAT - ((SAT / HEIGHT) * y))))
|
|
399
|
+
l = 50
|
|
400
|
+
hx = colorutils.color_to_hex([h, s, l], 'hsl')
|
|
401
|
+
r, g, b = colorutils.color_to_rgb(hx)
|
|
402
|
+
return ColorValues(h, s, l, r, g, b, hx)
|
|
403
|
+
|
|
404
|
+
def set_variables(self, h: int, s: int, l: int, r: int, g: int, b: int, hx: str) -> None:
|
|
405
|
+
"""Update the color value variables"""
|
|
406
|
+
self.hue.set(h)
|
|
407
|
+
self.sat.set(s)
|
|
408
|
+
self.lum.set(l)
|
|
409
|
+
self.red.set(r)
|
|
410
|
+
self.grn.set(g)
|
|
411
|
+
self.blu.set(b)
|
|
412
|
+
self.hex.set(hx)
|
|
413
|
+
|
|
414
|
+
def get_variables(self):
|
|
415
|
+
"""Get the values of all color models and return a
|
|
416
|
+
tuple of color values"""
|
|
417
|
+
h = self.hue.get()
|
|
418
|
+
s = self.sat.get()
|
|
419
|
+
l = self.lum.get()
|
|
420
|
+
r = self.red.get()
|
|
421
|
+
g = self.grn.get()
|
|
422
|
+
b = self.blu.get()
|
|
423
|
+
hx = self.hex.get()
|
|
424
|
+
return ColorValues(h, s, l, r, g, b, hx)
|
|
425
|
+
|
|
426
|
+
def update_preview(self) -> None:
|
|
427
|
+
"""Update the color in the preview frame"""
|
|
428
|
+
hx = self.hex.get()
|
|
429
|
+
fg = colorutils.contrast_color(
|
|
430
|
+
color=hx,
|
|
431
|
+
model='hex',
|
|
432
|
+
)
|
|
433
|
+
self.preview.configure(bg=hx)
|
|
434
|
+
self.preview_lbl.configure(bg=hx, fg=fg)
|
|
435
|
+
|
|
436
|
+
def update_luminance_scale(self) -> None:
|
|
437
|
+
"""Update the luminance scale with the change in hue and saturation"""
|
|
438
|
+
values = self.get_variables()
|
|
439
|
+
width = self.spectrum_width
|
|
440
|
+
xf = self.spectrum_point
|
|
441
|
+
for x, l in enumerate(range(0, width, xf)):
|
|
442
|
+
lum = l / width * LUM
|
|
443
|
+
fill = colorutils.update_hsl_value(
|
|
444
|
+
color=values.hex,
|
|
445
|
+
lum=lum,
|
|
446
|
+
inmodel='hex',
|
|
447
|
+
outmodel='hex'
|
|
448
|
+
)
|
|
449
|
+
tag = f'color{x}'
|
|
450
|
+
self.luminance_scale.itemconfig(tag, fill=fill)
|
|
451
|
+
|
|
452
|
+
def update_luminance_indicator(self) -> None:
|
|
453
|
+
"""Update the position of the luminance indicator"""
|
|
454
|
+
lum = self.lum.get()
|
|
455
|
+
x = int(lum / LUM * self.spectrum_width) - \
|
|
456
|
+
((self.spectrum_point - 2) // 2)
|
|
457
|
+
self.luminance_scale.moveto('luminance-indicator', x, 0)
|
|
458
|
+
self.luminance_scale.tag_raise('luminance-indicator')
|
|
459
|
+
|
|
460
|
+
def update_spectrum_indicator(self) -> None:
|
|
461
|
+
"""Move the spectrum indicator to a new location"""
|
|
462
|
+
values = self.get_variables()
|
|
463
|
+
x, y = self.coords_from_color(values.hex)
|
|
464
|
+
# move to the new color location
|
|
465
|
+
self.color_spectrum.moveto('spectrum-indicator', x, y)
|
|
466
|
+
self.color_spectrum.tag_raise('spectrum-indicator')
|
|
467
|
+
# adjust the outline color based on contrast of background
|
|
468
|
+
color = colorutils.contrast_color(values.hex, 'hex')
|
|
469
|
+
self.color_spectrum.itemconfig('spectrum-indicator', outline=color)
|
|
470
|
+
|
|
471
|
+
# color events
|
|
472
|
+
def sync_color_values(self, model):
|
|
473
|
+
"""Callback for when a color value changes. A change in one
|
|
474
|
+
value will automatically update the other values so that all
|
|
475
|
+
color models remain in sync."""
|
|
476
|
+
values = self.get_variables()
|
|
477
|
+
if model == HEX:
|
|
478
|
+
hx = values.hex
|
|
479
|
+
r, g, b = colorutils.color_to_rgb(hx)
|
|
480
|
+
h, s, l = colorutils.color_to_hsl(hx)
|
|
481
|
+
elif model == RGB:
|
|
482
|
+
r, g, b = values.r, values.g, values.b
|
|
483
|
+
h, s, l = colorutils.color_to_hsl([r, g, b], 'rgb')
|
|
484
|
+
hx = colorutils.color_to_hex([r, g, b])
|
|
485
|
+
elif model == HSL:
|
|
486
|
+
h, s, l = values.h, values.s, values.l
|
|
487
|
+
r, g, b = colorutils.color_to_rgb([h, s, l], 'hsl')
|
|
488
|
+
hx = colorutils.color_to_hex([h, s, l], 'hsl')
|
|
489
|
+
self.set_variables(h, s, l, r, g, b, hx)
|
|
490
|
+
self.update_preview()
|
|
491
|
+
self.update_luminance_indicator()
|
|
492
|
+
|
|
493
|
+
def on_entry_value_change(self, widget: ttk.Spinbox, model: Any) -> None:
|
|
494
|
+
"""Update the widget colors when the color value input is
|
|
495
|
+
changed"""
|
|
496
|
+
is_valid = widget.validate()
|
|
497
|
+
if is_valid:
|
|
498
|
+
self.sync_color_values(model)
|
|
499
|
+
self.update_luminance_scale()
|
|
500
|
+
self.update_spectrum_indicator()
|
|
501
|
+
|
|
502
|
+
def on_press_swatch(self, event: tkinter.Event) -> None:
|
|
503
|
+
"""Update the widget colors when a color swatch is clicked."""
|
|
504
|
+
button: tkFrame = self.nametowidget(event.widget)
|
|
505
|
+
color = button.cget('background')
|
|
506
|
+
self.hex.set(color)
|
|
507
|
+
self.sync_color_values(HEX)
|
|
508
|
+
self.update_luminance_scale()
|
|
509
|
+
self.update_spectrum_indicator()
|
|
510
|
+
|
|
511
|
+
def on_spectrum_interaction(self, event: tkinter.Event) -> None:
|
|
512
|
+
"""Update the widget colors when the color spectrum canvas is
|
|
513
|
+
pressed"""
|
|
514
|
+
values = self.color_from_coords(event.x, event.y)
|
|
515
|
+
self.hue.set(values.h)
|
|
516
|
+
self.sat.set(values.s)
|
|
517
|
+
self.lum.set(values.l)
|
|
518
|
+
self.sync_color_values(HSL)
|
|
519
|
+
self.update_luminance_scale()
|
|
520
|
+
self.update_spectrum_indicator()
|
|
521
|
+
|
|
522
|
+
def on_luminance_interaction(self, event: tkinter.Event) -> None:
|
|
523
|
+
"""Update the widget colors when the color luminance scale is
|
|
524
|
+
pressed"""
|
|
525
|
+
l = max(0, min(LUM, int((event.x / self.spectrum_width) * LUM)))
|
|
526
|
+
self.lum.set(l)
|
|
527
|
+
self.sync_color_values(HSL)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
from bootstack.dialogs.dialog import Dialog
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
class ColorChooserDialog:
|
|
534
|
+
"""Dialog wrapper for the ColorChooser widget.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
master: Parent widget used for positioning and event binding.
|
|
538
|
+
title: Dialog window title (localized).
|
|
539
|
+
initial_color: Initial color shown in the chooser; defaults to theme background.
|
|
540
|
+
|
|
541
|
+
!!! note "Events"
|
|
542
|
+
|
|
543
|
+
`<<DialogResult>>`: Fired when the dialog is closed.
|
|
544
|
+
Provides `event.data` with keys: `result` (ColorChoice|None), `confirmed` (bool).
|
|
545
|
+
"""
|
|
546
|
+
|
|
547
|
+
def __init__(
|
|
548
|
+
self,
|
|
549
|
+
master: Optional[tkinter.Misc] = None,
|
|
550
|
+
title: str = "color.chooser",
|
|
551
|
+
initial_color: Optional[str] = None,
|
|
552
|
+
) -> None:
|
|
553
|
+
self._master = master
|
|
554
|
+
# Title is now automatically localized by BaseWindow._setup_window
|
|
555
|
+
self._title = title
|
|
556
|
+
self._initial_color = initial_color
|
|
557
|
+
self.result: Optional[ColorChoice] = None
|
|
558
|
+
self._emitted_result = False
|
|
559
|
+
|
|
560
|
+
self._dropper = ColorDropperDialog()
|
|
561
|
+
self._dropper.result.trace_add('write', self._trace_dropper_color)
|
|
562
|
+
|
|
563
|
+
self._dialog = Dialog(
|
|
564
|
+
master=master,
|
|
565
|
+
title=self._title,
|
|
566
|
+
content_builder=self._build_content,
|
|
567
|
+
footer_builder=self._build_footer,
|
|
568
|
+
)
|
|
569
|
+
self._chooser: Optional[ColorChooser] = None
|
|
570
|
+
|
|
571
|
+
# builders -----------------------------------------------------------------
|
|
572
|
+
def _build_content(self, master: tkinter.Widget) -> None:
|
|
573
|
+
self._chooser = ColorChooser(master, self._initial_color)
|
|
574
|
+
self._chooser.pack(fill=BOTH, expand=YES)
|
|
575
|
+
|
|
576
|
+
def _build_footer(self, master: tkinter.Widget) -> None:
|
|
577
|
+
# color dropper (not supported on macOS)
|
|
578
|
+
winsys = ""
|
|
579
|
+
try:
|
|
580
|
+
winsys = master.tk.call("tk", "windowingsystem")
|
|
581
|
+
except Exception:
|
|
582
|
+
winsys = ""
|
|
583
|
+
if winsys != 'aqua':
|
|
584
|
+
dropper = ttk.Label(master, text=PEN, font=('-size 16'))
|
|
585
|
+
ToolTip(dropper, 'color.dropper')
|
|
586
|
+
dropper.pack(side=LEFT, padx=(0, 4))
|
|
587
|
+
dropper.bind("<Button-1>", self._on_show_color_dropper)
|
|
588
|
+
|
|
589
|
+
ok = ttk.Button(
|
|
590
|
+
master,
|
|
591
|
+
accent=PRIMARY,
|
|
592
|
+
text='button.ok',
|
|
593
|
+
command=self._on_ok,
|
|
594
|
+
)
|
|
595
|
+
ok.pack(side=RIGHT)
|
|
596
|
+
|
|
597
|
+
cancel = ttk.Button(
|
|
598
|
+
master,
|
|
599
|
+
accent=SECONDARY,
|
|
600
|
+
text='button.cancel',
|
|
601
|
+
command=self._on_cancel,
|
|
602
|
+
)
|
|
603
|
+
cancel.pack(side=RIGHT)
|
|
604
|
+
|
|
605
|
+
# callbacks ----------------------------------------------------------------
|
|
606
|
+
def _on_show_color_dropper(self, _: tkinter.Event) -> None:
|
|
607
|
+
self._dropper.show()
|
|
608
|
+
|
|
609
|
+
def _trace_dropper_color(self, *_: Any) -> None:
|
|
610
|
+
values = self._dropper.result.get()
|
|
611
|
+
if self._chooser:
|
|
612
|
+
self._chooser.hex.set(values[2])
|
|
613
|
+
self._chooser.sync_color_values('hex')
|
|
614
|
+
|
|
615
|
+
def _on_ok(self) -> None:
|
|
616
|
+
if self._chooser:
|
|
617
|
+
values = self._chooser.get_variables()
|
|
618
|
+
self.result = ColorChoice(
|
|
619
|
+
rgb=(values.r, values.g, values.b),
|
|
620
|
+
hsl=(values.h, values.s, values.l),
|
|
621
|
+
hex=values.hex
|
|
622
|
+
)
|
|
623
|
+
self._emit_result(confirmed=True)
|
|
624
|
+
if self._dialog.toplevel:
|
|
625
|
+
self._dialog.toplevel.after_idle(self._dialog.toplevel.destroy)
|
|
626
|
+
|
|
627
|
+
def _on_cancel(self) -> None:
|
|
628
|
+
self.result = None
|
|
629
|
+
self._emit_result(confirmed=False)
|
|
630
|
+
if self._dialog.toplevel:
|
|
631
|
+
self._dialog.toplevel.after_idle(self._dialog.toplevel.destroy)
|
|
632
|
+
|
|
633
|
+
# API ----------------------------------------------------------------------
|
|
634
|
+
def show(self, position: Optional[tuple[int, int]] = None, modal: bool = True) -> None:
|
|
635
|
+
"""Display the dialog."""
|
|
636
|
+
self.result = None
|
|
637
|
+
self._emitted_result = False
|
|
638
|
+
self._dialog.show(position=position, modal=modal)
|
|
639
|
+
# Ensure result event fires even if consumers prefer post-show access
|
|
640
|
+
if not self._emitted_result:
|
|
641
|
+
self._emit_result(confirmed=self.result is not None)
|
|
642
|
+
|
|
643
|
+
def on_dialog_result(self, callback: Callable[[Any], None]) -> Optional[str]:
|
|
644
|
+
"""Bind a callback fired when the dialog produces a result."""
|
|
645
|
+
target = self._master or self._dialog.toplevel
|
|
646
|
+
if target is None:
|
|
647
|
+
return None
|
|
648
|
+
|
|
649
|
+
def handler(event):
|
|
650
|
+
callback(getattr(event, "data", None))
|
|
651
|
+
|
|
652
|
+
return target.bind("<<DialogResult>>", handler, add="+")
|
|
653
|
+
|
|
654
|
+
def off_dialog_result(self, funcid: str) -> None:
|
|
655
|
+
"""Unbind a previously bound dialog result callback."""
|
|
656
|
+
target = self._master or self._dialog.toplevel
|
|
657
|
+
if target is None:
|
|
658
|
+
return
|
|
659
|
+
target.unbind("<<DialogResult>>", funcid)
|
|
660
|
+
|
|
661
|
+
# helpers ------------------------------------------------------------------
|
|
662
|
+
def _emit_result(self, confirmed: bool) -> None:
|
|
663
|
+
payload = {"result": self.result, "confirmed": confirmed}
|
|
664
|
+
target = self._master or self._dialog.toplevel
|
|
665
|
+
if not target:
|
|
666
|
+
return
|
|
667
|
+
try:
|
|
668
|
+
target.event_generate("<<DialogResult>>", data=payload)
|
|
669
|
+
except Exception:
|
|
670
|
+
try:
|
|
671
|
+
target.event_generate("<<DialogResult>>")
|
|
672
|
+
except Exception:
|
|
673
|
+
pass
|
|
674
|
+
self._emitted_result = True
|