[BMX] TextArea from Scratch prototype

Started by Filax, April 02, 2025, 16:34:43

Previous topic - Next topic

Filax

Text area , when you create a GUI, is really a pain to do :) :) Here is a greybox code, but it give you the approach to make your own!

There is still bugged, but it is functional...

Code: BASIC
SuperStrict

' Constants for GUI object types
Const GUI_TYPE_WINDOW:Int = 1
Const GUI_TYPE_TEXTAREA:Int = 2

' Constants for text alignment
Const ALIGN_LEFT:Int = 0
Const ALIGN_CENTER:Int = 1
Const ALIGN_RIGHT:Int = 2

' Base type for GUI objects
Type GuiObject
    Field Family:Int        ' Object type (window, textarea, etc.)
    Field Parent:GuiObject  ' Parent object
    Field Px:Int, Py:Int    ' Absolute position
    Field Tx:Int, Ty:Int    ' Size (width, height)
    Field Visible:Int       ' Visibility (1 = visible, 0 = hidden)
    Field Caption:String    ' Text content
    Field CursorX:Int       ' Cursor X position in pixels
    Field CursorY:Int       ' Current line (index)
    Field StringX:Int       ' Horizontal offset of the current line
    Field StringY:Int       ' Vertical offset of the text
    Field StringOffset:Int  ' Cursor position within the line (in characters)
    Field EditMode:Int      ' Edit mode active (1 = yes, 0 = no)
    Field CursorBlink:Int   ' Cursor blink state (0 or 1)
    Field Timer:Int         ' Timer for blinking
    Field TextAlign:Int     ' Text alignment (0 = left, 1 = center, 2 = right)
    Field SelectionStartY:Int   ' Start line of the selection
    Field SelectionStartOffset:Int ' Start offset of the selection
    Field SelectionEndY:Int     ' End line of the selection
    Field SelectionEndOffset:Int ' End offset of the selection
    Field Selecting:Int         ' Selection in progress indicator
    Field TargetColumnX:Int     ' Target horizontal column for vertical movement (in pixels relative to Px)
    
    Function CreateWindow:GuiObject(x:Int, y:Int, w:Int, h:Int, title:String)
        Local win:GuiObject = New GuiObject
        win.Family = GUI_TYPE_WINDOW
        win.Px = x
        win.Py = y
        win.Tx = w
        win.Ty = h
        win.Visible = 1
        win.Caption = title
        Return win
    End Function
    
    Function CreateTextArea:GuiObject(x:Int, y:Int, w:Int, h:Int, parent:GuiObject)
        Local ta:GuiObject = New GuiObject
        ta.Family = GUI_TYPE_TEXTAREA
        ta.Parent = parent
        ta.Px = parent.Px + x
        ta.Py = parent.Py + y
        ta.Tx = w
        ta.Ty = h
        ta.Visible = 1
        ta.Caption = ""
        ta.CursorX = 0
        ta.CursorY = 0
        ta.StringX = 0
        ta.StringY = 0
        ta.StringOffset = 1
        ta.EditMode = 0
        ta.CursorBlink = 0
        ta.Timer = MilliSecs()
        ta.TextAlign = ALIGN_LEFT
        ta.SelectionStartY = -1
        ta.SelectionStartOffset = -1
        ta.SelectionEndY = -1
        ta.SelectionEndOffset = -1
        ta.Selecting = 0
        ta.TargetColumnX = -1 ' Initialize to -1 (unset)
        Return ta
    End Function
End Type

' Global variables
Global Window:GuiObject
Global TextArea:GuiObject
Global KeyTimer:Int = MilliSecs()

' Initialization
Graphics 800, 600
Window = GuiObject.CreateWindow(50, 50, 700, 500, "Text Area Window")
TextArea = GuiObject.CreateTextArea(20, 20, 250, 80, Window)
TextArea.TextAlign = ALIGN_RIGHT
TextArea.Caption = "Compiling:Test_textArea.bmx.gui.release.win32.x86.c" + "~n" + "Processing:Test_textArea.bmx"

' Main loop
While Not (KeyHit(KEY_ESCAPE) Or AppTerminate())
    UpdateGUI()
    DrawGUI()
    
    If MouseHit(2)
        TextArea.TextAlign = ALIGN_CENTER
    End If

    If MouseHit(3)
        TextArea.TextAlign = ALIGN_LEFT
    End If
    
    DrawText GraphicsHertz(), 10, 10
        
    Flip
Wend

Function UpdateGUI()
    If TextArea.EditMode And (MilliSecs() - TextArea.Timer > 200) Then
        TextArea.CursorBlink = 1 - TextArea.CursorBlink
        TextArea.Timer = MilliSecs()
    EndIf
    
    If MouseDown(1) Then
        If MouseX() >= TextArea.Px And MouseX() < TextArea.Px + TextArea.Tx And ..
           MouseY() >= TextArea.Py And MouseY() < TextArea.Py + TextArea.Ty Then
            If Not TextArea.Selecting Then
                TextArea.EditMode = 1
                TextArea.Selecting = 1
                SetCursorFromClick()
                TextArea.SelectionStartY = TextArea.CursorY
                TextArea.SelectionStartOffset = TextArea.StringOffset
            EndIf
            SetCursorFromClick()
            TextArea.SelectionEndY = TextArea.CursorY
            TextArea.SelectionEndOffset = TextArea.StringOffset
        EndIf
    ElseIf TextArea.Selecting Then
        TextArea.Selecting = 0
    EndIf
    
    If TextArea.EditMode And (MilliSecs() - KeyTimer > 100) Then
        CheckKeys()
        KeyTimer = MilliSecs()
    EndIf
End Function

Function DrawGUI()
    Cls
    
    SetColor 100, 100, 100
    DrawRect Window.Px, Window.Py, Window.Tx, Window.Ty
    SetColor 255, 255, 255
    DrawText Window.Caption, Window.Px + 5, Window.Py + 5
    
    SetColor 255, 255, 255
    DrawRect TextArea.Px, TextArea.Py, TextArea.Tx, TextArea.Ty
    SetColor 0, 0, 0
    DrawRect TextArea.Px + 1, TextArea.Py + 1, TextArea.Tx - 2, TextArea.Ty - 2
    
    Local lines:String[] = TextArea.Caption.Split("~n")
    Local lineHeight:Int = 20
    Local visibleLines:Int = TextArea.Ty / lineHeight
    Local startLine:Int = Max(0, -TextArea.StringY / lineHeight)
    
    If TextArea.SelectionStartY >= 0 And TextArea.SelectionEndY >= 0 Then
        Local selStartY:Int = Min(TextArea.SelectionStartY, TextArea.SelectionEndY)
        Local selEndY:Int = Max(TextArea.SelectionStartY, TextArea.SelectionEndY)
        Local selStartOffset:Int = TextArea.SelectionStartOffset
        Local selEndOffset:Int = TextArea.SelectionEndOffset
        If selStartY = selEndY Then
            selStartOffset = Min(TextArea.SelectionStartOffset, TextArea.SelectionEndOffset)
            selEndOffset = Max(TextArea.SelectionStartOffset, TextArea.SelectionEndOffset)
        ElseIf TextArea.SelectionStartY > TextArea.SelectionEndY Then
            selStartOffset = TextArea.SelectionEndOffset
            selEndOffset = TextArea.SelectionStartOffset
        EndIf
        
        For Local i:Int = Max(startLine, selStartY) To Min(startLine + visibleLines - 1, selEndY)
            Local textX:Int = GetTextX(lines[i], i)
            Local selX1:Int, selX2:Int
            If i = selStartY And i = selEndY Then
                selX1 = textX + CalculateCursorX(lines[i], selStartOffset)
                selX2 = textX + CalculateCursorX(lines[i], selEndOffset)
            ElseIf i = selStartY Then
                selX1 = textX + CalculateCursorX(lines[i], selStartOffset)
                selX2 = textX + TextWidth(lines[i])
            ElseIf i = selEndY Then
                selX1 = textX
                selX2 = textX + CalculateCursorX(lines[i], selEndOffset)
            Else
                selX1 = textX
                selX2 = textX + TextWidth(lines[i])
            EndIf
            SetColor 0, 120, 255
            DrawRect selX1, TextArea.Py + 5 + (i - startLine) * lineHeight, selX2 - selX1, lineHeight
        Next
    EndIf
    
    For Local i:Int = startLine To Min(startLine + visibleLines - 1, lines.Length - 1)
        Local textX:Int = GetTextX(lines[i], i)
        SetColor 255, 255, 255
        DrawText lines[i], textX, TextArea.Py + 5 + (i - startLine) * lineHeight
    Next
    
    If TextArea.EditMode And TextArea.CursorBlink Then
        Local cursorLine:String = lines[TextArea.CursorY]
        Local cursorXPos:Int = GetTextX(cursorLine, TextArea.CursorY) + TextArea.CursorX
        Local cursorYPos:Int = TextArea.Py + 5 + (TextArea.CursorY - startLine) * lineHeight
        SetColor 255, 155, 100
        DrawText "|", cursorXPos - 3, cursorYPos
    EndIf
