class_name AssetCard extends Button signal activated(payload: Dictionary) signal drag_started(payload: Dictionary) var _payload: Dictionary = {} var _press_position: Vector2 = Vector2.ZERO var _dragging: bool = false var _drag_threshold: float = 8.0 var _icon: TextureRect = null var _glyph: Label = null var _box: VBoxContainer = null var _label: Label = null var _description: Label = null var _status_label: Label = null func configure(payload: Dictionary, icon_texture: Texture2D = null, theme: Variant = null) -> void: _payload = payload.duplicate(true) _drag_threshold = theme.number("palette.drag_threshold", 8.0, 1.0, 128.0) if theme != null else 8.0 custom_minimum_size = Vector2(96, 108) clip_contents = true if theme != null: var size_data: Variant = theme.value("palette.card_size", [96, 94]) if size_data is Array and (size_data as Array).size() == 2: custom_minimum_size = Vector2(float(size_data[0]), maxf(108.0, float(size_data[1]))) add_theme_stylebox_override("normal", theme.stylebox("palette")) var thumbnail_size: Vector2 = _payload.get("thumbnail_size", Vector2(64, 64)) focus_mode = Control.FOCUS_ALL tooltip_text = String(_payload.get("label", "")) for child: Node in get_children(): child.queue_free() _box = VBoxContainer.new(); _box.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT); _box.mouse_filter = Control.MOUSE_FILTER_IGNORE; _box.clip_contents = true _box.alignment = BoxContainer.ALIGNMENT_CENTER; _box.add_theme_constant_override("separation", 2); add_child(_box) _icon = TextureRect.new(); _icon.custom_minimum_size = thumbnail_size; _icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE; _icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED; _icon.texture = icon_texture; _icon.visible = icon_texture != null; _icon.mouse_filter = Control.MOUSE_FILTER_IGNORE; _box.add_child(_icon) _glyph = Label.new(); _glyph.text = String(_payload.get("icon", "")); _glyph.visible = icon_texture == null; _glyph.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; _glyph.mouse_filter = Control.MOUSE_FILTER_IGNORE; _glyph.add_theme_font_size_override("font_size", 30); _box.add_child(_glyph) _label = Label.new(); _label.text = String(_payload.get("label", "")); _label.custom_minimum_size = Vector2(0, 28); _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; _label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART; _label.clip_text = true; _label.mouse_filter = Control.MOUSE_FILTER_IGNORE; _box.add_child(_label) var description: String = String(_payload.get("description", "")) var status: String = String(_payload.get("status", "")) if not status.is_empty(): _status_label = Label.new() _status_label.name = "StatusLabel" _status_label.text = status _status_label.custom_minimum_size = Vector2(0.0, 22.0) _status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _status_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _status_label.add_theme_font_size_override("font_size", 16) _status_label.add_theme_color_override("font_color", _payload.get("status_color", Color.WHITE)) _status_label.mouse_filter = Control.MOUSE_FILTER_IGNORE _box.add_child(_status_label) if not description.is_empty(): _description = Label.new() _description.text = description _description.custom_minimum_size = Vector2(0.0, 34.0) _description.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _description.vertical_alignment = VERTICAL_ALIGNMENT_TOP _description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART _description.max_lines_visible = 2 _description.add_theme_font_size_override("font_size", 14) _description.mouse_filter = Control.MOUSE_FILTER_IGNORE _box.add_child(_description) func set_preview_control(preview: Control) -> void: if preview == null or _box == null: return var preview_size: Vector2 = _icon.custom_minimum_size if _icon != null else Vector2(64.0, 64.0) if _icon != null and _icon.get_parent() == _box: _box.remove_child(_icon) _icon.queue_free() _icon = null if _glyph != null and _glyph.get_parent() == _box: _box.remove_child(_glyph) _glyph.queue_free() _glyph = null preview.custom_minimum_size = preview_size preview.size_flags_horizontal = Control.SIZE_EXPAND_FILL preview.mouse_filter = Control.MOUSE_FILTER_IGNORE _box.add_child(preview) _box.move_child(preview, 0) func set_thumbnail(texture: Texture2D) -> void: if _icon != null: _icon.texture = texture; _icon.visible = texture != null if _glyph != null and texture != null: _glyph.visible = false func thumbnail_rect() -> TextureRect: return _icon func set_selected(selected: bool, selected_surface: Color = Color("#12394A"), selected_border: Color = Color("#22C6FF")) -> void: toggle_mode = true set_pressed_no_signal(selected) if not selected: return var selected_style := StyleBoxFlat.new() selected_style.bg_color = selected_surface selected_style.border_color = selected_border selected_style.set_border_width_all(3) selected_style.set_corner_radius_all(8) add_theme_stylebox_override("normal", selected_style) add_theme_stylebox_override("pressed", selected_style) add_theme_stylebox_override("hover", selected_style) add_theme_stylebox_override("hover_pressed", selected_style) func payload() -> Dictionary: return _payload.duplicate(true) func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: if event.pressed: _press_position = event.position; _dragging = false elif _dragging: accept_event() elif event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and not _dragging: if event.position.distance_to(_press_position) >= _drag_threshold: _dragging = true; drag_started.emit(payload()); accept_event() func _pressed() -> void: if not _dragging: activated.emit(payload())