Browse Source

Merge pull request #16 from `nightly` branch

Nightly v0.1.0
Ethosa 3 years ago
parent
commit
b3534cce52
43 changed files with 733 additions and 332 deletions
  1. 15 7
      README.md
  2. 39 81
      examples/calculator/main.nim
  3. 1 1
      examples/hello_world/main.nim
  4. 39 53
      examples/novel/main.nim
  5. 6 12
      examples/screensaver/main.nim
  6. 12 16
      examples/snake/main.nim
  7. 1 1
      nodesnim.nimble
  8. 1 0
      src/nodesnim.nim
  9. 3 2
      src/nodesnim/core.nim
  10. 44 23
      src/nodesnim/core/color.nim
  11. 16 4
      src/nodesnim/core/scene_builder.nim
  12. 96 0
      src/nodesnim/core/stylesheet.nim
  13. 3 0
      src/nodesnim/core/vector2.nim
  14. 5 0
      src/nodesnim/graphics.nim
  15. 298 0
      src/nodesnim/graphics/drawable.nim
  16. 1 0
      src/nodesnim/nodes/animation_player.nim
  17. 3 0
      src/nodesnim/nodes/node.nim
  18. 0 7
      src/nodesnim/nodescontrol/box.nim
  19. 14 15
      src/nodesnim/nodescontrol/button.nim
  20. 6 2
      src/nodesnim/nodescontrol/color_rect.nim
  21. 36 6
      src/nodesnim/nodescontrol/control.nim
  22. 3 3
      src/nodesnim/nodescontrol/counter.nim
  23. 2 2
      src/nodesnim/nodescontrol/edittext.nim
  24. 0 7
      src/nodesnim/nodescontrol/grid_box.nim
  25. 0 7
      src/nodesnim/nodescontrol/hbox.nim
  26. 1 4
      src/nodesnim/nodescontrol/label.nim
  27. 3 4
      src/nodesnim/nodescontrol/lineedit.nim
  28. 2 14
      src/nodesnim/nodescontrol/popup.nim
  29. 2 2
      src/nodesnim/nodescontrol/progress_bar.nim
  30. 2 2
      src/nodesnim/nodescontrol/rich_edit_text.nim
  31. 2 3
      src/nodesnim/nodescontrol/rich_label.nim
  32. 2 2
      src/nodesnim/nodescontrol/scroll.nim
  33. 3 3
      src/nodesnim/nodescontrol/slider.nim
  34. 11 32
      src/nodesnim/nodescontrol/subwindow.nim
  35. 2 2
      src/nodesnim/nodescontrol/texture_progress_bar.nim
  36. 2 3
      src/nodesnim/nodescontrol/texture_rect.nim
  37. 0 7
      src/nodesnim/nodescontrol/vbox.nim
  38. 3 3
      src/nodesnim/nodescontrol/vslider.nim
  39. 1 1
      src/nodesnim/window.nim
  40. 2 0
      tests/README.md
  41. 1 1
      tests/test42.nim
  42. 22 0
      tests/test44.nim
  43. 28 0
      tests/test45.nim

+ 15 - 7
README.md

