class_name EditorSelectionDropdown extends MenuButton ## Single-choice editor picker with a Studio-style selected row and no radio lane. signal item_selected(index: int) var selected: int = -1: set(value): _set_selected(value) get: return _selected_index var _selected_index: int = -1 var _popup_menu: PopupMenu var _field_icons: Array[Texture2D] = [] var _control_theme: Theme var _surface_color: Color = Color("#252B38") var _selected_surface_color: Color = Color("#103B4D") var _border_color: Color = Color("#FFFFFF33") var _accent_color: Color = Color("#22C6FF") var _focus_color: Color = Color("#8BD9FF") var _text_color: Color = Color("#F5F7FA") var _font_size: int = 18 var _row_separation: int = 6 func _ready() -> void: flat = false focus_mode = Control.FOCUS_ALL alignment = HORIZONTAL_ALIGNMENT_LEFT _popup_menu = get_popup() _popup_menu.shrink_width = false _popup_menu.transparent = false _popup_menu.transparent_bg = false _popup_menu.id_pressed.connect(_on_popup_item_pressed) about_to_popup.connect(_on_about_to_popup) _apply_popup_theme() _apply_field_theme() _sync_selected_item() queue_redraw() func configure( control_theme: Theme, surface_color: Color, selected_surface_color: Color, border_color: Color, accent_color: Color, focus_color: Color, text_color: Color, font_size: int, row_separation: int = 6 ) -> void: _control_theme = control_theme _surface_color = surface_color _selected_surface_color = selected_surface_color _border_color = border_color _accent_color = accent_color _focus_color = focus_color _text_color = text_color _font_size = font_size _row_separation = maxi(0, row_separation) if is_node_ready(): _apply_popup_theme() func add_item(label: String, id: int = -1) -> void: var index: int = get_popup().item_count var item_id: int = index if id < 0 else id add_styled_item(label, null, null, item_id) func add_styled_item( label: String, field_icon: Texture2D = null, popup_icon: Texture2D = null, id: int = -1 ) -> void: var popup: PopupMenu = get_popup() var index: int = popup.item_count var item_id: int = index if id < 0 else id _field_icons.append(field_icon) if popup_icon != null: popup.add_icon_item(popup_icon, label, item_id) popup.set_item_icon_max_width(index, 44) else: popup.add_item(label, item_id) popup.set_item_as_radio_checkable(index, false) if _selected_index < 0: _set_selected(index) func get_item_text(index: int) -> String: return get_popup().get_item_text(index) func get_item_icon(index: int) -> Texture2D: return _field_icons[index] if index >= 0 and index < _field_icons.size() else null func select(index: int) -> void: _set_selected(index) func clear() -> void: get_popup().clear() _field_icons.clear() _selected_index = -1 _sync_selected_item() func hide_popup() -> void: get_popup().hide() func _set_selected(index: int) -> void: var count: int = get_popup().item_count if index < 0 or count == 0: _selected_index = -1 else: _selected_index = clampi(index, 0, count - 1) _sync_selected_item() func _sync_selected_item() -> void: if not is_instance_valid(get_popup()): return if _selected_index < 0 or _selected_index >= get_popup().item_count: text = "" icon = null return text = get_popup().get_item_text(_selected_index) icon = _field_icons[_selected_index] if _selected_index < _field_icons.size() else null func _apply_popup_theme() -> void: if _popup_menu == null: return if _control_theme != null: _popup_menu.theme = _control_theme var panel := StyleBoxFlat.new() var opaque_surface: Color = _surface_color opaque_surface.a = 1.0 panel.bg_color = opaque_surface panel.border_color = _border_color panel.set_border_width_all(1) panel.set_corner_radius_all(8) panel.content_margin_left = 6.0 panel.content_margin_top = 6.0 panel.content_margin_right = 6.0 panel.content_margin_bottom = 6.0 _popup_menu.add_theme_stylebox_override("panel", panel) var selected_row := StyleBoxFlat.new() selected_row.bg_color = _selected_surface_color selected_row.border_color = _accent_color selected_row.set_border_width_all(2) selected_row.set_corner_radius_all(7) selected_row.content_margin_left = 5.0 selected_row.content_margin_top = 12.0 selected_row.content_margin_right = 5.0 selected_row.content_margin_bottom = 12.0 _popup_menu.add_theme_stylebox_override("hover", selected_row) _popup_menu.add_theme_color_override("font_hover_color", _text_color) _popup_menu.add_theme_color_override("font_color", _text_color) _popup_menu.add_theme_font_size_override("font_size", _font_size) _popup_menu.add_theme_constant_override("icon_max_width", 44) _popup_menu.add_theme_constant_override("h_separation", 10) _popup_menu.add_theme_constant_override("v_separation", _row_separation) _popup_menu.add_theme_constant_override("item_start_padding", 4) _popup_menu.add_theme_constant_override("item_end_padding", 4) add_theme_stylebox_override("focus", _make_focus_style()) queue_redraw() func _apply_field_theme() -> void: for state: String in ["normal", "hover", "pressed", "disabled"]: var original: StyleBox = get_theme_stylebox(state, "Button") if original == null: continue var field_style: StyleBox = original.duplicate() field_style.content_margin_right = maxf(field_style.content_margin_right, 28.0) add_theme_stylebox_override(state, field_style) func _make_focus_style() -> StyleBoxFlat: var focus_box := StyleBoxFlat.new() focus_box.bg_color = Color(0.0, 0.0, 0.0, 0.0) focus_box.border_color = _focus_color focus_box.set_border_width_all(2) focus_box.set_corner_radius_all(8) focus_box.content_margin_right = 28.0 return focus_box func _draw() -> void: var center := Vector2(size.x - 16.0, size.y * 0.5) var points := PackedVector2Array([ center + Vector2(-4.0, -2.0), center + Vector2(0.0, 2.0), center + Vector2(4.0, -2.0), ]) draw_polyline(points, _text_color.darkened(0.28), 2.0, true) func _on_about_to_popup() -> void: var target_width: int = maxi(roundi(size.x), _popup_menu.min_size.x) _popup_menu.min_size = Vector2i(target_width, _popup_menu.min_size.y) _popup_menu.size = Vector2i(maxi(_popup_menu.size.x, target_width), _popup_menu.size.y) call_deferred("_sync_popup_size") if _selected_index >= 0: _popup_menu.set_focused_item(_selected_index) else: _popup_menu.set_focused_item(-1) func _sync_popup_size() -> void: if _popup_menu == null: return var target_width: int = maxi(roundi(size.x), _popup_menu.min_size.x) _popup_menu.min_size = Vector2i(target_width, _popup_menu.min_size.y) _popup_menu.size = Vector2i(maxi(_popup_menu.size.x, target_width), _popup_menu.size.y) if _selected_index >= 0: _popup_menu.set_focused_item(_selected_index) func _on_popup_item_pressed(id: int) -> void: var index: int = get_popup().get_item_index(id) if index < 0 or index == _selected_index: return _set_selected(index) item_selected.emit(index)