drawable.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. # author: Ethosa
  2. import
  3. ../thirdparty/opengl,
  4. ../core/color,
  5. ../core/stylesheet,
  6. ../core/image,
  7. ../core/vector2,
  8. ../core/tools,
  9. strutils,
  10. re
  11. type
  12. DrawableObj* = object of RootObj
  13. shadow*: bool
  14. border_width*: float
  15. border_detail*: array[4, int] ## left-top, right-top, right-bottom, left-bottom
  16. border_radius*: array[4, float] ## left-top, right-top, right-bottom, left-bottom
  17. shadow_offset*: Vector2Obj
  18. border_color*: ColorRef
  19. shadow_color*: ColorRef
  20. background_color*: ColorRef
  21. texture*: GlTextureObj
  22. DrawableRef* = ref DrawableObj
  23. let standard_shadow_color: ColorRef = Color(0f, 0f, 0f, 0.5f)
  24. template drawablepattern*(`type`: untyped): untyped =
  25. result = `type`(
  26. texture: GlTextureObj(), border_width: 0,
  27. border_detail: [8, 8, 8, 8],
  28. border_radius: [0.float, 0, 0, 0],
  29. border_color: Color(0, 0, 0, 0),
  30. background_color: Color(0, 0, 0, 0),
  31. shadow_offset: Vector2(0, 0), shadow: false,
  32. shadow_color: standard_shadow_color
  33. )
  34. proc Drawable*: DrawableRef =
  35. drawablepattern(DrawableRef)
  36. template vd* =
  37. ## void template
  38. discard
  39. template recalc*(shadow: bool = false) =
  40. ## Calculates vertex positions.
  41. let (xw, yh) = (x + width, y - height)
  42. when not shadow:
  43. # left top
  44. for i in bezier_iter(1f/self.border_detail[0].float, Vector2(0, -self.border_radius[0]),
  45. Vector2(0, 0), Vector2(self.border_radius[0], 0)):
  46. vertex.add(Vector2(x + i.x, y + i.y))
  47. # right top
  48. for i in bezier_iter(1f/self.border_detail[1].float, Vector2(-self.border_radius[01], 0),
  49. Vector2(0, 0), Vector2(0, -self.border_radius[1])):
  50. vertex.add(Vector2(xw + i.x, y + i.y))
  51. # right bottom
  52. for i in bezier_iter(1f/self.border_detail[2].float, Vector2(0, -self.border_radius[2]),
  53. Vector2(0, 0), Vector2(-self.border_radius[2], 0)):
  54. vertex.add(Vector2(xw + i.x, yh - i.y))
  55. # left bottom
  56. for i in bezier_iter(1f/self.border_detail[3].float, Vector2(self.border_radius[3], 0),
  57. Vector2(0, 0), Vector2(0, self.border_radius[3])):
  58. vertex.add(Vector2(x + i.x, yh + i.y))
  59. else:
  60. glBegin(GL_QUAD_STRIP)
  61. # left top
  62. for i in bezier_iter(1f/self.border_detail[0].float, Vector2(0, -self.border_radius[0]),
  63. Vector2(0, 0), Vector2(self.border_radius[0], 0)):
  64. glColor4f(0, 0, 0, 0)
  65. glVertex2f(x + i.x + self.shadow_offset.x, y + i.y - self.shadow_offset.y)
  66. glColor(self.shadow_color)
  67. glVertex2f(x + i.x, y + i.y)
  68. # right top
  69. for i in bezier_iter(1f/self.border_detail[1].float, Vector2(-self.border_radius[1], 0),
  70. Vector2(0, 0), Vector2(0, -self.border_radius[1])):
  71. glColor4f(0, 0, 0, 0)
  72. glVertex2f(xw + i.x + self.shadow_offset.x, y + i.y - self.shadow_offset.y)
  73. glColor(self.shadow_color)
  74. glVertex2f(xw + i.x, y + i.y)
  75. # right bottom
  76. for i in bezier_iter(1f/self.border_detail[2].float, Vector2(0, -self.border_radius[2]),
  77. Vector2(0, 0), Vector2(-self.border_radius[2], 0)):
  78. glColor4f(0, 0, 0, 0)
  79. glVertex2f(xw + i.x + self.shadow_offset.x, yh - i.y - self.shadow_offset.y)
  80. glColor(self.shadow_color)
  81. glVertex2f(xw + i.x, yh - i.y)
  82. # left bottom
  83. for i in bezier_iter(1f/self.border_detail[3].float, Vector2(self.border_radius[3], 0),
  84. Vector2(0, 0), Vector2(0, self.border_radius[3])):
  85. glColor4f(0, 0, 0, 0)
  86. glVertex2f(x + i.x + self.shadow_offset.x, yh + i.y - self.shadow_offset.y)
  87. glColor(self.shadow_color)
  88. glVertex2f(x + i.x, yh + i.y)
  89. glColor4f(0, 0, 0, 0)
  90. glVertex2f(x + self.shadow_offset.x, y - self.border_radius[0] - self.shadow_offset.y)
  91. glColor(self.shadow_color)
  92. glVertex2f(x, y - self.border_radius[0])
  93. glEnd()
  94. template draw_template*(drawtype, color, function, secondfunc: untyped): untyped =
  95. ## Draws colorized vertexes
  96. ##
  97. ## Arguments:
  98. ## - `drawtype` - draw type, like `GL_POLYGON`
  99. ## - `color` - color for border drawing.
  100. ## - `function` - function called before `glBegin`
  101. ## - `secondfunc` - function called after `glEnd`
  102. glColor4f(`color`.r, `color`.g, `color`.b, `color`.a)
  103. `function`
  104. glBegin(`drawtype`)
  105. for i in vertex:
  106. glVertex2f(i.x, i.y)
  107. glEnd()
  108. `secondfunc`
  109. template draw_texture_template*(drawtype, color, function, secondfunc: untyped): untyped =
  110. glEnable(GL_TEXTURE_2D)
  111. glBindTexture(GL_TEXTURE_2D, self.texture.texture)
  112. glColor4f(`color`.r, `color`.g, `color`.b, `color`.a)
  113. `function`
  114. glBegin(`drawtype`)
  115. var
  116. texture_size = self.texture.size
  117. h = height
  118. w = width
  119. if texture_size.x < width:
  120. let q = width / texture_size.x
  121. texture_size.x *= q
  122. texture_size.y *= q
  123. if texture_size.y < height:
  124. let q = height / texture_size.y
  125. texture_size.x *= q
  126. texture_size.y *= q
  127. # crop .. :eyes:
  128. let q = width / texture_size.x
  129. texture_size.x *= q
  130. texture_size.y *= q
  131. h /= height/width
  132. for i in vertex:
  133. glTexCoord2f((-x + i.x - w + texture_size.x) / texture_size.x,
  134. (y - i.y - h + texture_size.y) / texture_size.y)
  135. glVertex2f(i.x, i.y)
  136. glEnd()
  137. `secondfunc`
  138. glDisable(GL_TEXTURE_2D)
  139. method enableShadow*(self: DrawableRef, val: bool = true) {.base.} =
  140. ## Enables shadow, when `val` is true.
  141. self.shadow = val
  142. method draw*(self: DrawableRef, x1, y1, width, height: float) {.base.} =
  143. var
  144. vertex: seq[Vector2Obj] = @[]
  145. x = x1
  146. y = y1
  147. if self.shadow:
  148. recalc(true)
  149. recalc()
  150. if self.texture.texture > 0'u32:
  151. draw_texture_template(GL_POLYGON, self.background_color, vd(), vd())
  152. else:
  153. draw_template(GL_POLYGON, self.background_color, vd(), vd())
  154. if self.border_width > 0f:
  155. draw_template(GL_LINE_LOOP, self.border_color, glLineWidth(self.border_width), glLineWidth(1))
  156. method getColor*(self: DrawableRef): ColorRef {.base.} =
  157. ## Returns background color.
  158. self.background_color
  159. method loadTexture*(self: DrawableRef, path: string) {.base.} =
  160. ## Loads texture from the file.
  161. ##
  162. ## Arguments:
  163. ## - `path` is an image path.
  164. self.texture = load(path)
  165. self.background_color = Color(1f, 1f, 1f, 1f)
  166. method setBorderColor*(self: DrawableRef, color: ColorRef) {.base.} =
  167. ## Changes border color.
  168. self.border_color = color
  169. method setBorderWidth*(self: DrawableRef, width: float) {.base.} =
  170. ## Changes border width.
  171. self.border_width = width
  172. method setColor*(self: DrawableRef, color: ColorRef) {.base.} =
  173. ## Changes background color.
  174. self.background_color = color
  175. method setCornerRadius*(self: DrawableRef, radius: float) {.base.} =
  176. ## Changes corners radius.
  177. ##
  178. ## Arguments:
  179. ## - `radius` is a new corner radius.
  180. self.border_radius = [radius, radius, radius, radius]
  181. method setCornerRadius*(self: DrawableRef, r1, r2, r3, r4: float) {.base.} =
  182. ## Changes corners radius.
  183. ##
  184. ## Arguments:
  185. ## - `r1` is a new left-top radius.
  186. ## - `r2` is a new right-top radius.
  187. ## - `r3` is a new right-bottm radius.
  188. ## - `r4` is a new left-bottm radius.
  189. self.border_radius = [r1, r2, r3, r4]
  190. method setCornerDetail*(self: DrawableRef, detail: int) {.base.} =
  191. ## Changes corners details.
  192. ##
  193. ## Arguments:
  194. ## - `detail` is a new corner detail.
  195. self.border_detail = [detail, detail, detail, detail]
  196. method setCornerDetail*(self: DrawableRef, d1, d2, d3, d4: int) {.base.} =
  197. ## Changes corners details.
  198. ##
  199. ## Arguments:
  200. ## - `d1` is a new left-top detail.
  201. ## - `d2` is a new right-top detail.
  202. ## - `d3` is a new right-bottm detail.
  203. ## - `d4` is a new left-bottm detail.
  204. self.border_detail = [d1, d2, d3, d4]
  205. method setTexture*(self: DrawableRef, texture: GlTextureObj) {.base.} =
  206. ## Changes drawable texture.
  207. self.texture = texture
  208. self.background_color = Color(1f, 1f, 1f, 1f)
  209. method setShadowColor*(self: DrawableRef, clr: ColorRef) {.base.} =
  210. self.shadow_color = clr
  211. method setShadowOffset*(self: DrawableRef, offset: Vector2Obj) {.base.} =
  212. ## Changes shadow offset.
  213. self.shadow_offset = offset
  214. method setStyle*(self: DrawableRef, s: StyleSheetRef) {.base.} =
  215. ## Sets new stylesheet.
  216. ##
  217. ## Styles:
  218. ## - `background-color` - `rgb(1, 1, 1)`; `rgba(1, 1, 1, 1)`; `#ffef`.
  219. ## - `background-image` - `"assets/image.jpg".
  220. ## - `background` - the same as `background-image` and `background-color`.
  221. ## - `border-color` - the same as `background-color`.
  222. ## - `border-width` - `0`; `8`.
  223. ## - `border-detail` - corners details. `8`; `8 8 8 8`.
  224. ## - `border-radius` - corners radius. same as `border-detail`.
  225. ## - `border` - corners radius and border color. `8 #ffef`.
  226. ## - `shadow` - enables shadow. `true`; `yes`; `on`; `off`; `no`; `false`.
  227. ## - `shadow-offset` - XY shadow offset. `3`; `5 10`.
  228. for i in s.dict:
  229. var matches: array[20, string]
  230. case i.key
  231. # background-color: rgb(51, 100, 255)
  232. of "background-color":
  233. var clr = Color(i.value)
  234. if not clr.isNil():
  235. self.setColor(clr)
  236. # background-image: "assets/img.jpg"
  237. of "background-image":
  238. self.loadTexture(i.value)
  239. # background: "url(path/to/img.jpg)"
  240. # background: "rgb(125, 82, 196)"
  241. # background: "url(img.jpg) #f6f"
  242. of "background":
  243. # #fff | rgba(1, 1, 1)
  244. if i.value.match(re"\A\s*(rgba?\([^\)]+\)\s*|#[a-f0-9]{3,8})\s*\Z", matches):
  245. let tmpclr = Color(matches[0])
  246. if not tmpclr.isNil():
  247. self.setColor(tmpclr)
  248. # url(path/to/image)
  249. elif i.value.match(re"\A\s*url\(([^\)]+)\)\s*\Z", matches):
  250. self.loadTexture(matches[0])
  251. # url(path to image) #fff | rgba(1, 1, 1)
  252. elif i.value.match(re"\A\s*url\(([^\)]+)\)\s+(rgba?\([^\)]+\)\s*|#[a-f0-9]{3,8})\s*\Z", matches):
  253. self.loadTexture(matches[0])
  254. let tmpclr = Color(matches[1])
  255. if not tmpclr.isNil():
  256. self.setColor(tmpclr)
  257. # border-color: rgba(55, 255, 177, 0.1)
  258. of "border-color":
  259. var clr = Color(i.value)
  260. if not clr.isNil():
  261. self.setBorderColor(clr)
  262. # border-radius: 5
  263. # border-radius: 2 4 8 16
  264. of "border-radius":
  265. let tmp = i.value.split(" ")
  266. if tmp.len() == 1:
  267. self.setCornerRadius(parseFloat(tmp[0]))
  268. elif tmp.len() == 4:
  269. self.setCornerRadius(parseFloat(tmp[0]), parseFloat(tmp[1]), parseFloat(tmp[2]), parseFloat(tmp[3]))
  270. # border-detail: 5
  271. # border-detail: 5 50 64 128
  272. of "border-detail":
  273. let tmp = i.value.split(" ")
  274. if tmp.len() == 1:
  275. self.setCornerDetail(parseInt(tmp[0]))
  276. elif tmp.len() == 4:
  277. self.setCornerDetail(parseInt(tmp[0]), parseInt(tmp[1]), parseInt(tmp[2]), parseInt(tmp[3]))
  278. # border-width: 5
  279. of "border-width":
  280. self.setBorderWidth(parseFloat(i.value))
  281. # border: 2 turquoise
  282. of "border":
  283. let tmp = i.value.rsplit(Whitespace, 1)
  284. self.setCornerRadius(parseFloat(tmp[0]))
  285. self.setBorderColor(Color(tmp[1]))
  286. # shadow: true
  287. of "shadow":
  288. self.enableShadow(parseBool(i.value))
  289. # shadow-offset: 3 3
  290. of "shadow-offset":
  291. let tmp = i.value.split(Whitespace, 1)
  292. if tmp.len() == 1:
  293. self.setShadowOffset(Vector2(parseFloat(tmp[0])))
  294. elif tmp.len() == 2:
  295. self.setShadowOffset(Vector2(parseFloat(tmp[0]), parseFloat(tmp[1])))
  296. of "shadow-color":
  297. let clr = Color(i.value)
  298. if not clr.isNil():
  299. self.setShadowColor(clr)
  300. else:
  301. discard