Patch9Frame repeat instead of strech?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By TomShar
:warning: Old Version Published before Godot 3 was released.

I’ve looked at the documentation and I can’t seem to find if it is possible to repeat instead of stretch the image when using a Patch9Frame. Is this something godot can do?

:bust_in_silhouette: Reply From: Zylann

Apparently not, there is no such thing on the built-in control. However that could be easy to implement in GDScript though, with a custom _draw() method.

Edit: may this script be useful to you :slight_smile:
It should be put on a Control node.

tool
extends Control

const MODE_STRETCH = 0
const MODE_TILE = 1

export(Texture) var texture = null setget set_texture
export var draw_center = true setget set_draw_center
export(int, "Stretch", "Tile") var slice_mode = MODE_STRETCH setget set_slice_mode
export var left_slice = 0 setget set_left_slice
export var right_slice = 0 setget set_right_slice
export var top_slice = 0 setget set_top_slice
export var bottom_slice = 0 setget set_bottom_slice


func _ready():
	pass


func set_texture(tex):
	texture = tex
	update()


func set_slice_mode(m):
	slice_mode = m
	update()


func set_draw_center(dc):
	draw_center = dc
	update()


func set_left_slice(m):
	if m < 0:
		m = 0
	left_slice = m
	update()

func set_right_slice(m):
	if m < 0:
		m = 0
	right_slice = m
	update()

func set_top_slice(m):
	if m < 0:
		m = 0
	top_slice = m
	update()

func set_bottom_slice(m):
	if m < 0:
		m = 0
	bottom_slice = m
	update()


func _draw():
	if texture == null:
		return

	var r = get_rect()
	var ts = texture.get_size()
	var rs = r.size
	var ks = rs / ts
	
	var dst_left = left_slice# * ks.x
	var dst_right = right_slice# * ks.x
	var dst_top = top_slice# * ks.y
	var dst_bottom = bottom_slice# * ks.y
	
	if top_slice > 0:
		if left_slice > 0:
			# Top-left corner
			draw_texture_rect_region(texture, \
				Rect2(0, 0, dst_left, dst_top), \
				Rect2(0, 0, left_slice, top_slice))
		if right_slice > 0:
			# Top-right corner
			draw_texture_rect_region(texture, \
				Rect2(rs.x - dst_right, 0, dst_right, dst_top), \
				Rect2(ts.x - right_slice, 0, right_slice, top_slice))
		# Top side
		draw_texture_rect_region_mode(texture, \
			Rect2(dst_left,    0,  rs.x - dst_left - dst_right,    dst_top), \
			Rect2(left_slice,  0,  ts.x - left_slice - right_slice,  top_slice))
	
	if bottom_slice > 0:
		if left_slice > 0:
			# Bottom-left corner
			draw_texture_rect_region(texture, \
				Rect2(0,  rs.y - dst_bottom,    dst_left, dst_bottom), \
				Rect2(0,  ts.y - bottom_slice,  left_slice, bottom_slice))
		if right_slice > 0:
			# Bottom-right corner
			draw_texture_rect_region(texture, \
				Rect2(rs.x - dst_right,    rs.y - dst_bottom,    dst_right, dst_bottom), \
				Rect2(ts.x - right_slice,  ts.y - bottom_slice,  right_slice, bottom_slice))
		# Bottom side
		draw_texture_rect_region_mode(texture, \
			Rect2(dst_left,    rs.y - dst_bottom,    rs.x - dst_left - dst_right,      dst_bottom), \
			Rect2(left_slice,  ts.y - bottom_slice,  ts.x - left_slice - right_slice,  bottom_slice))
	
	if left_slice > 0:
		# Left side
		draw_texture_rect_region_mode(texture, \
			Rect2(0,  dst_top,    dst_left,    rs.y - dst_top - dst_bottom), \
			Rect2(0,  top_slice,  left_slice,  ts.y - top_slice - bottom_slice))
	if right_slice > 0:
		# Right side
		draw_texture_rect_region_mode(texture, \
			Rect2(rs.x - dst_right,    dst_top,    dst_right,    rs.y - dst_top - dst_bottom), \
			Rect2(ts.x - right_slice,  top_slice,  right_slice,  ts.y - top_slice - bottom_slice))
	
	
	if draw_center:
		# Center
		draw_texture_rect_region_mode(texture, \
			Rect2(dst_left,    dst_top,    rs.x - dst_right - dst_left,      rs.y - dst_top - dst_bottom), \
			Rect2(left_slice,  top_slice,  ts.x - right_slice - left_slice,  ts.y - top_slice - bottom_slice))


