canvas.nim 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # author: Ethosa
  2. ## Canvas is the root type of all 2D and Control nodes.
  3. ##
  4. ## Canvas used for drawing primitive geometry.
  5. import
  6. math,
  7. ../thirdparty/opengl,
  8. ../thirdparty/sdl2,
  9. ../thirdparty/sdl2/image,
  10. ../core/vector2,
  11. ../core/color,
  12. ../core/anchor,
  13. ../core/enums,
  14. ../core/font,
  15. ../core/tools,
  16. node
  17. const TAU = PI + PI
  18. type
  19. CanvasObj* = object of NodeObj
  20. surface: SurfacePtr
  21. renderer: RendererPtr
  22. canvas_texture: Gluint
  23. position*: Vector2Obj ## Node position, by default is Vector2(0, 0).
  24. global_position*: Vector2Obj ## Node global position.
  25. rect_size*: Vector2Obj ## Node size.
  26. rect_min_size*: Vector2Obj
  27. size_anchor*: Vector2Obj ## Node size anchor.
  28. anchor*: AnchorObj ## Node anchor.
  29. CanvasRef* = ref CanvasObj
  30. proc Canvas*(name: string = "Canvas"): CanvasRef =
  31. ## Creates a new Canvas.
  32. ##
  33. ## Arguments:
  34. ## - `name` is a node name.
  35. runnableExamples:
  36. var canvas1 = Canvas("Canvas")
  37. nodepattern(CanvasRef)
  38. result.rect_size = Vector2(40, 40)
  39. result.rect_min_size = Vector2()
  40. result.position = Vector2()
  41. result.global_position = Vector2()
  42. result.anchor = Anchor(0, 0, 0, 0)
  43. result.size_anchor = Vector2()
  44. result.canvas_texture = 0
  45. result.surface = createRGBSurface(
  46. 0, 40, 40, 32,
  47. 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000'u32)
  48. result.renderer = result.surface.createSoftwareRenderer()
  49. result.kind = CANVAS_NODE
  50. result.type_of_node = NODE_TYPE_CONTROL
  51. # --- Private --- #
  52. template loadColor(color_argument_name: untyped): untyped =
  53. let clr = toUint32Tuple(`color_argument_name`)
  54. canvas.renderer.setDrawColor(clr.r.uint8, clr.g.uint8, clr.b.uint8, clr.a.uint8)
  55. template loadGL(canvas: untyped): untyped =
  56. if `canvas`.canvas_texture == 0:
  57. glGenTextures(1, `canvas`.canvas_texture.addr)
  58. glBindTexture(GL_TEXTURE_2D, `canvas`.canvas_texture)
  59. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
  60. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
  61. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
  62. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
  63. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA.GLint, `canvas`.surface.w, `canvas`.surface.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, `canvas`.surface.pixels)
  64. glBindTexture(GL_TEXTURE_2D, 0)
  65. # --- Public --- #
  66. method calcGlobalPosition*(self: CanvasRef) {.base.} =
  67. ## Returns global node position.
  68. self.global_position = self.position
  69. var current: CanvasRef = self
  70. while current.parent != nil:
  71. current = current.parent.CanvasRef
  72. self.global_position += current.position
  73. method calcPositionAnchor*(self: CanvasRef) {.base.} =
  74. ## Calculates node position with anchor.
  75. ## This used in the Window object.
  76. discard
  77. method draw*(canvas: CanvasRef, w, h: GLfloat) =
  78. ## This uses in the `window.nim`.
  79. let
  80. x = -w/2 + canvas.global_position.x
  81. y = h/2 - canvas.global_position.y
  82. glColor4f(1, 1, 1, 1)
  83. glBindTexture(GL_TEXTURE_2D, canvas.canvas_texture)
  84. glEnable(GL_TEXTURE_2D)
  85. glBegin(GL_QUADS)
  86. glTexCoord2f(0, 0)
  87. glVertex2f(x, y)
  88. glTexCoord2f(1, 0)
  89. glVertex2f(x + canvas.rect_size.x, y)
  90. glTexCoord2f(1, 1)
  91. glVertex2f(x + canvas.rect_size.x, y - canvas.rect_size.y)
  92. glTexCoord2f(0, 1)
  93. glVertex2f(x, y - canvas.rect_size.y)
  94. glEnd()
  95. glDisable(GL_TEXTURE_2D)
  96. glBindTexture(GL_TEXTURE_2D, 0)
  97. method duplicate*(self: CanvasRef): CanvasRef {.base.} =
  98. ## Duplicates Canvas object and create a new Canvas.
  99. self.deepCopy()
  100. method move*(self: CanvasRef, vec2: Vector2Obj) {.base, inline.} =
  101. ## Adds `vec2` to the node position.
  102. ##
  103. ## Arguments:
  104. ## - `vec2`: how much to add to the position on the X,Y axes.
  105. self.position += vec2
  106. self.anchor.clear()
  107. method move*(self: CanvasRef, x, y: float) {.base, inline.} =
  108. ## Adds `x` and` y` to the node position.
  109. ##
  110. ## Arguments:
  111. ## - `x`: how much to add to the position on the X axis.
  112. ## - `y`: how much to add to the position on the Y axis.
  113. self.position += Vector2(x, y)
  114. self.anchor.clear()
  115. method resize*(self: CanvasRef, w, h: GLfloat, save_anchor: bool = false) {.base.} =
  116. ## Resizes canvas.
  117. ##
  118. ## Arguments:
  119. ## - `w` is a new width.
  120. ## - `h` is a new height.
  121. if w > self.rect_min_size.x:
  122. self.rect_size.x = w
  123. if not save_anchor:
  124. self.size_anchor.x = 0.0
  125. else:
  126. self.rect_size.x = self.rect_min_size.x
  127. if h > self.rect_min_size.y:
  128. self.rect_size.y = h
  129. if not save_anchor:
  130. self.size_anchor.y = 0.0
  131. else:
  132. self.rect_size.y = self.rect_min_size.y
  133. if self.kind == CANVAS_NODE:
  134. var new_surface = createRGBSurface(
  135. 0, w.cint, h.cint, 32,
  136. 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000u32)
  137. self.surface.blitSurface(nil, new_surface, nil)
  138. self.renderer.destroy()
  139. self.surface.freeSurface()
  140. self.surface = new_surface
  141. self.renderer = self.surface.createSoftwareRenderer()
  142. loadGL(self)
  143. method setAnchor*(self: CanvasRef, anchor: AnchorObj) {.base.} =
  144. ## Changes node anchor.
  145. ##
  146. ## Arguments:
  147. ## - `anchor` - AnchorObj object.
  148. self.anchor = anchor
  149. method setAnchor*(self: CanvasRef, x1, y1, x2, y2: float) {.base.} =
  150. ## Changes node anchor.
  151. ##
  152. ## Arguments:
  153. ## - `x1` and `y1` - anchor relative to the parent node.
  154. ## - `x2` and `y2` - anchor relative to this node.
  155. self.anchor = Anchor(x1, y1, x2, y2)
  156. method setSizeAnchor*(self: CanvasRef, anchor: Vector2Obj) {.base.} =
  157. self.size_anchor = anchor
  158. method setSizeAnchor*(self: CanvasRef, x, y: float) {.base.} =
  159. self.size_anchor = Vector2(x, y)
  160. # --- Draw functions --- #
  161. proc bezier*(canvas: CanvasRef, x1, y1, x2, y2, x3, y3: GLfloat, color: ColorRef) =
  162. ## Draws a quadric bezier curve at 3 points.
  163. loadColor(color)
  164. for pnt in bezier_iter(0.001, Vector2(x1, y1), Vector2(x2, y2), Vector2(x3, y3)):
  165. canvas.renderer.drawPoint(pnt.x.cint, pnt.y.cint)
  166. loadGL(canvas)
  167. proc circle*(canvas: CanvasRef, x, y, radius: GLfloat, color: ColorRef, quality: int = 100) =
  168. ## Draws a circle in the canvas.
  169. ##
  170. ## Arguments:
  171. ## - `x` - circle center at X axis.
  172. ## - `y` - circle center at Y axis.
  173. ## - `radius` - circle radius.
  174. ## - `color` - Color object.
  175. ## - `quality` - circle quality.
  176. loadColor(color)
  177. for i in 0..quality:
  178. let angle = TAU*i.float/quality.float
  179. canvas.renderer.drawPoint((x + radius*cos(angle)).cint, (y + radius*sin(angle)).cint)
  180. loadGL(canvas)
  181. proc cubic_bezier*(canvas: CanvasRef, x1, y1, x2, y2, x3, y3, x4, y4: GLfloat, color: ColorRef) =
  182. ## Draws a quadric bezier curve at 3 points.
  183. loadColor(color)
  184. for pnt in cubic_bezier_iter(0.001, Vector2(x1, y1), Vector2(x2, y2), Vector2(x3, y3), Vector2(x4, y4)):
  185. canvas.renderer.drawPoint(pnt.x.cint, pnt.y.cint)
  186. loadGL(canvas)
  187. proc fill*(canvas: CanvasRef, color: ColorRef) =
  188. ## Fills canvas.
  189. loadColor(color)
  190. canvas.renderer.clear()
  191. loadGL(canvas)
  192. proc line*(canvas: CanvasRef, x1, y1, x2, y2: GLfloat, color: ColorRef) =
  193. ## Draws a line in the canvas.
  194. ##
  195. ## Arguments:
  196. ## - `x1` - first position at X axis.
  197. ## - `y1` - first position at Y axis.
  198. ## - `x2` - second position at X axis.
  199. ## - `y2` - second position at Y axis.
  200. ## - `color` - line color.
  201. loadColor(color)
  202. canvas.renderer.drawLine(x1.cint, y1.cint, x2.cint, y2.cint)
  203. loadGL(canvas)
  204. proc rect*(canvas: CanvasRef, x1, y1, x2, y2: GLfloat, color: ColorRef) =
  205. ## Draws a line in the canvas.
  206. ##
  207. ## Arguments:
  208. ## - `x1` - first position at X axis.
  209. ## - `y1` - first position at Y axis.
  210. ## - `x2` - second position at X axis.
  211. ## - `y2` - second position at Y axis.
  212. ## - `color` - rectangle color.
  213. loadColor(color)
  214. var rectangle = rect(x1.cint, y1.cint, x2.cint, y2.cint)
  215. canvas.renderer.drawRect(rectangle)
  216. loadGL(canvas)
  217. proc fillRect*(canvas: CanvasRef, x1, y1, x2, y2: GLfloat, color: ColorRef) =
  218. ## Draws a line in the canvas.
  219. ##
  220. ## Arguments:
  221. ## - `x1` - first position at X axis.
  222. ## - `y1` - first position at Y axis.
  223. ## - `x2` - second position at X axis.
  224. ## - `y2` - second position at Y axis.
  225. ## - `color` - rectangle color.
  226. loadColor(color)
  227. var rectangle = rect(x1.cint, y1.cint, x2.cint, y2.cint)
  228. canvas.renderer.fillRect(rectangle)
  229. loadGL(canvas)
  230. proc point*(canvas: CanvasRef, x, y: GLfloat, color: ColorRef) =
  231. ## Draws a point in the canvas.
  232. ##
  233. ## Arguments:
  234. ## - `x` - point position at X axis.
  235. ## - `y` - point position at Y axis.
  236. ## - `color` - point color.
  237. loadColor(color)
  238. canvas.renderer.drawPoint(x.cint, y.cint)
  239. loadGL(canvas)
  240. proc text*(canvas: CanvasRef, text: StyleText | string, x, y: Glfloat, align: Vector2Obj = Vector2()) =
  241. ## Draws multiline text.
  242. ##
  243. ## Arguments:
  244. ## - `text` - multiline colored text.
  245. ## - `align` - horizontal text alignment.
  246. when text is StyleText:
  247. var
  248. surface = text.renderSurface(Anchor(align.x, 0, align.y, 0))
  249. rectangle = rect(x.cint, y.cint, x.cint+surface.w, y.cint+surface.h)
  250. else:
  251. var
  252. surface = stext(text).renderSurface(Anchor(align.x, 0, align.y, 0))
  253. rectangle = rect(x.cint, y.cint, x.cint+surface.w, y.cint+surface.h)
  254. surface.blitSurface(nil, canvas.surface, rectangle.addr)
  255. loadGL(canvas)
  256. proc saveAs*(self: CanvasRef, filename: cstring) =
  257. ## Saves canvas as image file.
  258. discard self.surface.savePNG(filename)