# The MIT License (MIT)
#
# Copyright (C) 2026 Scott Duensing
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

.section Libraries
.topic lib.texthelp
.title Text Help Library
.toc 0 Text Help Library
.default
.index textHelp
.index Text Editing
.index Shared Library

.h1 Text Help Library

Shared text editing infrastructure library for DVX widget DXEs. Provides cursor blink management, cross-widget selection clearing, word boundary logic, and a complete single-line text editing engine. Used by TextInput, Spinner, ComboBox, AnsiTerm, and other widgets that need text editing behavior.

Header: texthelp/textHelp.h

Loaded as: bin/libs/texthelp.lib

.h2 How Widgets Use This Library

Widget DXEs that implement text editing delegate to textHelp for the four high-cost behaviors:

.list
.item Cursor blink. The library tracks a 250 ms blink timer in a static global and the focused widget reads sCursorBlinkOn (exposed via libdvx) when repainting.
.item Selection clearing. When a widget gains focus it calls clearOtherSelections(self) so only one widget ever has an active text selection.
.item Word boundaries. isWordChar, wordStart/wordEnd, and wordBoundaryLeft/wordBoundaryRight implement the logic for double-click word selection and Ctrl+Left/Right navigation in a uniform way.
.item Single-line editing engine. widgetTextEditOnKey, widgetTextEditMouseClick, widgetTextEditDragUpdateLine, and widgetTextEditPaintLine form a pointer-parameterized implementation of keyboard, mouse, drag, and paint behaviors. Widgets (TextInput, Spinner, ComboBox, AnsiTerm) hand the library pointers to their internal buffer, cursor, scroll offset, and selection state.
.endlist

The engine is intentionally pointer-parameterized rather than struct-based so widgets can reuse it without adopting a shared state struct. Each widget owns its own buffer and state and passes pointers in on every call.

.h2 Constants

.table
  Constant             Value    Description
  --------             -----    -----------
  TEXT_INPUT_PAD        3       Pixel padding inside text editing fields.
.endtable

.topic lib.texthelp.cursorblink
.title wgtUpdateCursorBlink
.toc 1 wgtUpdateCursorBlink
.index wgtUpdateCursorBlink
.index Cursor Blink

.h2 wgtUpdateCursorBlink

Advances the cursor blink timer. When the blink interval (250 ms) elapses, toggles the global sCursorBlinkOn flag and invalidates the currently focused widget for repaint. Registered automatically at library load time via a constructor function that sets the sCursorBlinkFn pointer.

.code
void wgtUpdateCursorBlink(void);
.endcode

Takes no arguments and returns nothing. Called by the core event loop.

.topic lib.texthelp.clearother
.title clearOtherSelections
.toc 1 clearOtherSelections
.index clearOtherSelections
.index Selection

.h2 clearOtherSelections

Clears the text selection on whichever widget previously had one, unless that widget is the one passed as the argument. Tracks the last selected widget in a static pointer for O(1) lookup instead of walking the widget tree. Validates that the previous widget's window is still in the window stack before touching it, so stale pointers from closed windows are handled safely. If the previous selection was in a different window, that window gets a full repaint to clear the stale highlight.

.code
void clearOtherSelections(WidgetT *except);
.endcode

.table
  Parameter    Type         Description
  ---------    ----         -----------
  except       WidgetT *    The widget that now owns the selection. Its selection is preserved.
.endtable

.topic lib.texthelp.iswordchar
.title isWordChar
.toc 1 isWordChar
.index isWordChar

.h2 isWordChar

Returns true if the character is alphanumeric or underscore. Used by word boundary functions to define what constitutes a "word" for double-click selection and Ctrl+arrow navigation.

.code
bool isWordChar(char c);
.endcode

.table
  Parameter    Type    Description
  ---------    ----    -----------
  c            char    The character to test.
.endtable

Returns true if c is alphanumeric (a-z, A-Z, 0-9) or underscore.

.topic lib.texthelp.wordend
.title wordEnd
.toc 1 wordEnd
.index wordEnd

.h2 wordEnd

Scans forward from the given position, returning the index of the first non-word character. Used to find the right boundary of a word for double-click word selection.

.code
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
.endcode

.table
  Parameter    Type           Description
  ---------    ----           -----------
  buf          const char *   The text buffer to scan.
  len          int32_t        Length of the text in the buffer.
  pos          int32_t        Starting position (character index).
.endtable

Returns the index one past the last word character.

.topic lib.texthelp.wordstart
.title wordStart
.toc 1 wordStart
.index wordStart

.h2 wordStart

Scans backward from the given position, returning the index of the first character of the current word. Used to find the left boundary of a word for double-click word selection.

.code
int32_t wordStart(const char *buf, int32_t pos);
.endcode

.table
  Parameter    Type           Description
  ---------    ----           -----------
  buf          const char *   The text buffer to scan.
  pos          int32_t        Starting position (character index).
.endtable

Returns the index of the first character of the word containing pos.

.topic lib.texthelp.wordboundaryleft
.title wordBoundaryLeft
.toc 1 wordBoundaryLeft
.index wordBoundaryLeft
.index Word Boundary

.h2 wordBoundaryLeft

Finds the left word boundary for Ctrl+Left navigation. Skips non-word characters first, then skips word characters, leaving the cursor at the start of the previous word.

.code
int32_t wordBoundaryLeft(const char *buf, int32_t pos);
.endcode

.table
  Parameter    Type           Description
  ---------    ----           -----------
  buf          const char *   The text buffer to scan.
  pos          int32_t        Starting position (character index).
.endtable

Returns the index of the previous word boundary. Returns 0 if pos is already at or before the start of the buffer.

.topic lib.texthelp.wordboundaryright
.title wordBoundaryRight
.toc 1 wordBoundaryRight
.index wordBoundaryRight
.index Word Boundary

.h2 wordBoundaryRight

Finds the right word boundary for Ctrl+Right navigation. Skips word characters first, then skips non-word characters, leaving the cursor at the start of the next word.

.code
int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
.endcode

.table
  Parameter    Type           Description
  ---------    ----           -----------
  buf          const char *   The text buffer to scan.
  len          int32_t        Length of the text buffer.
  pos          int32_t        Starting position (character index).
.endtable

Returns the index of the next word boundary. Returns len if pos is already at or past the end of the buffer.

.topic lib.texthelp.saveundo
.title textEditSaveUndo
.toc 1 textEditSaveUndo
.index textEditSaveUndo
.index Undo

.h2 textEditSaveUndo

Capture a snapshot of the current buffer contents and cursor position into an undo buffer. Called by widgetTextEditOnKey before every destructive operation (insert, delete, paste, cut). Truncates to bufSize - 1 characters so the null terminator always fits.

.code
void textEditSaveUndo(char *buf, int32_t len,
    int32_t cursor, char *undoBuf,
    int32_t *pUndoLen, int32_t *pUndoCursor,
    int32_t bufSize);
.endcode

.table
  Parameter       Type         Description
  ---------       ----         -----------
  buf             char *       Source buffer to snapshot.
  len             int32_t      Current length of source contents.
  cursor          int32_t      Current cursor position to save.
  undoBuf         char *       Destination undo buffer. If NULL the call is a no-op.
  pUndoLen        int32_t *    [out] Length of saved undo contents.
  pUndoCursor     int32_t *    [out] Saved cursor position.
  bufSize         int32_t      Size of the destination undo buffer including the null terminator.
.endtable

.topic lib.texthelp.dragupdate
.title widgetTextEditDragUpdateLine
.toc 1 widgetTextEditDragUpdateLine
.index widgetTextEditDragUpdateLine
.index Drag Select

.h2 widgetTextEditDragUpdateLine

Called during mouse drag to extend the selection in a single-line text field. Converts a viewport-relative pixel x coordinate to a character position and updates the cursor and selection end accordingly. Auto-scrolls the text when the mouse moves past the left or right visible edge.

