extends CanvasLayer @onready var _audio_player := $'AudioStreamPlayer' as AudioStreamPlayer var _sound_close := ( preload("res://audio/menu_close.ogg") as AudioStream ) var _sound_confirm := ( preload("res://audio/menu_confirm.ogg") as AudioStream ) var _sound_open := ( preload("res://audio/menu_open.ogg") as AudioStream ) var _sound_select := ( preload("res://audio/menu_select.ogg") as AudioStream ) class StackFrame: var node: Control var args: Dictionary var _stack: Array[StackFrame] var _viewport: Viewport signal returned(value: Variant) func _push(scene: PackedScene, args: Dictionary = {}) -> bool: var node := scene.instantiate() _link_up_buttons_to_audio(node) if node is Control: if args.has(&'ui_borrow_instead_of_pausing'): PlayerControl.borrow() else: PlayerControl.pause() if not _stack.is_empty(): var previous := _stack[_stack.size() - 1] if node is Notice: previous.node.process_mode = PROCESS_MODE_DISABLED else: remove_child(previous.node) node.process_mode = PROCESS_MODE_ALWAYS var frame := StackFrame.new() frame.node = node frame.args = args _stack.push_back(frame) add_child(node) var focus := node.find_next_valid_focus() as Control if focus: focus.grab_focus.call_deferred() if args.has(&'ui_open_sound_override'): _audio_player.stream = args.ui_open_sound_override else: _audio_player.stream = _sound_open if _audio_player.stream: _audio_player.play() return true else: node.queue_free() push_error("UI._push() called on non-Control PackedScene %s" % node) return false func _pop() -> bool: if _stack.is_empty(): push_error("UI._pop() called with empty stack") return false else: var frame := _stack[_stack.size() - 1] _stack.pop_back() remove_child(frame.node) frame.node.queue_free() if frame.args.has(&'ui_borrow_instead_of_pausing'): PlayerControl.restore() else: PlayerControl.unpause() if not _stack.is_empty(): var previous := _stack[_stack.size() - 1] if frame.node is Notice: previous.node.process_mode = PROCESS_MODE_INHERIT else: if not previous.node.get_parent(): add_child(previous.node) if frame.args.has(&'ui_close_sound_override'): _audio_player.stream = frame.args.ui_close_sound_override else: _audio_player.stream = _sound_close if _audio_player.stream: _audio_player.play() return true func Call(scene: PackedScene, args: Dictionary = {}) -> Variant: var caller_depth: int = _stack.size() if _push(scene, args): var _retval: Variant while _stack.size() != caller_depth: _retval = await returned return _retval else: return null func Return(whom: Control = null, what: Variant = null) -> bool: if _stack.is_empty(): push_error( "UI.Return() called with empty stack (whom: %s; what: %s)" % [ whom, what ] ) return false elif whom and (_stack[_stack.size() - 1].node != whom): push_error("UI.Return() called with wrong whom (%s) (what: %s)" % [ whom, what ]) return false elif _pop(): returned.emit(what) return true else: return false func Args(whom: Control = null) -> Dictionary: if _stack.is_empty(): push_error("UI.Args() called with empty stack (whom: %s)" % whom) return {} else: var frame := _stack[_stack.size() - 1] if (frame.node == whom) or not whom: return frame.args else: push_error("UI.Args() called with wrong whom (%s)" % whom) return {} func context() -> Control: return null if _stack.is_empty() else _stack[_stack.size() - 1].node func _process(_delta: float) -> void: if not _stack.is_empty(): var frame := _stack[_stack.size() - 1] if ( (not frame.args.has(&'no_autofocus')) or (not frame.args.no_autofocus) ): var focus := get_viewport().gui_get_focus_owner() if not focus: focus = frame.node.find_next_valid_focus() if focus: focus.grab_focus() func _input(event: InputEvent) -> void: var frame: StackFrame = null if not _stack.is_empty(): frame = _stack[_stack.size() - 1] var focus := get_viewport().gui_get_focus_owner() if frame and event.is_action_released(&'pause') and ( frame.args.has(&'on_confirm') or not frame.args.get(&'no_dismiss', false) ): if frame.args.has(&'on_confirm'): frame.args.on_confirm.call() if not frame.args.get(&'no_dismiss', false): Return(null, true) get_viewport().set_input_as_handled() elif frame and event.is_action_released(&'cancel') and ( frame.args.has(&'on_cancel') or not frame.args.get(&'no_dismiss', false) ): if frame.args.has(&'on_cancel'): frame.args.on_cancel.call() if not frame.args.get(&'no_dismiss', false): Return(null, true) get_viewport().set_input_as_handled() elif event.is_action_released(&'confirm'): if focus is BaseButton: get_viewport().set_input_as_handled() var press := InputEventAction.new() press.action = &'ui_accept' press.pressed = true var release := InputEventAction.new() release.action = &'ui_accept' release.pressed = false Input.parse_input_event(press) Input.call_deferred(&'parse_input_event', release) func say(message: Variant, options: Dictionary = {}) -> Variant: if message is Array: var retval: Variant var leadup_options := options.duplicate() if leadup_options.has(&'choices'): leadup_options.erase(&'choices') for i in message.size(): retval = await say( message[i], options if i == message.size() - 1 else leadup_options ) return retval else: options = options.duplicate() options[&'message'] = message options[&'ui_borrow_instead_of_pausing'] = true options[&'no_dismiss'] = true options[&'ui_open_sound_override'] = null options[&'ui_close_sound_override'] = null options[&'ui_confirm_sound_override'] = null options[&'ui_select_sound_override'] = null return await Call( preload('res://ui/Conversation/ConversationUI.tscn'), options ) func _play_sound_confirm() -> void: var frame: StackFrame = null if not _stack.is_empty(): frame = _stack[_stack.size() - 1] if frame and frame.args.has(&'ui_confirm_sound_override'): _audio_player.stream = frame.args.ui_confirm_sound_override else: _audio_player.stream = _sound_confirm if _audio_player.stream: _audio_player.play() func _play_sound_select(_dont_care) -> void: if not _audio_player.playing: var frame: StackFrame = null if not _stack.is_empty(): frame = _stack[_stack.size() - 1] if frame and frame.args.has(&'ui_select_sound_override'): _audio_player.stream = frame.args.ui_select_sound_override else: _audio_player.stream = _sound_select if _audio_player.stream: _audio_player.play() func _link_up_buttons_to_audio(what: Node) -> void: if what is BaseButton: what.pressed.connect(_play_sound_confirm) for child in what.get_children(): _link_up_buttons_to_audio(child) func _link_up_viewport_to_audio() -> void: _viewport.gui_focus_changed.connect(_play_sound_select) func _unlink_viewport_from_audio() -> void: _viewport.gui_focus_changed.disconnect(_play_sound_select) func _enter_tree() -> void: _viewport = get_viewport() _link_up_viewport_to_audio() func _exit_tree() -> void: _unlink_viewport_from_audio() _viewport = null