Add reusable UI components for Phase 7 UI Polish

- Introduced `EmojiQuickGrid`, `FontPresetPicker`, and `PresetSizeToggle` classes for emoji selection, font presets, and size toggles respectively.
- Implemented configuration methods for each component to handle dynamic content and theming.
- Created tests for the new components to ensure functionality and integration with the existing UI.
- Added a comprehensive architecture specification for Phase 7 UI Polish detailing scope, implementation, and validation strategies.
- Updated existing theme handling to support new UI elements and ensure consistent styling across the application.
This commit is contained in:
2026-09-18 09:42:48 -04:00
parent baa19a9341
commit 1a2a43c805
35 changed files with 1791 additions and 205 deletions
+28 -41
View File
@@ -10,8 +10,9 @@ const COLUMNS := 4
const ROWS := 3
const PAGE_SIZE := COLUMNS * ROWS
const CELL_MIN_SIZE := Vector2(140.0, 168.0)
const THUMB_SIZE := Vector2(120.0, 120.0)
const CELL_MIN_SIZE := Vector2(190.0, 220.0)
const THUMB_SIZE := Vector2(156.0, 156.0)
const ASSET_CARD := preload("res://scripts/ui/asset_card.gd")
var kind: String = ""
@@ -22,6 +23,7 @@ var _cell_texrects: Dictionary = {}
var _placeholder_tex: ImageTexture = null
var _ui_font: Font = null
var _emoji_font: Font = null
var _sandbox_theme: Variant = null
@onready var _title_label: Label = %TitleLabel
@onready var _grid: GridContainer = %GridContainer
@@ -87,6 +89,21 @@ func apply_font(ui_font: Font, emoji_font: Font) -> void:
for c: Control in controls:
_apply_font_to(c)
func apply_theme(theme: Variant) -> void:
_sandbox_theme = theme
if theme != null:
self.theme = theme.control_theme()
add_theme_stylebox_override("panel", theme.stylebox("popups.asset_selector"))
var cell_size: Variant = theme.value("popups.asset_selector.cell_size", CELL_MIN_SIZE)
if cell_size is Array and (cell_size as Array).size() == 2:
_cell_min_size = Vector2(float(cell_size[0]), float(cell_size[1]))
var thumbnail_size: Variant = theme.value("popups.asset_selector.thumbnail_size", THUMB_SIZE)
if thumbnail_size is Array and (thumbnail_size as Array).size() == 2:
_thumbnail_size = Vector2(float(thumbnail_size[0]), float(thumbnail_size[1]))
var _cell_min_size: Vector2 = CELL_MIN_SIZE
var _thumbnail_size: Vector2 = THUMB_SIZE
func _unhandled_input(event: InputEvent) -> void:
if visible and event is InputEventKey:
@@ -152,46 +169,16 @@ func _rebuild() -> void:
func _build_cell(entry: Dictionary) -> Control:
var key := _entry_key(entry)
var cell := Button.new()
cell.custom_minimum_size = CELL_MIN_SIZE
cell.toggle_mode = false
var cell: Variant = ASSET_CARD.new()
# Selector cells inherit the popup theme. Avoid a per-card state override so
# opening a selector never implies that one asset is pre-selected.
cell.configure({"placement_id": _entry_key(entry), "label": String(entry.get("name", entry.get("id", ""))), "icon": "", "category": String(entry.get("material_label", "")), "thumbnail_size": _thumbnail_size}, _thumbnails.get(key, null), null)
cell.custom_minimum_size = _cell_min_size
cell.focus_mode = Control.FOCUS_NONE
cell.pressed.connect(_on_cell_pressed.bind(entry))
var vbox := VBoxContainer.new()
vbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
vbox.mouse_filter = Control.MOUSE_FILTER_IGNORE
vbox.add_theme_constant_override("separation", 2)
cell.add_child(vbox)
var tex_rect := TextureRect.new()
tex_rect.custom_minimum_size = THUMB_SIZE
tex_rect.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
tex_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
tex_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
tex_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
cell.activated.connect(func(_payload: Dictionary) -> void: _on_cell_pressed(entry))
var tex: Texture2D = _thumbnails.get(key, null)
tex_rect.texture = tex if tex != null else _get_placeholder()
vbox.add_child(tex_rect)
_cell_texrects[key] = tex_rect
var name_label := Label.new()
name_label.text = String(entry.get("name", ""))
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
name_label.add_theme_font_size_override("font_size", 14)
_apply_font_to(name_label)
vbox.add_child(name_label)
if kind == "prop":
var badge := Label.new()
badge.text = String(entry.get("material_label", ""))
badge.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
badge.add_theme_font_size_override("font_size", 12)
badge.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7, 1.0))
_apply_font_to(badge)
vbox.add_child(badge)
cell.set_thumbnail(tex if tex != null else _get_placeholder())
_cell_texrects[key] = cell.thumbnail_rect()
return cell
@@ -211,7 +198,7 @@ func _apply_font_to(c: Control) -> void:
func _get_placeholder() -> ImageTexture:
if _placeholder_tex == null:
var img := Image.create(THUMB_SIZE.x, THUMB_SIZE.y, false, Image.FORMAT_RGBA8)
var img := Image.create(int(_thumbnail_size.x), int(_thumbnail_size.y), false, Image.FORMAT_RGBA8)
img.fill(Color(0.16, 0.16, 0.16, 1.0))
_placeholder_tex = ImageTexture.create_from_image(img)
return _placeholder_tex