edittext.nim 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. # author: Ethosa
  2. import ../thirdparty/sdl2 except Color
  3. import
  4. ../thirdparty/opengl,
  5. ../thirdparty/sdl2/ttf,
  6. ../core/font,
  7. ../core/color,
  8. ../core/anchor,
  9. ../core/vector2,
  10. ../core/enums,
  11. ../core/input,
  12. ../core/rect2,
  13. ../core/nodes_os,
  14. ../nodes/node,
  15. ../nodes/canvas,
  16. label,
  17. control
  18. type
  19. EditHandler* = proc(pressed_key: string)
  20. EditTextRef* = ref object of LabelObj
  21. is_blink, is_select: bool
  22. caret*, selectable: bool
  23. blink_time: uint8
  24. caret_color: ColorRef
  25. hint*: StyleText
  26. caret_pos: array[2, uint32]
  27. on_edit*: EditHandler ## This called when user press any key.
  28. const
  29. BLINK_TIME: uint8 = 15
  30. BLINK_WIDTH: float = 2
  31. let edit_handler*: EditHandler = proc(k: string) = discard
  32. proc EditText*(name: string = "EditText", hint: string = "Edit text ..."): EditTextRef =
  33. nodepattern(EditTextRef)
  34. controlpattern()
  35. result.is_blink = false
  36. result.is_select = false
  37. result.caret = true
  38. result.selectable = true
  39. result.caret_pos = [0u32, 0u32]
  40. result.caret_color = Color("#ffccddaa")
  41. result.blink_time = BLINK_TIME
  42. result.text = stext("")
  43. result.hint = stext(hint)
  44. result.hint.setColor(Color("#ccc"))
  45. result.text.setColor(Color("#555"))
  46. result.text_align = Anchor(0, 0, 0, 0)
  47. result.on_edit = edit_handler
  48. result.on_text_changed = text_changed_handler
  49. result.kind = EDIT_TEXT_NODE
  50. if result.text.chars.len() > result.hint.chars.len():
  51. result.rect_min_size = result.text.getTextSize()
  52. else:
  53. result.rect_min_size = result.hint.getTextSize()
  54. result.resize(result.rect_size.x, result.rect_size.y)
  55. method draw*(self: EditTextRef, w, h: Glfloat) =
  56. ## This method uses for redraw Label object.
  57. {.warning[LockLevel]: off.}
  58. procCall self.ControlRef.draw(w, h)
  59. let
  60. x = -w/2 + self.global_position.x
  61. y = h/2 - self.global_position.y
  62. xalign = x + self.rect_size.x*self.text_align.x1 - self.rect_min_size.x*self.text_align.x2
  63. yalign = y - self.rect_size.y*self.text_align.y1 + self.rect_min_size.y*self.text_align.y2
  64. var
  65. lines = self.text.splitLines()
  66. w: cint
  67. h: cint
  68. x1 = 0f
  69. y1 = 0f
  70. i = 0u32
  71. dec self.blink_time
  72. if self.blink_time == 0:
  73. self.blink_time = BLINK_TIME
  74. self.is_blink = not self.is_blink
  75. if self.text.chars.len == 0:
  76. self.hint.renderTo(Vector2(x+self.padding.x1, y-self.padding.y1), self.rect_size, self.text_align)
  77. else:
  78. self.text.renderTo(Vector2(x+self.padding.x1, y-self.padding.y1), self.rect_size, self.text_align)
  79. for line in lines:
  80. discard self.text.font.sizeUtf8(($line).cstring, addr w, addr h)
  81. x1 = self.rect_min_size.x*self.text_align.x1 - w.Glfloat*self.text_align.x2
  82. let coords = [xalign+x1, yalign-y1]
  83. for c in line.chars:
  84. discard self.text.font.sizeUtf8(($c).cstring, addr w, addr h)
  85. if self.is_select and (i >= self.caret_pos[0] and i < self.caret_pos[1]) or (i >= self.caret_pos[1] and i < self.caret_pos[0]):
  86. glColor4f(0.4, 0.4, 0.7, 0.5)
  87. glBegin(GL_QUADS)
  88. glVertex2f(coords[0], coords[1])
  89. glVertex2f(coords[0]+w.Glfloat, coords[1])
  90. glVertex2f(coords[0]+w.Glfloat, coords[1]-h.Glfloat)
  91. glVertex2f(coords[0], coords[1]-h.Glfloat)
  92. glEnd()
  93. if self.is_blink and i == self.caret_pos[0]:
  94. glColor4f(self.caret_color.r, self.caret_color.g, self.caret_color.b, self.caret_color.a)
  95. glBegin(GL_QUADS)
  96. glVertex2f(coords[0], coords[1])
  97. glVertex2f(coords[0]+BLINK_WIDTH, coords[1])
  98. glVertex2f(coords[0]+BLINK_WIDTH, coords[1]-h.Glfloat)
  99. glVertex2f(coords[0], coords[1]-h.Glfloat)
  100. glEnd()
  101. x1 += w.float
  102. inc i
  103. y1 += self.text.spacing
  104. y1 += h.float
  105. inc i
  106. template changeText(self, `text`, `save_properties`, t: untyped): untyped =
  107. var st = stext(`text`)
  108. if `self`.`t`.font.isNil():
  109. `self`.`t`.font = standard_font
  110. st.font = `self`.`t`.font
  111. if `save_properties`:
  112. for i in 0..<st.chars.len():
  113. if i < `self`.`t`.len():
  114. st.chars[i].color = `self`.`t`.chars[i].color
  115. st.chars[i].style = `self`.`t`.chars[i].style
  116. `self`.`t` = st
  117. `self`.rect_min_size = `self`.`t`.getTextSize()
  118. `self`.resize(`self`.rect_size.x, `self`.rect_size.y)
  119. `self`.`t`.rendered = false
  120. method moveCursorBy*(self: EditTextRef, value: int) {.base.} =
  121. if value > 0:
  122. self.caret_pos[0] += value.uint32
  123. else:
  124. self.caret_pos[0] -= (-value).uint32
  125. self.caret_pos[1] = self.caret_pos[0]
  126. method insert*(self: EditTextRef, position: uint32, value: string) {.base.} =
  127. let strtext = $self.text
  128. if position > 0 and position < self.text.len().uint32: # insert in caret pos
  129. self.setText(strtext[0..position-1] & value & strtext[position..^1])
  130. self.moveCursorBy(1)
  131. self.on_edit(value)
  132. elif position == 0: # insert in start of text.
  133. self.setText(value & strtext)
  134. self.moveCursorBy(1)
  135. self.on_edit(value)
  136. elif position == self.text.len().uint32: # insert in end of text.
  137. self.setText(strtext & value)
  138. self.moveCursorBy(1)
  139. self.on_edit(value)
  140. method setText*(self: EditTextRef, t: string, save_properties: bool = false) =
  141. ## Changes text.
  142. ##
  143. ## Arguments:
  144. ## - `text` is a new Label text.
  145. ## - `save_properties` - saves old text properties, if `true`.
  146. changeText(self, t, save_properties, text)
  147. method setHint*(self: EditTextRef, t: string, save_properties: bool = false) {.base.} =
  148. changeText(self, t, save_properties, hint)
  149. method setHintColor*(self: EditTextRef, color: ColorRef) {.base.} =
  150. self.hint.setColor(color)
  151. self.hint.rendered = false
  152. method handle*(self: EditTextRef, event: InputEvent, mouse_on: var NodeRef) =
  153. ## Handles user input. Thi uses in the `window.nim`.
  154. procCall self.ControlRef.handle(event, mouse_on)
  155. when not defined(android) and not defined(ios):
  156. if self.hovered: # Change cursor, if need
  157. setCursor(createSystemCursor(SDL_SYSTEM_CURSOR_IBEAM))
  158. else:
  159. setCursor(createSystemCursor(SDL_SYSTEM_CURSOR_ARROW))
  160. if event.kind == MOUSE and self.hovered:
  161. if event.pressed:
  162. self.caret_pos[0] = self.text.getPosUnderPoint(
  163. self.getGlobalMousePosition(),
  164. self.global_position + self.rect_size/2 - self.text.getTextSize()/2, self.text_align)
  165. if self.selectable:
  166. self.is_select = true
  167. self.caret_pos[1] = self.caret_pos[0]
  168. elif event.kind == MOTION:
  169. if self.is_select and event.pressed:
  170. self.caret_pos[1] = self.text.getPosUnderPoint(
  171. self.getGlobalMousePosition(),
  172. self.global_position + self.rect_size/2 - self.text.getTextSize()/2, self.text_align)
  173. if self.focused:
  174. if event.kind == TEXT and not event.pressed:
  175. # Other keys
  176. self.insert(self.caret_pos[0], event.key)
  177. elif event.kind == KEYBOARD and event.key_int in pressed_keys_cint:
  178. # Arrows
  179. if event.key_int == K_LEFT and self.caret_pos[0] > 0:
  180. self.moveCursorBy(-1)
  181. elif event.key_int == K_RIGHT and self.caret_pos[0] < self.text.len().uint32:
  182. self.moveCursorBy(1)
  183. elif event.key_int == 8: # Backspace
  184. if self.caret_pos[0] > 1 and self.caret_pos[0] < self.text.len().uint32:
  185. self.setText($self.text[0..self.caret_pos[0]-2] & $self.text[self.caret_pos[0]..^1])
  186. self.moveCursorBy(-1)
  187. elif self.caret_pos[0] == self.text.len().uint32 and self.caret_pos[0] > 0:
  188. self.setText($self.text[0..^2])
  189. self.moveCursorBy(-1)
  190. elif self.caret_pos[0] == 1:
  191. self.setText($self.text[1..^1])
  192. self.moveCursorBy(-1)
  193. elif event.key_int == 13: # Next line
  194. self.insert(self.caret_pos[0], "\n")