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:
@@ -0,0 +1,58 @@
|
||||
class_name AssetCard
|
||||
extends Button
|
||||
|
||||
signal activated(payload: Dictionary)
|
||||
signal drag_started(payload: Dictionary)
|
||||
|
||||
var _payload: Dictionary = {}
|
||||
var _press_position: Vector2 = Vector2.ZERO
|
||||
var _dragging: bool = false
|
||||
var _drag_threshold: float = 8.0
|
||||
var _icon: TextureRect = null
|
||||
var _glyph: Label = null
|
||||
var _label: Label = null
|
||||
|
||||
func configure(payload: Dictionary, icon_texture: Texture2D = null, theme: Variant = null) -> void:
|
||||
_payload = payload.duplicate(true)
|
||||
_drag_threshold = theme.number("palette.drag_threshold", 8.0, 1.0, 128.0) if theme != null else 8.0
|
||||
custom_minimum_size = Vector2(96, 108)
|
||||
clip_contents = true
|
||||
if theme != null:
|
||||
var size_data: Variant = theme.value("palette.card_size", [96, 94])
|
||||
if size_data is Array and (size_data as Array).size() == 2: custom_minimum_size = Vector2(float(size_data[0]), maxf(108.0, float(size_data[1])))
|
||||
add_theme_stylebox_override("normal", theme.stylebox("palette"))
|
||||
var thumbnail_size: Vector2 = _payload.get("thumbnail_size", Vector2(64, 64))
|
||||
focus_mode = Control.FOCUS_ALL
|
||||
tooltip_text = String(_payload.get("label", ""))
|
||||
for child: Node in get_children(): child.queue_free()
|
||||
var box := VBoxContainer.new(); box.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT); box.mouse_filter = Control.MOUSE_FILTER_IGNORE; box.clip_contents = true
|
||||
box.alignment = BoxContainer.ALIGNMENT_CENTER; box.add_theme_constant_override("separation", 2); add_child(box)
|
||||
_icon = TextureRect.new(); _icon.custom_minimum_size = thumbnail_size; _icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE; _icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED; _icon.texture = icon_texture; _icon.visible = icon_texture != null; _icon.mouse_filter = Control.MOUSE_FILTER_IGNORE; box.add_child(_icon)
|
||||
_glyph = Label.new(); _glyph.text = String(_payload.get("icon", "")); _glyph.visible = icon_texture == null; _glyph.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; _glyph.mouse_filter = Control.MOUSE_FILTER_IGNORE; _glyph.add_theme_font_size_override("font_size", 30); box.add_child(_glyph)
|
||||
_label = Label.new(); _label.text = String(_payload.get("label", "")); _label.custom_minimum_size = Vector2(0, 28); _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; _label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART; _label.clip_text = true; _label.mouse_filter = Control.MOUSE_FILTER_IGNORE; box.add_child(_label)
|
||||
|
||||
func set_thumbnail(texture: Texture2D) -> void:
|
||||
if _icon != null: _icon.texture = texture; _icon.visible = texture != null
|
||||
if _glyph != null and texture != null: _glyph.visible = false
|
||||
|
||||
func thumbnail_rect() -> TextureRect:
|
||||
return _icon
|
||||
|
||||
func set_selected(selected: bool) -> void:
|
||||
button_pressed = selected
|
||||
|
||||
func payload() -> Dictionary:
|
||||
return _payload.duplicate(true)
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
_press_position = event.position; _dragging = false
|
||||
elif _dragging:
|
||||
accept_event()
|
||||
elif event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and not _dragging:
|
||||
if event.position.distance_to(_press_position) >= _drag_threshold:
|
||||
_dragging = true; drag_started.emit(payload()); accept_event()
|
||||
|
||||
func _pressed() -> void:
|
||||
if not _dragging: activated.emit(payload())
|
||||
@@ -0,0 +1 @@
|
||||
uid://dayameygn6kdb
|
||||
@@ -0,0 +1,17 @@
|
||||
class_name ColorSwatchSelector
|
||||
extends HBoxContainer
|
||||
signal color_selected(color: Color)
|
||||
var _selected: Color = Color.WHITE
|
||||
var _group: ButtonGroup = ButtonGroup.new()
|
||||
func configure(colors: Array[Color], selected: Color, theme: Variant = null) -> void:
|
||||
for child: Node in get_children(): child.queue_free()
|
||||
_selected = selected
|
||||
for color: Color in colors:
|
||||
var button := Button.new(); button.custom_minimum_size = Vector2(44,44); button.toggle_mode = true; button.button_group = _group; button.tooltip_text = color.to_html(true)
|
||||
var box := StyleBoxFlat.new(); box.bg_color = color; box.set_corner_radius_all(22); button.add_theme_stylebox_override("normal", box)
|
||||
button.pressed.connect(func() -> void: set_selected(color, true)); add_child(button)
|
||||
button.set_pressed_no_signal(color.is_equal_approx(selected))
|
||||
func set_selected(color: Color, emit_signal: bool = false) -> void:
|
||||
_selected = color
|
||||
if emit_signal: color_selected.emit(color)
|
||||
func get_selected() -> Color: return _selected
|
||||
@@ -0,0 +1 @@
|
||||
uid://djy6yykmgtxk0
|
||||
@@ -0,0 +1,10 @@
|
||||
class_name EmojiQuickGrid
|
||||
extends GridContainer
|
||||
signal emoji_selected(text: String)
|
||||
func configure(items: Array[String], columns: int = 8, theme: Variant = null) -> void:
|
||||
for child: Node in get_children(): child.queue_free()
|
||||
self.columns = maxi(1, columns)
|
||||
for text: String in items:
|
||||
var button := Button.new(); button.text = text; button.custom_minimum_size = Vector2(44,44); button.focus_mode = Control.FOCUS_ALL
|
||||
if theme != null: button.add_theme_font_override("font", theme.emoji_font())
|
||||
button.pressed.connect(func() -> void: emoji_selected.emit(text)); add_child(button)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dlstr7bm0x258
|
||||
@@ -0,0 +1,17 @@
|
||||
class_name FontPresetPicker
|
||||
extends HBoxContainer
|
||||
signal preset_selected(id: String)
|
||||
var _selected_id: String = ""
|
||||
var _group: ButtonGroup = ButtonGroup.new()
|
||||
func configure(presets: Array[Dictionary], selected_id: String, theme: Variant = null) -> void:
|
||||
for child: Node in get_children(): child.queue_free()
|
||||
_selected_id = selected_id
|
||||
for item: Dictionary in presets:
|
||||
var id: String = String(item.get("id", "")); if id.is_empty(): continue
|
||||
var button := Button.new(); button.text = String(item.get("label", id)); button.custom_minimum_size = Vector2(44,44); button.toggle_mode = true; button.button_group = _group
|
||||
if item.get("font", null) is Font: button.add_theme_font_override("font", item["font"])
|
||||
button.pressed.connect(func() -> void: set_selected(id, true)); add_child(button); button.set_pressed_no_signal(id == selected_id)
|
||||
func set_selected(id: String, emit_signal: bool = false) -> void:
|
||||
_selected_id = id
|
||||
if emit_signal: preset_selected.emit(id)
|
||||
func get_selected_id() -> String: return _selected_id
|
||||
@@ -0,0 +1 @@
|
||||
uid://dfwkdo5sx6i4h
|
||||
@@ -0,0 +1,25 @@
|
||||
class_name PresetSizeToggle
|
||||
extends HBoxContainer
|
||||
signal preset_selected(id: String, value: int)
|
||||
var _presets: Array[Dictionary] = []
|
||||
var _selected_id: String = ""
|
||||
var _group: ButtonGroup = ButtonGroup.new()
|
||||
func configure(presets: Array[Dictionary], selected_id: String, theme: Variant = null) -> void:
|
||||
for child: Node in get_children(): child.queue_free()
|
||||
_presets = presets.duplicate(true); _selected_id = selected_id
|
||||
for item: Dictionary in _presets:
|
||||
if String(item.get("id","")).is_empty(): continue
|
||||
var button := Button.new(); button.text = String(item.get("label", item["id"])); button.custom_minimum_size = Vector2(44,44); button.toggle_mode = true; button.button_group = _group
|
||||
button.pressed.connect(func() -> void: set_selected(String(item["id"]), true)); add_child(button); button.set_pressed_no_signal(String(item["id"]) == selected_id)
|
||||
if not _presets.is_empty() and _selected_id.is_empty(): _selected_id = String(_presets[0].get("id", ""))
|
||||
func set_selected(id: String, emit_signal: bool = false) -> void:
|
||||
for item: Dictionary in _presets:
|
||||
if String(item.get("id", "")) == id:
|
||||
_selected_id = id
|
||||
if emit_signal: preset_selected.emit(id, int(item.get("value", 0)))
|
||||
return
|
||||
func get_selected_id() -> String: return _selected_id
|
||||
func get_selected_value() -> int:
|
||||
for item: Dictionary in _presets:
|
||||
if String(item.get("id", "")) == _selected_id: return int(item.get("value", 0))
|
||||
return 0
|
||||
@@ -0,0 +1 @@
|
||||
uid://fx38yavxtiv4
|
||||
Reference in New Issue
Block a user