Packages
Terminal emulation and driver infrastructure for Raxol. ANSI parsing, screen buffers, command processing, cursor management, input handling, session management, and termbox2 NIF integration.
Current section
Files
Jump to
Current section
Files
lib/raxol/terminal/buffer/cursor.ex
defmodule Raxol.Terminal.Buffer.Cursor do
@moduledoc """
Manages cursor state and operations for the screen buffer.
This module handles cursor position, visibility, style, and blink state.
"""
alias Raxol.Terminal.ScreenBuffer
@type cursor_style :: :block | :underline | :bar
@type t :: %__MODULE__{
position: {non_neg_integer(), non_neg_integer()},
visible: boolean(),
style: cursor_style(),
blink_state: boolean()
}
defstruct [
:position,
:visible,
:style,
:blink_state
]
@doc """
Initializes a new cursor state with default values.
"""
def init do
%__MODULE__{
position: {0, 0},
visible: true,
style: :block,
blink_state: true
}
end
@doc """
Sets the cursor position.
## Parameters
* `buffer` - The screen buffer to modify
* `x` - The x-coordinate (column)
* `y` - The y-coordinate (row)
## Returns
The updated screen buffer with new cursor position.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.set_position(buffer, 10, 5)
iex> Cursor.get_position(buffer)
{10, 5}
"""
def set_position(buffer, x, y) do
case buffer do
%{cursor: cursor} ->
new_cursor = %{cursor | position: {x, y}}
%{buffer | cursor: new_cursor}
%{cursor_position: _} ->
%{buffer | cursor_position: {x, y}}
_ ->
buffer
end
end
@doc """
Gets the current cursor position.
## Parameters
* `buffer` - The screen buffer to query
## Returns
A tuple {x, y} representing the cursor position.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> Cursor.get_position(buffer)
{0, 0}
"""
def get_position(%{cursor: %{position: position}}), do: position
def get_position(%{cursor_position: position}), do: position
def get_position(_buffer), do: {0, 0}
@doc """
Sets the cursor visibility.
## Parameters
* `buffer` - The screen buffer to modify
* `visible` - Whether the cursor should be visible
## Returns
The updated screen buffer with new cursor visibility.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.set_visibility(buffer, false)
iex> Cursor.visible?(buffer)
false
"""
def set_visibility(buffer, visible) do
%{buffer | cursor_visible: visible}
end
@doc """
Checks if the cursor is visible.
## Parameters
* `buffer` - The screen buffer to query
## Returns
A boolean indicating cursor visibility.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> Cursor.visible?(buffer)
true
"""
def visible?(%ScreenBuffer{} = buffer) do
buffer.cursor_visible
end
def visible?(%__MODULE__{} = cursor) do
cursor.visible
end
@doc """
Sets the cursor style.
## Parameters
* `buffer` - The screen buffer to modify
* `style` - The cursor style (:block, :underline, :bar)
## Returns
The updated screen buffer with new cursor style.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.set_style(buffer, :underline)
iex> Cursor.get_style(buffer)
:underline
"""
def set_style(buffer, style) do
Map.put(buffer, :cursor_style, style)
end
@doc """
Gets the current cursor style.
## Parameters
* `buffer` - The screen buffer to query
## Returns
The current cursor style.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> Cursor.get_style(buffer)
:block
"""
def get_style(buffer) do
buffer.cursor.style
end
@doc """
Sets the cursor blink state.
## Parameters
* `buffer` - The screen buffer to modify
* `blink` - Whether the cursor should blink
## Returns
The updated screen buffer with new cursor blink state.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.set_blink(buffer, false)
iex> Cursor.blinking?(buffer)
false
"""
def set_blink(buffer, blink) do
Map.put(buffer, :cursor_blink, blink)
end
@doc """
Checks if the cursor is blinking.
## Parameters
* `buffer` - The screen buffer to query
## Returns
A boolean indicating if the cursor is blinking.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> Cursor.blinking?(buffer)
true
"""
def blinking?(%ScreenBuffer{} = _buffer) do
# ScreenBuffer doesn't have cursor blink state, default to false
false
end
def blinking?(%__MODULE__{} = cursor) do
cursor.blink_state
end
@doc """
Moves the cursor up by the specified number of lines.
## Parameters
* `buffer` - The screen buffer to modify
* `lines` - Number of lines to move up
## Returns
The updated screen buffer with new cursor position.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.move_up(buffer, 2)
iex> Cursor.get_position(buffer)
{0, 0} # Cursor stays at top
"""
def move_up(buffer, lines) do
{x, y} = buffer.cursor.position
new_y = max(0, y - lines)
set_position(buffer, x, new_y)
end
@doc """
Moves the cursor down by the specified number of lines.
## Parameters
* `buffer` - The screen buffer to modify
* `lines` - Number of lines to move down
## Returns
The updated screen buffer with new cursor position.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.move_down(buffer, 2)
iex> Cursor.get_position(buffer)
{0, 2}
"""
def move_down(buffer, lines) do
{x, y} = buffer.cursor.position
new_y = min(buffer.height - 1, y + lines)
set_position(buffer, x, new_y)
end
@doc """
Moves the cursor forward by the specified number of columns.
## Parameters
* `buffer` - The screen buffer to modify
* `columns` - Number of columns to move forward
## Returns
The updated screen buffer with new cursor position.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.move_forward(buffer, 2)
iex> Cursor.get_position(buffer)
{2, 0}
"""
def move_forward(buffer, columns) do
{x, y} = buffer.cursor.position
new_x = min(buffer.width - 1, x + columns)
set_position(buffer, new_x, y)
end
@doc """
Moves the cursor backward by the specified number of columns.
## Parameters
* `buffer` - The screen buffer to modify
* `columns` - Number of columns to move backward
## Returns
The updated screen buffer with new cursor position.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.move_backward(buffer, 2)
iex> Cursor.get_position(buffer)
{0, 0} # Cursor stays at left edge
"""
def move_backward(buffer, columns) do
{x, y} = buffer.cursor.position
new_x = max(0, x - columns)
set_position(buffer, new_x, y)
end
@doc """
Moves the cursor to the specified position.
## Parameters
* `buffer` - The screen buffer to modify
* `position` - The target position as {x, y}
## Returns
The updated screen buffer with new cursor position.
## Examples
iex> buffer = ScreenBuffer.new(80, 24)
iex> buffer = Cursor.move_to(buffer, {10, 5})
iex> Cursor.get_position(buffer)
{10, 5}
"""
def move_to(buffer, {x, y}) do
x = max(0, min(buffer.width - 1, x))
y = max(0, min(buffer.height - 1, y))
set_position(buffer, x, y)
end
@doc """
Sets the cursor position on the ScreenBuffer struct.
"""
def set_cursor_position(buffer, x, y) do
%{buffer | cursor_position: {x, y}}
end
@doc """
Gets the cursor position from the ScreenBuffer struct.
"""
def get_cursor_position(buffer) do
buffer.cursor_position
end
end