@@ -7,12 +7,19 @@
 [![time tracker](https://wakatime.com/badge/github/Ethosa/nodesnim.svg)](https://wakatime.com/badge/github/Ethosa/nodesnim)
 [![test](https://github.com/Ethosa/nodesnim/workflows/test/badge.svg)](https://github.com/Ethosa/nodesnim/actions)
 
-<h4>Stable version - 0.0.5</h4>
+<h4>Stable version - 0.1.0</h4>
 </div>
 
 ## Install
-1. Install this repo
-   -  `nimble install nodesnim` or `nimble install https://github.com/Ethosa/nodesnim.git`
+1. Install Nodesnim
+   -  Stable: `nimble install nodesnim` or `nimble install https://github.com/Ethosa/nodesnim.git`
+   -  Nightly:
+      ```bash
+      git clone https://github.com/Ethosa/nodesnim/`
+      cd nodesnim
+      git checkout nightly
+      nimble install
+      ```
 2. Install dependencies
    -  Linux (tested on Ubuntu and Mint):
       - `sudo apt install -y freeglut3 freeglut3-dev`
@@ -27,13 +34,14 @@
 ## Features
 - Godot-like node system.
 - Build nodes with YML-like syntax.
+- Stylesheets (CSS-like).
 - Simple usage
   ```nim
   import nodesnim
 
   Window("Hello, world!")
-  
-  
+
+
   build:
     - Scene scene:
       name: "Main"
@@ -41,8 +49,8 @@
         call setSizeAnchor(1, 1)
         call setTextAlign(0.5, 0.5, 0.5, 0.5)
         call setText("Hello, world!")
-        background_color: Color(31, 45, 62)
-  
+        call setBackgroundColor(Color(31, 45, 62))
+
   addMainScene(scene)
   windowLaunch()
   

+ 39 - 81
examples/calculator/main.nim

@@ -5,46 +5,10 @@ import strutils
 Window("Calc")
 
 var
-  main = Scene("Main")
-
   first: string = ""
   second: string = ""
   sign: string = ""
 
-  vbox = VBox()
-  result = Label("Result")
-  buttons = GridBox("Buttons")
-
-  button_7 = Button("Button 7")
-  button_8 = Button("Button 8")
-  button_9 = Button("Button 9")
-  button_4 = Button("Button 4")
-  button_5 = Button("Button 5")
-  button_6 = Button("Button 6")
-  button_1 = Button("Button 1")
-  button_2 = Button("Button 2")
-  button_3 = Button("Button 3")
-  button_0 = Button("Button 0")
-  button_00 = Button("Button 00")
-  button_add = Button("Button +")
-  button_sub = Button("Button -")
-  button_mul = Button("Button *")
-  button_div = Button("Button /")
-  button_eq = Button("Button =")
-
-main.addChild(vbox)
-vbox.addChild(result)
-vbox.addChild(buttons)
-vbox.setChildAnchor(0.5, 0.5, 0.5, 0.5)
-vbox.setSizeAnchor(1, 1)
-buttons.setRow(4)
-buttons.addChilds(
-  button_7, button_8, button_9, button_add,
-  button_4, button_5, button_6, button_sub,
-  button_1, button_2, button_3, button_mul,
-  button_0, button_00, button_div, button_eq)
-
-
 proc number(self: ButtonRef, x, y: float) =
   if sign == "":
     first &= self.text
@@ -52,15 +16,17 @@ proc number(self: ButtonRef, x, y: float) =
     second &= self.text
 
 proc equal(): string =
+  let f = parseFloat(first)
   result =
-    if sign == "+":
-      $(parseFloat(first) + parseFloat(second))
-    elif sign == "-":
-      $(parseFloat(first) - parseFloat(second))
-    elif sign == "*":
-      $(parseFloat(first) * parseFloat(second))
-    elif sign == "/":
-      $(parseFloat(first) / parseFloat(second))
+    case sign:
+    of "+":
+      $(f + parseFloat(second))
+    of "-":
+      $(f - parseFloat(second))
+    of "x":
+      $(f * parseFloat(second))
+    of "/":
+      $(f / parseFloat(second))
     else:
       first
 
@@ -72,37 +38,34 @@ proc on_sign(self: ButtonRef, x, y: float) =
   elif first != "":
     sign = self.text
 
-
-button_1.text = "1"
-button_2.text = "2"
-button_3.text = "3"
-button_4.text = "4"
-button_5.text = "5"
-button_6.text = "6"
-button_7.text = "7"
-button_8.text = "8"
-button_9.text = "9"
-button_0.text = "0"
-button_00.text = "00"
-
-button_sub.text = "-"
-button_add.text = "+"
-button_mul.text = "*"
-button_div.text = "/"
-
-button_1.on_touch = number
-button_2.on_touch = number
-button_3.on_touch = number
-button_4.on_touch = number
-button_5.on_touch = number
-button_6.on_touch = number
-button_7.on_touch = number
-button_8.on_touch = number
-button_9.on_touch = number
-button_add.on_touch = on_sign
-button_sub.on_touch = on_sign
-button_mul.on_touch = on_sign
-button_div.on_touch = on_sign
+build:
+  - Scene main:
+    - Vbox vbox:
+      call setChildAnchor(0.5, 0.5, 0.5, 0.5)
+      call setSizeAnchor(1, 1)
+      - Label result:
+        call setTextAlign(1, 0, 1, 0)
+        call resize(160, 32)
+      - GridBox buttons:
+        call setRow(4)
+        - Button button_7(text: "7", on_touch: number)
+        - Button button_8(text: "8", on_touch: number)
+        - Button button_9(text: "9", on_touch: number)
+        - Button button_4(text: "4", on_touch: number)
+        - Button button_5(text: "5", on_touch: number)
+        - Button button_6(text: "6", on_touch: number)
+        - Button button_1(text: "1", on_touch: number)
+        - Button button_2(text: "2", on_touch: number)
+        - Button button_3(text: "3", on_touch: number)
+        - Button button_0(text: "0")
+        - Button button_00(text: "00")
+        - Button button_add(text: "+", on_touch: on_sign)
+        # Signs
+        - Button button_sub(text: "-", on_touch: on_sign)
+        - Button button_mul(text: "x", on_touch: on_sign)
+        - Button button_div(text: "/", on_touch: on_sign)
+        - Button button_eq:
+          text: "="
 
 
 button_0@on_touch(self, x, y):
@@ -118,15 +81,12 @@ button_00@on_touch(self, x, y):
     second &= "00"
 
 
-button_eq.text = "="
 button_eq@on_touch(self, x, y):
   first = equal()
   if sign != "":
     second = ""
     sign = ""
 
-result.setTextAlign(1, 0, 1, 0)
-result.resize(160, 32)
 result@on_process(self):
   if sign == "":
     result.text = first
@@ -135,7 +95,5 @@ result@on_process(self):
   else:
     result.text = first & " " & sign & " " & second
 
-
-addScene(main)
-setMainScene("Main")
+addMainScene(main)
 windowLaunch()

+ 1 - 1
examples/hello_world/main.nim

@@ -10,7 +10,7 @@ build:
       call setSizeAnchor(1, 1)
       call setTextAlign(0.5, 0.5, 0.5, 0.5)
       call setText("Hello, world!")
-      background_color: Color(31, 45, 62)
+      call setBackgroundColor(Color(31, 45, 62))
 
 addMainScene(scene)
 windowLaunch()

+ 39 - 53
examples/novel/main.nim

@@ -4,27 +4,10 @@ import nodesnim
 Window("Novel game", 1280, 720)
 
 var
-  main = Scene("Main")
-
-  button = Button("New game")
-
-
-  # Game scene
-  game_scene = Scene("Game")
-
   # Backgrounds:
   night = load("assets/night.jpg")
-
   # Charapters:
   akiko_default = load("assets/test.png", GL_RGBA)
-
-  name_charapter = Label()
-  dialog_text = RichLabel()
-  background_image = TextureRect()
-  foreground_rect = ColorRect()
-
-  charapter = TextureRect("Charapter")
-
   dialog = @[
     ("Me", "H-Hey .. ?", false),
     ("Eileen", "NANI??????", true)
@@ -32,39 +15,46 @@ var
   stage = -1
 
 
-game_scene.addChild(background_image)
-background_image.setSizeAnchor(1, 1)
-background_image.setTexture(night)
-background_image.setTextureAnchor(0.5, 0.5, 0.5, 0.5)
-background_image.texture_mode = TEXTURE_KEEP_ASPECT_RATIO
-
-game_scene.addChild(charapter)
-charapter.setSizeAnchor(1, 1)
-charapter.setTexture(akiko_default)
-charapter.setTextureAnchor(0.5, 0.5, 0.5, 0.5)
-charapter.texture_mode = TEXTURE_KEEP_ASPECT_RATIO
-charapter.visible = false
+build:
+  - Scene main:
+    call rename("Main")
+    - Button button:
+      text: "New game"
+      call resize(128, 32)
+      call setAnchor(0.5, 0.5, 0.5, 0.5)
+  - Scene game_scene:
+    call rename("Game")
+    - TextureRect background_image:
+      call setSizeAnchor(1, 1)
+      call setTexture(night)
+      call setTextureAnchor(0.5, 0.5, 0.5, 0.5)
+      texture_mode: TEXTURE_KEEP_ASPECT_RATIO
+    - TextureRect charapter:
+      call setSizeAnchor(1, 1)
+      call setTexture(akiko_default)
+      call setTextureAnchor(0.5, 0.5, 0.5, 0.5)
+      texture_mode: TEXTURE_KEEP_ASPECT_RATIO
+      visible: false
+    - RichLabel dialog_text:
+      call setSizeAnchor(0.8, 0.3)
+      call setAnchor(0.1, 0.6, 0, 0)
+      call setBackgroundColor(Color(0x0e131760))
+      - Label name_charapter:
+        call resize(128, 32)
+        call setAnchor(0, 0, 0, 1)
+        call setStyle(style({background-color: "#0e131760", border-radius: 8}))
+        call setTextAlign(0.1, 0.5, 0.1, 0.5)
+    - ColorRect foreground_rect:
+      call setSizeAnchor(1, 1)
+      color: Color(0x0e1317ff)
+    - AnimationPlayer animation:
+      loop: false
+      call addState(foreground_rect.color.a.addr,
+                    @[(tick: 0, value: 1.0), (tick: 100, value: 0.0)])
 
-game_scene.addChild(dialog_text)
-dialog_text.setSizeAnchor(0.8, 0.3)
-dialog_text.setAnchor(0.1, 0.6, 0, 0)
-dialog_text.setBackgroundColor(Color(0x0e131760))
-
-dialog_text.addChild(name_charapter)
-name_charapter.resize(128, 32)
-name_charapter.setAnchor(0, 0, 0, 1)
-name_charapter.setBackgroundColor(Color(0x0e131760))
-name_charapter.setTextAlign(0.1, 0.5, 0.1, 0.5)
-
-game_scene.addChild(foreground_rect)
-foreground_rect.setSizeAnchor(1, 1)
 
 foreground_rect@on_ready(self):
-  foreground_rect.color = Color(0x0e1317ff)
-
-foreground_rect@on_process(self):
-  if foreground_rect.color.a > 0f:
-    foreground_rect.color.a -= 0.001
+  animation.play()
 
 foreground_rect@on_input(self, event):
   if event.isInputEventMouseButton() and not event.pressed:
@@ -76,16 +66,12 @@ foreground_rect@on_input(self, event):
 
 
 
-main.addChild(button)
-button.text = "New game"
-button.resize(128, 32)
-button.setAnchor(0.5, 0.5, 0.5, 0.5)
 button.on_touch =
   proc(self: ButtonRef, x, y: float) =
     changeScene("Game")
 
+echo main.name, ", ", game_scene.name
 
-addScene(main)
+addMainScene(main)
 addScene(game_scene)
-setMainScene("Main")
 windowLaunch()

+ 6 - 12
examples/screensaver/main.nim

@@ -5,19 +5,15 @@ randomize()
 
 Window("ScreenSaver", 720, 480)
 
-
 var
-  main = Scene("Main")
-
-  img = load("img.png", GL_RGBA)
-
-  sprite = Sprite()
-
   direction = Vector2()
   speed = 3f
 
-sprite.centered = false
-sprite.setTexture(img)
+build:
+  - Scene main:
+    - Sprite sprite:
+      centered: false
+      call setTexture(load("img.png", GL_RGBA))
 
 sprite@on_process(self):
   let rect = Rect2(sprite.global_position, sprite.rect_size)
@@ -37,7 +33,5 @@ sprite@on_process(self):
   sprite.move(direction*speed)
 
 
-main.addChild(sprite )
-addScene(main)
-setMainScene("Main")
+addMainScene(main)
 windowLaunch()

+ 12 - 16
examples/snake/main.nim

@@ -14,13 +14,18 @@ type
     body: seq[Vector2Ref]
 
 
-var
-  main = Scene("Main")
-  game_over = Scene("GameOverScene")
-  label_go = Label("GameOverLabel")
-
-  canvas = Canvas("Canvas")
+build:
+  - Scene main:
+    call rename("Main")
+    - Canvas canvas
+  - Scene game_over:
+    call rename("GameOverScene")
+    - Label label_go:
+      text: "Game Over"
+      call setTextAlign(0.5, 0.5, 0.5, 0.5)
+      call setSizeAnchor(1, 1)
 
+var
   snake = Snake(
     dir: Vector2(),
     body: @[Vector2(0, 0)],
@@ -29,13 +34,6 @@ var
   time = 0
 
 
-game_over.addChild(label_go)
-
-label_go.text = "Game Over"
-label_go.setTextAlign(0.5, 0.5, 0.5, 0.5)
-label_go.setSizeAnchor(1, 1)
-
-
 Input.addKeyAction("forward", "w")
 Input.addKeyAction("backward", "s")
 Input.addKeyAction("left", "a")
@@ -124,7 +122,5 @@ canvas@on_process(self):
 
 
 addScene(game_over)
-main.addChild(canvas)
-addScene(main)
-setMainScene("Main")
+addMainScene(main)
 windowLaunch()

+ 1 - 1
nodesnim.nimble

@@ -1,7 +1,7 @@
 [Package]
 name = "nodesnim"
 author = "Ethosa"
-version = "0.0.5"
+version = "0.1.0"
 description = "The Nim GUI/2D framework based on OpenGL and SDL2."
 license = "MIT"
 srcDir = "src"

+ 1 - 0
src/nodesnim.nim

@@ -19,6 +19,7 @@ import
   nodesnim/environment,
 
   nodesnim/core,
+  nodesnim/graphics,
   nodesnim/nodes,
   nodesnim/nodescontrol,
   nodesnim/nodes2d,

+ 3 - 2
src/nodesnim/core.nim

@@ -14,9 +14,10 @@ import
   core/animation,
   core/nodes_os,
   core/vector3,
-  core/scene_builder
+  core/scene_builder,
+  core/stylesheet
 
 export
   vector2, rect2, circle2, polygon2, enums, anchor, color,
   exceptions, input, image, color_text, audio_stream,
-  animation, nodes_os, vector3, scene_builder
+  animation, nodes_os, vector3, scene_builder, stylesheet

+ 44 - 23
src/nodesnim/core/color.nim

@@ -1,5 +1,7 @@
 # author: Ethosa
-import strutils
+import
+  strutils,
+  re
 
 
 type
@@ -25,33 +27,16 @@ proc Color*(r, g, b, a: uint8): ColorRef =
   ## `r`, `g`, `b` and `a` is a numbers ranges `0..255`.
   result = ColorRef(r: r.int / 255, g: g.int / 255, b: b.int / 255, a: a.int / 255)
 
+proc Color*(r, g, b: uint8, a: float): ColorRef =
+  ## Creates a new Color from RGBA.
+  ## `r`, `g`, `b` and `a` is a numbers ranges `0..255`.
+  result = ColorRef(r: r.int / 255, g: g.int / 255, b: b.int / 255, a: a)
+
 proc Color*(r, g, b: uint8): ColorRef {.inline.} =
   ## Creates a new Color from RGB.
   ## `r`, `g` and `b` is a numbers ranges `0..255`.
   Color(r, g, b, 255)
 
-proc Color*(src: string): ColorRef =
-  ## Parses color from string.
-  ## `src` should be a string, begins with "#", "0x" or "0X" and have a RRGGBBAA color value.
-  runnableExamples:
-    var
-      clr1 = Color("#FFCCAAFF")
-      clr2 = Color("0xFFAACCFF")
-      clr3 = Color("0XAACCFFFF")
-      clr4 = Color("#AAFFCCFF")
-    echo clr1
-    echo clr2
-    echo clr3
-    echo clr4
-
-  let color = parseHexInt(src).uint32
-  result = ColorRef(
-    r: ((color shr 24) and 255).int / 255,
-    g: ((color shr 16) and 255).int / 255,
-    b: ((color shr 8) and 255).int / 255,
-    a: (color and 255).int / 255
-  )
-
 proc Color*(src: uint32): ColorRef =
   ## Translate uint32 color to the Color object.
   runnableExamples:
@@ -72,6 +57,42 @@ proc Color*(src: uint32): ColorRef =
     a: (src and 255).int / 255
   )
 
+proc Color*(src: string): ColorRef =
+  ## Parses color from string.
+  ## `src` should be a string, begins with "#", "0x" or "0X" and have a RRGGBBAA color value.
+  runnableExamples:
+    var
+      clr1 = Color("#FFCCAAFF")
+      clr2 = Color("0xFFAACCFF")
+      clr3 = Color("0XAACCFFFF")
+      clr4 = Color("#AAFFCCFF")
+    echo clr1
+    echo clr2
+    echo clr3
+    echo clr4
+  var target = src
+  var matched: array[20, string]
+
+  # #FFFFFFFF, #FFF, #FFFFFF, etc
+  if target.startsWith('#') or target.startsWith("0x") or target.startsWith("0X"):
+    target = target[1..^1]
+    if target[0] == 'x' or target[0] == 'X':
+      target = target[1..^1]
+
+    if target.len() == 3:  # #fff -> #ffffffff
+      target = target[0] & target[0] & target[1] & target[1] & target[2] & target[2] & "ff"
+    elif target.len() == 6:  # #ffffff -> #ffffffff
+      target &= "ff"
+    return Color(parseHexInt(target).uint32)
+
+  # rgba(255, 255, 255, 1.0)
+  elif target.match(re"\A\s*rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+.?\d*?)\s*\)\s*\Z", matched):
+    return Color(parseInt(matched[0]).uint8, parseInt(matched[1]).uint8, parseInt(matched[2]).uint8, parseFloat(matched[3]))
+
+  # rgb(255, 255, 255)
+  elif target.match(re"\A\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*\Z", matched):
+    return Color(parseInt(matched[0]).uint8, parseInt(matched[1]).uint8, parseInt(matched[2]).uint8)
+
 proc Color*(): ColorRef {.inline.} =
   ## Creates a new Color object with RGBA value (0, 0, 0, 0)
   ColorRef(r: 0, g: 0, b: 0, a: 0)

+ 16 - 4
src/nodesnim/core/scene_builder.nim

@@ -4,19 +4,31 @@ import
 
 proc addNode(level: var seq[NimNode], code: NimNode): NimNode {.compileTime.} =
   result = newStmtList()
-  if code.kind == nnkStmtList:
+  if code.kind in [nnkStmtList, nnkObjConstr]:
     for line in code.children():
       if line.kind == nnkPrefix:
         if line[0].kind == nnkIdent and line[1].kind == nnkCommand:
           if $line[0] == "-":
-            result.add(newVarStmt(line[1][1], newCall($line[1][0])))
+            if line[1][1].kind == nnkIdent:
+              result.add(newVarStmt(line[1][1], newCall($line[1][0])))
+            elif line[1][1].kind == nnkObjConstr:
+              result.add(newVarStmt(line[1][1][0], newCall($line[1][0])))
             if level.len() > 0:
               # - Scene main_scene:
-              result.add(newCall("addChild", level[^1], line[1][1]))
+              if line[1][1].kind == nnkIdent:
+                result.add(newCall("addChild", level[^1], line[1][1]))
+              elif line[1][1].kind == nnkObjConstr:
+                result.add(newCall("addChild", level[^1], line[1][1][0]))
+                level.add(line[1][1][0])
+                var nodes = addNode(level, line[1][1])
+                for i in nodes.children():
+                  result.add(i)
+      # call methodName(arg1, arg2) -> currentNode.methodName(arg1, arg2)
       elif line.kind == nnkCommand and $line[0] == "call" and level.len() > 0:
         line[1].insert(1, level[^1])
         result.add(line[1])
-      elif line.kind == nnkCall and level.len() > 1:
+      # property: value -> currentNode.property = value
+      elif line.kind in [nnkCall, nnkExprColonExpr] and level.len() > 1:
         var attr = newNimNode(nnkAsgn)
         attr.add(newNimNode(nnkDotExpr))
         attr[0].add(level[^1])

+ 96 - 0
src/nodesnim/core/stylesheet.nim

@@ -0,0 +1,96 @@
+# author: Ethosa
+import
+  macros,
+  strutils
+
+
+type
+  StyleSheetObj* = object
+    dict*: seq[tuple[key, value: string]]
+  StyleSheetRef* = ref StyleSheetObj
+
+
+proc StyleSheet*(arg: seq[tuple[key, value: string]] = @[]): StyleSheetRef =
+  StyleSheetRef(dict: arg)
+
+proc StyleSheet*(arg: string): StyleSheetRef =
+  let tmp = arg.split(Whitespace)
+  StyleSheetRef(dict: @[(tmp[0], tmp[1])])
+
+proc StyleSheet*(arg: openarray[tuple[key, value: string]]): StyleSheetRef =
+  var res: seq[tuple[key, value: string]] = @[]
+  for i in arg:
+    res.add(i)
+  StyleSheetRef(dict: res)
+
+proc `$`*(a: StyleSheetRef): string =
+  for i in a.dict:
+    result &= i.key & ": " & i.value & ";\n"
+  if result != "":
+    result = result[0..^2]
+
+proc `[]`*(a: StyleSheetRef, key: string): string =
+  for i in a.dict:
+    if i.key == key:
+      return i.value
+
+proc `[]=`*(a: StyleSheetRef, key, value: string) =
+  for i in 0..a.dict.high:
+    if a.dict[i].key == key:
+      a.dict[i].value = value
+      return
+  a.dict.add((key, value))
+
+proc count*(a: StyleSheetRef): int =
+  ## Returns styles count.
+  a.dict.len()
+
+
+# --- Macros --- #
+proc toStr(a: NimNode): string {.compileTime.} =
+  result =
+    case a.kind
+    of nnkIdent, nnkStrLit:
+      $a
+    of nnkIntLit:
+      $(intVal(a))
+    of nnkFloatLit:
+      $(floatVal(a))
+    of nnkInfix:
+      $a[1] & "-" & $a[2]
+    else:
+      ""
+  if a.kind == nnkCall:
+    for i in 1..a.len()-1:
+      result &= a[i].toStr() & ", "
+    if result != "":
+      result = $a[0] & "(" & result[0..^3] & ")"
+    else:
+      result = $a[0] & "()"
+
+
+macro style*(obj: untyped): untyped =
+  ## Translates CSS-like code into StyleSheet.
+  ##
+  ## ### Example
+  ## .. code-block:: nim
+  ##
+  ##    echo style(
+  ##      {
+  ##        background-color: rgba(255, 255, 255, 1),
+  ##        color: rgb(0, 0, 0)
+  ##      }
+  ##    )
+  if obj.kind == nnkTableConstr:
+    var
+      cssp = StyleSheetRef(dict: @[])
+      arr = newNimNode(nnkBracket)
+
+    for child in obj.children():
+      if child.kind == nnkExprColonExpr:
+        cssp[child[0].toStr()] = child[1].toStr()
+
+    for i in cssp.dict:
+      arr.add(newPar(newLit(i.key), newLit(i.value)))
+
+    result = newCall("StyleSheet", newCall("@", arr))

+ 3 - 0
src/nodesnim/core/vector2.nim

@@ -15,6 +15,9 @@ proc Vector2*(x, y: float): Vector2Ref {.inline.} =
 proc Vector2*(b: Vector2Ref): Vector2Ref {.inline.} =
   Vector2Ref(x: b.x, y: b.y)
 
+proc Vector2*(num: float): Vector2Ref {.inline.} =
+  Vector2Ref(x: num, y: num)
+
 proc Vector2*(): Vector2Ref {.inline.} =
   Vector2Ref(x: 0, y: 0)
 

+ 5 - 0
src/nodesnim/graphics.nim

@@ -0,0 +1,5 @@
+import
+  graphics/drawable
+
+export
+  drawable

+ 298 - 0
src/nodesnim/graphics/drawable.nim

@@ -0,0 +1,298 @@
+# author: Ethosa
+import
+  ../thirdparty/opengl,
+
+  ../core/color,
+  ../core/stylesheet,
+  ../core/image,
+  ../core/vector2,
+
+  math,
+  strutils,
+  re
+
+
+
+type
+  DrawableObj* = object
+    shadow: bool
+    border_width: float
+    border_detail_lefttop: int
+    border_detail_righttop: int
+    border_detail_leftbottom: int
+    border_detail_rightbottom: int
+    border_radius_lefttop: float
+    border_radius_righttop: float
+    border_radius_leftbottom: float
+    border_radius_rightbottom: float
+    shadow_offset: Vector2Ref
+    border_color: ColorRef
+    background_color: ColorRef
+    texture*: GlTextureObj
+  DrawableRef* = ref DrawableObj
+
+
+proc Drawable*: DrawableRef =
+  DrawableRef(
+    texture: GlTextureObj(), border_width: 0,
+    border_detail_lefttop: 20,
+    border_detail_righttop: 20,
+    border_detail_leftbottom: 20,
+    border_detail_rightbottom: 20,
+    border_radius_lefttop: 0,
+    border_radius_righttop: 0,
+    border_radius_leftbottom: 0,
+    border_radius_rightbottom: 0,
+    border_color: Color(0, 0, 0, 0),
+    background_color: Color(0, 0, 0, 0),
+    shadow_offset: Vector2(0, 0), shadow: false
+  )
+
+let shadow_color: ColorRef = Color(0f, 0f, 0f, 0.5f)
+
+
+template vd = discard
+
+template recalc =
+  # left top
+  var t = self.border_radius_lefttop
+  vertex.add(Vector2(x, y - t))
+  for i in 0..self.border_detail_lefttop:
+    let angle = TAU*(i/self.border_detail_lefttop)
+    if angle >= PI and angle <= PI+PI/2:
+      vertex.add(Vector2(x + t + t*cos(angle), y - t - t*sin(angle)))
+  vertex.add(Vector2(x + t, y))
+
+  # right top
+  t = self.border_radius_righttop
+  vertex.add(Vector2(x + width - t, y))
+  for i in 0..self.border_detail_righttop:
+    let angle = TAU*(i/self.border_detail_righttop)
+    if angle >= PI+PI/2 and angle <= TAU:
+      vertex.add(Vector2(x + width - t + t*cos(angle), y - t - t*sin(angle)))
+  vertex.add(Vector2(x + width, y - t))
+
+  # right bottom
+  t = self.border_radius_rightbottom
+  vertex.add(Vector2(x + width, y - height + t))
+  for i in 0..self.border_detail_rightbottom:
+    let angle = TAU*(i/self.border_detail_rightbottom)
+    if angle >= 0 and angle <= PI/2:
+      vertex.add(Vector2(x + width - t + t*cos(angle), y - height + t - t*sin(angle)))
+  vertex.add(Vector2(x + width - t, y - height))
+
+  # left bottom
+  t = self.border_radius_leftbottom
+  vertex.add(Vector2(x + t, y - height))
+  for i in 0..self.border_detail_leftbottom:
+    let angle = TAU*(i/self.border_detail_leftbottom)
+    if angle >= PI/2 and angle <= PI:
+      vertex.add(Vector2(x + t + t*cos(angle), y - height + t - t*sin(angle)))
+  vertex.add(Vector2(x, y - height + t))
+
+
+template draw_template(drawtype, color, function, secondfunc: untyped): untyped =
+  glColor4f(`color`.r, `color`.g, `color`.b, `color`.a)
+  `function`
+  glBegin(`drawtype`)
+
+  for i in vertex:
+    glVertex2f(i.x, i.y)
+
+  glEnd()
+  `secondfunc`
+
+template draw_texture_template(drawtype, color, function, secondfunc: untyped): untyped =
+  glEnable(GL_TEXTURE_2D)
+  glBindTexture(GL_TEXTURE_2D, self.texture.texture)
+  glColor4f(`color`.r, `color`.g, `color`.b, `color`.a)
+  `function`
+  glBegin(`drawtype`)
+
+  for i in vertex:
+    glTexCoord2f((x + width - i.x) / width, 1f - ((-y + height - -i.y) / height))
+    glVertex2f(i.x, i.y)
+
+  glEnd()
+  `secondfunc`
+  glDisable(GL_TEXTURE_2D)
+
+
+proc enableShadow*(self: DrawableRef, val: bool) =
+  ## Enables shadow, when `val` is true.
+  self.shadow = val
+
+proc draw*(self: DrawableRef, x1, y1, width, height: float) =
+  var
+    vertex: seq[Vector2Ref] = @[]
+    x = x1 + self.shadow_offset.x
+    y = y1 - self.shadow_offset.y
+
+  if self.shadow:
+    recalc()
+    if self.texture.texture > 0'u32:
+      draw_texture_template(GL_POLYGON, shadow_color, vd(), vd())
+    else:
+      draw_template(GL_POLYGON, shadow_color, vd(), vd())
+
+  vertex = @[]
+  x = x1
+  y = y1
+  recalc()
+
+  if self.texture.texture > 0'u32:
+    draw_texture_template(GL_POLYGON, self.background_color, vd(), vd())
+  else:
+    draw_template(GL_POLYGON, self.background_color, vd(), vd())
+  if self.border_width > 0f:
+    draw_template(GL_LINE_LOOP, self.border_color, glLineWidth(self.border_width), glLineWidth(1))
+
+
+proc getColor*(self: DrawableRef): ColorRef =
+  ## Returns background color.
+  self.background_color
+
+proc loadTexture*(self: DrawableRef, path: string) =
+  ## Loads texture from the file.
+  ##
+  ## Arguments:
+  ## - `path` is an image path.
+  self.texture = load(path)
+  self.background_color = Color(1f, 1f, 1f, 1f)
+
+proc setBorderColor*(self: DrawableRef, color: ColorRef) =
+  ## Changes border color.
+  self.border_color = color
+
+proc setBorderWidth*(self: DrawableRef, width: float) =
+  ## Changes border width.
+  self.border_width = width
+
+proc setColor*(self: DrawableRef, color: ColorRef) =
+  ## Changes background color.
+  self.background_color = color
+
+proc setCornerRadius*(self: DrawableRef, radius: float) =
+  ## Changes corner radius.
+  ##
+  ## Arguments:
+  ## - `radius` is a new corner radius.
+  self.border_radius_lefttop = radius
+  self.border_radius_righttop = radius
+  self.border_radius_leftbottom = radius
+  self.border_radius_rightbottom = radius
+
+proc setCornerRadius*(self: DrawableRef, r1, r2, r3, r4: float) =
+  ## Changes corner radius.
+  ##
+  ## Arguments:
+  ## - `r1` is a new left-top radius.
+  ## - `r2` is a new right-top radius.
+  ## - `r3` is a new right-bottm radius.
+  ## - `r4` is a new left-bottm radius.
+  self.border_radius_lefttop = r1
+  self.border_radius_righttop = r2
+  self.border_radius_rightbottom = r3
+  self.border_radius_leftbottom = r4
+
+proc setCornerDetail*(self: DrawableRef, detail: int) =
+  ## Changes corner detail.
+  ##
+  ## Arguments:
+  ## - `detail` is a new corner detail.
+  self.border_detail_lefttop = detail
+  self.border_detail_righttop = detail
+  self.border_detail_leftbottom = detail
+  self.border_detail_rightbottom = detail
+
+proc setCornerDetail*(self: DrawableRef, d1, d2, d3, d4: int) =
+  ## Changes corner detail.
+  ##
+  ## Arguments:
+  ## - `d1` is a new left-top detail.
+  ## - `d2` is a new right-top detail.
+  ## - `d3` is a new right-bottm detail.
+  ## - `d4` is a new left-bottm detail.
+  self.border_detail_lefttop = d1
+  self.border_detail_righttop = d2
+  self.border_detail_leftbottom = d4
+  self.border_detail_rightbottom = d3
+
+proc setTexture*(self: DrawableRef, texture: GlTextureObj) =
+  ## Changes drawable texture.
+  self.texture = texture
+  self.background_color = Color(1f, 1f, 1f, 1f)
+
+proc setShadowOffset*(self: DrawableRef, offset: Vector2Ref) =
+  ## Changes shadow offset.
+  self.shadow_offset = offset
+
+proc setStyle*(self: DrawableRef, s: StyleSheetRef) =
+  ## Sets a new stylesheet.
+  for i in s.dict:
+    var matches: array[20, string]
+    case i.key
+    # background-color: rgb(51, 100, 255)
+    of "background-color":
+      var clr = Color(i.value)
+      if not clr.isNil():
+        self.setColor(clr)
+    # background-image: "assets/img.jpg"
+    of "background-image":
+      self.loadTexture(i.value)
+    # background: "path/to/img.jpg"
+    # background: rgb(125, 82, 196)
+    # background: "img.jpg" #f6f
+    of "background":
+      if i.value.match(re"\A\s*(rgba?\([^\)]+\)\s*|#[a-f0-9]{3,8})\s*\Z", matches):
+        let tmpclr = Color(matches[0])
+        if not tmpclr.isNil():
+          self.setColor(tmpclr)
+      elif i.value.match(re"\A\s*url\(([^\)]+)\)\s*\Z", matches):
+        self.loadTexture(matches[0])
+      elif i.value.match(re"\A\s*url\(([^\)]+)\)\s+(rgba?\([^\)]+\)\s*|#[a-f0-9]{3,8})\s*\Z", matches):
+        self.loadTexture(matches[0])
+        let tmpclr = Color(matches[1])
+        if not tmpclr.isNil():
+          self.setColor(tmpclr)
+    # border-color: rgba(55, 255, 177, 0.1)
+    of "border-color":
+      var clr = Color(i.value)
+      if not clr.isNil():
+        self.setBorderColor(clr)
+    # border-radius: 5
+    # border-radius: 2 4 8 16
+    of "border-radius":
+      let tmp = i.value.split(" ")
+      if tmp.len() == 1:
+        self.setCornerRadius(parseFloat(tmp[0]))
+      elif tmp.len() == 4:
+        self.setCornerRadius(parseFloat(tmp[0]), parseFloat(tmp[1]), parseFloat(tmp[2]), parseFloat(tmp[3]))
+    # border-detail: 5
+    # border-detail: 5 50 64 128
+    of "border-detail":
+      let tmp = i.value.split(" ")
+      if tmp.len() == 1:
+        self.setCornerDetail(parseInt(tmp[0]))
+      elif tmp.len() == 4:
+        self.setCornerDetail(parseInt(tmp[0]), parseInt(tmp[1]), parseInt(tmp[2]), parseInt(tmp[3]))
+    # border-width: 5
+    of "border-width":
+      self.setBorderWidth(parseFloat(i.value))
+    # border: 2 turquoise
+    of "border":
+      let tmp = i.value.rsplit(Whitespace, 1)
+      self.setCornerRadius(parseFloat(tmp[0]))
+      self.setBorderColor(Color(tmp[1]))
+    # shadow: true
+    of "shadow":
+      self.enableShadow(parseBool(i.value))
+    # shadow-offset: 3 3
+    of "shadow-offset":
+      let tmp = i.value.split(Whitespace, 1)
+      if tmp.len() == 1:
+        self.setShadowOffset(Vector2(parseFloat(tmp[0])))
+      elif tmp.len() == 2:
+        self.setShadowOffset(Vector2(parseFloat(tmp[0]), parseFloat(tmp[1])))
+    else:
+      discard

+ 1 - 0
src/nodesnim/nodes/animation_player.nim

@@ -45,6 +45,7 @@ method draw*(self: AnimationPlayerRef, w: GLfloat, h: GLfloat) =
         self.tick = 0
         if not self.loop:
             self.is_played = false
+            return
 
     var
       current_states: seq[tuple[tick: int, value: float]] = @[]

+ 3 - 0
src/nodesnim/nodes/node.nim

@@ -197,6 +197,9 @@ method hasParent*(self: NodeRef): bool {.base, inline.} =
 method hide*(self: NodeRef) {.base.} =
   self.visible = false
 
+method rename*(self: NodeRef, new_name: string) {.base.} =
+  self.name = new_name
+
 method removeChild*(self: NodeRef, index: int) {.base.} =
   ## Removes node child at a specific position.
   ##

+ 0 - 7
src/nodesnim/nodescontrol/box.nim

@@ -60,13 +60,6 @@ method addChild*(self: BoxRef, child: NodeRef) =
 
 method draw*(self: BoxRef, w, h: GLfloat) =
   ## this method uses in the `window.nim`.
-  let
-    x = -w/2 + self.global_position.x
-    y = h/2 - self.global_position.y
-
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
-
   for child in self.children:
     child.CanvasRef.position.x = self.rect_size.x*self.child_anchor.x1 - child.CanvasRef.rect_size.x*self.child_anchor.x2
     child.CanvasRef.position.y = self.rect_size.y*self.child_anchor.y1 - child.CanvasRef.rect_size.y*self.child_anchor.y2

+ 14 - 15
src/nodesnim/nodescontrol/button.nim

@@ -12,6 +12,7 @@ import
   ../core/color,
 
   ../nodes/node,
+  ../graphics/drawable,
   control,
   label
 
@@ -21,9 +22,9 @@ type
     button_mask*: cint  ## Mask for handle clicks
     action_mask*: cint  ## BUTTON_RELEASE or BUTTON_CLICK.
 
-    normal_background_color*: ColorRef  ## color, when button is not pressed and not hovered.
-    hover_background_color*: ColorRef   ## color, when button hovered.
-    press_background_color*: ColorRef   ## color, when button pressed.
+    normal_background*: DrawableRef  ## color, when button is not pressed and not hovered.
+    hover_background*: DrawableRef   ## color, when button hovered.
+    press_background*: DrawableRef   ## color, when button pressed.
 
     normal_color*: ColorRef  ## text color, whenwhen button is not pressed and not hovered.
     hover_color*: ColorRef   ## text color, when button hovered.
@@ -55,25 +56,25 @@ proc Button*(name: string = "Button"): ButtonRef =
   result.press_color = Color(1f, 1f, 1f)
   result.button_mask = BUTTON_LEFT
   result.action_mask = BUTTON_RELEASE
-  result.normal_background_color = Color(0x444444ff)
-  result.hover_background_color = Color(0x505050ff)
-  result.press_background_color = Color(0x595959ff)
+  result.normal_background = Drawable()
+  result.hover_background = Drawable()
+  result.press_background = Drawable()
+  result.normal_background.setColor(Color(0x444444ff))
+  result.hover_background.setColor(Color(0x505050ff))
+  result.press_background.setColor(Color(0x595959ff))
   result.on_touch = proc(self: ButtonRef, x, y: float) = discard
   result.kind = BUTTON_NODE
 
 
 method draw*(self: ButtonRef, w, h: GLfloat) =
   ## this method uses in the `window.nim`.
-  let
-    x = -w/2 + self.global_position.x
-    y = h/2 - self.global_position.y
-    color =
+  self.background =
       if self.pressed and self.focused:
-        self.press_background_color
+        self.press_background
       elif self.hovered and not mouse_pressed:
-        self.hover_background_color
+        self.hover_background
       else:
-        self.normal_background_color
+        self.normal_background
   self.color =
     if self.pressed and self.focused:
       self.press_color
@@ -81,8 +82,6 @@ method draw*(self: ButtonRef, w, h: GLfloat) =
       self.hover_color
     else:
       self.normal_color
-  glColor4f(color.r, color.g, color.b, color.a)
-  glRectf(x, y, x + self.rect_size.x, y - self.rect_size.y)
   procCall self.LabelRef.draw(w, h)
 
 method duplicate*(self: ButtonRef, obj: var ButtonObj): ButtonRef {.base.} =

+ 6 - 2
src/nodesnim/nodescontrol/color_rect.nim

@@ -11,6 +11,7 @@ import
   ../core/enums,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -41,8 +42,11 @@ method draw*(self: ColorRectRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  glColor4f(self.color.r, self.color.g, self.color.b, self.color.a)
-  glRectf(x, y, x + self.rect_size.x, y - self.rect_size.y)
+  if self.background.getColor().a == 0.0:
+    glColor4f(self.color.r, self.color.g, self.color.b, self.color.a)
+    glRectf(x, y, x + self.rect_size.x, y - self.rect_size.y)
+  else:
+    self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   # Press
   if self.pressed:

+ 36 - 6
src/nodesnim/nodescontrol/control.nim

@@ -9,9 +9,13 @@ import
   ../core/input,
   ../core/enums,
   ../core/color,
+  ../core/stylesheet,
+
+  ../graphics/drawable,
 
   ../nodes/node,
-  ../nodes/canvas
+  ../nodes/canvas,
+  strutils
 
 
 type
@@ -21,7 +25,7 @@ type
     focused*: bool
 
     mousemode*: MouseMode
-    background_color*: ColorRef
+    background*: DrawableRef
 
     on_mouse_enter*: proc(self: ControlRef, x, y: float): void  ## This called when the mouse enters the Control node.
     on_mouse_exit*: proc(self: ControlRef, x, y: float): void   ## This called when the mouse exit from the Control node.
@@ -39,7 +43,7 @@ template controlpattern*: untyped =
   result.pressed = false
 
   result.mousemode = MOUSEMODE_SEE
-  result.background_color = Color()
+  result.background = Drawable()
   result.rect_size = Vector2()
   result.position = Vector2()
   result.global_position = Vector2()
@@ -84,8 +88,7 @@ method draw*(self: ControlRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   # Press
   if self.pressed:
@@ -135,4 +138,31 @@ method handle*(self: ControlRef, event: InputEvent, mouse_on: var NodeRef) =
 
 method setBackgroundColor*(self: ControlRef, color: ColorRef) {.base.} =
   ## Changes Control background color.
-  self.background_color = color
+  self.background.setColor(color)
+
+method setStyle*(self: ControlRef, style: StyleSheetRef) {.base.} =
+  self.background.setStyle(style)
+  for i in style.dict:
+    case i.key
+    # size-anchor: 1.0
+    # size-anchor: 0.5 1
+    of "size-anchor":
+      let tmp = i.value.split(Whitespace)
+      if tmp.len() == 1:
+        self.setSizeAnchor(Vector2(parseFloat(tmp[0])))
+      elif tmp.len() == 2:
+        self.setSizeAnchor(Vector2(parseFloat(tmp[0]), parseFloat(tmp[1])))
+    # position-anchor: 1
+    # position-anchor: 0.5 1 0.5 1
+    of "position-anchor":
+      let tmp = i.value.split(Whitespace)
+      if tmp.len() == 1:
+        let tmp2 = parseFloat(tmp[0])
+        self.setAnchor(Anchor(tmp2, tmp2, tmp2, tmp2))
+      elif tmp.len() == 4:
+        self.setAnchor(Anchor(
+          parseFloat(tmp[0]), parseFloat(tmp[1]),
+          parseFloat(tmp[2]), parseFloat(tmp[3]))
+        )
+    else:
+      discard

+ 3 - 3
src/nodesnim/nodescontrol/counter.nim

@@ -12,6 +12,7 @@ import
 
   ../nodes/node,
   ../nodes/canvas,
+  ../graphics/drawable,
   control,
   label
 
@@ -42,7 +43,7 @@ proc Counter*(name: string = "Counter"): CounterRef =
   result.label = Label()
   result.label.mousemode = MOUSEMODE_IGNORE
   result.label.parent = result
-  result.background_color = Color(0x212121ff)
+  result.background.setColor(Color(0x212121ff))
   result.kind = COUNTER_NODE
 
 
@@ -62,8 +63,7 @@ method draw*(self: CounterRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   self.label.calcGlobalPosition()
   self.label.resize(self.rect_size.x - 40, self.rect_size.y)

+ 2 - 2
src/nodesnim/nodescontrol/edittext.nim

@@ -13,6 +13,7 @@ import
   ../core/color,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -187,8 +188,7 @@ method draw*(self: EditTextRef, w, h: GLfloat) =
       else:
         self.hint_color
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
   var
     th = 0f
     char_num = 0

+ 0 - 7
src/nodesnim/nodescontrol/grid_box.nim

@@ -79,13 +79,6 @@ method addChild*(self: GridBoxRef, child: NodeRef) =
 
 method draw*(self: GridBoxRef, w, h: GLfloat) =
   ## This method uses in the `window.nim`.
-  let
-    x1 = -w/2 + self.global_position.x
-    y1 = h/2 - self.global_position.y
-
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x1, y1, x1+self.rect_size.x, y1-self.rect_size.y)
-
   var
     row = 0
     fakesize = self.getChildSize()

+ 0 - 7
src/nodesnim/nodescontrol/hbox.nim

@@ -61,13 +61,6 @@ method addChild*(self: HBoxRef, child: NodeRef) =
 
 method draw*(self: HBoxRef, w, h: GLfloat) =
   ## This uses in the `window.nim`.
-  let
-    x1 = -w/2 + self.global_position.x
-    y = h/2 - self.global_position.y
-
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x1, y, x1+self.rect_size.x, y-self.rect_size.y)
-
   var
     fakesize = self.getChildSize()
     x = self.rect_size.x*self.child_anchor.x1 - fakesize.x*self.child_anchor.x2

+ 1 - 4
src/nodesnim/nodescontrol/label.nim

@@ -49,14 +49,11 @@ proc Label*(name: string = "Label"): LabelRef =
 
 method draw*(self: LabelRef, w, h: GLfloat) =
   ## This uses in the `window.nim`.
+  procCall self.ControlRef.draw(w, h)
   let
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
-
-
   glColor4f(self.color.r, self.color.g, self.color.b, self.color.a)
   var th = 0f
 

+ 3 - 4
src/nodesnim/nodescontrol/lineedit.nim

@@ -12,6 +12,7 @@ import
   ../core/color,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -51,7 +52,7 @@ proc LineEdit*(name: string = "LineEdit"): LineEditRef =
   result.spacing = 2
   result.text_align = Anchor(0.5, 0.5, 0.5, 0.5)
   result.color = Color(1f, 1f, 1f)
-  result.background_color = Color(0x454545ff)
+  result.background.setColor(Color(0x454545ff))
   result.hint_color = Color(0.8, 0.8, 0.8)
   result.hint_text = "Edit text ..."
   result.caret_position = 0
@@ -113,9 +114,7 @@ method draw*(self: LineEditRef, w, h: GLfloat) =
         self.hint_color
     tw = self.font.glutBitmapLength(text).float
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
-
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   var
     char_num = 0

+ 2 - 14
src/nodesnim/nodescontrol/popup.nim

@@ -11,6 +11,7 @@ import
   ../core/color,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -28,7 +29,7 @@ proc Popup*(name: string = "Popup"): PopupRef =
     var p = Popup("Popup")
   nodepattern(PopupRef)
   controlpattern()
-  result.background_color = Color(0x212121ff)
+  result.background.setColor(Color(0x212121ff))
   result.rect_size.x = 160
   result.rect_size.y = 160
   result.visible = false
@@ -61,19 +62,6 @@ method calcPositionAnchor*(self: PopupRef) =
   procCall self.ControlRef.calcPositionAnchor()
   recalc()
 
-method draw*(self: PopupRef, w, h: GLfloat) =
-  ## This uses in the `window.nim`.
-  let
-    x = -w/2 + self.global_position.x
-    y = h/2 - self.global_position.y
-
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x + self.rect_size.x, y - self.rect_size.y)
-
-  # Press
-  if self.pressed:
-    self.on_press(self, last_event.x, last_event.y)
-
 method duplicate*(self: PopupRef): PopupRef {.base.} =
   ## Duplicates Popup object and create a new Popup.
   self.deepCopy()

+ 2 - 2
src/nodesnim/nodescontrol/progress_bar.nim

@@ -12,6 +12,7 @@ import
 
   ../nodes/node,
   ../nodes/canvas,
+  ../graphics/drawable,
   control,
   math
 
@@ -42,7 +43,6 @@ proc ProgressBar*(name: string = "ProgressBar"): ProgressBarRef =
   controlpattern()
   result.value = 0
   result.max_value = 100
-  result.background_color = Color(1f, 1f, 1f)
   result.progress_color = Color(0.6, 0.6, 0.6)
   result.rect_size.x = 120
   result.rect_size.y = 20
@@ -101,7 +101,7 @@ method draw*(self: ProgressBarRef, w, h: GLfloat) =
       orad = min(self.rect_size.x, self.rect_size.y) / 2
       irad = (min(self.rect_size.x, self.rect_size.y) / 2) - 5f
     # background:
-    glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
+    glColor4f(self.background.getColor().r, self.background.getColor().g, self.background.getColor().b, self.background.getColor().a)
     glBegin(GL_TRIANGLE_STRIP)
     for i in 0..90:
       let angle = TAU * (i/90)

+ 2 - 2
src/nodesnim/nodescontrol/rich_edit_text.nim

@@ -13,6 +13,7 @@ import
   ../core/color_text,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -177,8 +178,7 @@ method draw*(self: RichEditTextRef, w, h: GLfloat) =
       else:
         self.hint_text
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
   var
     th = 0f
     char_num = 0

+ 2 - 3
src/nodesnim/nodescontrol/rich_label.nim

@@ -13,6 +13,7 @@ import
   ../core/color_text,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -51,9 +52,7 @@ method draw*(self: RichLabelRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
-
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   var th = 0f
 

+ 2 - 2
src/nodesnim/nodescontrol/scroll.nim

@@ -12,6 +12,7 @@ import
 
   ../nodes/node,
   ../nodes/canvas,
+  ../graphics/drawable,
   control
 
 
@@ -82,8 +83,7 @@ method draw*(self: ScrollRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.viewport_w, y-self.viewport_h)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   # Press
   if self.pressed:

+ 3 - 3
src/nodesnim/nodescontrol/slider.nim

@@ -11,6 +11,7 @@ import
   ../core/enums,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -33,7 +34,7 @@ proc Slider*(name: string = "Slider"): SliderRef =
     var sc = Slider("Slider")
   nodepattern(SliderRef)
   controlpattern()
-  result.background_color = Color(1f, 1f, 1f)
+  result.background.setColor(Color(1f, 1f, 1f))
   result.rect_size.x = 120
   result.rect_size.y = 40
   result.progress_color = Color(0.5, 0.5, 0.5)
@@ -51,8 +52,7 @@ method draw*(self: SliderRef, w, h: GLfloat) =
     y = h/2 - self.global_position.y
 
   # Background
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x + self.rect_size.x, y - self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   # Progress
   let progress = self.rect_size.x * (self.value.float / self.max_value.float)

+ 11 - 32
src/nodesnim/nodescontrol/subwindow.nim

@@ -14,6 +14,7 @@ import
 
   ../nodes/node,
   ../nodes/canvas,
+  ../graphics/drawable,
   control,
   popup,
   label
@@ -24,8 +25,7 @@ type
     left_taked*, right_taked*, top_taked*, bottom_taked*: bool
     title_taked*: bool
     icon*: GlTextureObj
-    title_bar_color*: ColorRef
-    border_color*: ColorRef
+    title_bar*: DrawableRef
     title_taked_pos*: Vector2Ref
     title*: LabelRef
   SubWindowRef* = ref SubWindowObj
@@ -40,9 +40,11 @@ proc SubWindow*(name: string = "SubWindow"): SubWindowRef =
     var window = SubWindow("SubWindow")
   nodepattern(SubWindowRef)
   controlpattern()
-  result.background_color = Color(0x454545ff)
-  result.title_bar_color = Color(0x303030ff)
-  result.border_color = Color(0x212121ff)
+  result.title_bar = Drawable()
+  result.background.setColor(Color(0x454545ff))
+  result.title_bar.setColor(Color(0x303030ff))
+  result.background.setBorderColor(Color(0x212121ff))
+  result.background.setBorderWidth(1)
   result.rect_size.x = 320
   result.rect_size.y = 220
   result.visible = false
@@ -75,8 +77,7 @@ method draw*(self: SubWindowRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x + self.rect_size.x, y - self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   for child in self.getChildIter():
     child.CanvasRef.calcGlobalPosition()
@@ -91,29 +92,7 @@ method draw*(self: SubWindowRef, w, h: GLfloat) =
     else:
       child.visible = true
 
-  glColor4f(self.border_color.r, self.border_color.g, self.border_color.b, self.border_color.a)
-  glBegin(GL_LINE_LOOP)
-  glVertex2f(x, y)
-  glVertex2f(x+self.rect_size.x, y)
-  glVertex2f(x+self.rect_size.x, y-self.rect_size.y)
-  glVertex2f(x, y-self.rect_size.y)
-  glEnd()
-
-  glBegin(GL_QUADS)
-
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glVertex2f(x+1, y-32)
-  glVertex2f(x + self.rect_size.x - 1, y-32)
-  glVertex2f(x + self.rect_size.x - 1, y - self.rect_size.y + 32)
-  glVertex2f(x+1, y - self.rect_size.y + 32)
-
-  glColor4f(self.title_bar_color.r, self.title_bar_color.g, self.title_bar_color.b, self.title_bar_color.a)
-  glVertex2f(x+1, y-1)
-  glVertex2f(x + self.rect_size.x - 1, y-1)
-  glVertex2f(x + self.rect_size.x - 1, y-31)
-  glVertex2f(x+1, y-31)
-
-  glEnd()
+  self.title_bar.draw(x, y, self.rect_size.x, 32)
 
   let size = self.title.getTextSize()
   self.title.position.x = self.rect_size.x / 2 - size.x / 2
@@ -256,7 +235,7 @@ method setBorderColor*(self: SubWindowRef, color: ColorRef) {.base.} =
   ##
   ## Arguments:
   ## - `color` is a new border color.
-  self.border_color = color
+  self.background.setBorderColor(color)
 
 
 method setIcon*(self: SubWindowRef, gltexture: GlTextureObj) {.base.} =
@@ -280,7 +259,7 @@ method setTitleBarColor*(self: SubWindowRef, color: ColorRef) {.base.} =
   ##
   ## Arguments:
   ## - `color` is a new title bar color.
-  self.title_bar_color = color
+  self.title_bar.setColor(color)
 
 
 method setTitle*(self: SubWindowRef, title: string) {.base.} =

+ 2 - 2
src/nodesnim/nodescontrol/texture_progress_bar.nim

@@ -11,6 +11,7 @@ import
   ../core/enums,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -46,8 +47,7 @@ method draw*(self: TextureProgressBarRef, w, h: GLfloat) =
     y = h/2 - self.global_position.y
 
   # Background
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x + self.rect_size.x, y - self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   # Progress
   let

+ 2 - 3
src/nodesnim/nodescontrol/texture_rect.nim

@@ -12,6 +12,7 @@ import
   ../core/color,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -51,11 +52,9 @@ method draw*(self: TextureRectRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x+self.rect_size.x, y-self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
   glColor4f(self.texture_filter.r, self.texture_filter.g, self.texture_filter.b, self.texture_filter.a)
 
-
   if self.texture > 0'u32:
     glEnable(GL_TEXTURE_2D)
     glBindTexture(GL_TEXTURE_2D, self.texture)

+ 0 - 7
src/nodesnim/nodescontrol/vbox.nim

@@ -61,13 +61,6 @@ method addChild*(self: VBoxRef, child: NodeRef) =
 
 method draw*(self: VBoxRef, w, h: GLfloat) =
   ## This uses in the `window.nim`.
-  let
-    x = -w/2 + self.global_position.x
-    y1 = h/2 - self.global_position.y
-
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y1, x+self.rect_size.x, y1-self.rect_size.y)
-
   var
     fakesize = self.getChildSize()
     y = self.rect_size.y*self.child_anchor.y1 - fakesize.y*self.child_anchor.y2

+ 3 - 3
src/nodesnim/nodescontrol/vslider.nim

@@ -11,6 +11,7 @@ import
   ../core/enums,
 
   ../nodes/node,
+  ../graphics/drawable,
   control
 
 
@@ -33,7 +34,7 @@ proc VSlider*(name: string = "VSlider"): VSliderRef =
     var slider = VSlider("VSlider")
   nodepattern(VSliderRef)
   controlpattern()
-  result.background_color = Color(1f, 1f, 1f)
+  result.background.setColor(Color(1f, 1f, 1f))
   result.rect_size.x = 40
   result.rect_size.y = 120
   result.progress_color = Color(0.5, 0.5, 0.5)
@@ -51,8 +52,7 @@ method draw*(self: VSliderRef, w, h: GLfloat) =
     y = h/2 - self.global_position.y
 
   # Background
-  glColor4f(self.background_color.r, self.background_color.g, self.background_color.b, self.background_color.a)
-  glRectf(x, y, x + self.rect_size.x, y - self.rect_size.y)
+  self.background.draw(x, y, self.rect_size.x, self.rect_size.y)
 
   # Progress
   let progress = self.rect_size.y * (self.value.float / self.max_value.float)

+ 1 - 1
src/nodesnim/window.nim

@@ -21,7 +21,7 @@ var
   cmdCount {.importc: "cmdCount".}: cint
 
 
-when not defined(ios) and not defined(android):
+when not defined(ios) and not defined(android) and not defined(useGlew):
   when defined(debug):
     debug("Try to load OpenGL ...")
   loadExtensions()  # Load OpenGL extensions.

+ 2 - 0
tests/README.md

@@ -43,3 +43,5 @@
 41. [Use LineEdit node.](https://github.com/Ethosa/nodesnim/blob/master/tests/test41.nim)
 42. [Use Scene builder.](https://github.com/Ethosa/nodesnim/blob/master/tests/test42.nim)
 43. [Use AnimationPlayer node.](https://github.com/Ethosa/nodesnim/blob/master/tests/test43.nim)
+44. [Use StyleSheet object.](https://github.com/Ethosa/nodesnim/blob/master/tests/test44.nim)
+45. [Use Drawable and Control.](https://github.com/Ethosa/nodesnim/blob/master/tests/test45.nim)

+ 1 - 1
tests/test42.nim

@@ -7,7 +7,7 @@ build:
   - Scene scene:
     name: "Main scene"
     - Vbox background:  # Instead of var background = Vbox()
-      background_color: Color(21, 33, 48)  # You can change params without `objname`.param = value syntax.
+      call setBackgroundColor(Color(21, 33, 48))  # You can change params without `objname`.param = value syntax.
       call setSizeAnchor(1.0, 0.1)  # You can also call any method without `objname`.method(args) syntax. :eyes:
       call setAnchor(0.5, 0.5, 0.5, 0.5)
 

+ 22 - 0
tests/test44.nim

@@ -0,0 +1,22 @@
+# --- Test 44. Use StyleSheet object. --- #
+import nodesnim
+
+
+var mystyle = style(
+  {
+    background-color: rgba(255, 125, 255, 0.7),
+    color: rgb(34, 34, 34),
+    font-size: 1,
+    text-align: center
+  })
+echo mystyle
+
+var background = Color(mystyle["background-color"])
+
+assert background == Color(255, 125, 255, 0.7)
+
+echo StyleSheet(
+  {
+    "background-color": "#f2f2f7"
+  }
+)

+ 28 - 0
tests/test45.nim

@@ -0,0 +1,28 @@
+# --- Test 45. Use Drawable and Control. --- #
+import nodesnim
+
+
+Window("drawable oops")
+
+build:
+  - Scene scene:
+    - Control ctrl
+
+
+scene.addChild(ctrl)
+ctrl.resize(256, 96)
+ctrl.move(64, 64)
+ctrl.setStyle(style(
+  {
+    background-color: rgb(33, 65, 87),
+    border-radius: 8,
+    border-width: 1,
+    border-color: rgb(0, 0, 0),
+    shadow: true,
+    shadow-offset: 3,
+    size-anchor: "0.5 0.7"
+  }
+))
+
+addMainScene(scene)
+windowLaunch()