Browse Source

add text selection

Ethosa 4 years ago
parent
commit
d00c8df37f

+ 1 - 1
.github/workflows/test.yml

@@ -57,7 +57,7 @@ jobs:
       - name: Build examples
         run: |
           cd examples
-          for dir in hello_world calculator novel snake screensaver roguelike; do
+          for dir in hello_world calculator novel snake screensaver roguelike sample_messenger; do
             (
               cd "$dir"
               nim c main.nim

+ 0 - 1
README.md

@@ -49,7 +49,6 @@ The Nim GUI/2D framework based on OpenGL and SDL2.
 
   build:
     - Scene scene:
-      name: "Main"
       - Label hello:
         call setSizeAnchor(1, 1)
         call setTextAlign(0.5, 0.5, 0.5, 0.5)

+ 1 - 1
examples/sample_messenger/server_api/api.nim

@@ -20,4 +20,4 @@ proc enter*(): Future[bool] {.async.} =
 
 
 proc sendMessage*(msg: string) =
-  var response = waitFor client.get("http://127.0.0.1:5000/send?" & encodeQuery({"username": username, "msg": msg}))
+  discard waitFor client.get("http://127.0.0.1:5000/send?" & encodeQuery({"username": username, "msg": msg}))

+ 5 - 4
src/nodesnim/core/font.nim

@@ -211,10 +211,11 @@ proc getCaretPos*(text: StyleText, pos: uint32): tuple[a: Vector2Obj, b: uint16]
         return result
     result[0].y -= text.spacing
 
-proc getPosUnderPoint*(text: StyleText, global_pos, text_pos: Vector2Obj): uint32 =
+proc getPosUnderPoint*(text: StyleText, global_pos, text_pos: Vector2Obj, text_align: AnchorObj): uint32 =
   ## Returns caret position under mouse.
   if not text.font.isNil():
     let
+      textsize = text.getTextSize()
       local_pos = global_pos - text_pos
       lines = text.splitLines()
     var
@@ -228,14 +229,14 @@ proc getPosUnderPoint*(text: StyleText, global_pos, text_pos: Vector2Obj): uint3
       discard text.font.sizeUtf8(($line).cstring, addr w, addr h)
       if local_pos.y >= y and local_pos.y <= y + h.float:
         position.y = y
-      x = 0
+      x = textsize.x*text_align.x1 - w.Glfloat*text_align.x2
       for c in line.chars:
         discard text.font.sizeUtf8(($c).cstring, addr w, addr h)
-        x += w.float
         result += 1
         if local_pos.x >= x and local_pos.x <= x+w.float and position.y != -1f:
           position.x = x
           break
+        x += w.float
       if position.x != -1f:
         break
       y += text.spacing + h.float
@@ -262,7 +263,7 @@ proc renderSurface*(text: StyleText, align: AnchorObj): SurfacePtr =
     var
       surface = createRGBSurface(
         0, textsize.x.cint, textsize.y.cint, 32,
-        0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000'u32)
+        0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000u32)
       y: cint = 0
       w: cint
       h: cint

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

@@ -10,11 +10,11 @@ proc addNode(level: var seq[NimNode], code: NimNode): NimNode {.compileTime.} =
         if line[0].kind == nnkIdent and line[1].kind == nnkCommand:
           if $line[0] == "-":
             if line[1][1].kind == nnkIdent:
-              result.add(newVarStmt(line[1][1], newCall($line[1][0])))
+              result.add(newVarStmt(line[1][1], newCall($line[1][0], newStrLitNode($line[1][1]))))
             elif line[1][1].kind == nnkObjConstr:
-              result.add(newVarStmt(line[1][1][0], newCall($line[1][0])))
+              result.add(newVarStmt(line[1][1][0], newCall($line[1][0], newStrLitNode($line[1][1]))))
             elif line[1][1].kind == nnkPar:
-              result.add(newVarStmt(postfix(line[1][1][0], "*"), newCall($line[1][0])))
+              result.add(newVarStmt(postfix(line[1][1][0], "*"), newCall($line[1][0], newStrLitNode($line[1][1][0]))))
             if level.len() > 0:
               # - Scene main_scene:
               if line[1][1].kind == nnkIdent:
@@ -32,7 +32,7 @@ proc addNode(level: var seq[NimNode], code: NimNode): NimNode {.compileTime.} =
         line[1].insert(1, level[^1])
         result.add(line[1])
       # property: value -> currentNode.property = value
-      elif line.kind in [nnkCall, nnkExprColonExpr] and level.len() > 1:
+      elif line.kind in [nnkCall, nnkExprColonExpr] and level.len() > 0:
         var attr = newNimNode(nnkAsgn)
         attr.add(newNimNode(nnkDotExpr))
         attr[0].add(level[^1])
@@ -50,6 +50,11 @@ proc addNode(level: var seq[NimNode], code: NimNode): NimNode {.compileTime.} =
 
 macro build*(code: untyped): untyped =
   ## Builds nodes with YML-like syntax.
+  runnableExamples:
+    build:
+      - Scene scene:
+        Node test_node
+        Node node
   result = newStmtList()
   var
     current_level: seq[NimNode] = @[]

+ 94 - 57
src/nodesnim/nodescontrol/edittext.nim

@@ -2,6 +2,7 @@
 import
   ../thirdparty/opengl,
   ../thirdparty/sdl2,
+  ../thirdparty/sdl2/ttf,
 
   ../core/font,
   ../core/color,
@@ -21,12 +22,12 @@ import
 
 type
   EditTextRef* = ref object of LabelObj
-    hint*: StyleText
-    caret_color: ColorRef
-    caret_pos: uint32
+    is_blink, is_select: bool
+    caret*, selectable: bool
     blink_time: uint8
-    is_blink: bool
-    caret*: bool
+    caret_color: ColorRef
+    hint*: StyleText
+    caret_pos: array[2, uint32]
     on_edit*: proc(pressed_key: string): void  ## This called when user press any key.
 
 const
@@ -36,10 +37,12 @@ const
 proc EditText*(name: string = "EditText", hint: string = "Edit text ..."): EditTextRef =
   nodepattern(EditTextRef)
   controlpattern()
+  result.is_blink = false
+  result.is_select = false
   result.caret = true
-  result.caret_pos = 0
+  result.selectable = true
+  result.caret_pos = [0u32, 0u32]
   result.caret_color = Color("#ffccddaa")
-  result.is_blink = false
   result.blink_time = BLINK_TIME
   result.text = stext("")
   result.hint = stext(hint)
@@ -61,9 +64,15 @@ method draw*(self: EditTextRef, w, h: Glfloat) =
   let
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
-    caret = self.text.getCaretPos(self.caret_pos)
     xalign = x + self.rect_size.x*self.text_align.x1 - self.rect_min_size.x*self.text_align.x2
     yalign = y - self.rect_size.y*self.text_align.y1 + self.rect_min_size.y*self.text_align.y2
+  var
+    lines = self.text.splitLines()
+    w: cint
+    h: cint
+    x1 = 0f
+    y1 = 0f
+    i = 0u32
 
   dec self.blink_time
   if self.blink_time == 0:
@@ -75,25 +84,35 @@ method draw*(self: EditTextRef, w, h: Glfloat) =
   else:
     self.text.renderTo(Vector2(x+self.padding.x1, y-self.padding.y1), self.rect_size, self.text_align)
 
-  if self.is_blink:
-    glColor4f(self.caret_color.r, self.caret_color.g, self.caret_color.b, self.caret_color.a)
-    glBegin(GL_QUADS)
-    glVertex2f(
-      xalign + caret[0].x,
-      yalign - caret[0].y)
-    glVertex2f(
-      xalign + caret[0].x - BLINK_WIDTH,
-      yalign - caret[0].y)
-    glVertex2f(
-      xalign + caret[0].x - BLINK_WIDTH,
-      yalign - caret[0].y - caret[1].float)
-    glVertex2f(
-      xalign + caret[0].x,
-      yalign - caret[0].y - caret[1].float)
-    glEnd()
-
-
-template changeText( self, `text`, `save_properties`, t: untyped): untyped =
+  for line in lines:
+    discard self.text.font.sizeUtf8(($line).cstring, addr w, addr h)
+    x1 = self.rect_min_size.x*self.text_align.x1 - w.Glfloat*self.text_align.x2
+    for c in line.chars:
+      discard self.text.font.sizeUtf8(($c).cstring, addr w, addr h)
+      if self.is_select and (i >= self.caret_pos[0] and i < self.caret_pos[1]) or (i >= self.caret_pos[1] and i < self.caret_pos[0]):
+        glColor4f(0.4, 0.4, 0.7, 0.5)
+        glBegin(GL_QUADS)
+        glVertex2f(xalign+x1, yalign-y1)
+        glVertex2f(xalign+x1+w.Glfloat, yalign-y1)
+        glVertex2f(xalign+x1+w.Glfloat, yalign-y1-h.Glfloat)
+        glVertex2f(xalign+x1, yalign-y1-h.Glfloat)
+        glEnd()
+      if self.is_blink and i == self.caret_pos[0]:
+        glColor4f(self.caret_color.r, self.caret_color.g, self.caret_color.b, self.caret_color.a)
+        glBegin(GL_QUADS)
+        glVertex2f(xalign+x1, yalign-y1)
+        glVertex2f(xalign+x1+BLINK_WIDTH, yalign-y1)
+        glVertex2f(xalign+x1+BLINK_WIDTH, yalign-y1-h.Glfloat)
+        glVertex2f(xalign+x1, yalign-y1-h.Glfloat)
+        glEnd()
+      x1 += w.float
+      inc i
+    y1 += self.text.spacing
+    y1 += h.float
+    inc i
+
+
+template changeText(self, `text`, `save_properties`, t: untyped): untyped =
   var st = stext(`text`)
   if `self`.`t`.font.isNil():
     `self`.`t`.font = standard_font
@@ -109,6 +128,28 @@ template changeText( self, `text`, `save_properties`, t: untyped): untyped =
   `self`.resize(`self`.rect_size.x, `self`.rect_size.y)
   `self`.`t`.rendered = false
 
+method moveCursorBy*(self: EditTextRef, value: int) {.base.} =
+  if value > 0:
+    self.caret_pos[0] += value.uint32
+  else:
+    self.caret_pos[0] -= (-value).uint32
+  self.caret_pos[1] = self.caret_pos[0]
+
+method insert*(self: EditTextRef, position: uint32, value: string) {.base.} =
+  let strtext = $self.text
+  if position > 0 and position < self.text.len().uint32:  # insert in caret pos
+    self.setText(strtext[0..position-1] & value & strtext[position..^1])
+    self.moveCursorBy(1)
+    self.on_edit(value)
+  elif position == 0:  # insert in start of text.
+    self.setText(value & strtext)
+    self.moveCursorBy(1)
+    self.on_edit(value)
+  elif position == self.text.len().uint32:  # insert in end of text.
+    self.setText(strtext & value)
+    self.moveCursorBy(1)
+    self.on_edit(value)
+
 method setText*(self: EditTextRef, t: string, save_properties: bool = false) =
   ## Changes text.
   ##
@@ -124,7 +165,6 @@ method setHintColor*(self: EditTextRef, color: ColorRef) {.base.} =
   self.hint.setColor(color)
   self.hint.rendered = false
 
-
 method handle*(self: EditTextRef, event: InputEvent, mouse_on: var NodeRef) =
   ## Handles user input. Thi uses in the `window.nim`.
   procCall self.ControlRef.handle(event, mouse_on)
@@ -135,43 +175,40 @@ method handle*(self: EditTextRef, event: InputEvent, mouse_on: var NodeRef) =
     else:
       setCursor(createSystemCursor(SDL_SYSTEM_CURSOR_ARROW))
 
-  if event.kind == MOUSE and event.pressed and self.hovered:
-    self.caret_pos = self.text.getPosUnderPoint(
-      self.getGlobalMousePosition(),
-      self.global_position + self.rect_size/2 - self.text.getTextSize()/2)
+  if event.kind == MOUSE and self.hovered:
+    if event.pressed:
+      self.caret_pos[0] = self.text.getPosUnderPoint(
+        self.getGlobalMousePosition(),
+        self.global_position + self.rect_size/2 - self.text.getTextSize()/2, self.text_align)
+      if self.selectable:
+        self.is_select = true
+        self.caret_pos[1] = self.caret_pos[0]
+  elif event.kind == MOTION:
+    if self.is_select and event.pressed:
+      self.caret_pos[1] = self.text.getPosUnderPoint(
+        self.getGlobalMousePosition(),
+        self.global_position + self.rect_size/2 - self.text.getTextSize()/2, self.text_align)
 
   if self.focused:
     if event.kind == TEXT and not event.pressed:
       # Other keys
-      if self.caret_pos > 0 and self.caret_pos < self.text.len().uint32:  # insert in caret pos
-        self.setText(($self.text)[0..self.caret_pos-1] & event.key & ($self.text)[self.caret_pos..^1])
-        self.caret_pos += 1
-        self.on_edit(event.key)
-      elif self.caret_pos == 0:  # insert in start of text.
-        self.setText(event.key & ($self.text))
-        self.caret_pos += 1
-        self.on_edit(event.key)
-      elif self.caret_pos == self.text.len().uint32:  # insert in end of text.
-        self.setText(($self.text) & event.key)
-        self.caret_pos += 1
-        self.on_edit(event.key)
+      self.insert(self.caret_pos[0], event.key)
     elif event.kind == KEYBOARD and event.key in pressed_keys:
       # Arrows
-      if event.key_int == K_LEFT and self.caret_pos > 0:
-        self.caret_pos -= 1
-      elif event.key_int == K_RIGHT and self.caret_pos < self.text.len().uint32:
-        self.caret_pos += 1
+      if event.key_int == K_LEFT and self.caret_pos[0] > 0:
+        self.moveCursorBy(-1)
+      elif event.key_int == K_RIGHT and self.caret_pos[0] < self.text.len().uint32:
+        self.moveCursorBy(1)
 
       elif event.key_int == 8:  # Backspace
-        if self.caret_pos > 1 and self.caret_pos < self.text.len().uint32:
-          self.setText($self.text[0..self.caret_pos-2] & $self.text[self.caret_pos..^1])
-          self.caret_pos -= 1
-        elif self.caret_pos == self.text.len().uint32 and self.caret_pos > 0:
+        if self.caret_pos[0] > 1 and self.caret_pos[0] < self.text.len().uint32:
+          self.setText($self.text[0..self.caret_pos[0]-2] & $self.text[self.caret_pos[0]..^1])
+          self.moveCursorBy(-1)
+        elif self.caret_pos[0] == self.text.len().uint32 and self.caret_pos[0] > 0:
           self.setText($self.text[0..^2])
-          self.caret_pos -= 1
-        elif self.caret_pos == 1:
+          self.moveCursorBy(-1)
+        elif self.caret_pos[0] == 1:
           self.setText($self.text[1..^1])
-          self.caret_pos -= 1
+          self.moveCursorBy(-1)
       elif event.key_int == 13:  # Next line
-        self.setText($self.text[0..self.caret_pos-1] & "\n" & $self.text[self.caret_pos..^1])
-        self.caret_pos += 1
+        self.insert(self.caret_pos[0], "\n")

+ 0 - 2
src/nodesnim/nodescontrol/label.nim

@@ -52,8 +52,6 @@ method draw*(self: LabelRef, w, h: GLfloat) =
     x = -w/2 + self.global_position.x
     y = h/2 - self.global_position.y
 
-  if not self.text.rendered:
-    self.text.render(self.rect_size, self.text_align)
   self.text.renderTo(Vector2(x + self.padding.x1, y - self.padding.y1), self.rect_size, self.text_align)
 
 method duplicate*(self: LabelRef): LabelRef {.base.} =

+ 1 - 0
tests/README.md

@@ -49,3 +49,4 @@
 - [Use margin.](https://github.com/Ethosa/nodesnim/blob/master/tests/test47.nim)
 - [Use Camera3D node.](https://github.com/Ethosa/nodesnim/blob/master/tests/test48.nim)
 - [Use TileMap Isometric mode](https://github.com/Ethosa/nodesnim/blob/master/tests/test49.nim)
+- [Make your own node](https://github.com/Ethosa/nodesnim/blob/master/tests/test50.nim)

+ 21 - 0
tests/test50.nim

@@ -0,0 +1,21 @@
+# --- Make your own node --- #
+import nodesnim
+
+
+type
+  MyOwnNodeRef = ref MyOwnNodeObj
+  MyOwnNodeObj = object of NodeRef  # NodeRef/Node2DRef/ControlRef/Node3DRef
+    property: int
+
+
+proc MyOwnNode(name: string = "MyOwnNode"): MyOwnNodeRef =
+  nodepattern(MyOwnNodeRef)
+  # controlpattern()/node2dpattern()/node3dpattern()
+  result.property = 100
+
+
+build:
+  - MyOwnNode node:
+    property: 10
+
+echo node.property