func draw_texture_rect_region_mode(tex, dst_rect, src_rect):
	if slice_mode == MODE_STRETCH:
		draw_texture_rect_region(tex, dst_rect, src_rect)
	else:
		draw_texture_rect_region_tiled(tex, dst_rect, src_rect)


func draw_texture_rect_region_tiled(tex, dst_rect, src_rect):
	var pos = Vector2(0,0)
	var dst_pos = dst_rect.pos
	var src_size = src_rect.size
	
	while pos.y + src_size.y < dst_rect.size.y:
		while pos.x + src_size.x < dst_rect.size.x:
			draw_texture_rect_region(tex, Rect2(dst_pos + pos, src_size), src_rect)
			pos.x += src_size.x
		pos.x = 0
		pos.y += src_size.y
	
	var cropped_src_rect = Rect2( \
		src_rect.pos.x, \
		src_rect.pos.y, \
		src_rect.size.x, \
		dst_rect.size.y - pos.y)
	
	while pos.x + src_size.x < dst_rect.size.x:
		draw_texture_rect_region(tex, Rect2(dst_pos + pos, cropped_src_rect.size), cropped_src_rect)
		pos.x += src_size.x
	
	pos.y = 0
	
	var cropped_src_rect = Rect2(
		src_rect.pos.x, \
		src_rect.pos.y, \
		dst_rect.size.x - pos.x, \
		src_rect.size.y)
	
	while pos.y + src_size.y < dst_rect.size.y:
		draw_texture_rect_region(tex, Rect2(dst_pos + pos, cropped_src_rect.size), cropped_src_rect)
		pos.y += src_size.y
	
	var cropped_src_rect = Rect2(
		src_rect.pos.x, \
		src_rect.pos.y, \
		dst_rect.size.x - pos.x, \
		dst_rect.size.y - pos.y)
	
	draw_texture_rect_region(tex, Rect2(dst_pos + pos, cropped_src_rect.size), cropped_src_rect)

Thanks for the response. Ill test this tonight when I get home.

TomShar | 2017-02-20 11:55

@Zylann That works perfectly, only problem is the editor doesn’t show a preview. Is this possible to fix or will this only work at runtime?

TomShar | 2017-02-20 17:10

Strange, it is supposed to show a preview, I put the tool keyword on top and tested it myself. Do you have errors in the console? Godot 2.1.2?

Zylann | 2017-02-20 18:26

Huh, weird. I closed and re-opened the software and it is working now. Thanks again for this.

TomShar | 2017-02-20 18:33

There does seem to be a lot of draw_texture_rect_region calls. Should I be concerned about performance issues if I use this a lot/for large areas?

TomShar | 2017-02-20 18:38

If your tiles are very small and the frame is big, there will be more calls yes, but they should be done only once if the frame doesn’t change. You can put a print inside the _draw function to see how many times it gets called, or use the profiler to see if overall performance is affected.

Also, that looks like a lot of code but that’s because I made it linear to avoid if statements and useless calculations in loops.

Ideally this should be better put in the engine one day if many people request it.

Zylann | 2017-02-20 19:29

Ah, I’m new to the engine. I was expecting the _draw function to get called each frame for some reason. Obviously, that must only get run when a change is detected?

TomShar | 2017-02-20 20:22

Yes, like resizing the control, changing the texture, or when update() is called explicitely. Give it a try with a print to see how often it’s called :wink:

Zylann | 2017-02-20 20:25

Yeah I did, very interesting. Thanks for all the help.

TomShar | 2017-02-20 20:42