- Introduced `test_scenery_brick_preview.gd` to validate focused placement and CPU draw-cost for large brick walls. - Added `test_scenery_surface_cache.gd` to assess texture budget, visual equivalence, and cache invalidation. - Created `test_scenery_surface_preview.gd` to evaluate adaptive preview selection and placement behavior. - Implemented various helper functions to measure drawing performance and validate expected behaviors during tests. - Added unique identifiers for new test files to ensure proper tracking and management.
1217 lines
56 KiB
GDScript
1217 lines
56 KiB
GDScript
class_name AssetSelector
|
|
extends PopupPanel
|
|
|
|
signal item_selected(entry: Dictionary)
|
|
signal cancelled()
|
|
signal browse_requested()
|
|
signal refresh_requested()
|
|
signal make_artwork_requested()
|
|
signal edit_scenery_requested()
|
|
signal surface_color_confirmed(color: Color)
|
|
|
|
const COLUMNS := 4
|
|
const ROWS := 3
|
|
const PAGE_SIZE := COLUMNS * ROWS
|
|
const GRID_H_SEPARATION: float = 8.0
|
|
const GRID_V_SEPARATION: float = 8.0
|
|
const POPUP_SIDE_PADDING: float = 40.0
|
|
const POPUP_VERTICAL_PADDING: float = 32.0
|
|
const POPUP_SAFE_SCREEN_MARGIN: float = 24.0
|
|
const ARTWORK_FILTER_IDS: Array[String] = ["all", "nature", "buildings", "walls", "roads", "objects"]
|
|
const ARTWORK_FILTER_LABELS: Array[String] = ["All artwork", "Nature", "Buildings", "Walls", "Roads & paths", "Objects"]
|
|
|
|
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")
|
|
const BACKGROUND_CARD_PREVIEW := preload("res://scripts/stage/scenery/stage_background_card_preview.gd")
|
|
const STARTER_CARD_PREVIEW := preload("res://scripts/stage/scenery/stage_starter_card_preview.gd")
|
|
const SURFACE_RENDERER := preload("res://scripts/stage/scenery/stage_surface_renderer.gd")
|
|
const SURFACE_PREVIEW := preload("res://scripts/stage/scenery/stage_surface_preview.gd")
|
|
const SURFACE_COLOR_CONTROLS := preload("res://scripts/shared/stage_ui/stage_surface_color_controls.gd")
|
|
const SURFACE_COLOR_PICKER_VIEW := preload("res://scripts/shared/stage_ui/stage_surface_color_picker_view.gd")
|
|
const STAGE_PANEL_CHOICE_GROUP := preload("res://scripts/shared/stage_ui/stage_panel_choice_group.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 _strong_ui_font: Font = null
|
|
var _strong_action_font: Font = null
|
|
var _sandbox_theme: Variant = null
|
|
var _gallery_tab: String = "backgrounds"
|
|
var _gallery_tab_group: ButtonGroup
|
|
var _filter_chip_group: ButtonGroup
|
|
var _artwork_filter_buttons: Array[Button] = []
|
|
var _tag_id: String = "all"
|
|
var _current_sky_id: String = ""
|
|
var _sky_card_group: ButtonGroup
|
|
var _active_columns: int = COLUMNS
|
|
var _page_capacity: int = PAGE_SIZE
|
|
var _effective_cell_size: Vector2 = CELL_MIN_SIZE
|
|
var _surface_cell_size: Vector2 = CELL_MIN_SIZE
|
|
var _surface_color_history: Array[Color] = []
|
|
var _setup_view: VBoxContainer
|
|
var _color_picker_view: StageSurfaceColorPickerView
|
|
var _setup_kind: StringName = &""
|
|
var _setup_finish: StringName = &""
|
|
var _setup_color: Color = Color.WHITE
|
|
var _setup_original_color: Color = Color.WHITE
|
|
var _setup_color_chosen: bool = false
|
|
var _color_picker_open: bool = false
|
|
var _setup_preview: StageSurfacePreview
|
|
var _setup_color_controls: StageSurfaceColorControls
|
|
var _setup_cancel_button: Button
|
|
var _setup_draw_button: Button
|
|
var _color_back_button: Button
|
|
var _color_use_button: Button
|
|
|
|
@onready var _title_label: Label = %TitleLabel
|
|
@onready var _grid: GridContainer = %GridContainer
|
|
@onready var _content_stack: VBoxContainer = %ContentStack
|
|
@onready var _grid_scroll: ScrollContainer = %GridScroll
|
|
@onready var _surface_cards_row: HBoxContainer = %SurfaceCardsRow
|
|
@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
|
|
@onready var _footer_spacer: Control = %FooterSpacer
|
|
@onready var _tabs: HFlowContainer = %GalleryTabs
|
|
@onready var _backgrounds_tab: Button = %BackgroundsTab
|
|
@onready var _artwork_tab: Button = %ArtworkTab
|
|
@onready var _filter_chips: HFlowContainer = %ArtworkFilters
|
|
@onready var _make_artwork_button: Button = %MakeArtworkButton
|
|
@onready var _edit_scenery_button: Button = %EditSceneryButton
|
|
@onready var _title_bar: Control = $MarginContainer/VBoxContainer/TitleBar
|
|
@onready var _footer: Control = $MarginContainer/VBoxContainer/Footer
|
|
|
|
|
|
func _ready() -> void:
|
|
exclusive = true
|
|
_gallery_tab_group = ButtonGroup.new()
|
|
_gallery_tab_group.allow_unpress = false
|
|
_filter_chip_group = ButtonGroup.new()
|
|
_filter_chip_group.allow_unpress = false
|
|
for tab: Button in [_backgrounds_tab, _artwork_tab]:
|
|
tab.toggle_mode = true
|
|
tab.button_group = _gallery_tab_group
|
|
_build_surface_setup_view()
|
|
_rebuild_artwork_filter_chips()
|
|
_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())
|
|
_backgrounds_tab.pressed.connect(func() -> void: _set_gallery_tab("backgrounds"))
|
|
_artwork_tab.pressed.connect(func() -> void: _set_gallery_tab("artwork"))
|
|
_make_artwork_button.pressed.connect(func() -> void: make_artwork_requested.emit())
|
|
_edit_scenery_button.pressed.connect(func() -> void: edit_scenery_requested.emit())
|
|
get_tree().root.size_changed.connect(_on_root_size_changed)
|
|
_apply_chooser_visual_style()
|
|
|
|
|
|
func _on_root_size_changed() -> void:
|
|
if visible:
|
|
_update_popup_layout()
|
|
_rebuild()
|
|
|
|
|
|
func open(p_kind: String, entries: Array[Dictionary], current_sky_id: String = "") -> void:
|
|
kind = p_kind
|
|
_setup_kind = &""
|
|
_color_picker_open = false
|
|
_setup_view.visible = false
|
|
if _color_picker_view != null:
|
|
_color_picker_view.visible = false
|
|
_current_sky_id = current_sky_id if kind == "scenery" else ""
|
|
_title_label.text = "Choose Your Stickman" if kind == "stickman" else ("Choose Scenery" if kind == "scenery" else ("Choose a starter scene" if kind == "starters" else "Choose a Prop"))
|
|
_apply_chooser_visual_style()
|
|
_tabs.visible = kind == "scenery"
|
|
_filter_chips.visible = false
|
|
_make_artwork_button.visible = false
|
|
_edit_scenery_button.visible = false
|
|
_browse_button.visible = kind == "stickman" or kind == "prop" or kind == "scenery"
|
|
_refresh_button.visible = kind == "stickman" or kind == "prop" or kind == "scenery"
|
|
_empty_label.text = "No scenery found. Choose Make artwork to draw a new piece." if kind == "scenery" else ("No props found. Browse for a .prop file." if kind == "prop" else ("No starter scenes are available." if kind == "starters" else "No stickmen found."))
|
|
_gallery_tab = "backgrounds"
|
|
_tag_id = "all"
|
|
_backgrounds_tab.set_pressed_no_signal(true)
|
|
_artwork_tab.set_pressed_no_signal(false)
|
|
set_entries(entries)
|
|
if kind == "scenery":
|
|
_set_gallery_tab("backgrounds")
|
|
popup_centered(size)
|
|
|
|
|
|
func set_entries(entries: Array[Dictionary]) -> void:
|
|
_entries = entries
|
|
if kind == "scenery":
|
|
_rebuild_artwork_filter_chips()
|
|
_thumbnails.clear()
|
|
_cell_texrects.clear()
|
|
_page = 0
|
|
_update_popup_layout()
|
|
_rebuild()
|
|
|
|
|
|
func set_surface_color_history(colors: Array[Color]) -> void:
|
|
_surface_color_history = colors.duplicate()
|
|
if _setup_color_controls != null and is_instance_valid(_setup_color_controls):
|
|
_setup_color_controls.set_recent_colors(_surface_color_history)
|
|
|
|
|
|
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 set_current_sky(sky_id: StringName) -> void:
|
|
var next_id: String = String(sky_id)
|
|
if kind != "scenery" or _current_sky_id == next_id:
|
|
return
|
|
_current_sky_id = next_id
|
|
if _gallery_tab == "backgrounds":
|
|
_rebuild()
|
|
|
|
|
|
func close() -> void:
|
|
hide()
|
|
|
|
|
|
func apply_font(ui_font: Font, emoji_font: Font) -> void:
|
|
_ui_font = ui_font
|
|
_emoji_font = emoji_font
|
|
var base_font: Font = ui_font if ui_font != null else emoji_font
|
|
_strong_ui_font = _make_strong_font(base_font, 0.3)
|
|
_strong_action_font = _make_strong_font(base_font, 0.65)
|
|
var controls: Array = [
|
|
_title_label, _empty_label, _page_label,
|
|
_prev_button, _next_button, _browse_button, _refresh_button, _close_button,
|
|
_edit_scenery_button, _make_artwork_button,
|
|
_backgrounds_tab, _artwork_tab,
|
|
_setup_view.get_node_or_null("SetupHeading") if _setup_view != null else null,
|
|
get_meta("setup_helper", null) as Control,
|
|
_setup_cancel_button, _setup_draw_button,
|
|
_color_back_button, _color_use_button,
|
|
]
|
|
for c: Control in controls:
|
|
_apply_font_to(c)
|
|
_apply_chooser_visual_style()
|
|
|
|
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 wall_choices: StagePanelChoiceGroup = get_meta("wall_look_group", null) as StagePanelChoiceGroup
|
|
var ground_choices: StagePanelChoiceGroup = get_meta("ground_look_group", null) as StagePanelChoiceGroup
|
|
if wall_choices != null:
|
|
wall_choices.apply_theme(theme as SandboxTheme)
|
|
if ground_choices != null:
|
|
ground_choices.apply_theme(theme as SandboxTheme)
|
|
if _setup_color_controls != null:
|
|
_setup_color_controls.configure(_setup_color, _surface_color_history, _preset_surface_colors(), theme as SandboxTheme)
|
|
if _color_picker_view != null:
|
|
_color_picker_view.configure(_setup_kind, _setup_finish, _setup_color, theme as SandboxTheme)
|
|
_apply_chooser_visual_style()
|
|
|
|
|
|
func _build_surface_setup_view() -> void:
|
|
_setup_view = VBoxContainer.new()
|
|
_setup_view.name = "SurfaceSetupView"
|
|
_setup_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_setup_view.add_theme_constant_override("separation", _theme_integer("popups.asset_selector.setup_gap", 8, 4, 24))
|
|
_setup_view.visible = false
|
|
_content_stack.add_child(_setup_view)
|
|
var heading := Label.new()
|
|
heading.name = "SetupHeading"
|
|
heading.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
heading.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
heading.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_heading_size", 26, 18, 48))
|
|
_setup_view.add_child(heading)
|
|
var wall_choices: StagePanelChoiceGroup = STAGE_PANEL_CHOICE_GROUP.new() as StagePanelChoiceGroup
|
|
wall_choices.name = "WallLookChoices"
|
|
wall_choices.configure_layout(
|
|
false,
|
|
float(_theme_integer("popups.asset_selector.setup_choice_height", 68, 48, 104)),
|
|
_theme_integer("popups.asset_selector.setup_choice_font_size", 22, 16, 40),
|
|
_theme_integer("popups.asset_selector.setup_choice_corner_radius", 22, 8, 32),
|
|
_theme_integer("popups.asset_selector.setup_choice_border_width", 2, 1, 5)
|
|
)
|
|
wall_choices.add_choice(&"finish_wall_paint", "Paint", _make_look_cue(&"wall", &"finish_wall_paint"))
|
|
wall_choices.add_choice(&"finish_wall_brick", "Brick", _make_look_cue(&"wall", &"finish_wall_brick"))
|
|
wall_choices.add_choice(&"finish_wall_wood", "Wood", _make_look_cue(&"wall", &"finish_wall_wood"))
|
|
wall_choices.choice_selected.connect(_on_setup_look_choice)
|
|
wall_choices.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_setup_view.add_child(wall_choices)
|
|
var ground_choices: StagePanelChoiceGroup = STAGE_PANEL_CHOICE_GROUP.new() as StagePanelChoiceGroup
|
|
ground_choices.name = "BackgroundGroundLookChoices"
|
|
ground_choices.configure_layout(
|
|
false,
|
|
float(_theme_integer("popups.asset_selector.setup_choice_height", 68, 48, 104)),
|
|
_theme_integer("popups.asset_selector.setup_choice_font_size", 22, 16, 40),
|
|
_theme_integer("popups.asset_selector.setup_choice_corner_radius", 22, 8, 32),
|
|
_theme_integer("popups.asset_selector.setup_choice_border_width", 2, 1, 5)
|
|
)
|
|
ground_choices.add_choice(&"finish_background_grass", "Grass", _make_look_cue(&"background_ground", &"finish_background_grass"))
|
|
ground_choices.add_choice(&"finish_background_concrete", "Concrete", _make_look_cue(&"background_ground", &"finish_background_concrete"))
|
|
ground_choices.add_choice(&"finish_background_carpet", "Carpet", _make_look_cue(&"background_ground", &"finish_background_carpet"))
|
|
ground_choices.choice_selected.connect(_on_setup_look_choice)
|
|
ground_choices.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_setup_view.add_child(ground_choices)
|
|
set_meta("wall_look_group", wall_choices)
|
|
set_meta("ground_look_group", ground_choices)
|
|
var preview_frame := PanelContainer.new()
|
|
preview_frame.name = "SurfacePreviewFrame"
|
|
var preview_size: Vector2 = _theme_vector2("popups.asset_selector.surface_preview_size", Vector2(420.0, 132.0))
|
|
preview_size.y = minf(preview_size.y, 96.0)
|
|
preview_frame.custom_minimum_size.y = preview_size.y + 10.0
|
|
preview_frame.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_setup_preview = SURFACE_PREVIEW.new() as StageSurfacePreview
|
|
_setup_preview.set_rounded_frame(
|
|
float(_theme_integer("popups.asset_selector.surface_corner_radius", 14, 4, 32)),
|
|
_theme_color("popups.style_defaults.bg_color", Color("#202633FA"))
|
|
)
|
|
_setup_preview.custom_minimum_size = preview_size
|
|
_setup_preview.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
preview_frame.add_child(_setup_preview)
|
|
_setup_view.add_child(preview_frame)
|
|
_setup_color_controls = SURFACE_COLOR_CONTROLS.new() as StageSurfaceColorControls
|
|
_setup_color_controls.name = "SurfaceColorControls"
|
|
_setup_color_controls.set_compact_mode(true)
|
|
_setup_color_controls.color_selected.connect(_on_setup_color_selected)
|
|
_setup_color_controls.custom_color_requested.connect(_open_setup_color_view)
|
|
_setup_view.add_child(_setup_color_controls)
|
|
var helper := Label.new()
|
|
helper.name = "SurfaceRoleHelper"
|
|
helper.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
helper.add_theme_color_override("font_color", _theme_color("global.muted_text_color", Color("#AAB2C0")))
|
|
_setup_view.add_child(helper)
|
|
_color_picker_view = SURFACE_COLOR_PICKER_VIEW.new() as StageSurfaceColorPickerView
|
|
_color_picker_view.name = "SurfaceColorPickerView"
|
|
_color_picker_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
_color_picker_view.preview_color_changed.connect(_on_setup_color_preview)
|
|
_color_picker_view.visible = false
|
|
_content_stack.add_child(_color_picker_view)
|
|
_setup_cancel_button = Button.new()
|
|
_setup_cancel_button.name = "CancelSurfaceSetupButton"
|
|
_setup_cancel_button.text = "Cancel"
|
|
_setup_cancel_button.custom_minimum_size = Vector2(116.0, float(_theme_integer("stage_panels.action.min_target_size", 48, 44, 96)))
|
|
_setup_cancel_button.focus_mode = Control.FOCUS_ALL
|
|
_setup_cancel_button.pressed.connect(_cancel_surface_setup)
|
|
_footer.add_child(_setup_cancel_button)
|
|
_setup_draw_button = Button.new()
|
|
_setup_draw_button.name = "DrawSurfaceButton"
|
|
_setup_draw_button.custom_minimum_size = Vector2(244.0, float(_theme_integer("popups.asset_selector.tab_height", 60, 48, 96)))
|
|
_setup_draw_button.focus_mode = Control.FOCUS_ALL
|
|
_setup_draw_button.pressed.connect(_draw_selected_surface)
|
|
_footer.add_child(_setup_draw_button)
|
|
_color_back_button = Button.new()
|
|
_color_back_button.name = "BackToSurfaceButton"
|
|
_color_back_button.text = "Back to wall panel"
|
|
_color_back_button.custom_minimum_size = Vector2(200.0, float(_theme_integer("stage_panels.action.min_target_size", 48, 44, 96)))
|
|
_color_back_button.focus_mode = Control.FOCUS_ALL
|
|
_color_back_button.pressed.connect(_back_from_setup_color_view)
|
|
_footer.add_child(_color_back_button)
|
|
_color_use_button = Button.new()
|
|
_color_use_button.name = "UseColorButton"
|
|
_color_use_button.text = "Use color"
|
|
_color_use_button.custom_minimum_size = Vector2(160.0, float(_theme_integer("stage_panels.choice.minimum_height", 56, 44, 112)))
|
|
_color_use_button.focus_mode = Control.FOCUS_ALL
|
|
_color_use_button.pressed.connect(_use_setup_color)
|
|
_footer.add_child(_color_use_button)
|
|
set_meta("setup_heading", heading)
|
|
set_meta("setup_helper", helper)
|
|
_apply_font_to(heading)
|
|
_apply_font_to(helper)
|
|
_apply_font_to(_setup_cancel_button)
|
|
_apply_font_to(_setup_draw_button)
|
|
_apply_font_to(_color_back_button)
|
|
_apply_font_to(_color_use_button)
|
|
_apply_chooser_visual_style()
|
|
for choice: StagePanelChoiceGroup in [wall_choices, ground_choices]:
|
|
choice.apply_theme(_sandbox_theme as SandboxTheme)
|
|
_apply_font_to(choice)
|
|
|
|
|
|
func _make_look_cue(kind: StringName, finish: StringName) -> Control:
|
|
var preview: StageSurfacePreview = SURFACE_PREVIEW.new() as StageSurfacePreview
|
|
var cue_size: Vector2 = _theme_vector2("popups.asset_selector.look_cue_size", Vector2(80.0, 42.0))
|
|
cue_size.x = minf(cue_size.x, 56.0)
|
|
cue_size.y = minf(cue_size.y, 36.0)
|
|
preview.custom_minimum_size = cue_size
|
|
preview.set_rounded_frame(
|
|
float(_theme_integer("popups.asset_selector.look_corner_radius", 8, 2, 20)),
|
|
_theme_color("popups.style_defaults.bg_color", Color("#202633FA"))
|
|
)
|
|
preview.configure(kind, finish, SURFACE_RENDERER.default_tint(finish))
|
|
return preview
|
|
|
|
|
|
func _open_surface_setup(kind: StringName) -> void:
|
|
_setup_kind = kind
|
|
_setup_color_chosen = false
|
|
_setup_finish = &"finish_wall_paint" if kind == &"wall" else &"finish_background_grass"
|
|
_setup_color = SURFACE_RENDERER.default_tint(_setup_finish)
|
|
_setup_original_color = _setup_color
|
|
var wall_choices: StagePanelChoiceGroup = get_meta("wall_look_group") as StagePanelChoiceGroup
|
|
var ground_choices: StagePanelChoiceGroup = get_meta("ground_look_group") as StagePanelChoiceGroup
|
|
wall_choices.visible = kind == &"wall"
|
|
ground_choices.visible = kind == &"background_ground"
|
|
wall_choices.select_no_signal(_setup_finish)
|
|
ground_choices.select_no_signal(_setup_finish)
|
|
(get_meta("setup_heading") as Label).text = "Wall panel" if kind == &"wall" else "Background ground"
|
|
var setup_helper: Label = get_meta("setup_helper") as Label
|
|
setup_helper.text = "Looks like a wall · Stickmen can pass through." if kind == &"wall" else ""
|
|
setup_helper.visible = kind == &"wall"
|
|
_setup_draw_button.text = "Draw wall panel" if kind == &"wall" else "Draw background ground"
|
|
_color_back_button.text = "Back to wall panel" if kind == &"wall" else "Back to background ground"
|
|
_setup_color_controls.visible = _supports_setup_color()
|
|
_setup_color_controls.configure(_setup_color, _surface_color_history, _preset_surface_colors(), _sandbox_theme as SandboxTheme)
|
|
_update_setup_preview()
|
|
_page = 0
|
|
_rebuild()
|
|
# The setup controls are first measured before the containers have recalculated
|
|
# their minimum sizes. Re-measure after the next layout pass so the popup can
|
|
# shrink to the setup content instead of keeping the gallery height.
|
|
_update_popup_layout()
|
|
call_deferred("_update_popup_layout_after_layout")
|
|
_setup_draw_button.grab_focus()
|
|
|
|
|
|
func _on_setup_look_choice(id: StringName) -> void:
|
|
var supported: bool = SURFACE_RENDERER.is_wall_finish(id) if _setup_kind == &"wall" else SURFACE_RENDERER.is_background_finish(id)
|
|
if not supported:
|
|
return
|
|
_setup_finish = id
|
|
if not _setup_color_chosen:
|
|
_setup_color = SURFACE_RENDERER.default_tint(_setup_finish)
|
|
_setup_color_controls.visible = _supports_setup_color()
|
|
_setup_color_controls.set_selected_no_signal(_setup_color)
|
|
_update_setup_preview()
|
|
_update_popup_layout()
|
|
|
|
|
|
func _supports_setup_color() -> bool:
|
|
return _setup_kind == &"wall" or _setup_finish == &"finish_background_carpet"
|
|
|
|
|
|
func _on_setup_color_selected(color: Color) -> void:
|
|
_setup_color = _opaque_color(color)
|
|
_setup_color_chosen = true
|
|
_update_setup_preview()
|
|
surface_color_confirmed.emit(_setup_color)
|
|
|
|
|
|
func _update_setup_preview() -> void:
|
|
if _setup_preview == null:
|
|
return
|
|
_setup_preview.configure(_setup_kind, _setup_finish, _setup_color)
|
|
_setup_color_controls.set_selected_no_signal(_setup_color)
|
|
|
|
|
|
func _preset_surface_colors() -> Array[Color]:
|
|
if _sandbox_theme == null:
|
|
return [Color.WHITE, Color.BLACK, Color("#4C9AFF"), Color("#29A36A"), Color("#FFD84D"), Color("#D94F70")]
|
|
return (_sandbox_theme as SandboxTheme).swatches()
|
|
|
|
|
|
func _open_setup_color_view() -> void:
|
|
if _setup_kind.is_empty() or not _supports_setup_color():
|
|
return
|
|
_setup_original_color = _setup_color
|
|
_color_picker_view.configure(_setup_kind, _setup_finish, _setup_original_color, _sandbox_theme as SandboxTheme)
|
|
_color_picker_open = true
|
|
_title_label.text = "Choose color"
|
|
_rebuild()
|
|
_update_popup_layout()
|
|
call_deferred("_update_popup_layout_after_layout")
|
|
_color_picker_view.focus_picker()
|
|
|
|
|
|
func _on_setup_color_preview(color: Color) -> void:
|
|
_setup_color = _opaque_color(color)
|
|
_update_setup_preview()
|
|
|
|
|
|
func _use_setup_color() -> void:
|
|
_setup_color = _opaque_color(_color_picker_view.get_color())
|
|
_setup_color_chosen = true
|
|
_update_setup_preview()
|
|
surface_color_confirmed.emit(_setup_color)
|
|
_close_setup_color_view()
|
|
_setup_color_controls.focus_custom_button()
|
|
|
|
|
|
func _back_from_setup_color_view() -> void:
|
|
_setup_color = _setup_original_color
|
|
_setup_color_controls.set_selected_no_signal(_setup_color)
|
|
_update_setup_preview()
|
|
_close_setup_color_view()
|
|
_setup_color_controls.focus_custom_button()
|
|
|
|
|
|
func _close_setup_color_view() -> void:
|
|
_color_picker_open = false
|
|
_title_label.text = "Choose Scenery"
|
|
_tabs.visible = kind == "scenery"
|
|
_rebuild()
|
|
_update_popup_layout()
|
|
call_deferred("_update_popup_layout_after_layout")
|
|
|
|
|
|
func _opaque_color(color: Color) -> Color:
|
|
return Color(color.r, color.g, color.b, 1.0)
|
|
|
|
|
|
func _draw_selected_surface() -> void:
|
|
if _setup_kind.is_empty():
|
|
return
|
|
var background_kind: String = "wall" if _setup_kind == &"wall" else "background_ground"
|
|
item_selected.emit({"background_kind": background_kind, "finish_id": String(_setup_finish), "color": _setup_color})
|
|
|
|
|
|
func _cancel_surface_setup() -> void:
|
|
_color_picker_open = false
|
|
_color_picker_view.visible = false
|
|
_setup_kind = &""
|
|
_setup_view.visible = false
|
|
cancelled.emit()
|
|
|
|
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()
|
|
if _color_picker_open:
|
|
_back_from_setup_color_view()
|
|
else:
|
|
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 _rebuild_artwork_filter_chips() -> void:
|
|
if _filter_chips == null:
|
|
return
|
|
for child: Node in _filter_chips.get_children():
|
|
_filter_chips.remove_child(child)
|
|
child.queue_free()
|
|
_artwork_filter_buttons.clear()
|
|
_filter_chip_group = ButtonGroup.new()
|
|
_filter_chip_group.allow_unpress = false
|
|
var filter_ids: Array[String] = ARTWORK_FILTER_IDS.duplicate()
|
|
var filter_labels: Array[String] = ARTWORK_FILTER_LABELS.duplicate()
|
|
if _has_unsorted_artwork():
|
|
filter_ids.append("unsorted")
|
|
filter_labels.append("Unsorted")
|
|
if not filter_ids.has(_tag_id):
|
|
_tag_id = "all"
|
|
for index: int in range(filter_ids.size()):
|
|
var filter_id: String = filter_ids[index]
|
|
var chip: Button = Button.new()
|
|
chip.name = "ArtworkFilter_%s" % filter_id.capitalize()
|
|
chip.toggle_mode = true
|
|
chip.button_group = _filter_chip_group
|
|
chip.focus_mode = Control.FOCUS_ALL
|
|
chip.custom_minimum_size.y = float(_theme_integer("popups.asset_selector.filter_chip_height", 48, 44, 80))
|
|
chip.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
|
chip.pressed.connect(func() -> void: _on_artwork_filter_selected(filter_id))
|
|
_filter_chips.add_child(chip)
|
|
_artwork_filter_buttons.append(chip)
|
|
_sync_artwork_filter_chips(filter_ids, filter_labels)
|
|
_apply_chooser_visual_style()
|
|
|
|
|
|
func _sync_artwork_filter_chips(filter_ids: Array[String] = [], filter_labels: Array[String] = []) -> void:
|
|
var ids: Array[String] = filter_ids if not filter_ids.is_empty() else ARTWORK_FILTER_IDS.duplicate()
|
|
var labels: Array[String] = filter_labels if not filter_labels.is_empty() else ARTWORK_FILTER_LABELS.duplicate()
|
|
if ids.size() != _artwork_filter_buttons.size() or labels.size() != _artwork_filter_buttons.size():
|
|
return
|
|
for index: int in range(_artwork_filter_buttons.size()):
|
|
var chip: Button = _artwork_filter_buttons[index]
|
|
var selected: bool = ids[index] == _tag_id
|
|
chip.text = ("✓ " if selected else "") + labels[index]
|
|
chip.set_pressed_no_signal(selected)
|
|
|
|
|
|
func _on_artwork_filter_selected(filter_id: String) -> void:
|
|
_tag_id = filter_id
|
|
_sync_artwork_filter_chips(_current_filter_ids(), _current_filter_labels())
|
|
_page = 0
|
|
_update_popup_layout(true)
|
|
_rebuild()
|
|
|
|
|
|
func _current_filter_ids() -> Array[String]:
|
|
var ids: Array[String] = ARTWORK_FILTER_IDS.duplicate()
|
|
if _has_unsorted_artwork():
|
|
ids.append("unsorted")
|
|
return ids
|
|
|
|
|
|
func _current_filter_labels() -> Array[String]:
|
|
var labels: Array[String] = ARTWORK_FILTER_LABELS.duplicate()
|
|
if _has_unsorted_artwork():
|
|
labels.append("Unsorted")
|
|
return labels
|
|
|
|
|
|
func _has_unsorted_artwork() -> bool:
|
|
for entry: Dictionary in _entries:
|
|
if String(entry.get("gallery_tab", "artwork")) != "artwork":
|
|
continue
|
|
var tags: Variant = entry.get("tags", [])
|
|
if not tags is Array or (tags as Array).is_empty():
|
|
return true
|
|
return false
|
|
|
|
|
|
func _set_gallery_tab(tab: String) -> void:
|
|
_color_picker_open = false
|
|
_setup_kind = &""
|
|
_setup_view.visible = false
|
|
_color_picker_view.visible = false
|
|
_gallery_tab = tab
|
|
_backgrounds_tab.set_pressed_no_signal(tab == "backgrounds")
|
|
_artwork_tab.set_pressed_no_signal(tab == "artwork")
|
|
_filter_chips.visible = kind == "scenery" and tab == "artwork"
|
|
_make_artwork_button.visible = kind == "scenery" and tab == "artwork"
|
|
_edit_scenery_button.visible = kind == "scenery"
|
|
_browse_button.visible = kind == "stickman" or kind == "prop"
|
|
_refresh_button.visible = kind == "stickman" or kind == "prop"
|
|
_empty_label.text = "No artwork here yet." if tab == "artwork" else "Choose a Sky look or wall finish."
|
|
_page = 0
|
|
_update_popup_layout(true)
|
|
_rebuild()
|
|
|
|
|
|
func _filtered_entries() -> Array[Dictionary]:
|
|
if kind != "scenery":
|
|
return _entries
|
|
var filtered: Array[Dictionary] = []
|
|
for entry: Dictionary in _entries:
|
|
if String(entry.get("gallery_tab", "artwork")) != _gallery_tab:
|
|
continue
|
|
if _gallery_tab == "artwork" and _tag_id != "all":
|
|
var tags: Variant = entry.get("tags", [])
|
|
var tag_list: Array = tags as Array if tags is Array else []
|
|
if _tag_id == "unsorted":
|
|
if not tag_list.is_empty():
|
|
continue
|
|
elif _tag_id == "roads":
|
|
if not tag_list.has("roads") and not tag_list.has("paths"):
|
|
continue
|
|
elif not tag_list.has(_tag_id):
|
|
continue
|
|
filtered.append(entry)
|
|
return filtered
|
|
|
|
|
|
func _on_prev() -> void:
|
|
if _page > 0:
|
|
_page -= 1
|
|
_update_popup_layout()
|
|
_rebuild()
|
|
|
|
|
|
func _on_next() -> void:
|
|
if (_page + 1) * _page_capacity < _filtered_entries().size():
|
|
_page += 1
|
|
_update_popup_layout()
|
|
_rebuild()
|
|
|
|
|
|
func _page_count() -> int:
|
|
if _is_scenery_background_gallery() or _is_scenery_artwork_gallery():
|
|
return 1
|
|
var entries: Array[Dictionary] = _filtered_entries()
|
|
if entries.is_empty():
|
|
return 1
|
|
return int(ceil(float(entries.size()) / float(_page_capacity)))
|
|
|
|
|
|
func _usable_screen_size() -> Vector2:
|
|
var root_window: Window = get_tree().root
|
|
var available: Vector2 = Vector2(root_window.size)
|
|
var screen_index: int = DisplayServer.window_get_current_screen()
|
|
var usable_rect: Rect2i = DisplayServer.screen_get_usable_rect(screen_index)
|
|
if usable_rect.size.x > 0 and usable_rect.size.y > 0:
|
|
available.x = minf(available.x, float(usable_rect.size.x))
|
|
available.y = minf(available.y, float(usable_rect.size.y))
|
|
return available
|
|
|
|
|
|
func _update_popup_layout(keep_visible_position: bool = false) -> void:
|
|
var previous_position: Vector2i = position
|
|
var in_color_picker: bool = _color_picker_open
|
|
var in_setup: bool = not _setup_kind.is_empty() and not in_color_picker
|
|
var scenery_background_gallery: bool = _is_scenery_background_gallery() and not in_setup and not in_color_picker
|
|
var scenery_artwork_gallery: bool = _is_scenery_artwork_gallery() and not in_setup and not in_color_picker
|
|
var filtered: Array[Dictionary] = _filtered_entries()
|
|
_tabs.visible = kind == "scenery" and not in_color_picker
|
|
_filter_chips.visible = scenery_artwork_gallery
|
|
_position_footer()
|
|
_close_button.visible = not in_setup and not in_color_picker
|
|
_setup_cancel_button.visible = in_setup
|
|
_setup_draw_button.visible = in_setup
|
|
_setup_view.visible = in_setup
|
|
_color_picker_view.visible = in_color_picker
|
|
_surface_cards_row.visible = scenery_background_gallery and _has_surface_entries(filtered)
|
|
_color_back_button.visible = in_color_picker
|
|
_color_use_button.visible = in_color_picker
|
|
_edit_scenery_button.visible = kind == "scenery" and not in_setup and not in_color_picker
|
|
_footer_spacer.visible = scenery_artwork_gallery
|
|
_empty_label.visible = filtered.is_empty() and not in_setup and not in_color_picker
|
|
_grid.visible = not filtered.is_empty() and not in_setup and not in_color_picker
|
|
_grid_scroll.visible = not filtered.is_empty() or in_setup or in_color_picker
|
|
_footer.alignment = BoxContainer.ALIGNMENT_BEGIN if scenery_artwork_gallery else (BoxContainer.ALIGNMENT_END if in_setup or in_color_picker else BoxContainer.ALIGNMENT_CENTER)
|
|
_footer.add_theme_constant_override(
|
|
"separation",
|
|
_theme_integer("popups.asset_selector.footer_gap", 16, 8, 32) if in_setup or in_color_picker or kind == "scenery" else 8
|
|
)
|
|
var available: Vector2 = _usable_screen_size()
|
|
var width_ratio: float = 0.98 if in_setup else 0.94
|
|
var max_width: float = maxf(1.0, minf(available.x * width_ratio, available.x - POPUP_SAFE_SCREEN_MARGIN))
|
|
var max_height: float = maxf(1.0, minf(available.y * 0.86, available.y - POPUP_SAFE_SCREEN_MARGIN * 2.0))
|
|
var preferred_width: float = CELL_MIN_SIZE.x * COLUMNS + GRID_H_SEPARATION * (COLUMNS - 1) + POPUP_SIDE_PADDING
|
|
var preferred_height: float = max_height
|
|
if in_color_picker:
|
|
var color_dialog_size: Vector2 = _theme_vector2("popups.dialog.surface_color_size", Vector2(560.0, 640.0))
|
|
preferred_width = color_dialog_size.x
|
|
preferred_height = color_dialog_size.y
|
|
var popup_width: float = minf(preferred_width, max_width)
|
|
var grid_width: float = maxf(1.0, popup_width - POPUP_SIDE_PADDING)
|
|
_active_columns = clampi(floori((grid_width + GRID_H_SEPARATION) / (CELL_MIN_SIZE.x + GRID_H_SEPARATION)), 1, COLUMNS)
|
|
_page_capacity = maxi(1, filtered.size()) if scenery_background_gallery or scenery_artwork_gallery else _active_columns * ROWS
|
|
_grid.columns = _active_columns
|
|
var card_width: float = minf(CELL_MIN_SIZE.x, (grid_width - GRID_H_SEPARATION * (_active_columns - 1)) / float(_active_columns))
|
|
var card_height_limit: float = 248.0 if kind == "starters" else CELL_MIN_SIZE.y
|
|
var card_height: float = minf(card_height_limit, card_width + (58.0 if kind == "starters" else 30.0))
|
|
_effective_cell_size = Vector2(maxf(96.0, card_width), maxf(180.0, card_height))
|
|
var surface_width: float = minf(_effective_cell_size.x, maxf(96.0, (grid_width - GRID_H_SEPARATION) * 0.5))
|
|
var surface_height: float = maxf(180.0, minf(card_height_limit, surface_width + 30.0))
|
|
_surface_cell_size = Vector2(surface_width, surface_height)
|
|
var filtered_count: int = filtered.size()
|
|
var page_count: int = maxi(1, int(ceil(float(filtered_count) / float(_page_capacity))))
|
|
_page = clampi(_page, 0, page_count - 1)
|
|
var page_start: int = _page * _page_capacity
|
|
var page_items: int = clampi(filtered_count - page_start, 0, _page_capacity)
|
|
var grid_item_count: int = _count_sky_entries(filtered) if scenery_background_gallery else page_items
|
|
var grid_rows: int = int(ceil(float(grid_item_count) / float(_active_columns))) if grid_item_count > 0 else 0
|
|
var body_height: float
|
|
if in_color_picker:
|
|
_color_picker_view.set_available_size(Vector2(popup_width, minf(preferred_height, max_height)))
|
|
body_height = maxf(220.0, _color_picker_view.get_combined_minimum_size().y)
|
|
elif in_setup:
|
|
body_height = maxf(180.0, _visible_content_minimum_height(_setup_view))
|
|
else:
|
|
body_height = maxf(120.0, float(grid_rows) * _effective_cell_size.y + float(maxi(0, grid_rows - 1)) * GRID_V_SEPARATION)
|
|
if scenery_background_gallery and _has_surface_entries(filtered):
|
|
body_height += _surface_cell_size.y + GRID_V_SEPARATION
|
|
if page_items == 0:
|
|
body_height = maxf(body_height, _empty_label.get_combined_minimum_size().y)
|
|
_tabs.custom_minimum_size.x = grid_width
|
|
_filter_chips.custom_minimum_size.x = grid_width if _filter_chips.visible else 0.0
|
|
var fixed_height: float = _title_bar.get_combined_minimum_size().y + _footer.get_combined_minimum_size().y + POPUP_VERTICAL_PADDING
|
|
var visible_sections: int = 3
|
|
if _tabs.visible:
|
|
# HFlowContainer can report zero height before its first layout pass. Use
|
|
# the child button target as a floor so the first popup measurement reserves
|
|
# the tab row instead of letting the gallery scroll area consume that space.
|
|
fixed_height += maxf(
|
|
_tabs.get_combined_minimum_size().y,
|
|
float(_theme_integer("popups.asset_selector.tab_height", 60, 48, 96))
|
|
)
|
|
visible_sections += 1
|
|
if _filter_chips.visible:
|
|
fixed_height += _filter_chips.get_combined_minimum_size().y
|
|
visible_sections += 1
|
|
fixed_height += float(visible_sections - 1) * 8.0
|
|
if scenery_background_gallery:
|
|
var has_surface_row: bool = _has_surface_entries(filtered)
|
|
var content_rows: int = grid_rows + (1 if has_surface_row else 0)
|
|
var row_gaps: float = float(maxi(0, grid_rows - 1)) * GRID_V_SEPARATION
|
|
if has_surface_row and grid_rows > 0:
|
|
row_gaps += GRID_V_SEPARATION
|
|
if content_rows > 0:
|
|
var available_body_height: float = maxf(1.0, max_height - fixed_height)
|
|
var max_row_height: float = (available_body_height - row_gaps) / float(content_rows)
|
|
if max_row_height < _effective_cell_size.y or (has_surface_row and max_row_height < _surface_cell_size.y):
|
|
var compact_row_height: float = maxf(180.0, max_row_height)
|
|
if grid_rows > 0:
|
|
_effective_cell_size.y = minf(_effective_cell_size.y, compact_row_height)
|
|
if has_surface_row:
|
|
_surface_cell_size.y = minf(_surface_cell_size.y, compact_row_height)
|
|
body_height = 0.0
|
|
if grid_rows > 0:
|
|
body_height += float(grid_rows) * _effective_cell_size.y + float(maxi(0, grid_rows - 1)) * GRID_V_SEPARATION
|
|
if has_surface_row:
|
|
if grid_rows > 0:
|
|
body_height += GRID_V_SEPARATION
|
|
body_height += _surface_cell_size.y
|
|
if body_height <= 0.0:
|
|
body_height = maxf(120.0, _empty_label.get_combined_minimum_size().y)
|
|
var popup_height: float = minf(fixed_height + body_height, minf(preferred_height, max_height))
|
|
var popup_position: Vector2i = previous_position
|
|
if visible and keep_visible_position:
|
|
var minimum_anchor_height: float = minf(fixed_height + 120.0, minf(preferred_height, max_height))
|
|
var minimum_y: int = mini(roundi(POPUP_SAFE_SCREEN_MARGIN), maxi(0, floori(available.y - minimum_anchor_height)))
|
|
var maximum_y: int = maxi(minimum_y, floori(available.y - minimum_anchor_height - POPUP_SAFE_SCREEN_MARGIN))
|
|
popup_position.y = clampi(previous_position.y, minimum_y, maximum_y)
|
|
var height_below_anchor: float = maxf(1.0, available.y - float(popup_position.y) - POPUP_SAFE_SCREEN_MARGIN)
|
|
popup_height = minf(popup_height, height_below_anchor)
|
|
if in_color_picker:
|
|
var action_width: float = maxf(100.0, (popup_width - POPUP_SIDE_PADDING - 8.0) * 0.5)
|
|
_color_back_button.custom_minimum_size.x = action_width
|
|
_color_use_button.custom_minimum_size.x = action_width
|
|
size = Vector2i(maxi(1, roundi(popup_width)), maxi(1, roundi(popup_height)))
|
|
if visible:
|
|
if keep_visible_position:
|
|
var max_x: int = maxi(0, floori(available.x - float(size.x) - POPUP_SAFE_SCREEN_MARGIN))
|
|
var max_y: int = maxi(0, floori(available.y - float(size.y) - POPUP_SAFE_SCREEN_MARGIN))
|
|
var min_x: int = mini(roundi(POPUP_SAFE_SCREEN_MARGIN), max_x)
|
|
var min_y: int = mini(roundi(POPUP_SAFE_SCREEN_MARGIN), max_y)
|
|
position = Vector2i(
|
|
clampi(previous_position.x, min_x, max_x),
|
|
clampi(popup_position.y, min_y, max_y)
|
|
)
|
|
else:
|
|
popup_centered(size)
|
|
|
|
|
|
func _update_popup_layout_after_layout() -> void:
|
|
await get_tree().process_frame
|
|
if is_inside_tree():
|
|
_update_popup_layout()
|
|
|
|
|
|
func _rebuild() -> void:
|
|
_sky_card_group = ButtonGroup.new()
|
|
for container: Control in [_grid, _surface_cards_row]:
|
|
for child: Node in container.get_children():
|
|
container.remove_child(child)
|
|
child.queue_free()
|
|
_cell_texrects.clear()
|
|
var filtered: Array[Dictionary] = _filtered_entries()
|
|
var in_color_picker: bool = _color_picker_open
|
|
var in_setup: bool = not _setup_kind.is_empty() and not in_color_picker
|
|
var scenery_background_gallery: bool = _is_scenery_background_gallery() and not in_setup and not in_color_picker
|
|
var scenery_artwork_gallery: bool = _is_scenery_artwork_gallery() and not in_setup and not in_color_picker
|
|
_position_footer()
|
|
_close_button.visible = not in_setup and not in_color_picker
|
|
_setup_view.visible = in_setup
|
|
_color_picker_view.visible = in_color_picker
|
|
_filter_chips.visible = scenery_artwork_gallery
|
|
_surface_cards_row.visible = scenery_background_gallery and _has_surface_entries(filtered)
|
|
_setup_cancel_button.visible = in_setup
|
|
_setup_draw_button.visible = in_setup
|
|
_color_back_button.visible = in_color_picker
|
|
_color_use_button.visible = in_color_picker
|
|
_edit_scenery_button.visible = kind == "scenery" and not in_setup and not in_color_picker
|
|
_make_artwork_button.visible = scenery_artwork_gallery
|
|
_browse_button.visible = (kind == "stickman" or kind == "prop") and not in_setup and not in_color_picker
|
|
_refresh_button.visible = (kind == "stickman" or kind == "prop") and not in_setup and not in_color_picker
|
|
_footer_spacer.visible = scenery_artwork_gallery
|
|
_empty_label.visible = filtered.is_empty() and not in_setup and not in_color_picker
|
|
_grid.visible = not filtered.is_empty() and not in_setup and not in_color_picker
|
|
_grid_scroll.visible = not filtered.is_empty() or in_setup or in_color_picker
|
|
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_capacity >= filtered.size()
|
|
_page_label.text = "Page %d/%d" % [_page + 1, total]
|
|
if in_setup or in_color_picker:
|
|
_prev_button.visible = false
|
|
_next_button.visible = false
|
|
_page_label.visible = false
|
|
_browse_button.visible = false
|
|
_refresh_button.visible = false
|
|
_make_artwork_button.visible = false
|
|
return
|
|
if filtered.is_empty():
|
|
return
|
|
if scenery_background_gallery:
|
|
for entry: Dictionary in filtered:
|
|
var background_kind: String = String(entry.get("background_kind", ""))
|
|
if background_kind == "sky":
|
|
_grid.add_child(_build_cell(entry))
|
|
elif background_kind == "wall_setup" or background_kind == "background_ground_setup":
|
|
_surface_cards_row.add_child(_build_cell(entry, _surface_cell_size))
|
|
return
|
|
var bounds := page_bounds(filtered.size(), _page, _page_capacity)
|
|
for i: int in range(int(bounds["start"]), int(bounds["end"])):
|
|
_grid.add_child(_build_cell(filtered[i]))
|
|
|
|
|
|
func _position_footer() -> void:
|
|
var parent := _footer.get_parent() as VBoxContainer
|
|
if parent == null:
|
|
return
|
|
var target_index: int = parent.get_child_count() - 1
|
|
if _footer.get_index() != target_index:
|
|
parent.move_child(_footer, target_index)
|
|
if _close_button.get_parent() == _footer and _close_button.get_index() != 0:
|
|
_footer.move_child(_close_button, 0)
|
|
|
|
|
|
func _is_scenery_background_gallery() -> bool:
|
|
return kind == "scenery" and _gallery_tab == "backgrounds"
|
|
|
|
|
|
func _is_scenery_artwork_gallery() -> bool:
|
|
return kind == "scenery" and _gallery_tab == "artwork"
|
|
|
|
|
|
func _count_sky_entries(entries: Array[Dictionary]) -> int:
|
|
var count: int = 0
|
|
for entry: Dictionary in entries:
|
|
if String(entry.get("background_kind", "")) == "sky":
|
|
count += 1
|
|
return count
|
|
|
|
|
|
func _has_surface_entries(entries: Array[Dictionary]) -> bool:
|
|
for entry: Dictionary in entries:
|
|
var background_kind: String = String(entry.get("background_kind", ""))
|
|
if background_kind == "wall_setup" or background_kind == "background_ground_setup":
|
|
return true
|
|
return false
|
|
|
|
|
|
func _visible_content_minimum_height(container: VBoxContainer) -> float:
|
|
var height: float = 0.0
|
|
var visible_children: int = 0
|
|
for child: Node in container.get_children():
|
|
if not child is Control or not (child as Control).visible:
|
|
continue
|
|
height += (child as Control).get_combined_minimum_size().y
|
|
visible_children += 1
|
|
if visible_children > 1:
|
|
height += float(visible_children - 1) * float(container.get_theme_constant("separation"))
|
|
return height
|
|
|
|
|
|
func _build_cell(entry: Dictionary, requested_size: Vector2 = Vector2.ZERO) -> Control:
|
|
var key := _entry_key(entry)
|
|
var cell: Variant = ASSET_CARD.new()
|
|
# Give scenery cards rounded resting and interaction states. The selected sky
|
|
# state is applied separately below, so opening the gallery stays neutral.
|
|
var categories: Array = entry.get("tags", []) as Array
|
|
var category: String = ", ".join(PackedStringArray(categories)) if not categories.is_empty() else String(entry.get("category", entry.get("material_label", "")))
|
|
var card_size: Vector2 = _effective_cell_size if requested_size == Vector2.ZERO else requested_size
|
|
var is_background_ground_setup: bool = String(entry.get("background_kind", "")) == "background_ground_setup"
|
|
var thumb_height_budget: float = card_size.y - (74.0 if is_background_ground_setup else 64.0)
|
|
var thumb_edge: float = minf(_thumbnail_size.x, minf(card_size.x - 20.0, thumb_height_budget))
|
|
var thumb_size := Vector2.ONE * maxf(72.0, thumb_edge)
|
|
if kind == "starters":
|
|
thumb_size = Vector2.ONE * minf(128.0, maxf(72.0, thumb_edge))
|
|
var description: String = "" if is_background_ground_setup else String(entry.get("description", ""))
|
|
var card_payload: Dictionary = {"placement_id": _entry_key(entry), "label": String(entry.get("name", entry.get("id", ""))), "icon": String(entry.get("icon", "")), "category": category, "description": description, "thumbnail_size": thumb_size}
|
|
if entry.has("background_kind"):
|
|
card_payload["background_kind"] = entry["background_kind"]
|
|
card_payload["finish_id"] = entry.get("finish_id", "")
|
|
card_payload["id"] = entry.get("id", "")
|
|
var is_sky_choice: bool = String(entry.get("background_kind", "")) == "sky"
|
|
var is_current_sky: bool = is_sky_choice and String(entry.get("id", "")) == _current_sky_id
|
|
if is_current_sky:
|
|
card_payload["status"] = "Current Sky"
|
|
card_payload["status_color"] = _theme_color("mode_colors.edit", Color("#22C6FF"))
|
|
cell.configure(card_payload, _thumbnails.get(key, null), null)
|
|
if kind == "scenery":
|
|
var card_button: Button = cell as Button
|
|
var card_border_width: int = _theme_integer("popups.asset_selector.button_border_width", 2, 1, 5)
|
|
card_button.add_theme_stylebox_override("normal", _make_rounded_style(
|
|
_theme_color("stage_panels.choice.neutral_surface", Color("#171B24")),
|
|
_theme_color("stage_panels.choice.border_color", Color("#FFFFFF33")),
|
|
card_border_width,
|
|
_theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
|
))
|
|
card_button.add_theme_stylebox_override("hover", _make_rounded_style(
|
|
_theme_color("stage_panels.choice.hover_surface", Color("#303A4B")),
|
|
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
|
card_border_width + 1,
|
|
_theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
|
))
|
|
card_button.add_theme_stylebox_override("pressed", _make_rounded_style(
|
|
_theme_color("stage_panels.choice.selected_surface", Color("#12394A")),
|
|
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
|
card_border_width + 1,
|
|
_theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
|
))
|
|
card_button.add_theme_stylebox_override("focus", _make_rounded_style(
|
|
Color(0, 0, 0, 0),
|
|
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
|
card_border_width + 1,
|
|
_theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
|
))
|
|
cell.custom_minimum_size = card_size
|
|
cell.focus_mode = Control.FOCUS_ALL
|
|
cell.activated.connect(func(_payload: Dictionary) -> void: _on_cell_pressed(entry))
|
|
if is_sky_choice:
|
|
cell.button_group = _sky_card_group
|
|
cell.set_selected(is_current_sky, _theme_color("stage_panels.choice.selected_surface", Color("#12394A")), _theme_color("mode_colors.edit", Color("#22C6FF")))
|
|
var tex: Texture2D = _thumbnails.get(key, null)
|
|
if kind == "starters":
|
|
var preview: Control = STARTER_CARD_PREVIEW.new() as Control
|
|
preview.call("configure", entry)
|
|
cell.set_preview_control(preview)
|
|
elif entry.has("background_kind"):
|
|
var preview: StageBackgroundCardPreview = BACKGROUND_CARD_PREVIEW.new() as StageBackgroundCardPreview
|
|
preview.configure(entry)
|
|
cell.set_preview_control(preview)
|
|
else:
|
|
cell.set_thumbnail(tex if tex != null else _get_placeholder())
|
|
_cell_texrects[key] = cell.thumbnail_rect()
|
|
|
|
return cell
|
|
|
|
|
|
func _theme_color(path: String, fallback: Color) -> Color:
|
|
if _sandbox_theme != null:
|
|
return _sandbox_theme.color(path, fallback)
|
|
return fallback
|
|
|
|
|
|
func _theme_integer(path: String, fallback: int, minimum: int, maximum: int) -> int:
|
|
return fallback if _sandbox_theme == null else _sandbox_theme.integer(path, fallback, minimum, maximum)
|
|
|
|
|
|
func _theme_vector2(path: String, fallback: Vector2) -> Vector2:
|
|
if _sandbox_theme == null:
|
|
return fallback
|
|
var raw: Variant = _sandbox_theme.value(path, [fallback.x, fallback.y])
|
|
if not raw is Array or (raw as Array).size() != 2:
|
|
return fallback
|
|
return Vector2(float((raw as Array)[0]), float((raw as Array)[1]))
|
|
|
|
|
|
func _apply_chooser_visual_style() -> void:
|
|
var radius: int = _theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
|
var button_border_width: int = _theme_integer("popups.asset_selector.button_border_width", 2, 1, 5)
|
|
var accent: Color = _theme_color("mode_colors.edit", Color("#22C6FF"))
|
|
var text_color: Color = _theme_color("stage_panels.type.text_color", Color("#F5F7FA"))
|
|
var neutral: Color = _theme_color("stage_panels.choice.neutral_surface", Color("#171B24"))
|
|
var hover: Color = _theme_color("stage_panels.choice.hover_surface", Color("#303A4B"))
|
|
var selected: Color = _theme_color("stage_panels.choice.selected_surface", Color("#12394A"))
|
|
var border: Color = _theme_color("stage_panels.choice.border_color", Color("#FFFFFF33"))
|
|
if _sandbox_theme != null:
|
|
self.theme = _sandbox_theme.control_theme()
|
|
var shell: StyleBoxFlat = _sandbox_theme.stylebox("popups.asset_selector")
|
|
shell.set_corner_radius_all(radius)
|
|
shell.content_margin_left = 16.0
|
|
shell.content_margin_right = 16.0
|
|
shell.content_margin_top = 14.0
|
|
shell.content_margin_bottom = 14.0
|
|
add_theme_stylebox_override("panel", shell)
|
|
var preview_background: Color = _theme_color("popups.style_defaults.bg_color", Color("#202633FA"))
|
|
if _setup_preview != null:
|
|
_setup_preview.set_rounded_frame(
|
|
float(_theme_integer("popups.asset_selector.surface_corner_radius", 14, 4, 32)),
|
|
preview_background
|
|
)
|
|
var preview_frame: PanelContainer = null
|
|
if _setup_view != null:
|
|
preview_frame = _setup_view.get_node_or_null("SurfacePreviewFrame") as PanelContainer
|
|
if preview_frame != null:
|
|
var frame_style := StyleBoxFlat.new()
|
|
frame_style.bg_color = preview_background
|
|
frame_style.set_corner_radius_all(_theme_integer("popups.asset_selector.surface_corner_radius", 14, 4, 32))
|
|
preview_frame.add_theme_stylebox_override("panel", frame_style)
|
|
if _title_label != null:
|
|
_title_label.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_title_size", 30, 22, 48) if kind == "scenery" else 24)
|
|
_title_label.add_theme_color_override("font_color", text_color)
|
|
_apply_emphasis_font(_title_label)
|
|
for tab: Button in [_backgrounds_tab, _artwork_tab]:
|
|
if tab == null:
|
|
continue
|
|
tab.custom_minimum_size.y = float(_theme_integer("popups.asset_selector.tab_height", 60, 48, 96))
|
|
tab.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
tab.focus_mode = Control.FOCUS_ALL
|
|
tab.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.tab_font_size", 22, 16, 40))
|
|
tab.add_theme_color_override("font_color", text_color)
|
|
tab.add_theme_color_override("font_hover_color", text_color)
|
|
tab.add_theme_color_override("font_pressed_color", text_color)
|
|
tab.add_theme_stylebox_override("normal", _make_rounded_style(neutral, border, button_border_width, radius))
|
|
tab.add_theme_stylebox_override("hover", _make_rounded_style(hover, accent, button_border_width + 1, radius))
|
|
tab.add_theme_stylebox_override("pressed", _make_rounded_style(selected, accent, button_border_width + 1, radius))
|
|
tab.add_theme_stylebox_override("hover_pressed", _make_rounded_style(selected.lightened(0.06), accent, button_border_width + 1, radius))
|
|
tab.add_theme_stylebox_override("focus", _make_rounded_style(Color(0, 0, 0, 0), accent, button_border_width + 1, radius))
|
|
_apply_emphasis_font(tab)
|
|
if _tabs != null:
|
|
_tabs.add_theme_constant_override("h_separation", 8)
|
|
_tabs.add_theme_constant_override("v_separation", 8)
|
|
if _filter_chips != null:
|
|
_filter_chips.add_theme_constant_override("h_separation", _theme_integer("popups.asset_selector.filter_chip_gap", 8, 4, 24))
|
|
_filter_chips.add_theme_constant_override("v_separation", _theme_integer("popups.asset_selector.filter_chip_gap", 8, 4, 24))
|
|
for chip: Button in _artwork_filter_buttons:
|
|
chip.custom_minimum_size.y = float(_theme_integer("popups.asset_selector.filter_chip_height", 48, 44, 80))
|
|
chip.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.filter_chip_font_size", 20, 16, 32))
|
|
chip.add_theme_color_override("font_color", text_color)
|
|
chip.add_theme_color_override("font_hover_color", text_color)
|
|
chip.add_theme_color_override("font_pressed_color", text_color)
|
|
chip.add_theme_stylebox_override("normal", _make_rounded_style(neutral, border, button_border_width, radius))
|
|
chip.add_theme_stylebox_override("hover", _make_rounded_style(hover, accent, button_border_width + 1, radius))
|
|
chip.add_theme_stylebox_override("pressed", _make_rounded_style(selected, accent, button_border_width + 1, radius))
|
|
chip.add_theme_stylebox_override("hover_pressed", _make_rounded_style(selected.lightened(0.06), accent, button_border_width + 1, radius))
|
|
chip.add_theme_stylebox_override("focus", _make_rounded_style(Color(0, 0, 0, 0), accent, button_border_width + 1, radius))
|
|
_apply_emphasis_font(chip)
|
|
var heading: Label = get_meta("setup_heading", null) as Label
|
|
if heading != null:
|
|
heading.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_heading_size", 26, 18, 48))
|
|
heading.add_theme_color_override("font_color", text_color)
|
|
_apply_emphasis_font(heading)
|
|
_apply_rounded_button(_setup_cancel_button, false, accent, text_color, neutral, hover, border, radius)
|
|
_apply_rounded_button(_setup_draw_button, true, accent, text_color, neutral, hover, border, radius)
|
|
_apply_emphasis_font(_setup_draw_button, true)
|
|
_apply_rounded_button(_color_back_button, false, accent, text_color, neutral, hover, border, radius)
|
|
_apply_rounded_button(_color_use_button, true, accent, text_color, neutral, hover, border, radius)
|
|
if _close_button != null:
|
|
_close_button.custom_minimum_size = Vector2(136.0, float(_theme_integer("stage_panels.action.min_target_size", 48, 44, 96)))
|
|
_apply_rounded_button(_close_button, false, accent, text_color, neutral, hover, border, radius)
|
|
if _make_artwork_button != null:
|
|
_make_artwork_button.custom_minimum_size = Vector2(224.0, float(_theme_integer("stage_panels.action.min_target_size", 48, 44, 96)))
|
|
_apply_rounded_button(_make_artwork_button, false, accent, text_color, neutral, hover, border, radius)
|
|
if _edit_scenery_button != null:
|
|
_edit_scenery_button.text = "✋ Edit placed scenery"
|
|
_edit_scenery_button.custom_minimum_size = Vector2(292.0, float(_theme_integer("popups.asset_selector.tab_height", 60, 48, 96)))
|
|
_edit_scenery_button.focus_mode = Control.FOCUS_ALL
|
|
_edit_scenery_button.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.tab_font_size", 22, 16, 40))
|
|
_edit_scenery_button.add_theme_color_override("font_color", text_color)
|
|
_edit_scenery_button.add_theme_color_override("font_hover_color", text_color)
|
|
_edit_scenery_button.add_theme_color_override("font_pressed_color", text_color)
|
|
_edit_scenery_button.add_theme_stylebox_override("normal", _make_rounded_style(neutral, border, button_border_width, radius))
|
|
_edit_scenery_button.add_theme_stylebox_override("hover", _make_rounded_style(hover, accent, button_border_width + 1, radius))
|
|
_edit_scenery_button.add_theme_stylebox_override("pressed", _make_rounded_style(selected, accent, button_border_width + 1, radius))
|
|
_edit_scenery_button.add_theme_stylebox_override("hover_pressed", _make_rounded_style(selected.lightened(0.06), accent, button_border_width + 1, radius))
|
|
_edit_scenery_button.add_theme_stylebox_override("focus", _make_rounded_style(Color(0, 0, 0, 0), accent, button_border_width + 1, radius))
|
|
_apply_emphasis_font(_edit_scenery_button)
|
|
|
|
|
|
func _apply_rounded_button(
|
|
button: Button,
|
|
primary: bool,
|
|
accent: Color,
|
|
text_color: Color,
|
|
neutral: Color,
|
|
hover: Color,
|
|
border: Color,
|
|
radius: int
|
|
) -> void:
|
|
if button == null:
|
|
return
|
|
var primary_blue: Color = _theme_color("popups.asset_selector.action_color", Color("#087DBA"))
|
|
var base: Color = primary_blue if primary else neutral
|
|
var over: Color = primary_blue.lightened(0.12) if primary else hover
|
|
var down: Color = primary_blue.darkened(0.12) if primary else _selected_button_color()
|
|
var edge: Color = accent.lightened(0.15) if primary else border
|
|
var border_width: int = _theme_integer("popups.asset_selector.button_border_width", 2, 1, 5)
|
|
button.add_theme_stylebox_override("normal", _make_rounded_style(base, edge, border_width, radius))
|
|
button.add_theme_stylebox_override("hover", _make_rounded_style(over, edge, border_width + 1, radius))
|
|
button.add_theme_stylebox_override("pressed", _make_rounded_style(down, accent, border_width + 1, radius))
|
|
button.add_theme_stylebox_override("hover_pressed", _make_rounded_style(down.lightened(0.05), accent, border_width + 1, radius))
|
|
button.add_theme_stylebox_override("focus", _make_rounded_style(Color(0, 0, 0, 0), accent, border_width + 1, radius))
|
|
var label_color: Color = Color.WHITE if primary else text_color
|
|
button.add_theme_color_override("font_color", label_color)
|
|
button.add_theme_color_override("font_hover_color", label_color)
|
|
button.add_theme_color_override("font_pressed_color", label_color)
|
|
button.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_button_font_size", 20, 14, 40))
|
|
_apply_emphasis_font(button)
|
|
|
|
|
|
func _selected_button_color() -> Color:
|
|
return _theme_color("stage_panels.choice.selected_surface", Color("#12394A"))
|
|
|
|
|
|
func _make_rounded_style(background: Color, edge: Color, edge_width: int, radius: int) -> StyleBoxFlat:
|
|
var style := StyleBoxFlat.new()
|
|
style.bg_color = background
|
|
style.border_color = edge
|
|
style.set_border_width_all(edge_width)
|
|
style.set_corner_radius_all(radius)
|
|
style.content_margin_left = 12.0
|
|
style.content_margin_right = 12.0
|
|
style.content_margin_top = 8.0
|
|
style.content_margin_bottom = 8.0
|
|
return style
|
|
|
|
|
|
func _make_strong_font(font: Font, embolden: float) -> Font:
|
|
if font == null:
|
|
return null
|
|
var variation := FontVariation.new()
|
|
variation.base_font = font
|
|
variation.variation_embolden = embolden
|
|
return variation
|
|
|
|
|
|
func _apply_emphasis_font(control: Control, extra_bold: bool = false) -> void:
|
|
if control == null:
|
|
return
|
|
var font: Font = _strong_action_font if extra_bold else _strong_ui_font
|
|
if font != null:
|
|
control.add_theme_font_override("font", font)
|
|
|
|
|
|
func _on_cell_pressed(entry: Dictionary) -> void:
|
|
var background_kind: String = String(entry.get("background_kind", ""))
|
|
if background_kind == "wall_setup":
|
|
_open_surface_setup(&"wall")
|
|
return
|
|
if background_kind == "background_ground_setup":
|
|
_open_surface_setup(&"background_ground")
|
|
return
|
|
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
|