diff --git a/scripts/avatar/avatar_customization_ui.gd b/scripts/avatar/avatar_customization_ui.gd index 5cec953..f2b8769 100644 --- a/scripts/avatar/avatar_customization_ui.gd +++ b/scripts/avatar/avatar_customization_ui.gd @@ -76,6 +76,23 @@ var _preview_root: Node3D = null ## Currently selected slot id. var _selected_slot: String = "" +## Skin: a base colour picked from a swatch, plus a shade multiplier from the +## slider (0.5 = the swatch as-is, lower = darker, higher = lighter). The +## first swatch is the placeholder default yellow. +const SKIN_SWATCHES: Array[Color] = [ + Color(0.96, 0.80, 0.19), # yellow (default) + Color(0.95, 0.78, 0.66), # pale + Color(0.83, 0.62, 0.46), # tan + Color(0.55, 0.38, 0.27), # brown + Color(0.33, 0.21, 0.15), # deep brown + Color(0.62, 0.62, 0.64), # grey + Color(0.78, 0.24, 0.24), # red + Color(0.30, 0.62, 0.34), # green + Color(0.26, 0.46, 0.76), # blue + Color(0.62, 0.36, 0.72), # purple +] +var _skin_base: Color = SKIN_SWATCHES[0] + ## Turntable: the preview avatar's Y rotation (radians), whether the player is ## currently dragging it, and how long since they last touched it (drives the ## slow idle auto-spin). Starts facing the camera. @@ -289,14 +306,28 @@ func _build_ui() -> void: _item_list.add_theme_constant_override("separation", 4) item_scroll.add_child(_item_list) - # Skin tone control. + # Skin colour: a row of preset swatches + a shade slider that darkens or + # lightens the picked colour. The first swatch is the default yellow. var skin_header := Label.new() - skin_header.text = "Skin Tone" + skin_header.text = "Skin Colour" skin_header.add_theme_font_size_override("font_size", 16) controls_vbox.add_child(skin_header) + var swatch_grid := GridContainer.new() + swatch_grid.columns = 5 + swatch_grid.add_theme_constant_override("h_separation", 4) + swatch_grid.add_theme_constant_override("v_separation", 4) + controls_vbox.add_child(swatch_grid) + for c in SKIN_SWATCHES: + swatch_grid.add_child(_make_swatch(c)) + + var shade_label := Label.new() + shade_label.text = "Shade" + shade_label.add_theme_font_size_override("font_size", 13) + controls_vbox.add_child(shade_label) + _skin_tone_slider = HSlider.new() - _skin_tone_slider.name = "SkinToneSlider" + _skin_tone_slider.name = "ShadeSlider" _skin_tone_slider.min_value = 0.0 _skin_tone_slider.max_value = 1.0 _skin_tone_slider.step = 0.01 @@ -486,17 +517,44 @@ func _refresh_preview() -> void: # Skin tone # --------------------------------------------------------------------------- -func _on_skin_tone_changed(value: float) -> void: +## Build one clickable colour swatch. +func _make_swatch(color: Color) -> Button: + var b := Button.new() + b.custom_minimum_size = Vector2(40, 30) + b.tooltip_text = "#" + color.to_html(false) + for state in ["normal", "hover", "pressed", "focus", "disabled"]: + var sb := StyleBoxFlat.new() + sb.bg_color = color + sb.set_corner_radius_all(3) + if state == "hover" or state == "pressed" or state == "focus": + sb.set_border_width_all(2) + sb.border_color = Color.WHITE + b.add_theme_stylebox_override(state, sb) + b.pressed.connect(_on_skin_swatch_pressed.bind(color)) + return b + + +func _on_skin_swatch_pressed(color: Color) -> void: + _skin_base = color + _apply_skin_colour() + + +func _on_skin_tone_changed(_value: float) -> void: + _apply_skin_colour() + + +## Combine the picked swatch with the shade slider and push it to the loadout. +func _apply_skin_colour() -> void: if loadout == null: return - # Map slider 0..1 across a deep-to-pale skin range. - var t := value - loadout.skin_tone = Color( - 0.35 + 0.60 * t, # R - 0.22 + 0.55 * t, # G - 0.16 + 0.50 * t, # B - 1.0, - ) + var shade: float = _skin_tone_slider.value if _skin_tone_slider != null else 0.5 + var col := _skin_base + if shade < 0.5: + col = _skin_base.lerp(Color.BLACK, (0.5 - shade) * 1.7) + elif shade > 0.5: + col = _skin_base.lerp(Color.WHITE, (shade - 0.5) * 1.3) + col.a = 1.0 + loadout.skin_tone = col _refresh_preview() loadout_changed.emit(loadout)