rearrange scripts in folders
This commit is contained in:
226
flumi/Scripts/Engine/AutoSizingFlexContainer.gd
Normal file
226
flumi/Scripts/Engine/AutoSizingFlexContainer.gd
Normal file
@@ -0,0 +1,226 @@
|
||||
@tool
|
||||
class_name AutoSizingFlexContainer
|
||||
extends FlexContainer
|
||||
|
||||
signal flex_resized
|
||||
|
||||
var content_size: Vector2 = Vector2.ZERO
|
||||
|
||||
# This is the overridden layout logic for the auto-sizing container
|
||||
func _resort() -> void:
|
||||
# Check if we should fill horizontally (for w-full)
|
||||
if has_meta("should_fill_horizontal"):
|
||||
size_flags_horizontal = Control.SIZE_FILL
|
||||
else:
|
||||
if size_flags_horizontal == Control.SIZE_EXPAND_FILL and has_meta("size_flags_set_by_style_manager"):
|
||||
pass
|
||||
else:
|
||||
if not has_meta("size_flags_set_by_style_manager"):
|
||||
size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
# Check if we should fill vertically (for h-full)
|
||||
if has_meta("should_fill_vertical"):
|
||||
size_flags_vertical = Control.SIZE_FILL
|
||||
else:
|
||||
if not has_meta("size_flags_set_by_style_manager"):
|
||||
size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
if debug_draw:
|
||||
_draw_rects.clear()
|
||||
|
||||
var child_count = get_child_count()
|
||||
var valid_child_index = 0
|
||||
for i in range(child_count):
|
||||
var c = get_child(i)
|
||||
if not c is Control or c.is_set_as_top_level():
|
||||
continue
|
||||
|
||||
# Skip background panel from flex calculations
|
||||
if BackgroundUtils.is_background_panel(c):
|
||||
continue
|
||||
|
||||
var cid = c.get_instance_id()
|
||||
var target_index = _find_index_from_flex_list(_flex_list, cid)
|
||||
var flexbox: Flexbox
|
||||
|
||||
# If the child is not visible, remove its corresponding flexbox node
|
||||
if not c.is_visible_in_tree():
|
||||
if target_index != -1:
|
||||
_root.remove_child_at(target_index)
|
||||
_flex_list.remove_at(target_index)
|
||||
continue
|
||||
|
||||
# Find, swap, or create a new flexbox node for the child
|
||||
if target_index != -1:
|
||||
var old_flex_data = _flex_list[valid_child_index]
|
||||
var new_flex_data = _flex_list[target_index]
|
||||
flexbox = new_flex_data[FlexDataType.FLEXBOX]
|
||||
|
||||
if old_flex_data[FlexDataType.CID] != cid:
|
||||
_root.swap_child(valid_child_index, target_index)
|
||||
_flex_list[target_index] = old_flex_data
|
||||
_flex_list[valid_child_index] = new_flex_data
|
||||
else:
|
||||
flexbox = Flexbox.new()
|
||||
_root.insert_child(flexbox, valid_child_index)
|
||||
_flex_list.insert(valid_child_index, [cid, flexbox, c])
|
||||
|
||||
# Set the minimum size and apply flex properties for the child
|
||||
_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"))
|
||||
valid_child_index += 1
|
||||
|
||||
# Clean up any flexbox nodes for children that were removed
|
||||
child_count = valid_child_index
|
||||
if child_count != _flex_list.size():
|
||||
for i in range(_flex_list.size() - 1, child_count - 1, -1):
|
||||
_root.remove_child_at(i)
|
||||
_flex_list.resize(child_count)
|
||||
_root.mark_dirty_and_propogate()
|
||||
|
||||
|
||||
var auto_size_width = not has_meta("custom_css_width") and not has_meta("should_fill_horizontal") and not has_meta("custom_css_width_percentage")
|
||||
var auto_size_height = not has_meta("custom_css_height") and not has_meta("should_fill_vertical") and not has_meta("custom_css_height_percentage")
|
||||
|
||||
var available_width = NAN
|
||||
var available_height = NAN
|
||||
|
||||
if not auto_size_width:
|
||||
available_width = calculate_available_dimension(true)
|
||||
elif flex_wrap != FlexContainer.FlexWrap.NoWrap:
|
||||
available_width = get_parent_or_fallback_size(true)
|
||||
|
||||
if not auto_size_height:
|
||||
available_height = calculate_available_dimension(false)
|
||||
|
||||
_root.calculate_layout(available_width, available_height, 1) # 1 = LTR direction
|
||||
|
||||
# Get the size computed by Yoga
|
||||
var computed_size = Vector2(
|
||||
_root.get_computed_width(),
|
||||
_root.get_computed_height()
|
||||
)
|
||||
|
||||
# Respect any explicit width/height set via metadata
|
||||
var custom_w = calculate_custom_dimension(true)
|
||||
var custom_h = calculate_custom_dimension(false)
|
||||
|
||||
var needed_size = Vector2(
|
||||
max(custom_w, computed_size.x),
|
||||
max(custom_h, computed_size.y)
|
||||
)
|
||||
|
||||
# Store the actual content size for background drawing
|
||||
content_size = needed_size
|
||||
|
||||
# Construct the new minimum size for this container
|
||||
var new_min_size = custom_minimum_size
|
||||
if auto_size_width:
|
||||
# If flex wrap is enabled, constrain width to parent's available space
|
||||
if flex_wrap == FlexContainer.FlexWrap.Wrap:
|
||||
var parent_width = get_parent_or_fallback_size(true)
|
||||
if not is_nan(parent_width) and parent_width > 0:
|
||||
new_min_size.x = min(needed_size.x, parent_width)
|
||||
else:
|
||||
new_min_size.x = needed_size.x
|
||||
else:
|
||||
new_min_size.x = needed_size.x
|
||||
else:
|
||||
# For w-full, ensure minimum size matches the needed size
|
||||
new_min_size.x = needed_size.x
|
||||
if auto_size_height:
|
||||
new_min_size.y = needed_size.y
|
||||
else:
|
||||
# For h-full, ensure minimum size matches the needed size
|
||||
new_min_size.y = needed_size.y
|
||||
|
||||
if not custom_minimum_size.is_equal_approx(new_min_size):
|
||||
custom_minimum_size = new_min_size
|
||||
|
||||
# For w-full/h-full, also force the actual size if SIZE_FILL isn't working
|
||||
if has_meta("should_fill_horizontal") and size.x < new_min_size.x:
|
||||
size.x = new_min_size.x
|
||||
if has_meta("should_fill_vertical") and size.y < new_min_size.y:
|
||||
size.y = new_min_size.y
|
||||
|
||||
# Apply the calculated layout to each child control
|
||||
for flex_data in _flex_list:
|
||||
var flexbox = flex_data[FlexDataType.FLEXBOX]
|
||||
var c = flex_data[FlexDataType.CONTROL]
|
||||
var offset = Vector2(flexbox.get_computed_left(), flexbox.get_computed_top())
|
||||
var rect_size = Vector2(flexbox.get_computed_width(), flexbox.get_computed_height())
|
||||
_fit_child_in_rect(c, Rect2(offset, rect_size))
|
||||
|
||||
if debug_draw:
|
||||
_draw_debug_rect(Rect2(offset, rect_size), Color(1, 0, 0, 0.8))
|
||||
|
||||
|
||||
if not _is_inside_background_container():
|
||||
BackgroundUtils.update_background_panel(self)
|
||||
|
||||
emit_signal("flex_resized")
|
||||
|
||||
func _is_inside_background_container() -> bool:
|
||||
var current_parent = get_parent()
|
||||
while current_parent:
|
||||
if current_parent is PanelContainer:
|
||||
if current_parent.has_meta("hover_stylebox") or current_parent.has_meta("normal_stylebox"):
|
||||
return true
|
||||
current_parent = current_parent.get_parent()
|
||||
return false
|
||||
|
||||
func calculate_available_dimension(is_width: bool) -> float:
|
||||
var percentage_key = "custom_css_width_percentage" if is_width else "custom_css_height_percentage"
|
||||
var fill_key = "should_fill_horizontal" if is_width else "should_fill_vertical"
|
||||
|
||||
if has_meta(fill_key):
|
||||
return get_parent_or_fallback_size(is_width)
|
||||
elif has_meta(percentage_key):
|
||||
var percentage_str = get_meta(percentage_key)
|
||||
var percentage = float(percentage_str.replace("%", "")) / 100.0
|
||||
var parent_size = get_parent_size(is_width)
|
||||
return parent_size * percentage if parent_size > 0 else (custom_minimum_size.x if is_width else custom_minimum_size.y)
|
||||
else:
|
||||
return size.x if is_width else size.y
|
||||
|
||||
func calculate_custom_dimension(is_width: bool) -> float:
|
||||
var dimension_key = "custom_css_width" if is_width else "custom_css_height"
|
||||
var percentage_key = "custom_css_width_percentage" if is_width else "custom_css_height_percentage"
|
||||
var fill_key = "should_fill_horizontal" if is_width else "should_fill_vertical"
|
||||
|
||||
if has_meta(dimension_key):
|
||||
return float(get_meta(dimension_key))
|
||||
elif has_meta(percentage_key):
|
||||
var percentage_str = get_meta(percentage_key)
|
||||
var percentage = float(percentage_str.replace("%", "")) / 100.0
|
||||
var parent_size = get_parent_size(is_width)
|
||||
if parent_size > 0:
|
||||
return parent_size * percentage
|
||||
elif (size.x if is_width else size.y) > 0:
|
||||
return (size.x if is_width else size.y) * percentage
|
||||
else:
|
||||
return 0.0
|
||||
elif has_meta(fill_key):
|
||||
return 0.0
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
func get_parent_size(is_width: bool) -> float:
|
||||
var parent_container = get_parent()
|
||||
if parent_container:
|
||||
return parent_container.size.x if is_width else parent_container.size.y
|
||||
return 0.0
|
||||
|
||||
func get_parent_or_fallback_size(is_width: bool) -> float:
|
||||
var parent_container = get_parent()
|
||||
if parent_container and (parent_container.size.x if is_width else parent_container.size.y) > 0:
|
||||
return parent_container.size.x if is_width else parent_container.size.y
|
||||
elif (size.x if is_width else size.y) > 0:
|
||||
return size.x if is_width else size.y
|
||||
else:
|
||||
var fallback = custom_minimum_size.x if is_width else custom_minimum_size.y
|
||||
return fallback if fallback > 0 else NAN
|
||||
1
flumi/Scripts/Engine/AutoSizingFlexContainer.gd.uid
Normal file
1
flumi/Scripts/Engine/AutoSizingFlexContainer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://feiw2baeu5ye
|
||||
94
flumi/Scripts/Engine/FontManager.gd
Normal file
94
flumi/Scripts/Engine/FontManager.gd
Normal file
@@ -0,0 +1,94 @@
|
||||
class_name FontManager
|
||||
extends RefCounted
|
||||
|
||||
static var loaded_fonts: Dictionary = {}
|
||||
static var font_requests: Array = []
|
||||
static var refresh_callback: Callable
|
||||
|
||||
static func register_font(name: String, src: String, weight: String = "400") -> void:
|
||||
var font_info = {
|
||||
"name": name,
|
||||
"src": src,
|
||||
"weight": weight,
|
||||
"font_resource": null
|
||||
}
|
||||
font_requests.append(font_info)
|
||||
|
||||
static func load_all_fonts() -> void:
|
||||
if font_requests.size() == 0:
|
||||
return
|
||||
|
||||
for font_info in font_requests:
|
||||
load_font(font_info)
|
||||
|
||||
|
||||
static func load_font(font_info: Dictionary) -> void:
|
||||
var src = font_info["src"]
|
||||
|
||||
if src.begins_with("http://") or src.begins_with("https://"):
|
||||
load_web_font(font_info)
|
||||
|
||||
static func load_web_font(font_info: Dictionary) -> void:
|
||||
var src = font_info["src"]
|
||||
var name = font_info["name"]
|
||||
|
||||
var http_request = HTTPRequest.new()
|
||||
var temp_parent = Node.new()
|
||||
Engine.get_main_loop().root.add_child(temp_parent)
|
||||
temp_parent.add_child(http_request)
|
||||
|
||||
http_request.timeout = 30.0
|
||||
|
||||
http_request.request_completed.connect(func(_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray):
|
||||
if response_code == 200:
|
||||
|
||||
if body.size() > 0:
|
||||
var font = FontFile.new()
|
||||
font.data = body
|
||||
font_info["font_resource"] = font
|
||||
loaded_fonts[name] = font
|
||||
|
||||
# Trigger font refresh if callback is available
|
||||
if refresh_callback.is_valid():
|
||||
refresh_callback.call(name)
|
||||
else:
|
||||
print("FontManager: Empty font data received for ", name)
|
||||
else:
|
||||
print("FontManager: Failed to load font ", name, " - HTTP ", response_code)
|
||||
|
||||
if is_instance_valid(temp_parent):
|
||||
temp_parent.queue_free()
|
||||
)
|
||||
|
||||
var headers = PackedStringArray()
|
||||
headers.append("User-Agent: " + UserAgent.get_user_agent())
|
||||
|
||||
http_request.request(src, headers)
|
||||
|
||||
static func get_font(family_name: String) -> Font:
|
||||
if family_name == "sans-serif":
|
||||
var sys_font = SystemFont.new()
|
||||
sys_font.font_names = ["sans-serif"]
|
||||
return sys_font
|
||||
elif family_name == "serif":
|
||||
var sys_font = SystemFont.new()
|
||||
sys_font.font_names = ["serif"]
|
||||
return sys_font
|
||||
elif family_name == "monospace":
|
||||
var sys_font = SystemFont.new()
|
||||
sys_font.font_names = ["Consolas", "monospace"]
|
||||
return sys_font
|
||||
elif loaded_fonts.has(family_name):
|
||||
return loaded_fonts[family_name]
|
||||
else:
|
||||
# Fallback to system font
|
||||
var sys_font = SystemFont.new()
|
||||
sys_font.font_names = [family_name]
|
||||
return sys_font
|
||||
|
||||
static func clear_fonts() -> void:
|
||||
loaded_fonts.clear()
|
||||
font_requests.clear()
|
||||
|
||||
static func set_refresh_callback(callback: Callable) -> void:
|
||||
refresh_callback = callback
|
||||
1
flumi/Scripts/Engine/FontManager.gd.uid
Normal file
1
flumi/Scripts/Engine/FontManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c0kg201cqluo8
|
||||
68
flumi/Scripts/Engine/MaxSizeControl.gd
Normal file
68
flumi/Scripts/Engine/MaxSizeControl.gd
Normal file
@@ -0,0 +1,68 @@
|
||||
@tool
|
||||
class_name MaxSizeControl
|
||||
extends Control
|
||||
|
||||
@export var max_size: Vector2 = Vector2(-1, -1):
|
||||
set(value):
|
||||
max_size = value
|
||||
_enforce_size_limits()
|
||||
|
||||
var content_node: Control
|
||||
|
||||
func _ready():
|
||||
# Auto-detect content node
|
||||
if get_child_count() > 0:
|
||||
setup_content_node(get_child(0))
|
||||
|
||||
# Connect to our own resize
|
||||
resized.connect(_on_resized)
|
||||
|
||||
func setup_content_node(node: Control):
|
||||
content_node = node
|
||||
if content_node:
|
||||
# Make content fill the container initially
|
||||
content_node.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
|
||||
# Connect to content changes
|
||||
if not content_node.minimum_size_changed.is_connected(_on_content_changed):
|
||||
content_node.minimum_size_changed.connect(_on_content_changed)
|
||||
|
||||
_enforce_size_limits()
|
||||
|
||||
func _on_content_changed():
|
||||
_enforce_size_limits()
|
||||
|
||||
func _on_resized():
|
||||
_enforce_size_limits()
|
||||
|
||||
func _enforce_size_limits():
|
||||
if not content_node:
|
||||
return
|
||||
|
||||
var target_width = max_size.x if max_size.x > 0 else content_node.get_combined_minimum_size().x
|
||||
var target_height = max_size.y if max_size.y > 0 else content_node.get_combined_minimum_size().y
|
||||
|
||||
custom_minimum_size = Vector2(target_width, target_height)
|
||||
|
||||
# Set children's minimum size to match the constrained size
|
||||
for child in get_children():
|
||||
if child is Control:
|
||||
child.custom_minimum_size = Vector2(target_width, target_height)
|
||||
|
||||
# Force the content to fit within our bounds and enable clipping
|
||||
content_node.size = Vector2(target_width, target_height)
|
||||
content_node.position = Vector2.ZERO
|
||||
|
||||
# Always enable clipping if max_size is set
|
||||
var needs_clipping = max_size.x > 0 or max_size.y > 0
|
||||
content_node.clip_contents = needs_clipping
|
||||
clip_contents = true
|
||||
|
||||
func _get_minimum_size() -> Vector2:
|
||||
# Only use max_size, ignore content's natural size
|
||||
var final_size = Vector2.ZERO
|
||||
if max_size.x > 0:
|
||||
final_size.x = max_size.x
|
||||
if max_size.y > 0:
|
||||
final_size.y = max_size.y
|
||||
return final_size
|
||||
1
flumi/Scripts/Engine/MaxSizeControl.gd.uid
Normal file
1
flumi/Scripts/Engine/MaxSizeControl.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cmxmcn3ghw8t2
|
||||
64
flumi/Scripts/Engine/ResizableTextEdit.gd
Normal file
64
flumi/Scripts/Engine/ResizableTextEdit.gd
Normal file
@@ -0,0 +1,64 @@
|
||||
extends TextEdit
|
||||
|
||||
@onready var resize_handle = TextureRect.new()
|
||||
var is_resizing = false
|
||||
var resize_start_pos = Vector2()
|
||||
var original_size = Vector2()
|
||||
|
||||
var min_size = Vector2(100, 50)
|
||||
|
||||
func _ready():
|
||||
# Create resize handle as TextureRect child of TextEdit
|
||||
resize_handle.texture = load("res://Assets/Icons/resize-handle.svg")
|
||||
resize_handle.size = Vector2(32, 32)
|
||||
resize_handle.mouse_default_cursor_shape = Control.CURSOR_FDIAGSIZE
|
||||
resize_handle.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
add_child(resize_handle)
|
||||
|
||||
# Position handle in bottom-right corner
|
||||
_update_handle_position()
|
||||
|
||||
# Connect signals
|
||||
resize_handle.gui_input.connect(_on_resize_handle_input)
|
||||
resized.connect(_update_handle_position)
|
||||
|
||||
func _gui_input(event):
|
||||
if event is InputEventMouseButton and get_global_rect().has_point(get_viewport().get_mouse_position()):
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
set_v_scroll(get_v_scroll() - 2)
|
||||
accept_event()
|
||||
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
set_v_scroll(get_v_scroll() + 2)
|
||||
accept_event()
|
||||
|
||||
func _update_handle_position():
|
||||
if resize_handle:
|
||||
resize_handle.position = Vector2(size.x - 32, size.y - 32)
|
||||
|
||||
func _on_resize_handle_input(event):
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
is_resizing = true
|
||||
resize_start_pos = event.global_position
|
||||
original_size = size
|
||||
else:
|
||||
is_resizing = false
|
||||
|
||||
elif event is InputEventMouseMotion and is_resizing:
|
||||
var delta = event.global_position - resize_start_pos
|
||||
var new_size = original_size + delta
|
||||
new_size.x = max(new_size.x, min_size.x)
|
||||
new_size.y = max(new_size.y, min_size.y)
|
||||
|
||||
size = new_size
|
||||
|
||||
# Sync parent Control size
|
||||
var parent_control = get_parent() as Control
|
||||
if parent_control:
|
||||
parent_control.size = new_size
|
||||
parent_control.custom_minimum_size = new_size
|
||||
if parent_control:
|
||||
parent_control.size = new_size
|
||||
parent_control.custom_minimum_size = new_size
|
||||
|
||||
1
flumi/Scripts/Engine/ResizableTextEdit.gd.uid
Normal file
1
flumi/Scripts/Engine/ResizableTextEdit.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c5xpoyqcg1p8k
|
||||
850
flumi/Scripts/Engine/StyleManager.gd
Normal file
850
flumi/Scripts/Engine/StyleManager.gd
Normal file
@@ -0,0 +1,850 @@
|
||||
class_name StyleManager
|
||||
extends RefCounted
|
||||
|
||||
static var body_text_color: Color = Color.BLACK
|
||||
|
||||
static func parse_size(val):
|
||||
if val == null: return null
|
||||
if typeof(val) == TYPE_INT or typeof(val) == TYPE_FLOAT:
|
||||
return float(val)
|
||||
|
||||
# Handle bracketed values like [5px], [2rem], [50%]
|
||||
if val.begins_with("[") and val.ends_with("]"):
|
||||
var clean_val = val.replace("[", "").replace("]", "")
|
||||
if clean_val.ends_with("px"):
|
||||
return float(clean_val.replace("px", ""))
|
||||
elif clean_val.ends_with("rem"):
|
||||
return float(clean_val.replace("rem", "")) * 16.0
|
||||
elif clean_val.ends_with("%"):
|
||||
return clean_val
|
||||
else:
|
||||
return float(clean_val)
|
||||
|
||||
if val.ends_with("px"):
|
||||
return float(val.replace("px", ""))
|
||||
if val.ends_with("rem"):
|
||||
return float(val.replace("rem", "")) * 16.0
|
||||
if val.ends_with("%"):
|
||||
return val
|
||||
return float(val)
|
||||
|
||||
static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control:
|
||||
var styles = parser.get_element_styles_with_inheritance(element, "", [])
|
||||
var label = null
|
||||
var target = null
|
||||
|
||||
if not (node is FlexContainer):
|
||||
target = node if node is RichTextLabel else node.get_node_or_null("RichTextLabel")
|
||||
label = target
|
||||
# Also check for Button nodes
|
||||
if not target and node is HTMLButton:
|
||||
var button_node = node.get_node_or_null("ButtonNode")
|
||||
if button_node:
|
||||
target = button_node
|
||||
|
||||
if element.tag_name == "input":
|
||||
apply_input_border_styles(node, styles)
|
||||
elif element.tag_name == "img":
|
||||
apply_image_styles(node, styles)
|
||||
|
||||
# Unified font applying for label and button
|
||||
if target and styles.has("font-family") and styles["font-family"] not in ["sans-serif", "serif", "monospace"]:
|
||||
var main_node = Engine.get_main_loop().current_scene
|
||||
main_node.register_font_dependent_element(target, styles, element, parser)
|
||||
|
||||
var width = null
|
||||
var height = null
|
||||
|
||||
if styles.has("width"):
|
||||
width = parse_size(styles["width"])
|
||||
if styles.has("height"):
|
||||
height = parse_size(styles["height"])
|
||||
|
||||
var skip_sizing = SizingUtils.should_skip_sizing(node, element, parser)
|
||||
|
||||
if (width != null or height != null) and not skip_sizing:
|
||||
if width != null:
|
||||
if width is String and width.ends_with("%"):
|
||||
if width == "100%":
|
||||
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
node.custom_minimum_size.x = 0
|
||||
else:
|
||||
# For other percentages, convert to viewport-relative size
|
||||
var percent = float(width.replace("%", "")) / 100.0
|
||||
var viewport_width = node.get_viewport().get_visible_rect().size.x if node.get_viewport() else 800
|
||||
node.custom_minimum_size.x = viewport_width * percent
|
||||
node.set_meta("size_flags_set_by_style_manager", true)
|
||||
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
else:
|
||||
node.custom_minimum_size.x = width
|
||||
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
if height != null:
|
||||
if height is String and height.ends_with("%"):
|
||||
if height == "100%":
|
||||
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
node.custom_minimum_size.y = 0
|
||||
else:
|
||||
# For other percentages, convert to viewport-relative size
|
||||
var percent = float(height.replace("%", "")) / 100.0
|
||||
var viewport_height = node.get_viewport().get_visible_rect().size.y if node.get_viewport() else 600
|
||||
node.custom_minimum_size.y = viewport_height * percent
|
||||
node.set_meta("size_flags_set_by_style_manager", true)
|
||||
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
else:
|
||||
node.custom_minimum_size.y = height
|
||||
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
apply_element_centering(node, styles)
|
||||
|
||||
if label and label != node:
|
||||
label.anchors_preset = Control.PRESET_FULL_RECT
|
||||
|
||||
# Apply z-index
|
||||
if styles.has("z-index"):
|
||||
node.z_index = styles["z-index"]
|
||||
|
||||
# Apply opacity
|
||||
if styles.has("opacity"):
|
||||
node.modulate.a = styles["opacity"]
|
||||
|
||||
if styles.has("display"):
|
||||
if styles["display"] == "none":
|
||||
node.visible = false
|
||||
else:
|
||||
node.visible = true
|
||||
|
||||
# Apply cursor
|
||||
if styles.has("cursor"):
|
||||
var cursor_shape = get_cursor_shape_from_type(styles["cursor"])
|
||||
node.mouse_default_cursor_shape = cursor_shape
|
||||
|
||||
# For text elements, apply cursor and handle mouse events appropriately
|
||||
if label:
|
||||
label.mouse_default_cursor_shape = cursor_shape
|
||||
|
||||
# For non-pointer cursors on RichTextLabel, disable text interaction and let parent handle cursor
|
||||
if label is RichTextLabel and cursor_shape != Control.CURSOR_POINTING_HAND:
|
||||
label.selection_enabled = false
|
||||
label.context_menu_enabled = false
|
||||
label.shortcut_keys_enabled = false
|
||||
# Let parent container handle the cursor by ignoring mouse on text element
|
||||
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
else:
|
||||
# For pointer cursors or non-RichTextLabel, ensure they can receive mouse events
|
||||
if label.mouse_filter == Control.MOUSE_FILTER_PASS:
|
||||
label.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
# Check for margins first and wrap in MarginContainer if needed
|
||||
var has_margin = styles.has("margin") or styles.has("margin-top") or styles.has("margin-right") or styles.has("margin-bottom") or styles.has("margin-left")
|
||||
node = handle_margin_wrapper(node, styles, has_margin)
|
||||
|
||||
var needs_styling = styles.has("background-color") or styles.has("border-radius") or styles.has("border-width") or styles.has("border-top-width") or styles.has("border-right-width") or styles.has("border-bottom-width") or styles.has("border-left-width") or styles.has("border-color") or styles.has("padding") or styles.has("padding-top") or styles.has("padding-right") or styles.has("padding-bottom") or styles.has("padding-left")
|
||||
|
||||
if needs_styling:
|
||||
# If node is a MarginContainer wrapper, get the actual content node for styling
|
||||
var content_node = node
|
||||
if node is MarginContainer and node.has_meta("is_margin_wrapper"):
|
||||
if node.get_child_count() > 0:
|
||||
content_node = node.get_child(0)
|
||||
|
||||
var target_node_for_bg = content_node if content_node is FlexContainer else (label if label else content_node)
|
||||
if target_node_for_bg:
|
||||
# Clear existing metadata first to ensure clean state
|
||||
clear_styling_metadata(target_node_for_bg)
|
||||
|
||||
# Set new metadata based on current styles
|
||||
set_styling_metadata(target_node_for_bg, styles)
|
||||
|
||||
if target_node_for_bg is FlexContainer:
|
||||
BackgroundUtils.update_background_panel(target_node_for_bg)
|
||||
elif target_node_for_bg is PanelContainer:
|
||||
apply_stylebox_to_panel_container(target_node_for_bg, styles)
|
||||
else:
|
||||
apply_stylebox_to_container_direct(target_node_for_bg, styles)
|
||||
|
||||
if target_node_for_bg.has_method("add_background_rect"):
|
||||
target_node_for_bg.call_deferred("add_background_rect")
|
||||
else:
|
||||
var content_node = node
|
||||
if node is MarginContainer and node.has_meta("is_margin_wrapper"):
|
||||
if node.get_child_count() > 0:
|
||||
content_node = node.get_child(0)
|
||||
|
||||
var target_node_for_bg = content_node if content_node is FlexContainer else (label if label else content_node)
|
||||
if target_node_for_bg:
|
||||
clear_styling_metadata(target_node_for_bg)
|
||||
|
||||
if target_node_for_bg is FlexContainer:
|
||||
BackgroundUtils.update_background_panel(target_node_for_bg)
|
||||
elif target_node_for_bg is PanelContainer:
|
||||
target_node_for_bg.remove_theme_stylebox_override("panel")
|
||||
else:
|
||||
target_node_for_bg.remove_theme_stylebox_override("panel")
|
||||
target_node_for_bg.remove_theme_stylebox_override("background")
|
||||
|
||||
if label:
|
||||
apply_styles_to_label(label, styles, element, parser)
|
||||
|
||||
var transform_target = node
|
||||
|
||||
if node is MarginContainer and node.name.begins_with("MarginWrapper_"):
|
||||
if node.get_child_count() > 0:
|
||||
transform_target = node.get_child(0)
|
||||
|
||||
apply_transform_properties(transform_target, styles)
|
||||
|
||||
|
||||
return node
|
||||
|
||||
static func apply_stylebox_to_panel_container(panel_container: PanelContainer, styles: Dictionary) -> void:
|
||||
var has_visual_styles = BackgroundUtils.needs_background_wrapper(styles)
|
||||
|
||||
if has_visual_styles:
|
||||
var style_box = BackgroundUtils.create_stylebox_from_styles(styles, panel_container)
|
||||
panel_container.add_theme_stylebox_override("panel", style_box)
|
||||
else:
|
||||
panel_container.remove_theme_stylebox_override("panel")
|
||||
clear_styling_metadata(panel_container)
|
||||
|
||||
static func apply_stylebox_to_container_direct(container: Control, styles: Dictionary) -> void:
|
||||
var has_visual_styles = BackgroundUtils.needs_background_wrapper(styles)
|
||||
|
||||
if has_visual_styles:
|
||||
var style_box = BackgroundUtils.create_stylebox_from_styles(styles, container)
|
||||
|
||||
container.add_theme_stylebox_override("panel", style_box)
|
||||
container.add_theme_stylebox_override("background", style_box)
|
||||
else:
|
||||
container.remove_theme_stylebox_override("panel")
|
||||
container.remove_theme_stylebox_override("background")
|
||||
clear_styling_metadata(container)
|
||||
|
||||
static func set_styling_metadata(node: Control, styles: Dictionary) -> void:
|
||||
# Basic styling properties
|
||||
var basic_properties = [
|
||||
["background-color", "custom_css_background_color"],
|
||||
["border-radius", "custom_css_border_radius"],
|
||||
["border-width", "custom_css_border_width"],
|
||||
["border-color", "custom_css_border_color"]
|
||||
]
|
||||
|
||||
for prop in basic_properties:
|
||||
if styles.has(prop[0]):
|
||||
node.set_meta(prop[1], styles[prop[0]])
|
||||
|
||||
# Padding properties
|
||||
var padding_properties = [
|
||||
["padding", "padding"],
|
||||
["padding-top", "padding_top"],
|
||||
["padding-right", "padding_right"],
|
||||
["padding-bottom", "padding_bottom"],
|
||||
["padding-left", "padding_left"]
|
||||
]
|
||||
|
||||
for prop in padding_properties:
|
||||
if styles.has(prop[0]):
|
||||
node.set_meta(prop[1], styles[prop[0]])
|
||||
|
||||
# Individual border sides
|
||||
var border_sides = ["top", "right", "bottom", "left"]
|
||||
for side in border_sides:
|
||||
var width_key = "border-" + side + "-width"
|
||||
if styles.has(width_key):
|
||||
node.set_meta("custom_css_" + width_key.replace("-", "_"), styles[width_key])
|
||||
|
||||
static func clear_styling_metadata(node: Control) -> void:
|
||||
var metadata_keys = [
|
||||
"custom_css_background_color",
|
||||
"custom_css_border_radius",
|
||||
"custom_css_border_width",
|
||||
"custom_css_border_color",
|
||||
"padding",
|
||||
"padding_top",
|
||||
"padding_right",
|
||||
"padding_bottom",
|
||||
"padding_left"
|
||||
]
|
||||
|
||||
for key in metadata_keys:
|
||||
if node.has_meta(key):
|
||||
node.remove_meta(key)
|
||||
|
||||
static func handle_margin_wrapper(node: Control, styles: Dictionary, needs_margin: bool):
|
||||
var current_wrapper = null
|
||||
|
||||
if node is MarginContainer and node.has_meta("is_margin_wrapper"):
|
||||
current_wrapper = node
|
||||
|
||||
elif node.get_parent() and node.get_parent() is MarginContainer:
|
||||
var parent = node.get_parent()
|
||||
if parent.has_meta("is_margin_wrapper"):
|
||||
current_wrapper = parent
|
||||
|
||||
if needs_margin:
|
||||
if current_wrapper:
|
||||
update_margin_wrapper(current_wrapper, styles)
|
||||
return current_wrapper
|
||||
else:
|
||||
return apply_margin_wrapper(node, styles)
|
||||
else:
|
||||
if current_wrapper:
|
||||
if current_wrapper == node:
|
||||
if node.get_child_count() > 0:
|
||||
var content_node = node.get_child(0)
|
||||
return remove_margin_wrapper(current_wrapper, content_node)
|
||||
else:
|
||||
return remove_margin_wrapper(current_wrapper, node)
|
||||
else:
|
||||
return node
|
||||
|
||||
static func update_margin_wrapper(margin_container: MarginContainer, styles: Dictionary) -> void:
|
||||
clear_margin_overrides(margin_container)
|
||||
apply_margin_styles_to_container(margin_container, styles)
|
||||
|
||||
static func remove_margin_wrapper(margin_container: MarginContainer, original_node: Control) -> Control:
|
||||
var original_parent = margin_container.get_parent()
|
||||
var node_index = margin_container.get_index()
|
||||
|
||||
original_node.size_flags_horizontal = margin_container.size_flags_horizontal
|
||||
original_node.size_flags_vertical = margin_container.size_flags_vertical
|
||||
|
||||
margin_container.remove_child(original_node)
|
||||
|
||||
if original_parent:
|
||||
original_parent.remove_child(margin_container)
|
||||
original_parent.add_child(original_node)
|
||||
original_parent.move_child(original_node, node_index)
|
||||
|
||||
margin_container.queue_free()
|
||||
|
||||
return original_node
|
||||
|
||||
static func apply_margin_wrapper(node: Control, styles: Dictionary) -> Control:
|
||||
var margin_container = MarginContainer.new()
|
||||
margin_container.name = "MarginWrapper_" + node.name
|
||||
margin_container.set_meta("is_margin_wrapper", true)
|
||||
|
||||
var needs_fill = false
|
||||
|
||||
if node.has_meta("should_fill_horizontal"):
|
||||
needs_fill = true
|
||||
|
||||
if node.get_child_count() > 0:
|
||||
var vbox = node.get_child(0)
|
||||
if vbox is VBoxContainer and vbox.get_child_count() > 0:
|
||||
var flex_child = vbox.get_child(0)
|
||||
if flex_child and flex_child.has_meta("should_fill_horizontal"):
|
||||
needs_fill = true
|
||||
|
||||
if needs_fill:
|
||||
margin_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
|
||||
var has_explicit_width = styles.has("width")
|
||||
var has_explicit_height = styles.has("height")
|
||||
|
||||
if has_explicit_width and not needs_fill:
|
||||
margin_container.size_flags_horizontal = node.size_flags_horizontal
|
||||
elif not needs_fill:
|
||||
margin_container.size_flags_horizontal = node.size_flags_horizontal
|
||||
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
if has_explicit_height:
|
||||
margin_container.size_flags_vertical = node.size_flags_vertical
|
||||
else:
|
||||
margin_container.size_flags_vertical = node.size_flags_vertical
|
||||
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
|
||||
apply_margin_styles_to_container(margin_container, styles)
|
||||
|
||||
# Handle reparenting properly
|
||||
var original_parent = node.get_parent()
|
||||
if original_parent:
|
||||
var node_index = node.get_index()
|
||||
original_parent.remove_child(node)
|
||||
margin_container.add_child(node)
|
||||
original_parent.add_child(margin_container)
|
||||
original_parent.move_child(margin_container, node_index)
|
||||
else:
|
||||
margin_container.add_child(node)
|
||||
|
||||
return margin_container
|
||||
|
||||
static func clear_margin_overrides(margin_container: MarginContainer) -> void:
|
||||
margin_container.remove_theme_constant_override("margin_top")
|
||||
margin_container.remove_theme_constant_override("margin_right")
|
||||
margin_container.remove_theme_constant_override("margin_bottom")
|
||||
margin_container.remove_theme_constant_override("margin_left")
|
||||
|
||||
static func apply_margin_styles_to_container(margin_container: MarginContainer, styles: Dictionary) -> void:
|
||||
# Apply general margin first
|
||||
if styles.has("margin"):
|
||||
var general_margin = parse_size(styles["margin"])
|
||||
if general_margin != null:
|
||||
var margin_sides = ["margin_top", "margin_right", "margin_bottom", "margin_left"]
|
||||
for side in margin_sides:
|
||||
margin_container.add_theme_constant_override(side, general_margin)
|
||||
|
||||
# Apply individual margin overrides
|
||||
var margin_mappings = [
|
||||
["margin-top", "margin_top"],
|
||||
["margin-right", "margin_right"],
|
||||
["margin-bottom", "margin_bottom"],
|
||||
["margin-left", "margin_left"]
|
||||
]
|
||||
|
||||
for mapping in margin_mappings:
|
||||
var style_key = mapping[0]
|
||||
var theme_key = mapping[1]
|
||||
if styles.has(style_key):
|
||||
var margin_val = parse_size(styles[style_key])
|
||||
if margin_val != null:
|
||||
margin_container.add_theme_constant_override(theme_key, margin_val)
|
||||
|
||||
static func apply_styles_to_label(label: Control, styles: Dictionary, element: HTMLParser.HTMLElement, parser, text_override: String = "") -> void:
|
||||
if label is Button:
|
||||
apply_font_to_button(label, styles)
|
||||
return
|
||||
|
||||
if not label is RichTextLabel:
|
||||
return
|
||||
|
||||
var text = text_override if text_override != "" else (element.get_preserved_text() if element.tag_name == "pre" else element.get_bbcode_formatted_text(parser))
|
||||
|
||||
var font_size = 24 # default
|
||||
|
||||
if styles.has("font-family"):
|
||||
var font_family = styles["font-family"]
|
||||
var font_resource = FontManager.get_font(font_family)
|
||||
|
||||
# set a sans-serif fallback first
|
||||
if font_family not in ["sans-serif", "serif", "monospace"]:
|
||||
if not FontManager.loaded_fonts.has(font_family):
|
||||
# Font not loaded yet, use sans-serif as fallback
|
||||
var fallback_font = FontManager.get_font("sans-serif")
|
||||
apply_font_to_label(label, fallback_font, styles)
|
||||
|
||||
if font_resource:
|
||||
apply_font_to_label(label, font_resource, styles)
|
||||
else:
|
||||
# No custom font family, but check if we need to apply font weight
|
||||
if styles.has("font-thin") or styles.has("font-extralight") or styles.has("font-light") or styles.has("font-normal") or styles.has("font-medium") or styles.has("font-semibold") or styles.has("font-extrabold") or styles.has("font-black"):
|
||||
var default_font = FontManager.get_font("sans-serif")
|
||||
apply_font_to_label(label, default_font, styles)
|
||||
|
||||
# Apply font size
|
||||
if styles.has("font-size"):
|
||||
font_size = int(styles["font-size"])
|
||||
|
||||
label.add_theme_font_size_override("normal_font_size", font_size)
|
||||
label.add_theme_font_size_override("bold_font_size", font_size)
|
||||
label.add_theme_font_size_override("italics_font_size", font_size)
|
||||
label.add_theme_font_size_override("bold_italics_font_size", font_size)
|
||||
label.add_theme_font_size_override("mono_font_size", font_size)
|
||||
|
||||
var has_existing_bbcode = text.contains("[url=") or text.contains("[color=")
|
||||
|
||||
# Apply color
|
||||
var color_tag = ""
|
||||
if not has_existing_bbcode and styles.has("color"):
|
||||
var color = styles["color"] as Color
|
||||
if color == Color.BLACK and StyleManager.body_text_color != Color.BLACK:
|
||||
color = StyleManager.body_text_color
|
||||
color_tag = "[color=#%s]" % color.to_html(false)
|
||||
elif not has_existing_bbcode and StyleManager.body_text_color != Color.BLACK:
|
||||
color_tag = "[color=#%s]" % StyleManager.body_text_color.to_html(false)
|
||||
|
||||
# Apply text styling (but not for text with existing BBCode)
|
||||
var bold_open = ""
|
||||
var bold_close = ""
|
||||
if not has_existing_bbcode and styles.has("font-bold") and styles["font-bold"]:
|
||||
bold_open = "[b]"
|
||||
bold_close = "[/b]"
|
||||
|
||||
var italic_open = ""
|
||||
var italic_close = ""
|
||||
if not has_existing_bbcode and styles.has("font-italic") and styles["font-italic"]:
|
||||
italic_open = "[i]"
|
||||
italic_close = "[/i]"
|
||||
|
||||
var underline_open = ""
|
||||
var underline_close = ""
|
||||
if not has_existing_bbcode and styles.has("underline") and styles["underline"]:
|
||||
underline_open = "[u]"
|
||||
underline_close = "[/u]"
|
||||
# Apply monospace font
|
||||
var mono_open = ""
|
||||
var mono_close = ""
|
||||
if styles.has("font-mono") and styles["font-mono"]:
|
||||
# If font-family is already monospace, just use BBCode for styling
|
||||
if not (styles.has("font-family") and styles["font-family"] == "monospace"):
|
||||
mono_open = "[code]"
|
||||
mono_close = "[/code]"
|
||||
if styles.has("text-align"):
|
||||
match styles["text-align"]:
|
||||
"left":
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
"center":
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
"right":
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
"justify":
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_FILL
|
||||
# Construct final text
|
||||
|
||||
var styled_text = "%s%s%s%s%s%s%s%s%s%s%s" % [
|
||||
color_tag,
|
||||
bold_open,
|
||||
italic_open,
|
||||
underline_open,
|
||||
mono_open,
|
||||
text,
|
||||
mono_close,
|
||||
underline_close,
|
||||
italic_close,
|
||||
bold_close,
|
||||
"[/color]" if color_tag.length() > 0 else "",
|
||||
]
|
||||
|
||||
label.text = styled_text
|
||||
|
||||
static func apply_body_styles(body: HTMLParser.HTMLElement, parser: HTMLParser, website_container: Control, website_background: Control) -> void:
|
||||
var styles = parser.get_element_styles_with_inheritance(body, "", [])
|
||||
|
||||
# Apply background color
|
||||
if styles.has("background-color"):
|
||||
var style_box = StyleBoxFlat.new()
|
||||
style_box.bg_color = styles["background-color"] as Color
|
||||
website_background.add_theme_stylebox_override("panel", style_box)
|
||||
|
||||
if styles.has("color"):
|
||||
StyleManager.body_text_color = styles["color"]
|
||||
# Apply padding
|
||||
var has_padding = styles.has("padding") or styles.has("padding-top") or styles.has("padding-right") or styles.has("padding-bottom") or styles.has("padding-left")
|
||||
|
||||
if has_padding:
|
||||
var margin_container = MarginContainer.new()
|
||||
margin_container.name = "BodyMarginContainer"
|
||||
margin_container.size_flags_horizontal = website_container.size_flags_horizontal
|
||||
margin_container.size_flags_vertical = website_container.size_flags_vertical
|
||||
|
||||
# ScrollContainer
|
||||
# |__ BodyMarginContainer
|
||||
# |__ WebsiteContainer
|
||||
var original_parent = website_container.get_parent()
|
||||
var container_index = website_container.get_index()
|
||||
|
||||
original_parent.remove_child(website_container)
|
||||
original_parent.add_child(margin_container)
|
||||
original_parent.move_child(margin_container, container_index)
|
||||
margin_container.add_child(website_container)
|
||||
|
||||
var padding_val = parse_size(styles["padding"] if styles.has("padding") else 0)
|
||||
|
||||
margin_container.add_theme_constant_override("margin_left", padding_val)
|
||||
margin_container.add_theme_constant_override("margin_right", padding_val)
|
||||
margin_container.add_theme_constant_override("margin_top", padding_val)
|
||||
margin_container.add_theme_constant_override("margin_bottom", padding_val)
|
||||
|
||||
# Apply individual padding values using our helper function
|
||||
var padding_mappings = [
|
||||
["padding-top", "margin_top"],
|
||||
["padding-right", "margin_right"],
|
||||
["padding-bottom", "margin_bottom"],
|
||||
["padding-left", "margin_left"]
|
||||
]
|
||||
|
||||
for mapping in padding_mappings:
|
||||
var style_key = mapping[0]
|
||||
var margin_key = mapping[1]
|
||||
if styles.has(style_key):
|
||||
var margin_val = parse_size(styles[style_key])
|
||||
if margin_val != null:
|
||||
margin_container.add_theme_constant_override(margin_key, margin_val)
|
||||
|
||||
static func parse_radius(radius_str: String) -> int:
|
||||
return SizeUtils.parse_radius(radius_str)
|
||||
|
||||
static func apply_font_to_label(label: RichTextLabel, font_resource: Font, styles: Dictionary = {}) -> void:
|
||||
# Create normal font with appropriate weight
|
||||
var normal_font = SystemFont.new()
|
||||
normal_font.font_names = font_resource.font_names if font_resource is SystemFont else ["Arial"]
|
||||
|
||||
# Set weight based on styles
|
||||
var font_weight = 400 # Default normal weight
|
||||
if styles.has("font-thin"):
|
||||
font_weight = 100
|
||||
elif styles.has("font-extralight"):
|
||||
font_weight = 200
|
||||
elif styles.has("font-light"):
|
||||
font_weight = 300
|
||||
elif styles.has("font-normal"):
|
||||
font_weight = 400
|
||||
elif styles.has("font-medium"):
|
||||
font_weight = 500
|
||||
elif styles.has("font-semibold"):
|
||||
font_weight = 600
|
||||
elif styles.has("font-bold"):
|
||||
font_weight = 700
|
||||
elif styles.has("font-extrabold"):
|
||||
font_weight = 800
|
||||
elif styles.has("font-black"):
|
||||
font_weight = 900
|
||||
|
||||
normal_font.font_weight = font_weight
|
||||
|
||||
label.add_theme_font_override("normal_font", normal_font)
|
||||
|
||||
var bold_font = SystemFont.new()
|
||||
bold_font.font_names = font_resource.font_names if font_resource is SystemFont else ["Arial"]
|
||||
bold_font.font_weight = 700 # Bold weight
|
||||
label.add_theme_font_override("bold_font", bold_font)
|
||||
|
||||
var italic_font = SystemFont.new()
|
||||
italic_font.font_names = font_resource.font_names if font_resource is SystemFont else ["Arial"]
|
||||
italic_font.font_italic = true
|
||||
label.add_theme_font_override("italics_font", italic_font)
|
||||
|
||||
var bold_italic_font = SystemFont.new()
|
||||
bold_italic_font.font_names = font_resource.font_names if font_resource is SystemFont else ["Arial"]
|
||||
bold_italic_font.font_weight = 700 # Bold weight
|
||||
bold_italic_font.font_italic = true
|
||||
label.add_theme_font_override("bold_italics_font", bold_italic_font)
|
||||
|
||||
static func apply_font_to_button(button: Button, styles: Dictionary) -> void:
|
||||
if styles.has("font-family"):
|
||||
var font_family = styles["font-family"]
|
||||
var font_resource = FontManager.get_font(font_family)
|
||||
|
||||
# Set fallback first for FOUT prevention
|
||||
if font_family not in ["sans-serif", "serif", "monospace"]:
|
||||
if not FontManager.loaded_fonts.has(font_family):
|
||||
var fallback_font = FontManager.get_font("sans-serif")
|
||||
button.add_theme_font_override("font", fallback_font)
|
||||
|
||||
if font_resource:
|
||||
button.add_theme_font_override("font", font_resource)
|
||||
|
||||
# Apply font size
|
||||
if styles.has("font-size"):
|
||||
var font_size = int(styles["font-size"])
|
||||
button.add_theme_font_size_override("font_size", font_size)
|
||||
|
||||
static func get_cursor_shape_from_type(cursor_type: String) -> Control.CursorShape:
|
||||
match cursor_type:
|
||||
"pointer", "hand":
|
||||
return Control.CURSOR_POINTING_HAND
|
||||
"text":
|
||||
return Control.CURSOR_IBEAM
|
||||
"crosshair":
|
||||
return Control.CURSOR_CROSS
|
||||
"move":
|
||||
return Control.CURSOR_MOVE
|
||||
"not-allowed", "forbidden":
|
||||
return Control.CURSOR_FORBIDDEN
|
||||
"wait":
|
||||
return Control.CURSOR_WAIT
|
||||
"help":
|
||||
return Control.CURSOR_HELP
|
||||
"grab":
|
||||
return Control.CURSOR_DRAG
|
||||
"grabbing":
|
||||
return Control.CURSOR_CAN_DROP
|
||||
"e-resize", "ew-resize":
|
||||
return Control.CURSOR_HSIZE
|
||||
"n-resize", "ns-resize":
|
||||
return Control.CURSOR_VSIZE
|
||||
"ne-resize":
|
||||
return Control.CURSOR_BDIAGSIZE
|
||||
"nw-resize":
|
||||
return Control.CURSOR_FDIAGSIZE
|
||||
"se-resize":
|
||||
return Control.CURSOR_FDIAGSIZE
|
||||
"sw-resize":
|
||||
return Control.CURSOR_BDIAGSIZE
|
||||
"default", "auto", _:
|
||||
return Control.CURSOR_ARROW
|
||||
|
||||
static func apply_input_border_styles(input_node: Control, styles: Dictionary) -> void:
|
||||
if not BackgroundUtils.needs_background_wrapper(styles):
|
||||
return
|
||||
|
||||
# Find the appropriate input control to style
|
||||
var styleable_controls = []
|
||||
|
||||
# Get all potential input controls that support StyleBox
|
||||
var line_edit = input_node.get_node_or_null("LineEdit")
|
||||
var spinbox = input_node.get_node_or_null("SpinBox")
|
||||
var file_container = input_node.get_node_or_null("FileContainer")
|
||||
|
||||
if line_edit: styleable_controls.append(line_edit)
|
||||
if spinbox: styleable_controls.append(spinbox)
|
||||
if file_container:
|
||||
var file_button = file_container.get_node_or_null("FileButton")
|
||||
if file_button: styleable_controls.append(file_button)
|
||||
|
||||
# Apply styles using BackgroundUtils
|
||||
for control in styleable_controls:
|
||||
var style_box = BackgroundUtils.create_stylebox_from_styles(styles)
|
||||
|
||||
# Set appropriate content margins for inputs if not specified
|
||||
if not styles.has("padding") and not styles.has("padding-left"):
|
||||
style_box.content_margin_left = 5.0
|
||||
if not styles.has("padding") and not styles.has("padding-right"):
|
||||
style_box.content_margin_right = 5.0
|
||||
if not styles.has("padding") and not styles.has("padding-top"):
|
||||
style_box.content_margin_top = 2.0
|
||||
if not styles.has("padding") and not styles.has("padding-bottom"):
|
||||
style_box.content_margin_bottom = 2.0
|
||||
|
||||
# Apply the style to the appropriate states
|
||||
if control is LineEdit:
|
||||
control.add_theme_stylebox_override("normal", style_box)
|
||||
control.add_theme_stylebox_override("focus", style_box)
|
||||
elif control is SpinBox:
|
||||
control.add_theme_stylebox_override("normal", style_box)
|
||||
control.add_theme_stylebox_override("focus", style_box)
|
||||
elif control is Button:
|
||||
control.add_theme_stylebox_override("normal", style_box)
|
||||
|
||||
static func apply_image_styles(image_node: Control, styles: Dictionary) -> void:
|
||||
if not image_node is TextureRect:
|
||||
return
|
||||
|
||||
var texture_rect = image_node as TextureRect
|
||||
|
||||
if styles.has("object-fit"):
|
||||
var object_fit = styles["object-fit"]
|
||||
match object_fit:
|
||||
"none":
|
||||
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP
|
||||
"fill":
|
||||
texture_rect.stretch_mode = TextureRect.STRETCH_SCALE
|
||||
"contain":
|
||||
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT
|
||||
"cover":
|
||||
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
|
||||
|
||||
static func apply_transform_properties(node: Control, styles: Dictionary) -> void:
|
||||
if node is FlexContainer:
|
||||
var has_panel_children = false
|
||||
for child in node.get_children():
|
||||
if child is PanelContainer:
|
||||
has_panel_children = true
|
||||
break
|
||||
|
||||
if has_panel_children:
|
||||
apply_transform_properties_direct(node, styles)
|
||||
else:
|
||||
for child in node.get_children():
|
||||
if child is RichTextLabel or child is Control:
|
||||
apply_transform_properties_direct(child, styles)
|
||||
return
|
||||
apply_transform_properties_direct(node, styles)
|
||||
else:
|
||||
apply_transform_properties_direct(node, styles)
|
||||
|
||||
static func apply_transform_properties_direct(node: Control, styles: Dictionary) -> void:
|
||||
var scale_x = styles.get("scale-x", 1.0)
|
||||
var scale_y = styles.get("scale-y", 1.0)
|
||||
var rotation = styles.get("rotate", 0.0)
|
||||
|
||||
var has_transform = scale_x != 1.0 or scale_y != 1.0 or rotation != 0.0
|
||||
var duration = get_transition_duration(styles)
|
||||
|
||||
if has_transform:
|
||||
node.set_meta("css_transform_applied", true)
|
||||
|
||||
# Set pivot point to center
|
||||
node.pivot_offset = node.size / 2
|
||||
|
||||
if duration > 0:
|
||||
animate_transform(node, Vector2(scale_x, scale_y), rotation, duration)
|
||||
else:
|
||||
node.scale = Vector2(scale_x, scale_y)
|
||||
node.rotation = rotation
|
||||
await_and_restore_transform(node, Vector2(scale_x, scale_y), rotation)
|
||||
else:
|
||||
if duration > 0:
|
||||
animate_transform(node, Vector2.ONE, 0.0, duration)
|
||||
else:
|
||||
node.scale = Vector2.ONE
|
||||
node.rotation = 0.0
|
||||
if node.has_meta("css_transform_applied"):
|
||||
node.remove_meta("css_transform_applied")
|
||||
|
||||
static func get_transition_duration(styles: Dictionary) -> float:
|
||||
if styles.has("transition-transform"):
|
||||
return parse_transition_duration(styles["transition-transform"])
|
||||
elif styles.has("transition"):
|
||||
return parse_transition_duration(styles["transition"])
|
||||
return 0.0
|
||||
|
||||
static func parse_transition_duration(value: String) -> float:
|
||||
if value.ends_with("ms"):
|
||||
return float(value.replace("ms", "")) / 1000.0
|
||||
elif value.ends_with("s"):
|
||||
return float(value.replace("s", ""))
|
||||
return float(value) if value.is_valid_float() else 0.0
|
||||
|
||||
static func animate_transform(node: Control, target_scale: Vector2, target_rotation: float, duration: float) -> void:
|
||||
var tween = node.create_tween()
|
||||
tween.set_parallel(true)
|
||||
tween.tween_property(node, "scale", target_scale, duration).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
|
||||
tween.tween_property(node, "rotation", target_rotation, duration).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN_OUT)
|
||||
|
||||
static func await_and_restore_transform(node: Control, target_scale: Vector2, target_rotation: float) -> void:
|
||||
var tree = Engine.get_main_loop()
|
||||
|
||||
await tree.process_frame
|
||||
node.scale = target_scale
|
||||
node.rotation = target_rotation
|
||||
node.pivot_offset = node.size / 2
|
||||
|
||||
static func apply_flexcontainer_centering(node: Control, styles: Dictionary) -> void:
|
||||
if not node is FlexContainer:
|
||||
return
|
||||
|
||||
var should_center_h = styles.has("mx-auto") or styles.has("justify-self-center")
|
||||
var should_center_v = styles.has("my-auto") or styles.has("align-self-center")
|
||||
|
||||
if should_center_h and not node.has_meta("size_flags_horizontal_set"):
|
||||
node.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
|
||||
if should_center_v and not node.has_meta("size_flags_vertical_set"):
|
||||
node.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
|
||||
if should_center_h or should_center_v:
|
||||
node.set_meta("size_flags_set_by_style_manager", true)
|
||||
|
||||
static func apply_element_centering(node: Control, styles: Dictionary) -> void:
|
||||
var should_center_h = styles.has("mx-auto") or styles.has("justify-self-center")
|
||||
var should_center_v = styles.has("my-auto") or styles.has("align-self-center")
|
||||
|
||||
# For FlexContainers, use the existing logic with metadata checks
|
||||
if node is FlexContainer:
|
||||
if should_center_h and not node.has_meta("size_flags_horizontal_set"):
|
||||
node.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
|
||||
if should_center_v and not node.has_meta("size_flags_vertical_set"):
|
||||
node.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
|
||||
if should_center_h or should_center_v:
|
||||
node.set_meta("size_flags_set_by_style_manager", true)
|
||||
else:
|
||||
# For other controls, apply centering more directly
|
||||
if should_center_h:
|
||||
# Only apply if no explicit width was set, or if explicit sizing allows centering
|
||||
var has_explicit_width = styles.has("width")
|
||||
if not has_explicit_width or not node.has_meta("size_flags_horizontal_set"):
|
||||
node.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
|
||||
if should_center_v:
|
||||
# Only apply if no explicit height was set, or if explicit sizing allows centering
|
||||
var has_explicit_height = styles.has("height")
|
||||
if not has_explicit_height or not node.has_meta("size_flags_vertical_set"):
|
||||
node.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
|
||||
if should_center_h or should_center_v:
|
||||
node.set_meta("size_flags_set_by_style_manager", true)
|
||||
1
flumi/Scripts/Engine/StyleManager.gd.uid
Normal file
1
flumi/Scripts/Engine/StyleManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dg2rhkcabusn1
|
||||
Reference in New Issue
Block a user