feat: Custom sticky tooltip for Dictate button with fade-out
- Tooltip appears on hover and stays while mouse is still - Starts 2-second fade when mouse moves or button is clicked - Custom popup window instead of built-in tooltip for better control - Automatically cleaned up when dialog closes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d30013778b
commit
b06b3f98b1
|
|
@ -413,6 +413,9 @@ class DiscussionGUI:
|
||||||
"""Handle mouse down on dictate button - start timing for mode detection."""
|
"""Handle mouse down on dictate button - start timing for mode detection."""
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
# Hide tooltip when button is clicked
|
||||||
|
self._hide_dictate_tooltip()
|
||||||
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
# Check for double-click (two clicks within 300ms)
|
# Check for double-click (two clicks within 300ms)
|
||||||
|
|
@ -481,6 +484,108 @@ class DiscussionGUI:
|
||||||
if self._continuous_recorder is None:
|
if self._continuous_recorder is None:
|
||||||
self._start_dictation()
|
self._start_dictation()
|
||||||
|
|
||||||
|
def _show_dictate_tooltip(self):
|
||||||
|
"""Show the custom dictate button tooltip."""
|
||||||
|
if dpg.does_item_exist("dictate_tooltip_window"):
|
||||||
|
return # Already visible
|
||||||
|
|
||||||
|
# Get button position for tooltip placement
|
||||||
|
if dpg.does_item_exist("dictate_btn"):
|
||||||
|
btn_pos = dpg.get_item_pos("dictate_btn")
|
||||||
|
# Get the parent window position to calculate absolute position
|
||||||
|
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_fading = False
|
||||||
|
self._tooltip_alpha = 1.0
|
||||||
|
self._tooltip_last_mouse_pos = dpg.get_mouse_pos(local=False)
|
||||||
|
|
||||||
|
# Create tooltip window
|
||||||
|
with dpg.window(
|
||||||
|
tag="dictate_tooltip_window",
|
||||||
|
no_title_bar=True,
|
||||||
|
no_resize=True,
|
||||||
|
no_move=True,
|
||||||
|
no_scrollbar=True,
|
||||||
|
no_collapse=True,
|
||||||
|
no_background=False,
|
||||||
|
pos=[tooltip_x, tooltip_y],
|
||||||
|
autosize=True
|
||||||
|
):
|
||||||
|
dpg.add_text("Voice Dictation", tag="tt_title", color=(200, 200, 255))
|
||||||
|
dpg.add_separator()
|
||||||
|
dpg.add_text("Double-click: Continuous mode", tag="tt_line1")
|
||||||
|
dpg.add_text(" Transcribed text appends to end.", tag="tt_line2", color=(150, 150, 150))
|
||||||
|
dpg.add_text(" Click again to stop.", tag="tt_line3", color=(150, 150, 150))
|
||||||
|
dpg.add_spacer(height=5)
|
||||||
|
dpg.add_text("Click & hold: Walkie-talkie mode", tag="tt_line4")
|
||||||
|
dpg.add_text(" Inserts at last edited position.", tag="tt_line5", color=(150, 150, 150))
|
||||||
|
dpg.add_text(" Release to stop.", tag="tt_line6", color=(150, 150, 150))
|
||||||
|
dpg.add_spacer(height=5)
|
||||||
|
dpg.add_text("Tip: Type at insertion point first", tag="tt_line7", color=(255, 200, 100))
|
||||||
|
dpg.add_text("to set cursor position.", tag="tt_line8", color=(255, 200, 100))
|
||||||
|
|
||||||
|
def _hide_dictate_tooltip(self):
|
||||||
|
"""Hide the custom dictate button tooltip immediately."""
|
||||||
|
if dpg.does_item_exist("dictate_tooltip_window"):
|
||||||
|
dpg.delete_item("dictate_tooltip_window")
|
||||||
|
self._tooltip_visible = False
|
||||||
|
self._tooltip_fading = False
|
||||||
|
|
||||||
|
def _start_tooltip_fade(self):
|
||||||
|
"""Start fading out the tooltip."""
|
||||||
|
import time
|
||||||
|
if self._tooltip_visible and not self._tooltip_fading:
|
||||||
|
self._tooltip_fading = True
|
||||||
|
self._tooltip_fade_start_time = time.time()
|
||||||
|
|
||||||
|
def _update_tooltip_fade(self):
|
||||||
|
"""Update tooltip fade animation. Call this from frame callback."""
|
||||||
|
import time
|
||||||
|
|
||||||
|
if not self._tooltip_visible:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_mouse = dpg.get_mouse_pos(local=False)
|
||||||
|
|
||||||
|
# Check if mouse moved significantly
|
||||||
|
dx = abs(current_mouse[0] - self._tooltip_last_mouse_pos[0])
|
||||||
|
dy = abs(current_mouse[1] - self._tooltip_last_mouse_pos[1])
|
||||||
|
|
||||||
|
if dx > 5 or dy > 5: # Mouse moved
|
||||||
|
if not self._tooltip_fading:
|
||||||
|
self._start_tooltip_fade()
|
||||||
|
self._tooltip_last_mouse_pos = current_mouse
|
||||||
|
|
||||||
|
# Handle fade animation
|
||||||
|
if self._tooltip_fading:
|
||||||
|
elapsed = time.time() - self._tooltip_fade_start_time
|
||||||
|
fade_duration = 2.0 # 2 seconds to fade
|
||||||
|
|
||||||
|
if elapsed >= fade_duration:
|
||||||
|
self._hide_dictate_tooltip()
|
||||||
|
else:
|
||||||
|
# Calculate alpha (1.0 to 0.0 over 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
|
||||||
|
if self._tooltip_alpha < 0.1:
|
||||||
|
self._hide_dictate_tooltip()
|
||||||
|
|
||||||
|
def _on_dictate_hover(self, sender, app_data):
|
||||||
|
"""Handle mouse hovering over dictate button."""
|
||||||
|
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
|
||||||
|
|
@ -696,6 +801,13 @@ class DiscussionGUI:
|
||||||
self._last_text_value = ""
|
self._last_text_value = ""
|
||||||
self._approx_cursor_pos = 0 # Approximate cursor position for insertion
|
self._approx_cursor_pos = 0 # Approximate cursor position for insertion
|
||||||
|
|
||||||
|
# Custom tooltip state for sticky fade behavior
|
||||||
|
self._tooltip_visible = False
|
||||||
|
self._tooltip_last_mouse_pos = (0, 0)
|
||||||
|
self._tooltip_fade_start_time = 0.0
|
||||||
|
self._tooltip_fading = False
|
||||||
|
self._tooltip_alpha = 1.0
|
||||||
|
|
||||||
# Initialize Dear PyGui
|
# Initialize Dear PyGui
|
||||||
dpg.create_context()
|
dpg.create_context()
|
||||||
dpg.create_viewport(title="Orchestrated Discussions", width=1400, height=900)
|
dpg.create_viewport(title="Orchestrated Discussions", width=1400, height=900)
|
||||||
|
|
@ -2060,6 +2172,8 @@ class DiscussionGUI:
|
||||||
dpg.delete_item(window_tag)
|
dpg.delete_item(window_tag)
|
||||||
if dpg.does_item_exist("dictate_btn_handlers"):
|
if dpg.does_item_exist("dictate_btn_handlers"):
|
||||||
dpg.delete_item("dictate_btn_handlers")
|
dpg.delete_item("dictate_btn_handlers")
|
||||||
|
# Clean up any existing tooltip
|
||||||
|
self._hide_dictate_tooltip()
|
||||||
# Reset dictation state when dialog opens
|
# Reset dictation state when dialog opens
|
||||||
self._dictation_mode = "idle"
|
self._dictation_mode = "idle"
|
||||||
self._last_dictate_click_time = 0.0
|
self._last_dictate_click_time = 0.0
|
||||||
|
|
@ -2078,25 +2192,13 @@ class DiscussionGUI:
|
||||||
tag="dictate_btn",
|
tag="dictate_btn",
|
||||||
width=100
|
width=100
|
||||||
)
|
)
|
||||||
# Add tooltip with usage instructions
|
|
||||||
with dpg.tooltip("dictate_btn"):
|
|
||||||
dpg.add_text("Voice Dictation", color=(200, 200, 255))
|
|
||||||
dpg.add_separator()
|
|
||||||
dpg.add_text("Double-click: Continuous mode")
|
|
||||||
dpg.add_text(" Transcribed text appends to end.", color=(150, 150, 150))
|
|
||||||
dpg.add_text(" Click again to stop.", color=(150, 150, 150))
|
|
||||||
dpg.add_spacer(height=5)
|
|
||||||
dpg.add_text("Click & hold: Walkie-talkie mode")
|
|
||||||
dpg.add_text(" Inserts at last edited position.", color=(150, 150, 150))
|
|
||||||
dpg.add_text(" Release to stop.", color=(150, 150, 150))
|
|
||||||
dpg.add_spacer(height=5)
|
|
||||||
dpg.add_text("Tip: Type at insertion point first", color=(255, 200, 100))
|
|
||||||
dpg.add_text("to set cursor position.", color=(255, 200, 100))
|
|
||||||
# Add item handlers for press-and-hold vs double-click detection
|
# Add item handlers for press-and-hold vs double-click detection
|
||||||
|
# Also includes hover handler for custom sticky tooltip
|
||||||
with dpg.item_handler_registry(tag="dictate_btn_handlers"):
|
with dpg.item_handler_registry(tag="dictate_btn_handlers"):
|
||||||
dpg.add_item_activated_handler(callback=self._on_dictate_activated)
|
dpg.add_item_activated_handler(callback=self._on_dictate_activated)
|
||||||
dpg.add_item_deactivated_handler(callback=self._on_dictate_deactivated)
|
dpg.add_item_deactivated_handler(callback=self._on_dictate_deactivated)
|
||||||
dpg.add_item_active_handler(callback=self._on_dictate_active)
|
dpg.add_item_active_handler(callback=self._on_dictate_active)
|
||||||
|
dpg.add_item_hover_handler(callback=self._on_dictate_hover)
|
||||||
dpg.bind_item_handler_registry("dictate_btn", "dictate_btn_handlers")
|
dpg.bind_item_handler_registry("dictate_btn", "dictate_btn_handlers")
|
||||||
dpg.add_button(
|
dpg.add_button(
|
||||||
label="Add Artifact",
|
label="Add Artifact",
|
||||||
|
|
@ -4144,6 +4246,9 @@ 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
|
||||||
|
self._update_tooltip_fade()
|
||||||
|
|
||||||
# Render frame
|
# Render frame
|
||||||
dpg.render_dearpygui_frame()
|
dpg.render_dearpygui_frame()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue