scale and rotate
This commit is contained in:
@@ -897,6 +897,18 @@ static func parse_utility_class_internal(rule: CSSRule, utility_name: String) ->
|
||||
"object-contain": rule.properties["object-fit"] = "contain"; return
|
||||
"object-cover": rule.properties["object-fit"] = "cover"; return
|
||||
|
||||
if utility_name.begins_with("scale-") or utility_name.begins_with("scale-x-") or utility_name.begins_with("scale-y-"):
|
||||
var transform_properties = TransformUtils.parse_scale_utility(utility_name)
|
||||
for property in transform_properties:
|
||||
rule.properties[property] = transform_properties[property]
|
||||
return
|
||||
|
||||
if utility_name.begins_with("rotate-") or utility_name.begins_with("rotate-x-") or utility_name.begins_with("rotate-y-"):
|
||||
var transform_properties = TransformUtils.parse_rotation_utility(utility_name)
|
||||
for property in transform_properties:
|
||||
rule.properties[property] = transform_properties[property]
|
||||
return
|
||||
|
||||
# Handle more utility classes as needed
|
||||
# Add more cases here for other utilities
|
||||
|
||||
|
||||
@@ -2523,6 +2523,139 @@ var HTML_CONTENT = """<head>
|
||||
</body>
|
||||
""".to_utf8_buffer()
|
||||
|
||||
# Set the active HTML content to use the tween demo
|
||||
var HTML_CONTENT_TRANSFORM_TEST = """<head>
|
||||
<title>Transform CSS Demo - Scale & Rotation</title>
|
||||
<icon src="https://picsum.photos/32/32?random=1">
|
||||
<meta name="theme-color" content="#8b5cf6">
|
||||
<meta name="description" content="Testing scale and rotation CSS utilities">
|
||||
<style>
|
||||
body { bg-[#f8fafc] p-8 }
|
||||
h1 { text-[#8b5cf6] text-4xl font-bold text-center mb-8 }
|
||||
h2 { text-[#7c3aed] text-2xl font-semibold mb-4 }
|
||||
.demo-grid { flex flex-wrap gap-6 justify-center }
|
||||
.demo-item { bg-white p-6 rounded-xl shadow-lg text-center }
|
||||
.demo-box { w-[100px] h-[100px] bg-[#3b82f6] text-white text-xl font-bold rounded-lg flex items-center justify-center mb-4 mx-auto }
|
||||
.code-block { bg-[#1e293b] text-[#e2e8f0] p-3 rounded font-mono text-sm }
|
||||
.description { text-[#64748b] text-sm mb-3 }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>🔄 Scale & Rotation CSS Demonstration</h1>
|
||||
|
||||
<div style="demo-grid">
|
||||
<!-- Normal (no transform) -->
|
||||
<div style="demo-item">
|
||||
<h2>Normal</h2>
|
||||
<div style="description">No transform applied</div>
|
||||
<div style="demo-box">Box</div>
|
||||
<div style="code-block">No transform</div>
|
||||
</div>
|
||||
|
||||
<!-- Scale 50% -->
|
||||
<div style="demo-item">
|
||||
<h2>Scale 50%</h2>
|
||||
<div style="description">Scaled to 50% size</div>
|
||||
<div style="demo-box scale-50">Box</div>
|
||||
<div style="code-block">scale-50</div>
|
||||
</div>
|
||||
|
||||
<!-- Scale 150% -->
|
||||
<div style="demo-item">
|
||||
<h2>Scale 150%</h2>
|
||||
<div style="description">Scaled to 150% size</div>
|
||||
<div style="demo-box scale-150">Box</div>
|
||||
<div style="code-block">scale-150</div>
|
||||
</div>
|
||||
|
||||
<!-- Scale X-axis only -->
|
||||
<div style="demo-item">
|
||||
<h2>Scale X 75%</h2>
|
||||
<div style="description">X-axis scaled to 75%</div>
|
||||
<div style="demo-box scale-x-75">Box</div>
|
||||
<div style="code-block">scale-x-75</div>
|
||||
</div>
|
||||
|
||||
<!-- Scale Y-axis only -->
|
||||
<div style="demo-item">
|
||||
<h2>Scale Y 125%</h2>
|
||||
<div style="description">Y-axis scaled to 125%</div>
|
||||
<div style="demo-box scale-y-125">Box</div>
|
||||
<div style="code-block">scale-y-125</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom scale with bracket notation -->
|
||||
<div style="demo-item">
|
||||
<h2>Custom Scale</h2>
|
||||
<div style="description">Custom scale value 1.7</div>
|
||||
<div style="demo-box scale-[170]">Box</div>
|
||||
<div style="code-block">scale-[170]</div>
|
||||
</div>
|
||||
|
||||
<!-- Rotate 45 degrees -->
|
||||
<div style="demo-item">
|
||||
<h2>Rotate 45°</h2>
|
||||
<div style="description">Rotated 45 degrees</div>
|
||||
<div style="demo-box rotate-45">Box</div>
|
||||
<div style="code-block">rotate-45</div>
|
||||
</div>
|
||||
|
||||
<!-- Rotate 90 degrees -->
|
||||
<div style="demo-item">
|
||||
<h2>Rotate 90°</h2>
|
||||
<div style="description">Rotated 90 degrees</div>
|
||||
<div style="demo-box rotate-90">Box</div>
|
||||
<div style="code-block">rotate-90</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom rotation with bracket notation -->
|
||||
<div style="demo-item">
|
||||
<h2>Custom Rotate</h2>
|
||||
<div style="description">Custom rotation 30deg</div>
|
||||
<div style="demo-box rotate-[30deg]">Box</div>
|
||||
<div style="code-block">rotate-[30deg]</div>
|
||||
</div>
|
||||
|
||||
<!-- Combined scale and rotation -->
|
||||
<div style="demo-item">
|
||||
<h2>Scale + Rotate</h2>
|
||||
<div style="description">Scale 125% and rotate 45°</div>
|
||||
<div style="demo-box scale-125 rotate-45">Box</div>
|
||||
<div style="code-block">scale-125 rotate-45</div>
|
||||
</div>
|
||||
|
||||
<!-- Rotation in radians -->
|
||||
<div style="demo-item">
|
||||
<h2>Radians</h2>
|
||||
<div style="description">Rotation using radians</div>
|
||||
<div style="demo-box rotate-[1.57rad]">Box</div>
|
||||
<div style="code-block">rotate-[1.57rad]</div>
|
||||
</div>
|
||||
|
||||
<!-- Hover effects with transforms -->
|
||||
<div style="demo-item">
|
||||
<h2>Hover Effect</h2>
|
||||
<div style="description">Hover to see scale effect</div>
|
||||
<div style="demo-box hover:scale-110">Box</div>
|
||||
<div style="code-block">hover:scale-110</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="bg-white p-6 rounded-xl shadow-lg mt-8 max-w-4xl mx-auto">
|
||||
<h2>📝 Transform Utility Reference</h2>
|
||||
<div style="text-[#475569]">
|
||||
<p><strong>Scale utilities:</strong> scale-{value}, scale-x-{value}, scale-y-{value}</p>
|
||||
<p><strong>Named values:</strong> 0, 50, 75, 90, 95, 100, 105, 110, 125, 150, 200</p>
|
||||
<p><strong>Custom values:</strong> scale-[{number}] where number is percentage (100 = 1.0)</p>
|
||||
<p><strong>Rotation utilities:</strong> rotate-{degrees}, rotate-x-{degrees}, rotate-y-{degrees}</p>
|
||||
<p><strong>Named degrees:</strong> 0, 1, 2, 3, 6, 12, 45, 90, 180, 270</p>
|
||||
<p><strong>Custom rotations:</strong> rotate-[{value}deg], rotate-[{value}rad], rotate-[{value}turn]</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
""".to_utf8_buffer()
|
||||
|
||||
# Set the active HTML content to use the transform demo
|
||||
func _ready():
|
||||
HTML_CONTENT = HTML_CONTENT_TWEEN
|
||||
HTML_CONTENT = HTML_CONTENT_TRANSFORM_TEST
|
||||
|
||||
@@ -181,6 +181,14 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
|
||||
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:
|
||||
@@ -651,3 +659,88 @@ static func apply_image_styles(image_node: Control, styles: Dictionary) -> void:
|
||||
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 has_transform = false
|
||||
var scale_x = 1.0
|
||||
var scale_y = 1.0
|
||||
var rotation_z = 0.0
|
||||
|
||||
# Check for scale properties
|
||||
if styles.has("scale-x"):
|
||||
scale_x = styles["scale-x"]
|
||||
has_transform = true
|
||||
if styles.has("scale-y"):
|
||||
scale_y = styles["scale-y"]
|
||||
has_transform = true
|
||||
|
||||
# Check for rotation properties (prioritize Z-axis for 2D)
|
||||
if styles.has("rotate-z"):
|
||||
rotation_z = styles["rotate-z"]
|
||||
has_transform = true
|
||||
elif styles.has("rotate-x"):
|
||||
rotation_z = styles["rotate-x"]
|
||||
has_transform = true
|
||||
elif styles.has("rotate-y"):
|
||||
rotation_z = styles["rotate-y"]
|
||||
has_transform = true
|
||||
|
||||
if has_transform:
|
||||
# Set metadata flag to tell FlexContainer to preserve transforms
|
||||
node.set_meta("css_transform_applied", true)
|
||||
node.set_meta("pending_scale", Vector2(scale_x, scale_y))
|
||||
node.set_meta("pending_rotation", rotation_z)
|
||||
|
||||
# Apply immediately
|
||||
node.scale = Vector2(scale_x, scale_y)
|
||||
node.rotation = rotation_z
|
||||
|
||||
# Reapply after FlexContainer layout using timer
|
||||
var timer = Timer.new()
|
||||
timer.wait_time = 0.1
|
||||
timer.one_shot = true
|
||||
timer.autostart = true
|
||||
var main_scene = Engine.get_main_loop().current_scene
|
||||
if main_scene:
|
||||
timer.timeout.connect(func(): _reapply_transforms(node))
|
||||
main_scene.add_child(timer)
|
||||
else:
|
||||
# Reset transforms to default when no transforms are specified
|
||||
node.scale = Vector2.ONE
|
||||
node.rotation = 0.0
|
||||
if node.has_meta("css_transform_applied"):
|
||||
node.remove_meta("css_transform_applied")
|
||||
|
||||
static func _reapply_transforms(node: Control) -> void:
|
||||
if not is_instance_valid(node):
|
||||
return
|
||||
|
||||
if node.has_meta("pending_scale"):
|
||||
node.scale = node.get_meta("pending_scale")
|
||||
|
||||
if node.has_meta("pending_rotation"):
|
||||
node.rotation = node.get_meta("pending_rotation")
|
||||
|
||||
if node.has_meta("pending_scale"):
|
||||
node.remove_meta("pending_scale")
|
||||
if node.has_meta("pending_rotation"):
|
||||
node.remove_meta("pending_rotation")
|
||||
|
||||
@@ -182,6 +182,8 @@ static func setup_panel_hover_support(panel: PanelContainer, normal_styles: Dict
|
||||
# Store references for the hover handlers
|
||||
panel.set_meta("normal_stylebox", normal_stylebox)
|
||||
panel.set_meta("hover_stylebox", hover_stylebox)
|
||||
panel.set_meta("normal_styles", normal_styles.duplicate(true))
|
||||
panel.set_meta("hover_styles", merged_hover_styles.duplicate(true))
|
||||
|
||||
# Connect mouse events
|
||||
panel.mouse_entered.connect(_on_panel_mouse_entered.bind(panel))
|
||||
@@ -191,11 +193,28 @@ static func _on_panel_mouse_entered(panel: PanelContainer):
|
||||
if panel.has_meta("hover_stylebox"):
|
||||
var hover_stylebox = panel.get_meta("hover_stylebox")
|
||||
panel.add_theme_stylebox_override("panel", hover_stylebox)
|
||||
|
||||
if panel.has_meta("hover_styles"):
|
||||
var hover_styles = panel.get_meta("hover_styles")
|
||||
var transform_target = find_transform_target_for_panel(panel)
|
||||
StyleManager.apply_transform_properties_direct(transform_target, hover_styles)
|
||||
|
||||
static func _on_panel_mouse_exited(panel: PanelContainer):
|
||||
if panel.has_meta("normal_stylebox"):
|
||||
var normal_stylebox = panel.get_meta("normal_stylebox")
|
||||
panel.add_theme_stylebox_override("panel", normal_stylebox)
|
||||
|
||||
if panel.has_meta("normal_styles"):
|
||||
var normal_styles = panel.get_meta("normal_styles")
|
||||
var transform_target = find_transform_target_for_panel(panel)
|
||||
StyleManager.apply_transform_properties_direct(transform_target, normal_styles)
|
||||
|
||||
static func find_transform_target_for_panel(panel: PanelContainer) -> Control:
|
||||
var parent = panel.get_parent()
|
||||
if parent and parent is FlexContainer:
|
||||
return parent
|
||||
|
||||
return panel
|
||||
|
||||
static func needs_background_wrapper(styles: Dictionary) -> bool:
|
||||
return styles.has("background-color") or styles.has("border-radius") or styles.has("padding") or styles.has("padding-top") or styles.has("padding-right") or styles.has("padding-bottom") or styles.has("padding-left") 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("border-style") or styles.has("border-top-color") or styles.has("border-right-color") or styles.has("border-bottom-color") or styles.has("border-left-color")
|
||||
|
||||
158
flumi/Scripts/Utils/TransformUtils.gd
Normal file
158
flumi/Scripts/Utils/TransformUtils.gd
Normal file
@@ -0,0 +1,158 @@
|
||||
class_name TransformUtils
|
||||
extends RefCounted
|
||||
|
||||
# Utility functions for parsing CSS transform values (scale and rotation)
|
||||
|
||||
# Parse scale values - returns a Vector2 with x and y scale factors
|
||||
static func parse_scale(val: String) -> Vector2:
|
||||
if val == null or val.is_empty():
|
||||
return Vector2.ONE
|
||||
|
||||
# Named scale values
|
||||
var named = {
|
||||
"0": Vector2.ZERO,
|
||||
"50": Vector2(0.5, 0.5),
|
||||
"75": Vector2(0.75, 0.75),
|
||||
"90": Vector2(0.9, 0.9),
|
||||
"95": Vector2(0.95, 0.95),
|
||||
"100": Vector2.ONE,
|
||||
"105": Vector2(1.05, 1.05),
|
||||
"110": Vector2(1.1, 1.1),
|
||||
"125": Vector2(1.25, 1.25),
|
||||
"150": Vector2(1.5, 1.5),
|
||||
"200": Vector2(2.0, 2.0)
|
||||
}
|
||||
|
||||
if named.has(val):
|
||||
return named[val]
|
||||
|
||||
# Direct numeric value (treat as percentage: 100 = 1.0, 50 = 0.5, etc.)
|
||||
if val.is_valid_int():
|
||||
var scale_factor = float(val) / 100.0
|
||||
return Vector2(scale_factor, scale_factor)
|
||||
|
||||
# Direct decimal value
|
||||
if val.is_valid_float():
|
||||
var scale_factor = float(val)
|
||||
return Vector2(scale_factor, scale_factor)
|
||||
|
||||
return Vector2.ONE
|
||||
|
||||
# Parse rotation values - returns rotation in radians
|
||||
static func parse_rotation(val: String) -> float:
|
||||
if val == null or val.is_empty():
|
||||
return 0.0
|
||||
|
||||
# Named rotation values (in degrees, converted to radians)
|
||||
var named = {
|
||||
"0": 0.0,
|
||||
"1": deg_to_rad(1.0),
|
||||
"2": deg_to_rad(2.0),
|
||||
"3": deg_to_rad(3.0),
|
||||
"6": deg_to_rad(6.0),
|
||||
"12": deg_to_rad(12.0),
|
||||
"45": deg_to_rad(45.0),
|
||||
"90": deg_to_rad(90.0),
|
||||
"180": deg_to_rad(180.0),
|
||||
"270": deg_to_rad(270.0)
|
||||
}
|
||||
|
||||
if named.has(val):
|
||||
return named[val]
|
||||
|
||||
# Handle explicit unit specifications
|
||||
if val.ends_with("deg"):
|
||||
var degrees = float(val.replace("deg", ""))
|
||||
return deg_to_rad(degrees)
|
||||
elif val.ends_with("rad"):
|
||||
return float(val.replace("rad", ""))
|
||||
elif val.ends_with("turn"):
|
||||
var turns = float(val.replace("turn", ""))
|
||||
return turns * 2.0 * PI
|
||||
|
||||
# Direct numeric value - assume degrees
|
||||
if val.is_valid_float():
|
||||
return deg_to_rad(float(val))
|
||||
|
||||
return 0.0
|
||||
|
||||
# Parse arbitrary value from brackets like [1.7] or [45deg] or [3.5rad]
|
||||
static func parse_bracket_value(bracket_content: String, value_type: String):
|
||||
if bracket_content.is_empty():
|
||||
return null
|
||||
|
||||
match value_type:
|
||||
"scale":
|
||||
return parse_scale(bracket_content)
|
||||
"rotation":
|
||||
return parse_rotation(bracket_content)
|
||||
_:
|
||||
return null
|
||||
|
||||
# Extract bracket content for transform utilities
|
||||
static func extract_bracket_content(string: String, start_idx: int) -> String:
|
||||
var open_idx = string.find("[", start_idx)
|
||||
if open_idx == -1:
|
||||
return ""
|
||||
var close_idx = string.find("]", open_idx)
|
||||
if close_idx == -1:
|
||||
return ""
|
||||
return string.substr(open_idx + 1, close_idx - open_idx - 1)
|
||||
|
||||
# Parse scale utility and return the appropriate property name and value
|
||||
static func parse_scale_utility(utility_name: String) -> Dictionary:
|
||||
var result = {}
|
||||
|
||||
if utility_name.begins_with("scale-x-"):
|
||||
var val = utility_name.substr(8) # after "scale-x-"
|
||||
if val.begins_with("[") and val.ends_with("]"):
|
||||
val = val.substr(1, val.length() - 2)
|
||||
var scale_vec = parse_scale(val)
|
||||
result["scale-x"] = scale_vec.x
|
||||
return result
|
||||
elif utility_name.begins_with("scale-y-"):
|
||||
var val = utility_name.substr(8) # after "scale-y-"
|
||||
if val.begins_with("[") and val.ends_with("]"):
|
||||
val = val.substr(1, val.length() - 2)
|
||||
var scale_vec = parse_scale(val)
|
||||
result["scale-y"] = scale_vec.y
|
||||
return result
|
||||
elif utility_name.begins_with("scale-"):
|
||||
var val = utility_name.substr(6) # after "scale-"
|
||||
if val.begins_with("[") and val.ends_with("]"):
|
||||
val = val.substr(1, val.length() - 2)
|
||||
var scale_vec = parse_scale(val)
|
||||
result["scale-x"] = scale_vec.x
|
||||
result["scale-y"] = scale_vec.y
|
||||
return result
|
||||
|
||||
return result
|
||||
|
||||
# Parse rotation utility and return the appropriate property name and value
|
||||
static func parse_rotation_utility(utility_name: String) -> Dictionary:
|
||||
var result = {}
|
||||
|
||||
if utility_name.begins_with("rotate-x-"):
|
||||
var val = utility_name.substr(9) # after "rotate-x-"
|
||||
if val.begins_with("[") and val.ends_with("]"):
|
||||
val = val.substr(1, val.length() - 2)
|
||||
var rotation = parse_rotation(val)
|
||||
result["rotate-x"] = rotation
|
||||
return result
|
||||
elif utility_name.begins_with("rotate-y-"):
|
||||
var val = utility_name.substr(9) # after "rotate-y-"
|
||||
if val.begins_with("[") and val.ends_with("]"):
|
||||
val = val.substr(1, val.length() - 2)
|
||||
var rotation = parse_rotation(val)
|
||||
result["rotate-y"] = rotation
|
||||
return result
|
||||
elif utility_name.begins_with("rotate-"):
|
||||
var val = utility_name.substr(7) # after "rotate-"
|
||||
if val.begins_with("[") and val.ends_with("]"):
|
||||
val = val.substr(1, val.length() - 2)
|
||||
var rotation = parse_rotation(val)
|
||||
result["rotate-z"] = rotation # Default rotation is around Z-axis
|
||||
return result
|
||||
|
||||
return result
|
||||
|
||||
1
flumi/Scripts/Utils/TransformUtils.gd.uid
Normal file
1
flumi/Scripts/Utils/TransformUtils.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://m8sb57jskjyp
|
||||
@@ -41,6 +41,10 @@ static func init_patterns():
|
||||
"^opacity-\\[.*\\]$", # custom opacity values
|
||||
"^z-\\[.*\\]$", # custom z-index values
|
||||
"^cursor-[a-zA-Z-]+$", # cursor types
|
||||
"^scale-(x-|y-)?\\d+$", # scale utilities like scale-100, scale-x-75, scale-y-150
|
||||
"^scale-(x-|y-)?\\[.*\\]$", # custom scale values like scale-[1.5], scale-x-[2.0]
|
||||
"^rotate-(x-|y-)?\\d+$", # rotation utilities like rotate-45, rotate-x-90, rotate-y-180
|
||||
"^rotate-(x-|y-)?\\[.*\\]$", # custom rotation values like rotate-[45deg], rotate-x-[90deg], rotate-y-[3.5rad]
|
||||
"^(hover|active):", # pseudo classes
|
||||
]
|
||||
for pattern in utility_patterns:
|
||||
|
||||
Reference in New Issue
Block a user