244 lines
6.3 KiB
GDScript
244 lines
6.3 KiB
GDScript
@tool
|
|
class_name FlexContainer
|
|
extends Container
|
|
|
|
@export var debug_draw = false
|
|
|
|
var _draw_rects = []
|
|
|
|
const EDGES = [1, 2, 3, 0]
|
|
const _PADDING_WRAPPER_NAME = "__FlexPaddingWrapper"
|
|
|
|
|
|
enum FlexDirection {Column,ColumnReverse,Row,RowReverse}
|
|
enum FlexWrap {NoWrap,Wrap,WrapReverse}
|
|
enum JustifyContent {FlexStart,Center,FlexEnd,SpaceBetween,SpaceAround,SpaceEvenly}
|
|
enum AlignItems {Auto,FlexStart,Center,FlexEnd,Stretch,Baseline,SpaceBetween,SpaceAround}
|
|
enum AlignContent {Auto,FlexStart,Center,FlexEnd,Stretch,Baseline,SpaceBetween,SpaceAround}
|
|
|
|
const DEFAULT_VALUE = {
|
|
reverse = 0,
|
|
flex_wrap = FlexWrap.NoWrap,
|
|
flex_direction = FlexDirection.Row,
|
|
justify_content = JustifyContent.FlexStart,
|
|
align_items = AlignItems.Stretch, # Note: Stretch is the default
|
|
align_content = AlignContent.FlexStart
|
|
}
|
|
|
|
var _root: Flexbox
|
|
var _initialized = false
|
|
|
|
var _flex_list = []
|
|
enum FlexDataType { CID = 0, FLEXBOX, CONTROL }
|
|
|
|
var direction_reverse = DEFAULT_VALUE.reverse
|
|
@export var flex_direction:FlexDirection = DEFAULT_VALUE.flex_direction
|
|
@export var flex_wrap:FlexWrap = DEFAULT_VALUE.flex_wrap
|
|
@export var justify_content:JustifyContent = DEFAULT_VALUE.justify_content
|
|
@export var align_items:AlignItems = DEFAULT_VALUE.align_items
|
|
@export var align_content:AlignContent = DEFAULT_VALUE.align_content
|
|
|
|
|
|
var state:Dictionary = {flex_direction=null, flex_wrap=null,justify_content=null,align_items=null,align_content=null}
|
|
|
|
func _init():
|
|
_root = Flexbox.new()
|
|
update_state()
|
|
|
|
|
|
func update_state():
|
|
for property in state:
|
|
var value = get(property)
|
|
state[property] = value
|
|
|
|
|
|
func _ready() -> void:
|
|
_root.set_flex_direction(get("flex_direction"))
|
|
_root.set_flex_wrap(get("flex_wrap"))
|
|
_root.set_justify_content(get("justify_content"))
|
|
_root.set_align_items(get("align_items"))
|
|
_root.set_align_content(get("align_content"))
|
|
update_state()
|
|
set_process_input(false)
|
|
_initialized = true
|
|
|
|
|
|
func _notification(what: int) -> void:
|
|
match what:
|
|
NOTIFICATION_SORT_CHILDREN:
|
|
_resort()
|
|
[NOTIFICATION_TRANSLATION_CHANGED, NOTIFICATION_LAYOUT_DIRECTION_CHANGED]:
|
|
queue_sort()
|
|
|
|
|
|
func _resort() -> void:
|
|
if not is_instance_valid(_root):
|
|
return
|
|
|
|
_root.remove_all_children()
|
|
_flex_list.clear()
|
|
|
|
var root_size = get_size()
|
|
_root.set_width(root_size.x)
|
|
_root.set_height(root_size.y)
|
|
|
|
if debug_draw:
|
|
_draw_rects.clear()
|
|
_draw_debug_rect(Rect2(Vector2.ZERO, root_size), Color(0, 0.8, 0.5, 1))
|
|
|
|
for i in range(get_child_count()):
|
|
var c = get_child(i)
|
|
if not c.is_class("Control") or c.is_set_as_top_level() or not c.is_visible_in_tree():
|
|
continue
|
|
|
|
var flexbox = Flexbox.new()
|
|
_root.insert_child(flexbox, _flex_list.size())
|
|
_flex_list.append([c.get_instance_id(), flexbox, c])
|
|
|
|
_set_control_min_size(c, flexbox)
|
|
var flex_metas = c.get_meta("flex_metas", {})
|
|
if flex_metas.size():
|
|
apply_flex_meta(flexbox, flex_metas)
|
|
if flex_metas.has("padding"):
|
|
padding_wrapper(c, flex_metas.get("padding"))
|
|
|
|
_root.mark_dirty_and_propogate()
|
|
|
|
_root.calculate_layout(root_size.x, root_size.y, 1)
|
|
|
|
for flex_data in _flex_list:
|
|
var flexbox = flex_data[FlexDataType.FLEXBOX]
|
|
var c = flex_data[FlexDataType.CONTROL]
|
|
|
|
if not is_instance_valid(c):
|
|
continue
|
|
|
|
var offset = Vector2(flexbox.get_computed_left(), flexbox.get_computed_top())
|
|
var size = Vector2(flexbox.get_computed_width(), flexbox.get_computed_height())
|
|
_fit_child_in_rect(c, Rect2(offset, size))
|
|
|
|
if debug_draw:
|
|
_draw_debug_rect(Rect2(offset, size), Color(1, 0, 0, 0.8))
|
|
|
|
queue_redraw()
|
|
|
|
|
|
func padding_wrapper(node:Control,spacing_value:Array):
|
|
var wrapper_node = node.get_node_or_null(_PADDING_WRAPPER_NAME)
|
|
if not wrapper_node:
|
|
wrapper_node = MarginContainer.new()
|
|
wrapper_node.name = _PADDING_WRAPPER_NAME
|
|
var children_to_wrap = node.get_children()
|
|
for child in children_to_wrap:
|
|
if child != wrapper_node:
|
|
child.reparent(wrapper_node)
|
|
node.add_child(wrapper_node)
|
|
wrapper_node.add_theme_constant_override("margin_left", spacing_value[0])
|
|
wrapper_node.add_theme_constant_override("margin_top", spacing_value[1])
|
|
wrapper_node.add_theme_constant_override("margin_right", spacing_value[2])
|
|
wrapper_node.add_theme_constant_override("margin_bottom", spacing_value[3])
|
|
|
|
|
|
func _find_index_from_flex_list(flex_list: Array, cid: int) -> int:
|
|
for i in range(flex_list.size()):
|
|
if flex_list[i][FlexDataType.CID] == cid:
|
|
return i
|
|
return -1
|
|
|
|
|
|
func _set_control_min_size(c: Control, flexbox: Flexbox):
|
|
var min_size = c.get_combined_minimum_size()
|
|
flexbox.set_min_width(min_size.x)
|
|
flexbox.set_min_height(min_size.y)
|
|
|
|
|
|
func _fit_child_in_rect(child: Control, rect: Rect2) -> void:
|
|
if not is_instance_valid(child):
|
|
return
|
|
child.set_position(rect.position)
|
|
child.set_size(rect.size)
|
|
child.set_rotation(0)
|
|
child.set_scale(Vector2.ONE)
|
|
|
|
|
|
func apply_flex_meta(node, metas):
|
|
for key in metas:
|
|
var value = metas[key]
|
|
apply_child_property(node, key, value)
|
|
|
|
|
|
func apply_child_property(node, prop, value):
|
|
match prop:
|
|
"basis":
|
|
if typeof(value) == TYPE_STRING and value == "auto":
|
|
node.set_flex_basis_auto()
|
|
else:
|
|
node.set_flex_basis(value)
|
|
"grow":
|
|
node.set_flex_grow(value)
|
|
"padding":
|
|
for i in range(4):
|
|
var edge = EDGES[i]
|
|
node.set_padding(edge, value[i])
|
|
"margin":
|
|
for i in range(4):
|
|
var edge = EDGES[i]
|
|
var value1 = value[i]
|
|
if typeof(value1) == TYPE_STRING and value1 == "auto":
|
|
node.set_margin_auto(edge)
|
|
else:
|
|
node.set_margin(edge, value1)
|
|
"align_self":
|
|
node.set_align_self(value)
|
|
|
|
|
|
func flex_property_changed(property, value):
|
|
value = process_value(property, value)
|
|
state[property] = value
|
|
set(property, value)
|
|
match property:
|
|
"flex_direction":
|
|
_root.set_flex_direction(value)
|
|
"flex_wrap":
|
|
_root.set_flex_wrap(value)
|
|
"justify_content":
|
|
_root.set_justify_content(value)
|
|
"align_items":
|
|
_root.set_align_items(value)
|
|
"align_content":
|
|
_root.set_align_content(value)
|
|
|
|
|
|
func update_layout():
|
|
queue_sort()
|
|
|
|
|
|
func edit_set_state(p_state):
|
|
for property in p_state:
|
|
var value = p_state[property]
|
|
flex_property_changed(property, value)
|
|
update_layout()
|
|
|
|
|
|
func edit_get_state():
|
|
return state.duplicate()
|
|
|
|
|
|
func _draw():
|
|
for r in _draw_rects:
|
|
draw_rect(r.rect, r.color, false, 2)
|
|
|
|
|
|
func _draw_debug_rect(rect, color):
|
|
_draw_rects.append({rect = rect, color = color})
|
|
|
|
|
|
func process_value(key, value):
|
|
if DEFAULT_VALUE.has(key) && value == -1:
|
|
return DEFAULT_VALUE[key]
|
|
return value
|
|
|
|
|
|
func get_class():
|
|
return "FlexContainer"
|