Browse Source

add input.nim

Ethosa 5 years ago
parent
commit
17f847f7e9

+ 9 - 7
src/nodesnim.nim

@@ -1,5 +1,6 @@
 import
   nodesnim/thirdparty/opengl,
+  nodesnim/thirdparty/opengl/glut,
 
   nodesnim/window,
   nodesnim/environment,
@@ -10,16 +11,17 @@ import
   nodesnim/core/anchor,
   nodesnim/core/color,
   nodesnim/core/exceptions,
+  nodesnim/core/input,
 
   nodesnim/nodes/node,
   nodesnim/nodes/scene,
-  nodesnim/nodes/canvas
+  nodesnim/nodes/canvas,
 
-export
-  opengl,
+  nodesnim/nodescontrol/control
 
+export
+  opengl, glut,
   window, environment,
-
-  vector2, rect2, enums, anchor, color, exceptions,
-  
-  node, scene, canvas
+  vector2, rect2, enums, anchor, color, exceptions, input,
+  node, scene, canvas,
+  control

+ 216 - 0
src/nodesnim/core/input.nim

@@ -0,0 +1,216 @@
+# author: Ethosa
+import
+  vector2,
+  rect2
+
+{.used.}
+
+
+type
+  InputEventType* {.size: sizeof(int8).} = enum
+    MOUSE, TOUCH, MOTION, KEYBOARD, UNKNOWN
+
+  InputAction* = object
+    kind*: InputEventType
+    key_int*: int8
+    button_index*: cint
+    name*, key*: string
+
+  Input* = object
+
+  InputEvent* = ref object
+    kind*: InputEventType
+    pressed*: bool
+    key_int*: int8
+    button_index*: cint
+    x*, y*, xrel*, yrel*: float
+    key*: string
+
+  InputEventVoid* = distinct int8
+  InputEventMouseButton* = distinct int8
+  InputEventMouseMotion* = distinct int8
+  InputEventTouchScreen* = distinct int8
+  InputEventKeyboard* = distinct int8
+
+
+const
+  BUTTON_LEFT* = 0
+  BUTTON_MIDDLE* = 1
+  BUTTON_RIGHT* = 2
+
+  K_F1* = 1
+  K_F2* = 2
+  K_F3* = 3
+  K_F4* = 4
+  K_F5* = 5
+  K_F6* = 6
+  K_F7* = 7
+  K_F8* = 8
+  K_F9* = 9
+  K_TAB* = 9
+  K_F10* = 10
+  K_F11* = 11
+  K_F12* = 12
+  K_ENTER* = 13
+  K_ESCAPE* = 27
+  K_SPACE* = 32
+  K_NUM_MUL* = 42
+  K_NUM_SUB* = 45
+  K_NUM_ADD* = 43
+  K_NUM_POINT* = 46
+  K_NUM_DIV* = 47
+  K_0* = 48
+  K_1* = 49
+  K_2* = 50
+  K_3* = 51
+  K_4* = 52
+  K_5* = 53
+  K_6* = 54
+  K_7* = 55
+  K_8* = 56
+  K_9* = 57
+  K_LEFT* = 100
+  K_UP* = 101
+  K_RIGHT* = 102
+  K_DOWN* = 103
+  K_PAGE_UP* = 104
+  K_PAGE_DOWN* = 105
+  K_HOME* = 106
+  K_END* = 107
+  K_INSERT* = 108
+  K_DELETE* = 127
+
+
+var
+  pressed_keys*: seq[string] = @[]
+  pressed_keys_ints*: seq[int8] = @[]
+  last_event*: InputEvent = InputEvent()
+  press_state*: int = 0
+  actionlist*: seq[InputAction] = @[]
+  mouse_pressed*: bool = false
+
+
+proc isInputEventVoid*(a: InputEvent): bool =
+  a.kind == UNKNOWN
+
+proc isInputEventMouseButton*(a: InputEvent): bool =
+  a.kind == MOUSE
+
+proc isInputEventMouseMotion*(a: InputEvent): bool =
+  a.kind == MOTION
+
+proc isInputEventTouchScreen*(a: InputEvent): bool =
+  a.kind == TOUCH
+
+proc isInputEventKeyboard*(a: InputEvent): bool =
+  a.kind == KEYBOARD
+
+
+proc addButtonAction*(a: type Input, name: string, button: cint) =
+  ## Adds a new action on button.
+  ##
+  ## Arguments:
+  ## - `name` - action name.
+  ## - `button` - button index, e.g.: BUTTON_LEFT, BUTTON_RIGHT or BUTTON_MIDDLE.
+  actionlist.add(InputAction(kind: MOUSE, name: name, button_index: button))
+
+proc addKeyAction*(a: type Input, name, key: string) =
+  ## Adds a new action on keyboard.
+  ##
+  ## Arguments:
+  ## - `name` - action name.
+  ## - `key` - key, e.g.: "w", "1", etc.
+  actionlist.add(InputAction(kind: KEYBOARD, name: name, key: key))
+
+proc addKeyAction*(a: type Input, name, key: int8) =
+  ## Adds a new action on keyboard.
+  ##
+  ## Arguments:
+  ## - `name` - action name.
+  ## - `key` - key, e.g.: K_ESCAPE, K_0, etc.
+  actionlist.add(InputAction(kind: KEYBOARD, name: name, key_int: key))
+
+proc addTouchAction*(a: type Input, name: string) =
+  ## Adds a new action on touch screen.
+  ##
+  ## Arguments:
+  ## - `name` - action name.
+  actionlist.add(InputAction(kind: TOUCH, name: name))
+
+
+proc isActionJustPressed*(a: type Input, name: string): bool =
+  ## Returns true, when action active one times.
+  ##
+  ## Arguments:
+  ## - `name` - action name.
+  result = false
+  for action in actionlist:
+    if action.name == name:
+      if action.kind == MOUSE and (last_event.kind == MOUSE or last_event.kind == MOTION):
+        if action.button_index == last_event.button_index and mouse_pressed:
+          if press_state == 1:
+            result = true
+      elif action.kind == TOUCH and last_event.kind == TOUCH:
+        if press_state == 1:
+          result = true
+      elif action.kind == KEYBOARD and last_event.kind == KEYBOARD:
+        if action.key in pressed_keys or action.key_int in pressed_keys_ints:
+          if press_state == 1:
+            result = true
+
+proc isActionPressed*(a: type Input, name: string): bool =
+  ## Returns true, when action active one or more times.
+  ##
+  ## Arguments:
+  ## - `name` - action name.
+  result = false
+  for action in actionlist:
+    if action.name == name:
+      if action.kind == MOUSE and (last_event.kind == MOUSE or last_event.kind == MOTION):
+        if action.button_index == last_event.button_index and mouse_pressed:
+          if press_state > 0:
+            result = true
+      elif action.kind == TOUCH and last_event.kind == TOUCH:
+        if press_state > 0:
+          result = true
+      elif action.kind == KEYBOARD and last_event.kind == KEYBOARD:
+        if action.key in pressed_keys or action.key_int in pressed_keys_ints:
+          if press_state > 0:
+            result = true
+
+proc isActionReleased*(a: type Input, name: string): bool =
+  ## Returns true, when action no more active.
+  ##
+  ## Arguments:
+  ## - `name` - action name.
+  result = false
+  for action in actionlist:
+    if action.name == name:
+      if action.kind == MOUSE and (last_event.kind == MOUSE or last_event.kind == MOTION):
+        if action.button_index == last_event.button_index and not mouse_pressed:
+          if press_state == 0:
+            result = true
+      elif action.kind == KEYBOARD and last_event.kind == KEYBOARD:
+        if action.key notin pressed_keys or action.key_int notin pressed_keys_ints:
+          if press_state == 0:
+            result = true
+
+proc `$`*(event: InputEvent): string =
+  case event.kind
+  of UNKNOWN:
+    "Unknown event."
+  of MOUSE:
+    var button =
+      if event.button_index == BUTTON_RIGHT:
+        "right"
+      elif event.button_index == BUTTON_LEFT:
+        "left"
+      else:
+        "middle"
+    "InputEventMouseButton(x: " & $event.x & ", y: " & $event.y & ", pressed:" & $event.pressed & ", button: " & button & ")"
+  of MOTION:
+    "InputEventMotionButton(x: " & $event.x & ", y: " & $event.y & ", xrel:" & $event.xrel & ", yrel:" & $event.yrel & ")"
+  of TOUCH:
+    "InputEventTouchScreen(x: " & $event.x & ", y: " & $event.y & ")"
+  of KEYBOARD:
+    "InputEventKeyboard(key: " & event.key & ", pressed:" & $event.pressed & ")"

+ 5 - 4
src/nodesnim/nodes/node.nim

@@ -6,7 +6,8 @@ import
 
   ../core/vector2,
   ../core/enums,
-  ../core/anchor
+  ../core/anchor,
+  ../core/input
 {.used.}
 
 
@@ -24,7 +25,7 @@ type
     children*: seq[NodePtr]          ## Node children.
     enter*: proc()                   ## This called when scene changed.
     exit*: proc()                    ## This called when exit from the scene.
-    input*: proc()  ## This called on user input.
+    input*: proc(event: InputEvent)  ## This called on user input.
     ready*: proc()                   ## This called when the scene changed and the `enter` was called.
     process*: proc()                 ## This called every frame.
   NodePtr* = ptr NodeObj
@@ -37,7 +38,7 @@ template nodepattern*(nodetype: untyped): untyped =
     rect_size: Vector2(0, 0),
     ready: proc() = discard,
     process: proc() = discard,
-    input: proc() = discard,
+    input: proc(event: InputEvent) = discard,
     enter: proc() = discard,
     exit: proc() = discard,
     is_ready: false, pausemode: INHERIT, visible: true,
@@ -161,7 +162,7 @@ method getPauseMode*(self: NodePtr): PauseMode {.base.} =
     current = current.parent
     result = current.pausemode
 
