How do I add a shader to a TextureProgressBar's texture_progress?

It would be easier to create a custom Control that has the same functionality as a ProgressBar and do everything yourself.

If you want to go low level you could also use a ProgressBar and create a custom StyleBox class that uses the RenderingServer directly to draw a new canvas item with the Material you want.

Like:

@tool
class_name StyleBoxShader extends StyleBox

@export var material:Material
@export var texture:Texture2D

# the canvas item we are going to use
var canvas_item_rid:RID


func _init() -> void:
	# create the canvas item
	canvas_item_rid = RenderingServer.canvas_item_create()


func _notification(what: int) -> void:
	if what == NOTIFICATION_PREDELETE:
		# don't forget to free it
		RenderingServer.free_rid(canvas_item_rid)


func _draw(to_canvas_item: RID, rect: Rect2) -> void:
	# clear the canvas item
	RenderingServer.canvas_item_clear(canvas_item_rid)
	# set its parent to the canvas item from the object we want to draw
	# this will inherit transform, modulation and visibility from it
	RenderingServer.canvas_item_set_parent(canvas_item_rid, to_canvas_item)
	# set our material to the canvas item
	RenderingServer.canvas_item_set_material(canvas_item_rid, material.get_rid())
	if is_instance_valid(texture):
		# if texture is valid, use it
		RenderingServer.canvas_item_add_texture_rect(canvas_item_rid, rect, texture.get_rid())
	else:
		# if texture is not valid, draw a white rectangle
		RenderingServer.canvas_item_add_rect(canvas_item_rid, rect, Color.WHITE)


func _get_draw_rect(rect: Rect2) -> Rect2:
	return rect


func _get_minimum_size() -> Vector2:
	return Vector2.ZERO

Result:

(I enabled CanvasItem.clip_children in the ProgressBar to get the rounded corners)

This “trick” could also work with a custom Texture2D but the TextureProgressBar class uses the RenderingServer directly when drawing the textures using nine patch so the Texture2D._draw*() callbacks are never called.

5 Likes