Phase 14 - Organized directories.
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,205 @@
|
||||
class_name AssetSelector
|
||||
extends PopupPanel
|
||||
|
||||
signal item_selected(entry: Dictionary)
|
||||
signal cancelled()
|
||||
signal browse_requested()
|
||||
signal refresh_requested()
|
||||
|
||||
const COLUMNS := 4
|
||||
const ROWS := 3
|
||||
const PAGE_SIZE := COLUMNS * ROWS
|
||||
|
||||
const CELL_MIN_SIZE := Vector2(190.0, 220.0)
|
||||
const THUMB_SIZE := Vector2(156.0, 156.0)
|
||||
const ASSET_CARD := preload("res://scripts/shared/ui/asset_card.gd")
|
||||
|
||||
var kind: String = ""
|
||||
|
||||
var _entries: Array[Dictionary] = []
|
||||
var _page: int = 0
|
||||
var _thumbnails: Dictionary = {}
|
||||
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
|
||||
@onready var _empty_label: Label = %EmptyLabel
|
||||
@onready var _page_label: Label = %PageLabel
|
||||
@onready var _prev_button: Button = %PrevButton
|
||||
@onready var _next_button: Button = %NextButton
|
||||
@onready var _browse_button: Button = %BrowseButton
|
||||
@onready var _refresh_button: Button = %RefreshButton
|
||||
@onready var _close_button: Button = %CloseButton
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
exclusive = true
|
||||
_prev_button.pressed.connect(_on_prev)
|
||||
_next_button.pressed.connect(_on_next)
|
||||
_browse_button.pressed.connect(func() -> void: browse_requested.emit())
|
||||
_refresh_button.pressed.connect(func() -> void: refresh_requested.emit())
|
||||
_close_button.pressed.connect(func() -> void: cancelled.emit())
|
||||
get_tree().root.size_changed.connect(_on_root_size_changed)
|
||||
|
||||
|
||||
func _on_root_size_changed() -> void:
|
||||
if visible:
|
||||
popup_centered()
|
||||
|
||||
|
||||
func open(p_kind: String, entries: Array[Dictionary]) -> void:
|
||||
kind = p_kind
|
||||
_title_label.text = "Choose Your Stickman" if kind == "stickman" else "Choose a Prop"
|
||||
_browse_button.visible = kind == "stickman" or kind == "prop"
|
||||
_refresh_button.visible = kind == "stickman" or kind == "prop"
|
||||
_empty_label.text = "No props found. Browse for a .prop file." if kind == "prop" else "No stickmen found."
|
||||
set_entries(entries)
|
||||
popup_centered()
|
||||
|
||||
|
||||
func set_entries(entries: Array[Dictionary]) -> void:
|
||||
_entries = entries
|
||||
_thumbnails.clear()
|
||||
_cell_texrects.clear()
|
||||
_page = 0
|
||||
_rebuild()
|
||||
|
||||
|
||||
func set_thumbnail(entry: Dictionary, tex: Texture2D) -> void:
|
||||
var key := _entry_key(entry)
|
||||
_thumbnails[key] = tex
|
||||
if _cell_texrects.has(key):
|
||||
(_cell_texrects[key] as TextureRect).texture = tex
|
||||
|
||||
|
||||
func close() -> void:
|
||||
hide()
|
||||
|
||||
|
||||
func apply_font(ui_font: Font, emoji_font: Font) -> void:
|
||||
_ui_font = ui_font
|
||||
_emoji_font = emoji_font
|
||||
var controls: Array = [
|
||||
_title_label, _empty_label, _page_label,
|
||||
_prev_button, _next_button, _browse_button, _refresh_button, _close_button,
|
||||
]
|
||||
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:
|
||||
var key := event as InputEventKey
|
||||
if key.pressed and not key.echo and key.keycode == KEY_ESCAPE:
|
||||
get_viewport().set_input_as_handled()
|
||||
cancelled.emit()
|
||||
|
||||
|
||||
static func page_bounds(total: int, page: int, page_size: int = PAGE_SIZE) -> Dictionary:
|
||||
var page_count := maxi(1, int(ceil(float(total) / float(page_size))))
|
||||
var start := page * page_size
|
||||
if start >= total:
|
||||
start = 0
|
||||
var end := mini(start + page_size, total)
|
||||
return { "start": start, "end": end, "total": total, "page_count": page_count }
|
||||
|
||||
|
||||
func _entry_key(entry: Dictionary) -> String:
|
||||
if kind == "stickman":
|
||||
return String(entry.get("path", ""))
|
||||
return String(entry.get("id", ""))
|
||||
|
||||
|
||||
func _on_prev() -> void:
|
||||
if _page > 0:
|
||||
_page -= 1
|
||||
_rebuild()
|
||||
|
||||
|
||||
func _on_next() -> void:
|
||||
if (_page + 1) * PAGE_SIZE < _entries.size():
|
||||
_page += 1
|
||||
_rebuild()
|
||||
|
||||
|
||||
func _page_count() -> int:
|
||||
if _entries.is_empty():
|
||||
return 1
|
||||
return int(ceil(float(_entries.size()) / float(PAGE_SIZE)))
|
||||
|
||||
|
||||
func _rebuild() -> void:
|
||||
for c: Node in _grid.get_children():
|
||||
c.queue_free()
|
||||
_cell_texrects.clear()
|
||||
_empty_label.visible = _entries.is_empty()
|
||||
_grid.visible = not _entries.is_empty()
|
||||
var total := _page_count()
|
||||
var single := total <= 1
|
||||
_prev_button.visible = not single
|
||||
_next_button.visible = not single
|
||||
_page_label.visible = not single
|
||||
_prev_button.disabled = _page <= 0
|
||||
_next_button.disabled = (_page + 1) * PAGE_SIZE >= _entries.size()
|
||||
_page_label.text = "Page %d/%d" % [_page + 1, total]
|
||||
if _entries.is_empty():
|
||||
return
|
||||
var bounds := page_bounds(_entries.size(), _page)
|
||||
for i: int in range(int(bounds["start"]), int(bounds["end"])):
|
||||
_grid.add_child(_build_cell(_entries[i]))
|
||||
|
||||
|
||||
func _build_cell(entry: Dictionary) -> Control:
|
||||
var key := _entry_key(entry)
|
||||
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.activated.connect(func(_payload: Dictionary) -> void: _on_cell_pressed(entry))
|
||||
var tex: Texture2D = _thumbnails.get(key, null)
|
||||
cell.set_thumbnail(tex if tex != null else _get_placeholder())
|
||||
_cell_texrects[key] = cell.thumbnail_rect()
|
||||
|
||||
return cell
|
||||
|
||||
|
||||
func _on_cell_pressed(entry: Dictionary) -> void:
|
||||
item_selected.emit(entry)
|
||||
|
||||
|
||||
func _apply_font_to(c: Control) -> void:
|
||||
if c == null:
|
||||
return
|
||||
if _ui_font != null:
|
||||
c.add_theme_font_override("font", _ui_font)
|
||||
elif _emoji_font != null:
|
||||
c.add_theme_font_override("font", _emoji_font)
|
||||
|
||||
|
||||
func _get_placeholder() -> ImageTexture:
|
||||
if _placeholder_tex == null:
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1ikg6vt3tiu3
|
||||
@@ -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