-method handle*(self: NodePtr, mouse_on: var NodePtr) {.base.} =
+method handle*(self: NodePtr, event: InputEvent, mouse_on: var NodePtr) {.base.} =
   ## Handles user input.
   ## This used in the Window object.
   discard

+ 7 - 4
src/nodesnim/nodes/scene.nim

@@ -2,7 +2,8 @@
 import
   node,
   ../thirdparty/opengl,
-  ../core/enums
+  ../core/enums,
+  ../core/input
 
 
 type
@@ -42,8 +43,10 @@ method exit*(scene: ScenePtr) {.base.} =
     child.enter()
     child.is_ready = false
 
-method handleScene*(scene: ScenePtr, mouse_on: var NodePtr, paused: bool) {.base.} =
+method handleScene*(scene: ScenePtr, event: InputEvent, mouse_on: var NodePtr, paused: bool) {.base.} =
   for child in scene.getChildIter():
+    if paused and child.getPauseMode() != PROCESS:
+      continue
     if child.visible:
-      child.handle(mouse_on)
-    child.input()
+      child.handle(event, mouse_on)
+    child.input(event)

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

@@ -0,0 +1,22 @@
+# author: Ethosa
+import
+  ../core/vector2,
+  ../core/rect2,
+  ../core/anchor,
+  ../core/input,
+
+  ../nodes/node,
+  ../nodes/canvas
+
+
+type
+  ControlObj* = object of CanvasObj
+    hovered*: bool
+    pressed*: bool
+
+    mouse_enter*: proc(x, y: float): void
+    mouse_exit*: proc(x, y: float): void
+    click*: proc(x, y: float): void
+    press*: proc(x, y: float): void
+    release*: proc(x, y: float): void
+  ControlPtr* = ptr ControlObj

+ 73 - 1
src/nodesnim/window.nim

@@ -5,6 +5,7 @@ import
 
   core/color,
   core/exceptions,
+  core/input,
 
   nodes/node,
   nodes/scene,
@@ -29,6 +30,9 @@ var
   paused*: bool = false
 
 
+# --- Callbacks --- #
+var mouse_on: NodePtr = nil
+
 proc display {.cdecl.} =
   ## Displays window.
   let (r, g, b, a) = env.color.toFloatTuple()
@@ -37,14 +41,76 @@ proc display {.cdecl.} =
 
   # Draw current scene.
   current_scene.drawScene(width.GLfloat, height.GLfloat, paused)
+  press_state = -1
 
   # Update window.
   glFlush()
   glutSwapBuffers()
 
+template check(event, condition, conditionelif: untyped): untyped =
+  if last_event is `event` and `condition`:
+    press_state = 2
+  elif `conditionelif`:
+    press_state = 1
+  else:
+    press_state = 0
+
 proc mouse(button, state, x, y: cint) {.cdecl.} =
   ## Handle mouse input.
