Added GDSubMenuButton
Implemented GDSubMenuButton, taken from iRadDev's project.
This commit is contained in:
parent
59c785ce7c
commit
cf3ee97bbb
2 changed files with 200 additions and 0 deletions
199
lib/UI/GDSubMenuButton.gd
Normal file
199
lib/UI/GDSubMenuButton.gd
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
extends Button
|
||||
class_name GDSubMenuButton
|
||||
|
||||
# Original code by iRadDev (https://github.com/iRadEntertainment/ridiculous-stream-overlay/)
|
||||
|
||||
@export var expand_on_hover := true:
|
||||
set(val):
|
||||
expand_on_hover = val
|
||||
if !is_node_ready(): await ready
|
||||
if val:
|
||||
if not mouse_entered.is_connected(expand_menu):
|
||||
mouse_entered.connect(expand_menu.bind(true))
|
||||
else:
|
||||
if mouse_entered.is_connected(expand_menu):
|
||||
mouse_entered.disconnect(expand_menu)
|
||||
|
||||
@export var fold_on_child_button_pressed := true
|
||||
@export var is_radial := false
|
||||
@export_range(0.0, 64.0, 0.5) var offset_gap: float = 8:
|
||||
set(val):
|
||||
offset_gap = val
|
||||
expand_menu(is_open)
|
||||
@export_range(0.0, 360.0, 0.5) var radial_angle: float = 180:
|
||||
set(val):
|
||||
radial_angle = val
|
||||
_radial_angle_rad = deg_to_rad(radial_angle)
|
||||
expand_menu(is_open)
|
||||
|
||||
@export_range(0.0, 0.6, 0.01) var anim_duration = 0.15
|
||||
@export_range(0.0, 0.1, 0.001) var anim_delay = 0.03
|
||||
const MIN_SIZE = Vector2(48, 48)
|
||||
|
||||
var main_menu_button: GDSubMenuButton
|
||||
var tw: Tween
|
||||
|
||||
enum Edge { UP, LEFT, BOT, RIGHT}
|
||||
var closest_edge := Edge.RIGHT
|
||||
|
||||
var _radial_angle_rad: float = PI
|
||||
var parent: GDSubMenuButton
|
||||
var parent_dir := Vector2.DOWN
|
||||
var custom_pos := Vector2()
|
||||
|
||||
var is_open := false
|
||||
var is_sub_menu := false
|
||||
var is_dragged := false
|
||||
var is_mouse_click_down := false
|
||||
var grabbed_at := Vector2()
|
||||
var all_btns: Array[Button]
|
||||
|
||||
signal btn_child_expanded(btn_child: GDSubMenuButton, is_open: bool)
|
||||
signal properly_pressed
|
||||
|
||||
|
||||
func start(_main_menu_button: GDSubMenuButton = null) -> void:
|
||||
main_menu_button = _main_menu_button
|
||||
if not main_menu_button:
|
||||
main_menu_button = self
|
||||
|
||||
gui_input.connect(_on_gui_input)
|
||||
properly_pressed.connect(toggle_menu)
|
||||
if expand_on_hover:
|
||||
mouse_entered.connect(expand_menu.bind(true))
|
||||
|
||||
if get_parent() is GDSubMenuButton:
|
||||
parent = get_parent()
|
||||
is_sub_menu = true
|
||||
|
||||
all_btns = []
|
||||
for child in get_children():
|
||||
if child is Button:
|
||||
all_btns.append(child)
|
||||
|
||||
for btn in all_btns:
|
||||
if btn is GDSubMenuButton:
|
||||
btn.start(main_menu_button)
|
||||
btn.btn_child_expanded.connect(_on_btn_child_expanded)
|
||||
elif btn is Button:
|
||||
if fold_on_child_button_pressed:
|
||||
btn.pressed.connect(main_menu_button.expand_menu.bind(false))
|
||||
btn.focus_mode = Control.FOCUS_NONE
|
||||
btn.custom_minimum_size = MIN_SIZE
|
||||
GDSubMenuButton.assign_texture_to_button_from_icon(btn, 10)
|
||||
btn.add_to_group("UI")
|
||||
expand_menu(false)
|
||||
|
||||
func _on_btn_child_expanded(btn_expanded: GDSubMenuButton, opened: bool) -> void:
|
||||
for btn in all_btns:
|
||||
if btn is GDSubMenuButton and btn != btn_expanded and opened:
|
||||
btn.expand_menu(false)
|
||||
|
||||
func expand_menu(value: bool, except: GDSubMenuButton = null) -> void:
|
||||
if !is_node_ready(): await ready
|
||||
if all_btns.is_empty(): return
|
||||
if parent:
|
||||
if parent.tw:
|
||||
if parent.tw.is_running() and parent.is_open:
|
||||
return
|
||||
|
||||
is_open = value
|
||||
|
||||
if is_sub_menu:
|
||||
var parent_center: Vector2 = parent.size/2
|
||||
var this_center := size/2 + position
|
||||
parent_dir = parent_center.direction_to(this_center)
|
||||
|
||||
var angle_start = 0
|
||||
var angle_step = 0
|
||||
if is_open and is_radial:
|
||||
angle_start = - _radial_angle_rad/2 + parent_dir.angle()
|
||||
angle_step = _radial_angle_rad / (all_btns.size()-1)
|
||||
|
||||
if tw:
|
||||
tw.kill()
|
||||
tw = create_tween()
|
||||
tw.set_ease(Tween.EASE_IN_OUT)
|
||||
tw.set_trans(Tween.TRANS_CUBIC)
|
||||
|
||||
for i in all_btns.size():
|
||||
var btn: Button = all_btns[i]
|
||||
if btn == except:
|
||||
continue
|
||||
var delay: float = i * anim_delay
|
||||
var new_pos := size / 2.0 -(btn.size / 2.0)
|
||||
|
||||
# opening
|
||||
if is_open:
|
||||
if btn is GDSubMenuButton and btn.custom_pos != Vector2():
|
||||
new_pos = btn.custom_pos.rotated(main_menu_button.parent_dir.angle())
|
||||
elif is_radial:
|
||||
new_pos += Vector2.from_angle(angle_start + i * angle_step) * (size.x + offset_gap)
|
||||
else:
|
||||
new_pos += parent_dir * (i+1) * (size.x + offset_gap)
|
||||
btn.visible = true
|
||||
|
||||
# closing
|
||||
else:
|
||||
if btn is GDSubMenuButton:
|
||||
btn.expand_menu(false)
|
||||
tw.parallel().tween_property(btn, "visible", false, anim_duration).set_delay(delay)
|
||||
|
||||
tw.parallel().tween_property(btn, "position", new_pos, anim_duration).set_delay(delay)
|
||||
tw.parallel().tween_property(btn, "modulate:a", 1 if is_open else 0, anim_duration).set_delay(delay)
|
||||
|
||||
btn_child_expanded.emit(self, is_open)
|
||||
|
||||
func toggle_menu():
|
||||
expand_menu(!is_open)
|
||||
|
||||
func _process(d: float) -> void:
|
||||
if is_dragged:
|
||||
position = position.lerp(get_parent().get_local_mouse_position() - grabbed_at, d*10)
|
||||
|
||||
func _on_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
is_mouse_click_down = event.is_pressed()
|
||||
if is_mouse_click_down:
|
||||
grabbed_at = event.position
|
||||
elif is_dragged:
|
||||
custom_pos = position.rotated(-main_menu_button.parent_dir.angle())
|
||||
is_dragged = false
|
||||
if event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
custom_pos = Vector2()
|
||||
if is_sub_menu:
|
||||
expand_menu(false)
|
||||
parent.expand_menu(parent.is_open)
|
||||
if event is InputEventMouseMotion and is_mouse_click_down:
|
||||
is_dragged = true
|
||||
if is_open:
|
||||
expand_menu(false)
|
||||
|
||||
func _pressed() -> void:
|
||||
if is_dragged: return
|
||||
properly_pressed.emit()
|
||||
|
||||
static func assign_texture_to_button_from_icon(btn: Button, offset: float) -> void:
|
||||
var tex = btn.icon
|
||||
if not tex: return
|
||||
btn.icon = null
|
||||
var normal_col = btn.get_theme_color("icon_normal_color")
|
||||
var pressed_col = btn.get_theme_color("icon_pressed_color")
|
||||
var hover_col = btn.get_theme_color("icon_hover_color")
|
||||
var tex_rect = TextureRect.new()
|
||||
tex_rect.name = "ico"
|
||||
tex_rect.texture = tex
|
||||
tex_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
tex_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
tex_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
tex_rect.modulate = normal_col
|
||||
tex_rect.set_anchor_and_offset(SIDE_TOP, 0, offset)
|
||||
tex_rect.set_anchor_and_offset(SIDE_LEFT, 0, offset)
|
||||
tex_rect.set_anchor_and_offset(SIDE_RIGHT, 1, -offset)
|
||||
tex_rect.set_anchor_and_offset(SIDE_BOTTOM, 1, -offset)
|
||||
btn.mouse_exited.connect(func(): tex_rect.modulate = normal_col)
|
||||
btn.mouse_entered.connect(func(): tex_rect.modulate = hover_col)
|
||||
btn.pressed.connect(func(): tex_rect.modulate = pressed_col)
|
||||
btn.add_child(tex_rect)
|
||||
btn.size = btn.custom_minimum_size
|
||||
Loading…
Add table
Add a link
Reference in a new issue