diff --git a/leafmap/toolbar.py b/leafmap/toolbar.py index f6e6db8fd6..1db232723f 100644 --- a/leafmap/toolbar.py +++ b/leafmap/toolbar.py @@ -12,6 +12,32 @@ from . import pc from .common import * +# Debounce toolbar hover events to prevent the expand/collapse feedback loop +# that makes the toolbar flicker in webview frontends such as VS Code (#1330). +TOOLBAR_HOVER_DEBOUNCE_MS = 250 +TOOLBAR_HOVER_MODE = "debounce" + + +def _toolbar_hover_event(source: widgets.Widget) -> "ipyevents.Event": + """Create a debounced ipyevents Event for a toolbar hover container. + + Debouncing the ``mouseenter``/``mouseleave`` events avoids the rapid + expand/collapse feedback loop that makes the toolbar flicker in + webview-based frontends such as VS Code (see #1330). + + Args: + source (widgets.Widget): The widget to watch for hover events. + + Returns: + ipyevents.Event: The configured hover event. + """ + return ipyevents.Event( + source=source, + watched_events=["mouseenter", "mouseleave"], + wait=TOOLBAR_HOVER_DEBOUNCE_MS, + throttle_or_debounce=TOOLBAR_HOVER_MODE, + ) + def tool_template(m, opened: Optional[bool] = True) -> Optional[widgets.Widget]: """Generates a tool GUI template using ipywidgets. Icons can be found at https://fontawesome.com/v4/icons @@ -547,9 +573,7 @@ def tool_callback(change): toolbar_footer = widgets.VBox() toolbar_footer.children = [toolbar_grid] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -2076,9 +2100,7 @@ def census_widget(m) -> widgets.Widget: # output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -2260,9 +2282,7 @@ def providers_change(change): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -2468,9 +2488,7 @@ def float_slider_changed(change): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -2668,9 +2686,7 @@ def inspector_gui( output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def chk_change(change): if hasattr(m, "pixel_values"): @@ -3091,9 +3107,7 @@ def tool_callback(change): toolbar_footer = widgets.VBox(layout=widgets.Layout(overflow="hidden")) toolbar_footer.children = [toolbar_grid] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -3270,9 +3284,7 @@ def plotly_tool_template(canvas): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -3485,9 +3497,7 @@ def providers_change(change): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -3599,9 +3609,7 @@ def plotly_whitebox_gui(canvas): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -3725,9 +3733,7 @@ def search_geojson_gui(m): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def layer_change(change): if change["new"]: @@ -3905,9 +3911,7 @@ def select_table_gui(m=None): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -4181,9 +4185,7 @@ def height_changed(change): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -4368,9 +4370,7 @@ def int_slider_changed(change): buttons, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -4912,9 +4912,7 @@ def reset_options(reset_url=True): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def update_bands(): if len(stac_data) > 0: @@ -5607,9 +5605,7 @@ def reset_options(): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def update_bands(): excluded = [ @@ -6148,9 +6144,7 @@ def int_slider_changed(change): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -6595,9 +6589,7 @@ def change_dataset(change): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": @@ -7006,9 +6998,7 @@ def change_dataset(change): output, ] - toolbar_event = ipyevents.Event( - source=toolbar_widget, watched_events=["mouseenter", "mouseleave"] - ) + toolbar_event = _toolbar_hover_event(toolbar_widget) def handle_toolbar_event(event): if event["type"] == "mouseenter": diff --git a/requirements.txt b/requirements.txt index 067fc47998..6b9dd82ccb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ folium gdown geojson geopandas -ipyevents +ipyevents>=0.8.0 ipyfilechooser ipyleaflet ipyvue>=1.12.0