class AuthorEngine::CodeEditor::Cursor

Attributes

active_line[R]
line_x[R]

Public Class Methods

new(view:, text_input:, text:) click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 8
def initialize(view:, text_input:, text:)
  @view = view
  @text_input = text_input
  @text = text

  @x, @y = 0, 0

  @last_blink = Gosu.milliseconds
  @blink_interval = 250
  @show = false

  @newline_data = {}
  @active_line  = 0
  @active_line_history_size  = 2
  @active_line_history_index = 0
  @active_line_history = []

  @highlight_color = Gosu::Color.rgba(dark_gray.red, dark_gray.green, dark_gray.blue, 100)
  @selection_color = window.lighten(Gosu::Color.rgba(@view.background.red, @view.background.green, @view.background.blue, 100), 100)

  @repeatable_keys = [
    {
      key: Gosu::KbUp,
      down: false,
      repeat_delay: 50,
      last_repeat: 0,
      action: proc {move(:up)}
    },
    {
      key: Gosu::KbDown,
      down: false,
      repeat_delay: 50,
      last_repeat: 0,
      action: proc {move(:down)}
    }
  ]

  caret_stay_left_of_last_newline
end

Public Instance Methods

build_newline_data() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 159
def build_newline_data
  i = 0
  virt_caret = 0

  @text_input.text.each_line do |line|
    virt_caret += line.length
    @newline_data[i] = {position_end_of_line: virt_caret-1, text: line.chomp, text_length: line.chomp.length} # go behind newline

    i+=1
  end

end
button_down(id) click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 74
def button_down(id)
  @repeatable_keys.detect do |key|
    if key[:key] == id
      key[:down] = true
      key[:last_repeat] = Gosu.milliseconds + key[:repeat_delay]
      return true
    end
  end

  case id
  when Gosu::KbA
    select_all if window.control_button_down?
  end
end
button_up(id) click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 89
def button_up(id)
  @repeatable_keys.detect do |key|
    if key[:key] == id
      key[:down] = false
      return true
    end
  end

  # FIXME: Can't seem to get cursor position before it's set to 0...
  # CAUTION: This randomly started working!
  #          And then stopped...?

  caret_stay_left_of_last_newline

  case id
  when Gosu::MsLeft
    return unless @view.mouse_inside_view?

    index = row_at(window.mouse_y)
    line  = @newline_data.dig(index)
    return unless line # no line at index
    right_offset = column_at((window.mouse_x + @view.x_offset.abs) - @text.x, window.mouse_y)
    pos = (line[:position_end_of_line] - line[:text_length]) + right_offset

    set_position(pos)

  # TODO: move to button_down? to fix popping to the top and back
  when Gosu::KbHome
    line = @newline_data[last_active_line(0)]
    pos  = line[:position_end_of_line] - line[:text_length]

    set_position(pos)

  # TODO: move to button_down? to fix popping to the bottom and back
  when Gosu::KbEnd
    line = @newline_data[last_active_line(@newline_data.size-1)]
    pos  = line[:position_end_of_line]

    set_position(pos)
  end
end
calculate_active_line() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 172
def calculate_active_line
  sub_text = @text_input.text[0..position]
  @active_line = sub_text.lines.size-1
end
calculate_x_and_y() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 177
def calculate_x_and_y
  @y = @text.y + (@active_line * @text.height)

  if position == 0
    @x = 0
    return
  end

  line = @text_input.text[0..position-1].lines[@active_line]
  sub_text = ""
  if line
    sub_text = line[0..position-1]
  end

  @x = @text.font.markup_width(sub_text)
end
calculate_x_offset() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 194
def calculate_x_offset
  two_zeros = @text.font.text_width("00")
  if @x + two_zeros > @view.width - @text.x
    @view.x_offset = (@view.width - @text.x) - (@x + two_zeros)
  else
    @view.x_offset = 0
  end
end
calculate_y_offset() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 219
def calculate_y_offset
  y_offset = @view.height - ((@text.y - (window.container.header_height - (@text.height*2))) + (@active_line * @text.height))

  if y_offset > 0 # top is visible, reset to 0 to prevent inverse scrolling
    y_offset = 0
  else
    # FIXME
    top    = (@text.y + @view.y_offset.abs) + @text.height
    bottom = (@text.y + @view.y_offset.abs + @view.height) - @text.height * 2

    if (@y).between?(top, bottom) # don't follow cursor up if not at top of screen
      y_offset = @view.y_offset
    elsif @y < top && y_offset <= 0
      y_offset = @view.y_offset + @text.height
    end
  end

  @view.y_offset = y_offset
end
caret_stay_left_of_last_newline() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 212
def caret_stay_left_of_last_newline
  @text_input.text+="\n" unless @text_input.text.end_with?("\n")

  eof = @text_input.text.chomp.length
  set_position(eof) if position > eof
