Initial WhaleTown V2 frontend

This commit is contained in:
2026-07-19 22:42:20 +08:00
commit 435c578dde
1421 changed files with 54486 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
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)

View File

@@ -0,0 +1 @@
uid://buh0lhtug6bw8

1211
scenes/ui/mall/MallPanel.gd Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
uid://bej4ywlrtg1tb

View File

@@ -0,0 +1,13 @@
[gd_scene load_steps=2 format=4 uid="uid://whaletown_mall_panel"]
[ext_resource type="Script" path="res://scenes/ui/mall/MallPanel.gd" id="1_mall_panel"]
[node name="MallPanel" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("1_mall_panel")