Files
stickman/scripts/shared/editor_ui/editor_selection_dropdown.gd
T

232 lines
6.8 KiB
GDScript

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)