.code
void widgetTextEditDragUpdateLine(
    int32_t vx, int32_t leftEdge, int32_t maxChars,
    const BitmapFontT *font, int32_t len,
    int32_t *pCursorPos, int32_t *pScrollOff,
    int32_t *pSelEnd);
.endcode

.table
  Parameter     Type                Description
  ---------     ----                -----------
  vx            int32_t             Mouse x position in viewport coordinates.
  leftEdge      int32_t             Pixel x of the left edge of the text area.
  maxChars      int32_t             Maximum visible characters in the field.
  font          const BitmapFontT * Bitmap font used for character width.
  len           int32_t             Total length of the text buffer.
  pCursorPos    int32_t *           [in/out] Current cursor position.
  pScrollOff    int32_t *           [in/out] Horizontal scroll offset in characters.
  pSelEnd       int32_t *           [out] Updated selection end position.
.endtable

.topic lib.texthelp.mouseclick
.title widgetTextEditMouseClick
.toc 1 widgetTextEditMouseClick
.index widgetTextEditMouseClick
.index Mouse Click
.index Multi-Click
.index Word Select

.h2 widgetTextEditMouseClick

Handles mouse click events for single-line text widgets. Converts pixel coordinates to a character position using the font's character width. Detects multi-click sequences via the core multiClickDetect() function:

.list
.item Single click -- places the cursor and begins a potential drag selection.
.item Double click -- selects the word under the cursor (if wordSelect is true), or selects all text.
.item Triple click -- selects all text in the field.
.endlist

.code
void widgetTextEditMouseClick(
    WidgetT *w, int32_t vx, int32_t vy,
    int32_t textLeftX, const BitmapFontT *font,
    const char *buf, int32_t len, int32_t scrollOff,
    int32_t *pCursorPos, int32_t *pSelStart,
    int32_t *pSelEnd, bool wordSelect,
    bool dragSelect);
.endcode

.table
  Parameter     Type                Description
  ---------     ----                -----------
  w             WidgetT *           The widget receiving the click.
  vx            int32_t             Mouse x in viewport coordinates.
  vy            int32_t             Mouse y in viewport coordinates.
  textLeftX     int32_t             Pixel x of the left edge of the text area.
  font          const BitmapFontT * Bitmap font for character width calculation.
  buf           const char *        Text buffer contents.
  len           int32_t             Length of text in the buffer.
  scrollOff     int32_t             Current horizontal scroll offset.
  pCursorPos    int32_t *           [out] Computed cursor position.
  pSelStart     int32_t *           [out] Selection start.
  pSelEnd       int32_t *           [out] Selection end.
  wordSelect    bool                If true, double-click selects word; if false, selects all.
  dragSelect    bool                If true, single-click begins drag selection.
.endtable

.topic lib.texthelp.onkey
.title widgetTextEditOnKey
.toc 1 widgetTextEditOnKey
.index widgetTextEditOnKey
.index Key Handling
.index Text Editing
.index Undo
.index Clipboard

.h2 widgetTextEditOnKey

The core single-line text editing engine. Processes a single keystroke and updates the buffer, cursor, scroll offset, selection, and undo state. All state is passed by pointer so the function is reusable across TextInput, Spinner, ComboBox, and any other single-line widget.

.code
void widgetTextEditOnKey(
    WidgetT *w, int32_t key, int32_t mod,
    char *buf, int32_t bufSize,
    int32_t *pLen, int32_t *pCursor,
    int32_t *pScrollOff,
    int32_t *pSelStart, int32_t *pSelEnd,
    char *undoBuf, int32_t *pUndoLen,
    int32_t *pUndoCursor, int32_t fieldWidth);
.endcode

