edittext.nim 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. # author: Ethosa
  2. ## It provides primitive text input.
  3. import
  4. strutils,
  5. ../thirdparty/opengl,
  6. ../thirdparty/opengl/glut,
  7. ../core/vector2,
  8. ../core/rect2,
  9. ../core/anchor,
  10. ../core/input,
  11. ../core/enums,
  12. ../core/color,
  13. ../nodes/node,
  14. control
  15. type
  16. EditTextObj* = object of ControlPtr
  17. blit_caret*: bool
  18. blit_speed*: float
  19. blit_time*: float
  20. caret_position*: int
  21. font*: pointer ## Glut font data.
  22. spacing*: float ## Font spacing.
  23. size*: float ## Font size.
  24. text*: string ## EditText text.
  25. hint_text*: string
  26. color*: ColorRef ## Text color.
  27. hint_color*: ColorRef ## Hint color.
  28. caret_color*: ColorRef
  29. text_align*: AnchorRef ## Text align.
  30. on_edit*: proc(pressed_key: string): void ## This called when user press any key.
  31. EditTextPtr* = ptr EditTextObj
  32. proc EditText*(name: string, variable: var EditTextObj): EditTextPtr =
  33. ## Creates a new EditText pointer.
  34. ##
  35. ## Arguments:
  36. ## - `name` is a node name.
  37. ## - `variable` is a EditTextObj variable.
  38. runnableExamples:
  39. var
  40. editobj: EditTextObj
  41. edit = EditText("EditText", editobj)
  42. nodepattern(EditTextObj)
  43. controlpattern()
  44. variable.rect_size.x = 64
  45. variable.rect_size.y = 32
  46. variable.text = ""
  47. variable.font = GLUT_BITMAP_HELVETICA_12
  48. variable.size = 12
  49. variable.spacing = 2
  50. variable.text_align = Anchor(0, 0, 0, 0)
  51. variable.color = Color(1f, 1f, 1f)
  52. variable.hint_color = Color(0.8, 0.8, 0.8)
  53. variable.hint_text = "Edit text ..."
  54. variable.caret_position = 0
  55. variable.blit_caret = true
  56. variable.caret_color = Color(1f, 1f, 1f, 0.7)
  57. variable.blit_speed = 0.05
  58. variable.blit_time = 0f
  59. variable.on_edit = proc(key: string) = discard
  60. variable.kind = EDIT_TEXT_NODE
  61. proc EditText*(obj: var EditTextObj): EditTextPtr {.inline.} =
  62. ## Creates a new EditText pointer with default name "EditText".
  63. ##
  64. ## Arguments:
  65. ## - `name` is a node name.
  66. ## - `variable` is a EditTextObj variable.
  67. runnableExamples:
  68. var
  69. editobj: EditTextObj
  70. edit = EditText(editobj)
  71. EditText("EditText", obj)
  72. method getTextSize*(self: EditTextPtr): Vector2Ref {.base.} =
  73. ## Returns text size.
  74. result = Vector2()
  75. for line in self.text.splitLines(): # get text height
  76. var x: float = 0f
  77. for c in line:
  78. x += self.font.glutBitmapWidth(c.int).float
  79. if x > result.x:
  80. result.x = x
  81. result.y += self.spacing + self.size
  82. if result.y > 0:
  83. result.y -= self.spacing
  84. method getLine*(self: EditTextPtr): int {.base.} =
  85. ## Returns current caret line.
  86. var
  87. caret_pos = 0
  88. l = 0
  89. for line in self.text.splitLines():
  90. for c in line:
  91. if caret_pos == self.caret_position:
  92. break
  93. inc caret_pos
  94. if caret_pos == self.caret_position:
  95. break
  96. inc l
  97. inc caret_pos
  98. return l
  99. method getCharPositionUnderMouse*(self: EditTextPtr): int {.base.} =
  100. ## Returns char position under mouse.
  101. let
  102. size = self.getTextSize()
  103. pos = Vector2Ref(x: last_event.x, y: last_event.y) - self.global_position
  104. if pos.y > size.y:
  105. return self.text.len()
  106. else:
  107. var
  108. res = Vector2()
  109. caret_pos = 0
  110. current_pos = 0
  111. for line in self.text.splitLines(): # get text height
  112. var x: float = 0f
  113. current_pos = 0
  114. res.y += self.spacing + self.size
  115. for c in line:
  116. x += self.font.glutBitmapWidth(c.int).float
  117. inc caret_pos
  118. inc current_pos
  119. if res.y >= pos.y:
  120. if current_pos < line.len() and x <= pos.x:
  121. continue
  122. return caret_pos
  123. inc caret_pos
  124. if x > res.x:
  125. res.x = x
  126. method getCharUnderMouse*(self: EditTextPtr): char {.base.} =
  127. ## Returns char under mouse
  128. return self.text[self.getCharPositionUnderMouse()]
  129. method getWordPositionUnderMouse*(self: EditTextPtr): tuple[startpos, endpos: int] {.base.} =
  130. ## Returns words under mouse.
  131. ## Returns (-1, -1), if under mouse no founds words.
  132. var caret = self.getCharPositionUnderMouse()
  133. if caret == self.text.len():
  134. return (-1, -1)
  135. if self.text.len() > 0 and self.text[caret] != ' ':
  136. # Left
  137. var i = caret
  138. while self.text[i] != ' ':
  139. dec i
  140. if i < 0:
  141. break
  142. if i > 0:
  143. if self.text[i] == ' ':
  144. i += 1
  145. result.startpos = i
  146. else:
  147. result.startpos = 0
  148. # Right
  149. i = caret
  150. while self.text[i] != ' ':
  151. inc i
  152. if i > self.text.len()-1:
  153. break
  154. if i < self.text.len():
  155. if self.text[i] == ' ':
  156. i -= 1
  157. result.endpos = i
  158. else:
  159. result.endpos = self.text.len()-1
  160. else:
  161. return (-1, -1)
  162. method getWordUnderMouse*(self: EditTextPtr): string {.base.} =
  163. ## Returns words under mouse.
  164. let (s, e) = self.getWordPositionUnderMouse()
  165. if self.text.len() > 0 and s > -1:
  166. return self.text[s..e]
  167. method draw*(self: EditTextPtr, w, h: GLfloat) =
  168. ## This method uses in the `window.nim`
  169. self.calcGlobalPosition()
  170. let
  171. x = -w/2 + self.global_position.x
  172. y = h/2 - self.global_position.y
  173. text =
  174. if self.text.len() > 0:
  175. self.text
  176. else:
  177. self.hint_text
  178. color =
  179. if self.text.len() > 0:
  180. self.color
  181. else:
  182. self.hint_color
  183. glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
  184. glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
  185. var
  186. th = 0f
  187. char_num = 0
  188. for line in text.splitLines(): # get text height
  189. th += self.spacing + self.size
  190. if th != 0:
  191. th -= self.spacing
  192. var ty = y - self.rect_size.y*self.text_align.y1 + th*self.text_align.y2 - self.size
  193. for line in text.splitLines():
  194. var tw = self.font.glutBitmapLength(line).float
  195. # Draw text:
  196. var tx = x + self.rect_size.x*self.text_align.x1 - tw * self.text_align.x2
  197. for c in line:
  198. glColor4f(color.r, color.g, color.b, color.a)
  199. let
  200. cw = self.font.glutBitmapWidth(c.int).float
  201. right =
  202. if self.text_align.x2 > 0.9 and self.text_align.x1 > 0.9:
  203. 1f
  204. else:
  205. 0f
  206. bottom =
  207. if self.text_align.y2 > 0.9 and self.text_align.y1 > 0.9:
  208. 1f
  209. else:
  210. 0f
  211. if tx >= x and tx < x + self.rect_size.x+right and ty <= y and ty > y - self.rect_size.y+bottom:
  212. glRasterPos2f(tx, ty) # set char position
  213. self.font.glutBitmapCharacter(c.int) # render char
  214. inc char_num
  215. if char_num == self.caret_position and self.blit_caret and self.blit_time > 0.8 and self.focused:
  216. glColor4f(self.caret_color.r, self.caret_color.g, self.caret_color.b, self.caret_color.a)
  217. glRectf(tx+cw, ty, tx+cw+1.5, ty+self.size-2)
  218. if self.blit_time > 2f:
  219. self.blit_time = 0f
  220. tx += cw
  221. inc char_num
  222. ty -= self.spacing + self.size
  223. self.blit_time += self.blit_speed
  224. # Press
  225. if self.pressed:
  226. self.press(last_event.x, last_event.y)
  227. method duplicate*(self: EditTextPtr, obj: var EditTextObj): EditTextPtr {.base.} =
  228. ## Duplicates EditText object and create a new EditText pointer.
  229. obj = self[]
  230. obj.addr
  231. method handle*(self: EditTextPtr, event: InputEvent, mouse_on: var NodePtr) =
  232. ## Handles user input. Thi uses in the `window.nim`.
  233. procCall self.ControlPtr.handle(event, mouse_on)
  234. if self.hovered: # Change cursor, if need
  235. glutSetCursor(GLUT_CURSOR_TEXT)
  236. else:
  237. glutSetCursor(GLUT_CURSOR_LEFT_ARROW)
  238. if event.kind == MOUSE and event.pressed:
  239. self.caret_position = self.getCharPositionUnderMouse()
  240. if self.focused:
  241. if event.kind == KEYBOARD:
  242. if event.key_cint in pressed_keys_cints: # Special chars
  243. if event.key_cint == K_LEFT and self.caret_position > 0:
  244. self.caret_position -= 1
  245. elif event.key_cint == K_RIGHT and self.caret_position < self.text.len():
  246. self.caret_position += 1
  247. elif event.key in pressed_keys: # Normal chars
  248. if event.key_int == 8: # Backspace
  249. if self.caret_position > 1 and self.caret_position < self.text.len():
  250. self.text = self.text[0..self.caret_position-2] & self.text[self.caret_position..^1]
  251. self.caret_position -= 1
  252. elif self.caret_position == self.text.len() and self.caret_position > 0:
  253. self.text = self.text[0..^2]
  254. self.caret_position -= 1
  255. elif self.caret_position == 1:
  256. self.text = self.text[1..^1]
  257. self.caret_position -= 1
  258. # Other keys
  259. elif self.caret_position > 0 and self.caret_position < self.text.len():
  260. self.text = self.text[0..self.caret_position-1] & event.key & self.text[self.caret_position..^1]
  261. self.caret_position += 1
  262. self.on_edit(event.key)
  263. elif self.caret_position == 0:
  264. self.text = event.key & self.text
  265. self.caret_position += 1
  266. self.on_edit(event.key)
  267. elif self.caret_position == self.text.len():
  268. self.text &= event.key
  269. self.caret_position += 1
  270. self.on_edit(event.key)
  271. method setTextAlign*(self: EditTextPtr, align: AnchorRef) {.base.} =
  272. ## Changes text align.
  273. self.text_align = align
  274. method setTextAlign*(self: EditTextPtr, x1, y1, x2, y2: float) {.base.} =
  275. ## Changes text align.
  276. self.text_align = Anchor(x1, y1, x2, y2)
  277. method setText*(self: EditTextPtr, value: string) {.base.} =
  278. ## Changes EditText text.
  279. self.text = value