mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2026-01-04 23:10:08 +01:00
190 lines
6.6 KiB
GDScript
190 lines
6.6 KiB
GDScript
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
|