graphics_tablet_input.gd 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. extends Control
  2. # Automatically split lines at regular intervals to avoid performance issues
  3. # while drawing. This is especially due to the width curve which has to be recreated
  4. # on every new point.
  5. const SPLIT_POINT_COUNT = 1024
  6. var stroke: Line2D
  7. var width_curve: Curve
  8. var pressures := PackedFloat32Array()
  9. var event_position: Vector2
  10. var event_tilt: Vector2
  11. var line_color := Color.BLACK
  12. var line_width: float = 3.0
  13. # If `true`, modulate line width accordding to pen pressure.
  14. # This is done using a width curve that is continuously recreated to match the line's actual profile
  15. # as the line is being drawn by the user.
  16. var pressure_sensitive: bool = true
  17. var show_tilt_vector: bool = true
  18. @onready var tablet_info: Label = %TabletInfo
  19. func _ready() -> void:
  20. # This makes tablet and mouse input reported as often as possible regardless of framerate.
  21. # When accumulated input is disabled, we can query the pen/mouse position at every input event
  22. # seen by the operating system, without being limited to the framerate the application runs at.
  23. # The downside is that this uses more CPU resources, so input accumulation should only be
  24. # disabled when you need to have access to precise input coordinates.
  25. Input.use_accumulated_input = false
  26. start_stroke()
  27. %TabletDriver.text = "Tablet driver: %s" % DisplayServer.tablet_get_current_driver()
  28. func _input(event: InputEvent) -> void:
  29. if event is InputEventKey:
  30. if Input.is_action_pressed(&"increase_line_width"):
  31. $CanvasLayer/PanelContainer/Options/LineWidth/HSlider.value += 0.5
  32. #_on_line_width_value_changed(line_width)
  33. if Input.is_action_pressed(&"decrease_line_width"):
  34. $CanvasLayer/PanelContainer/Options/LineWidth/HSlider.value -= 0.5
  35. #_on_line_width_value_changed(line_width)
  36. if not stroke:
  37. return
  38. if event is InputEventMouseMotion:
  39. var event_mouse_motion := event as InputEventMouseMotion
  40. tablet_info.text = "Pressure: %.3f\nTilt: %.3v\nInverted pen: %s" % [
  41. event_mouse_motion.pressure,
  42. event_mouse_motion.tilt,
  43. "Yes" if event_mouse_motion.pen_inverted else "No",
  44. ]
  45. if event_mouse_motion.pressure <= 0 and stroke.points.size() > 1:
  46. # Initial part of a stroke; create a new line.
  47. start_stroke()
  48. # Enable the buttons if they were previously disabled.
  49. %ClearAllLines.disabled = false
  50. %UndoLastLine.disabled = false
  51. if event_mouse_motion.pressure > 0:
  52. # Continue existing line.
  53. stroke.add_point(event_mouse_motion.position)
  54. pressures.push_back(event_mouse_motion.pressure)
  55. # Only compute the width curve if it's present, as it's not even created
  56. # if pressure sensitivity is disabled.
  57. if width_curve:
  58. width_curve.clear_points()
  59. for pressure_idx in range(pressures.size()):
  60. width_curve.add_point(Vector2(
  61. float(pressure_idx) / pressures.size(),
  62. pressures[pressure_idx]
  63. ))
  64. # Split into a new line if it gets too long to avoid performance issues.
  65. # This is mostly reached when input accumulation is disabled, as enabling
  66. # input accumulation will naturally reduce point count by a lot.
  67. if stroke.get_point_count() >= SPLIT_POINT_COUNT:
  68. start_stroke()
  69. event_position = event_mouse_motion.position
  70. event_tilt = event_mouse_motion.tilt
  71. queue_redraw()
  72. func _draw() -> void:
  73. if show_tilt_vector:
  74. # Draw tilt vector.
  75. draw_line(event_position, event_position + event_tilt * 50, Color(1, 0, 0, 0.5), 2, true)
  76. func start_stroke() -> void:
  77. var new_stroke := Line2D.new()
  78. new_stroke.begin_cap_mode = Line2D.LINE_CAP_ROUND
  79. new_stroke.end_cap_mode = Line2D.LINE_CAP_ROUND
  80. new_stroke.joint_mode = Line2D.LINE_JOINT_ROUND
  81. # Adjust round precision depending on line width to improve performance
  82. # and ensure it doesn't go above the default.
  83. new_stroke.round_precision = mini(line_width, 8)
  84. new_stroke.default_color = line_color
  85. new_stroke.width = line_width
  86. if pressure_sensitive:
  87. new_stroke.width_curve = Curve.new()
  88. add_child(new_stroke)
  89. new_stroke.owner = self
  90. stroke = new_stroke
  91. if pressure_sensitive:
  92. width_curve = new_stroke.width_curve
  93. else:
  94. width_curve = null
  95. pressures.clear()
  96. func _on_undo_last_line_pressed() -> void:
  97. # Remove last node of type Line2D in the scene.
  98. var last_line_2d: Line2D = find_children("", "Line2D")[-1]
  99. if last_line_2d:
  100. # Remove stray empty line present at the end due to mouse motion.
  101. # Note that doing it once doesn't always suffice, as multiple empty lines
  102. # may exist at the end of the list (e.g. after changing line width/color settings).
  103. # In this case, the user will have to use undo multiple times.
  104. if last_line_2d.get_point_count() == 0:
  105. last_line_2d.queue_free()
  106. var other_last_line_2d: Line2D = find_children("", "Line2D")[-2]
  107. if other_last_line_2d:
  108. other_last_line_2d.queue_free()
  109. else:
  110. last_line_2d.queue_free()
  111. # Since a new line is created as soon as mouse motion occurs (even if nothing is visible yet),
  112. # we consider the list of lines to be empty with up to 2 items in it here.
  113. %UndoLastLine.disabled = find_children("", "Line2D").size() <= 2
  114. start_stroke()
  115. func _on_clear_all_lines_pressed() -> void:
  116. # Remove all nodes of type Line2D in the scene.
  117. for node in find_children("", "Line2D"):
  118. node.queue_free()
  119. %ClearAllLines.disabled = true
  120. start_stroke()
  121. func _on_line_color_changed(color: Color) -> void:
  122. line_color = color
  123. # Required to make the setting change apply immediately.
  124. start_stroke()
  125. func _on_line_width_value_changed(value: float) -> void:
  126. line_width = value
  127. $CanvasLayer/PanelContainer/Options/LineWidth/Value.text = "%.1f" % value
  128. # Required to make the setting change apply immediately.
  129. start_stroke()
  130. func _on_pressure_sensitive_toggled(toggled_on: bool) -> void:
  131. pressure_sensitive = toggled_on
  132. # Required to make the setting change apply immediately.
  133. start_stroke()
  134. func _on_show_tilt_vector_toggled(toggled_on: bool) -> void:
  135. show_tilt_vector = toggled_on
  136. func _on_msaa_item_selected(index: int) -> void:
  137. get_viewport().msaa_2d = index as Viewport.MSAA
  138. func _on_max_fps_value_changed(value: float) -> void:
  139. # Since the project has low-processor usage mode enabled, we change its sleep interval instead.
  140. # Since this is a value in microseconds between frames, we have to convert it from a FPS value.
  141. @warning_ignore("narrowing_conversion")
  142. OS.low_processor_usage_mode_sleep_usec = 1_000_000.0 / value
  143. $CanvasLayer/PanelContainer/Options/MaxFPS/Value.text = str(roundi(value))
  144. func _on_v_sync_toggled(toggled_on: bool) -> void:
  145. DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if toggled_on else DisplayServer.VSYNC_DISABLED)
  146. func _on_input_accumulation_toggled(toggled_on: bool) -> void:
  147. Input.use_accumulated_input = toggled_on