Instantly share code, notes, and snippets.
Last active
December 26, 2024 16:55
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save Shilo/ebac01e4ef0a3e93d9dae6dffeb164fb to your computer and use it in GitHub Desktop.
Godot 4 more advanced version of the `TouchScreenButton` class that allows for more customization and control over the button's behavior.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// A more advanced version of the `TouchScreenButton` class that allows | |
/// for more customization and control over the button's behavior. | |
/// Example: Unhandled input processing, consuming input, circle hit box, and visual properties. | |
/// Fixes: Will not react to input that is handled/stopped by nodes. Will not pass input to nodes behind it. | |
/// TODO: Implement `PassbyPress` property. | |
/// </summary> | |
[GlobalClass, Tool] | |
public partial class TOLTouchScreenButton : TouchScreenButton | |
{ | |
private bool _processUnhandledInput; | |
[Export] | |
public bool ProcessUnhandledInput | |
{ | |
set | |
{ | |
this._processUnhandledInput = value; | |
this.SetProcessUnhandledInput(value); | |
this.SetProcessInput(!value); | |
} | |
get => this._processUnhandledInput; | |
} | |
[Export] | |
public bool ConsumeInput; | |
private bool _disabled; | |
[Export] | |
public bool Disabled | |
{ | |
set | |
{ | |
if (this._disabled == value) | |
return; | |
this._disabled = value; | |
this.SelfModulate = value ? this.DisabledTint : Colors.White; | |
} | |
get => this._disabled; | |
} | |
// The amount of extra pixels to add to the button's hit box. Allows more forgiving touch input. | |
[Export] | |
public Vector2I TouchMargin; | |
[ExportGroup("Visual")] | |
[Export] | |
public bool IsCircle | |
{ | |
set => this.ShaderMaterial?.SetShaderParameter("is_circle", value); | |
get => (bool)(this.ShaderMaterial?.GetShaderParameter("is_circle") ?? false); | |
} | |
[Export] | |
public Color PressedTint = Colors.Gray; | |
[Export] | |
public Color DisabledTint = new(1, 1, 1, 0.5f); | |
[Export(PropertyHint.ColorNoAlpha)] | |
public Color BorderColor | |
{ | |
set => this.ShaderMaterial?.SetShaderParameter("border_color", value); | |
get => (Color)(this.ShaderMaterial?.GetShaderParameter("border_color") ?? Colors.Black); | |
} | |
[Export(PropertyHint.Range, "0, 10, 1")] | |
public float BorderWidth | |
{ | |
set => this.ShaderMaterial?.SetShaderParameter("border_width", value); | |
get => (float)(this.ShaderMaterial?.GetShaderParameter("border_width") ?? 0); | |
} | |
public new event Action Pressed; | |
public new event Action Released; | |
private int _pressedIndex = -1; | |
private string _action; | |
private Color _lastModulate; | |
public ShaderMaterial ShaderMaterial => this.Material as ShaderMaterial; | |
public override void _Ready() | |
{ | |
if (Engine.IsEditorHint()) | |
return; | |
this.ProcessUnhandledInput = this._processUnhandledInput; | |
this._action = this.Action; | |
this.Action = ""; | |
this.Connect(CanvasItem.SignalName.VisibilityChanged, new Callable(this, MethodName.OnVisibilityChanged), | |
(uint)ConnectFlags.Deferred); | |
} | |
private void OnVisibilityChanged() | |
{ | |
// Reset the input processing state when the button's visibility is re-shown. | |
// Because godot overrides the node's ProcessUnhandledInput and ProcessInput states when re-drawing node. | |
if (this.Visible) | |
this.ProcessUnhandledInput = this._processUnhandledInput; | |
} | |
public override void _Input(InputEvent @event) | |
{ | |
this.ProcessInput(@event); | |
} | |
public override void _UnhandledInput(InputEvent @event) | |
{ | |
this.ProcessInput(@event); | |
} | |
private void ProcessInput(InputEvent @event) | |
{ | |
if (this.Disabled || !this.Visible || @event is not InputEventScreenTouch touchEvent) | |
return; | |
if (touchEvent.Pressed && this.HasGlobalPoint(touchEvent.Position)) | |
{ | |
this._pressedIndex = touchEvent.Index; | |
if (this.ConsumeInput) | |
GetViewport().SetInputAsHandled(); | |
this._lastModulate = this.SelfModulate; | |
this.SelfModulate = this.PressedTint; | |
this.Pressed?.Invoke(); | |
this.EmitAction(true); | |
} | |
else if (!touchEvent.Pressed && this._pressedIndex == touchEvent.Index) | |
{ | |
this._pressedIndex = -1; | |
if (this.ConsumeInput) | |
GetViewport().SetInputAsHandled(); | |
this.SelfModulate = this._lastModulate; | |
this.Released?.Invoke(); | |
this.EmitAction(false); | |
} | |
} | |
private bool HasGlobalPoint(Vector2 point) | |
{ | |
if (this.TextureNormal == null) | |
return false; | |
Vector2 position = this.GlobalPosition - this.TouchMargin; | |
Vector2 size = this.TextureNormal.GetSize() * this.GlobalScale + this.TouchMargin * 2; | |
if (this.IsCircle) | |
{ | |
Vector2 halfSize = size / 2; | |
float radius = Mathf.Min(halfSize.X, halfSize.Y); | |
return (point - (position + halfSize)).Length() <= radius; | |
} | |
else | |
{ | |
var rect = new Rect2(position, size); | |
return rect.HasPoint(point); | |
} | |
} | |
private void EmitAction(bool pressed) | |
{ | |
if (string.IsNullOrEmpty(this._action)) | |
return; | |
var pressedEvent = new InputEventAction(); | |
pressedEvent.Action = this._action; | |
pressedEvent.Pressed = pressed; | |
Input.ParseInputEvent(pressedEvent); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
shader_type canvas_item; | |
uniform vec3 border_color: source_color = vec3(0); | |
uniform float border_width: hint_range(0, 10) = 0; | |
uniform bool is_circle = false; | |
void fragment() { | |
float center = 0.5; | |
float border_width_uv = border_width * min(TEXTURE_PIXEL_SIZE.x, TEXTURE_PIXEL_SIZE.y); | |
float border_start = center - border_width_uv; | |
if (is_circle) { | |
if (step(border_start, length(UV - vec2(center, center))) > 0.5) { | |
COLOR.rgb = border_color; | |
} | |
COLOR.a *= step(length(UV - vec2(center, center)), center); | |
} else { | |
if (step(border_start, abs(UV.x - center)) > 0.5 || | |
step(border_start, abs(UV.y - center)) > 0.5) { | |
COLOR.rgb = border_color; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment