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,851 @@
|
|
|
1
|
+
"""bs.Slider — a themed horizontal or vertical slider widget."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import tkinter as tk
|
|
5
|
+
import tkinter.font as tkfont
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from bootstack.widgets._impl.mixins.configure_mixin import ConfigureDelegationMixin, configure_delegate
|
|
9
|
+
from bootstack.widgets.types import Master, Orient, AccentToken, SurfaceToken, WidgetState
|
|
10
|
+
from bootstack.signals import Signal
|
|
11
|
+
from bootstack.events import SliderEvent, SliderCommitEvent
|
|
12
|
+
from ._shared import (
|
|
13
|
+
HL, HANDLE_SIZE, TRACK_H, HALO_PAD,
|
|
14
|
+
BADGE_H, BADGE_PAD_X, BADGE_GAP, BADGE_FONT_SIZE,
|
|
15
|
+
TICK_GAP, MAJOR_TICK_H, MINOR_TICK_H, LABEL_GAP, LABEL_FONT_SIZE,
|
|
16
|
+
STEP, STEP_LARGE,
|
|
17
|
+
_make_badge, _make_handle, _make_track, _make_focus_ring,
|
|
18
|
+
resolve_colors, get_widget_bg,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Slider(ConfigureDelegationMixin, tk.Frame):
|
|
23
|
+
# Slider manages its own styling and theming; skip autostyle registration
|
|
24
|
+
# for this widget and all tk children created inside it.
|
|
25
|
+
_bs_autostyle = False
|
|
26
|
+
|
|
27
|
+
"""A themed horizontal or vertical slider widget.
|
|
28
|
+
|
|
29
|
+
Replaces `ttk.Scale` with full theming support, optional tick marks,
|
|
30
|
+
value badge, min/max labels, and reactive signal/variable
|
|
31
|
+
binding.
|
|
32
|
+
|
|
33
|
+
The current value is accessible via the `value` property, `get()` /
|
|
34
|
+
`set()` methods, `configure(value=x)`, or a bound `variable` /
|
|
35
|
+
`signal`.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
master: Master = None,
|
|
41
|
+
*,
|
|
42
|
+
minvalue: float = 0,
|
|
43
|
+
maxvalue: float = 100,
|
|
44
|
+
value: float = 0,
|
|
45
|
+
orient: Orient = "horizontal",
|
|
46
|
+
accent: AccentToken = "primary",
|
|
47
|
+
surface: SurfaceToken = "content",
|
|
48
|
+
variable: tk.DoubleVar | None = None,
|
|
49
|
+
signal: Signal[float] | None = None,
|
|
50
|
+
state: WidgetState = "normal",
|
|
51
|
+
show_value: bool = False,
|
|
52
|
+
show_minmax: bool = False,
|
|
53
|
+
tick_interval: float | None = None,
|
|
54
|
+
minor_ticks: int = 0,
|
|
55
|
+
tick_labels: bool = True,
|
|
56
|
+
tick_format: str = "{:.0f}",
|
|
57
|
+
step: float | None = None,
|
|
58
|
+
**kw: Any,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Create a Slider widget.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
master: Parent widget.
|
|
64
|
+
minvalue: Minimum value of the slider range.
|
|
65
|
+
maxvalue: Maximum value of the slider range.
|
|
66
|
+
value: Initial value. Ignored when `variable` or `signal` is provided.
|
|
67
|
+
orient: Orientation — `'horizontal'` or `'vertical'`.
|
|
68
|
+
accent: Accent token controlling fill, handle, and focus ring color.
|
|
69
|
+
surface: Surface token for trough and background color context.
|
|
70
|
+
variable: Tkinter DoubleVar to bind to the slider value.
|
|
71
|
+
See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
|
|
72
|
+
signal: Reactive Signal linked to the slider value.
|
|
73
|
+
state: Widget state — `'normal'` or `'disabled'`.
|
|
74
|
+
show_value: Show a floating badge above (horizontal) or beside
|
|
75
|
+
(vertical) the handle displaying the current value.
|
|
76
|
+
show_minmax: Show `minvalue` and `maxvalue` labels at the
|
|
77
|
+
track ends. Redundant when `tick_interval` is set.
|
|
78
|
+
tick_interval: Spacing between major tick marks. `None` disables ticks.
|
|
79
|
+
minor_ticks: Number of minor tick marks between each major tick.
|
|
80
|
+
tick_labels: Show numeric labels at major tick positions.
|
|
81
|
+
tick_format: Format string for tick and badge labels (e.g. `"{:.1f}°C"`).
|
|
82
|
+
step: Snap increment. When set, click/drag values snap to multiples
|
|
83
|
+
of `step` (measured from `minvalue`) and arrow keys move by `step`.
|
|
84
|
+
`None` leaves the value continuous. Independent of `tick_interval`.
|
|
85
|
+
"""
|
|
86
|
+
self._minvalue = float(minvalue)
|
|
87
|
+
self._maxvalue = float(maxvalue)
|
|
88
|
+
self._step_size = float(step) if step else None
|
|
89
|
+
self._kbd_step = self._step_size if self._step_size else float(STEP)
|
|
90
|
+
self._kbd_step_large = self._step_size * 10 if self._step_size else float(STEP_LARGE)
|
|
91
|
+
self._orient = orient
|
|
92
|
+
self._accent = accent
|
|
93
|
+
self._surface = surface
|
|
94
|
+
self._state = state
|
|
95
|
+
self._show_value = show_value
|
|
96
|
+
self._show_minmax = show_minmax
|
|
97
|
+
self._tick_interval = tick_interval
|
|
98
|
+
self._minor_ticks = minor_ticks
|
|
99
|
+
self._tick_labels = tick_labels
|
|
100
|
+
self._tick_format = tick_format
|
|
101
|
+
|
|
102
|
+
self._handle_size = HANDLE_SIZE
|
|
103
|
+
self._track_h = TRACK_H
|
|
104
|
+
|
|
105
|
+
# Cross-axis allocation (y for horizontal, x for vertical).
|
|
106
|
+
# For vertical sliders the badge sits to the LEFT of the track, so
|
|
107
|
+
# badge_zone must be wide enough for the widest formatted value.
|
|
108
|
+
if show_value:
|
|
109
|
+
if orient == "vertical":
|
|
110
|
+
_bfont = tkfont.Font(family="TkDefaultFont", size=BADGE_FONT_SIZE, weight="bold")
|
|
111
|
+
_bw = max(_bfont.measure(tick_format.format(float(maxvalue))) + BADGE_PAD_X * 2, BADGE_H)
|
|
112
|
+
self._badge_zone = _bw + BADGE_GAP
|
|
113
|
+
else:
|
|
114
|
+
self._badge_zone = BADGE_H + BADGE_GAP
|
|
115
|
+
else:
|
|
116
|
+
self._badge_zone = 0
|
|
117
|
+
self._tick_zone = self._calc_tick_zone()
|
|
118
|
+
# Reserve HALO_PAD on each side of the handle band so the keyboard focus
|
|
119
|
+
# ring (a halo just outside the handle) is never clipped.
|
|
120
|
+
cross_size = self._badge_zone + HALO_PAD + self._handle_size + HALO_PAD + self._tick_zone
|
|
121
|
+
|
|
122
|
+
# Variable / signal binding — always create a Signal so callers get a
|
|
123
|
+
# clean reactive API; derive the tk.DoubleVar from it.
|
|
124
|
+
if signal is not None:
|
|
125
|
+
self._signal: Signal[float] = signal
|
|
126
|
+
elif variable is not None:
|
|
127
|
+
self._signal = Signal(variable.get())
|
|
128
|
+
# Keep the provided variable in sync via trace rather than replacing it
|
|
129
|
+
self._signal.var.trace_add(
|
|
130
|
+
"write", lambda *_: variable.set(self._signal.var.get()))
|
|
131
|
+
variable.trace_add(
|
|
132
|
+
"write", lambda *_: self._signal.var.set(variable.get()))
|
|
133
|
+
else:
|
|
134
|
+
self._signal = Signal(self._snap(float(value)))
|
|
135
|
+
self._var = self._signal.var
|
|
136
|
+
self._prev_value: float = self._var.get()
|
|
137
|
+
|
|
138
|
+
# Image refs (prevent GC)
|
|
139
|
+
self._handle_photo: Any = None
|
|
140
|
+
self._track_photo: Any = None
|
|
141
|
+
|
|
142
|
+
# Badge geometry (set when show_value=True)
|
|
143
|
+
self._badge_w: int = 0
|
|
144
|
+
self._badge_photo: Any = None
|
|
145
|
+
|
|
146
|
+
# Stable canvas width captured in _on_configure — used for badge clamping
|
|
147
|
+
# to break any circular dependency between badge position and frame size.
|
|
148
|
+
self._cw: int = 0
|
|
149
|
+
|
|
150
|
+
# Tick canvas item IDs
|
|
151
|
+
self._tick_items: list[int] = []
|
|
152
|
+
|
|
153
|
+
kw.setdefault('bd', 0)
|
|
154
|
+
kw.setdefault('relief', 'flat')
|
|
155
|
+
|
|
156
|
+
# Resolve theme colors — use parent's actual background so the slider
|
|
157
|
+
# canvas blends with its container surface and the trough is computed
|
|
158
|
+
# against the real surface color, not always #ffffff.
|
|
159
|
+
self._colors = resolve_colors(self._accent, self._surface, get_widget_bg(master))
|
|
160
|
+
|
|
161
|
+
# Build frame + canvas
|
|
162
|
+
# highlightcolor starts as bg (invisible); shown only on keyboard focus
|
|
163
|
+
if self._orient == "horizontal":
|
|
164
|
+
super().__init__(
|
|
165
|
+
master,
|
|
166
|
+
width=1,
|
|
167
|
+
height=cross_size + HL * 2,
|
|
168
|
+
takefocus=True,
|
|
169
|
+
highlightthickness=HL,
|
|
170
|
+
highlightcolor=self._colors['bg'],
|
|
171
|
+
highlightbackground=self._colors['bg'],
|
|
172
|
+
**kw,
|
|
173
|
+
)
|
|
174
|
+
self.pack_propagate(False)
|
|
175
|
+
self._canvas = tk.Canvas(
|
|
176
|
+
self, width=1, height=cross_size,
|
|
177
|
+
bd=0, highlightthickness=0, bg=self._colors['bg'],
|
|
178
|
+
)
|
|
179
|
+
self._canvas.place(x=0, y=0, relwidth=1, height=cross_size)
|
|
180
|
+
else:
|
|
181
|
+
super().__init__(
|
|
182
|
+
master,
|
|
183
|
+
width=cross_size + HL * 2,
|
|
184
|
+
height=1,
|
|
185
|
+
takefocus=True,
|
|
186
|
+
highlightthickness=HL,
|
|
187
|
+
highlightcolor=self._colors['bg'],
|
|
188
|
+
highlightbackground=self._colors['bg'],
|
|
189
|
+
**kw,
|
|
190
|
+
)
|
|
191
|
+
self.pack_propagate(False)
|
|
192
|
+
self._canvas = tk.Canvas(
|
|
193
|
+
self, width=cross_size, height=1,
|
|
194
|
+
bd=0, highlightthickness=0, bg=self._colors['bg'],
|
|
195
|
+
)
|
|
196
|
+
self._canvas.place(x=0, y=0, relheight=1, width=cross_size)
|
|
197
|
+
|
|
198
|
+
# Center of the track in the cross axis (y for horizontal, x for vertical)
|
|
199
|
+
self._track_center = self._badge_zone + HALO_PAD + self._handle_size // 2
|
|
200
|
+
|
|
201
|
+
# Canvas items — track must be below handle in z-order
|
|
202
|
+
if self._orient == "horizontal":
|
|
203
|
+
self._track_item = self._canvas.create_image(0, self._track_center, anchor="w")
|
|
204
|
+
self._handle_item = self._canvas.create_image(0, self._track_center, anchor="center")
|
|
205
|
+
else:
|
|
206
|
+
self._track_item = self._canvas.create_image(self._track_center, 0, anchor="n")
|
|
207
|
+
self._handle_item = self._canvas.create_image(self._track_center, 0, anchor="center")
|
|
208
|
+
|
|
209
|
+
# Keyboard focus ring — drawn ON the handle (a ring just inside its edge,
|
|
210
|
+
# transparent center), above it in z-order; hidden until the widget takes
|
|
211
|
+
# keyboard focus. Kept within the handle footprint (no overhang) so the
|
|
212
|
+
# track can run edge-to-edge and align with neighboring form fields.
|
|
213
|
+
self._focus_ring_diam = self._handle_size - 2
|
|
214
|
+
self._focus_ring_photo = _make_focus_ring(self._focus_ring_diam, self._colors['focus'])
|
|
215
|
+
self._focus_ring_item = self._canvas.create_image(
|
|
216
|
+
0, 0, anchor="center", image=self._focus_ring_photo, state="hidden")
|
|
217
|
+
|
|
218
|
+
# Badge items — PIL image background + text item on top
|
|
219
|
+
self._badge_item: int | None = None
|
|
220
|
+
self._badge_text_item: int | None = None
|
|
221
|
+
if self._show_value:
|
|
222
|
+
badge_font = tkfont.Font(family="TkDefaultFont", size=BADGE_FONT_SIZE, weight="bold")
|
|
223
|
+
max_text = self._tick_format.format(self._maxvalue)
|
|
224
|
+
self._badge_w = max(badge_font.measure(max_text) + BADGE_PAD_X * 2, BADGE_H)
|
|
225
|
+
self._badge_photo = _make_badge(self._badge_w, BADGE_H, self._colors['badge_bg'])
|
|
226
|
+
self._badge_item = self._canvas.create_image(0, 0, anchor="nw", image=self._badge_photo)
|
|
227
|
+
self._badge_text_item = self._canvas.create_text(
|
|
228
|
+
0, 0, text="", fill=self._colors['badge_text'],
|
|
229
|
+
font=badge_font, anchor="center",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Min/max end-labels (only when ticks are not drawn)
|
|
233
|
+
self._minlabel_item: int | None = None
|
|
234
|
+
self._maxlabel_item: int | None = None
|
|
235
|
+
if self._show_minmax and self._tick_interval is None:
|
|
236
|
+
label_font = tkfont.Font(family="TkDefaultFont", size=LABEL_FONT_SIZE)
|
|
237
|
+
if self._orient == "horizontal":
|
|
238
|
+
label_y = self._track_center + self._handle_size // 2 + TICK_GAP + LABEL_GAP
|
|
239
|
+
self._minlabel_item = self._canvas.create_text(
|
|
240
|
+
0, label_y,
|
|
241
|
+
text=self._tick_format.format(self._minvalue),
|
|
242
|
+
fill=self._colors['tick'], font=label_font, anchor="nw",
|
|
243
|
+
)
|
|
244
|
+
self._maxlabel_item = self._canvas.create_text(
|
|
245
|
+
0, label_y,
|
|
246
|
+
text=self._tick_format.format(self._maxvalue),
|
|
247
|
+
fill=self._colors['tick'], font=label_font, anchor="ne",
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
label_x = self._track_center + self._handle_size // 2 + TICK_GAP + LABEL_GAP
|
|
251
|
+
self._minlabel_item = self._canvas.create_text(
|
|
252
|
+
label_x, 0,
|
|
253
|
+
text=self._tick_format.format(self._minvalue),
|
|
254
|
+
fill=self._colors['tick'], font=label_font, anchor="w",
|
|
255
|
+
)
|
|
256
|
+
self._maxlabel_item = self._canvas.create_text(
|
|
257
|
+
label_x, 0,
|
|
258
|
+
text=self._tick_format.format(self._maxvalue),
|
|
259
|
+
fill=self._colors['tick'], font=label_font, anchor="w",
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
self._render_handle(self._colors)
|
|
263
|
+
|
|
264
|
+
# Keyboard-vs-mouse focus tracking (ring only on Tab navigation)
|
|
265
|
+
self._mouse_pressed = False
|
|
266
|
+
self._kbd_focused = False # True while the widget holds keyboard focus
|
|
267
|
+
|
|
268
|
+
# Input bindings
|
|
269
|
+
self._canvas.bind("<Button-1>", self._on_click)
|
|
270
|
+
self._canvas.bind("<B1-Motion>", self._on_drag)
|
|
271
|
+
self._canvas.bind("<Button-1>", self._mouse_focus_set, add=True)
|
|
272
|
+
self._canvas.bind("<ButtonRelease-1>", self._on_release)
|
|
273
|
+
self._canvas.tag_bind(self._handle_item, "<Button-1>", self._on_click)
|
|
274
|
+
self._canvas.tag_bind(self._handle_item, "<B1-Motion>", self._on_drag)
|
|
275
|
+
self._canvas.tag_bind(self._handle_item, "<Button-1>", self._mouse_focus_set, add=True)
|
|
276
|
+
self._canvas.tag_bind(self._handle_item, "<ButtonRelease-1>", self._on_release)
|
|
277
|
+
|
|
278
|
+
self.bind("<FocusIn>", self._on_focus_in)
|
|
279
|
+
self.bind("<FocusOut>", self._on_focus_out)
|
|
280
|
+
self.bind("<Left>", lambda e: self._step(-self._kbd_step))
|
|
281
|
+
self.bind("<Right>", lambda e: self._step(+self._kbd_step))
|
|
282
|
+
self.bind("<Down>", lambda e: self._step(-self._kbd_step))
|
|
283
|
+
self.bind("<Up>", lambda e: self._step(+self._kbd_step))
|
|
284
|
+
self.bind("<Shift-Left>", lambda e: self._step(-self._kbd_step_large))
|
|
285
|
+
self.bind("<Shift-Right>", lambda e: self._step(+self._kbd_step_large))
|
|
286
|
+
self.bind("<Home>", lambda e: self._jump(self._minvalue))
|
|
287
|
+
self.bind("<End>", lambda e: self._jump(self._maxvalue))
|
|
288
|
+
self._canvas.bind("<Configure>", self._on_configure)
|
|
289
|
+
|
|
290
|
+
self._var.trace_add("write", self._on_var_write)
|
|
291
|
+
# The unified theme walk re-resolves the track/handle colors via
|
|
292
|
+
# `_bs_apply_theme` on a theme change. The <Map> flush also covers a
|
|
293
|
+
# redraw deferred while the slider was off-screen (value/size changes).
|
|
294
|
+
self.bind("<Map>", lambda e: self._on_map(), add="+")
|
|
295
|
+
|
|
296
|
+
# ------------------------------------------------------------------
|
|
297
|
+
# Layout helpers
|
|
298
|
+
# ------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
def _calc_tick_zone(self) -> int:
|
|
301
|
+
if self._tick_interval is not None:
|
|
302
|
+
base = TICK_GAP + MAJOR_TICK_H
|
|
303
|
+
if self._tick_labels:
|
|
304
|
+
if self._orient == "vertical":
|
|
305
|
+
base += LABEL_GAP + self._measure_max_label_width()
|
|
306
|
+
else:
|
|
307
|
+
base += LABEL_GAP + LABEL_FONT_SIZE + 4
|
|
308
|
+
return base
|
|
309
|
+
if self._show_minmax:
|
|
310
|
+
if self._orient == "vertical":
|
|
311
|
+
return TICK_GAP + LABEL_GAP + self._measure_max_label_width()
|
|
312
|
+
return TICK_GAP + LABEL_GAP + LABEL_FONT_SIZE + 4
|
|
313
|
+
return 0
|
|
314
|
+
|
|
315
|
+
def _measure_max_label_width(self) -> int:
|
|
316
|
+
"""Return pixel width of the widest tick/minmax label."""
|
|
317
|
+
font = tkfont.Font(family="TkDefaultFont", size=LABEL_FONT_SIZE)
|
|
318
|
+
candidates = [self._tick_format.format(self._minvalue),
|
|
319
|
+
self._tick_format.format(self._maxvalue)]
|
|
320
|
+
return max(font.measure(t) for t in candidates) + 4
|
|
321
|
+
|
|
322
|
+
def _track_range(self) -> tuple[int, int]:
|
|
323
|
+
"""Return (start, end) handle travel range in canvas coords (main axis)."""
|
|
324
|
+
# Inset by the handle half-width only, so at min/max the handle's edge sits
|
|
325
|
+
# flush with the canvas edge. The focus ring lives inside the handle now, so
|
|
326
|
+
# no extra overhang has to be reserved.
|
|
327
|
+
edge = self._handle_size // 2
|
|
328
|
+
if self._orient == "horizontal":
|
|
329
|
+
# _cw is the canvas's exact allocated width (from canvas <Configure>).
|
|
330
|
+
w = self._cw if self._cw > 0 else max(1, self._canvas.winfo_width())
|
|
331
|
+
return edge, max(edge + 1, w - edge - 1)
|
|
332
|
+
else:
|
|
333
|
+
h = self.winfo_height() - 2 * HL
|
|
334
|
+
return edge, max(edge + 1, h - edge - 1)
|
|
335
|
+
|
|
336
|
+
def _value_to_pos(self, val: float) -> int:
|
|
337
|
+
"""Map a value to a canvas pixel position along the main axis."""
|
|
338
|
+
start, end = self._track_range()
|
|
339
|
+
span = self._maxvalue - self._minvalue
|
|
340
|
+
if span == 0:
|
|
341
|
+
return start
|
|
342
|
+
ratio = max(0.0, min(1.0, (val - self._minvalue) / span))
|
|
343
|
+
if self._orient == "vertical":
|
|
344
|
+
ratio = 1.0 - ratio # vertical: top = max, bottom = min
|
|
345
|
+
return start + int(ratio * (end - start))
|
|
346
|
+
|
|
347
|
+
def _track_extent(self) -> int:
|
|
348
|
+
"""Length of the full-width track (handle's min edge to max edge).
|
|
349
|
+
|
|
350
|
+
The track spans `[0, extent]` so it meets the container edges and aligns
|
|
351
|
+
with neighboring form fields; the handle still travels `[r, extent - r]`
|
|
352
|
+
(its center), so ticks/labels — placed at the handle centers — sit `r`
|
|
353
|
+
inside each end and the handle covers that lead-in/out at the extremes.
|
|
354
|
+
"""
|
|
355
|
+
_, end = self._track_range()
|
|
356
|
+
return end + self._handle_size // 2
|
|
357
|
+
|
|
358
|
+
def _pos_to_value(self, pos: int) -> float:
|
|
359
|
+
"""Map a canvas pixel position to a slider value."""
|
|
360
|
+
start, end = self._track_range()
|
|
361
|
+
if end == start:
|
|
362
|
+
return self._minvalue
|
|
363
|
+
ratio = max(0.0, min(1.0, (pos - start) / (end - start)))
|
|
364
|
+
if self._orient == "vertical":
|
|
365
|
+
ratio = 1.0 - ratio
|
|
366
|
+
return self._minvalue + ratio * (self._maxvalue - self._minvalue)
|
|
367
|
+
|
|
368
|
+
def _snap(self, v: float) -> float:
|
|
369
|
+
"""Clamp `v` to [min, max] and, when `step` is set, snap it to the
|
|
370
|
+
nearest grid point (measured from `minvalue`). `maxvalue` stays reachable
|
|
371
|
+
even when the range is not an exact multiple of `step`."""
|
|
372
|
+
v = max(self._minvalue, min(self._maxvalue, float(v)))
|
|
373
|
+
if not self._step_size:
|
|
374
|
+
return v
|
|
375
|
+
grid = self._minvalue + round((v - self._minvalue) / self._step_size) * self._step_size
|
|
376
|
+
grid = max(self._minvalue, min(self._maxvalue, grid))
|
|
377
|
+
# Let the exact maximum win when the value is closer to it than to the
|
|
378
|
+
# last grid point (an off-grid max would otherwise be unreachable).
|
|
379
|
+
if abs(v - self._maxvalue) < abs(v - grid):
|
|
380
|
+
return self._maxvalue
|
|
381
|
+
return grid
|
|
382
|
+
|
|
383
|
+
# ------------------------------------------------------------------
|
|
384
|
+
# Rendering helpers
|
|
385
|
+
# ------------------------------------------------------------------
|
|
386
|
+
|
|
387
|
+
def _render_handle(self, colors: dict[str, str]) -> None:
|
|
388
|
+
fill = colors['handle_disabled'] if self._state == "disabled" else colors['handle']
|
|
389
|
+
self._handle_photo = _make_handle(fill, colors['ring'], colors['handle_border'])
|
|
390
|
+
self._canvas.itemconfig(self._handle_item, image=self._handle_photo)
|
|
391
|
+
|
|
392
|
+
def _update_focus_ring(self) -> None:
|
|
393
|
+
"""Position the focus halo over the handle when the widget holds keyboard
|
|
394
|
+
focus; hide it otherwise."""
|
|
395
|
+
item = getattr(self, "_focus_ring_item", None)
|
|
396
|
+
if item is None:
|
|
397
|
+
return
|
|
398
|
+
if not self._kbd_focused or self._state == "disabled":
|
|
399
|
+
self._canvas.itemconfigure(item, state="hidden")
|
|
400
|
+
return
|
|
401
|
+
pos = self._value_to_pos(self._var.get())
|
|
402
|
+
if self._orient == "horizontal":
|
|
403
|
+
self._canvas.coords(item, pos, self._track_center)
|
|
404
|
+
else:
|
|
405
|
+
self._canvas.coords(item, self._track_center, pos)
|
|
406
|
+
self._canvas.itemconfigure(item, state="normal")
|
|
407
|
+
self._canvas.tag_raise(item)
|
|
408
|
+
|
|
409
|
+
def _update_badge_colors(self, colors: dict[str, str]) -> None:
|
|
410
|
+
if self._badge_item is not None:
|
|
411
|
+
self._badge_photo = _make_badge(self._badge_w, BADGE_H, colors['badge_bg'])
|
|
412
|
+
self._canvas.itemconfig(self._badge_item, image=self._badge_photo)
|
|
413
|
+
if self._badge_text_item is not None:
|
|
414
|
+
self._canvas.itemconfig(self._badge_text_item, fill=colors['badge_text'])
|
|
415
|
+
|
|
416
|
+
# ------------------------------------------------------------------
|
|
417
|
+
# Sync: value → visual
|
|
418
|
+
# ------------------------------------------------------------------
|
|
419
|
+
|
|
420
|
+
def _sync(self) -> None:
|
|
421
|
+
val = self._var.get()
|
|
422
|
+
pos = self._value_to_pos(val)
|
|
423
|
+
# Full-width track: spans the handle's edge-to-edge travel (0 .. extent),
|
|
424
|
+
# so it meets the container edges and the handle covers the rounded ends.
|
|
425
|
+
extent = self._track_extent()
|
|
426
|
+
|
|
427
|
+
fill_c = self._colors['fill_disabled'] if self._state == "disabled" else self._colors['fill']
|
|
428
|
+
|
|
429
|
+
if self._orient == "horizontal":
|
|
430
|
+
photo = _make_track(extent, self._track_h, 0, pos,
|
|
431
|
+
self._colors['trough'], fill_c)
|
|
432
|
+
if photo:
|
|
433
|
+
self._track_photo = photo
|
|
434
|
+
self._canvas.coords(self._track_item, 0, self._track_center)
|
|
435
|
+
self._canvas.itemconfig(self._track_item, image=self._track_photo)
|
|
436
|
+
self._canvas.coords(self._handle_item, pos, self._track_center)
|
|
437
|
+
if self._show_value:
|
|
438
|
+
self._update_badge_h(pos, val)
|
|
439
|
+
if self._show_minmax and self._tick_interval is None:
|
|
440
|
+
label_y = self._track_center + self._handle_size // 2 + TICK_GAP + LABEL_GAP
|
|
441
|
+
self._canvas.coords(self._minlabel_item, self._value_to_pos(self._minvalue), label_y)
|
|
442
|
+
self._canvas.coords(self._maxlabel_item, self._value_to_pos(self._maxvalue), label_y)
|
|
443
|
+
else:
|
|
444
|
+
# Vertical: bottom = min, top = max. fill_px = pixels from bottom.
|
|
445
|
+
fill_px = extent - pos
|
|
446
|
+
photo = _make_track(extent, self._track_h, 0, fill_px,
|
|
447
|
+
self._colors['trough'], fill_c,
|
|
448
|
+
vertical=True)
|
|
449
|
+
if photo:
|
|
450
|
+
self._track_photo = photo
|
|
451
|
+
# After rotate(90), image is track_h wide × extent tall; anchor "n"
|
|
452
|
+
self._canvas.coords(self._track_item, self._track_center, 0)
|
|
453
|
+
self._canvas.itemconfig(self._track_item, image=self._track_photo)
|
|
454
|
+
self._canvas.coords(self._handle_item, self._track_center, pos)
|
|
455
|
+
if self._show_value:
|
|
456
|
+
self._update_badge_v(pos, val)
|
|
457
|
+
if self._show_minmax and self._tick_interval is None:
|
|
458
|
+
label_x = self._track_center + self._handle_size // 2 + TICK_GAP + LABEL_GAP
|
|
459
|
+
self._canvas.coords(self._minlabel_item, label_x, self._value_to_pos(self._minvalue))
|
|
460
|
+
self._canvas.coords(self._maxlabel_item, label_x, self._value_to_pos(self._maxvalue))
|
|
461
|
+
|
|
462
|
+
self._update_focus_ring() # keep the halo on the handle
|
|
463
|
+
|
|
464
|
+
def _update_badge_h(self, handle_x: int, val: float) -> None:
|
|
465
|
+
if self._badge_item is None:
|
|
466
|
+
return
|
|
467
|
+
bw = self._badge_w
|
|
468
|
+
# Use stable _cw captured in _on_configure; fall back to live query only if
|
|
469
|
+
# _on_configure has not fired yet (widget not yet laid out).
|
|
470
|
+
cw = self._cw if self._cw > 0 else max(1, self.winfo_width() - 2 * HL)
|
|
471
|
+
# Clamp so image occupies [badge_x, badge_x+bw-1] ⊆ [0, cw-1].
|
|
472
|
+
badge_x = max(0, min(handle_x - bw // 2, cw - bw))
|
|
473
|
+
badge_y = self._badge_zone - BADGE_H - 2
|
|
474
|
+
self._canvas.coords(self._badge_item, badge_x, badge_y)
|
|
475
|
+
if self._badge_text_item is not None:
|
|
476
|
+
self._canvas.coords(self._badge_text_item, badge_x + bw // 2, badge_y + BADGE_H // 2)
|
|
477
|
+
self._canvas.itemconfig(self._badge_text_item, text=self._tick_format.format(val))
|
|
478
|
+
|
|
479
|
+
def _update_badge_v(self, handle_y: int, val: float) -> None:
|
|
480
|
+
if self._badge_item is None:
|
|
481
|
+
return
|
|
482
|
+
bw = self._badge_w
|
|
483
|
+
ch = self.winfo_height() - 2 * HL
|
|
484
|
+
badge_x = self._badge_zone - bw - 2
|
|
485
|
+
badge_y = max(0, min(handle_y - BADGE_H // 2, ch - BADGE_H))
|
|
486
|
+
self._canvas.coords(self._badge_item, badge_x, badge_y)
|
|
487
|
+
if self._badge_text_item is not None:
|
|
488
|
+
self._canvas.coords(self._badge_text_item, badge_x + bw // 2, badge_y + BADGE_H // 2)
|
|
489
|
+
self._canvas.itemconfig(self._badge_text_item, text=self._tick_format.format(val))
|
|
490
|
+
|
|
491
|
+
# ------------------------------------------------------------------
|
|
492
|
+
# Tick marks
|
|
493
|
+
# ------------------------------------------------------------------
|
|
494
|
+
|
|
495
|
+
def _setup_ticks(self) -> None:
|
|
496
|
+
if self._tick_interval is None:
|
|
497
|
+
return
|
|
498
|
+
for item in self._tick_items:
|
|
499
|
+
self._canvas.delete(item)
|
|
500
|
+
self._tick_items.clear()
|
|
501
|
+
|
|
502
|
+
tick_color = self._colors['tick']
|
|
503
|
+
font = tkfont.Font(family="TkDefaultFont", size=LABEL_FONT_SIZE)
|
|
504
|
+
start, end = self._track_range()
|
|
505
|
+
span = self._maxvalue - self._minvalue
|
|
506
|
+
if span <= 0:
|
|
507
|
+
return
|
|
508
|
+
|
|
509
|
+
if self._orient == "horizontal":
|
|
510
|
+
major_y0 = self._track_center + self._handle_size // 2 + TICK_GAP
|
|
511
|
+
major_y1 = major_y0 + MAJOR_TICK_H
|
|
512
|
+
minor_y1 = major_y0 + MINOR_TICK_H
|
|
513
|
+
label_y = major_y1 + LABEL_GAP
|
|
514
|
+
|
|
515
|
+
val = self._minvalue
|
|
516
|
+
while val <= self._maxvalue + 1e-9:
|
|
517
|
+
x = self._value_to_pos(val)
|
|
518
|
+
self._tick_items.append(
|
|
519
|
+
self._canvas.create_line(x, major_y0, x, major_y1, fill=tick_color, width=1))
|
|
520
|
+
if self._tick_labels:
|
|
521
|
+
self._tick_items.append(
|
|
522
|
+
self._canvas.create_text(x, label_y, text=self._tick_format.format(val),
|
|
523
|
+
fill=tick_color, font=font, anchor="n"))
|
|
524
|
+
if self._minor_ticks > 0:
|
|
525
|
+
minor_step = self._tick_interval / (self._minor_ticks + 1)
|
|
526
|
+
for i in range(1, self._minor_ticks + 1):
|
|
527
|
+
mv = val + i * minor_step
|
|
528
|
+
if mv >= self._maxvalue - 1e-9:
|
|
529
|
+
break
|
|
530
|
+
mx = self._value_to_pos(mv)
|
|
531
|
+
self._tick_items.append(
|
|
532
|
+
self._canvas.create_line(mx, major_y0, mx, minor_y1,
|
|
533
|
+
fill=tick_color, width=1))
|
|
534
|
+
val += self._tick_interval
|
|
535
|
+
else:
|
|
536
|
+
major_x0 = self._track_center + self._handle_size // 2 + TICK_GAP
|
|
537
|
+
major_x1 = major_x0 + MAJOR_TICK_H
|
|
538
|
+
minor_x1 = major_x0 + MINOR_TICK_H
|
|
539
|
+
label_x = major_x1 + LABEL_GAP
|
|
540
|
+
|
|
541
|
+
val = self._minvalue
|
|
542
|
+
while val <= self._maxvalue + 1e-9:
|
|
543
|
+
y = self._value_to_pos(val)
|
|
544
|
+
self._tick_items.append(
|
|
545
|
+
self._canvas.create_line(major_x0, y, major_x1, y, fill=tick_color, width=1))
|
|
546
|
+
if self._tick_labels:
|
|
547
|
+
self._tick_items.append(
|
|
548
|
+
self._canvas.create_text(label_x, y, text=self._tick_format.format(val),
|
|
549
|
+
fill=tick_color, font=font, anchor="w"))
|
|
550
|
+
if self._minor_ticks > 0:
|
|
551
|
+
minor_step = self._tick_interval / (self._minor_ticks + 1)
|
|
552
|
+
for i in range(1, self._minor_ticks + 1):
|
|
553
|
+
mv = val + i * minor_step
|
|
554
|
+
if mv >= self._maxvalue - 1e-9:
|
|
555
|
+
break
|
|
556
|
+
my = self._value_to_pos(mv)
|
|
557
|
+
self._tick_items.append(
|
|
558
|
+
self._canvas.create_line(major_x0, my, minor_x1, my,
|
|
559
|
+
fill=tick_color, width=1))
|
|
560
|
+
val += self._tick_interval
|
|
561
|
+
|
|
562
|
+
# ------------------------------------------------------------------
|
|
563
|
+
# Event handlers
|
|
564
|
+
# ------------------------------------------------------------------
|
|
565
|
+
|
|
566
|
+
def _on_configure(self, event: tk.Event) -> None:
|
|
567
|
+
# Bound to canvas <Configure>, so event.width/height IS the canvas size —
|
|
568
|
+
# no border arithmetic needed.
|
|
569
|
+
if self._orient == "horizontal" and event.width > self._handle_size:
|
|
570
|
+
self._cw = event.width
|
|
571
|
+
self._canvas.configure(scrollregion=(0, 0, event.width, event.height))
|
|
572
|
+
self._setup_ticks()
|
|
573
|
+
self._sync()
|
|
574
|
+
elif self._orient == "vertical" and event.height > self._handle_size:
|
|
575
|
+
self._canvas.configure(scrollregion=(0, 0, event.width, event.height))
|
|
576
|
+
self._setup_ticks()
|
|
577
|
+
self._sync()
|
|
578
|
+
|
|
579
|
+
def _on_var_write(self, *_: Any) -> None:
|
|
580
|
+
self._sync()
|
|
581
|
+
val = self._var.get()
|
|
582
|
+
self.event_generate("<<Change>>", data=SliderEvent(value=val, prev_value=self._prev_value))
|
|
583
|
+
self._prev_value = val
|
|
584
|
+
|
|
585
|
+
def _mouse_focus_set(self, _event: tk.Event) -> None:
|
|
586
|
+
self._mouse_pressed = True
|
|
587
|
+
self.focus_set()
|
|
588
|
+
|
|
589
|
+
def _on_focus_in(self, _event: tk.Event) -> None:
|
|
590
|
+
if self._mouse_pressed:
|
|
591
|
+
self._mouse_pressed = False
|
|
592
|
+
return
|
|
593
|
+
self._kbd_focused = True
|
|
594
|
+
# The handle halo is the focus indicator — no whole-widget frame ring.
|
|
595
|
+
self._update_focus_ring()
|
|
596
|
+
|
|
597
|
+
def _on_focus_out(self, _event: tk.Event) -> None:
|
|
598
|
+
self._kbd_focused = False
|
|
599
|
+
self._update_focus_ring()
|
|
600
|
+
|
|
601
|
+
def _on_click(self, event: tk.Event) -> None:
|
|
602
|
+
if self._state == "disabled":
|
|
603
|
+
return
|
|
604
|
+
# event.x/y are already in canvas coordinates — no winfo_x/y offset needed.
|
|
605
|
+
if self._orient == "horizontal":
|
|
606
|
+
self._var.set(self._snap(self._pos_to_value(event.x)))
|
|
607
|
+
else:
|
|
608
|
+
self._var.set(self._snap(self._pos_to_value(event.y)))
|
|
609
|
+
|
|
610
|
+
def _on_drag(self, event: tk.Event) -> None:
|
|
611
|
+
self._on_click(event)
|
|
612
|
+
|
|
613
|
+
def _on_release(self, event: tk.Event) -> None:
|
|
614
|
+
if self._state == "disabled":
|
|
615
|
+
return
|
|
616
|
+
val = self._var.get()
|
|
617
|
+
self.event_generate("<<Commit>>", data=SliderCommitEvent(value=val))
|
|
618
|
+
|
|
619
|
+
def _step(self, amount: float) -> None:
|
|
620
|
+
if self._state == "disabled":
|
|
621
|
+
return
|
|
622
|
+
new = self._snap(self._var.get() + amount)
|
|
623
|
+
self._var.set(new)
|
|
624
|
+
self.event_generate("<<Commit>>", data=SliderCommitEvent(value=new))
|
|
625
|
+
|
|
626
|
+
def _jump(self, value: float) -> None:
|
|
627
|
+
"""Keyboard jump (Home/End) — set then commit, like _step does."""
|
|
628
|
+
if self._state == "disabled":
|
|
629
|
+
return
|
|
630
|
+
new = max(self._minvalue, min(self._maxvalue, float(value)))
|
|
631
|
+
self._var.set(new)
|
|
632
|
+
self.event_generate("<<Commit>>", data=SliderCommitEvent(value=new))
|
|
633
|
+
|
|
634
|
+
def _reclamp(self) -> None:
|
|
635
|
+
"""Pull the current value back into [min, max] (and onto the step grid)
|
|
636
|
+
after a range or step change."""
|
|
637
|
+
cur = self._var.get()
|
|
638
|
+
clamped = self._snap(cur)
|
|
639
|
+
if clamped != cur:
|
|
640
|
+
self._var.set(clamped) # trace fires _sync + <<Change>>
|
|
641
|
+
else:
|
|
642
|
+
self._sync()
|
|
643
|
+
|
|
644
|
+
def _on_map(self) -> None:
|
|
645
|
+
if getattr(self, '_theme_update_pending', False):
|
|
646
|
+
self._on_theme_changed()
|
|
647
|
+
|
|
648
|
+
def _bs_apply_theme(self) -> None:
|
|
649
|
+
"""Theme walk hook — re-resolve colors and redraw track, handle, badge,
|
|
650
|
+
focus halo, and tick marks + tick labels. The walk owns visibility
|
|
651
|
+
gating, so this always repaints (no early-out).
|
|
652
|
+
"""
|
|
653
|
+
self._theme_update_pending = False
|
|
654
|
+
self._colors = resolve_colors(self._accent, self._surface, get_widget_bg(self.master))
|
|
655
|
+
colors = self._colors
|
|
656
|
+
self.configure(
|
|
657
|
+
highlightbackground=colors['bg'],
|
|
658
|
+
highlightcolor=colors['bg'], # reset to hidden; re-shown on next keyboard focus
|
|
659
|
+
)
|
|
660
|
+
self._canvas.configure(bg=colors['bg'])
|
|
661
|
+
self._render_handle(colors)
|
|
662
|
+
self._update_badge_colors(colors)
|
|
663
|
+
# Recolor the focus halo to the new theme's focus color.
|
|
664
|
+
self._focus_ring_photo = _make_focus_ring(self._focus_ring_diam, colors['focus'])
|
|
665
|
+
self._canvas.itemconfig(self._focus_ring_item, image=self._focus_ring_photo)
|
|
666
|
+
if self._minlabel_item is not None:
|
|
667
|
+
self._canvas.itemconfig(self._minlabel_item, fill=colors['tick'])
|
|
668
|
+
if self._maxlabel_item is not None:
|
|
669
|
+
self._canvas.itemconfig(self._maxlabel_item, fill=colors['tick'])
|
|
670
|
+
self._setup_ticks() # redraws ticks + tick labels with colors['tick']
|
|
671
|
+
self._sync() # _sync repositions/shows the halo as needed
|
|
672
|
+
|
|
673
|
+
def _on_theme_changed(self) -> None:
|
|
674
|
+
# Value/size redraws keep their own off-screen deferral (flushed on
|
|
675
|
+
# <Map>); a genuine theme change is driven through _bs_apply_theme by the
|
|
676
|
+
# unified theme walk.
|
|
677
|
+
if not self.winfo_viewable():
|
|
678
|
+
self._theme_update_pending = True
|
|
679
|
+
return
|
|
680
|
+
self._bs_apply_theme()
|
|
681
|
+
|
|
682
|
+
# ------------------------------------------------------------------
|
|
683
|
+
# Public API
|
|
684
|
+
# ------------------------------------------------------------------
|
|
685
|
+
|
|
686
|
+
@property
|
|
687
|
+
def signal(self) -> Signal[float]:
|
|
688
|
+
"""Reactive Signal linked to the slider value."""
|
|
689
|
+
return self._signal
|
|
690
|
+
|
|
691
|
+
@property
|
|
692
|
+
def value(self) -> float:
|
|
693
|
+
"""Current slider value."""
|
|
694
|
+
return self._var.get()
|
|
695
|
+
|
|
696
|
+
def get(self) -> float:
|
|
697
|
+
"""Return the current slider value."""
|
|
698
|
+
return self._var.get()
|
|
699
|
+
|
|
700
|
+
def set(self, value: float) -> None:
|
|
701
|
+
"""Set the slider to a new value, clamped to [minvalue, maxvalue] and
|
|
702
|
+
snapped to the step grid when `step` is set.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
value: New value to set.
|
|
706
|
+
"""
|
|
707
|
+
self._var.set(self._snap(float(value)))
|
|
708
|
+
|
|
709
|
+
def on_changed(self, callback: Callable[[SliderEvent], None]) -> str:
|
|
710
|
+
"""Register a callback for `<<Change>>` events.
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
callback: Called with a Tkinter event; `event.value` is
|
|
714
|
+
the current value and `event.prev_value` is the
|
|
715
|
+
value before the change.
|
|
716
|
+
|
|
717
|
+
Returns:
|
|
718
|
+
Bind ID — pass to `off_changed()` to unsubscribe.
|
|
719
|
+
"""
|
|
720
|
+
return self.bind("<<Change>>", callback, add="+")
|
|
721
|
+
|
|
722
|
+
def off_changed(self, bind_id: str | None = None) -> None:
|
|
723
|
+
"""Unsubscribe from `<<Change>>`.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
bind_id: ID returned by `on_changed()`. If None, removes all.
|
|
727
|
+
"""
|
|
728
|
+
self.unbind("<<Change>>", bind_id)
|
|
729
|
+
|
|
730
|
+
def on_commit(self, callback: Callable[[SliderCommitEvent], None]) -> str:
|
|
731
|
+
"""Register a callback for `<<Commit>>` events.
|
|
732
|
+
|
|
733
|
+
Fires on mouse release and keyboard commit, not continuously during drag.
|
|
734
|
+
Use for expensive downstream operations.
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
callback: Called with a Tkinter event; `event.value` is
|
|
738
|
+
the committed value.
|
|
739
|
+
|
|
740
|
+
Returns:
|
|
741
|
+
Bind ID — pass to `off_commit()` to unsubscribe.
|
|
742
|
+
"""
|
|
743
|
+
return self.bind("<<Commit>>", callback, add="+")
|
|
744
|
+
|
|
745
|
+
def off_commit(self, bind_id: str | None = None) -> None:
|
|
746
|
+
"""Unsubscribe from `<<Commit>>`.
|
|
747
|
+
|
|
748
|
+
Args:
|
|
749
|
+
bind_id: ID returned by `on_commit()`. If None, removes all.
|
|
750
|
+
"""
|
|
751
|
+
self.unbind("<<Commit>>", bind_id)
|
|
752
|
+
|
|
753
|
+
@property
|
|
754
|
+
def variable(self) -> tk.DoubleVar:
|
|
755
|
+
"""Underlying Tkinter DoubleVar.
|
|
756
|
+
|
|
757
|
+
See [tkinter Variables](https://docs.python.org/3/library/tkinter.html#tkinter-variables).
|
|
758
|
+
"""
|
|
759
|
+
return self._var
|
|
760
|
+
|
|
761
|
+
# ------------------------------------------------------------------
|
|
762
|
+
# Configure delegates
|
|
763
|
+
# ------------------------------------------------------------------
|
|
764
|
+
|
|
765
|
+
@configure_delegate('value')
|
|
766
|
+
def _delegate_value(self, value=None):
|
|
767
|
+
if value is None:
|
|
768
|
+
return self._var.get()
|
|
769
|
+
self.set(value)
|
|
770
|
+
|
|
771
|
+
@configure_delegate('minvalue')
|
|
772
|
+
def _delegate_minvalue(self, value=None):
|
|
773
|
+
if value is None:
|
|
774
|
+
return self._minvalue
|
|
775
|
+
self._minvalue = float(value)
|
|
776
|
+
self._setup_ticks()
|
|
777
|
+
self._reclamp()
|
|
778
|
+
|
|
779
|
+
@configure_delegate('maxvalue')
|
|
780
|
+
def _delegate_maxvalue(self, value=None):
|
|
781
|
+
if value is None:
|
|
782
|
+
return self._maxvalue
|
|
783
|
+
self._maxvalue = float(value)
|
|
784
|
+
self._setup_ticks()
|
|
785
|
+
self._reclamp()
|
|
786
|
+
|
|
787
|
+
@configure_delegate('step')
|
|
788
|
+
def _delegate_step(self, value=None):
|
|
789
|
+
if value is None:
|
|
790
|
+
return self._step_size
|
|
791
|
+
self._step_size = float(value) if value else None
|
|
792
|
+
self._kbd_step = self._step_size if self._step_size else float(STEP)
|
|
793
|
+
self._kbd_step_large = self._step_size * 10 if self._step_size else float(STEP_LARGE)
|
|
794
|
+
self._reclamp() # re-snap the current value onto the new grid
|
|
795
|
+
|
|
796
|
+
@configure_delegate('accent')
|
|
797
|
+
def _delegate_accent(self, value=None):
|
|
798
|
+
if value is None:
|
|
799
|
+
return self._accent
|
|
800
|
+
self._accent = value
|
|
801
|
+
self._on_theme_changed()
|
|
802
|
+
|
|
803
|
+
@configure_delegate('state')
|
|
804
|
+
def _delegate_state(self, value=None):
|
|
805
|
+
if value is None:
|
|
806
|
+
return self._state
|
|
807
|
+
self._state = value
|
|
808
|
+
self._render_handle(self._colors)
|
|
809
|
+
self._sync()
|
|
810
|
+
|
|
811
|
+
@configure_delegate('tick_format')
|
|
812
|
+
def _delegate_tick_format(self, value=None):
|
|
813
|
+
if value is None:
|
|
814
|
+
return self._tick_format
|
|
815
|
+
self._tick_format = value
|
|
816
|
+
self._setup_ticks()
|
|
817
|
+
self._sync()
|
|
818
|
+
|
|
819
|
+
@configure_delegate('tick_interval')
|
|
820
|
+
def _delegate_tick_interval(self, value=None):
|
|
821
|
+
if value is None:
|
|
822
|
+
return self._tick_interval
|
|
823
|
+
self._tick_interval = value
|
|
824
|
+
self._setup_ticks()
|
|
825
|
+
|
|
826
|
+
@configure_delegate('show_value')
|
|
827
|
+
def _delegate_show_value(self, value=None):
|
|
828
|
+
if value is None:
|
|
829
|
+
return self._show_value
|
|
830
|
+
# show_value creates/destroys canvas items; must be set at construction time.
|
|
831
|
+
|
|
832
|
+
@configure_delegate('minor_ticks')
|
|
833
|
+
def _delegate_minor_ticks(self, value=None):
|
|
834
|
+
if value is None:
|
|
835
|
+
return self._minor_ticks
|
|
836
|
+
self._minor_ticks = int(value)
|
|
837
|
+
self._setup_ticks()
|
|
838
|
+
|
|
839
|
+
@configure_delegate('tick_labels')
|
|
840
|
+
def _delegate_tick_labels(self, value=None):
|
|
841
|
+
if value is None:
|
|
842
|
+
return self._tick_labels
|
|
843
|
+
self._tick_labels = bool(value)
|
|
844
|
+
self._setup_ticks()
|
|
845
|
+
|
|
846
|
+
@configure_delegate('surface')
|
|
847
|
+
def _delegate_surface(self, value=None):
|
|
848
|
+
if value is None:
|
|
849
|
+
return self._surface
|
|
850
|
+
self._surface = value
|
|
851
|
+
self._on_theme_changed()
|