End Function

Function GetTextX:Int(line:String, lineIndex:Int)
    Local textX:Int
    If lineIndex = TextArea.CursorY And TextArea.EditMode Then
        Select TextArea.TextAlign
            Case ALIGN_LEFT
                textX = TextArea.Px + 5 + TextArea.StringX
            Case ALIGN_CENTER
                textX = TextArea.Px + (TextArea.Tx - TextWidth(line)) / 2 + TextArea.StringX
            Case ALIGN_RIGHT
                textX = TextArea.Px + TextArea.Tx - TextWidth(line) - 5 + TextArea.StringX
        End Select
    Else
        Select TextArea.TextAlign
            Case ALIGN_LEFT
                textX = TextArea.Px + 5
            Case ALIGN_CENTER
                textX = TextArea.Px + (TextArea.Tx - TextWidth(line)) / 2
            Case ALIGN_RIGHT
                textX = TextArea.Px + TextArea.Tx - TextWidth(line) - 5
        End Select
    EndIf
    Return textX
End Function

Function CalculateCursorX:Int(line:String, offset:Int)
    Local width:Int = 0
    For Local i:Int = 1 To Min(offset - 1, Len(line))
        width :+ TextWidth(Mid(line, i, 1))
    Next
    Return width
End Function

Function CheckKeys()
    Local lines:String[] = TextArea.Caption.Split("~n")
    Local lineHeight:Int = 20
    Local currentLine:String = lines[TextArea.CursorY]
    Local shiftDown:Int = KeyDown(KEY_LSHIFT) Or KeyDown(KEY_RSHIFT)
    
    If KeyDown(KEY_LEFT) Then
        If shiftDown Then
            If TextArea.SelectionStartY = -1 Then
                TextArea.SelectionStartY = TextArea.CursorY
                TextArea.SelectionStartOffset = TextArea.StringOffset
            EndIf
            TextArea.StringOffset :- 1
            If TextArea.StringOffset < 1 Then
                If TextArea.CursorY > 0 Then
                    TextArea.CursorY :- 1
                    TextArea.StringOffset = Len(lines[TextArea.CursorY]) + 1
                    TextArea.StringX = 0
                Else
                    TextArea.StringOffset = 1
                EndIf
            EndIf
            TextArea.SelectionEndY = TextArea.CursorY
            TextArea.SelectionEndOffset = TextArea.StringOffset
        Else
            TextArea.StringOffset :- 1
            If TextArea.StringOffset < 1 Then
                If TextArea.CursorY > 0 Then
                    TextArea.CursorY :- 1
                    TextArea.StringOffset = Len(lines[TextArea.CursorY]) + 1
                    TextArea.StringX = 0
                Else
                    TextArea.StringOffset = 1
                EndIf
            EndIf
            ClearSelection()
        EndIf
        TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
        TextArea.TargetColumnX = GetTextX(lines[TextArea.CursorY], TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
        AdjustScrollX()
        
    ElseIf KeyDown(KEY_RIGHT) Then
        If shiftDown Then
            If TextArea.SelectionStartY = -1 Then
                TextArea.SelectionStartY = TextArea.CursorY
                TextArea.SelectionStartOffset = TextArea.StringOffset
            EndIf
            TextArea.StringOffset :+ 1
            If TextArea.StringOffset > Len(lines[TextArea.CursorY]) + 1 Then
                If TextArea.CursorY < lines.Length - 1 Then
                    TextArea.CursorY :+ 1
                    TextArea.StringOffset = 1
                    TextArea.StringX = 0
                Else
                    TextArea.StringOffset = Len(lines[TextArea.CursorY]) + 1
                EndIf
            EndIf
            TextArea.SelectionEndY = TextArea.CursorY
            TextArea.SelectionEndOffset = TextArea.StringOffset
        Else
            TextArea.StringOffset :+ 1
            If TextArea.StringOffset > Len(lines[TextArea.CursorY]) + 1 Then
                If TextArea.CursorY < lines.Length - 1 Then
                    TextArea.CursorY :+ 1
                    TextArea.StringOffset = 1
                    TextArea.StringX = 0
                Else
                    TextArea.StringOffset = Len(lines[TextArea.CursorY]) + 1
                EndIf
            EndIf
            ClearSelection()
        EndIf
        TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
        TextArea.TargetColumnX = GetTextX(lines[TextArea.CursorY], TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
        AdjustScrollX()
        
    ElseIf KeyDown(KEY_UP) Then
        If TextArea.CursorY > 0 Then
            ' Set target column on first vertical move if not already set
            If TextArea.TargetColumnX = -1 Then
                TextArea.TargetColumnX = GetTextX(currentLine, TextArea.CursorY) + TextArea.CursorX - TextArea.Px
            EndIf
            If shiftDown Then
                If TextArea.SelectionStartY = -1 Then
                    TextArea.SelectionStartY = TextArea.CursorY
                    TextArea.SelectionStartOffset = TextArea.StringOffset
                EndIf
                TextArea.CursorY :- 1
                Local newLine:String = lines[TextArea.CursorY]
                TextArea.StringOffset = CalculateOffsetFromX(newLine, TextArea.TargetColumnX)
                TextArea.SelectionEndY = TextArea.CursorY
                TextArea.SelectionEndOffset = TextArea.StringOffset
            Else
                TextArea.CursorY :- 1
                Local newLine:String = lines[TextArea.CursorY]
                TextArea.StringOffset = CalculateOffsetFromX(newLine, TextArea.TargetColumnX)
                ClearSelection()
            EndIf
            TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
            TextArea.StringX = 0
            If TextArea.CursorY * lineHeight + TextArea.StringY < 0 Then
                TextArea.StringY = -TextArea.CursorY * lineHeight
            EndIf
            AdjustScrollX()
        EndIf
        
    ElseIf KeyDown(KEY_DOWN) Then
        If TextArea.CursorY < lines.Length - 1 Then
            ' Set target column on first vertical move if not already set
            If TextArea.TargetColumnX = -1 Then
                TextArea.TargetColumnX = GetTextX(currentLine, TextArea.CursorY) + TextArea.CursorX - TextArea.Px
            EndIf
            If shiftDown Then
                If TextArea.SelectionStartY = -1 Then
                    TextArea.SelectionStartY = TextArea.CursorY
                    TextArea.SelectionStartOffset = TextArea.StringOffset
                EndIf
                TextArea.CursorY :+ 1
                Local newLine:String = lines[TextArea.CursorY]
                TextArea.StringOffset = CalculateOffsetFromX(newLine, TextArea.TargetColumnX)
                TextArea.SelectionEndY = TextArea.CursorY
                TextArea.SelectionEndOffset = TextArea.StringOffset
            Else
                TextArea.CursorY :+ 1
                Local newLine:String = lines[TextArea.CursorY]
                TextArea.StringOffset = CalculateOffsetFromX(newLine, TextArea.TargetColumnX)
                ClearSelection()
            EndIf
            TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
            TextArea.StringX = 0
            If (TextArea.CursorY + 1) * lineHeight + TextArea.StringY > TextArea.Ty Then
                TextArea.StringY = TextArea.Ty - (TextArea.CursorY + 1) * lineHeight
            EndIf
            AdjustScrollX()
        EndIf
        
    ElseIf KeyDown(KEY_HOME) Then
        TextArea.StringOffset = 1
        TextArea.CursorX = 0
        Local lineBaseX:Int = TextArea.Px + TextArea.Tx - TextWidth(lines[TextArea.CursorY]) - 5
        If TextArea.TextAlign = ALIGN_RIGHT Then
            If TextWidth(lines[TextArea.CursorY]) < TextArea.Tx - 10 Then
                TextArea.StringX = 0
            Else
                TextArea.StringX = (TextArea.Px + 5) - lineBaseX
            EndIf
        Else
            TextArea.StringX = 0
        EndIf
        ClearSelection()
        TextArea.TargetColumnX = GetTextX(lines[TextArea.CursorY], TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
        AdjustScrollX()
        
    ElseIf KeyDown(KEY_END) Then
        TextArea.StringOffset = Len(lines[TextArea.CursorY]) + 1
        TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
        Local lineBaseX:Int = TextArea.Px + TextArea.Tx - TextWidth(lines[TextArea.CursorY]) - 5
        If TextArea.TextAlign = ALIGN_RIGHT Then
            TextArea.StringX = 0
            If lineBaseX + TextArea.CursorX > TextArea.Px + TextArea.Tx - 5 Then
                TextArea.StringX = (TextArea.Px + TextArea.Tx - 5) - (lineBaseX + TextArea.CursorX)
            EndIf
        Else
            AdjustScrollX()
        EndIf
        ClearSelection()
        TextArea.TargetColumnX = GetTextX(lines[TextArea.CursorY], TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
        
    ElseIf KeyDown(KEY_DELETE) Then
        If TextArea.SelectionStartY >= 0 Then
            DeleteSelection(lines)
            TextArea.Caption = JoinStrings(lines, "~n")
            ClearSelection()
        ElseIf TextArea.StringOffset <= Len(lines[TextArea.CursorY]) Then
            lines[TextArea.CursorY] = Left(lines[TextArea.CursorY], TextArea.StringOffset - 1) + Right(lines[TextArea.CursorY], Len(lines[TextArea.CursorY]) - TextArea.StringOffset)
            TextArea.Caption = JoinStrings(lines, "~n")
            TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
        ElseIf TextArea.CursorY < lines.Length - 1 Then
            lines[TextArea.CursorY] :+ lines[TextArea.CursorY + 1]
            For Local i:Int = TextArea.CursorY + 1 Until lines.Length - 1
                lines[i] = lines[i + 1]
            Next
            lines = lines[..lines.Length - 1]
            TextArea.Caption = JoinStrings(lines, "~n")
            TextArea.StringX = 0
        EndIf
        FlushKeys()
        
    ElseIf KeyDown(KEY_BACKSPACE) Then
        If TextArea.SelectionStartY >= 0 Then
            DeleteSelection(lines)
            TextArea.Caption = JoinStrings(lines, "~n")
            ClearSelection()
        ElseIf TextArea.StringOffset > 1 Then
            TextArea.StringOffset :- 1
            lines[TextArea.CursorY] = Left(lines[TextArea.CursorY], TextArea.StringOffset - 1) + ..
                                      Right(lines[TextArea.CursorY], Len(lines[TextArea.CursorY]) - TextArea.StringOffset)
            TextArea.Caption = JoinStrings(lines, "~n")
            TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
            TextArea.TargetColumnX = GetTextX(lines[TextArea.CursorY], TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
            AdjustScrollX()
        ElseIf TextArea.CursorY > 0 Then
            TextArea.StringOffset = Len(lines[TextArea.CursorY - 1]) + 1
            lines[TextArea.CursorY - 1] :+ lines[TextArea.CursorY]
            For Local i:Int = TextArea.CursorY Until lines.Length - 1
                lines[i] = lines[i + 1]
            Next
            lines = lines[..lines.Length - 1]
            TextArea.CursorY :- 1
            TextArea.Caption = JoinStrings(lines, "~n")
            TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
            TextArea.StringX = 0
            AdjustScrollX()
        EndIf
        FlushKeys()
        
    ElseIf KeyHit(KEY_ENTER) Then
        If TextArea.SelectionStartY >= 0 Then
            DeleteSelection(lines)
            TextArea.Caption = JoinStrings(lines, "~n")
            ClearSelection()
        EndIf
        Local currentLine:String = lines[TextArea.CursorY]
        lines = lines[..TextArea.CursorY + 1] + [currentLine[TextArea.StringOffset - 1..]] + lines[TextArea.CursorY + 1..]
        lines[TextArea.CursorY] = currentLine[..TextArea.StringOffset - 1]
        TextArea.Caption = JoinStrings(lines, "~n")
        TextArea.CursorY :+ 1
        TextArea.StringOffset = 1
        TextArea.CursorX = 0
        TextArea.StringX = 0
        If (TextArea.CursorY + 1) * lineHeight + TextArea.StringY > TextArea.Ty Then
            TextArea.StringY = TextArea.Ty - (TextArea.CursorY + 1) * lineHeight
        EndIf
        
    Else
        Local char:Int = GetChar()
        If char > 31 And char < 127 Then
            If TextArea.SelectionStartY >= 0 Then
                DeleteSelection(lines)
                ClearSelection()
            EndIf
            lines[TextArea.CursorY] = Left(lines[TextArea.CursorY], TextArea.StringOffset - 1) + ..
                                      Chr(char) + Right(lines[TextArea.CursorY], Len(lines[TextArea.CursorY]) - (TextArea.StringOffset - 1))
            TextArea.Caption = JoinStrings(lines, "~n")
            TextArea.StringOffset :+ 1
            TextArea.CursorX = CalculateCursorX(lines[TextArea.CursorY], TextArea.StringOffset)
            TextArea.TargetColumnX = GetTextX(lines[TextArea.CursorY], TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
            AdjustScrollX()
            FlushKeys()
        EndIf
    EndIf
End Function

Function SetCursorFromClick()
    Local lines:String[] = TextArea.Caption.Split("~n")
    Local lineHeight:Int = 20
    Local clickY:Int = MouseY() - (TextArea.Py + 5 + TextArea.StringY)
    
    TextArea.CursorY = Max(0, Min(lines.Length - 1, clickY / lineHeight))
    Local currentLine:String = lines[TextArea.CursorY]
    
    Local clickX:Int
    Select TextArea.TextAlign
        Case ALIGN_LEFT
            clickX = MouseX() - (TextArea.Px + 5 + TextArea.StringX)
        Case ALIGN_CENTER
            clickX = MouseX() - (TextArea.Px + (TextArea.Tx - TextWidth(currentLine)) / 2 + TextArea.StringX)
        Case ALIGN_RIGHT
            clickX = MouseX() - (TextArea.Px + TextArea.Tx - TextWidth(currentLine) - 5 + TextArea.StringX)
    End Select
    
    Local curWidth:Int = 0
    If clickX <= 0 Then
        TextArea.StringOffset = 1
        TextArea.CursorX = 0
        AdjustScrollX()
        TextArea.TargetColumnX = GetTextX(currentLine, TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
        Return
    EndIf
    
    For Local i:Int = 1 To Len(currentLine)
        Local charWidth:Int = TextWidth(Mid(currentLine, i, 1))
        Local halfCharWidth:Int = charWidth / 2
        curWidth :+ charWidth
        If clickX <= curWidth - halfCharWidth Then
            TextArea.StringOffset = i
            TextArea.CursorX = curWidth - charWidth
            AdjustScrollX()
            TextArea.TargetColumnX = GetTextX(currentLine, TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
            Return
        EndIf
    Next
    
    TextArea.StringOffset = Len(currentLine) + 1
    TextArea.CursorX = CalculateCursorX(currentLine, TextArea.StringOffset)
    AdjustScrollX()
    TextArea.TargetColumnX = GetTextX(currentLine, TextArea.CursorY) + TextArea.CursorX - TextArea.Px ' Update target column
End Function

Function CalculateOffsetFromX:Int(line:String, targetX:Int)
    Local curWidth:Int = 0
    If targetX <= 0 Then
        Return 1
    EndIf
    For Local i:Int = 1 To Len(line)
        Local charWidth:Int = TextWidth(Mid(line, i, 1))
        Local halfCharWidth:Int = charWidth / 2
        curWidth :+ charWidth
        If targetX <= curWidth - halfCharWidth Then
            Return i
        EndIf
    Next
    Return Len(line) + 1
End Function

Function AdjustScrollX()
    Local lines:String[] = TextArea.Caption.Split("~n")
    Local currentLine:String = lines[TextArea.CursorY]
    Local lineWidth:Int = TextWidth(currentLine)
    Local baseX:Int ' Base position without StringX
    
    ' Calculate the base position without StringX
    Select TextArea.TextAlign
        Case ALIGN_LEFT
            baseX = TextArea.Px + 5
        Case ALIGN_CENTER
            baseX = TextArea.Px + (TextArea.Tx - lineWidth) / 2
        Case ALIGN_RIGHT
            baseX = TextArea.Px + TextArea.Tx - lineWidth - 5
    End Select
    
    ' Absolute cursor position with current StringX
    Local cursorPos:Int = baseX + TextArea.CursorX + TextArea.StringX
    
    ' Adjust StringX to keep the cursor within visible bounds
    If cursorPos < TextArea.Px + 5 Then
        TextArea.StringX = (TextArea.Px + 5) - baseX - TextArea.CursorX
    ElseIf cursorPos > TextArea.Px + TextArea.Tx - 5 Then
        TextArea.StringX = (TextArea.Px + TextArea.Tx - 5) - baseX - TextArea.CursorX
    ElseIf lineWidth < TextArea.Tx - 10 And TextArea.TextAlign <> ALIGN_RIGHT Then
        ' If the line is short and doesn't need scrolling, reset StringX except for ALIGN_RIGHT
        TextArea.StringX = 0
    EndIf
End Function

Function DeleteSelection(lines:String[] Var)
    Local startY:Int = Min(TextArea.SelectionStartY, TextArea.SelectionEndY)
    Local endY:Int = Max(TextArea.SelectionStartY, TextArea.SelectionEndY)
    Local startOffset:Int = TextArea.SelectionStartOffset
    Local endOffset:Int = TextArea.SelectionEndOffset
    If startY = endY Then
        startOffset = Min(TextArea.SelectionStartOffset, TextArea.SelectionEndOffset)
        endOffset = Max(TextArea.SelectionStartOffset, TextArea.SelectionEndOffset)
    ElseIf TextArea.SelectionStartY > TextArea.SelectionEndY Then
        startOffset = TextArea.SelectionEndOffset
        endOffset = TextArea.SelectionStartOffset
    EndIf
    
    If startY = endY Then
        lines[startY] = Left(lines[startY], startOffset - 1) + Right(lines[startY], Len(lines[startY]) - endOffset + 1)
    Else
        lines[startY] = Left(lines[startY], startOffset - 1)
        lines[endY] = Right(lines[endY], Len(lines[endY]) - endOffset + 1)
        For Local i:Int = startY + 1 To endY - 1
            lines[i] = ""
        Next
        Local newLines:String[]
        For Local i:Int = 0 Until lines.Length
            If lines[i] <> "" Or i = startY Then
                newLines :+ [lines[i]]
            EndIf
        Next
        lines = newLines
    EndIf
    
    TextArea.CursorY = startY
    TextArea.StringOffset = startOffset
    TextArea.CursorX = CalculateCursorX(lines[startY], startOffset)
    TextArea.StringX = 0
End Function

Function ClearSelection()
    TextArea.SelectionStartY = -1
    TextArea.SelectionStartOffset = -1
    TextArea.SelectionEndY = -1
    TextArea.SelectionEndOffset = -1
End Function

Function JoinStrings:String(lines:String[], separator:String)
    Local result:String = ""
    For Local i:Int = 0 Until lines.Length
        result :+ lines[i]
        If i < lines.Length - 1 Then
            result :+ separator
        EndIf
    Next
    Return result
End Function

Baggey

SUPER! :D

Hope to see more of your .bmx Stuff!  ;)

Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 32GB ram  2x1TB SSD and NVIDIA Quadro K1200 on 2 x HP Z24's . DID Technology stop! Or have we been assimulated!

Windows10, Parrot OS, Raspberry Pi Black Edition! , ZX Spectrum 48k, C64, Enterprise 128K, The SID chip. Im Misunderstood!

Filax

#2
Quote from: Baggey on April 03, 2025, 18:01:27SUPER! :D

Hope to see more of your .bmx Stuff!  ;)

Baggey

Hey! :) I do my best :) I integreted this code into my gui in blitzmax! :) (Example to attached file)

Hardcoal

Youre making interesting things.. keep going
Things I've done:   https://itch.io/profile/hardcoal  [I keep improving them btw]

Filax

I opened my store on Itch.io, i'll try to put my softwares on it! :)
https://blackcreepycat.itch.io/