refactor(ui): add stance icons to implant badge (#787)
Replace text-only stance labels with SVG icons + tint shader per D-086. Alpha-mask shader swaps color at runtime via ShaderMaterial parameter. Icons: walk, crouch, careful, sprint at 18px inside ImplantPanel row. Also fix stale UILayer/HUD test path in test_ui_framework_sprint15. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
shader_type canvas_item;
|
||||||
|
// D-086: Runtime icon tint — replaces color channel with tint_color, preserves alpha mask.
|
||||||
|
// Applied to SVG-sourced TextureRect nodes so stance/status icons can be color-coded at runtime.
|
||||||
|
// Default: #c8d0e0 (insert chrome) — identical to the SVG's authored stroke color.
|
||||||
|
uniform vec4 tint_color : source_color = vec4(0.784, 0.816, 0.878, 1.0);
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
float a = texture(TEXTURE, UV).a;
|
||||||
|
COLOR = vec4(tint_color.rgb, a * tint_color.a);
|
||||||
|
}
|
||||||
@@ -122,14 +122,14 @@ func test_minimap_placeholder_exists_in_ui_layer() -> void:
|
|||||||
assert_that(_instance.get_node_or_null("InsertOverlay/Minimap")).is_not_null()
|
assert_that(_instance.get_node_or_null("InsertOverlay/Minimap")).is_not_null()
|
||||||
|
|
||||||
|
|
||||||
func test_hud_exists_in_ui_layer() -> void:
|
func test_hud_exists_in_insert_overlay() -> void:
|
||||||
# D-049: HUD must be in UILayer.
|
# D-051: HUD moved to InsertOverlay (layer 10) for bloom treatment (#786, PR #115).
|
||||||
var scene := MAIN_SCENE
|
var scene := MAIN_SCENE
|
||||||
_instance = scene.instantiate()
|
_instance = scene.instantiate()
|
||||||
auto_free(_instance)
|
auto_free(_instance)
|
||||||
add_child(_instance)
|
add_child(_instance)
|
||||||
|
|
||||||
assert_that(_instance.get_node_or_null("UILayer/HUD")).is_not_null()
|
assert_that(_instance.get_node_or_null("InsertOverlay/HUD")).is_not_null()
|
||||||
|
|
||||||
|
|
||||||
func test_interaction_list_exists_in_insert_overlay() -> void:
|
func test_interaction_list_exists_in_insert_overlay() -> void:
|
||||||
|
|||||||
@@ -1,23 +1,33 @@
|
|||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
## D-053: Stance indicator — implant-styled badge (D-169).
|
## D-053: Stance indicator — implant-styled badge (D-169).
|
||||||
## Sprint/Walk/Careful/Crouch. Color-coded for quick read.
|
## Sprint/Walk/Careful/Crouch. Icon (D-086) + color-coded label.
|
||||||
|
|
||||||
const STANCE_COLORS := {
|
const STANCE_COLORS := {
|
||||||
"Sprint": Color("#d45d5d"), # Red — fast, loud, dangerous
|
"Sprint": Color("#d45d5d"), # Red — fast, loud, dangerous
|
||||||
"Walk": Color("#c8d0e0"), # Default — neutral white-blue
|
"Walk": Color("#c8d0e0"), # Default — neutral white-blue
|
||||||
"Careful": Color("#6bc9a6"), # Green — quiet, observant
|
"Careful": Color("#6bc9a6"), # Green — quiet, observant
|
||||||
"Crouch": Color("#e8c547"), # Amber — very quiet, slow
|
"Crouch": Color("#e8c547"), # Amber — very quiet, slow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STANCE_ICONS := {
|
||||||
|
"Sprint": "res://assets/icons/icon_stance_sprint.svg",
|
||||||
|
"Walk": "res://assets/icons/icon_stance_walk.svg",
|
||||||
|
"Careful": "res://assets/icons/icon_stance_careful.svg",
|
||||||
|
"Crouch": "res://assets/icons/icon_stance_crouch.svg",
|
||||||
|
}
|
||||||
|
|
||||||
var _current_stance: String = "Walk"
|
var _current_stance: String = "Walk"
|
||||||
var _panel: ImplantPanel
|
var _panel: ImplantPanel
|
||||||
var _row: ImplantDataRow
|
var _icon: TextureRect
|
||||||
|
var _label: Label
|
||||||
|
var _icon_mat: ShaderMaterial
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
var theme_res := load("res://ui/implant/default_implant.tres") as ImplantTheme
|
var theme_res := load("res://ui/implant/default_implant.tres") as ImplantTheme
|
||||||
|
var icon_shader := load("res://shaders/icon_tint.gdshader") as Shader
|
||||||
|
|
||||||
_panel = ImplantPanel.new()
|
_panel = ImplantPanel.new()
|
||||||
_panel.name = "StancePanel"
|
_panel.name = "StancePanel"
|
||||||
@@ -25,17 +35,53 @@ func _ready() -> void:
|
|||||||
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
add_child(_panel)
|
add_child(_panel)
|
||||||
|
|
||||||
_row = ImplantDataRow.new("Walk", STANCE_COLORS["Walk"])
|
var hbox := HBoxContainer.new()
|
||||||
_panel.add_component(_row)
|
hbox.add_theme_constant_override("separation", 4)
|
||||||
|
hbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
|
||||||
|
_icon = TextureRect.new()
|
||||||
|
_icon.expand_mode = TextureRect.EXPAND_KEEP_SIZE
|
||||||
|
_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
|
_icon.custom_minimum_size = Vector2(18.0, 18.0)
|
||||||
|
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_icon_mat = ShaderMaterial.new()
|
||||||
|
_icon_mat.shader = icon_shader
|
||||||
|
_icon_mat.set_shader_parameter("tint_color", STANCE_COLORS["Walk"])
|
||||||
|
_icon.material = _icon_mat
|
||||||
|
hbox.add_child(_icon)
|
||||||
|
|
||||||
|
_label = Label.new()
|
||||||
|
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
if theme_res:
|
||||||
|
_label.add_theme_font_size_override("font_size", theme_res.font_body)
|
||||||
|
hbox.add_child(_label)
|
||||||
|
|
||||||
|
_panel.add_component(hbox)
|
||||||
|
_apply_stance("Walk")
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_stance(stance: String) -> void:
|
||||||
|
_current_stance = stance
|
||||||
|
var color: Color = STANCE_COLORS.get(stance, Color("#c8d0e0"))
|
||||||
|
var icon_path: String = STANCE_ICONS.get(stance, "")
|
||||||
|
|
||||||
|
if _icon and not icon_path.is_empty():
|
||||||
|
var tex := load(icon_path) as Texture2D
|
||||||
|
if tex:
|
||||||
|
_icon.texture = tex
|
||||||
|
_icon_mat.set_shader_parameter("tint_color", color)
|
||||||
|
|
||||||
|
if _label:
|
||||||
|
_label.text = stance
|
||||||
|
_label.add_theme_color_override("font_color", color)
|
||||||
|
|
||||||
|
|
||||||
func update_from_state() -> void:
|
func update_from_state() -> void:
|
||||||
var stance: String = GameState.player_stance
|
var stance: String = GameState.player_stance
|
||||||
if stance == _current_stance:
|
if stance == _current_stance:
|
||||||
return
|
return
|
||||||
_current_stance = stance
|
_apply_stance(stance)
|
||||||
var color: Color = STANCE_COLORS.get(stance, Color("#c8d0e0"))
|
|
||||||
_row.set_content(stance, color)
|
|
||||||
|
|
||||||
|
|
||||||
func get_current_stance() -> String:
|
func get_current_stance() -> String:
|
||||||
|
|||||||
Reference in New Issue
Block a user