Browse Source

fix, memory optimization :eyes:, add `Chart` :heart:

Ethosa 3 years ago
parent
commit
aad957b3f3

+ 3 - 2
src/nodesnim/core.nim

@@ -18,10 +18,11 @@ import
   core/tools,
   core/font,
   core/tileset,
-  core/themes
+  core/themes,
+  core/chartdata
 
 export
   vector2, rect2, circle, polygon2, enums, anchor, color,
   exceptions, input, image, audio_stream,
   animation, nodes_os, vector3, scene_builder, stylesheet,
-  tools, font, tileset, themes
+  tools, font, tileset, themes, chartdata

+ 107 - 0
src/nodesnim/core/chartdata.nim

@@ -0,0 +1,107 @@
+# author: Ethosa
+import
+  algorithm,
+  sequtils,
+  color,
+  enums
+
+type
+  ChartDataValue* = object
+    case kind*: ChartDataValueType
+    of INTEGER_VALUE:
+      ival*: int
+    of FLOAT_VALUE:
+      fval*: float
+    of STRING_VALUE:
+      sval*: string
+    of CHAR_VALUE:
+      cval*: char
+
+  ChartData* = ref object
+    data_color*: ColorRef
+    data_name*: string
+    x_axis*: seq[ChartDataValue]
+    y_axis*: seq[ChartDataValue]
+
+
+proc newChartData*(data_name: string = "data", data_color: ColorRef = Color()): ChartData =
+  ## Creates a new empty ChartData.
+  runnableExamples:
+    var chart_data = newChartData()
+  ChartData(x_axis: @[], y_axis: @[], data_name: data_name, data_color: data_color)
+
+proc newChartData*(x_length, y_length: int, data_name: string = "data",
+                   data_color: ColorRef = Color()): ChartData =
+  ## Creates a new ChartData with specified length.
+  ##
+  ## Arguments:
+  ## - x_length -- length of `x_axis`;
+  ## - y_length -- length of `y_axis`.
+  runnableExamples:
+    var chart_data1 = newChartData(5, 5)
+  result = newChartData(data_name, data_color)
+  result.x_axis.setLen(x_length)
+  result.y_axis.setLen(y_length)
+
+proc newChartData*(x_data, y_data: seq[ChartDataValue],
+                   data_name: string = "data", data_color: ColorRef = Color()): ChartData =
+  ## Creates a new ChartData from specified data.
+  ##
+  ## Arguments:
+  ## - x_data -- specified data for `x_axis`;
+  ## - y_data -- specified data for `y_axis`.
+  runnableExamples:
+    var chart_data2 = newChartData(@[1, 5, 2], @["10.10.2021", "10.11.2021", "10.01.2021"])
+  ChartData(x_axis: x_data, y_axis: y_data, data_name: data_name, data_color: data_color)
+
+
+proc cmp(x, y: ChartDataValue): int =
+  if x.kind == y.kind:
+    case x.kind
+    of INTEGER_VALUE:
+      return cmp(x.ival, y.ival)
+    of FLOAT_VALUE:
+      return cmp(x.fval, y.fval)
+    of STRING_VALUE:
+      return cmp(x.sval, y.sval)
+    of CHAR_VALUE:
+      return cmp(x.cval, y.cval)
+proc findMax*(data: seq[ChartDataValue]): ChartDataValue =
+  ## Returns max value from sequence.
+  (data.sorted do (x, y: ChartDataValue) -> int: cmp(x, y))[^1]
+
+proc findMax*(chart_data: ChartData): tuple[x, y: ChartDataValue] =
+  ## Returns max values from ChartData.
+  (x: findMax(chart_data.x_axis), y: findMax(chart_data.y_axis))
+
+proc len*(data: ChartData): int =
+  zip(data.x_axis, data.y_axis).len
+
+proc getNum*(val: ChartDataValue): float =
+  case val.kind
+  of INTEGER_VALUE:
+    val.ival.float
+  of FLOAT_VALUE:
+    val.fval
+  of CHAR_VALUE:
+    ord(val.cval).float
+  of STRING_VALUE:
+    0f
+
+
+converter toChartDataValue*(val: seq[int]): seq[ChartDataValue] =
+  result = @[]
+  for i in val:
+    result.add(ChartDataValue(kind: INTEGER_VALUE, ival: i))
+converter toChartDataValue*(val: seq[float]): seq[ChartDataValue] =
+  result = @[]
+  for i in val:
+    result.add(ChartDataValue(kind: FLOAT_VALUE, fval: i))
+converter toChartDataValue*(val: seq[string]): seq[ChartDataValue] =
+  result = @[]
+  for i in val:
+    result.add(ChartDataValue(kind: STRING_VALUE, sval: i))
+converter toChartDataValue*(val: seq[char]): seq[ChartDataValue] =
+  result = @[]
+  for i in val:
+    result.add(ChartDataValue(kind: CHAR_VALUE, cval: i))

