For easier use, I recommend using this code:
@tool
extends Range
class_name RangeProgressBar
const PROGRESS_BAR_SHAPED: Shader = preload("path")
@export var keep_aspect: bool = false:
set(v): keep_aspect = v; queue_redraw()
@export_range(0, 16, 0.01, "or_greater") var fill_speed: float = 0:
set(v): fill_speed = v; update_process(); queue_redraw()
var progress: float = value:
set(v):
set_process(fill_speed and !is_equal_approx(value, v))
progress = v if is_processing() else value
if material is not ShaderMaterial:
material = ShaderMaterial.new()
material.shader = PROGRESS_BAR_SHAPED
material.resource_local_to_scene = true
material.set_shader_parameter(&"progress", get_target_ratio())
func _init() -> void:
update_process()
changed.connect(update_process)
value_changed.connect(update_process.unbind(1))
func _process(delta: float) -> void:
progress = lerpf(progress, value, fill_speed * delta)
func _draw() -> void:
if keep_aspect:
var draw_size := Vector2.ONE * minf(size.x, size.y)
draw_rect(Rect2((size - draw_size) / 2.0, draw_size), Color.WHITE)
else:
draw_rect(Rect2(Vector2.ZERO, size), Color.WHITE)
func get_target_ratio() -> float:
if max_value > min_value:
return clampf((progress - min_value) / (max_value - min_value), 0, 1)
return 0.0
func update_process() -> void:
progress = progress
shader_type canvas_item;
uniform int fill_mode : hint_enum("Linear", "Radial", "Dual Radial", "Expansion", "Shrink", "Split Out", "Split In") = 0;
uniform vec4 under_color : source_color = vec4(0.0, 0.0, 0.0, 0.2);
uniform vec4 progress_color : source_color = vec4(1.0, 1.0, 1.0, 0.2);
uniform float roundness : hint_range(0.0, 1.0) = 0.4;
uniform float inner_radius_ratio : hint_range(0.0, 0.99) = 0;
uniform float initial_angle_deg : hint_range(0, 360) = 0;
uniform float fill_degrees : hint_range(0, 360) = 360;
uniform int segment_count = 0;
uniform float segment_width_ratio : hint_range(0.1, 1.0) = 1.0;
uniform float progress : hint_range(0.0, 1.0) = 0.0;
float get_shape_dist(vec2 uv, float r, vec2 aspect) {
vec2 q = abs((uv - 0.5) * aspect) - (aspect * 0.5 - r);
float d = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
return d * 2.0;
}
void fragment() {
vec2 derivative = vec2(dFdx(UV.x), dFdy(UV.y));
float aspect_ratio = abs(derivative.y / derivative.x);
vec2 aspect_vec = vec2(aspect_ratio, 1.0);
if (aspect_ratio > 1.0) aspect_vec = vec2(1.0, 1.0 / aspect_ratio);
aspect_vec /= min(aspect_vec.x, aspect_vec.y);
float r = clamp(roundness, 0.0, 1.0) * 0.5;
float raw_dist = get_shape_dist(UV, r, aspect_vec);
float dist = raw_dist + 1.0;
float af = fwidth(dist) * 0.5;
float inner_mask = (inner_radius_ratio > 0.0) ? smoothstep(inner_radius_ratio - af, inner_radius_ratio + af, dist) : 1.0;
float outer_mask = smoothstep(1.0 + af, 1.0 - af, dist);
float ring_mask = inner_mask * outer_mask;
if (ring_mask <= 0.0) discard;
vec2 uv_centered = UV - 0.5;
float angle_rad = radians(initial_angle_deg);
float cos_a = cos(angle_rad);
float sin_a = sin(angle_rad);
float max_extent = 0.5 * (abs(cos_a * aspect_vec.x) + abs(sin_a * aspect_vec.y));
float rotated_x_raw = (uv_centered.x * aspect_vec.x) * cos_a - (uv_centered.y * aspect_vec.y) * sin_a;
float linear_pos = (rotated_x_raw / (max_extent * 2.0)) + 0.5;
float start_angle_rad = radians(initial_angle_deg - 90.0);
float current_angle_rad = atan(uv_centered.y * aspect_vec.y, uv_centered.x * aspect_vec.x);
float raw_angle_diff = mod(current_angle_rad - start_angle_rad, TAU);
float angle_norm = raw_angle_diff / TAU;
float total_fill_ratio = fill_degrees / 360.0;
float current_pos;
bool in_active_zone;
if (fill_mode == 0 || fill_mode >= 5) {
current_pos = clamp(linear_pos / total_fill_ratio, 0.0, 1.0);
in_active_zone = linear_pos <= total_fill_ratio;
} else {
current_pos = clamp(angle_norm / total_fill_ratio, 0.0, 1.0);
in_active_zone = angle_norm <= total_fill_ratio;
}
if (!in_active_zone) discard;
if (segment_count > 0) {
float sc = float(segment_count);
float fraction_in_segment = fract(current_pos * sc);
float half_gap_frac = (1.0 - segment_width_ratio) * 0.5;
if (fraction_in_segment < half_gap_frac || fraction_in_segment > (1.0 - half_gap_frac)) discard;
}
bool filled = false;
float start_r = (inner_radius_ratio <= 0.0) ? 0.0 : inner_radius_ratio;
switch (fill_mode) {
case 0: filled = current_pos <= progress; break;
case 1: filled = current_pos <= progress; break;
case 2: filled = current_pos <= progress * 0.5 || current_pos >= 1.0 - progress * 0.5; break;
case 3: filled = dist <= mix(start_r, 1.0, progress); break;
case 4: filled = dist >= mix(1.0, start_r, progress); break;
case 5: filled = current_pos >= 0.5 - progress * 0.5 && current_pos <= 0.5 + progress * 0.5; break;
case 6: filled = current_pos <= progress * 0.5 || current_pos >= 1.0 - progress * 0.5; break;
}
COLOR = mix(under_color, progress_color, float(filled)) * ring_mask * COLOR;
}This feature is currently in beta.
Join the discussion! Log in to share your thoughts.
Log In to Comment