Files
aetherbound-guild/runtime/ui/shared/market_chrome.gd
T
wushenghua db7b3d65e4 fix(m58-b): market read-surface type scale, contrast and slot ownership
Measured inventory first: the market modules authored their labels at raw token
sizes while the view uses the canvas text scale, leaving module labels near 6.5
physical pixels at 760, and the card, trait and cost surfaces were drawn in
fixed pixels against proportional label slots. Every module now takes the view's
canvas type scale; the card band and price strip are fractions of the card size
that cover their slots; the trait chip and cost strip are proportional chrome
with the coin anchored in the authored socket; ledger labels move to ink on
parchment and the ledger keeps the full availability string while the card row
carries only the amount.
2026-09-13 16:53:51 +08:00

241 lines
13 KiB
GDScript

class_name ABGMarketChrome
extends Control
## Shared Q-style market chrome. This is deliberately asset-light: the
## authored background and character sprites remain independent, while this
## control owns the reusable paper/wood/ribbon material language. Text and
## values are always rendered by the engine above it.
const INK := Color("#3b2b22")
const INK_SOFT := Color("#6d5038")
const PAPER := Color("#f7e9c7")
const PAPER_LIGHT := Color("#fff5dc")
const PAPER_SHADE := Color("#e4c990")
const WOOD := Color("#74472d")
const WOOD_LIGHT := Color("#a56c3e")
const TEAL := Color("#2f6b73")
const TEAL_LIGHT := Color("#74aeb0")
const BRASS := Color("#d9a848")
const RED := Color("#ad4d4c")
const BLUE := Color("#3d7390")
const SHADOW := Color(0.10, 0.07, 0.05, 0.34)
var variant := "card"
var accent := "blue"
func configure(next_variant: String, next_accent := "blue") -> ABGMarketChrome:
variant = next_variant
accent = next_accent
return self
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
queue_redraw()
func _draw() -> void:
if size.x < 2.0 or size.y < 2.0:
return
match variant:
"title":
_draw_title()
"resource":
_draw_resource()
"ledger":
_draw_ledger()
"wood_button":
_draw_wood_button()
"ledger_action":
_draw_ledger_action()
"ledger_cost":
_draw_ledger_cost()
"compact_label":
_draw_compact_label()
"compact_cost":
_draw_compact_cost()
"trait_chip":
_draw_trait_chip()
"cost_strip":
_draw_cost_strip()
_:
_draw_card()
func _draw_title() -> void:
var rect := Rect2(5.0, 7.0, size.x - 10.0, size.y - 12.0)
_draw_round(Rect2(rect.position + Vector2(4.0, 5.0), rect.size), 15.0, SHADOW)
_draw_round(rect, 15.0, PAPER)
_draw_round(rect.grow(-3.0), 12.0, Color(PAPER_LIGHT, 0.18), INK, 2.0)
# cloth header and brass corner stitches
var band := Rect2(rect.position + Vector2(18.0, 7.0), Vector2(rect.size.x - 36.0, 13.0))
_draw_round(band, 6.0, TEAL)
_draw_round(Rect2(band.position + Vector2(7.0, 3.0), Vector2(band.size.x - 14.0, 3.0)), 2.0, TEAL_LIGHT)
for point in [Vector2(rect.position.x + 12.0, rect.position.y + 16.0), Vector2(rect.end.x - 12.0, rect.position.y + 16.0), Vector2(rect.position.x + 12.0, rect.end.y - 13.0), Vector2(rect.end.x - 12.0, rect.end.y - 13.0)]:
_draw_diamond(point, 4.0, BRASS)
# little hanging tabs make the title read as a physical sign
draw_line(Vector2(rect.position.x + 24.0, rect.position.y), Vector2(rect.position.x + 24.0, rect.position.y - 7.0), INK_SOFT, 2.0, true)
draw_line(Vector2(rect.end.x - 24.0, rect.position.y), Vector2(rect.end.x - 24.0, rect.position.y - 7.0), INK_SOFT, 2.0, true)
func _draw_resource() -> void:
var rect := Rect2(3.0, 4.0, size.x - 6.0, size.y - 8.0)
_draw_round(Rect2(rect.position + Vector2(3.0, 4.0), rect.size), 10.0, SHADOW)
_draw_round(rect, 10.0, PAPER_LIGHT)
_draw_round(rect.grow(-2.0), 8.0, Color(PAPER, 0.3), INK_SOFT, 1.5)
_draw_diamond(Vector2(rect.position.x + 11.0, rect.get_center().y), 4.0, BRASS)
_draw_diamond(Vector2(rect.end.x - 11.0, rect.get_center().y), 4.0, BRASS)
func _draw_card() -> void:
# Card geometry is expressed as fractions of the card size so the drawn
# ribbon band and price strip always cover the same proportions as the
# label slots in m52_market_slots.gd (profession 0.055..0.155, price
# 0.88..0.96). Fixed pixel insets drifted away from those slots and left
# the profession label straddling the band and the parchment face.
var inset_x := size.x * 0.024
var inset_top := size.y * 0.035
var inset_bottom := size.y * 0.045
var rect := Rect2(inset_x, inset_top, size.x - inset_x * 2.0, size.y - inset_top - inset_bottom)
var radius := maxf(6.0, size.y * 0.032)
_draw_round(Rect2(rect.position + Vector2(size.x * 0.019, size.y * 0.016), rect.size), radius, SHADOW)
_draw_round(rect, radius, PAPER)
_draw_round(rect.grow(-radius * 0.25), radius * 0.75, Color(PAPER_LIGHT, 0.10), INK_SOFT, 1.5)
# Rope and hanging knot
var rope_x := rect.position.x + rect.size.x * 0.5
var knot_y := rect.position.y - size.y * 0.008
draw_line(Vector2(rope_x - size.x * 0.049, 0.0), Vector2(rope_x - size.x * 0.034, knot_y + size.y * 0.024), WOOD_LIGHT, 2.0, true)
draw_line(Vector2(rope_x + size.x * 0.049, 0.0), Vector2(rope_x + size.x * 0.034, knot_y + size.y * 0.024), WOOD_LIGHT, 2.0, true)
draw_circle(Vector2(rope_x, knot_y + size.y * 0.027), size.x * 0.024, WOOD)
# Profession ribbon: spans the card's profession slot (0.055..0.155 of the
# card height) with a small overlap so the label sits fully on the band.
var ribbon_color := BLUE if accent == "blue" else RED if accent == "red" else BRASS
var ribbon_top := size.y * 0.052
var ribbon_bottom := size.y * 0.150
var ribbon := Rect2(rect.position.x + size.x * 0.049, ribbon_top, rect.size.x - size.x * 0.098, ribbon_bottom - ribbon_top)
_draw_round(ribbon, maxf(3.0, size.y * 0.012), ribbon_color)
draw_line(Vector2(ribbon.position.x + size.x * 0.024, ribbon.position.y + 1.0), Vector2(ribbon.end.x - size.x * 0.024, ribbon.position.y + 1.0), Color(PAPER_LIGHT, 0.64), 1.0, true)
# Bottom price strip: spans the card's price slot (0.88..0.96).
var price_top := size.y * 0.872
var price_bottom := size.y * 0.968
var price := Rect2(rect.position.x + size.x * 0.044, price_top, rect.size.x - size.x * 0.088, price_bottom - price_top)
_draw_round(price, maxf(4.0, size.y * 0.016), PAPER_SHADE)
draw_line(Vector2(price.position.x + size.x * 0.020, price.position.y + size.y * 0.005), Vector2(price.end.x - size.x * 0.020, price.position.y + size.y * 0.005), Color(PAPER_LIGHT, 0.64), 1.0, true)
# seal marker
var seal_center := Vector2(rect.end.x - size.x * 0.083, rect.position.y + size.y * 0.205)
draw_circle(seal_center, size.x * 0.049, Color(ribbon_color, 0.92))
draw_arc(seal_center, size.x * 0.039, 0.0, TAU, 18, PAPER_LIGHT, 1.2, true)
_draw_diamond(seal_center, size.x * 0.015, PAPER_LIGHT)
func _draw_ledger() -> void:
var rect := Rect2(5.0, 6.0, size.x - 10.0, size.y - 12.0)
_draw_round(Rect2(rect.position + Vector2(5.0, 7.0), rect.size), 16.0, SHADOW)
_draw_round(rect, 16.0, PAPER)
_draw_round(rect.grow(-4.0), 12.0, Color(PAPER_LIGHT, 0.10), INK_SOFT, 2.0)
var header := Rect2(rect.position.x + 10.0, rect.position.y + 10.0, rect.size.x - 20.0, minf(36.0, rect.size.y * 0.18))
_draw_round(header, 9.0, TEAL)
draw_line(Vector2(header.position.x + 9.0, header.end.y - 6.0), Vector2(header.end.x - 9.0, header.end.y - 6.0), Color(TEAL_LIGHT, 0.8), 2.0, true)
# portrait frame and lower action dock
var portrait := Rect2(rect.position.x + 14.0, header.end.y + 10.0, rect.size.x - 28.0, rect.size.y * 0.35)
_draw_round(portrait, 11.0, Color(PAPER_SHADE, 0.52), INK_SOFT, 1.2)
var dock := Rect2(rect.position.x + 14.0, rect.end.y - 58.0, rect.size.x - 28.0, 42.0)
_draw_round(dock, 10.0, Color(TEAL, 0.92), INK, 2.0)
draw_line(Vector2(dock.position.x + 8.0, dock.position.y + 6.0), Vector2(dock.end.x - 8.0, dock.position.y + 6.0), Color(TEAL_LIGHT, 0.8), 1.0, true)
for point in [Vector2(rect.position.x + 12.0, rect.position.y + 18.0), Vector2(rect.end.x - 12.0, rect.position.y + 18.0), Vector2(rect.position.x + 12.0, rect.end.y - 18.0), Vector2(rect.end.x - 12.0, rect.end.y - 18.0)]:
_draw_diamond(point, 4.0, BRASS)
func _draw_wood_button() -> void:
var rect := Rect2(3.0, 4.0, size.x - 6.0, size.y - 8.0)
var cut := minf(10.0, rect.size.y * 0.24)
var points := PackedVector2Array([Vector2(rect.position.x + cut, rect.position.y), Vector2(rect.end.x - cut, rect.position.y), Vector2(rect.end.x, rect.position.y + cut), Vector2(rect.end.x, rect.end.y - cut), Vector2(rect.end.x - cut, rect.end.y), Vector2(rect.position.x + cut, rect.end.y), Vector2(rect.position.x, rect.end.y - cut), Vector2(rect.position.x, rect.position.y + cut)])
var shadow := PackedVector2Array()
for point in points:
shadow.append(point + Vector2(3.0, 5.0))
draw_colored_polygon(shadow, SHADOW)
draw_colored_polygon(points, WOOD)
draw_polyline(PackedVector2Array([points[0], points[1], points[2], points[3], points[4], points[5], points[6], points[7], points[0]]), WOOD_LIGHT, 2.0, true)
draw_line(Vector2(rect.position.x + cut + 8.0, rect.position.y + 6.0), Vector2(rect.end.x - cut - 8.0, rect.position.y + 6.0), Color(PAPER_LIGHT, 0.34), 1.5, true)
_draw_diamond(Vector2(rect.position.x + cut + 5.0, rect.get_center().y), 3.0, BRASS)
_draw_diamond(Vector2(rect.end.x - cut - 5.0, rect.get_center().y), 3.0, BRASS)
func _draw_ledger_action() -> void:
var rect := Rect2(3.0, 4.0, size.x - 6.0, size.y - 8.0)
_draw_round(Rect2(rect.position + Vector2(3.0, 5.0), rect.size), 10.0, SHADOW)
_draw_round(rect, 10.0, TEAL, INK, 2.0)
_draw_round(rect.grow(-4.0), 7.0, Color(TEAL_LIGHT, 0.18), Color(TEAL_LIGHT, 0.64), 1.0)
draw_line(Vector2(rect.position.x + 10.0, rect.position.y + 6.0), Vector2(rect.end.x - 10.0, rect.position.y + 6.0), Color(PAPER_LIGHT, 0.66), 1.2, true)
_draw_diamond(Vector2(rect.position.x + 10.0, rect.get_center().y), 3.0, BRASS)
_draw_diamond(Vector2(rect.end.x - 10.0, rect.get_center().y), 3.0, BRASS)
func _draw_ledger_cost() -> void:
# Fill the module so the availability and amount labels sit on the strip
# instead of overflowing onto the parchment behind it.
var rect := Rect2(size.x * 0.008, size.y * 0.06, size.x * 0.984, size.y * 0.88)
var radius := maxf(5.0, size.y * 0.20)
_draw_round(Rect2(rect.position + Vector2(size.x * 0.006, size.y * 0.07), rect.size), radius, SHADOW)
_draw_round(rect, radius, PAPER_SHADE, INK_SOFT, 1.0)
_draw_round(rect.grow(-radius * 0.4), radius * 0.7, Color(PAPER_LIGHT, 0.20), Color(PAPER_LIGHT, 0.70), 1.0)
draw_line(Vector2(rect.position.x + size.x * 0.025, rect.position.y + size.y * 0.16), Vector2(rect.end.x - size.x * 0.025, rect.position.y + size.y * 0.16), Color(PAPER_LIGHT, 0.75), 1.0, true)
_draw_diamond(Vector2(rect.position.x + size.x * 0.022, rect.get_center().y), maxf(2.0, size.y * 0.09), BRASS)
_draw_diamond(Vector2(rect.end.x - size.x * 0.022, rect.get_center().y), maxf(2.0, size.y * 0.09), BRASS)
func _draw_trait_chip() -> void:
# Proportional label chip: teal caps scale with the chip so the text strip
# is always the middle band and no cap can cover a glyph.
var cap := size.x * 0.14
var middle_top := size.y * 0.06
var middle_bottom := size.y * 0.94
var body := Rect2(cap * 0.55, middle_top, size.x - cap * 1.1, middle_bottom - middle_top)
_draw_round(Rect2(body.position + Vector2(0.0, size.y * 0.09), body.size), maxf(3.0, size.y * 0.22), SHADOW)
_draw_round(body, maxf(3.0, size.y * 0.20), PAPER_LIGHT, INK_SOFT, 1.0)
draw_line(Vector2(body.position.x + size.x * 0.02, body.position.y + 1.0), Vector2(body.end.x - size.x * 0.02, body.position.y + 1.0), Color(PAPER_SHADE, 0.9), 1.0, true)
for side in [0.0, 1.0]:
var center_x := cap * 0.5 if side == 0.0 else size.x - cap * 0.5
var points := PackedVector2Array([
Vector2(center_x - cap * 0.5, middle_top),
Vector2(center_x + cap * 0.5, middle_top + size.y * 0.16),
Vector2(center_x + cap * 0.5, middle_bottom - size.y * 0.16),
Vector2(center_x - cap * 0.5, middle_bottom)])
draw_colored_polygon(points, TEAL)
draw_polyline(points + PackedVector2Array([points[0]]), Color(TEAL_LIGHT, 0.8), 1.2, true)
_draw_diamond(Vector2(center_x, size.y * 0.5), maxf(1.5, size.y * 0.10), BRASS)
func _draw_cost_strip() -> void:
# Proportional cost row: the coin socket is the leftmost fraction and the
# labels own the band to its right.
var socket := size.x * 0.19
var band := Rect2(socket, size.y * 0.10, size.x - socket - size.x * 0.02, size.y * 0.80)
_draw_round(Rect2(band.position + Vector2(0.0, size.y * 0.08), band.size), maxf(3.0, size.y * 0.24), SHADOW)
_draw_round(band, maxf(3.0, size.y * 0.22), PAPER_SHADE, INK_SOFT, 1.0)
draw_line(Vector2(band.position.x + size.x * 0.015, band.position.y + 1.5), Vector2(band.end.x - size.x * 0.015, band.position.y + 1.5), Color(PAPER_LIGHT, 0.75), 1.0, true)
var coin := Vector2(socket * 0.5, size.y * 0.5)
draw_circle(coin, minf(socket, size.y) * 0.44, Color(BRASS, 0.95))
draw_arc(coin, minf(socket, size.y) * 0.36, 0.0, TAU, 20, PAPER_LIGHT, 1.2, true)
_draw_diamond(coin, minf(socket, size.y) * 0.16, PAPER_LIGHT)
func _draw_compact_label() -> void:
var rect := Rect2(2.0, 2.0, size.x - 4.0, size.y - 4.0)
_draw_round(Rect2(rect.position + Vector2(2.0, 2.0), rect.size), 5.0, SHADOW)
_draw_round(rect, 5.0, PAPER_LIGHT, INK_SOFT, 1.0)
_draw_round(rect.grow(-2.0), 3.0, Color(PAPER, 0.18), Color(PAPER_SHADE, 0.75), 1.0)
func _draw_compact_cost() -> void:
var rect := Rect2(2.0, 2.0, size.x - 4.0, size.y - 4.0)
_draw_round(Rect2(rect.position + Vector2(2.0, 2.0), rect.size), 5.0, SHADOW)
_draw_round(rect, 5.0, PAPER_SHADE, INK_SOFT, 1.0)
_draw_round(rect.grow(-2.0), 3.0, Color(PAPER_LIGHT, 0.26), Color(PAPER_LIGHT, 0.76), 1.0)
_draw_diamond(Vector2(rect.position.x + 7.0, rect.get_center().y), 2.5, BRASS)
_draw_diamond(Vector2(rect.end.x - 7.0, rect.get_center().y), 2.5, BRASS)
func _draw_round(rect: Rect2, radius: float, fill: Color, border := Color(0, 0, 0, 0), width := 0.0) -> void:
var style := StyleBoxFlat.new()
style.bg_color = fill
style.corner_radius_top_left = int(radius)
style.corner_radius_top_right = int(radius)
style.corner_radius_bottom_left = int(radius)
style.corner_radius_bottom_right = int(radius)
if width > 0.0:
style.border_color = border
style.set_border_width_all(int(width))
draw_style_box(style, rect)
func _draw_diamond(center: Vector2, radius: float, color: Color) -> void:
draw_colored_polygon(PackedVector2Array([center + Vector2(0.0, -radius), center + Vector2(radius, 0.0), center + Vector2(0.0, radius), center + Vector2(-radius, 0.0)]), color)