+ 9 - 0
src/nodesnim/core/enums.nim

@@ -99,4 +99,13 @@ type
     SCREEN_MODE_NONE,  ## default mode.
     SCREEN_MODE_EXPANDED  ## Keep screen size.
 
+  ChartType* = enum
+    LINE_CHART
+
+  ChartDataValueType* = enum
+    INTEGER_VALUE,
+    FLOAT_VALUE,
+    STRING_VALUE,
+    CHAR_VALUE
+
 {.pop.}

+ 1 - 2
src/nodesnim/core/font.nim

@@ -304,7 +304,7 @@ proc renderSurface*(text: StyleText, align: AnchorObj): SurfacePtr =
             color(uint8(c.color.r * 255), uint8(c.color.g * 255), uint8(c.color.b * 255), uint8(c.color.a * 255)))
           r = rect(x, y, w, h)
         rendered.blitSurface(nil, surface, addr r)
-        rendered = nil
+        rendered.freeSurface()
         x += w
       y += h + text.spacing.cint
     return surface
@@ -331,7 +331,6 @@ proc render*(text: StyleText, size: Vector2Obj, align: AnchorObj) =
 
     # free memory
     surface.freeSurface()
-    surface = nil
   text.rendered = true
 
 proc renderTo*(text: StyleText, pos, size: Vector2Obj, align: AnchorObj) =

+ 1 - 1
src/nodesnim/core/themes.nim

@@ -31,7 +31,7 @@ var
              "background_deep": Color("#cbb693"),
              "accent": Color("#fbaefa"),
              "accent_dark": Color("#da9de9"),
-             "foreground": Color("#59616b"),
+             "foreground": Color("#39414b"),
              "url_color": Color("#2a9afc")}.toTable())
   ]
   current_theme* = themes[0].deepCopy()

+ 3 - 2
src/nodesnim/nodescontrol.nim

@@ -19,10 +19,11 @@ import
   nodescontrol/switch,
   nodescontrol/subwindow,
   nodescontrol/checkbox,
-  nodescontrol/tooltip
+  nodescontrol/tooltip,
+  nodescontrol/chart
 
 export
   control, color_rect, texture_rect, label, button,
   box, hbox, vbox, grid_box, edittext, scroll, progress_bar,
   slider, popup, texture_button, texture_progress_bar,
-  counter, switch, subwindow, checkbox, tooltip
+  counter, switch, subwindow, checkbox, tooltip, chart

+ 64 - 0
src/nodesnim/nodescontrol/chart.nim

@@ -0,0 +1,64 @@
+# author: Ethosa
+## Provides charting functionality
+import
+  ../thirdparty/opengl,
+  ../thirdparty/sdl2,
+
+  ../core/font,
+  ../core/enums,
+  ../core/color,
+  ../core/exceptions,
+  ../core/vector2,
+  ../core/chartdata,
+  ../core/themes,
+
+  ../nodes/node,
+
+  control,
+  sequtils
+
+
+type
+  ChartObj* = object of ControlObj
+    line_color*: ColorRef
+    chart_type*: ChartType
+    data*: ChartData
+  ChartRef* = ref ChartObj
+
+
+proc Chart*(name: string = "Chart", chart_type: ChartType = LINE_CHART): ChartRef =
+  ## Creates a new Chart object.
+  nodepattern(ChartRef)
+  controlpattern()
+  result.chart_type = chart_type
+  result.data = newChartData("some data", current_theme~accent)
+  result.line_color = current_theme~foreground
+
+
+method draw*(self: ChartRef, w, h: GLfloat) =
+  {.warning[LockLevel]: off.}
+  procCall self.ControlRef.draw(w, h)
+  let
+    x = -w/2 + self.global_position.x
+    y = h/2 - self.global_position.y
+    start_x = x + self.rect_size.x/10
+    end_y = y - self.rect_size.y + self.rect_size.y/10
+    data = zip(self.data.x_axis, self.data.y_axis)
+    width = (self.rect_size.x - self.rect_size.x/5) / data.len.float
+    max_val = self.data.findMax().y.getNum()
+
+  glColor(self.line_color)
+  glLineWidth(2)
+  glBegin(GL_LINE_STRIP)
+  glVertex2f(start_x, y)
+  glVertex2f(start_x, end_y)
+  glVertex2f(x + self.rect_size.x - self.rect_size.x/10, end_y)
+  glEnd()
+
+  glColor(self.data.data_color)
+  for i in data.low..data.high:
+    let
+      j = i.float
+      h = (self.rect_size.y - self.rect_size.y/5) * (data[i][1].getNum() / max_val)
+    glRectf(start_x + width*j, end_y + h, start_x + width*(j+1), end_y)
+

+ 31 - 13
src/nodesnim/nodescontrol/checkbox.nim

@@ -7,13 +7,15 @@ import
   ../core/input,
   ../core/vector2,
   ../core/font,
+  ../core/themes,
+  ../core/anchor,
+  ../core/nodes_os,
 
   ../graphics/drawable,
 
   ../nodes/node,
   ../nodes/canvas,
 
-  label,
   control
 
 
@@ -21,10 +23,10 @@ type
   ToggleHandler* = proc(self: CheckBoxRef, toggled: bool)
   CheckBoxRef* = ref object of ControlRef
     enabled*: bool
-
+    check_color*: ColorRef
     box: DrawableRef
-    text: LabelRef
-
+    text*: StyleText
+    text_align*: AnchorObj
     on_toggle*: ToggleHandler  ## This called when switch toggled.
 
 let toggle_handler*: ToggleHandler = proc(self: CheckBoxRef, toggled: bool) = discard
@@ -41,13 +43,15 @@ proc CheckBox*(name: string = "CheckBox"): CheckBoxRef =
   controlpattern()
   result.enabled = false
   result.box = Drawable()
-  result.text = Label()
+  result.text = stext""
+  result.text_align = Anchor()
   result.kind = CHECKBOX_NODE
 
   result.box.setCornerRadius(8)
   result.box.setCornerDetail(8)
-  result.box.setColor(Color("#444444"))
-  result.box.setBorderColor(Color("#555555"))
+  result.box.setColor(current_theme~background_deep)
+  result.box.setBorderColor(current_theme~foreground)
+  result.check_color = current_theme~foreground
   result.box.setBorderWidth(1)
   result.on_toggle = toggle_handler
 
