defmodule Raxol.Terminal.StyleBuffer do @moduledoc """ Manages terminal style state and operations. This module handles text attributes, colors, and formatting for terminal output. """ @default_width Raxol.Core.Defaults.terminal_width() @default_height Raxol.Core.Defaults.terminal_height() @type style :: %{ foreground: String.t() | nil, background: String.t() | nil, bold: boolean(), italic: boolean(), underline: boolean(), attributes: [atom()] } @type position :: {non_neg_integer(), non_neg_integer()} @type region :: {position(), position()} @type t :: %__MODULE__{ current_style: style(), default_style: style(), style_map: %{position() => style()}, width: non_neg_integer(), height: non_neg_integer() } defstruct current_style: %{ foreground: nil, background: nil, bold: false, italic: false, underline: false, attributes: [] }, default_style: %{ foreground: nil, background: nil, bold: false, italic: false, underline: false, attributes: [] }, style_map: %{}, width: @default_width, height: @default_height @doc """ Creates a new style buffer with the given dimensions. """ @spec new(non_neg_integer(), non_neg_integer()) :: t() def new(width, height) do default_style = %{ foreground: nil, background: nil, bold: false, italic: false, underline: false, attributes: [] } %__MODULE__{ current_style: default_style, default_style: default_style, style_map: %{}, width: width, height: height } end @doc """ Sets the foreground color. """ @spec set_foreground(t(), String.t()) :: t() def set_foreground(buffer, color) do %{buffer | current_style: Map.put(buffer.current_style, :foreground, color)} end @doc """ Sets the background color. """ @spec set_background(t(), String.t()) :: t() def set_background(buffer, color) do %{buffer | current_style: Map.put(buffer.current_style, :background, color)} end @doc """ Sets text attributes (list of atoms). """ @spec set_attributes(t(), [atom()]) :: t() def set_attributes(buffer, attrs) when is_list(attrs) do %{buffer | current_style: Map.put(buffer.current_style, :attributes, attrs)} end @doc """ Gets the current style. """ @spec get_style(t()) :: style() def get_style(%__MODULE__{current_style: style}), do: style @doc """ Resets the style to default. """ @spec reset_style(t()) :: t() def reset_style(buffer) do %{buffer | current_style: buffer.default_style} end @doc """ Gets the style at a specific position (x, y). Returns the style at the position or the current style if not set. """ @spec get_style_at(t(), non_neg_integer(), non_neg_integer()) :: style() def get_style_at(%__MODULE__{style_map: map, current_style: style}, x, y) do Map.get(map, {x, y}, style) end @doc """ Applies a style to a rectangular region. """ @spec apply_style_to_region( t(), style(), {non_neg_integer(), non_neg_integer()}, {non_neg_integer(), non_neg_integer()} ) :: t() def apply_style_to_region(buffer, style, {x1, y1}, {x2, y2}) do coords = for x <- x1..x2, y <- y1..y2, do: {x, y} new_map = Enum.reduce(coords, buffer.style_map, fn pos, acc -> Map.put(acc, pos, style) end) %{buffer | style_map: new_map} end @doc """ Sets the default style. """ @spec set_default_style(t(), style()) :: t() def set_default_style(buffer, style) do %{buffer | default_style: style} end @doc """ Gets the default style. """ @spec get_default_style(t()) :: style() def get_default_style(%__MODULE__{default_style: style}), do: style @doc """ Merges two styles. """ @spec merge_styles(style(), style()) :: style() def merge_styles(style1, style2) do Map.merge(style1, style2) end @doc """ Validates a style map. """ @spec validate_style(style()) :: :ok | {:error, String.t()} def validate_style(style) when is_map(style) do # Basic validation - check for known style keys valid_keys = [ :foreground, :background, :bold, :italic, :underline, :blink, :reverse, :hidden, :strikethrough ] invalid_keys = Map.keys(style) -- valid_keys validate_key_result(invalid_keys == [], invalid_keys) end def validate_style(_), do: {:error, "Style must be a map"} defp validate_key_result(true, _invalid_keys), do: :ok defp validate_key_result(false, invalid_keys), do: {:error, "Invalid style keys: #{inspect(invalid_keys)}"} end