Class: AuthorEngine::CodeEditor::Cursor

Inherits:
Object
  • Object
show all
Includes:
Part::Colors, Support
Defined in:
lib/author_engine/code_editor/cursor.rb

Constant Summary

Constants included from Part::Colors

Part::Colors::COLORS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Part::Colors

#black, #blue, #brown, #dark_blue, #dark_gray, #dark_green, #dark_purple, #green, #indigo, #light_gray, #orange, #peach, #pink, #red, #rgb, #white, #xml_color, #yellow

Methods included from Support

#code_editor, #mouse_over?, #sprite_editor, #window

Constructor Details

#initialize(view:, text_input:, text:) ⇒ Cursor

Returns a new instance of Cursor.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 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

Instance Attribute Details

#active_lineObject (readonly)

Returns the value of attribute active_line.



7
8
9
# File 'lib/author_engine/code_editor/cursor.rb', line 7

def active_line
  @active_line
end

#line_xObject (readonly)

Returns the value of attribute line_x.



7
8
9
# File 'lib/author_engine/code_editor/cursor.rb', line 7

def line_x
  @line_x
end

Instance Method Details

#build_newline_dataObject



159
160
161
162
163
164
165
166
167
168
169
170
# 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) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
# 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) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# 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_lineObject



172
173
174
175
# 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_yObject



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# 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_offsetObject



194
195
196
197
198
199
200
201
# 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_offsetObject



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# 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_newlineObject



212
213
214
215
216
217
# 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) ⇒ Object

returns the column for x on line y



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# 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

#drawObject



48
49
50
51
52
# 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_activelineObject



265
266
267
# 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_selectionObject



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# 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) ⇒ Object

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



250
251
252
253
254
255
256
257
258
259
260
261
262
263
# 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) ⇒ Object



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# 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

#positionObject



286
287
288
# File 'lib/author_engine/code_editor/cursor.rb', line 286

def position
  @text_input.caret_pos
end

#row_at(y) ⇒ Object

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



132
133
134
# 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_allObject



295
296
297
298
# 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) ⇒ Object



290
291
292
293
# 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

#updateObject



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 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_historyObject



239
240
241
242
243
244
245
246
247
# 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_caretObject



203
204
205
206
207
208
209
210
# 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