#111/#120: the crafter and hangar panels get room to breathe

#111. Six vessels were authored as one row of 65px buttons with 105px
captions overlapping each other, so "Energy Propulser" was clipped and the
price was crushed onto a second line under it. Laid out in code now, in two
rows of three, and the price is GLYPHS rather than words - every resource
already has a mark the resource bar and the transfer list draw, so a price
reads as a number beside a symbol instead of "15 ore · 5 gas" eating the two
lines the vessel's own name needed.

#120. The hangar's icons give up height so their captions have somewhere to
go - vessel names are two and three words long and were wrapping into a box
barely tall enough for one line, so "Gas Bomber" read as "Gas". And there is
a rule between the two halves now: what is parked here and what is flying
are different questions, and with only a heading between them the grids read
as one long list.

The divider is drawn by the menu itself rather than by a separate node,
because its y comes from the same layout pass that places both grids - a
line positioned independently would drift away from the sections it divides
the first time either changed size.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Loïc Wisniewski 2026-08-23 21:59:26 +02:00
commit c5c41b85e7
2 changed files with 100 additions and 0 deletions

20
ui/HangarMenuPanel.gd Normal file
View file

@ -0,0 +1,20 @@
extends Control
# #120: the rule between the hangar's two halves.
#
# The panel itself draws it rather than a separate node, because the y comes
# from BuildController._layout_hangar() - which is also what places the two
# grids - and a divider that had to be positioned independently would drift
# away from the sections it divides the first time either grid changed size.
const RULE_COLOR: Color = Color(0.45, 0.9, 0.55)
@onready var controller: Node = get_node("../../BuildController")
func _draw() -> void:
if controller == null or controller.hangar_divider_y <= 0.0:
return
var y: float = controller.hangar_divider_y
NeonDraw.draw_glowing_line(self, Vector2(24.0, y), Vector2(size.x - 24.0, y),
RULE_COLOR, 0.5, 0.4)

80
ui/VesselPriceStrip.gd Normal file
View file

@ -0,0 +1,80 @@
extends Control
# #111: what a vessel costs, in glyphs.
#
# The craft menu used to spell prices out - "15 ore · 5 gas" - which is three
# words of chrome per button on a screen with six of them, and it left the
# vessel's own name fighting for the same two lines. Every resource already
# has a glyph the resource bar and the transfer list draw; this reuses it, so
# a price reads as a number beside a mark rather than as a sentence.
#
# Built in code from the costs it is given, so a vessel whose price changes -
# or a resource added to the catalog - needs nothing here.
const GLYPH_SIZE: float = 26.0
const GAP: float = 4.0
const PAIR_GAP: float = 14.0
const FONT_SIZE: int = 24
var _pairs: Array = []
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
func set_costs(costs: Dictionary) -> void:
for pair in _pairs:
pair.glyph.queue_free()
pair.label.queue_free()
_pairs.clear()
for resource_type in GameState.resource_types():
var amount: int = int(costs.get(resource_type, 0))
if amount <= 0:
continue
var glyph := ResourceGlyph.new()
glyph.resource_type = String(resource_type)
glyph.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(glyph)
var label := Label.new()
label.text = str(amount)
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
label.add_theme_font_size_override("font_size", FONT_SIZE)
label.add_theme_color_override("font_color", GameState.resource_color(resource_type))
label.add_theme_color_override("font_outline_color", Color(0.01, 0.01, 0.035, 0.95))
label.add_theme_constant_override("outline_size", 2)
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
add_child(label)
_pairs.append({"glyph": glyph, "label": label, "width": _text_width(label)})
_layout()
func _text_width(label: Label) -> float:
var font: Font = label.get_theme_font("font")
if font == null:
return 20.0
return font.get_string_size(label.text, HORIZONTAL_ALIGNMENT_LEFT, -1.0,
FONT_SIZE).x
func _layout() -> void:
# Centred as a block: a one-resource price and a two-resource price
# should both sit under the middle of the button they belong to.
var total: float = 0.0
for pair in _pairs:
total += GLYPH_SIZE + GAP + float(pair.width) + PAIR_GAP
total = maxf(total - PAIR_GAP, 0.0)
var x: float = (size.x - total) * 0.5
var mid: float = (size.y - GLYPH_SIZE) * 0.5
for pair in _pairs:
pair.glyph.position = Vector2(x, mid)
pair.glyph.size = Vector2(GLYPH_SIZE, GLYPH_SIZE)
x += GLYPH_SIZE + GAP
pair.label.position = Vector2(x, 0.0)
pair.label.size = Vector2(float(pair.width), size.y)
x += float(pair.width) + PAIR_GAP
func is_free() -> bool:
return _pairs.is_empty()