@@ -305,7 +305,9 @@ void Patch(float sx, float sy, float sw, float sh, float dx, float dy, float dw,
305305
306306 private void RenderCustom ( BoundingBox box , CustomRenderData data )
307307 {
308- if ( data . CustomData is HsvGradientData gradient )
308+ if ( data . CustomData is TextInputWidget widget )
309+ RenderTextInput ( box , widget ) ;
310+ else if ( data . CustomData is HsvGradientData gradient )
309311 RenderHsvGradient ( box , gradient ) ;
310312 }
311313
@@ -376,6 +378,143 @@ private unsafe void RenderHsvGradient(BoundingBox box, HsvGradientData gradient)
376378 }
377379 }
378380
381+ private void RenderTextInput ( BoundingBox box , TextInputWidget widget )
382+ {
383+ var style = widget . CurrentStyle ;
384+ var padding = style . Padding ;
385+
386+ // Background
387+ var bgRect = new Rectangle ( box . X , box . Y , box . Width , box . Height ) ;
388+ var bgColor = widget . IsFocused ? style . FocusedBackgroundColor : style . BackgroundColor ;
389+ if ( style . CornerRadius . TopLeft > 0 )
390+ {
391+ float minDim = Math . Min ( box . Width , box . Height ) ;
392+ float roundness = Math . Clamp ( ( style . CornerRadius . TopLeft * 2 ) / minDim , 0 , 1 ) ;
393+ Raylib . DrawRectangleRounded ( bgRect , roundness , 8 , ToRayColor ( bgColor ) ) ;
394+ }
395+ else
396+ {
397+ Raylib . DrawRectangleRec ( bgRect , ToRayColor ( bgColor ) ) ;
398+ }
399+
400+ // Border
401+ if ( style . Border . Width . Top > 0 || style . Border . Width . Left > 0 )
402+ {
403+ float minDim = Math . Min ( box . Width , box . Height ) ;
404+ float roundness = style . CornerRadius . TopLeft > 0
405+ ? Math . Clamp ( ( style . CornerRadius . TopLeft * 2 ) / minDim , 0 , 1 ) : 0 ;
406+ float lineThick = Math . Max (
407+ Math . Max ( style . Border . Width . Top , style . Border . Width . Bottom ) ,
408+ Math . Max ( style . Border . Width . Left , style . Border . Width . Right ) ) ;
409+ Raylib . DrawRectangleRoundedLines ( bgRect , roundness , 8 , lineThick , ToRayColor ( style . Border . Color ) ) ;
410+ }
411+
412+ // Clip content to element bounds
413+ PushScissor ( box ) ;
414+
415+ // Content area (offset by scroll)
416+ float textX = box . X + padding . Left ;
417+ float textY = box . Y + padding . Top - widget . ScrollY ;
418+ float lineHeight = widget . ComputedLineHeight ;
419+
420+ // Calculate visible row range to skip off-screen lines
421+ int firstVisibleRow = Math . Max ( 0 , ( int ) ( widget . ScrollY / lineHeight ) - 1 ) ;
422+ int lastVisibleRow = ( int ) ( ( widget . ScrollY + box . Height ) / lineHeight ) + 1 ;
423+
424+ // Draw selection highlight
425+ if ( widget . IsFocused && widget . HasSelection )
426+ {
427+ int selStart = Math . Min ( widget . SelectionStart , widget . SelectionEnd ) ;
428+ int selEnd = Math . Max ( widget . SelectionStart , widget . SelectionEnd ) ;
429+ var selColor = ToRayColor ( style . SelectionColor ) ;
430+
431+ // For each visible line that intersects the selection
432+ int pos = 0 ;
433+ int row = 0 ;
434+ string text = widget . Text ;
435+ while ( pos <= text . Length && pos < selEnd )
436+ {
437+ int lineStart = pos ;
438+ int lineEnd = text . IndexOf ( '\n ' , pos ) ;
439+ if ( lineEnd < 0 ) lineEnd = text . Length ;
440+
441+ // Only process visible rows
442+ if ( row > lastVisibleRow ) break ;
443+ if ( row >= firstVisibleRow && lineEnd > selStart && lineStart < selEnd )
444+ {
445+ int hlStart = Math . Max ( lineStart , selStart ) ;
446+ int hlEnd = Math . Min ( lineEnd , selEnd ) ;
447+ float x1 = textX + widget . MeasureSubstring ( lineStart , hlStart ) ;
448+ float x2 = textX + widget . MeasureSubstring ( lineStart , hlEnd ) ;
449+ float w = x2 - x1 ;
450+
451+ // Selection spans past the end of this line (into the \n):
452+ // show a minimal marker so the user sees the line is selected
453+ if ( selEnd > lineEnd && w < 1f )
454+ w = 1f ;
455+
456+ float y = textY + row * lineHeight ;
457+ Raylib . DrawRectangleRec (
458+ new Rectangle ( x1 , y , w , lineHeight ) ,
459+ selColor ) ;
460+ }
461+
462+ pos = lineEnd + 1 ;
463+ row ++ ;
464+ }
465+ }
466+
467+ // Draw only visible text lines (skip off-screen rows)
468+ if ( widget . Text . Length > 0 && style . FontId < _fonts . Length )
469+ {
470+ var font = _fonts [ style . FontId ] ;
471+ var textColor = ToRayColor ( style . TextColor ) ;
472+ string text = widget . Text ;
473+ int lineStart = 0 ;
474+ int row = 0 ;
475+
476+ // Skip to first visible row
477+ while ( row < firstVisibleRow && lineStart <= text . Length )
478+ {
479+ int lineEnd = text . IndexOf ( '\n ' , lineStart ) ;
480+ if ( lineEnd < 0 ) { lineStart = text . Length + 1 ; break ; }
481+ lineStart = lineEnd + 1 ;
482+ row ++ ;
483+ }
484+
485+ // Render visible rows only
486+ while ( lineStart <= text . Length && row <= lastVisibleRow )
487+ {
488+ int lineEnd = text . IndexOf ( '\n ' , lineStart ) ;
489+ if ( lineEnd < 0 ) lineEnd = text . Length ;
490+ if ( lineEnd > lineStart )
491+ {
492+ string line = text . Substring ( lineStart , lineEnd - lineStart ) ;
493+ Raylib . DrawTextEx ( font , line ,
494+ new System . Numerics . Vector2 ( textX , textY + row * lineHeight ) ,
495+ style . FontSize , style . LetterSpacing , textColor ) ;
496+ }
497+ lineStart = lineEnd + 1 ;
498+ row ++ ;
499+ }
500+ }
501+
502+ // Draw cursor (blink every 0.5s)
503+ if ( widget . IsFocused && ( int ) ( Raylib . GetTime ( ) * 2 ) % 2 == 0 )
504+ {
505+ var ( cursorRow , cursorCol ) = widget . GetRowCol ( widget . CursorIndex ) ;
506+ int lineStart = widget . FindLineStart ( widget . CursorIndex ) ;
507+ float cursorX = textX + widget . MeasureSubstring ( lineStart , widget . CursorIndex ) ;
508+ float cursorY = textY + cursorRow * lineHeight ;
509+
510+ Raylib . DrawRectangleRec (
511+ new Rectangle ( cursorX , cursorY , 1.5f , lineHeight ) ,
512+ ToRayColor ( style . CursorColor ) ) ;
513+ }
514+
515+ PopScissor ( ) ;
516+ }
517+
379518 private void RenderShadow ( BoundingBox box , ShadowRenderData data )
380519 {
381520 // The bounding box arrives pre-expanded (offset + blur + spread already applied).
0 commit comments