/
DimerTrue
/
ecg_gui
Обзор
Документация
Войти
/
DimerTrue
/
ecg_gui
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
ui/tooltips.py
225 строк
7 KB
DimerTrue
ui bug fixed
20 июл 2026, 17:04
20 июл 2026, 17:04
5fabad5
Код
Авторство
О чём код?
# tooltip.py import tkinter as tk from config.theme import Theme from config import ui_config from utils.string_utils import line_break class ToolTip: """Всплывающее окно подсказки""" def __init__( self, widget, text, delay=500, follow_mouse=False, on_enter_callback=None, on_leave_callback=None, on_motion_callback=None, ): """ widget - виджет, для которого создается подсказка text - текст подсказки delay - задержка в миллисекундах (по умолчанию 500 мс = 0.5 сек) follow_mouse - следовать ли за курсором """ self.widget = widget self.text = "\n".join(line_break(text, ui_config.MAX_LINE_LENGTH)) self.delay = delay self.follow_mouse = follow_mouse self.tip_window = None self.after_id = None self.on_enter_callback = on_enter_callback self.on_leave_callback = on_leave_callback self.on_motion_callback = on_motion_callback self._is_visible = False widget.bind("<Enter>", self.on_enter) widget.bind("<Leave>", self.on_leave) if follow_mouse: widget.bind("<Motion>", self.on_motion) def unbind_events(self): """Отвязка событий от виджета""" try: self.widget.unbind("<Enter>") self.widget.unbind("<Leave>") self.widget.unbind("<Motion>") except: pass def on_enter(self, event): self.after_id = self.widget.after(self.delay, self.show_tip) # Вызываем дополнительный callback если есть if self.on_enter_callback: self.on_enter_callback(event) def on_leave(self, event): if self.after_id: self.widget.after_cancel(self.after_id) self.after_id = None self._is_visible = False self.hide_tip() # Вызываем дополнительный callback если есть if self.on_leave_callback: self.on_leave_callback(event) def on_motion(self, event): """Обновляем позицию подсказки при движении мыши""" if self.tip_window and self.follow_mouse: x = event.x_root + 20 y = event.y_root + 20 self.tip_window.wm_geometry(f"+{x}+{y}") # Вызываем дополнительный callback если есть if self.on_motion_callback: self.on_motion_callback(event) def show_tip(self): """Показываем подсказку""" if self._is_visible: return tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(True) # Определяем начальную позицию if self.follow_mouse: # При follow_mouse используем текущие координаты курсора x = self.widget.winfo_pointerx() + 20 y = self.widget.winfo_pointery() + 20 else: # Иначе позиционируем относительно виджета x = self.widget.winfo_rootx() + 20 y = self.widget.winfo_rooty() + 20 tw.wm_geometry(f"+{x}+{y}") colors = Theme.get_colors() label = tk.Label( tw, text=self.text, font=(ui_config.MAIN_FONT, ui_config.WIDGET_TEXT_SIZE), justify=tk.LEFT, background=colors["tooltip_bg"], relief=tk.SOLID, borderwidth=1, padx=5, pady=2, ) label.pack() self.tip_window = tw self._is_visible = True def hide_tip(self): if self.tip_window: self.tip_window.destroy() self._is_visible = False self.tip_window = None class PlotToolTip: """Всплывающее окно подсказки для легенды""" def __init__(self, canvas, legend, text, delay=500): self.canvas = canvas self.legend = legend # Форматируем текст с переносами self.text = "\n".join(line_break(text, ui_config.MAX_LINE_LENGTH)) self.delay = delay self.tip_window = None self.after_id = None self.tk_widget = canvas.get_tk_widget() self.tk_widget.bind("<Motion>", self.on_motion_tk) self.tk_widget.bind("<Leave>", self.on_leave_tk) def on_motion_tk(self, event): """Обработчик движения мыши через Tkinter""" try: renderer = self.canvas.get_renderer() bbox = self.legend.get_window_extent(renderer) x_fig = ( event.x / self.tk_widget.winfo_width() * self.canvas.figure.bbox.width ) y_fig = ( (self.tk_widget.winfo_height() - event.y) / self.tk_widget.winfo_height() * self.canvas.figure.bbox.height ) inside = bbox.x0 <= x_fig <= bbox.x1 and bbox.y0 <= y_fig <= bbox.y1 if inside: # Если подсказка уже видна, то просто двигаем её за мышью if self.tip_window is not None: self.tip_window.wm_geometry( f"+{event.x_root + 15}+{event.y_root + 15}" ) else: # Если ещё не видна, то перезапускаем таймер if self.after_id: self.tk_widget.after_cancel(self.after_id) self.after_id = self.tk_widget.after(self.delay, self.show_tip) else: # Курсор вышел за пределы легенды if self.after_id: self.tk_widget.after_cancel(self.after_id) self.after_id = None self.hide_tip() except Exception: pass def on_leave_tk(self, event): """Курсор покинул widget""" if self.after_id: self.tk_widget.after_cancel(self.after_id) self.after_id = None self.hide_tip() def show_tip(self): """Показываем подсказку в текущей позиции мыши""" if self.tip_window is not None: return # Берём актуальные координаты x = self.tk_widget.winfo_pointerx() + 15 y = self.tk_widget.winfo_pointery() + 15 colors = Theme.get_colors() tw = tk.Toplevel(self.tk_widget) tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") label = tk.Label( tw, text=self.text, font=(ui_config.MAIN_FONT, ui_config.WIDGET_TEXT_SIZE), justify=tk.LEFT, background=colors["tooltip_bg"], relief=tk.SOLID, borderwidth=1, padx=5, pady=2, foreground="black", ) label.pack() self.tip_window = tw def hide_tip(self): """Скрываем подсказку""" if self.tip_window is not None: self.tip_window.destroy() self.tip_window = None