Files
godot-demo-projects/misc/graphics_tablet_input/graphics_tablet_input.gd
2025-02-10 18:17:34 +01:00

190 lines
6.6 KiB
GDScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Control
# Automatically split lines at regular intervals to avoid performance issues
# while drawing. This is especially due to the width curve which has to be recreated
# on every new point.
const SPLIT_POINT_COUNT = 1024
var stroke: Line2D
var width_curve: Curve
var pressures := PackedFloat32Array()
var event_position: Vector2
var event_tilt: Vector2
var line_color := Color.BLACK
var line_width: float = 3.0
# If `true`, modulate line width accordding to pen pressure.
# This is done using a width curve that is continuously recreated to match the line's actual profile
# as the line is being drawn by the user.
var pressure_sensitive: bool = true
var show_tilt_vector: bool = true
@onready var tablet_info: Label = %TabletInfo
func _ready() -> void:
# This makes tablet and mouse input reported as often as possible regardless of framerate.
# When accumulated input is disabled, we can query the pen/mouse position at every input event
# seen by the operating system, without being limited to the framerate the application runs at.
# The downside is that this uses more CPU resources, so input accumulation should only be
# disabled when you need to have access to precise input coordinates.
Input.use_accumulated_input = false
start_stroke()
%TabletDriver.text = "Tablet driver: %s" % DisplayServer.tablet_get_current_driver()
func _input(event: InputEvent) -> void:
if event is InputEventKey:
if Input.is_action_pressed(&"increase_line_width"):
$CanvasLayer/PanelContainer/Options/LineWidth/HSlider.value += 0.5
#_on_line_width_value_changed(line_width)
if Input.is_action_pressed(&"decrease_line_width"):
$CanvasLayer/PanelContainer/Options/LineWidth/HSlider.value -= 0.5
#_on_line_width_value_changed(line_width)
if not stroke:
return
if event is InputEventMouseMotion:
var event_mouse_motion := event as InputEventMouseMotion
tablet_info.text = "Pressure: %.3f\nTilt: %.3v\nInverted pen: %s" % [
event_mouse_motion.pressure,
event_mouse_motion.tilt,
"Yes" if event_mouse_motion.pen_inverted else "No",
]
if event_mouse_motion.pressure <= 0 and stroke.points.size() > 1:
# Initial part of a stroke; create a new line.
start_stroke()
# Enable the buttons if they were previously disabled.
%ClearAllLines.disabled = false
%UndoLastLine.disabled = false
if event_mouse_motion.pressure > 0:
# Continue existing line.
stroke.add_point(event_mouse_motion.position)
pressures.push_back(event_mouse_motion.pressure)
# Only compute the width curve if it's present, as it's not even created
# if pressure sensitivity is disabled.
if width_curve:
width_curve.clear_points()
for pressure_idx in range(pressures.size()):
width_curve.add_point(Vector2(
float(pressure_idx) / pressures.size(),
pressures[pressure_idx]
))
# Split into a new line if it gets too long to avoid performance issues.
# This is mostly reached when input accumulation is disabled, as enabling
# input accumulation will naturally reduce point count by a lot.
if stroke.get_point_count() >= SPLIT_POINT_COUNT:
start_stroke()
event_position = event_mouse_motion.position
event_tilt = event_mouse_motion.tilt
queue_redraw()
func _draw() -> void:
if show_tilt_vector:
# Draw tilt vector.
draw_line(event_position, event_position + event_tilt * 50, Color(1, 0, 0, 0.5), 2, true)
func start_stroke() -> void:
var new_stroke := Line2D.new()
new_stroke.begin_cap_mode = Line2D.LINE_CAP_ROUND
new_stroke.end_cap_mode = Line2D.LINE_CAP_ROUND
new_stroke.joint_mode = Line2D.LINE_JOINT_ROUND
# Adjust round precision depending on line width to improve performance
# and ensure it doesn't go above the default.
new_stroke.round_precision = mini(line_width, 8)
new_stroke.default_color = line_color
new_stroke.width = line_width
if pressure_sensitive:
new_stroke.width_curve = Curve.new()
add_child(new_stroke)
new_stroke.owner = self
stroke = new_stroke
if pressure_sensitive:
width_curve = new_stroke.width_curve
else:
width_curve = null
pressures.clear()
func _on_undo_last_line_pressed() -> void:
# Remove last node of type Line2D in the scene.
var last_line_2d: Line2D = find_children("", "Line2D")[-1]
if last_line_2d:
# Remove stray empty line present at the end due to mouse motion.
# Note that doing it once doesn't always suffice, as multiple empty lines
# may exist at the end of the list (e.g. after changing line width/color settings).
# In this case, the user will have to use undo multiple times.
if last_line_2d.get_point_count() == 0:
last_line_2d.queue_free()
var other_last_line_2d: Line2D = find_children("", "Line2D")[-2]
if other_last_line_2d:
other_last_line_2d.queue_free()
else:
last_line_2d.queue_free()
# Since a new line is created as soon as mouse motion occurs (even if nothing is visible yet),
# we consider the list of lines to be empty with up to 2 items in it here.
%UndoLastLine.disabled = find_children("", "Line2D").size() <= 2
start_stroke()
func _on_clear_all_lines_pressed() -> void:
# Remove all nodes of type Line2D in the scene.
for node in find_children("", "Line2D"):
node.queue_free()
%ClearAllLines.disabled = true
start_stroke()
func _on_line_color_changed(color: Color) -> void:
line_color = color
# Required to make the setting change apply immediately.
start_stroke()
func _on_line_width_value_changed(value: float) -> void:
line_width = value
$CanvasLayer/PanelContainer/Options/LineWidth/Value.text = "%.1f" % value
# Required to make the setting change apply immediately.
start_stroke()
func _on_pressure_sensitive_toggled(toggled_on: bool) -> void:
pressure_sensitive = toggled_on
# Required to make the setting change apply immediately.
start_stroke()
func _on_show_tilt_vector_toggled(toggled_on: bool) -> void:
show_tilt_vector = toggled_on
func _on_msaa_item_selected(index: int) -> void:
get_viewport().msaa_2d = index as Viewport.MSAA
func _on_max_fps_value_changed(value: float) -> void:
# Since the project has low-processor usage mode enabled, we change its sleep interval instead.
# Since this is a value in microseconds between frames, we have to convert it from a FPS value.
@warning_ignore("narrowing_conversion")
OS.low_processor_usage_mode_sleep_usec = 1_000_000.0 / value
$CanvasLayer/PanelContainer/Options/MaxFPS/Value.text = str(roundi(value))
func _on_v_sync_toggled(toggled_on: bool) -> void:
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if toggled_on else DisplayServer.VSYNC_DISABLED)
func _on_input_accumulation_toggled(toggled_on: bool) -> void:
Input.use_accumulated_input = toggled_on