end
column_at(x, y, y_is_line = false) click to toggle source

returns the column for x on line y

# File lib/author_engine/code_editor/cursor.rb, line 137
def column_at(x, y, y_is_line = false)
  x = @text.x if x < x-@text.x
  line  = @newline_data.dig(row_at(y)) unless y_is_line
  line  = @newline_data.dig(y) if y_is_line
  column= 0
  return unless line

  text  = line[:text]
  buffer= ""
  local_x=0

  text.size.times do |i|
    local_x = @text.font.text_width(buffer)

    break if local_x >= x
    column+=1
    buffer+=text.chars[i]
  end

  return column
end
draw() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 48
def draw
  highlight_activeline
  highlight_selection
  Gosu.draw_rect(@text.x + @x, @y, 1, @text.height, light_gray) if @show
end
highlight_activeline() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 265
def highlight_activeline
  Gosu.draw_rect(0 - @view.x_offset, @y, @view.width, @text.height, @highlight_color)
end
highlight_selection() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 269
def highlight_selection
  return if @text_input.selection_start == position

  line      = @newline_data[@active_line]
  selection_x = 0
  if @text_input.selection_start < position
    selection_x = @text.font.text_width(@text_input.text[@text_input.selection_start..position-1])

    Gosu.draw_rect((@x + @text.x) - selection_x, @text.y + (@active_line * @text.height), selection_x, @text.height, @selection_color)
  else
    selection_x = @text.font.text_width(@text_input.text[position..@text_input.selection_start-1])

    Gosu.draw_rect((@x + @text.x), @text.y + (@active_line * @text.height), selection_x, @text.height, @selection_color)
  end

end
last_active_line(poison) click to toggle source

poison: line index at which home is 0 and end is @newline_data.size-1

# File lib/author_engine/code_editor/cursor.rb, line 250
def last_active_line(poison)
  candidate = @active_line

  # p poison

  list = @active_line_history.reject{|l| l == poison}
  return candidate unless list

  # p @active_line_history,list

  candidate = list.reverse.first if list.size > 0

  return candidate
end
move(direction) click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 300
def move(direction)
  pos = @text_input.caret_pos
  line = nil

  if direction == :up
    return if @active_line == 0
    line  = @newline_data.dig(@active_line-1)
    return unless line # no line at index
    # current_offset = column_at(@x, (@active_line), true) # current line offset
    above_offset = column_at(@x, (@active_line-1), true) # line up offset

    # right_offset = current_offset
    # right_offset = above_offset if current_offset >= above_offset
    right_offset = above_offset

    pos = (line[:position_end_of_line] - line[:text_length]) + right_offset

  elsif direction == :down
    return if @text_input.caret_pos == @text_input.text.size
    return unless @newline_data[@active_line+1]
    line  = @newline_data.dig(@active_line+1)
    return unless line # no line at index
    # current_offset = column_at(@x, (@active_line), true) # current line offset
    below_offset = column_at(@x, (@active_line+1), true) # line down offset

    # right_offset = current_offset
    # right_offset = below_offset if current_offset >= below_offset
    right_offset = below_offset

    pos = (line[:position_end_of_line] - line[:text_length]) + right_offset

  else
    raise ":up or :down please."
  end

  set_position(pos)
end
position() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 286
def position
  @text_input.caret_pos
end
row_at(y) click to toggle source

returns the line of lines from the top that y is at

# File lib/author_engine/code_editor/cursor.rb, line 132
def row_at(y)
  return (((y.to_f - window.container.header_height.to_f) - @view.y_offset.to_f) / @text.height).floor
end
select_all() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 295
def select_all
  @text_input.selection_start = 0
  @text_input.caret_pos = @text_input.text.length-1
end
set_position(int) click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 290
def set_position(int)
  @text_input.caret_pos = int
  @text_input.selection_start = int # See: https://github.com/gosu/gosu/issues/228
end
update() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 54
def update
  if (Gosu.milliseconds - @last_blink) > @blink_interval
    @last_blink = Gosu.milliseconds
    @show = !@show
  end

  update_caret

  update_active_line_history

  @repeatable_keys.each do |key|
    if key[:down]
      if Gosu.milliseconds > key[:last_repeat] + key[:repeat_delay]
        key[:action].call
        key[:last_repeat] = Gosu.milliseconds
      end
    end
  end
end
update_active_line_history() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 239
def update_active_line_history
  @active_line_history_index = 0 unless @active_line_history_index < @active_line_history_size

  unless @active_line_history[@active_line_history_index-1] == @active_line
    @active_line_history[@active_line_history_index] = @active_line
    @active_line_history_index+=1
  end

end
update_caret() click to toggle source
# File lib/author_engine/code_editor/cursor.rb, line 203
def update_caret
  build_newline_data
  calculate_active_line

  calculate_x_and_y
  calculate_x_offset
  calculate_y_offset
end