Source code for simvx.ide.widgets.goto_line

"""GotoLineDialog -- small floating dialog for line number navigation (Ctrl+G)."""

import logging
from typing import TYPE_CHECKING

from simvx.core import PointerFilter
from simvx.core.descriptors import Property
from simvx.core.input import Key, KeyEvent, TextEvent
from simvx.core.ui.core import Control, FocusMode
from simvx.core.ui.theme import get_theme

if TYPE_CHECKING:
    from ..state import State

log = logging.getLogger(__name__)

_WIDTH = 300.0
_HEIGHT = 60.0
_PAD = 8.0
_FONT_SIZE = 14.0
_OVERLAY_BG = (0.0, 0.0, 0.0, 0.25)


[docs] class GotoLineDialog(Control): """Small centered dialog for entering a line number. Enter navigates to the line, Escape dismisses. """ pointer_filter = Property(PointerFilter.STOP, hint="How this control takes part in the pointer hit test") top_level = Property(True, on_change="_invalidate_transform", hint="Placed on the screen, not by its parent") visible = Property( False, coerce=bool, hint="The goto-line prompt is up only between show() and its answer", on_change="_on_visible_changed", ) # Above every panel: a prompt is what the user is looking at. z_index = Property(2000, range=(-4096, 4096), hint="Draw order (higher = on top)", on_change="_invalidate_z_cache") # Zero because this dialog lays itself out against the parent rect in # ``on_draw`` and never reads its own size. size_x = Property(0.0, range=(0, 10000), hint="Control width", on_change="_on_size_changed") size_y = Property(0.0, range=(0, 10000), hint="Control height", on_change="_on_size_changed") # The prompt takes the keyboard while it is up: it focuses itself from # show(), is never a Tab stop, and takes typing. focus_mode = Property(FocusMode.CLICK, hint="Whether this control can take keyboard focus")
[docs] @property def wants_text_input(self) -> bool: return True
def __init__(self, state: State, **kwargs): super().__init__(**kwargs) self._state = state self._input: str = "" self._max_line: int = 1 self._cursor_blink: float = 0.0
[docs] def show(self, max_line: int = 1): self.visible = True self._input = "" self._max_line = max(max_line, 1) self._cursor_blink = 0.0 self.grab_focus()
[docs] def hide(self): self.visible = False self._input = "" self.release_focus()
[docs] def on_gui_input(self, event): # Keyboard only: the dialog takes digits, Enter and Escape, and leaves # the pointer to whatever is under it. if not self.visible: return if isinstance(event, TextEvent): if event.text.isdigit(): self._input += event.text return if not isinstance(event, KeyEvent) or not event.pressed: return if event.key is Key.ESCAPE: self.hide() elif event.key is Key.ENTER: self._navigate() elif event.key is Key.BACKSPACE and self._input: self._input = self._input[:-1]
def _navigate(self): if not self._input: self.hide() return try: line = int(self._input) except ValueError: self.hide() return line = max(1, min(line, self._max_line)) - 1 # Convert to 0-indexed path = self._state.active_file self.hide() self._state.goto(path, line, 0)
[docs] def on_update(self, dt: float): if self.visible: self._cursor_blink += dt if self._cursor_blink > 1.0: self._cursor_blink = 0.0
[docs] def on_draw(self, renderer): if not self.visible: return theme = get_theme() ss = self._get_parent_size() sw, sh = ss.x, ss.y scale = _FONT_SIZE / 16.0 # Semi-transparent overlay renderer.rect((0, 0), (sw, sh), fill=_OVERLAY_BG) # Dialog centered dx = (sw - _WIDTH) / 2 dy = (sh - _HEIGHT) / 2 # Background + border renderer.rect((dx, dy), (_WIDTH, _HEIGHT), fill=theme.popup_bg) renderer.rect((dx, dy), (_WIDTH, _HEIGHT), stroke=theme.accent) # Label label = f"Go to Line (1-{self._max_line}):" label_y = dy + 6 renderer.text(label, (dx + _PAD, label_y), fill=theme.text_dim, scale=scale * 0.85) # Input field input_y = dy + 26 input_h = 24 renderer.rect((dx + _PAD, input_y), (_WIDTH - _PAD * 2, input_h), fill=theme.bg_input) renderer.rect((dx + _PAD, input_y), (_WIDTH - _PAD * 2, input_h), stroke=theme.accent) # Input text text_y = input_y + (input_h - _FONT_SIZE) / 2 renderer.text(self._input, (dx + _PAD + 4, text_y), fill=theme.text, scale=scale) # Cursor if self._cursor_blink < 0.5: cx = dx + _PAD + 4 + renderer.text_width(self._input, scale) renderer.line((cx, input_y + 4), (cx, input_y + input_h - 4), stroke=theme.text)