Refine chat layout and controls
This commit is contained in:
parent
308447d481
commit
26da065716
5 changed files with 401 additions and 53 deletions
|
|
@ -449,6 +449,9 @@ func _initialize_after_data_root() -> void:
|
||||||
_game_ui.interactive_pointer_ui_changed.connect(
|
_game_ui.interactive_pointer_ui_changed.connect(
|
||||||
_ui_pixelation.set_interactive_ui_open
|
_ui_pixelation.set_interactive_ui_open
|
||||||
)
|
)
|
||||||
|
_game_ui.passive_pointer_ui_changed.connect(
|
||||||
|
_ui_pixelation.set_passive_pointer_ui_enabled
|
||||||
|
)
|
||||||
_game_ui.player_menu_backdrop_visibility_changed.connect(
|
_game_ui.player_menu_backdrop_visibility_changed.connect(
|
||||||
_set_player_menu_backdrop_visible
|
_set_player_menu_backdrop_visible
|
||||||
)
|
)
|
||||||
|
|
|
||||||
415
ui/chat_ui.gd
415
ui/chat_ui.gd
|
|
@ -6,8 +6,27 @@ const RECENT_SECONDS: float = 8.0
|
||||||
const SPEECH_SECONDS: float = 6.0
|
const SPEECH_SECONDS: float = 6.0
|
||||||
const DRAFT_SAVE_DELAY: float = 0.4
|
const DRAFT_SAVE_DELAY: float = 0.4
|
||||||
const IDLE_ALPHA: float = 0.58
|
const IDLE_ALPHA: float = 0.58
|
||||||
const PANEL_POSITION := Vector2(30, 376)
|
const PANEL_WIDTH: float = 390.0
|
||||||
const PANEL_SIZE := Vector2(390, 250)
|
const COMPACT_HEIGHT: float = 250.0
|
||||||
|
const BOTTOM_MARGIN: float = 94.0
|
||||||
|
const MIN_TOP_MARGIN: float = 24.0
|
||||||
|
const MIN_HISTORY_HEIGHT: float = 92.0
|
||||||
|
const HISTORY_FONT_SIZE: int = 17
|
||||||
|
const INPUT_FONT_SIZE: int = 23
|
||||||
|
const HINT_FONT_SIZE: int = 12
|
||||||
|
const HISTORY_INPUT_GAP: int = 8
|
||||||
|
const HANDLE_SIZE := Vector2(34, 42)
|
||||||
|
const HANDLE_GAP: float = 6.0
|
||||||
|
const COLLAPSED_REVEAL_WIDTH: float = 8.0
|
||||||
|
const HINT_EDGE_MARGIN: float = 4.0
|
||||||
|
|
||||||
|
enum PresentationState {
|
||||||
|
COLLAPSED,
|
||||||
|
COMPACT,
|
||||||
|
EXPANDED,
|
||||||
|
}
|
||||||
|
|
||||||
|
signal text_entry_ownership_changed(active: bool)
|
||||||
|
|
||||||
var _service: NetworkChatService
|
var _service: NetworkChatService
|
||||||
var _session: NetworkSession
|
var _session: NetworkSession
|
||||||
|
|
@ -15,20 +34,23 @@ var _spawn: PlayerSpawnService
|
||||||
var _player: Player
|
var _player: Player
|
||||||
var _fishing_spot: FishingSpot
|
var _fishing_spot: FishingSpot
|
||||||
var _settings: PlayerSettingsManager
|
var _settings: PlayerSettingsManager
|
||||||
var _history: Label
|
var _history: RichTextLabel
|
||||||
var _entry: LineEdit
|
var _entry: LineEdit
|
||||||
var _status: Label
|
var _status: Label
|
||||||
var _panel: PanelContainer
|
var _panel: PanelContainer
|
||||||
var _collapse_button: Button
|
var _collapse_button: Button
|
||||||
|
var _height_button: Button
|
||||||
var _unread_indicator: Label
|
var _unread_indicator: Label
|
||||||
var _hint: Label
|
var _hint: Label
|
||||||
var _speech_layer: Control
|
var _speech_layer: Control
|
||||||
var _speech: Dictionary[int, Dictionary] = {}
|
var _speech: Dictionary[int, Dictionary] = {}
|
||||||
var _draft_save_timer: Timer
|
var _draft_save_timer: Timer
|
||||||
var _opacity_tween: Tween
|
var _opacity_tween: Tween
|
||||||
|
var _height_tween: Tween
|
||||||
var _opened: bool = false
|
var _opened: bool = false
|
||||||
var _available: bool = false
|
var _available: bool = false
|
||||||
var _collapsed: bool = false
|
var _presentation_state := PresentationState.COMPACT
|
||||||
|
var _visible_state_before_collapse := PresentationState.COMPACT
|
||||||
var _panel_hovered: bool = false
|
var _panel_hovered: bool = false
|
||||||
var _collapsed_has_unread: bool = false
|
var _collapsed_has_unread: bool = false
|
||||||
var _last_message_time: float = -INF
|
var _last_message_time: float = -INF
|
||||||
|
|
@ -43,6 +65,7 @@ func _ready() -> void:
|
||||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
_build_ui()
|
_build_ui()
|
||||||
|
resized.connect(_on_viewport_resized)
|
||||||
set_process(true)
|
set_process(true)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,7 +90,13 @@ func setup(
|
||||||
_session.peer_removed.connect(_on_peer_removed)
|
_session.peer_removed.connect(_on_peer_removed)
|
||||||
_entry.text = _settings.current_settings.chat_draft
|
_entry.text = _settings.current_settings.chat_draft
|
||||||
_entry.caret_column = _entry.text.length()
|
_entry.caret_column = _entry.text.length()
|
||||||
_set_collapsed(_settings.current_settings.chat_collapsed, false)
|
_set_presentation_state(
|
||||||
|
PresentationState.COLLAPSED
|
||||||
|
if _settings.current_settings.chat_collapsed
|
||||||
|
else PresentationState.COMPACT,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
_refresh_history()
|
_refresh_history()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -77,8 +106,10 @@ func open_chat() -> void:
|
||||||
or not _session.is_gameplay_session_active()
|
or not _session.is_gameplay_session_active()
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
_set_collapsed(false)
|
if _presentation_state == PresentationState.COLLAPSED:
|
||||||
|
_set_presentation_state(_visible_state_before_collapse, true, true)
|
||||||
_opened = true
|
_opened = true
|
||||||
|
text_entry_ownership_changed.emit(true)
|
||||||
_prior_movement = _player.is_movement_enabled()
|
_prior_movement = _player.is_movement_enabled()
|
||||||
_prior_camera = _player.is_camera_input_enabled()
|
_prior_camera = _player.is_camera_input_enabled()
|
||||||
_player.set_movement_enabled(false)
|
_player.set_movement_enabled(false)
|
||||||
|
|
@ -109,6 +140,7 @@ func close_chat() -> void:
|
||||||
if not _opened:
|
if not _opened:
|
||||||
return
|
return
|
||||||
_opened = false
|
_opened = false
|
||||||
|
text_entry_ownership_changed.emit(false)
|
||||||
_entry.release_focus()
|
_entry.release_focus()
|
||||||
_entry.hide()
|
_entry.hide()
|
||||||
_player.set_movement_enabled(_prior_movement)
|
_player.set_movement_enabled(_prior_movement)
|
||||||
|
|
@ -149,8 +181,7 @@ func _exit_tree() -> void:
|
||||||
|
|
||||||
func _build_ui() -> void:
|
func _build_ui() -> void:
|
||||||
_panel = PanelContainer.new()
|
_panel = PanelContainer.new()
|
||||||
_panel.position = PANEL_POSITION
|
_panel.name = "ChatPanel"
|
||||||
_panel.size = PANEL_SIZE
|
|
||||||
_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
_panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
_panel.add_theme_stylebox_override("panel", _chat_panel_style())
|
_panel.add_theme_stylebox_override("panel", _chat_panel_style())
|
||||||
_panel.mouse_entered.connect(func() -> void:
|
_panel.mouse_entered.connect(func() -> void:
|
||||||
|
|
@ -163,46 +194,93 @@ func _build_ui() -> void:
|
||||||
)
|
)
|
||||||
add_child(_panel)
|
add_child(_panel)
|
||||||
var box := VBoxContainer.new()
|
var box := VBoxContainer.new()
|
||||||
box.add_theme_constant_override("separation", 6)
|
box.add_theme_constant_override("separation", HISTORY_INPUT_GAP)
|
||||||
_panel.add_child(box)
|
_panel.add_child(box)
|
||||||
_history = Label.new()
|
_history = RichTextLabel.new()
|
||||||
_history.custom_minimum_size = Vector2(360, 158)
|
_history.name = "ChatHistory"
|
||||||
|
_history.custom_minimum_size = Vector2(360, MIN_HISTORY_HEIGHT)
|
||||||
|
_history.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||||
_history.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
_history.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
|
_history.fit_content = false
|
||||||
|
_history.scroll_active = true
|
||||||
|
_history.scroll_following = false
|
||||||
_history.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
|
_history.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
|
||||||
_history.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
_history.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
_history.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
_history.add_theme_font_override("normal_font", UtilityPageStyle.TuffyFont)
|
||||||
_history.add_theme_color_override("font_color", UtilityPageStyle.LIGHT_TEXT)
|
_history.add_theme_font_size_override("normal_font_size", HISTORY_FONT_SIZE)
|
||||||
|
_history.add_theme_color_override("default_color", UtilityPageStyle.LIGHT_TEXT)
|
||||||
|
_history.add_theme_stylebox_override("normal", _borderless_style(Color.TRANSPARENT))
|
||||||
box.add_child(_history)
|
box.add_child(_history)
|
||||||
_status = Label.new()
|
_status = Label.new()
|
||||||
_status.custom_minimum_size = Vector2(360, 20)
|
_status.custom_minimum_size = Vector2(360, 20)
|
||||||
|
_status.hide()
|
||||||
_status.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
_status.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
_status.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
_status.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||||
_status.add_theme_color_override("font_color", Color("ffd6a1"))
|
_status.add_theme_color_override("font_color", Color("ffd6a1"))
|
||||||
box.add_child(_status)
|
box.add_child(_status)
|
||||||
_entry = LineEdit.new()
|
_entry = LineEdit.new()
|
||||||
|
_entry.name = "ChatEntry"
|
||||||
_entry.placeholder_text = "type a message…"
|
_entry.placeholder_text = "type a message…"
|
||||||
_entry.max_length = NetworkChatProtocol.MAX_VISIBLE_CHARACTERS
|
_entry.max_length = NetworkChatProtocol.MAX_VISIBLE_CHARACTERS
|
||||||
_entry.text_submitted.connect(func(_value: String) -> void: _send())
|
_entry.text_submitted.connect(func(_value: String) -> void: _send())
|
||||||
_entry.text_changed.connect(_on_draft_changed)
|
_entry.text_changed.connect(_on_draft_changed)
|
||||||
UtilityPageStyle.apply_line_edit(_entry)
|
UtilityPageStyle.apply_ocean_line_edit(_entry)
|
||||||
|
_entry.add_theme_font_size_override("font_size", INPUT_FONT_SIZE)
|
||||||
box.add_child(_entry)
|
box.add_child(_entry)
|
||||||
_entry.hide()
|
_entry.hide()
|
||||||
_collapse_button = Button.new()
|
_collapse_button = Button.new()
|
||||||
_collapse_button.position = Vector2(2, PANEL_POSITION.y + 101)
|
_collapse_button.name = "ChatCollapseButton"
|
||||||
_collapse_button.size = Vector2(34, 48)
|
_collapse_button.size = HANDLE_SIZE
|
||||||
|
_collapse_button.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
_collapse_button.focus_mode = Control.FOCUS_ALL
|
||||||
|
_collapse_button.z_index = 3
|
||||||
_collapse_button.tooltip_text = "Collapse chat"
|
_collapse_button.tooltip_text = "Collapse chat"
|
||||||
_collapse_button.pressed.connect(func() -> void:
|
_collapse_button.pressed.connect(func() -> void:
|
||||||
_set_collapsed(not _collapsed)
|
if _presentation_state == PresentationState.COLLAPSED:
|
||||||
|
_set_presentation_state(
|
||||||
|
_visible_state_before_collapse,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if _opened:
|
||||||
|
close_chat()
|
||||||
|
_set_presentation_state(PresentationState.COLLAPSED, true, true)
|
||||||
)
|
)
|
||||||
_collapse_button.focus_entered.connect(func() -> void:
|
_collapse_button.focus_entered.connect(func() -> void:
|
||||||
_update_panel_opacity(true)
|
_update_panel_opacity(true)
|
||||||
)
|
)
|
||||||
UtilityPageStyle.apply_button(_collapse_button)
|
_collapse_button.mouse_entered.connect(_on_exterior_handle_entered)
|
||||||
|
_collapse_button.mouse_exited.connect(_on_exterior_handle_exited)
|
||||||
|
_apply_flat_chat_button(_collapse_button)
|
||||||
add_child(_collapse_button)
|
add_child(_collapse_button)
|
||||||
|
_height_button = Button.new()
|
||||||
|
_height_button.name = "ChatHeightButton"
|
||||||
|
_height_button.size = HANDLE_SIZE
|
||||||
|
_height_button.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
|
_height_button.focus_mode = Control.FOCUS_ALL
|
||||||
|
_height_button.z_index = 3
|
||||||
|
_height_button.pressed.connect(func() -> void:
|
||||||
|
_set_presentation_state(
|
||||||
|
PresentationState.COMPACT
|
||||||
|
if _presentation_state == PresentationState.EXPANDED
|
||||||
|
else PresentationState.EXPANDED,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_height_button.focus_entered.connect(func() -> void:
|
||||||
|
_update_panel_opacity(true)
|
||||||
|
)
|
||||||
|
_height_button.mouse_entered.connect(_on_exterior_handle_entered)
|
||||||
|
_height_button.mouse_exited.connect(_on_exterior_handle_exited)
|
||||||
|
_apply_flat_chat_button(_height_button)
|
||||||
|
add_child(_height_button)
|
||||||
_unread_indicator = Label.new()
|
_unread_indicator = Label.new()
|
||||||
|
_unread_indicator.name = "ChatUnreadIndicator"
|
||||||
_unread_indicator.text = "•"
|
_unread_indicator.text = "•"
|
||||||
_unread_indicator.position = Vector2(8, PANEL_POSITION.y + 86)
|
|
||||||
_unread_indicator.size = Vector2(22, 20)
|
_unread_indicator.size = Vector2(22, 20)
|
||||||
|
_unread_indicator.z_index = 4
|
||||||
_unread_indicator.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
_unread_indicator.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
_unread_indicator.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
_unread_indicator.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
_unread_indicator.add_theme_font_override(
|
_unread_indicator.add_theme_font_override(
|
||||||
|
|
@ -212,11 +290,12 @@ func _build_ui() -> void:
|
||||||
_unread_indicator.add_theme_color_override("font_color", Color("ffe08a"))
|
_unread_indicator.add_theme_color_override("font_color", Color("ffe08a"))
|
||||||
add_child(_unread_indicator)
|
add_child(_unread_indicator)
|
||||||
_hint = Label.new()
|
_hint = Label.new()
|
||||||
|
_hint.name = "ChatHint"
|
||||||
_hint.text = "Press Enter to chat"
|
_hint.text = "Press Enter to chat"
|
||||||
_hint.position = Vector2(30, 655)
|
_hint.size = Vector2(180, 18)
|
||||||
_hint.size = Vector2(260, 28)
|
|
||||||
_hint.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
_hint.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
_hint.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
_hint.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||||
|
_hint.add_theme_font_size_override("font_size", HINT_FONT_SIZE)
|
||||||
_hint.add_theme_color_override(
|
_hint.add_theme_color_override(
|
||||||
"font_color", Color(0.94, 0.91, 0.80, 0.72)
|
"font_color", Color(0.94, 0.91, 0.80, 0.72)
|
||||||
)
|
)
|
||||||
|
|
@ -235,13 +314,14 @@ func _build_ui() -> void:
|
||||||
add_child(_draft_save_timer)
|
add_child(_draft_save_timer)
|
||||||
_panel.hide()
|
_panel.hide()
|
||||||
_collapse_button.hide()
|
_collapse_button.hide()
|
||||||
|
_height_button.hide()
|
||||||
_unread_indicator.hide()
|
_unread_indicator.hide()
|
||||||
_hint.hide()
|
_hint.hide()
|
||||||
|
_layout_presentation(false)
|
||||||
|
|
||||||
|
|
||||||
func _chat_panel_style() -> StyleBoxFlat:
|
func _chat_panel_style() -> StyleBoxFlat:
|
||||||
var style := UtilityPageStyle.button_style(Color(0.025, 0.13, 0.19, 0.94))
|
var style := _borderless_style(Color(0.025, 0.13, 0.19, 0.94))
|
||||||
style.border_color = Color(0.75, 0.84, 0.74, 0.66)
|
|
||||||
style.set_corner_radius_all(10)
|
style.set_corner_radius_all(10)
|
||||||
style.content_margin_left = 12
|
style.content_margin_left = 12
|
||||||
style.content_margin_right = 12
|
style.content_margin_right = 12
|
||||||
|
|
@ -250,6 +330,62 @@ func _chat_panel_style() -> StyleBoxFlat:
|
||||||
return style
|
return style
|
||||||
|
|
||||||
|
|
||||||
|
func _borderless_style(color: Color) -> StyleBoxFlat:
|
||||||
|
var style := StyleBoxFlat.new()
|
||||||
|
style.bg_color = color
|
||||||
|
style.set_border_width_all(0)
|
||||||
|
return style
|
||||||
|
|
||||||
|
|
||||||
|
func _flat_chat_button_style(color: Color) -> StyleBoxFlat:
|
||||||
|
var style := StyleBoxFlat.new()
|
||||||
|
style.bg_color = color
|
||||||
|
style.set_border_width_all(0)
|
||||||
|
style.set_corner_radius_all(0)
|
||||||
|
style.anti_aliasing = false
|
||||||
|
style.shadow_size = 0
|
||||||
|
style.shadow_color = Color.TRANSPARENT
|
||||||
|
style.shadow_offset = Vector2.ZERO
|
||||||
|
style.content_margin_left = 0
|
||||||
|
style.content_margin_right = 0
|
||||||
|
style.content_margin_top = 0
|
||||||
|
style.content_margin_bottom = 0
|
||||||
|
return style
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_flat_chat_button(button: Button) -> void:
|
||||||
|
button.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||||
|
button.add_theme_color_override(
|
||||||
|
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||||
|
)
|
||||||
|
button.add_theme_color_override("font_hover_color", Color.WHITE)
|
||||||
|
button.add_theme_color_override("font_pressed_color", Color.WHITE)
|
||||||
|
button.add_theme_color_override("font_focus_color", Color.WHITE)
|
||||||
|
button.add_theme_color_override(
|
||||||
|
"font_disabled_color",
|
||||||
|
Color(UtilityPageStyle.OCEAN_TEXT_SECONDARY, 0.52),
|
||||||
|
)
|
||||||
|
button.add_theme_stylebox_override(
|
||||||
|
"normal", _flat_chat_button_style(UtilityPageStyle.OCEAN_BUTTON)
|
||||||
|
)
|
||||||
|
button.add_theme_stylebox_override(
|
||||||
|
"hover", _flat_chat_button_style(UtilityPageStyle.OCEAN_BUTTON_HOVER)
|
||||||
|
)
|
||||||
|
button.add_theme_stylebox_override(
|
||||||
|
"pressed", _flat_chat_button_style(UtilityPageStyle.OCEAN_SELECTED)
|
||||||
|
)
|
||||||
|
button.add_theme_stylebox_override(
|
||||||
|
"hover_pressed",
|
||||||
|
_flat_chat_button_style(UtilityPageStyle.OCEAN_SELECTED),
|
||||||
|
)
|
||||||
|
button.add_theme_stylebox_override(
|
||||||
|
"focus", _flat_chat_button_style(UtilityPageStyle.OCEAN_SELECTED)
|
||||||
|
)
|
||||||
|
button.add_theme_stylebox_override(
|
||||||
|
"disabled", _flat_chat_button_style(UtilityPageStyle.OCEAN_DISABLED)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _speech_panel_style() -> StyleBoxFlat:
|
func _speech_panel_style() -> StyleBoxFlat:
|
||||||
var style := StyleBoxFlat.new()
|
var style := StyleBoxFlat.new()
|
||||||
style.bg_color = Color(UtilityPageStyle.PAPER_ALT, 0.96)
|
style.bg_color = Color(UtilityPageStyle.PAPER_ALT, 0.96)
|
||||||
|
|
@ -278,7 +414,7 @@ func _send() -> void:
|
||||||
_pending_send_body = ""
|
_pending_send_body = ""
|
||||||
_entry.editable = true
|
_entry.editable = true
|
||||||
elif _send_pending:
|
elif _send_pending:
|
||||||
_status.text = "Sending…"
|
_set_status("Sending…")
|
||||||
|
|
||||||
|
|
||||||
func _on_local_message_confirmed(message: Dictionary) -> void:
|
func _on_local_message_confirmed(message: Dictionary) -> void:
|
||||||
|
|
@ -291,7 +427,7 @@ func _on_local_message_confirmed(message: Dictionary) -> void:
|
||||||
_pending_send_body = ""
|
_pending_send_body = ""
|
||||||
_entry.editable = true
|
_entry.editable = true
|
||||||
_entry.clear()
|
_entry.clear()
|
||||||
_status.text = ""
|
_set_status("")
|
||||||
_flush_draft()
|
_flush_draft()
|
||||||
close_chat()
|
close_chat()
|
||||||
|
|
||||||
|
|
@ -299,7 +435,7 @@ func _on_local_message_confirmed(message: Dictionary) -> void:
|
||||||
func _on_message(message: Dictionary) -> void:
|
func _on_message(message: Dictionary) -> void:
|
||||||
_last_message_time = Time.get_ticks_msec() / 1000.0
|
_last_message_time = Time.get_ticks_msec() / 1000.0
|
||||||
_refresh_history()
|
_refresh_history()
|
||||||
if _collapsed:
|
if _presentation_state == PresentationState.COLLAPSED:
|
||||||
_collapsed_has_unread = true
|
_collapsed_has_unread = true
|
||||||
_unread_indicator.show()
|
_unread_indicator.show()
|
||||||
_update_panel_opacity(true)
|
_update_panel_opacity(true)
|
||||||
|
|
@ -348,7 +484,7 @@ func _on_rejected(message: String) -> void:
|
||||||
_send_pending = false
|
_send_pending = false
|
||||||
_pending_send_body = ""
|
_pending_send_body = ""
|
||||||
_entry.editable = true
|
_entry.editable = true
|
||||||
_status.text = message
|
_set_status(message)
|
||||||
if not _opened:
|
if not _opened:
|
||||||
open_chat()
|
open_chat()
|
||||||
|
|
||||||
|
|
@ -356,10 +492,10 @@ func _on_rejected(message: String) -> void:
|
||||||
func _refresh_history() -> void:
|
func _refresh_history() -> void:
|
||||||
if _service == null:
|
if _service == null:
|
||||||
return
|
return
|
||||||
|
var scroll_state := _capture_history_scroll()
|
||||||
var lines: Array[String] = []
|
var lines: Array[String] = []
|
||||||
var messages := _service.get_history()
|
var messages := _service.get_history()
|
||||||
var start := maxi(0, messages.size() - 8)
|
for message: Dictionary in messages:
|
||||||
for message: Dictionary in messages.slice(start):
|
|
||||||
if int(message["kind"]) == NetworkChatProtocol.Kind.SYSTEM:
|
if int(message["kind"]) == NetworkChatProtocol.Kind.SYSTEM:
|
||||||
lines.append("• %s" % str(message["body"]))
|
lines.append("• %s" % str(message["body"]))
|
||||||
else:
|
else:
|
||||||
|
|
@ -367,38 +503,76 @@ func _refresh_history() -> void:
|
||||||
str(message["sender_display_name"]), str(message["body"]),
|
str(message["sender_display_name"]), str(message["body"]),
|
||||||
])
|
])
|
||||||
_history.text = "\n".join(lines)
|
_history.text = "\n".join(lines)
|
||||||
|
_restore_history_scroll.call_deferred(scroll_state)
|
||||||
|
|
||||||
|
|
||||||
func _refresh_visibility() -> void:
|
func _refresh_visibility() -> void:
|
||||||
if not _available:
|
if not _available:
|
||||||
_panel.hide()
|
_panel.hide()
|
||||||
_collapse_button.hide()
|
_collapse_button.hide()
|
||||||
|
_height_button.hide()
|
||||||
_unread_indicator.hide()
|
_unread_indicator.hide()
|
||||||
_hint.hide()
|
_hint.hide()
|
||||||
return
|
return
|
||||||
_collapse_button.show()
|
_collapse_button.show()
|
||||||
_panel.visible = not _collapsed
|
var collapsed := _presentation_state == PresentationState.COLLAPSED
|
||||||
_unread_indicator.visible = _collapsed and _collapsed_has_unread
|
_panel.show()
|
||||||
|
_height_button.visible = not collapsed
|
||||||
|
_unread_indicator.visible = collapsed and _collapsed_has_unread
|
||||||
_hint.visible = not _opened
|
_hint.visible = not _opened
|
||||||
|
|
||||||
|
|
||||||
func _set_collapsed(value: bool, persist: bool = true) -> void:
|
func _set_presentation_state(
|
||||||
_collapsed = value
|
state: PresentationState,
|
||||||
_collapse_button.text = "›" if _collapsed else "‹"
|
persist: bool = true,
|
||||||
_collapse_button.tooltip_text = (
|
animate: bool = false,
|
||||||
"Expand chat" if _collapsed else "Collapse chat"
|
) -> void:
|
||||||
)
|
_refresh_handle_labels(state)
|
||||||
if not _collapsed:
|
if state == _presentation_state and not animate:
|
||||||
|
_layout_presentation(false)
|
||||||
|
_refresh_visibility()
|
||||||
|
return
|
||||||
|
var previous_state := _presentation_state
|
||||||
|
if (
|
||||||
|
state == PresentationState.COLLAPSED
|
||||||
|
and previous_state != PresentationState.COLLAPSED
|
||||||
|
):
|
||||||
|
_visible_state_before_collapse = previous_state
|
||||||
|
_presentation_state = state
|
||||||
|
var collapsed := state == PresentationState.COLLAPSED
|
||||||
|
if not collapsed:
|
||||||
_collapsed_has_unread = false
|
_collapsed_has_unread = false
|
||||||
_unread_indicator.hide()
|
_unread_indicator.hide()
|
||||||
|
_layout_presentation(animate)
|
||||||
if persist and _settings != null:
|
if persist and _settings != null:
|
||||||
_settings.update_chat_preferences(_entry.text, _collapsed)
|
_settings.update_chat_preferences(_entry.text, collapsed)
|
||||||
_draft_save_timer.start()
|
_draft_save_timer.start()
|
||||||
_refresh_visibility()
|
_refresh_visibility()
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh_handle_labels(state: PresentationState) -> void:
|
||||||
|
var collapsed := state == PresentationState.COLLAPSED
|
||||||
|
_collapse_button.text = ">" if collapsed else "<"
|
||||||
|
_collapse_button.tooltip_text = (
|
||||||
|
"Show chat" if collapsed else "Hide chat"
|
||||||
|
)
|
||||||
|
_height_button.text = "v" if state == PresentationState.EXPANDED else "^"
|
||||||
|
_height_button.tooltip_text = (
|
||||||
|
"Compact chat"
|
||||||
|
if state == PresentationState.EXPANDED
|
||||||
|
else "Expand chat history"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _update_panel_opacity(immediate_recheck: bool = false) -> void:
|
func _update_panel_opacity(immediate_recheck: bool = false) -> void:
|
||||||
if _panel == null or _collapsed or not _available:
|
if _panel == null or not _available:
|
||||||
|
return
|
||||||
|
if _presentation_state == PresentationState.COLLAPSED:
|
||||||
|
if _height_tween == null or not _height_tween.is_running():
|
||||||
|
_panel.modulate.a = IDLE_ALPHA
|
||||||
|
_collapse_button.modulate.a = (
|
||||||
|
1.0 if _collapsed_has_unread else 0.82
|
||||||
|
)
|
||||||
return
|
return
|
||||||
var recent := (
|
var recent := (
|
||||||
Time.get_ticks_msec() / 1000.0 - _last_message_time < RECENT_SECONDS
|
Time.get_ticks_msec() / 1000.0 - _last_message_time < RECENT_SECONDS
|
||||||
|
|
@ -407,6 +581,7 @@ func _update_panel_opacity(immediate_recheck: bool = false) -> void:
|
||||||
var wants_full := (
|
var wants_full := (
|
||||||
_opened or recent or _panel_hovered
|
_opened or recent or _panel_hovered
|
||||||
or focus_owner == _collapse_button
|
or focus_owner == _collapse_button
|
||||||
|
or focus_owner == _height_button
|
||||||
or focus_owner == _entry
|
or focus_owner == _entry
|
||||||
)
|
)
|
||||||
var desired := 1.0 if wants_full else IDLE_ALPHA
|
var desired := 1.0 if wants_full else IDLE_ALPHA
|
||||||
|
|
@ -416,9 +591,11 @@ func _update_panel_opacity(immediate_recheck: bool = false) -> void:
|
||||||
if _opacity_tween != null and _opacity_tween.is_valid():
|
if _opacity_tween != null and _opacity_tween.is_valid():
|
||||||
_opacity_tween.kill()
|
_opacity_tween.kill()
|
||||||
_opacity_tween = create_tween()
|
_opacity_tween = create_tween()
|
||||||
_opacity_tween.tween_property(
|
_opacity_tween.set_parallel(true)
|
||||||
_panel, "modulate:a", desired, 0.18
|
for control: Control in [_panel, _collapse_button, _height_button]:
|
||||||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
_opacity_tween.tween_property(
|
||||||
|
control, "modulate:a", desired, 0.18
|
||||||
|
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||||
|
|
||||||
|
|
||||||
func _update_speech() -> void:
|
func _update_speech() -> void:
|
||||||
|
|
@ -463,17 +640,163 @@ func _update_speech() -> void:
|
||||||
func _on_draft_changed(_value: String) -> void:
|
func _on_draft_changed(_value: String) -> void:
|
||||||
if _settings == null:
|
if _settings == null:
|
||||||
return
|
return
|
||||||
_settings.update_chat_preferences(_entry.text, _collapsed)
|
_settings.update_chat_preferences(
|
||||||
|
_entry.text,
|
||||||
|
_presentation_state == PresentationState.COLLAPSED,
|
||||||
|
)
|
||||||
_draft_save_timer.start()
|
_draft_save_timer.start()
|
||||||
|
|
||||||
|
|
||||||
func _flush_draft() -> void:
|
func _flush_draft() -> void:
|
||||||
if _settings == null or _entry == null:
|
if _settings == null or _entry == null:
|
||||||
return
|
return
|
||||||
_settings.update_chat_preferences(_entry.text, _collapsed)
|
_settings.update_chat_preferences(
|
||||||
|
_entry.text,
|
||||||
|
_presentation_state == PresentationState.COLLAPSED,
|
||||||
|
)
|
||||||
_settings.save_chat_preferences()
|
_settings.save_chat_preferences()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_status(text: String) -> void:
|
||||||
|
_status.text = text
|
||||||
|
_status.visible = not text.is_empty()
|
||||||
|
|
||||||
|
|
||||||
|
func _layout_presentation(animate: bool) -> void:
|
||||||
|
if _panel == null:
|
||||||
|
return
|
||||||
|
var viewport_height := size.y
|
||||||
|
if viewport_height <= 0.0:
|
||||||
|
viewport_height = 720.0
|
||||||
|
var bottom_margin := minf(
|
||||||
|
BOTTOM_MARGIN,
|
||||||
|
maxf(MIN_TOP_MARGIN, (viewport_height - MIN_HISTORY_HEIGHT) * 0.25),
|
||||||
|
)
|
||||||
|
var bottom := viewport_height - bottom_margin
|
||||||
|
var height := COMPACT_HEIGHT
|
||||||
|
var layout_state := (
|
||||||
|
_visible_state_before_collapse
|
||||||
|
if _presentation_state == PresentationState.COLLAPSED
|
||||||
|
else _presentation_state
|
||||||
|
)
|
||||||
|
if layout_state == PresentationState.EXPANDED:
|
||||||
|
height = maxf(MIN_HISTORY_HEIGHT, viewport_height - 2.0 * bottom_margin)
|
||||||
|
height = minf(height, maxf(MIN_HISTORY_HEIGHT, bottom - MIN_TOP_MARGIN))
|
||||||
|
var target_position := Vector2(
|
||||||
|
-(PANEL_WIDTH - COLLAPSED_REVEAL_WIDTH)
|
||||||
|
if _presentation_state == PresentationState.COLLAPSED
|
||||||
|
else 0.0,
|
||||||
|
bottom - height,
|
||||||
|
)
|
||||||
|
var target_size := Vector2(PANEL_WIDTH, height)
|
||||||
|
var handle_stack_height := HANDLE_SIZE.y * 2.0 + HANDLE_GAP
|
||||||
|
var handle_stack_top := (
|
||||||
|
bottom - COMPACT_HEIGHT * 0.5 - handle_stack_height * 0.5
|
||||||
|
)
|
||||||
|
var collapse_position := Vector2(
|
||||||
|
COLLAPSED_REVEAL_WIDTH
|
||||||
|
if _presentation_state == PresentationState.COLLAPSED
|
||||||
|
else PANEL_WIDTH,
|
||||||
|
bottom - COMPACT_HEIGHT * 0.5 - HANDLE_SIZE.y * 0.5
|
||||||
|
if _presentation_state == PresentationState.COLLAPSED
|
||||||
|
else handle_stack_top + HANDLE_SIZE.y + HANDLE_GAP,
|
||||||
|
)
|
||||||
|
var height_position := Vector2(
|
||||||
|
COLLAPSED_REVEAL_WIDTH
|
||||||
|
if _presentation_state == PresentationState.COLLAPSED
|
||||||
|
else PANEL_WIDTH,
|
||||||
|
handle_stack_top,
|
||||||
|
)
|
||||||
|
var unread_position := collapse_position + Vector2(6.0, -18.0)
|
||||||
|
var hint_position := Vector2(
|
||||||
|
HINT_EDGE_MARGIN,
|
||||||
|
viewport_height - _hint.size.y - HINT_EDGE_MARGIN,
|
||||||
|
)
|
||||||
|
if _height_tween != null and _height_tween.is_valid():
|
||||||
|
_height_tween.kill()
|
||||||
|
if not animate:
|
||||||
|
_panel.position = target_position
|
||||||
|
_panel.size = target_size
|
||||||
|
_collapse_button.position = collapse_position
|
||||||
|
_height_button.position = height_position
|
||||||
|
_unread_indicator.position = unread_position
|
||||||
|
_hint.position = hint_position
|
||||||
|
return
|
||||||
|
var scroll_state := _capture_history_scroll()
|
||||||
|
_height_tween = create_tween().set_parallel(true)
|
||||||
|
_height_tween.tween_property(
|
||||||
|
_panel, "position", target_position, UIMotion.CHAT_RESIZE_DURATION
|
||||||
|
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||||
|
_height_tween.tween_property(
|
||||||
|
_panel, "size", target_size, UIMotion.CHAT_RESIZE_DURATION
|
||||||
|
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||||
|
_height_tween.tween_property(
|
||||||
|
_collapse_button,
|
||||||
|
"position",
|
||||||
|
collapse_position,
|
||||||
|
UIMotion.CHAT_RESIZE_DURATION,
|
||||||
|
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||||
|
_height_tween.tween_property(
|
||||||
|
_height_button,
|
||||||
|
"position",
|
||||||
|
height_position,
|
||||||
|
UIMotion.CHAT_RESIZE_DURATION,
|
||||||
|
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||||
|
_height_tween.tween_property(
|
||||||
|
_unread_indicator,
|
||||||
|
"position",
|
||||||
|
unread_position,
|
||||||
|
UIMotion.CHAT_RESIZE_DURATION,
|
||||||
|
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||||
|
_height_tween.finished.connect(func() -> void:
|
||||||
|
_restore_history_scroll(scroll_state)
|
||||||
|
)
|
||||||
|
_hint.position = hint_position
|
||||||
|
|
||||||
|
|
||||||
|
func _capture_history_scroll() -> Dictionary:
|
||||||
|
if _history == null:
|
||||||
|
return {"pinned": true, "value": 0.0}
|
||||||
|
var scroll := _history.get_v_scroll_bar()
|
||||||
|
if scroll == null:
|
||||||
|
return {"pinned": true, "value": 0.0}
|
||||||
|
var bottom := maxf(scroll.min_value, scroll.max_value - scroll.page)
|
||||||
|
return {
|
||||||
|
"pinned": scroll.value >= bottom - 2.0,
|
||||||
|
"value": scroll.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func _restore_history_scroll(state: Dictionary) -> void:
|
||||||
|
if _history == null:
|
||||||
|
return
|
||||||
|
var scroll := _history.get_v_scroll_bar()
|
||||||
|
if scroll == null:
|
||||||
|
return
|
||||||
|
if bool(state.get("pinned", true)):
|
||||||
|
scroll.value = maxf(scroll.min_value, scroll.max_value - scroll.page)
|
||||||
|
else:
|
||||||
|
scroll.value = clampf(
|
||||||
|
float(state.get("value", 0.0)),
|
||||||
|
scroll.min_value,
|
||||||
|
maxf(scroll.min_value, scroll.max_value - scroll.page),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_viewport_resized() -> void:
|
||||||
|
_layout_presentation(false)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_exterior_handle_entered() -> void:
|
||||||
|
_panel_hovered = true
|
||||||
|
_update_panel_opacity(true)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_exterior_handle_exited() -> void:
|
||||||
|
_panel_hovered = false
|
||||||
|
_update_panel_opacity(true)
|
||||||
|
|
||||||
|
|
||||||
func _on_peer_removed(peer_id: int) -> void:
|
func _on_peer_removed(peer_id: int) -> void:
|
||||||
var state: Dictionary = _speech.get(peer_id, {})
|
var state: Dictionary = _speech.get(peer_id, {})
|
||||||
var bubble := state.get("bubble") as PanelContainer
|
var bubble := state.get("bubble") as PanelContainer
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ const PlayerSettingsManagerType = preload(
|
||||||
signal pixelation_settings_visibility_changed(is_visible: bool)
|
signal pixelation_settings_visibility_changed(is_visible: bool)
|
||||||
signal crisp_reset_focus_requested
|
signal crisp_reset_focus_requested
|
||||||
signal interactive_pointer_ui_changed(is_open: bool)
|
signal interactive_pointer_ui_changed(is_open: bool)
|
||||||
|
signal passive_pointer_ui_changed(is_enabled: bool)
|
||||||
signal player_menu_backdrop_visibility_changed(is_visible: bool)
|
signal player_menu_backdrop_visibility_changed(is_visible: bool)
|
||||||
|
|
||||||
@onready var _status_label: Label = %StatusLabel
|
@onready var _status_label: Label = %StatusLabel
|
||||||
|
|
@ -73,11 +74,15 @@ var _gameplay_ui_enabled: bool = false
|
||||||
var _fishing_spot: FishingSpotType
|
var _fishing_spot: FishingSpotType
|
||||||
var _system_menu_open: bool = false
|
var _system_menu_open: bool = false
|
||||||
var _shop_open: bool = false
|
var _shop_open: bool = false
|
||||||
|
var _chat_input_open: bool = false
|
||||||
var _player_menu_hotbar_visible: bool = false
|
var _player_menu_hotbar_visible: bool = false
|
||||||
var _item_effects: PlayerItemEffectsType
|
var _item_effects: PlayerItemEffectsType
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
_chat_ui.text_entry_ownership_changed.connect(
|
||||||
|
_on_chat_text_entry_ownership_changed
|
||||||
|
)
|
||||||
_hotbar_ui.presentation_transition_finished.connect(
|
_hotbar_ui.presentation_transition_finished.connect(
|
||||||
_on_hotbar_presentation_transition_finished
|
_on_hotbar_presentation_transition_finished
|
||||||
)
|
)
|
||||||
|
|
@ -615,16 +620,23 @@ func _refresh_hotbar_visibility() -> void:
|
||||||
|
|
||||||
func _emit_interactive_pointer_ui_changed() -> void:
|
func _emit_interactive_pointer_ui_changed() -> void:
|
||||||
interactive_pointer_ui_changed.emit(
|
interactive_pointer_ui_changed.emit(
|
||||||
_system_menu_open or _player_menu_open or _shop_open
|
_system_menu_open or _player_menu_open or _shop_open or _chat_input_open
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_chat_text_entry_ownership_changed(active: bool) -> void:
|
||||||
|
_chat_input_open = active
|
||||||
|
_emit_interactive_pointer_ui_changed()
|
||||||
|
|
||||||
|
|
||||||
func _refresh_chat_availability() -> void:
|
func _refresh_chat_availability() -> void:
|
||||||
if _chat_ui == null:
|
if _chat_ui == null:
|
||||||
return
|
return
|
||||||
_chat_ui.set_available(
|
var chat_available := (
|
||||||
_gameplay_ui_enabled
|
_gameplay_ui_enabled
|
||||||
and not _system_menu_open
|
and not _system_menu_open
|
||||||
and not _player_menu_open
|
and not _player_menu_open
|
||||||
and not _shop_open
|
and not _shop_open
|
||||||
)
|
)
|
||||||
|
_chat_ui.set_available(chat_available)
|
||||||
|
passive_pointer_ui_changed.emit(chat_available)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const PLAYER_MENU_PAGE_DURATION: float = 0.12
|
||||||
const PLAYER_MENU_INVENTORY_DURATION: float = 0.10
|
const PLAYER_MENU_INVENTORY_DURATION: float = 0.10
|
||||||
const PLAYER_MENU_ENTER_SCALE: float = 0.985
|
const PLAYER_MENU_ENTER_SCALE: float = 0.985
|
||||||
const PLAYER_MENU_EXIT_SCALE: float = 0.99
|
const PLAYER_MENU_EXIT_SCALE: float = 0.99
|
||||||
|
const CHAT_RESIZE_DURATION: float = 0.15
|
||||||
|
|
||||||
|
|
||||||
static func bubble_duration(distance: float) -> float:
|
static func bubble_duration(distance: float) -> float:
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ var _requested_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
|
||||||
var _effective_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
|
var _effective_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE
|
||||||
var _gameplay_active: bool = false
|
var _gameplay_active: bool = false
|
||||||
var _interactive_ui_open: bool = false
|
var _interactive_ui_open: bool = false
|
||||||
|
var _passive_pointer_ui_enabled: bool = false
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -57,12 +58,20 @@ func set_interactive_ui_open(is_open: bool) -> void:
|
||||||
_refresh_mouse_filter()
|
_refresh_mouse_filter()
|
||||||
|
|
||||||
|
|
||||||
|
func set_passive_pointer_ui_enabled(is_enabled: bool) -> void:
|
||||||
|
_passive_pointer_ui_enabled = is_enabled
|
||||||
|
_refresh_mouse_filter()
|
||||||
|
|
||||||
|
|
||||||
func _refresh_mouse_filter() -> void:
|
func _refresh_mouse_filter() -> void:
|
||||||
mouse_filter = (
|
if not _gameplay_active or _interactive_ui_open:
|
||||||
Control.MOUSE_FILTER_STOP
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||||
if not _gameplay_active or _interactive_ui_open
|
elif _passive_pointer_ui_enabled:
|
||||||
else Control.MOUSE_FILTER_IGNORE
|
# Forward pointer events to lightweight gameplay overlays such as Chat,
|
||||||
)
|
# while allowing events they do not consume to continue to gameplay.
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_PASS
|
||||||
|
else:
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
|
||||||
|
|
||||||
func _resize_presentation() -> void:
|
func _resize_presentation() -> void:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue