From d2ea687ec35b7fff8f22fcb8be7525228da78fd9 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sun, 5 Apr 2026 10:27:45 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20star=20map=20info=20panel=20?= =?UTF-8?q?=E2=80=94=20dynamic=20height=20measurement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Panel now measures all variable content (GTTR text, adjacent systems) before calculating height. Fixes clipping on hub systems with many neighbors and long GTTR excerpts. Unified separator spacing (8px). Co-Authored-By: Claude Opus 4.6 (1M context) --- client/ui/star_map.gd | 167 ++++++++++++++++++++++++------------------ 1 file changed, 94 insertions(+), 73 deletions(-) diff --git a/client/ui/star_map.gd b/client/ui/star_map.gd index 41c7ddee5..5bfb96072 100644 --- a/client/ui/star_map.gd +++ b/client/ui/star_map.gd @@ -403,57 +403,15 @@ func _draw_info_panel(sz: Vector2) -> void: var font := get_theme_default_font() var panel_w: float = POPUP_WIDTH var pad: float = POPUP_PADDING + var sep_h: float = 8.0 # separator line + spacing var inner_w: float = panel_w - pad * 2.0 + var sep_color := Color(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b, 0.12) - # Measure GTTR text height first so we can size the panel - var gttr: String = node.get("gttr_excerpt", "") - # Strip markdown bold markers for display - gttr = gttr.replace("**", "") - var gttr_block_h: float = 0.0 - if not gttr.is_empty(): - var gttr_size: Vector2 = font.get_multiline_string_size( - gttr, HORIZONTAL_ALIGNMENT_LEFT, inner_w, POPUP_GTTR_FONT_SIZE, - POPUP_GTTR_MAX_LINES) - gttr_block_h = gttr_size.y + 6.0 - - # Fixed rows: name, id, star+hop, corridor, bodies+pop, adjacent, separator lines - var fixed_h: float = ( - POPUP_LINE_H * 2.0 # name + id - + 4.0 # separator - + POPUP_LINE_H * 3.0 # star+hop, corridor, bodies+pop - + 4.0 # separator - + gttr_block_h - + 4.0 # separator (before adjacent) - + POPUP_LINE_H # adjacent systems label - + pad * 2.0 - ) - var panel_h: float = fixed_h - - var panel_pos := Vector2(sz.x - panel_w - POPUP_MARGIN, POPUP_MARGIN) - var x: float = panel_pos.x + pad - var y: float = panel_pos.y + pad + POPUP_LINE_H # baseline offset - - # Background + border - draw_rect(Rect2(panel_pos, Vector2(panel_w, panel_h)), COLOR_INFO_BG) - draw_rect(Rect2(panel_pos, Vector2(panel_w, panel_h)), - Color(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b, 0.15), false, 1.0) - - # ── System name ────────────────────────────────────────────────────────── + # ── Prepare all text content before measuring ──────────────────────────── var sys_name: String = node.get("proper_name", "") if sys_name.is_empty(): sys_name = node.get("system_id", "Unknown") - draw_string(font, Vector2(x, y), sys_name, HORIZONTAL_ALIGNMENT_LEFT, inner_w, 15, COLOR_TEXT) - y += POPUP_LINE_H - draw_string(font, Vector2(x, y), node.get("system_id", ""), HORIZONTAL_ALIGNMENT_LEFT, inner_w, 10, COLOR_TEXT_DIM) - y += POPUP_LINE_H - - # Separator - draw_line(Vector2(x, y + 1.0), Vector2(panel_pos.x + panel_w - pad, y + 1.0), - Color(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b, 0.12), 1.0) - y += 6.0 - - # ── Stats block ────────────────────────────────────────────────────────── var star_type: String = node.get("star_type", "") var hop: int = int(node.get("hop_distance", 0)) var stat_line_1: String @@ -461,13 +419,9 @@ func _draw_info_panel(sz: Vector2) -> void: stat_line_1 = "Hop %d from Gateway" % hop else: stat_line_1 = "%s star · hop %d" % [star_type, hop] - draw_string(font, Vector2(x, y), stat_line_1, HORIZONTAL_ALIGNMENT_LEFT, inner_w, 11, COLOR_TEXT_DIM) - y += POPUP_LINE_H var sector_str: String = node.get("geographic_sector", "unknown").replace("_", " ").to_upper() var sector_color: Color = SECTOR_COLORS.get(node.get("geographic_sector", ""), COLOR_TEXT_DIM) - draw_string(font, Vector2(x, y), sector_str + " corridor", HORIZONTAL_ALIGNMENT_LEFT, inner_w, 11, sector_color) - y += POPUP_LINE_H var bodies: String = node.get("bodies", "") var population: String = node.get("population", "") @@ -479,39 +433,106 @@ func _draw_info_panel(sz: Vector2) -> void: elif not population.is_empty(): stat_line_3 = "Population: " + population else: - stat_line_3 = "%d aperture%s · %s" % [ + stat_line_3 = "%d aperture%s" % [ int(node.get("aperture_count", 0)), - "s" if int(node.get("aperture_count", 0)) != 1 else "", - node.get("gate_topology", "")] - draw_string(font, Vector2(x, y), stat_line_3, HORIZONTAL_ALIGNMENT_LEFT, inner_w, 11, COLOR_TEXT_DIM) - y += POPUP_LINE_H + "s" if int(node.get("aperture_count", 0)) != 1 else ""] - # ── GTTR excerpt ───────────────────────────────────────────────────────── - if not gttr.is_empty(): - draw_line(Vector2(x, y + 1.0), Vector2(panel_pos.x + panel_w - pad, y + 1.0), - Color(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b, 0.12), 1.0) - y += 6.0 - draw_multiline_string(font, Vector2(x, y), gttr, HORIZONTAL_ALIGNMENT_LEFT, - inner_w, POPUP_GTTR_FONT_SIZE, POPUP_GTTR_MAX_LINES, - Color(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b, 0.75)) - y += gttr_block_h + var gttr: String = node.get("gttr_excerpt", "").replace("**", "") - # ── Adjacent systems ────────────────────────────────────────────────────── - draw_line(Vector2(x, y + 1.0), Vector2(panel_pos.x + panel_w - pad, y + 1.0), - Color(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b, 0.12), 1.0) - y += 6.0 var adj: Array = node.get("adjacent_systems", []) - if adj.is_empty(): - draw_string(font, Vector2(x, y), "No gate connections", HORIZONTAL_ALIGNMENT_LEFT, inner_w, 10, COLOR_TEXT_DIM) - else: + var adj_line: String = "" + if not adj.is_empty(): var adj_names: Array = [] for neighbor_id: String in adj: var neighbor: Dictionary = _node_lookup.get(neighbor_id, {}) var n_name: String = neighbor.get("proper_name", "") adj_names.append(n_name if not n_name.is_empty() else neighbor_id) - var adj_line: String = " · ".join(adj_names) - draw_multiline_string(font, Vector2(x, y), adj_line, HORIZONTAL_ALIGNMENT_LEFT, - inner_w, 10, 2, COLOR_TEXT_DIM) + adj_line = " · ".join(adj_names) + + # ── Measure variable-height blocks ─────────────────────────────────────── + var gttr_h: float = 0.0 + if not gttr.is_empty(): + gttr_h = font.get_multiline_string_size( + gttr, HORIZONTAL_ALIGNMENT_LEFT, inner_w, + POPUP_GTTR_FONT_SIZE, POPUP_GTTR_MAX_LINES).y + + var adj_h: float = POPUP_LINE_H # minimum one line + if not adj_line.is_empty(): + adj_h = font.get_multiline_string_size( + adj_line, HORIZONTAL_ALIGNMENT_LEFT, inner_w, 10, 3).y + + # ── Calculate total panel height ───────────────────────────────────────── + var panel_h: float = pad # top padding + panel_h += POPUP_LINE_H # system name (15px font) + panel_h += POPUP_LINE_H # system id (10px font) + panel_h += sep_h # separator + panel_h += POPUP_LINE_H # star type + hop + panel_h += POPUP_LINE_H # corridor + panel_h += POPUP_LINE_H # bodies + pop + if not gttr.is_empty(): + panel_h += sep_h + gttr_h + panel_h += sep_h + adj_h # adjacent systems + panel_h += pad # bottom padding + + # ── Draw background ────────────────────────────────────────────────────── + var panel_pos := Vector2(sz.x - panel_w - POPUP_MARGIN, POPUP_MARGIN) + var x: float = panel_pos.x + pad + var x_right: float = panel_pos.x + panel_w - pad + var y: float = panel_pos.y + pad + POPUP_LINE_H # baseline offset + + draw_rect(Rect2(panel_pos, Vector2(panel_w, panel_h)), COLOR_INFO_BG) + draw_rect(Rect2(panel_pos, Vector2(panel_w, panel_h)), sep_color, false, 1.0) + + # ── Draw content ───────────────────────────────────────────────────────── + + # System name + draw_string(font, Vector2(x, y), sys_name, + HORIZONTAL_ALIGNMENT_LEFT, inner_w, 15, COLOR_TEXT) + y += POPUP_LINE_H + + # System ID + draw_string(font, Vector2(x, y), node.get("system_id", ""), + HORIZONTAL_ALIGNMENT_LEFT, inner_w, 10, COLOR_TEXT_DIM) + y += POPUP_LINE_H + + # Separator + draw_line(Vector2(x, y + 2.0), Vector2(x_right, y + 2.0), sep_color, 1.0) + y += sep_h + + # Star type + hop + draw_string(font, Vector2(x, y), stat_line_1, + HORIZONTAL_ALIGNMENT_LEFT, inner_w, 11, COLOR_TEXT_DIM) + y += POPUP_LINE_H + + # Corridor + draw_string(font, Vector2(x, y), sector_str + " corridor", + HORIZONTAL_ALIGNMENT_LEFT, inner_w, 11, sector_color) + y += POPUP_LINE_H + + # Bodies + population + draw_string(font, Vector2(x, y), stat_line_3, + HORIZONTAL_ALIGNMENT_LEFT, inner_w, 11, COLOR_TEXT_DIM) + y += POPUP_LINE_H + + # GTTR excerpt + if not gttr.is_empty(): + draw_line(Vector2(x, y + 2.0), Vector2(x_right, y + 2.0), sep_color, 1.0) + y += sep_h + draw_multiline_string(font, Vector2(x, y), gttr, + HORIZONTAL_ALIGNMENT_LEFT, inner_w, POPUP_GTTR_FONT_SIZE, + POPUP_GTTR_MAX_LINES, + Color(COLOR_TEXT.r, COLOR_TEXT.g, COLOR_TEXT.b, 0.75)) + y += gttr_h + + # Adjacent systems + draw_line(Vector2(x, y + 2.0), Vector2(x_right, y + 2.0), sep_color, 1.0) + y += sep_h + if adj_line.is_empty(): + draw_string(font, Vector2(x, y), "No gate connections", + HORIZONTAL_ALIGNMENT_LEFT, inner_w, 10, COLOR_TEXT_DIM) + else: + draw_multiline_string(font, Vector2(x, y), adj_line, + HORIZONTAL_ALIGNMENT_LEFT, inner_w, 10, 3, COLOR_TEXT_DIM) func _draw_title() -> void: