rimlike/scenes/ui/resume_toast.gd
megaproxy fd6f958344 sprint A cleanup: accessibility, signals, race, debris
G: large_text scales global theme font (14→20 at 1.4×) via new
GameState.get_font_scale + EventBus.settings_changed. reduce_motion
gates ResumeToast fade (HintOverlay already gated).

I: InspectTooltip long-press wired (500ms hold, 12px drift cancel,
tap-to-clear pin). Stale Phase 19 TODO replaced with accurate doc.

H: Pawn.arrived_at_destination now also emitted on
EventBus.pawn_arrived_at_destination; DirtinessSystem subscribes and
bumps indoor traffic dirt (BUMP_INDOOR_TRAFFIC = 0.2). Outdoor-tracked
bump needs Pawn.prev_tile — flagged for Phase 20.

P: CraftingProvider caches ingredient item ref on Job.ingredient_item;
JobRunner._tick_pickup validates is_instance_valid + not being_carried
before the tile scan, cancels cleanly if another pawn grabbed it.

J: rest_provider.gd deleted. Removed @onready + register call from
world.gd, ext_resource + node from world.tscn. Provider count comment
updated to 9.

M: DIRTY_THRESHOLD extracted — cleaning_provider and job_runner now
reference DirtinessSystem.DIRT_DIRTY_THRESHOLD.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:38:14 +01:00

129 lines
4.3 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name ResumeToast extends CanvasLayer
## Phase 16 — "Welcome back / Load failed" toast.
##
## Layer 22 — above Modal (20) but below LoadMenu (25).
## Listens to EventBus.load_finished.
## On ok=true: "Welcome back — away N minutes/hours" for SHOW_DURATION_SEC, then fades.
## On ok=false: "Load failed (corrupt or version mismatch)" — same fade cadence.
const SHOW_DURATION_SEC: float = 5.0
const FADE_DURATION_SEC: float = 0.8
var _root: Control = null
var _label: Label = null
var _show_timer: Timer = null
var _fade_time: float = 0.0
var _fading: bool = false
func _ready() -> void:
layer = 22
_build_ui()
_root.visible = false
EventBus.load_finished.connect(_on_load_finished)
Audit.log("resume_toast", "ResumeToast ready")
func _exit_tree() -> void:
if EventBus.load_finished.is_connected(_on_load_finished):
EventBus.load_finished.disconnect(_on_load_finished)
func _process(delta: float) -> void:
if not _fading:
return
_fade_time -= delta
if _fade_time <= 0.0:
_fading = false
_root.visible = false
_root.modulate = Color.WHITE
return
_root.modulate.a = clampf(_fade_time / FADE_DURATION_SEC, 0.0, 1.0)
# ── UI construction ───────────────────────────────────────────────────────────
func _build_ui() -> void:
# Top-center strip — sits just below the top bar.
_root = Control.new()
_root.name = "ToastRoot"
_root.set_anchors_preset(Control.PRESET_TOP_WIDE)
_root.custom_minimum_size = Vector2(0, 56)
_root.offset_top = 56 # below the 48 px top bar + a little gap
_root.offset_bottom = 112
_root.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_root)
var panel := PanelContainer.new()
panel.name = "Panel"
panel.set_anchors_preset(Control.PRESET_CENTER_TOP)
panel.custom_minimum_size = Vector2(400, 48)
panel.offset_left = -200
panel.offset_right = 200
panel.offset_top = 0
panel.offset_bottom = 48
panel.mouse_filter = Control.MOUSE_FILTER_IGNORE
_root.add_child(panel)
_label = Label.new()
_label.name = "ToastLabel"
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_label.autowrap_mode = TextServer.AUTOWRAP_OFF
panel.add_child(_label)
_show_timer = Timer.new()
_show_timer.name = "ShowTimer"
_show_timer.one_shot = true
_show_timer.wait_time = SHOW_DURATION_SEC
_show_timer.timeout.connect(_start_fade)
add_child(_show_timer)
# ── event handling ────────────────────────────────────────────────────────────
func _on_load_finished(_slot: StringName, ok: bool, real_seconds_away: int) -> void:
if ok:
_label.text = _format_welcome(real_seconds_away)
else:
_label.text = Strings.t(&"ui.load_failed")
_root.modulate = Color.WHITE
_root.visible = true
_fading = false
_show_timer.start()
Audit.log("resume_toast", "showing — ok=%s seconds_away=%d" % [ok, real_seconds_away])
func _start_fade() -> void:
if bool(GameState.settings.get("accessibility_reduce_motion", false)):
# Snap to hidden immediately — no fade animation.
_root.visible = false
_root.modulate = Color.WHITE
return
_fading = true
_fade_time = FADE_DURATION_SEC
# ── helpers ───────────────────────────────────────────────────────────────────
func _format_welcome(seconds_away: int) -> String:
var duration_str: String
if seconds_away < 120:
# Less than 2 minutes — show as "1 minute"
duration_str = Strings.t(&"ui.welcome_back_min").format({"n": 1})
elif seconds_away < 3600:
# Under an hour — show in minutes.
var mins: int = seconds_away / 60
var key: StringName = &"ui.welcome_back_min" if mins == 1 else &"ui.welcome_back_mins"
duration_str = Strings.t(key).format({"n": mins})
elif seconds_away < 7200:
# 12 hours exactly → singular.
duration_str = Strings.t(&"ui.welcome_back_hour").format({"n": 1})
else:
var hrs: int = seconds_away / 3600
var key: StringName = &"ui.welcome_back_hour" if hrs == 1 else &"ui.welcome_back_hours"
duration_str = Strings.t(key).format({"n": hrs})
return Strings.t(&"ui.welcome_back").format({"n": duration_str})