"""GotoLineDialog -- small floating dialog for line number navigation (Ctrl+G)."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from simvx.core import Vec2
from simvx.core.ui.core import Control, UIInputEvent
from simvx.core.ui.theme import get_theme
if TYPE_CHECKING:
from ..state import IDEState
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.
"""
def __init__(self, state: IDEState, **kwargs):
super().__init__(**kwargs)
self._state = state
self.visible = False
self.z_index = 2000
self._input: str = ""
self._max_line: int = 1
self._cursor_blink: float = 0.0
self.size = Vec2(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.set_focus()
[docs]
def hide(self):
self.visible = False
self._input = ""
self.release_focus()
def _on_gui_input(self, event: UIInputEvent):
if not self.visible:
return
if event.key == "escape" and event.pressed:
self.hide()
return
if event.key == "enter" and event.pressed:
self._navigate()
return
if event.key == "backspace" and event.pressed:
if self._input:
self._input = self._input[:-1]
return
if event.char and event.char.isdigit():
self._input += event.char
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 process(self, dt: float):
if self.visible:
self._cursor_blink += dt
if self._cursor_blink > 1.0:
self._cursor_blink = 0.0
[docs]
def 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 / 14.0
# Semi-transparent overlay
renderer.draw_filled_rect(0, 0, sw, sh, _OVERLAY_BG)
# Dialog centered
dx = (sw - _WIDTH) / 2
dy = (sh - _HEIGHT) / 2
# Background + border
renderer.draw_filled_rect(dx, dy, _WIDTH, _HEIGHT, theme.popup_bg)
renderer.draw_rect_coloured(dx, dy, _WIDTH, _HEIGHT, theme.accent)
# Label
label = f"Go to Line (1-{self._max_line}):"
label_y = dy + 6
renderer.draw_text_coloured(label, dx + _PAD, label_y, scale * 0.85, theme.text_dim)
# Input field
input_y = dy + 26
input_h = 24
renderer.draw_filled_rect(dx + _PAD, input_y, _WIDTH - _PAD * 2, input_h, theme.bg_input)
renderer.draw_rect_coloured(dx + _PAD, input_y, _WIDTH - _PAD * 2, input_h, theme.accent)
# Input text
text_y = input_y + (input_h - _FONT_SIZE) / 2
renderer.draw_text_coloured(self._input, dx + _PAD + 4, text_y, scale, theme.text)
# Cursor
if self._cursor_blink < 0.5:
cx = dx + _PAD + 4 + renderer.text_width(self._input, scale)
renderer.draw_line_coloured(cx, input_y + 4, cx, input_y + input_h - 4, theme.text)