概述
为了方便编写一些自定义容器和控件、节点时方便元素布局,所以编写了一套布局的求取函数,统一放置在一个名为Layouts
的静态函数库中。
本文介绍我自定义的一些布局计算和实现以及函数编写的思路,并提供完整的函数库代码(持续更行)。
因为正在编写中,所以可能有部分布局还未实现或正在经历修改。
关于布局的基础思想
- 布局的本质是排列矩形,在Godot中也就是计算
Rect2
Rect2
可以并列、重叠以及嵌套,以对应各种不同的布局方式- 布局是一种参数化的抽象,不仅仅可以用于自定义容器,也可以结合CanvasItem绘图函数,用于创建自定义控件和2D节点的子元素或参数元素布局
获得设定内外边距后的矩形
通过封装Rect2
提供的边距偏移方法,就可以获得设定内边距或外边距的矩形。
一般用于控件或容器,只需要设计内边距。
在Godot的StyleBox
中,内边距不叫padding
,而是被称为content_margin
。所以在设计中可能并不特意区分padding
和margin
的称谓。
内边距盒模型:
外边距盒模型:
两个盒模型中:
- 实线:为初始的矩形
- 虚线:为设定边距后偏移得到的矩形
内边距矩形求取函数
这里我只实现设定内边距的矩形求取函数。
# 求设定内边距的矩形
static func padding_rect(
rect:Rect2, # 外框矩形
top:float = 0.0, # 顶部边距
right:float = 0.0, # 右侧边距
bottom:float = 0.0,# 底部边距
left:float = 0.0, # 左侧边距
) -> Rect2:
# 通过反向偏移边距,获得内容区矩形
# min限定只能向内进行偏移
var content_rect:Rect2 = rect.grow_individual(
min(-left,0),
min(-top,0),
min(-right,0),
min(-bottom,0),
)
return content_rect
Grid布局
均分网格布局
Grid布局可以作为一个核心布局,均分布局可以被取代。
columns
:指定列数rows
:指定行数col_gap
:列间距row_gap
:行间距margin_top
:顶部内边距margin_bottom
:底部内边距margin_left
:左侧内边距margin_right
:右侧内边距
求解思路:
- 根据外框矩形和边距,算出内容区域的矩形;
- 根据行数和列数,以及行列间隔,获得左上角第一个单元格大小,然后偏移获得其他矩形。
# 均分网格布局
static func grid(
rect:Rect2, # 外框矩形
# =========== 基础参数 ===========
columns:int, # 列数
rows:int, # 行数
col_gap:float = 0.0, # 列间距
row_gap:float = 0.0, # 行间距
# =========== 边距设定 ===========
margin_top:float = 0.0, # 顶部边距
margin_bottom:float = 0.0,# 底部边距
margin_left:float = 0.0, # 左侧边距
margin_right:float = 0.0, # 右侧边距
) -> Array[Rect2]:
var rects:Array[Rect2]
# 通过反向偏移边距,获得内容区矩形
# min限定只能向内进行偏移
var content_rect:Rect2 = rect.grow_individual(
min(-margin_left,0),
min(-margin_top,0),
min(-margin_right,0),
min(-margin_bottom,0),
)
# 减除行列间距后内容区域的尺寸
var calc_size = content_rect.size - Vector2(col_gap,row_gap) * Vector2(max(columns - 1,0),max(rows -1,0))
# 左上角第一个单元格矩形
var cell_rect:Rect2 = Rect2(
content_rect.position,
calc_size/Vector2(columns,rows)
)
# 单个偏移
var trans:Vector2 = cell_rect.size + Vector2(col_gap,row_gap)
# 计算出所有单元格的矩形
for x in range(columns):
for y in range(rows):
rects.append(translate(Vector2(x,y) * trans) * cell_rect)
return rects
测试代码:
@tool
extends Control
@export var image:Texture2D:
set(val):
image = val
queue_redraw()
## 行数
@export var rows:int = 1:
set(val):
rows = val
queue_redraw()
## 列数
@export var cols:int = 1:
set(val):
cols = val
queue_redraw()
## 列间距
@export var col_gap:float=0.0:
set(val):
col_gap = val
queue_redraw()
## 行间距
@export var row_gap:float=0.0:
set(val):
row_gap = val
queue_redraw()
## 顶部边距
@export var margin_top:float=0.0:
set(val):
margin_top = val
queue_redraw()
## 顶部边距
@export var margin_bottom:float=0.0:
set(val):
margin_bottom = val
queue_redraw()
## 顶部边距
@export var margin_left:float=0.0:
set(val):
margin_left = val
queue_redraw()
## 顶部边距
@export var margin_right:float=0.0:
set(val):
margin_right = val
queue_redraw()
func _draw() -> void:
var rect = get_rect() * get_transform()
var rects:Array[Rect2] = Layouts.grid(
rect,cols,rows,col_gap,row_gap,
margin_top,margin_bottom,margin_left,margin_right
)
# 绘制图片
for rc in rects:
draw_texture_rect(image,rc,false)
测试效果:
比例布局
给定多个数字,每一格占总数分之自己。比如(1,2,3)第一格占1/6,第二格2/6,第三格占3/6。垂直版本类似。
比例布局是非均分网格布局的基础。
均分布局
基于均分网格布局,可以生成一些常用的均分布局,以简化参数。
水平和垂直居中
中间宽度固定,上下或左右居中的布局。
ABA布局
两端宽度固定,中间自适应的布局。
特殊布局
类紫微斗数盘布局
边角悬浮定位布局
头图文档布局
可以拆解为边悬浮定位布局+水平居中布局。
函数库完整代码
# ========================================================
# 名称:Layouts
# 类型:静态函数库
# 简介:提供布局函数
# 作者:巽星石
# Godot版本:v4.4.stable.steam [4c311cbee]
# 创建时间:2025年3月3日20:27:59
# 最后修改时间:2025年3月8日20:06:59
# ========================================================
class_name Layouts
# ============================ 基础函数和辅助方法 ============================
# 偏移
static func translate(offset: Vector2) -> Transform2D:
return Transform2D().translated(offset)
# 求设定内边距的矩形
static func padding_rect(
rect:Rect2, # 外框矩形
top:float = 0.0, # 顶部边距
right:float = 0.0, # 右侧边距
bottom:float = 0.0,# 底部边距
left:float = 0.0, # 左侧边距
) -> Rect2:
# 通过反向偏移边距,获得内容区矩形
# min限定只能向内进行偏移
var content_rect:Rect2 = rect.grow_individual(
min(-left,0),
min(-top,0),
min(-right,0),
min(-bottom,0),
)
return content_rect
# ============================ 布局计算求取函数 ============================
# =========================== 【水平和垂直居中布局】
# 水平定宽居中布局
static func HCenter(
rect:Rect2, # 外框矩形
width:float, # 固定宽度
padding_top:=0.0, # 上边距
padding_bottom:=0.0, # 下边距
) -> Rect2:
var dw = (rect.size.x - width)/2.0
return padding_rect(
rect,padding_top,dw,padding_bottom,dw
)
# 垂直定宽居中布局
static func VCenter(
rect:Rect2, # 外框矩形
height:float, # 固定高度
padding_left:=0.0, # 上边距
padding_right:=0.0, # 下边距
) -> Rect2:
var dh = (rect.size.y - height)/2.0
return padding_rect(
rect,dh,padding_right,dh,padding_left
)
# 水平垂直定尺寸居中布局
static func Center(
rect:Rect2, # 外框矩形
width:float, # 固定宽度
height:float, # 固定高度
) -> Rect2:
var dw = (rect.size.x - width)/2.0
var dh = (rect.size.y - height)/2.0
return padding_rect(
rect,dh,dw,dh,dw
)
# =========================== 【均分布局】
# 均分网格布局
static func grid(
rect:Rect2, # 外框矩形
# =========== 基础参数 ===========
columns:int, # 列数
rows:int, # 行数
col_gap:float = 0.0, # 列间距
row_gap:float = 0.0, # 行间距
# =========== 边距设定 ===========
margin_top:float = 0.0, # 顶部边距
margin_right:float = 0.0, # 右侧边距
margin_bottom:float = 0.0,# 底部边距
margin_left:float = 0.0, # 左侧边距
) -> Array[Rect2]:
var rects:Array[Rect2]
# 获取内容区矩形
var content_rect:Rect2 = padding_rect(
rect,margin_top,margin_right,margin_bottom,margin_left,
)
# 减除行列间距后内容区域的尺寸
var calc_size = content_rect.size - Vector2(col_gap,row_gap) * Vector2(max(columns - 1,0),max(rows -1,0))
# 左上角第一个单元格矩形
var cell_rect:Rect2 = Rect2(
content_rect.position,
calc_size/Vector2(columns,rows)
)
# 单个偏移
var trans:Vector2 = cell_rect.size + Vector2(col_gap,row_gap)
# 计算出所有单元格的矩形
for x in range(columns):
for y in range(rows):
rects.append(translate(Vector2(x,y) * trans) * cell_rect)
return rects
# 上下均分布局
static func top_down(
rect:Rect2, # 外框矩形
# =========== 基础参数 ===========
gap:float = 0.0, # 行间距
# =========== 边距设定 ===========
margin_top:float = 0.0, # 顶部边距
margin_bottom:float = 0.0,# 底部边距
margin_left:float = 0.0, # 左侧边距
margin_right:float = 0.0, # 右侧边距
) -> Array[Rect2]:
return grid(
rect,1,2,0,gap,
margin_top,margin_bottom,margin_left,margin_right
)
# 左右均分布局
static func left_right(
rect:Rect2, # 外框矩形
# =========== 基础参数 ===========
gap:float = 0.0, # 列间距
# =========== 边距设定 ===========
margin_top:float = 0.0, # 顶部边距
margin_bottom:float = 0.0,# 底部边距
margin_left:float = 0.0, # 左侧边距
margin_right:float = 0.0, # 右侧边距
) -> Array[Rect2]:
return grid(
rect,2,1,gap,0,
margin_top,margin_bottom,margin_left,margin_right
)
# 四均分布局
static func four_split(
rect:Rect2, # 外框矩形
# =========== 基础参数 ===========
col_gap:float = 0.0, # 列间距
row_gap:float = 0.0, # 行间距
# =========== 边距设定 ===========
margin_top:float = 0.0, # 顶部边距
margin_bottom:float = 0.0,# 底部边距
margin_left:float = 0.0, # 左侧边距
margin_right:float = 0.0, # 右侧边距
) -> Array[Rect2]:
return grid(
rect,2,2,col_gap,row_gap,
margin_top,margin_bottom,margin_left,margin_right
)
# 九均分布局 (九宫格)
static func nine_split(
rect:Rect2, # 外框矩形
# =========== 基础参数 ===========
col_gap:float = 0.0, # 列间距
row_gap:float = 0.0, # 行间距
# =========== 边距设定 ===========
margin_top:float = 0.0, # 顶部边距
margin_bottom:float = 0.0,# 底部边距
margin_left:float = 0.0, # 左侧边距
margin_right:float = 0.0, # 右侧边距
) -> Array[Rect2]:
return grid(
rect,3,3,col_gap,row_gap,
margin_top,margin_bottom,margin_left,margin_right
)