fix: Use item state polling for tooltip hover detection

Modal dialogs may block hover handler events, so now check
dpg.get_item_state("dictate_btn")["hovered"] in the main loop
instead of relying solely on the hover handler callback.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-01-04 13:40:12 -04:00
parent b06b3f98b1
commit 6650280421
1 changed files with 43 additions and 37 deletions

View File

@ -489,22 +489,17 @@ class DiscussionGUI:
if dpg.does_item_exist("dictate_tooltip_window"): if dpg.does_item_exist("dictate_tooltip_window"):
return # Already visible return # Already visible
# Get button position for tooltip placement # Get mouse position for tooltip placement (show near cursor)
if dpg.does_item_exist("dictate_btn"): mouse_pos = dpg.get_mouse_pos(local=False)
btn_pos = dpg.get_item_pos("dictate_btn") tooltip_x = mouse_pos[0] + 15
# Get the parent window position to calculate absolute position tooltip_y = mouse_pos[1] + 15
parent_pos = dpg.get_item_pos("comment_dialog") if dpg.does_item_exist("comment_dialog") else (0, 0)
tooltip_x = parent_pos[0] + btn_pos[0] + 110 # Right of button
tooltip_y = parent_pos[1] + btn_pos[1] + 30
else:
tooltip_x, tooltip_y = 500, 300
self._tooltip_visible = True self._tooltip_visible = True
self._tooltip_fading = False self._tooltip_fading = False
self._tooltip_alpha = 1.0 self._tooltip_alpha = 1.0
self._tooltip_last_mouse_pos = dpg.get_mouse_pos(local=False) self._tooltip_last_mouse_pos = mouse_pos
# Create tooltip window # Create tooltip as a popup window (appears above modal)
with dpg.window( with dpg.window(
tag="dictate_tooltip_window", tag="dictate_tooltip_window",
no_title_bar=True, no_title_bar=True,
@ -513,21 +508,26 @@ class DiscussionGUI:
no_scrollbar=True, no_scrollbar=True,
no_collapse=True, no_collapse=True,
no_background=False, no_background=False,
no_focus_on_appearing=True,
pos=[tooltip_x, tooltip_y], pos=[tooltip_x, tooltip_y],
autosize=True autosize=True,
no_open_over_existing_popup=False
): ):
dpg.add_text("Voice Dictation", tag="tt_title", color=(200, 200, 255)) dpg.add_text("Voice Dictation", color=(200, 200, 255))
dpg.add_separator() dpg.add_separator()
dpg.add_text("Double-click: Continuous mode", tag="tt_line1") dpg.add_text("Double-click: Continuous mode")
dpg.add_text(" Transcribed text appends to end.", tag="tt_line2", color=(150, 150, 150)) dpg.add_text(" Transcribed text appends to end.", color=(150, 150, 150))
dpg.add_text(" Click again to stop.", tag="tt_line3", color=(150, 150, 150)) dpg.add_text(" Click again to stop.", color=(150, 150, 150))
dpg.add_spacer(height=5) dpg.add_spacer(height=5)
dpg.add_text("Click & hold: Walkie-talkie mode", tag="tt_line4") dpg.add_text("Click & hold: Walkie-talkie mode")
dpg.add_text(" Inserts at last edited position.", tag="tt_line5", color=(150, 150, 150)) dpg.add_text(" Inserts at last edited position.", color=(150, 150, 150))
dpg.add_text(" Release to stop.", tag="tt_line6", color=(150, 150, 150)) dpg.add_text(" Release to stop.", color=(150, 150, 150))
dpg.add_spacer(height=5) dpg.add_spacer(height=5)
dpg.add_text("Tip: Type at insertion point first", tag="tt_line7", color=(255, 200, 100)) dpg.add_text("Tip: Type at insertion point first", color=(255, 200, 100))
dpg.add_text("to set cursor position.", tag="tt_line8", color=(255, 200, 100)) dpg.add_text("to set cursor position.", color=(255, 200, 100))
# Bring tooltip to front
dpg.focus_item("dictate_tooltip_window")
def _hide_dictate_tooltip(self): def _hide_dictate_tooltip(self):
"""Hide the custom dictate button tooltip immediately.""" """Hide the custom dictate button tooltip immediately."""
@ -543,16 +543,29 @@ class DiscussionGUI:
self._tooltip_fading = True self._tooltip_fading = True
self._tooltip_fade_start_time = time.time() self._tooltip_fade_start_time = time.time()
def _update_tooltip_fade(self): def _update_tooltip(self):
"""Update tooltip fade animation. Call this from frame callback.""" """Update tooltip visibility and fade animation. Call this from frame callback."""
import time import time
# Check if dictate button exists and mouse is over it
if dpg.does_item_exist("dictate_btn"):
# Check if button is hovered using item state
state = dpg.get_item_state("dictate_btn")
is_hovered = state.get("hovered", False) if state else False
if is_hovered and not self._tooltip_visible:
# Mouse entered button - show tooltip
self._show_dictate_tooltip()
elif not is_hovered and self._tooltip_visible and not self._tooltip_fading:
# Mouse left button - start fade
self._start_tooltip_fade()
if not self._tooltip_visible: if not self._tooltip_visible:
return return
current_mouse = dpg.get_mouse_pos(local=False) current_mouse = dpg.get_mouse_pos(local=False)
# Check if mouse moved significantly # Check if mouse moved significantly while tooltip is visible
dx = abs(current_mouse[0] - self._tooltip_last_mouse_pos[0]) dx = abs(current_mouse[0] - self._tooltip_last_mouse_pos[0])
dy = abs(current_mouse[1] - self._tooltip_last_mouse_pos[1]) dy = abs(current_mouse[1] - self._tooltip_last_mouse_pos[1])
@ -571,21 +584,14 @@ class DiscussionGUI:
else: else:
# Calculate alpha (1.0 to 0.0 over fade_duration) # Calculate alpha (1.0 to 0.0 over fade_duration)
self._tooltip_alpha = 1.0 - (elapsed / fade_duration) self._tooltip_alpha = 1.0 - (elapsed / fade_duration)
# Apply alpha to window
if dpg.does_item_exist("dictate_tooltip_window"):
# DearPyGui doesn't have direct alpha control, so we hide at threshold # DearPyGui doesn't have direct alpha control, so we hide at threshold
if self._tooltip_alpha < 0.1: if self._tooltip_alpha < 0.1:
self._hide_dictate_tooltip() self._hide_dictate_tooltip()
def _on_dictate_hover(self, sender, app_data): def _on_dictate_hover(self, sender, app_data):
"""Handle mouse hovering over dictate button.""" """Handle mouse hovering over dictate button (backup, main check is in _update_tooltip)."""
self._show_dictate_tooltip() self._show_dictate_tooltip()
def _on_dictate_unhover(self):
"""Handle mouse leaving dictate button area - start fade."""
if self._tooltip_visible:
self._start_tooltip_fade()
def _start_dictation(self): def _start_dictation(self):
"""Start continuous recording with chunked transcription.""" """Start continuous recording with chunked transcription."""
# Create recorder with callbacks # Create recorder with callbacks
@ -4246,8 +4252,8 @@ final = json.dumps(parsed)''',
# Poll for updates from background threads (output, turn completion) # Poll for updates from background threads (output, turn completion)
self._poll_background_tasks() self._poll_background_tasks()
# Update tooltip fade animation # Update tooltip visibility and fade animation
self._update_tooltip_fade() self._update_tooltip()
# Render frame # Render frame
dpg.render_dearpygui_frame() dpg.render_dearpygui_frame()