-  discard
+  check(InputEventMouseButton, last_event.pressed and state == GLUT_DOWN, state == GLUT_DOWN)
+  last_event.button_index = button
+  last_event.x = x.float
+  last_event.y = y.float
+  last_event.kind = MOUSE
+  mouse_pressed = state == GLUT_DOWN
+  last_event.pressed = state == GLUT_DOWN
+
+  current_scene.handleScene(last_event, mouse_on, paused)
+
+proc keyboardpress(c: int8, x, y: cint) {.cdecl.} =
+  ## Called when press any key on keyboard.
+  let key = $c.char
+  check(InputEventKeyboard, last_event.pressed, true)
+  last_event.key = key
+  last_event.key_int = c
+  last_event.x = x.float
+  last_event.y = y.float
+  if key notin pressed_keys:
+    pressed_keys.add(key)
+    pressed_keys_ints.add(c)
+  last_event.kind = KEYBOARD
+
+  current_scene.handleScene(last_event, mouse_on, paused)
+
+proc keyboardup(c: int8, x, y: cint) {.cdecl.} =
+  ## Called when any key no more pressed.
+  let key = $c.char
+  check(InputEventKeyboard, false, false)
+  last_event.key = key
+  last_event.key_int = c
+  last_event.x = x.float
+  last_event.y = y.float
+  last_event.kind = KEYBOARD
+  var i = 0
+  for k in pressed_keys:
+    if k == key:
+      pressed_keys.delete(i)
+      pressed_keys_ints.delete(i)
+      break
+    inc i
+
+  current_scene.handleScene(last_event, mouse_on, paused)
+
+proc motion(x, y: cint) {.cdecl.} =
+  ## Called on any mouse motion.
+  last_event.kind = MOTION
+  last_event.xrel = last_event.x - x.float
+  last_event.yrel = last_event.y - y.float
+  last_event.x = x.float
+  last_event.y = y.float
+
+  current_scene.handleScene(last_event, mouse_on, paused)
+
 
 proc reshape(w, h: cint) {.cdecl.} =
   ## This called when window resized.
@@ -105,9 +171,15 @@ proc Window*(title: cstring, w: cint = 640, h: cint = 360) {.cdecl.} =
 
 
 proc windowLauch* =
+  ## Start main window loop.
   glutDisplayFunc(display)
+  glutIdleFunc(display)
   glutReshapeFunc(reshape)
   glutMouseFunc(mouse)
+  glutKeyboardFunc(keyboardpress)
+  glutKeyboardUpFunc(keyboardup)
+  glutMotionFunc(motion)
+  glutPassiveMotionFunc(motion)
   if main_scene == nil:
     raise newException(MainSceneNotLoadedError, "Main scene is not indicated!")
   changeScene(main_scene.name)

+ 2 - 0
tests/README.md

@@ -1,3 +1,5 @@
 <h1 align="center">Tests</h1>
 
 1. [Create a window and set up the main scene.](https://github.com/Ethosa/nodesnim/blob/master/tests/test1.nim)
+2. [Use Canvas node.](https://github.com/Ethosa/nodesnim/blob/master/tests/test2.nim)
+3. [Window events handling.](https://github.com/Ethosa/nodesnim/blob/master/tests/test3.nim)

+ 9 - 5
tests/test1.nim

@@ -2,13 +2,17 @@
 import nodesnim
 
 
-Window("hello world")
+Window(
+  "hello world",  # Window name
+  640,            # Window width,
+  360             # Window height
+)
 
 var
   mainobj: SceneObj
-  main = Scene("Main", mainobj)
+  main = Scene("Main", mainobj)  # Create pointer to Scene object.
 
 
-addScene(main)
-setMainScene("Main")
-windowLauch()
+addScene(main)        # Add new scene in window.
+setMainScene("Main")  # Set main scene.
+windowLauch()         # Start main loop.

+ 50 - 0
tests/test3.nim

@@ -0,0 +1,50 @@
+# --- Test 3. Window events handling. --- #
+import nodesnim
+
+
+Window("newwindow")
+
+var
+  mainobj: SceneObj
+  main = Scene("Main", mainobj)
+
+  nodeobj: NodeObj
+  node = Node("My node", nodeobj)
+
+main.addChild(node)
+
+# Bind actions:
+Input.addKeyAction("forward", "w")
+Input.addKeyAction("backward", "s")
+Input.addKeyAction("left", "a")
+Input.addKeyAction("right", "d")
+Input.addButtonAction("click", BUTTON_LEFT)
+Input.addButtonAction("release", BUTTON_RIGHT)
+
+
+node.process =
+  proc() =  # This called every frame.
+    if Input.isActionPressed("forward"):  # returns true, when user press "w"
+      echo "forward"
+    if Input.isActionPressed("backward"):  # returns true, when user press "s"
+      echo "backward"
+    if Input.isActionPressed("left"):  # returns true, when user press "a"
+      echo "left"
+    if Input.isActionPressed("right"):  # returns true, when user press "d"
+      echo "right"
+
+    if Input.isActionJustPressed("click"):  # returns true, when the user clicks the left button one time.
+      echo "clicked!"
+
+    if Input.isActionReleased("release"):  # returns true, when the user no more press on the right button.
+      echo "release!"
+
+node.input =
+  proc(event: InputEvent) =  # This called only on user input.
+    if event.isInputEventMouseButton() and event.pressed:
+      echo "hi"
+
+
+addScene(main)
+setMainScene("Main")
+windowLauch()