.table
  Parameter     Type         Description
  ---------     ----         -----------
  w             WidgetT *    The widget receiving the key event.
  key           int32_t      Key code (ASCII for printable, control code or scan|0x100 for extended).
  mod           int32_t      Modifier flags (KEY_MOD_SHIFT, etc.).
  buf           char *       [in/out] The text buffer.
  bufSize       int32_t      Total size of the buffer including null terminator.
  pLen          int32_t *    [in/out] Current text length.
  pCursor       int32_t *    [in/out] Cursor position (character index).
  pScrollOff    int32_t *    [in/out] Horizontal scroll offset.
  pSelStart     int32_t *    [in/out] Selection start (-1 if no selection).
  pSelEnd       int32_t *    [in/out] Selection end (-1 if no selection).
  undoBuf       char *       Undo buffer (same size as buf). NULL to disable undo.
  pUndoLen      int32_t *    [in/out] Length of undo buffer contents.
  pUndoCursor   int32_t *    [in/out] Cursor position saved in undo state.
  fieldWidth    int32_t      Visible field width in pixels. 0 = use widget width.
.endtable

.h3 Supported Keys

.table
  Key              Action
  ---              ------
  Printable        Insert character at cursor (replaces selection if active).
  Backspace        Delete character before cursor or delete selection.
  Delete           Delete character after cursor or delete selection.
  Left             Move cursor left. With Shift, extend selection.
  Right            Move cursor right. With Shift, extend selection.
  Ctrl+Left        Move cursor to previous word boundary. With Shift, extend selection.
  Ctrl+Right       Move cursor to next word boundary. With Shift, extend selection.
  Home             Move cursor to start. With Shift, extend selection.
  End              Move cursor to end. With Shift, extend selection.
  Ctrl+A           Select all text.
  Ctrl+C           Copy selection to clipboard.
  Ctrl+V           Paste from clipboard (newlines stripped).
  Ctrl+X           Cut selection to clipboard.
  Ctrl+Z           Undo (swaps current and undo buffers).
.endtable

Fires w->onChange after any mutation (insert, delete, paste, cut, undo). Automatically adjusts scroll offset to keep cursor visible after every operation.

.topic lib.texthelp.paintline
.title widgetTextEditPaintLine
.toc 1 widgetTextEditPaintLine
.index widgetTextEditPaintLine
.index Text Rendering
.index Selection Highlight

.h2 widgetTextEditPaintLine

Renders a single line of text with optional selection highlighting and a blinking cursor. Splits the visible text into up to three segments (pre-selection, selection, post-selection) and draws each with appropriate colors. The selection highlight uses the menu highlight colors from the color scheme. The cursor is drawn as a one-pixel vertical line when sCursorBlinkOn is true.

.code
void widgetTextEditPaintLine(
    DisplayT *d, const BlitOpsT *ops,
    const BitmapFontT *font,
    const ColorSchemeT *colors,
    int32_t textX, int32_t textY,
    const char *buf, int32_t visLen,
    int32_t scrollOff, int32_t cursorPos,
    int32_t selStart, int32_t selEnd,
    uint32_t fg, uint32_t bg,
    bool showCursor,
    int32_t cursorMinX, int32_t cursorMaxX);
.endcode

.table
  Parameter     Type                  Description
  ---------     ----                  -----------
  d             DisplayT *            Display context for drawing.
  ops           const BlitOpsT *      Blit operations table for the current pixel format.
  font          const BitmapFontT *   Bitmap font for text rendering.
  colors        const ColorSchemeT *  Color scheme (used for selection highlight colors).
  textX         int32_t               Pixel x of the start of visible text.
  textY         int32_t               Pixel y of the text baseline.
  buf           const char *          Visible portion of the text buffer (buf + scrollOff).
  visLen        int32_t               Number of visible characters to draw.
  scrollOff     int32_t               Scroll offset (used to map selection to visible range).
  cursorPos     int32_t               Absolute cursor position in the full buffer.
  selStart      int32_t               Selection start (-1 if no selection).
  selEnd        int32_t               Selection end (-1 if no selection).
  fg            uint32_t              Foreground (text) color.
  bg            uint32_t              Background color.
  showCursor    bool                  Whether to draw the cursor (false when widget lacks focus).
  cursorMinX    int32_t               Left clipping bound for cursor drawing.
  cursorMaxX    int32_t               Right clipping bound for cursor drawing.
.endtable
