Ver código fonte

add Stylesheet and Drawable :eyes:

Ethosa 3 anos atrás
pai
commit
29cd1756e8

+ 9 - 2
README.md

@@ -11,8 +11,15 @@
 </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`

+ 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)

+ 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

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

@@ -0,0 +1,297 @@
+# 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)
+  )
+
+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

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

@@ -10,6 +10,8 @@ import
   ../core/enums,
   ../core/color,
 
+  ../graphics/drawable,
+
   ../nodes/node,
   ../nodes/canvas
 
@@ -22,6 +24,7 @@ type
 
     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.
@@ -40,6 +43,7 @@ template controlpattern*: untyped =
 
   result.mousemode = MOUSEMODE_SEE
   result.background_color = Color()
+  result.background = Drawable()
   result.rect_size = Vector2()
   result.position = Vector2()
   result.global_position = Vector2()
@@ -87,6 +91,8 @@ method draw*(self: ControlRef, w, h: GLfloat) =
   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:
     self.on_press(self, last_event.x, last_event.y)

+ 1 - 0
tests/README.md

@@ -43,3 +43,4 @@
 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)

+ 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"
+  }
+)