- Implemented `test_editor_visual_harmonization.gd` to validate the Stickman editor and prop editor UI layouts, ensuring proper functionality and visual consistency. - Created `test_editor_visual_theme.gd` to verify the loading and application of visual themes in the editor, including semantic overrides and default values. - Introduced `test_phase19_ground_finish_chooser.gd` to test the ground finish chooser's functionality, including layout responsiveness, selection behavior, and interaction with the stage. - Added new scripts for ground finish preview and chooser UI components to enhance the editor's visual representation of ground finishes.
1664 lines
77 KiB
GDScript
1664 lines
77 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)
|
||
signal plain_color_preview_requested(color: Color)
|
||
signal plain_color_commit_requested(color: Color)
|
||
signal plain_color_preview_cancel_requested()
|
||
signal stage_preview_dimming_changed(should_dim: bool)
|
||
|
||
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 _current_background_kind: StringName = &"sky"
|
||
var _current_plain_color: Color = Color("#F5EFE0")
|
||
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_row_heading: Label
|
||
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
|
||
var _plain_color_view: VBoxContainer
|
||
var _plain_color_preview: StageSurfacePreview
|
||
var _plain_color_controls: StageSurfaceColorControls
|
||
var _plain_color_helper: Label
|
||
var _plain_color_choices_open: bool = false
|
||
var _plain_color_original: Color = Color("#F5EFE0")
|
||
var _plain_color_draft: Color = Color("#F5EFE0")
|
||
var _color_picker_owner: StringName = &"surface"
|
||
var _backgrounds_back_button: Button
|
||
var _plain_footer_row: HBoxContainer
|
||
var _plain_footer_column: VBoxContainer
|
||
var _stage_preview_is_dimmed: bool = true
|
||
|
||
@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()
|
||
_build_plain_background_view()
|
||
_build_surface_row_heading()
|
||
_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())
|
||
popup_hide.connect(_on_popup_hide)
|
||
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 = "",
|
||
current_background_kind: String = "sky",
|
||
plain_color: Color = Color("#F5EFE0"),
|
||
browse_state: Dictionary = {}
|
||
) -> void:
|
||
var restore_scenery_browse_state: bool = p_kind == "scenery" and not browse_state.is_empty()
|
||
var restored_gallery_tab: String = String(browse_state.get("gallery_tab", "backgrounds")) if restore_scenery_browse_state else "backgrounds"
|
||
var restored_filter_id: String = String(browse_state.get("tag_id", "all")) if restore_scenery_browse_state else "all"
|
||
var restored_scroll: int = int(browse_state.get("scroll_vertical", 0)) if restore_scenery_browse_state else 0
|
||
_cancel_plain_picker_preview(false)
|
||
kind = p_kind
|
||
_setup_kind = &""
|
||
_color_picker_open = false
|
||
_plain_color_choices_open = false
|
||
_setup_view.visible = false
|
||
_plain_color_view.visible = false
|
||
if _color_picker_view != null:
|
||
_color_picker_view.visible = false
|
||
_current_sky_id = current_sky_id if kind == "scenery" else ""
|
||
_current_background_kind = &"plain" if current_background_kind == "plain" else &"sky"
|
||
_current_plain_color = _opaque_color(plain_color)
|
||
_title_label.text = "Choose a Stickman" if kind == "stickman" else ("Choose Scenery" if kind == "scenery" else ("Choose a starter scene" if kind == "starters" else "Choose a Prop"))
|
||
_backgrounds_back_button.visible = false
|
||
_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 = false
|
||
_refresh_button.visible = false
|
||
_empty_label.text = "No scenery found. Choose Make artwork to draw a new piece." if kind == "scenery" else ("No props in the library yet." if kind == "prop" else ("No starter scenes are available." if kind == "starters" else "No stickmen in the library yet."))
|
||
_gallery_tab = restored_gallery_tab
|
||
_tag_id = restored_filter_id
|
||
_backgrounds_tab.set_pressed_no_signal(_gallery_tab == "backgrounds")
|
||
_artwork_tab.set_pressed_no_signal(_gallery_tab == "artwork")
|
||
set_entries(entries)
|
||
if kind == "scenery":
|
||
_set_gallery_tab(_gallery_tab)
|
||
popup_centered(size)
|
||
call_deferred("_settle_popup_after_open")
|
||
if restore_scenery_browse_state:
|
||
call_deferred("_restore_browse_scroll_after_open", restored_scroll, restored_gallery_tab)
|
||
|
||
|
||
func capture_browse_state() -> Dictionary:
|
||
if kind != "scenery":
|
||
return {}
|
||
return {
|
||
"gallery_tab": _gallery_tab,
|
||
"tag_id": _tag_id,
|
||
"scroll_vertical": _grid_scroll.scroll_vertical if _grid_scroll != null else 0,
|
||
}
|
||
|
||
|
||
func _restore_browse_scroll_after_open(scroll_vertical: int, gallery_tab: String) -> void:
|
||
await get_tree().process_frame
|
||
if not is_inside_tree() or not visible or kind != "scenery" or _gallery_tab != gallery_tab:
|
||
return
|
||
_grid_scroll.scroll_vertical = maxi(0, scroll_vertical)
|
||
|
||
|
||
func _settle_popup_after_open() -> void:
|
||
await get_tree().process_frame
|
||
await get_tree().process_frame
|
||
if is_inside_tree() and visible:
|
||
# PopupPanel can resize after the first native layout pass. Reapply the
|
||
# content-sized width once child minimum sizes and DPI scaling have settled.
|
||
var size_before_settle: Vector2i = size
|
||
_update_popup_layout(true)
|
||
if size != size_before_settle:
|
||
popup_centered(size)
|
||
|
||
|
||
func set_entries(entries: Array[Dictionary]) -> void:
|
||
_entries = entries
|
||
if _grid_scroll != null:
|
||
_grid_scroll.scroll_vertical = 0
|
||
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)
|
||
if _plain_color_controls != null and is_instance_valid(_plain_color_controls):
|
||
_plain_color_controls.set_recent_colors(_surface_color_history)
|
||
|
||
|
||
func set_background_state(background_kind: StringName, sky_id: StringName, plain_color: Color) -> void:
|
||
_current_background_kind = &"plain" if background_kind == &"plain" else &"sky"
|
||
_current_sky_id = String(sky_id)
|
||
_current_plain_color = _opaque_color(plain_color)
|
||
for entry: Dictionary in _entries:
|
||
if String(entry.get("background_kind", "")) == "plain":
|
||
entry["plain_color"] = _current_plain_color
|
||
if not _plain_color_choices_open and not (_color_picker_open and _color_picker_owner == &"plain"):
|
||
_plain_color_draft = _current_plain_color
|
||
if _plain_color_preview != null:
|
||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||
if _plain_color_controls != null:
|
||
_plain_color_controls.set_selected_no_signal(_current_plain_color)
|
||
if _color_picker_open and _color_picker_owner == &"plain" and _color_picker_view != null:
|
||
_color_picker_view.configure_plain_background(_current_plain_color, _sandbox_theme as SandboxTheme)
|
||
if kind == "scenery" and _gallery_tab == "backgrounds":
|
||
_rebuild()
|
||
|
||
|
||
func open_plain_background_choices(color: Color) -> void:
|
||
_current_plain_color = _opaque_color(color)
|
||
_plain_color_draft = _current_plain_color
|
||
_plain_color_choices_open = true
|
||
_color_picker_open = false
|
||
_color_picker_owner = &"plain"
|
||
_title_label.text = "Choose background color"
|
||
_backgrounds_back_button.text = "‹ Backgrounds"
|
||
_backgrounds_back_button.visible = true
|
||
_plain_color_view.visible = true
|
||
_setup_view.visible = false
|
||
_color_picker_view.visible = false
|
||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||
_plain_color_controls.configure(_current_plain_color, _surface_color_history, _preset_surface_colors(), _sandbox_theme as SandboxTheme)
|
||
_update_popup_layout(true)
|
||
_rebuild()
|
||
call_deferred("_update_popup_layout_after_layout")
|
||
call_deferred("_focus_plain_color_controls")
|
||
|
||
|
||
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,
|
||
_backgrounds_back_button, _plain_color_helper,
|
||
_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 _plain_color_controls != null:
|
||
_plain_color_controls.configure(_current_plain_color, _surface_color_history, _preset_surface_colors(), theme as SandboxTheme)
|
||
if _color_picker_view != null:
|
||
if _color_picker_owner == &"plain":
|
||
_color_picker_view.configure_plain_background(_plain_color_draft, theme as SandboxTheme)
|
||
else:
|
||
_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_color_picker_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 _build_plain_background_view() -> void:
|
||
_plain_color_view = VBoxContainer.new()
|
||
_plain_color_view.name = "PlainBackgroundColorChoices"
|
||
_plain_color_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_plain_color_view.add_theme_constant_override("separation", _theme_integer("popups.asset_selector.setup_gap", 8, 4, 24))
|
||
_plain_color_view.visible = false
|
||
_content_stack.add_child(_plain_color_view)
|
||
_plain_color_helper = Label.new()
|
||
_plain_color_helper.name = "PlainBackgroundHelper"
|
||
_plain_color_helper.text = "Tap a color to use it on the Stage."
|
||
_plain_color_helper.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||
_plain_color_helper.add_theme_color_override("font_color", _theme_color("global.muted_text_color", Color("#AAB2C0")))
|
||
_plain_color_view.add_child(_plain_color_helper)
|
||
var preview_frame := PanelContainer.new()
|
||
preview_frame.name = "PlainBackgroundPreviewFrame"
|
||
var preview_size: Vector2 = _theme_vector2("popups.asset_selector.surface_preview_size", Vector2(420.0, 132.0))
|
||
preview_size.y = minf(preview_size.y, 132.0)
|
||
preview_frame.custom_minimum_size.y = preview_size.y + 6.0
|
||
preview_frame.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_plain_color_preview = SURFACE_PREVIEW.new() as StageSurfacePreview
|
||
_plain_color_preview.name = "PlainBackgroundPreview"
|
||
_plain_color_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"))
|
||
)
|
||
_plain_color_preview.custom_minimum_size = preview_size
|
||
_plain_color_preview.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
preview_frame.add_child(_plain_color_preview)
|
||
_plain_color_view.add_child(preview_frame)
|
||
_plain_color_controls = SURFACE_COLOR_CONTROLS.new() as StageSurfaceColorControls
|
||
_plain_color_controls.name = "PlainBackgroundColorControls"
|
||
_plain_color_controls.color_selected.connect(_on_plain_color_selected)
|
||
_plain_color_controls.custom_color_requested.connect(_open_plain_color_picker)
|
||
_plain_color_view.add_child(_plain_color_controls)
|
||
_backgrounds_back_button = Button.new()
|
||
_backgrounds_back_button.name = "BackgroundsBackButton"
|
||
_backgrounds_back_button.text = "‹ Backgrounds"
|
||
_backgrounds_back_button.custom_minimum_size = Vector2(148.0, float(_theme_integer("stage_panels.action.min_target_size", 48, 44, 96)))
|
||
_backgrounds_back_button.focus_mode = Control.FOCUS_ALL
|
||
_backgrounds_back_button.visible = false
|
||
_backgrounds_back_button.pressed.connect(_back_to_backgrounds)
|
||
_footer.add_child(_backgrounds_back_button)
|
||
_plain_footer_row = HBoxContainer.new()
|
||
_plain_footer_row.name = "PlainBackgroundFooterRow"
|
||
_plain_footer_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_plain_footer_row.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||
_plain_footer_row.add_theme_constant_override("separation", _theme_integer("popups.asset_selector.footer_gap", 16, 8, 32))
|
||
_plain_footer_row.visible = false
|
||
_footer.add_child(_plain_footer_row)
|
||
_plain_footer_column = VBoxContainer.new()
|
||
_plain_footer_column.name = "PlainBackgroundFooterColumn"
|
||
_plain_footer_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_plain_footer_column.add_theme_constant_override("separation", _theme_integer("popups.asset_selector.footer_gap", 16, 8, 32))
|
||
_plain_footer_column.visible = false
|
||
_footer.add_child(_plain_footer_column)
|
||
_apply_font_to(_plain_color_helper)
|
||
_apply_font_to(_backgrounds_back_button)
|
||
_apply_chooser_visual_style()
|
||
|
||
|
||
func _build_surface_row_heading() -> void:
|
||
_surface_row_heading = Label.new()
|
||
_surface_row_heading.name = "MoreBackgroundsHeading"
|
||
_surface_row_heading.text = "More backgrounds"
|
||
_surface_row_heading.visible = false
|
||
_surface_row_heading.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_heading_size", 26, 18, 48))
|
||
_content_stack.add_child(_surface_row_heading)
|
||
_content_stack.move_child(_surface_row_heading, _surface_cards_row.get_index())
|
||
_apply_font_to(_surface_row_heading)
|
||
|
||
|
||
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 _on_plain_color_selected(color: Color) -> void:
|
||
_current_plain_color = _opaque_color(color)
|
||
_plain_color_draft = _current_plain_color
|
||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||
_plain_color_controls.set_selected_no_signal(_current_plain_color)
|
||
plain_color_commit_requested.emit(_current_plain_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
|
||
_color_picker_owner = &"surface"
|
||
_title_label.text = "Choose color"
|
||
_rebuild()
|
||
_update_popup_layout()
|
||
call_deferred("_update_popup_layout_after_layout")
|
||
_color_picker_view.focus_picker()
|
||
|
||
|
||
func _open_plain_color_picker() -> void:
|
||
if not _plain_color_choices_open:
|
||
return
|
||
_plain_color_original = _current_plain_color
|
||
_plain_color_draft = _current_plain_color
|
||
_color_picker_owner = &"plain"
|
||
_color_picker_view.configure_plain_background(_plain_color_draft, _sandbox_theme as SandboxTheme)
|
||
_color_back_button.text = "‹ Plain Background"
|
||
_plain_color_choices_open = false
|
||
_color_picker_open = true
|
||
_title_label.text = "Choose background color"
|
||
_backgrounds_back_button.visible = false
|
||
_update_popup_layout(true)
|
||
_rebuild()
|
||
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 _on_color_picker_preview(color: Color) -> void:
|
||
if _color_picker_owner == &"plain":
|
||
_plain_color_draft = _opaque_color(color)
|
||
plain_color_preview_requested.emit(_plain_color_draft)
|
||
return
|
||
_on_setup_color_preview(color)
|
||
|
||
|
||
func _use_setup_color() -> void:
|
||
if _color_picker_owner == &"plain":
|
||
_current_plain_color = _opaque_color(_color_picker_view.get_color())
|
||
_plain_color_draft = _current_plain_color
|
||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||
plain_color_commit_requested.emit(_current_plain_color)
|
||
_close_plain_color_picker()
|
||
return
|
||
_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:
|
||
if _color_picker_owner == &"plain":
|
||
_cancel_plain_picker_preview(true)
|
||
return
|
||
_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 _close_plain_color_picker() -> void:
|
||
_color_picker_open = false
|
||
_plain_color_choices_open = true
|
||
_title_label.text = "Choose background color"
|
||
_backgrounds_back_button.text = "‹ Backgrounds"
|
||
_backgrounds_back_button.visible = true
|
||
_plain_color_view.visible = true
|
||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||
_plain_color_controls.set_selected_no_signal(_current_plain_color)
|
||
_update_popup_layout(true)
|
||
_rebuild()
|
||
call_deferred("_update_popup_layout_after_layout")
|
||
call_deferred("_focus_plain_color_controls")
|
||
|
||
|
||
func _cancel_plain_picker_preview(return_to_choices: bool = true) -> void:
|
||
if not _color_picker_open or _color_picker_owner != &"plain":
|
||
return
|
||
_plain_color_draft = _current_plain_color
|
||
_color_picker_open = false
|
||
plain_color_preview_cancel_requested.emit()
|
||
if _color_picker_view != null:
|
||
_color_picker_view.configure_plain_background(_current_plain_color, _sandbox_theme as SandboxTheme)
|
||
if return_to_choices and kind == "scenery":
|
||
_plain_color_choices_open = true
|
||
_title_label.text = "Choose background color"
|
||
_backgrounds_back_button.text = "‹ Backgrounds"
|
||
_backgrounds_back_button.visible = true
|
||
_plain_color_view.visible = true
|
||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||
_plain_color_controls.set_selected_no_signal(_current_plain_color)
|
||
_update_popup_layout(true)
|
||
_rebuild()
|
||
call_deferred("_update_popup_layout_after_layout")
|
||
call_deferred("_focus_plain_color_controls")
|
||
|
||
|
||
func _back_to_backgrounds() -> void:
|
||
if _color_picker_open and _color_picker_owner == &"plain":
|
||
_cancel_plain_picker_preview(true)
|
||
return
|
||
_plain_color_choices_open = false
|
||
_plain_color_view.visible = false
|
||
_backgrounds_back_button.visible = false
|
||
_title_label.text = "Choose the scene background"
|
||
_update_popup_layout(true)
|
||
_rebuild()
|
||
call_deferred("_focus_plain_background_card")
|
||
|
||
|
||
func _focus_plain_color_controls() -> void:
|
||
if _plain_color_controls != null and _plain_color_controls.visible:
|
||
_plain_color_controls.focus_custom_button()
|
||
|
||
|
||
func _focus_plain_background_card() -> void:
|
||
for node: Node in _grid.get_children():
|
||
var card: AssetCard = node as AssetCard
|
||
if card != null and String(card.get_meta("entry_id", "")) == "plain_background":
|
||
card.grab_focus()
|
||
return
|
||
|
||
|
||
func _on_popup_hide() -> void:
|
||
if _color_picker_open and _color_picker_owner == &"plain":
|
||
_cancel_plain_picker_preview(true)
|
||
|
||
|
||
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:
|
||
if _color_picker_open and _color_picker_owner == &"plain":
|
||
_cancel_plain_picker_preview(false)
|
||
_color_picker_open = false
|
||
_plain_color_choices_open = false
|
||
_setup_kind = &""
|
||
_setup_view.visible = false
|
||
_plain_color_view.visible = false
|
||
_color_picker_view.visible = false
|
||
_backgrounds_back_button.visible = false
|
||
_gallery_tab = tab
|
||
_title_label.text = "Choose the scene background" if tab == "backgrounds" else "Choose Scenery"
|
||
_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 = false
|
||
_refresh_button.visible = false
|
||
_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_plain_choices: bool = _plain_color_choices_open and not in_color_picker
|
||
var in_setup: bool = not _setup_kind.is_empty() and not in_color_picker
|
||
var in_subview: bool = in_setup or in_color_picker or in_plain_choices
|
||
var scenery_background_gallery: bool = _is_scenery_background_gallery() and not in_subview
|
||
var scenery_artwork_gallery: bool = _is_scenery_artwork_gallery() and not in_subview
|
||
var asset_scrolling_gallery: bool = _uses_scrolling_asset_gallery() and not in_subview
|
||
var filtered: Array[Dictionary] = _filtered_entries()
|
||
_tabs.visible = kind == "scenery" and not in_subview
|
||
_filter_chips.visible = scenery_artwork_gallery
|
||
_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
|
||
_plain_color_view.visible = in_plain_choices
|
||
_backgrounds_back_button.visible = in_plain_choices
|
||
_color_picker_view.visible = in_color_picker
|
||
_surface_cards_row.visible = scenery_background_gallery and _has_surface_entries(filtered)
|
||
_surface_row_heading.visible = _surface_cards_row.visible
|
||
_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_subview
|
||
_grid.visible = not filtered.is_empty() and not in_subview
|
||
_grid_scroll.visible = not filtered.is_empty() or in_subview
|
||
_footer.alignment = BoxContainer.ALIGNMENT_BEGIN if scenery_artwork_gallery or asset_scrolling_gallery or in_subview else BoxContainer.ALIGNMENT_CENTER
|
||
_footer.add_theme_constant_override(
|
||
"separation",
|
||
_theme_integer("popups.asset_selector.footer_gap", 16, 8, 32) if in_subview or kind == "scenery" else 8
|
||
)
|
||
var available: Vector2 = _usable_screen_size()
|
||
var width_ratio: float = 0.98 if in_subview or asset_scrolling_gallery else (0.96 if kind == "starters" else 0.94)
|
||
var max_width: float = maxf(1.0, minf(available.x * width_ratio, available.x - POPUP_SAFE_SCREEN_MARGIN))
|
||
var height_ratio: float = _theme_number("popups.asset_selector.starter_viewport_height_ratio", 0.96, 0.7, 0.98) if kind == "starters" else 0.86
|
||
var max_height: float = maxf(1.0, minf(available.y * height_ratio, available.y - POPUP_SAFE_SCREEN_MARGIN * 2.0))
|
||
var preferred_width: float = _cell_min_size.x * (2 if kind == "starters" else COLUMNS) + GRID_H_SEPARATION * ((2 if kind == "starters" else COLUMNS) - 1) + POPUP_SIDE_PADDING
|
||
var preferred_height: float = max_height
|
||
if kind == "starters":
|
||
preferred_width = _theme_number("popups.asset_selector.starter_dialog_width", 1160.0, 440.0, 2400.0)
|
||
if in_color_picker or in_plain_choices:
|
||
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)
|
||
_position_footer(popup_width)
|
||
var grid_width: float = maxf(1.0, popup_width - POPUP_SIDE_PADDING)
|
||
var minimum_card_width: float = _cell_min_size.x
|
||
if kind == "starters":
|
||
minimum_card_width = _theme_number("popups.asset_selector.starter_card_min_width", 340.0, 220.0, 800.0)
|
||
_active_columns = clampi(floori((grid_width + GRID_H_SEPARATION) / (minimum_card_width + GRID_H_SEPARATION)), 1, 2 if kind == "starters" else COLUMNS)
|
||
_page_capacity = maxi(1, filtered.size()) if scenery_background_gallery or scenery_artwork_gallery or asset_scrolling_gallery or kind == "starters" else _active_columns * ROWS
|
||
_grid.columns = _active_columns
|
||
var available_card_width: float = (grid_width - GRID_H_SEPARATION * (_active_columns - 1)) / float(_active_columns)
|
||
var card_width: float = available_card_width if kind == "starters" else minf(_cell_min_size.x, available_card_width)
|
||
var card_height_limit: float = 248.0 if kind != "starters" else INF
|
||
var card_height: float
|
||
if kind == "starters":
|
||
var preview_horizontal_inset: float = _theme_number("popups.asset_selector.starter_preview_horizontal_inset", 10.0, 0.0, 32.0)
|
||
var preview_aspect: float = _theme_number("popups.asset_selector.starter_preview_aspect", 2.0, 1.2, 2.8)
|
||
var preview_top_inset: float = _theme_number("popups.asset_selector.starter_preview_top_inset", 6.0, 0.0, 24.0)
|
||
var preview_height: float = maxf(1.0, card_width - preview_horizontal_inset * 2.0) / preview_aspect
|
||
var text_and_spacing_height: float = _theme_number("popups.asset_selector.starter_card_text_height", 92.0, 72.0, 144.0)
|
||
card_height = preview_height + preview_top_inset + text_and_spacing_height
|
||
else:
|
||
card_height = minf(card_height_limit, card_width + 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_scene_background_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_plain_choices:
|
||
body_height = maxf(220.0, _visible_content_minimum_height(_plain_color_view))
|
||
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 + _surface_row_heading.get_combined_minimum_size().y + GRID_V_SEPARATION * 2.0
|
||
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)))
|
||
_position_footer(float(size.x))
|
||
_sync_stage_preview_dimming()
|
||
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(visible)
|
||
|
||
|
||
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_plain_choices: bool = _plain_color_choices_open and not in_color_picker
|
||
var in_setup: bool = not _setup_kind.is_empty() and not in_color_picker
|
||
var in_subview: bool = in_setup or in_color_picker or in_plain_choices
|
||
var scenery_background_gallery: bool = _is_scenery_background_gallery() and not in_subview
|
||
var scenery_artwork_gallery: bool = _is_scenery_artwork_gallery() and not in_subview
|
||
var asset_scrolling_gallery: bool = _uses_scrolling_asset_gallery() and not in_subview
|
||
_position_footer(float(size.x))
|
||
_sync_stage_preview_dimming()
|
||
_close_button.visible = not in_setup and not in_color_picker
|
||
_setup_view.visible = in_setup
|
||
_plain_color_view.visible = in_plain_choices
|
||
_backgrounds_back_button.visible = in_plain_choices
|
||
_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)
|
||
_surface_row_heading.visible = _surface_cards_row.visible
|
||
_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_subview
|
||
_make_artwork_button.visible = scenery_artwork_gallery
|
||
_browse_button.visible = false
|
||
_refresh_button.visible = false
|
||
_footer_spacer.visible = scenery_artwork_gallery
|
||
_empty_label.visible = filtered.is_empty() and not in_subview
|
||
_grid.visible = not filtered.is_empty() and not in_subview
|
||
_grid_scroll.visible = not filtered.is_empty() or in_subview
|
||
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_subview:
|
||
_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" or background_kind == "plain":
|
||
_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(target_width: float = -1.0) -> 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)
|
||
var in_plain_choices: bool = _plain_color_choices_open and not _color_picker_open
|
||
if in_plain_choices:
|
||
var available_width: float = target_width if target_width >= 0.0 else float(size.x)
|
||
var stack_actions: bool = available_width < 380.0
|
||
var action_container: Control = _plain_footer_column if stack_actions else _plain_footer_row
|
||
_plain_footer_row.visible = not stack_actions
|
||
_plain_footer_column.visible = stack_actions
|
||
_move_footer_control(_backgrounds_back_button, action_container, 0)
|
||
if stack_actions:
|
||
_move_footer_control(_close_button, action_container, 1)
|
||
_move_footer_control(_footer_spacer, _footer, 1)
|
||
_footer_spacer.visible = false
|
||
_backgrounds_back_button.custom_minimum_size.x = 0.0
|
||
_close_button.custom_minimum_size.x = 0.0
|
||
_backgrounds_back_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
_close_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
else:
|
||
_move_footer_control(_footer_spacer, action_container, 1)
|
||
_move_footer_control(_close_button, action_container, 2)
|
||
_footer_spacer.visible = true
|
||
_backgrounds_back_button.custom_minimum_size.x = 148.0
|
||
_close_button.custom_minimum_size.x = 136.0
|
||
_backgrounds_back_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||
_close_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||
return
|
||
_plain_footer_row.visible = false
|
||
_plain_footer_column.visible = false
|
||
_move_footer_control(_backgrounds_back_button, _footer, 1)
|
||
_move_footer_control(_footer_spacer, _footer, 1)
|
||
_move_footer_control(_close_button, _footer, 0)
|
||
_backgrounds_back_button.custom_minimum_size.x = 148.0
|
||
_close_button.custom_minimum_size.x = 136.0
|
||
_backgrounds_back_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||
_close_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||
|
||
|
||
func _move_footer_control(control: Control, target: Control, index: int) -> void:
|
||
if control == null or target == null:
|
||
return
|
||
if control.get_parent() != target:
|
||
var old_parent: Node = control.get_parent()
|
||
if old_parent != null:
|
||
old_parent.remove_child(control)
|
||
target.add_child(control)
|
||
var safe_index: int = clampi(index, 0, maxi(0, target.get_child_count() - 1))
|
||
if control.get_index() != safe_index:
|
||
target.move_child(control, safe_index)
|
||
|
||
|
||
func _sync_stage_preview_dimming() -> void:
|
||
var should_dim: bool = not (_plain_color_choices_open and kind == "scenery")
|
||
if _color_picker_open and _color_picker_owner == &"plain":
|
||
should_dim = false
|
||
if should_dim == _stage_preview_is_dimmed:
|
||
return
|
||
_stage_preview_is_dimmed = should_dim
|
||
stage_preview_dimming_changed.emit(should_dim)
|
||
|
||
|
||
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 _uses_scrolling_asset_gallery() -> bool:
|
||
return kind == "prop" or kind == "stickman"
|
||
|
||
|
||
func _count_scene_background_entries(entries: Array[Dictionary]) -> int:
|
||
var count: int = 0
|
||
for entry: Dictionary in entries:
|
||
if String(entry.get("background_kind", "")) in ["sky", "plain"]:
|
||
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()
|
||
# Scene cards have clear rounded resting and interaction states. Selected
|
||
# background state is applied separately below.
|
||
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":
|
||
var preview_horizontal_inset: float = _theme_number("popups.asset_selector.starter_preview_horizontal_inset", 10.0, 0.0, 32.0)
|
||
var preview_aspect: float = _theme_number("popups.asset_selector.starter_preview_aspect", 2.0, 1.2, 2.8)
|
||
var preview_width: float = maxf(72.0, card_size.x - preview_horizontal_inset * 2.0)
|
||
thumb_size = Vector2(preview_width, preview_width / preview_aspect)
|
||
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 kind == "starters":
|
||
card_payload["label_font_size"] = _theme_integer("popups.asset_selector.starter_label_size", 22, 18, 32)
|
||
card_payload["label_min_height"] = _theme_integer("popups.asset_selector.starter_label_height", 34, 28, 52)
|
||
card_payload["description_font_size"] = _theme_integer("popups.asset_selector.starter_description_size", 20, 16, 30)
|
||
card_payload["description_min_height"] = _theme_integer("popups.asset_selector.starter_description_height", 48, 36, 80)
|
||
card_payload["preview_corner_radius"] = _theme_integer("popups.asset_selector.starter_preview_corner_radius", 12, 6, 24)
|
||
card_payload["preview_horizontal_inset"] = roundi(_theme_number("popups.asset_selector.starter_preview_horizontal_inset", 10.0, 0.0, 32.0))
|
||
card_payload["preview_top_inset"] = _theme_integer("popups.asset_selector.starter_preview_top_inset", 6, 0, 24)
|
||
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 background_kind: String = String(entry.get("background_kind", ""))
|
||
var is_scene_background_choice: bool = background_kind == "sky" or background_kind == "plain"
|
||
var is_current_background: bool = background_kind == String(_current_background_kind) and (
|
||
background_kind == "plain" or String(entry.get("id", "")) == _current_sky_id
|
||
)
|
||
if is_current_background:
|
||
card_payload["status"] = "Current background"
|
||
card_payload["status_color"] = _theme_color("mode_colors.edit", Color("#22C6FF"))
|
||
cell.configure(card_payload, _thumbnails.get(key, null), null)
|
||
if kind == "scenery" or kind == "starters" or _uses_scrolling_asset_gallery():
|
||
var card_button: Button = cell as Button
|
||
var card_border_width: int = _theme_integer("popups.asset_selector.button_border_width", 2, 1, 5)
|
||
var card_corner_radius: int = _theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
||
var card_neutral_surface: Color = _theme_color("stage_panels.choice.neutral_surface", Color("#171B24"))
|
||
var card_neutral_border: Color = _theme_color("stage_panels.choice.border_color", Color("#FFFFFF33"))
|
||
var card_hover_surface: Color = _theme_color("stage_panels.choice.hover_surface", Color("#303A4B"))
|
||
var card_selected_surface: Color = _theme_color("stage_panels.choice.selected_surface", Color("#12394A"))
|
||
var card_hover_border_width: int = card_border_width + 1
|
||
card_button.add_theme_stylebox_override("normal", _make_rounded_style(
|
||
card_neutral_surface,
|
||
card_neutral_border,
|
||
card_border_width,
|
||
card_corner_radius
|
||
))
|
||
card_button.add_theme_stylebox_override("hover", _make_rounded_style(
|
||
card_hover_surface,
|
||
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
||
card_hover_border_width,
|
||
card_corner_radius
|
||
))
|
||
card_button.add_theme_stylebox_override("pressed", _make_rounded_style(
|
||
card_selected_surface,
|
||
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
||
card_hover_border_width,
|
||
card_corner_radius
|
||
))
|
||
card_button.add_theme_stylebox_override("hover_pressed", _make_rounded_style(
|
||
card_selected_surface.lightened(0.05),
|
||
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
||
card_hover_border_width,
|
||
card_corner_radius
|
||
))
|
||
var focus_color: Color = _theme_color("stage_panels.type.text_color", Color("#F5F7FA")) if kind == "starters" else _theme_color("mode_colors.edit", Color("#22C6FF"))
|
||
var focus_style: StyleBoxFlat = _make_rounded_style(
|
||
Color(0, 0, 0, 0),
|
||
focus_color,
|
||
card_hover_border_width,
|
||
card_corner_radius
|
||
)
|
||
if kind == "starters":
|
||
focus_style.set_expand_margin_all(float(card_border_width))
|
||
card_button.add_theme_stylebox_override("focus", focus_style)
|
||
cell.custom_minimum_size = card_size
|
||
cell.focus_mode = Control.FOCUS_ALL
|
||
cell.set_meta("entry_id", String(entry.get("id", "")))
|
||
cell.activated.connect(func(_payload: Dictionary) -> void: _on_cell_pressed(entry))
|
||
if is_scene_background_choice:
|
||
cell.button_group = _sky_card_group
|
||
cell.set_selected(is_current_background, _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, thumb_size)
|
||
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_number(path: String, fallback: float, minimum: float, maximum: float) -> float:
|
||
return fallback if _sandbox_theme == null else _sandbox_theme.number(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:
|
||
var title_size: int = _theme_integer("popups.asset_selector.starter_title_size", 30, 22, 48) if kind == "starters" else _theme_integer("popups.asset_selector.setup_title_size", 30, 22, 48)
|
||
_title_label.add_theme_font_size_override("font_size", title_size)
|
||
_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)
|
||
if _surface_row_heading != null:
|
||
_surface_row_heading.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_heading_size", 26, 18, 48))
|
||
_surface_row_heading.add_theme_color_override("font_color", text_color)
|
||
_apply_emphasis_font(_surface_row_heading)
|
||
if _plain_color_helper != null:
|
||
_plain_color_helper.add_theme_font_size_override("font_size", _theme_integer("stage_panels.type.body_size", 20, 14, 32))
|
||
_plain_color_helper.add_theme_color_override("font_color", _theme_color("stage_panels.type.muted_text_color", Color("#AAB2C0")))
|
||
_apply_font_to(_plain_color_helper)
|
||
_apply_rounded_button(_backgrounds_back_button, false, accent, text_color, neutral, hover, border, radius)
|
||
_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
|