flexbox, code cleanup
This commit is contained in:
243
addons/godot-flexbox/flex_container.gd
Normal file
243
addons/godot-flexbox/flex_container.gd
Normal file
@@ -0,0 +1,243 @@
|
||||
@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"
|
||||
Reference in New Issue
Block a user