@@ -63,16 +67,16 @@ method draw*(self: CheckBoxRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  if not self.text.text.rendered:
-    self.text.text.render(self.text.rect_size, self.text.text_align)
-  self.text.text.renderTo(Vector2(x+36, y-4), self.text.rect_size, self.text.text_align)
-  self.rect_min_size = self.text.text.getTextSize()
+  self.text.render(self.rect_size, self.text_align)
+  self.text.renderTo(Vector2(x+36, y-4), self.rect_size, self.text_align)
+  self.rect_min_size = self.text.getTextSize()
   self.rect_min_size.x += 36
   self.resize(self.rect_size.x, self.rect_size.y)
 
   self.box.draw(x+4, y-4, 24, 24)
   if self.enabled:
-    glColor4f(1f, 1f, 1f, 1f)
+    glColor(self.check_color)
+    glLineWidth(2)
     glBegin(GL_LINES)
     glVertex2f(x+10, y-10)
     glVertex2f(x+22, y-22)
@@ -80,6 +84,7 @@ method draw*(self: CheckBoxRef, w, h: GLfloat) =
     glVertex2f(x+22, y-10)
     glVertex2f(x+10, y-22)
     glEnd()
+    glLineWidth(1)
 
 method duplicate*(self: CheckBoxRef): CheckBoxRef {.base.} =
   ## Duplicates ChechBox object and create a new ChechBox.
@@ -90,7 +95,20 @@ method enable*(self: CheckBoxRef) {.base.} =
   self.on_toggle(self, self.enabled)
 
 method setText*(self: CheckBoxRef, value: string, save_properties: bool = false) {.base.} =
-  self.text.setText(value, save_properties)
+  var st = stext(value)
+  if self.text.font.isNil():
+    self.text.font = standard_font
+  st.font = self.text.font
+
+  if save_properties:
+    for i in 0..<st.chars.len():
+      if i < self.text.len():
+        st.chars[i].color = self.text.chars[i].color
+        st.chars[i].style = self.text.chars[i].style
+  self.text = st
+  self.rect_min_size = self.text.getTextSize()
+  self.resize(self.rect_size.x, self.rect_size.y, true)
+  self.text.rendered = false
 
 method toggle*(self: CheckBoxRef) {.base.} =
   self.enabled = not self.enabled

+ 1 - 0
src/nodesnim/nodescontrol/subwindow.nim

@@ -101,6 +101,7 @@ method draw*(self: SubWindowRef, w, h: GLfloat) =
   self.title.position.x = self.rect_size.x / 2 - size.x / 2
   self.title.position.y = 1 + 15 - size.y / 2
   self.title.calcGlobalPosition()
+  self.title.text.rendered = false
   self.title.draw(w, h)
 
   if self.icon.texture > 0'u32:

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

@@ -39,8 +39,8 @@ proc Switch*(name: string = "Switch"): SwitchRef =
   controlpattern()
   result.color_disable = current_theme~accent_dark
   result.color_enable = current_theme~accent
-  result.back_disable = current_theme~background
-  result.back_enable = current_theme~background_deep
+  result.back_disable = current_theme~background_deep
+  result.back_enable = current_theme~accent_dark
   result.value = false
   result.rect_size.x = 50
   result.rect_size.y = 20

+ 7 - 11
src/nodesnim/nodescontrol/tooltip.nim

@@ -7,6 +7,7 @@ import
   ../core/enums,
   ../core/color,
   ../core/anchor,
+  ../core/themes,
 
   ../nodes/node,
   ../nodes/canvas,
@@ -15,12 +16,12 @@ import
 
   ../window,
 
-  control
+  control,
+  label
 
 
 type
-  ToolTipObj* = object of ControlObj
-    text*: StyleText
+  ToolTipObj* = object of LabelObj
   ToolTipRef* = ref ToolTipObj
 
 const TOOLTIP_SPACE: float = 32f
@@ -31,8 +32,8 @@ proc ToolTip*(name: string = "ToolTip",
   nodepattern(ToolTipRef)
   controlpattern()
   result.text = stext(tooltip)
-  result.background.setColor(Color("#444"))
-  result.background.setBorderColor(Color("#555"))
+  result.background.setColor(current_theme~background)
+  result.background.setBorderColor(current_theme~background_deep)
   result.background.setBorderWidth(0.5)
   result.background.setCornerRadius(4)
   result.background.setCornerDetail(4)
@@ -44,12 +45,7 @@ proc ToolTip*(name: string = "ToolTip",
 
 method postdraw*(self: ToolTipRef, w, h: GLfloat) =
   {.warning[LockLevel]: off.}
-  procCall self.ControlRef.draw(w, h)
-  let
-    x = -w/2 + self.global_position.x
-    y = h/2 - self.global_position.y
-
-  self.text.renderTo(Vector2(x, y), self.rect_size, Anchor(0, 0, 0, 0))
+  procCall self.LabelRef.draw(w, h)
 
 method showAt*(self: ToolTipRef, x, y: float) {.base.} =
   self.moveTo(x, y)

+ 12 - 0
tests/test3.nim

@@ -337,6 +337,18 @@ suite "Work with Control nodes.":
           tooltip.showAtMouse()
     getSceneByName("main").addChild(tooltip)
 
+  test "Line chart test":
+    build:
+      - Chart line_chart:
+        data: newChartData(
+          @["one", "two", "three", "four", "five", "six"],
+          @[1, 8, 18, 32, 4, 16], "myData", current_theme~accent)
+
+        call move(100, 450)
+        call resize(320, 196)
+
+    getSceneByName("main").addChildren(line_chart)
+
 
   test "Launch window":
     windowLaunch()