extends Button class_name LevelCard ## One map on the level select: its photograph, its name, and its mode. ## ## The cards used to be a two-stop gradient generated from `color1` and `color2` ## in the map's meta file. That tells a player which card they clicked last time ## and nothing at all about the map, which is the entire job of a level select — ## and the hover state was `use_hdr = true` on the gradient texture, which is not ## a visible change on any of them. ## ## The preview comes from assets/ui/map_previews/.png, shot by ## debug/map_preview_capture.gd from inside the map. A map with no preview falls ## back to its gradient, so nothing breaks for a map that has not been ## photographed yet. ## ## Built on Button rather than TextureButton so it gets focus, keyboard ## navigation and the theme's state machinery for free. The image sits inside the ## button's border rather than under it, so the border reads as a frame around a ## photograph instead of a rectangle behind one. const INSET := 5 var _plate: ColorRect var _label: Label var _mode_label: Label func setup(title: String, preview_path: String, gradient: Gradient, card_size: Vector2) -> void: custom_minimum_size = card_size clip_contents = true # The frame. Papaya at rest, volt on hover, and the fill stays transparent # so the photograph is what the player sees. var clear := Color(0, 0, 0, 0) add_theme_stylebox_override("normal", _frame(clear, UITheme.PAPAYA, 3)) add_theme_stylebox_override("hover", _frame(clear, UITheme.VOLT, 5)) add_theme_stylebox_override("pressed", _frame( Color(UITheme.VOLT.r, UITheme.VOLT.g, UITheme.VOLT.b, 0.25), UITheme.VOLT, 5)) add_theme_stylebox_override("focus", _frame(clear, UITheme.CYAN, 5)) add_theme_stylebox_override("disabled", _frame( Color(UITheme.INK.r, UITheme.INK.g, UITheme.INK.b, 0.7), UITheme.DEAD_EDGE, 3)) if ResourceLoader.exists(preview_path): var shot := TextureRect.new() shot.texture = load(preview_path) shot.expand_mode = TextureRect.EXPAND_IGNORE_SIZE # COVERED, not scaled: a photograph letterboxed inside a card reads as a # thumbnail in a file browser. Filling the card and cropping reads as a # poster, which is what this is. shot.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED shot.mouse_filter = Control.MOUSE_FILTER_IGNORE _fill(shot) add_child(shot) else: var grad := TextureRect.new() var tex := GradientTexture2D.new() tex.gradient = gradient tex.width = int(card_size.x) tex.height = int(card_size.y) tex.fill_to = Vector2(1, 1) grad.texture = tex grad.expand_mode = TextureRect.EXPAND_IGNORE_SIZE grad.stretch_mode = TextureRect.STRETCH_SCALE grad.mouse_filter = Control.MOUSE_FILTER_IGNORE _fill(grad) add_child(grad) # The name plate: a hard ink band across the bottom rather than a gradient # fade, because the theme is drawn and a soft fade is not. _plate = ColorRect.new() _plate.color = Color(UITheme.INK.r, UITheme.INK.g, UITheme.INK.b, 0.88) _plate.mouse_filter = Control.MOUSE_FILTER_IGNORE _plate.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_WIDE) _plate.offset_top = -62 _plate.offset_left = INSET _plate.offset_right = -INSET _plate.offset_bottom = -INSET add_child(_plate) var stack := VBoxContainer.new() stack.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) stack.add_theme_constant_override("separation", -2) stack.mouse_filter = Control.MOUSE_FILTER_IGNORE _plate.add_child(stack) _label = Label.new() _label.text = title _label.add_theme_font_size_override("font_size", 28) _label.add_theme_color_override("font_color", UITheme.PAPER) _label.add_theme_color_override("font_outline_color", UITheme.INK) _label.add_theme_constant_override("outline_size", 6) _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER stack.add_child(_label) _mode_label = Label.new() _mode_label.add_theme_font_size_override("font_size", 16) _mode_label.add_theme_color_override("font_color", UITheme.PAPER_DIM) _mode_label.add_theme_color_override("font_outline_color", UITheme.INK) _mode_label.add_theme_constant_override("outline_size", 4) _mode_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER stack.add_child(_mode_label) # The label follows the frame, same rule as every other control: hover turns # the frame volt, so the name turns volt with it. mouse_entered.connect(func(): _tint(UITheme.VOLT)) mouse_exited.connect(func(): _tint(UITheme.PAPER)) focus_entered.connect(func(): _tint(UITheme.CYAN)) focus_exited.connect(func(): _tint(UITheme.PAPER)) ## What pressing this card will start. Shown under the name so the player is ## never guessing which mode the button they are about to press launches. func set_mode_text(text: String) -> void: if _mode_label: _mode_label.text = text func _tint(c: Color) -> void: if _label: _label.add_theme_color_override("font_color", c) func _fill(c: Control) -> void: c.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) c.offset_left = INSET c.offset_top = INSET c.offset_right = -INSET c.offset_bottom = -INSET func _frame(fill: Color, border: Color, width: int) -> StyleBoxFlat: var sb := StyleBoxFlat.new() sb.bg_color = fill sb.border_color = border sb.set_border_width_all(width) sb.set_corner_radius_all(5) sb.shadow_color = Color(UITheme.INK.r, UITheme.INK.g, UITheme.INK.b, 0.6) sb.shadow_size = 4 sb.shadow_offset = Vector2(5, 5) return sb