forked from xiangwang25/whale-town-front-v2
178 lines
6.8 KiB
GDScript
178 lines
6.8 KiB
GDScript
class_name MallItemCard
|
|
extends Button
|
|
|
|
# ============================================================================
|
|
# MallItemCard.gd - 商城商品卡片
|
|
# ============================================================================
|
|
# 展示商品槽位、状态、价格和选中态,由 MallPanel 统一驱动。
|
|
# ============================================================================
|
|
|
|
signal item_selected(item: Dictionary)
|
|
|
|
const TEXT_COLOR: Color = Color(0.111, 0.231, 0.380)
|
|
const MUTED_COLOR: Color = Color(0.486, 0.584, 0.694)
|
|
const ACCENT_COLOR: Color = Color(0.086, 0.608, 0.922)
|
|
const LOCKED_COLOR: Color = Color(0.455, 0.510, 0.584)
|
|
const COIN_TEXTURE: Texture2D = preload("res://assets/ui/mall/branding/branding_whale_coin.png")
|
|
const CATALOG_SCRIPT: Script = preload("res://Config/MallCatalog.gd")
|
|
|
|
var itemData: Dictionary = {}
|
|
var isSelected: bool = false
|
|
|
|
var _background: PanelContainer
|
|
var _icon: TextureRect
|
|
var _nameLabel: Label
|
|
var _statusLabel: Label
|
|
var _priceLabel: Label
|
|
var _lockedOverlay: ColorRect
|
|
|
|
func _ready() -> void:
|
|
focus_mode = Control.FOCUS_NONE
|
|
mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
|
custom_minimum_size = Vector2(218, 288)
|
|
add_theme_stylebox_override("normal", StyleBoxEmpty.new())
|
|
add_theme_stylebox_override("hover", StyleBoxEmpty.new())
|
|
add_theme_stylebox_override("pressed", StyleBoxEmpty.new())
|
|
add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
|
_build_ui()
|
|
pressed.connect(_on_pressed)
|
|
|
|
func setup(data: Dictionary, selected: bool) -> void:
|
|
itemData = data.duplicate(true)
|
|
isSelected = selected
|
|
if not is_inside_tree():
|
|
return
|
|
_render()
|
|
|
|
func set_selected(selected: bool) -> void:
|
|
isSelected = selected
|
|
if is_inside_tree():
|
|
_render()
|
|
|
|
func _build_ui() -> void:
|
|
_background = PanelContainer.new()
|
|
_background.name = "background"
|
|
_background.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
_background.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(_background)
|
|
|
|
var margin := MarginContainer.new()
|
|
margin.name = "contentMargin"
|
|
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
margin.add_theme_constant_override("margin_left", 14)
|
|
margin.add_theme_constant_override("margin_top", 14)
|
|
margin.add_theme_constant_override("margin_right", 14)
|
|
margin.add_theme_constant_override("margin_bottom", 14)
|
|
margin.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(margin)
|
|
|
|
var content := VBoxContainer.new()
|
|
content.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
content.add_theme_constant_override("separation", 7)
|
|
content.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
margin.add_child(content)
|
|
|
|
_statusLabel = Label.new()
|
|
_statusLabel.custom_minimum_size = Vector2(0, 24)
|
|
_statusLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
_statusLabel.add_theme_font_size_override("font_size", 15)
|
|
_statusLabel.add_theme_color_override("font_color", MUTED_COLOR)
|
|
content.add_child(_statusLabel)
|
|
|
|
_icon = TextureRect.new()
|
|
_icon.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
|
_icon.custom_minimum_size = Vector2(152, 134)
|
|
_icon.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
|
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
content.add_child(_icon)
|
|
|
|
_nameLabel = Label.new()
|
|
_nameLabel.custom_minimum_size = Vector2(0, 48)
|
|
_nameLabel.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
_nameLabel.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
_nameLabel.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
_nameLabel.add_theme_font_size_override("font_size", 19)
|
|
_nameLabel.add_theme_color_override("font_color", TEXT_COLOR)
|
|
content.add_child(_nameLabel)
|
|
|
|
var priceRow := HBoxContainer.new()
|
|
priceRow.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
priceRow.add_theme_constant_override("separation", 4)
|
|
priceRow.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
content.add_child(priceRow)
|
|
|
|
var coin := TextureRect.new()
|
|
coin.texture = COIN_TEXTURE
|
|
coin.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
|
coin.custom_minimum_size = Vector2(28, 28)
|
|
coin.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
|
coin.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
coin.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
priceRow.add_child(coin)
|
|
|
|
_priceLabel = Label.new()
|
|
_priceLabel.add_theme_font_size_override("font_size", 18)
|
|
_priceLabel.add_theme_color_override("font_color", MUTED_COLOR)
|
|
priceRow.add_child(_priceLabel)
|
|
|
|
_lockedOverlay = ColorRect.new()
|
|
_lockedOverlay.name = "lockedOverlay"
|
|
_lockedOverlay.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
_lockedOverlay.color = Color(0.350, 0.420, 0.510, 0.18)
|
|
_lockedOverlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(_lockedOverlay)
|
|
|
|
_render()
|
|
|
|
func _render() -> void:
|
|
_background.add_theme_stylebox_override("panel", _create_card_style(isSelected))
|
|
var status := str(itemData.get("status", CATALOG_SCRIPT.STATUS_COMING_SOON))
|
|
var iconPath := str(itemData.get("icon", ""))
|
|
_icon.texture = _load_texture(iconPath)
|
|
_nameLabel.text = str(itemData.get("name", "商品槽位"))
|
|
_statusLabel.text = CATALOG_SCRIPT.get_status_label(status)
|
|
_priceLabel.text = _format_price(itemData)
|
|
_lockedOverlay.visible = status == CATALOG_SCRIPT.STATUS_COMING_SOON or status == CATALOG_SCRIPT.STATUS_LOCKED
|
|
_statusLabel.add_theme_color_override("font_color", ACCENT_COLOR if status == CATALOG_SCRIPT.STATUS_AVAILABLE else LOCKED_COLOR)
|
|
|
|
func _create_card_style(selected: bool) -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = Color(0.982, 0.995, 1.0, 1.0)
|
|
style.corner_radius_top_left = 18
|
|
style.corner_radius_top_right = 18
|
|
style.corner_radius_bottom_left = 18
|
|
style.corner_radius_bottom_right = 18
|
|
style.border_width_left = 2 if not selected else 4
|
|
style.border_width_top = 2 if not selected else 4
|
|
style.border_width_right = 2 if not selected else 4
|
|
style.border_width_bottom = 2 if not selected else 4
|
|
style.border_color = Color(0.658, 0.835, 0.965, 0.86) if not selected else Color(0.086, 0.690, 0.960, 1.0)
|
|
style.shadow_color = Color(0.086, 0.314, 0.520, 0.15 if selected else 0.08)
|
|
style.shadow_size = 14 if selected else 8
|
|
style.shadow_offset = Vector2(0, 4)
|
|
return style
|
|
|
|
func _format_price(data: Dictionary) -> String:
|
|
var status := str(data.get("status", ""))
|
|
if status == CATALOG_SCRIPT.STATUS_OWNED:
|
|
return "已购买"
|
|
if status == CATALOG_SCRIPT.STATUS_COMING_SOON or status == CATALOG_SCRIPT.STATUS_LOCKED:
|
|
return "暂未开放"
|
|
return str(int(data.get("price", 0)))
|
|
|
|
func _load_texture(path: String) -> Texture2D:
|
|
if path.is_empty():
|
|
return null
|
|
if FileAccess.file_exists("%s.import" % path):
|
|
var texture := load(path) as Texture2D
|
|
if texture != null:
|
|
return texture
|
|
var image := Image.load_from_file(ProjectSettings.globalize_path(path))
|
|
if image == null or image.is_empty():
|
|
return null
|
|
return ImageTexture.create_from_image(image)
|
|
|
|
func _on_pressed() -> void:
|
|
item_selected.emit(itemData)
|