diff --git a/misc/graphics_tablet_input/README.md b/misc/graphics_tablet_input/README.md new file mode 100644 index 0000000000..d1fd626ef5 --- /dev/null +++ b/misc/graphics_tablet_input/README.md @@ -0,0 +1,32 @@ +# Graphics Tablet Input + +A demo showing how to use graphics tablet input in Godot. Godot has full support +for pressure sensitivity, tilt and pen inversion (i.e. checking whether the +"eraser" is being used on the pen). Note that some platforms and tablets +may not support reporting tilt. + +Input accumulation and V-Sync are disabled by default in this demo to minimize +input lag and get crisp lines (even at low FPS). This makes for the most +responsive drawing experience possible. You can toggle them in the sidebar to +see the difference it makes. Note that on Android, iOS and Web platforms, V-Sync +is forced at a system level and cannot be disabled. + +Lines are drawn using the Line2D node. Every time you lift off the open and start a new +line, a new Line2D node is created. Line antialiasing is provided by enabling 2D MSAA +in the Project Settings. + +Mouse input can also be used to draw in this demo, but using a tablet is recommended. + +> [!NOTE] +> +> If you experience issues on Windows, try changing the tablet driver in the Project +> Settings to **wintab** instead of the default **winink**. Also, try changing your +> tablet's input mode from relative to absolute mode. + +Language: GDScript + +Renderer: Mobile + +## Screenshots + +![Screenshot](screenshots/graphics_tablet_input.webp) diff --git a/misc/graphics_tablet_input/graphics_tablet_input.gd b/misc/graphics_tablet_input/graphics_tablet_input.gd new file mode 100644 index 0000000000..49aa8fb165 --- /dev/null +++ b/misc/graphics_tablet_input/graphics_tablet_input.gd @@ -0,0 +1,189 @@ +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 diff --git a/misc/graphics_tablet_input/graphics_tablet_input.tscn b/misc/graphics_tablet_input/graphics_tablet_input.tscn new file mode 100644 index 0000000000..4599a7afde --- /dev/null +++ b/misc/graphics_tablet_input/graphics_tablet_input.tscn @@ -0,0 +1,228 @@ +[gd_scene load_steps=13 format=3 uid="uid://dxpettbof8pr8"] + +[ext_resource type="Script" path="res://graphics_tablet_input.gd" id="1_fhuxi"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0pp2f"] +content_margin_left = 12.0 +content_margin_top = 12.0 +content_margin_right = 12.0 +content_margin_bottom = 12.0 +bg_color = Color(0.223529, 0.223529, 0.223529, 1) + +[sub_resource type="InputEventAction" id="InputEventAction_spbge"] +action = &"undo_last_line" + +[sub_resource type="Shortcut" id="Shortcut_3mcds"] +events = [SubResource("InputEventAction_spbge")] + +[sub_resource type="InputEventAction" id="InputEventAction_7n722"] +action = &"clear_all_lines" + +[sub_resource type="Shortcut" id="Shortcut_5wv4g"] +events = [SubResource("InputEventAction_7n722")] + +[sub_resource type="InputEventAction" id="InputEventAction_y8lr1"] +action = &"change_line_color" + +[sub_resource type="Shortcut" id="Shortcut_1nmmy"] +events = [SubResource("InputEventAction_y8lr1")] + +[sub_resource type="InputEventAction" id="InputEventAction_0l3by"] +action = &"toggle_pressure_sensitive" + +[sub_resource type="Shortcut" id="Shortcut_mnr5q"] +events = [SubResource("InputEventAction_0l3by")] + +[sub_resource type="InputEventAction" id="InputEventAction_4p65y"] +action = &"toggle_tilt_vector" + +[sub_resource type="Shortcut" id="Shortcut_231fk"] +events = [SubResource("InputEventAction_4p65y")] + +[node name="ColorRect" type="ColorRect"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_default_cursor_shape = 3 +script = ExtResource("1_fhuxi") + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="PanelContainer" type="PanelContainer" parent="CanvasLayer"] +offset_right = 264.0 +offset_bottom = 648.0 +size_flags_horizontal = 0 +theme_override_styles/panel = SubResource("StyleBoxFlat_0pp2f") + +[node name="Options" type="VBoxContainer" parent="CanvasLayer/PanelContainer"] +custom_minimum_size = Vector2(240, 0) +layout_mode = 2 +size_flags_horizontal = 0 +theme_override_constants/separation = 10 + +[node name="UndoLastLine" type="Button" parent="CanvasLayer/PanelContainer/Options"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +shortcut = SubResource("Shortcut_3mcds") +text = "Undo Last Line" + +[node name="ClearAllLines" type="Button" parent="CanvasLayer/PanelContainer/Options"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +shortcut = SubResource("Shortcut_5wv4g") +text = "Clear All Lines" + +[node name="HSeparator" type="HSeparator" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 + +[node name="LineColor" type="HBoxContainer" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/Options/LineColor"] +layout_mode = 2 +text = "Line Color" + +[node name="ColorPickerButton" type="ColorPickerButton" parent="CanvasLayer/PanelContainer/Options/LineColor"] +custom_minimum_size = Vector2(0, 30) +layout_mode = 2 +size_flags_horizontal = 3 +shortcut = SubResource("Shortcut_1nmmy") + +[node name="LineWidth" type="HBoxContainer" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +tooltip_text = "(-: Decrease, +: Increase)" +theme_override_constants/separation = 10 + +[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/Options/LineWidth"] +layout_mode = 2 +text = "Line Width" + +[node name="HSlider" type="HSlider" parent="CanvasLayer/PanelContainer/Options/LineWidth"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +min_value = 0.5 +max_value = 20.0 +step = 0.5 +value = 3.0 + +[node name="Value" type="Label" parent="CanvasLayer/PanelContainer/Options/LineWidth"] +custom_minimum_size = Vector2(35, 0) +layout_mode = 2 +text = "3.0" +horizontal_alignment = 1 + +[node name="PressureSensitive" type="CheckButton" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +tooltip_text = "If enabled, modulates line width according to pen pressure. +This has no effect if drawing with a mouse or a tablet +without pen pressure support." +button_pressed = true +shortcut = SubResource("Shortcut_mnr5q") +text = "Pressure-Sensitive" + +[node name="ShowTiltVector" type="CheckButton" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +tooltip_text = "If enabled, shows a visual representation of the tilt vector +reported by the graphics tablet. Tilt is not supported by all +graphics tablets." +button_pressed = true +shortcut = SubResource("Shortcut_231fk") +text = "Show Tilt Vector" + +[node name="HSeparator2" type="HSeparator" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 + +[node name="MSAA" type="HBoxContainer" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/Options/MSAA"] +layout_mode = 2 +text = "MSAA 2D" + +[node name="OptionButton" type="OptionButton" parent="CanvasLayer/PanelContainer/Options/MSAA"] +layout_mode = 2 +selected = 3 +item_count = 4 +popup/item_0/text = "Disabled" +popup/item_1/text = "2×" +popup/item_1/id = 1 +popup/item_2/text = "4×" +popup/item_2/id = 2 +popup/item_3/text = "8×" +popup/item_3/id = 3 + +[node name="MaxFPS" type="HBoxContainer" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/Options/MaxFPS"] +layout_mode = 2 +text = "Max FPS" + +[node name="HSlider" type="HSlider" parent="CanvasLayer/PanelContainer/Options/MaxFPS"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +min_value = 10.0 +max_value = 240.0 +step = 5.0 +value = 145.0 + +[node name="Value" type="Label" parent="CanvasLayer/PanelContainer/Options/MaxFPS"] +custom_minimum_size = Vector2(35, 0) +layout_mode = 2 +text = "145" +horizontal_alignment = 1 + +[node name="VSync" type="CheckButton" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +tooltip_text = "Disable V-Sync to achieve lower input latency. + +Note that Android, iOS and Web platforms enforce +V-Sync at a system level with no reliable way to disable it." +text = "V-Sync" + +[node name="InputAccumulation" type="CheckButton" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +tooltip_text = "If enabled, inputs are collected and merged into a single input event on every rendered frame. +By default, this is enabled in Godot, but this project disables it by default. + +This should be left disabled for drawing apps that expect precise input, +as lines can become visibly jagged otherwise." +text = "Input Accumulation" + +[node name="Spacer" type="Control" parent="CanvasLayer/PanelContainer/Options"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="TabletInfo" type="Label" parent="CanvasLayer/PanelContainer/Options"] +unique_name_in_owner = true +modulate = Color(1, 1, 1, 0.67451) +layout_mode = 2 +text = "Pressure: 0.000 +Tilt: (0.000, 0.000) +Inverted pen: No" + +[node name="TabletDriver" type="Label" parent="CanvasLayer/PanelContainer/Options"] +unique_name_in_owner = true +modulate = Color(1, 1, 1, 0.501961) +layout_mode = 2 +text = "Tablet driver: " + +[connection signal="pressed" from="CanvasLayer/PanelContainer/Options/UndoLastLine" to="." method="_on_undo_last_line_pressed"] +[connection signal="pressed" from="CanvasLayer/PanelContainer/Options/ClearAllLines" to="." method="_on_clear_all_lines_pressed"] +[connection signal="color_changed" from="CanvasLayer/PanelContainer/Options/LineColor/ColorPickerButton" to="." method="_on_line_color_changed"] +[connection signal="value_changed" from="CanvasLayer/PanelContainer/Options/LineWidth/HSlider" to="." method="_on_line_width_value_changed"] +[connection signal="toggled" from="CanvasLayer/PanelContainer/Options/PressureSensitive" to="." method="_on_pressure_sensitive_toggled"] +[connection signal="toggled" from="CanvasLayer/PanelContainer/Options/ShowTiltVector" to="." method="_on_show_tilt_vector_toggled"] +[connection signal="item_selected" from="CanvasLayer/PanelContainer/Options/MSAA/OptionButton" to="." method="_on_msaa_item_selected"] +[connection signal="value_changed" from="CanvasLayer/PanelContainer/Options/MaxFPS/HSlider" to="." method="_on_max_fps_value_changed"] +[connection signal="toggled" from="CanvasLayer/PanelContainer/Options/VSync" to="." method="_on_v_sync_toggled"] +[connection signal="toggled" from="CanvasLayer/PanelContainer/Options/InputAccumulation" to="." method="_on_input_accumulation_toggled"] diff --git a/misc/graphics_tablet_input/icon.svg b/misc/graphics_tablet_input/icon.svg new file mode 100644 index 0000000000..4ccedb7859 --- /dev/null +++ b/misc/graphics_tablet_input/icon.svg @@ -0,0 +1 @@ + diff --git a/misc/graphics_tablet_input/icon.svg.import b/misc/graphics_tablet_input/icon.svg.import new file mode 100644 index 0000000000..92a5218d8a --- /dev/null +++ b/misc/graphics_tablet_input/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://besl3a0sbq1v0" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/misc/graphics_tablet_input/project.godot b/misc/graphics_tablet_input/project.godot new file mode 100644 index 0000000000..bd0bd74ce8 --- /dev/null +++ b/misc/graphics_tablet_input/project.godot @@ -0,0 +1,71 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Graphics Tablet Input" +config/description="A demo showing how to use graphics tablet input in Godot." +config/tags=PackedStringArray("2d", "demo", "input", "official", "porting") +run/main_scene="res://graphics_tablet_input.tscn" +config/features=PackedStringArray("4.3") +run/low_processor_mode=true +config/icon="res://icon.svg" + +[display] + +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" +window/vsync/vsync_mode=0 + +[input] + +undo_last_line={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194308,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":90,"physical_keycode":0,"key_label":0,"unicode":122,"location":0,"echo":false,"script":null) +] +} +clear_all_lines={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":4194308,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +change_line_color={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":67,"physical_keycode":0,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null) +] +} +increase_line_width={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194437,"physical_keycode":0,"key_label":0,"unicode":43,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":61,"key_label":0,"unicode":61,"location":0,"echo":false,"script":null) +] +} +decrease_line_width={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194435,"physical_keycode":0,"key_label":0,"unicode":45,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":45,"key_label":0,"unicode":41,"location":0,"echo":false,"script":null) +] +} +toggle_pressure_sensitive={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":80,"physical_keycode":0,"key_label":0,"unicode":112,"location":0,"echo":false,"script":null) +] +} +toggle_tilt_vector={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":84,"physical_keycode":0,"key_label":0,"unicode":116,"location":0,"echo":false,"script":null) +] +} + +[rendering] + +renderer/rendering_method="mobile" +anti_aliasing/quality/msaa_2d=3 diff --git a/misc/graphics_tablet_input/screenshots/.gdignore b/misc/graphics_tablet_input/screenshots/.gdignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/misc/graphics_tablet_input/screenshots/graphics_tablet_input.webp b/misc/graphics_tablet_input/screenshots/graphics_tablet_input.webp new file mode 100644 index 0000000000..c2c64a28ac Binary files /dev/null and b/misc/graphics_tablet_input/screenshots/graphics_tablet_input.webp differ