123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- 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
|