Compact chapter overview rows

This commit is contained in:
2026-06-20 00:48:30 +09:00
parent 82dc5d1f2c
commit fc8ae7a0dd
3 changed files with 239 additions and 42 deletions

View File

@@ -120,6 +120,9 @@ const LOCAL_COMMAND_TITLE_MIN_FONT_SIZE := 10
const LOCAL_COMMAND_DETAIL_FONT_SIZE := 12
const LOCAL_COMMAND_DETAIL_MIN_FONT_SIZE := 10
const BRIEFING_CAMP_THUMBNAIL_SIZE := Vector2(188, 86)
const CHAPTER_SEAL_BADGE_SIZE := Vector2(54, 54)
const CHAPTER_SCENARIO_THUMBNAIL_SIZE := Vector2(72, 50)
const CHAPTER_INFO_BADGE_SIZE := Vector2(86, 22)
const CAMP_TALK_PORTRAIT_SIZE := Vector2(58, 58)
const SHOP_MERCHANT_BADGE_SIZE := Vector2(50, 50)
const SHOP_ITEM_ICON_SIZE := Vector2(48, 48)
@@ -9870,7 +9873,7 @@ func _rebuild_chapter_menu() -> void:
child.queue_free()
var entries := campaign_state.get_chapter_progress_entries()
if chapter_status_label != null:
chapter_status_label.text = "%s 병서 · 봉인 전장 %d/%d" % [
chapter_status_label.text = "%s · 진행 %d/%d" % [
campaign_state.campaign_title,
campaign_state.completed_scenarios.size(),
campaign_state.get_scenario_count()
@@ -9882,55 +9885,231 @@ func _rebuild_chapter_menu() -> void:
chapter_list.add_child(empty_label)
return
for entry in entries:
var completed := int(entry.get("completed", 0))
var total := int(entry.get("total", 0))
var current := bool(entry.get("current", false))
var status := "현 전장" if current else ("봉인 완료" if total > 0 and completed >= total else "미개봉")
var current_title := str(entry.get("current_scenario_title", ""))
var line := "병서 %02d/%02d · %s [%s] %d/%d" % [
int(entry.get("position", 0)),
int(entry.get("chapter_count", 0)),
entry.get("title", ""),
status,
completed,
total
]
if current and not current_title.is_empty():
line += "\n현 전장: %s" % current_title
var chapter_label := Label.new()
chapter_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
chapter_label.custom_minimum_size = Vector2(560, 0)
chapter_label.text = line
_apply_label_style(chapter_label, UI_AGED_INK)
chapter_list.add_child(chapter_label)
for scenario in entry.get("scenarios", []):
_add_chapter_scenario_entry(scenario)
_add_chapter_progress_entry(entry)
if bool(entry.get("current", false)) or int(entry.get("completed", 0)) > 0:
for scenario in entry.get("scenarios", []):
_add_chapter_scenario_entry(scenario)
func _add_chapter_progress_entry(entry: Dictionary) -> void:
var tooltip_text := _format_chapter_progress_tooltip(entry)
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(580, 58)
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_theme_constant_override("separation", 8)
row.tooltip_text = tooltip_text
chapter_list.add_child(row)
row.add_child(_make_initials_badge(str(entry.get("title", "병서")), CHAPTER_SEAL_BADGE_SIZE, 16))
var column := VBoxContainer.new()
column.custom_minimum_size = Vector2(518, 0)
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
column.add_theme_constant_override("separation", 4)
column.tooltip_text = tooltip_text
row.add_child(column)
var title_label := Label.new()
title_label.custom_minimum_size = Vector2(518, 26)
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
title_label.text = "%02d/%02d %s" % [
int(entry.get("position", 0)),
int(entry.get("chapter_count", 0)),
str(entry.get("title", ""))
]
title_label.tooltip_text = tooltip_text
_apply_label_style(title_label, UI_AGED_INK, 13)
column.add_child(title_label)
var badge_row := HBoxContainer.new()
badge_row.custom_minimum_size = Vector2(518, 22)
badge_row.add_theme_constant_override("separation", 5)
badge_row.tooltip_text = tooltip_text
column.add_child(badge_row)
for badge_text in _format_chapter_progress_badges(entry):
var text := str(badge_text).strip_edges()
if text.is_empty():
continue
badge_row.add_child(_make_chapter_info_badge(text, tooltip_text))
func _add_chapter_scenario_entry(scenario: Dictionary) -> void:
var scenario_id := str(scenario.get("id", ""))
var title := str(scenario.get("title", scenario_id))
var position := int(scenario.get("position", 0))
var total := int(scenario.get("total", 0))
var completed := bool(scenario.get("completed", false))
var current := bool(scenario.get("current", false))
var available := bool(scenario.get("available", false))
var status := "현 전장" if current else ("회상 가능" if completed else "미개봉")
var text := " %02d/%02d %s [%s]" % [position, total, title, status]
var tooltip_text := _format_chapter_scenario_tooltip(scenario)
var row := HBoxContainer.new()
row.custom_minimum_size = Vector2(580, 58)
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_theme_constant_override("separation", 8)
row.tooltip_text = tooltip_text
chapter_list.add_child(row)
row.add_child(_make_chapter_scenario_thumbnail(scenario, tooltip_text))
var column := VBoxContainer.new()
column.custom_minimum_size = Vector2(500, 0)
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
column.add_theme_constant_override("separation", 4)
column.tooltip_text = tooltip_text
row.add_child(column)
var scenario_button := Button.new()
scenario_button.custom_minimum_size = Vector2(500, 28)
scenario_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scenario_button.text = title
scenario_button.tooltip_text = tooltip_text
scenario_button.disabled = not available
scenario_button.add_theme_font_size_override("font_size", 12)
_apply_button_style(scenario_button)
if available:
var scenario_button := Button.new()
scenario_button.custom_minimum_size = Vector2(560, 0)
scenario_button.text = text
_apply_button_style(scenario_button)
scenario_button.pressed.connect(_on_chapter_scenario_pressed.bind(scenario_id))
chapter_list.add_child(scenario_button)
column.add_child(scenario_button)
var badge_row := HBoxContainer.new()
badge_row.custom_minimum_size = Vector2(500, 22)
badge_row.add_theme_constant_override("separation", 5)
badge_row.tooltip_text = tooltip_text
column.add_child(badge_row)
for badge_text in _format_chapter_scenario_badges(scenario):
var text := str(badge_text).strip_edges()
if text.is_empty():
continue
badge_row.add_child(_make_chapter_info_badge(text, tooltip_text))
func _format_chapter_progress_badges(entry: Dictionary) -> Array:
var completed := int(entry.get("completed", 0))
var total := int(entry.get("total", 0))
return [
_chapter_progress_status_text(entry),
"전장 %d/%d" % [completed, total]
]
func _format_chapter_scenario_badges(scenario: Dictionary) -> Array:
return [
"%02d/%02d" % [int(scenario.get("position", 0)), int(scenario.get("total", 0))],
_chapter_scenario_status_text(scenario),
"열람" if bool(scenario.get("available", false)) else "잠김"
]
func _chapter_progress_status_text(entry: Dictionary) -> String:
var completed := int(entry.get("completed", 0))
var total := int(entry.get("total", 0))
if bool(entry.get("current", false)):
return "진행 중"
if total > 0 and completed >= total:
return "완료"
return "미개방"
func _chapter_scenario_status_text(scenario: Dictionary) -> String:
if bool(scenario.get("current", false)):
return "현 전장"
if bool(scenario.get("completed", false)):
return "회상"
return "미개방"
func _format_chapter_progress_tooltip(entry: Dictionary) -> String:
var lines := [
"병서 %02d/%02d · %s" % [
int(entry.get("position", 0)),
int(entry.get("chapter_count", 0)),
str(entry.get("title", ""))
],
"상태: %s" % _chapter_progress_status_text(entry),
"전장 진행: %d/%d" % [int(entry.get("completed", 0)), int(entry.get("total", 0))]
]
var current_title := str(entry.get("current_scenario_title", "")).strip_edges()
if bool(entry.get("current", false)) and not current_title.is_empty():
lines.append("현 전장: %s" % current_title)
return _join_strings(lines, "\n")
func _format_chapter_scenario_tooltip(scenario: Dictionary) -> String:
var title := str(scenario.get("title", scenario.get("id", "")))
var status := _chapter_scenario_status_text(scenario)
var lines := [
"%02d/%02d · %s" % [int(scenario.get("position", 0)), int(scenario.get("total", 0)), title],
"상태: %s" % status
]
if bool(scenario.get("current", false)):
lines.append("클릭하면 이 전장의 군막으로 이동합니다.")
elif bool(scenario.get("completed", false)):
lines.append("클릭하면 회상 전투를 엽니다.")
else:
var scenario_label := Label.new()
scenario_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
scenario_label.custom_minimum_size = Vector2(560, 0)
scenario_label.text = text
_apply_label_style(scenario_label, UI_AGED_INK)
chapter_list.add_child(scenario_label)
lines.append("앞 전장을 마치면 열립니다.")
return _join_strings(lines, "\n")
func _make_chapter_info_badge(text: String, tooltip_text: String) -> PanelContainer:
var panel := PanelContainer.new()
panel.custom_minimum_size = CHAPTER_INFO_BADGE_SIZE
panel.tooltip_text = tooltip_text
_apply_panel_style(panel, "caption")
var label := Label.new()
label.set_anchors_preset(Control.PRESET_FULL_RECT)
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
label.text = text
label.tooltip_text = tooltip_text
_apply_label_style(label, UI_PARCHMENT_TEXT, 10)
panel.add_child(label)
return panel
func _make_chapter_scenario_thumbnail(scenario: Dictionary, tooltip_text: String) -> PanelContainer:
var panel := PanelContainer.new()
panel.custom_minimum_size = CHAPTER_SCENARIO_THUMBNAIL_SIZE
panel.tooltip_text = tooltip_text
_apply_panel_style(panel, "map")
var stack := Control.new()
stack.custom_minimum_size = CHAPTER_SCENARIO_THUMBNAIL_SIZE
stack.tooltip_text = tooltip_text
panel.add_child(stack)
var texture_rect := TextureRect.new()
texture_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
texture_rect.modulate = Color(0.92, 0.82, 0.62, 0.96)
texture_rect.tooltip_text = tooltip_text
var texture := _load_art_texture(_chapter_scenario_background_path(str(scenario.get("id", ""))))
texture_rect.texture = texture
texture_rect.visible = texture != null
stack.add_child(texture_rect)
var fallback_label := Label.new()
fallback_label.set_anchors_preset(Control.PRESET_FULL_RECT)
fallback_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
fallback_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
fallback_label.text = _dialogue_portrait_initials(str(scenario.get("title", scenario.get("id", "")))) if texture == null else ""
fallback_label.visible = texture == null
fallback_label.tooltip_text = tooltip_text
_apply_label_style(fallback_label, UI_OLD_BRONZE, 16)
stack.add_child(fallback_label)
stack.add_child(_make_map_lattice_overlay(CHAPTER_SCENARIO_THUMBNAIL_SIZE))
return panel
func _chapter_scenario_background_path(scenario_id: String) -> String:
var scenario_path := campaign_state.get_scenario_path(scenario_id)
if scenario_path.is_empty() or not FileAccess.file_exists(scenario_path):
return ""
var parsed = JSON.parse_string(FileAccess.get_file_as_string(scenario_path))
if typeof(parsed) != TYPE_DICTIONARY:
return ""
var map_data = parsed.get("map", {})
if typeof(map_data) != TYPE_DICTIONARY:
return ""
return str(map_data.get("background", ""))
func _on_chapter_scenario_pressed(scenario_id: String) -> void: