feat: harmonize Prop and Stickman editor UI
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user