class_name ABGAssetChrome extends RefCounted ## Shared asset nine-patch chrome for first-play pages. ## ## Large parchment/wood surfaces use the approved generated UI components with ## their decorative borders isolated as nine-patch corners. Small controls use ## the size-independent shared vector material (`market_chrome.gd`) so they ## never stretch ornament pixels. Text and values stay engine-rendered. const MarketChrome = preload("res://ui/shared/market_chrome.gd") const PAPER_PANEL = preload("res://assets/ui_m52_components/v2_trimmed/chrome/parchment_panel.png") const ACTION_PLATE = preload("res://assets/ui_m52_components/v2_trimmed/chrome/action_button.png") const NAME_PLATE = preload("res://assets/ui_m52_components/v2_trimmed/frames/name_plate.png") const TITLE_PLAQUE = preload("res://assets/ui_m52_market/v2/title_plaque.png") const WOOD_PLATE = preload("res://assets/ui_m52_market/v2/wood_button.png") const INK := Color("#2f2417") const INK_SOFT := Color("#5e4a32") const IVORY := Color("#f6ecd4") static var _scaled_cache := {} ## Release the downscaled textures held by the static cache. Pages must call ## this when they leave the tree so a strict run does not report leaked RIDs. static func clear_cache() -> void: _scaled_cache.clear() ## Plain parchment panel. Defaults keep the wood frame in the corners for a ## medium-sized region; callers scale down and adjust margins per region. static func paper(node_name: String, area: Rect2, scale := 0.55, h_margin := 85.0, v_margin := 75.0) -> NinePatchRect: return plate(node_name, area, PAPER_PANEL, scale, h_margin, v_margin) ## Wide wood rail (top rail, bottom rail). `name_plate` has a thin frame and a ## plain parchment center, so it stretches cleanly across a full canvas width. static func rail(node_name: String, area: Rect2, scale := 0.5, h_margin := 105.0, v_margin := 45.0) -> NinePatchRect: return plate(node_name, area, NAME_PLATE, scale, h_margin, v_margin) static func plate(node_name: String, area: Rect2, source: Texture2D, scale: float, h_margin: float, v_margin: float) -> NinePatchRect: var node := NinePatchRect.new() node.name = node_name node.texture = scaled(source, scale) node.patch_margin_left = maxi(2, int(round(h_margin * scale))) node.patch_margin_right = maxi(2, int(round(h_margin * scale))) node.patch_margin_top = maxi(2, int(round(v_margin * scale))) node.patch_margin_bottom = maxi(2, int(round(v_margin * scale))) node.axis_stretch_horizontal = NinePatchRect.AXIS_STRETCH_MODE_STRETCH node.axis_stretch_vertical = NinePatchRect.AXIS_STRETCH_MODE_STRETCH node.draw_center = true node.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS node.mouse_filter = Control.MOUSE_FILTER_IGNORE place(node, area) return node ## Size-independent shared vector material for small controls (name plates, ## command plates). `variant` is a `market_chrome.gd` variant name. static func market(node_name: String, area: Rect2, variant: String, accent := "blue") -> Control: var node = MarketChrome.new().configure(variant, accent) node.name = node_name node.resized.connect(node.queue_redraw) place(node, area) return node static func scaled(source: Texture2D, scale: float) -> Texture2D: if is_equal_approx(scale, 1.0): return source var key := "%s@%.3f" % [source.resource_path, scale] var cached: Texture2D = _scaled_cache.get(key) if cached != null: return cached var image := source.get_image() image.resize( maxi(2, int(round(float(image.get_width()) * scale))), maxi(2, int(round(float(image.get_height()) * scale))), Image.INTERPOLATE_LANCZOS ) var texture := ImageTexture.create_from_image(image) _scaled_cache[key] = texture return texture static func place(control: Control, area: Rect2) -> void: control.anchor_left = area.position.x control.anchor_top = area.position.y control.anchor_right = area.end.x control.anchor_bottom = area.end.y control.offset_left = 0.0 control.offset_top = 0.0 control.offset_right = 0.0 control.offset_bottom = 0.0 ## Typography: one base family with explicit tracking and weight so titles, ## values and captions read as different ranks instead of one flat label size. static func styled_font(tracking := 0.0, embolden := 0.0) -> FontVariation: var variation := FontVariation.new() variation.base_font = preload("res://assets/fonts/NotoSansSC.ttf") variation.spacing_glyph = int(round(tracking)) variation.variation_embolden = embolden return variation static func styled_label(value: String, actual_size: int, color := INK, tracking := 0.0, embolden := 0.0) -> Label: var label := Label.new() label.text = value label.add_theme_font_override("font", styled_font(tracking, embolden)) label.add_theme_font_size_override("font_size", actual_size) label.add_theme_color_override("font_color", color) label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.mouse_filter = Control.MOUSE_FILTER_IGNORE return label static func ink_label(value: String, color := INK, design_size := 14.0) -> Label: var label := Label.new() label.text = value label.add_theme_font_override("font", preload("res://assets/fonts/NotoSansSC.ttf")) label.add_theme_font_size_override("font_size", int(design_size)) label.add_theme_color_override("font_color", color) label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.mouse_filter = Control.MOUSE_FILTER_IGNORE return label