diff --git a/Assets/PerfectWorld/Scripts/Chat/UI/ChatSystemlUI.cs b/Assets/PerfectWorld/Scripts/Chat/UI/ChatSystemlUI.cs index 17d416447b..fdb12bd4b2 100644 --- a/Assets/PerfectWorld/Scripts/Chat/UI/ChatSystemlUI.cs +++ b/Assets/PerfectWorld/Scripts/Chat/UI/ChatSystemlUI.cs @@ -156,7 +156,7 @@ namespace BrewMonster.Scripts.ChatUI void OnOpenChatPanelRequested(OpenChatPanelRequestedEvent _) { if (this == null) return; - OpenChatPanel(); + OpenChatPanel(_.focusInputOnOpen); } private bool ShouldShowMessage(ChatMessageData data) @@ -347,13 +347,18 @@ namespace BrewMonster.Scripts.ChatUI } public void OpenChatPanel() + { + OpenChatPanel(true); + } + + public void OpenChatPanel(bool focusInputOnOpen) { if (chatPanelUIGO == null) return; SetChatPanelAndBgVisible(true); RefreshVisible(); _chatInput ??= GetComponent(); - if (_chatInput != null && _chatInput.inputField != null) + if (focusInputOnOpen && _chatInput != null && _chatInput.inputField != null) _chatInput.inputField.ActivateInputField(); } @@ -392,5 +397,13 @@ namespace BrewMonster.Scripts.ChatUI /// /// Mini chat (hoặc HUD) publish để mở panel chat đầy đủ; subscribe. /// - public struct OpenChatPanelRequestedEvent { } + public struct OpenChatPanelRequestedEvent + { + public bool focusInputOnOpen; + + public OpenChatPanelRequestedEvent(bool focusInputOnOpen) + { + this.focusInputOnOpen = focusInputOnOpen; + } + } } diff --git a/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs b/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs index 101a4f021e..132a66326a 100644 --- a/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs +++ b/Assets/PerfectWorld/Scripts/Chat/UI/MiniChatUI.cs @@ -84,7 +84,7 @@ namespace BrewMonster.Scripts.ChatUI if (!string.IsNullOrEmpty(chatDialogName) && CECUIManager.Instance != null) CECUIManager.Instance.ShowUI(chatDialogName); - EventBus.Publish(new OpenChatPanelRequestedEvent()); + EventBus.Publish(new OpenChatPanelRequestedEvent(false)); } void OnChannelFilterChanged(ChatChannelFilterChangedEvent e) diff --git a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab index f1481018a6..baca285293 100644 --- a/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab +++ b/Assets/Prefabs/ChatSystem/prefab_ChatSystemUI.prefab @@ -187,6 +187,7 @@ RectTransform: m_Children: - {fileID: 2739455247741079987} - {fileID: 1473246120053017968} + - {fileID: 3282421669272709967} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -234,6 +235,13 @@ MonoBehaviour: m_EditorClassIdentifier: inputField: {fileID: 3037472824809581303} chatSystem: {fileID: 11400000, guid: 43f54723aa074c74e83e5be28975bee5, type: 2} + inputBarRoot: {fileID: 6545370471477453367} + keyboardBottomPadding: 0 + followMobileKeyboard: 1 + typingPreviewRoot: {fileID: 4018402239503345071} + typingPreviewText: {fileID: 5673195965320428126} + typingPreviewRect: {fileID: 3282421669272709967} + previewVerticalOffset: 8 _spriteMap: {fileID: 11400000, guid: f634ecf63ca3d004f82af9b17c966fc9, type: 2} channelButtons: - channel: 0 @@ -1110,6 +1118,142 @@ MonoBehaviour: m_OnValueChanged: m_PersistentCalls: m_Calls: [] +--- !u!1 &2898638287858915607 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2130739312226787242} + - component: {fileID: 9133278188770223934} + - component: {fileID: 5673195965320428126} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2130739312226787242 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2898638287858915607} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3282421669272709967} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &9133278188770223934 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2898638287858915607} + m_CullTransparentMesh: 1 +--- !u!114 &5673195965320428126 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2898638287858915607} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: New Text + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} + m_sharedMaterial: {fileID: 9092487103257209053, guid: 369c2e14814cc9a4b8e3ad4e37769134, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 0, g: 0, b: 0, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 256 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 0 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &3095457626805744279 GameObject: m_ObjectHideFlags: 0 @@ -1869,6 +2013,82 @@ MonoBehaviour: m_OnClick: m_PersistentCalls: m_Calls: [] +--- !u!1 &4018402239503345071 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3282421669272709967} + - component: {fileID: 1622721548801034628} + - component: {fileID: 7848833373218203551} + m_Layer: 5 + m_Name: Typing Preview + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &3282421669272709967 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4018402239503345071} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2130739312226787242} + m_Father: {fileID: 806170753671297629} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1622721548801034628 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4018402239503345071} + m_CullTransparentMesh: 1 +--- !u!114 &7848833373218203551 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4018402239503345071} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 --- !u!1 &4042949282556349356 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/ChatInputHandler.cs b/Assets/Scripts/ChatInputHandler.cs index 88371501f1..7ed8df6ca6 100644 --- a/Assets/Scripts/ChatInputHandler.cs +++ b/Assets/Scripts/ChatInputHandler.cs @@ -31,6 +31,24 @@ namespace BrewMonster.Scripts.ChatUI public TMP_InputField inputField; public ChatSystemSO chatSystem; + [Header("Mobile Keyboard Follow")] + [Tooltip("Rect chứa input/chat composer cần neo lên trên keyboard.")] + [SerializeField] private RectTransform inputBarRoot; + [Tooltip("Padding đáy thêm cho vùng safe-area/gesture.")] + [SerializeField] private float keyboardBottomPadding = 0f; + [Tooltip("Bật để input bar bám theo keyboard khi mobile keyboard mở.")] + [SerializeField] private bool followMobileKeyboard = true; + + [Header("Typing Preview")] + [Tooltip("Root của box xem trước nội dung đang gõ (nằm trên input bar).")] + [SerializeField] private GameObject typingPreviewRoot; + [Tooltip("Text hiển thị nội dung đang gõ realtime.")] + [SerializeField] private TMP_Text typingPreviewText; + [Tooltip("Rect của preview box để neo theo input bar.")] + [SerializeField] private RectTransform typingPreviewRect; + [Tooltip("Khoảng cách dọc cộng thêm cho preview box.")] + [SerializeField] private float previewVerticalOffset = 8f; + [Header("Emoji")] [Tooltip("SO ánh xạ emotion → TMP sprite tag. Gán EmotionLibrarySpriteMap đã build từ Emotion Atlas Converter.")] [SerializeField] EmotionLibrarySpriteMap _spriteMap; @@ -110,6 +128,9 @@ namespace BrewMonster.Scripts.ChatUI private float m_dwTickFarCry = 0; private float m_dwTickFarCry2 = 0; + private Vector2 _inputBarBaseAnchoredPos; + private Vector2 _typingPreviewBaseAnchoredPos; + private bool _cachedAnchors; private void Awake() { @@ -172,6 +193,13 @@ namespace BrewMonster.Scripts.ChatUI } UpdateChatDropdownInteractable(); + CacheInitialAnchors(); + UpdateTypingPreviewFromInput(); + } + + private void Update() + { + UpdateKeyboardAnchoredLayout(); } // ===================================================== @@ -812,6 +840,8 @@ namespace BrewMonster.Scripts.ChatUI { inputField.text = ""; } + + UpdateTypingPreviewFromInput(); } private int GetEmotionSetForCurrentChannel() @@ -877,6 +907,7 @@ namespace BrewMonster.Scripts.ChatUI if (_syncingWireToDisplay) { _lastInputFieldText = newText; + UpdateTypingPreviewFromInput(); return; } @@ -884,6 +915,7 @@ namespace BrewMonster.Scripts.ChatUI { _lastInputFieldText = newText; SyncChatWireBodyFromInput(); + UpdateTypingPreviewFromInput(); return; } @@ -905,11 +937,13 @@ namespace BrewMonster.Scripts.ChatUI inputField.ForceLabelUpdate(); _lastInputFieldText = fixedText; SyncChatWireBodyFromInput(); + UpdateTypingPreviewFromInput(); return; } _lastInputFieldText = newText; SyncChatWireBodyFromInput(); + UpdateTypingPreviewFromInput(); } /// @@ -981,6 +1015,61 @@ namespace BrewMonster.Scripts.ChatUI inputField.ActivateInputField(); } + void CacheInitialAnchors() + { + if (_cachedAnchors) return; + if (inputBarRoot != null) + _inputBarBaseAnchoredPos = inputBarRoot.anchoredPosition; + if (typingPreviewRect != null) + _typingPreviewBaseAnchoredPos = typingPreviewRect.anchoredPosition; + _cachedAnchors = true; + } + + void UpdateKeyboardAnchoredLayout() + { + if (!followMobileKeyboard) + return; + + CacheInitialAnchors(); + float keyboardHeight = GetVisibleKeyboardHeight(); + float yOffset = keyboardHeight + keyboardBottomPadding; + + if (inputBarRoot != null) + { + var inputPos = _inputBarBaseAnchoredPos; + inputPos.y += yOffset; + inputBarRoot.anchoredPosition = inputPos; + } + + if (typingPreviewRect != null) + { + var previewPos = _typingPreviewBaseAnchoredPos; + previewPos.y += yOffset + previewVerticalOffset; + typingPreviewRect.anchoredPosition = previewPos; + } + } + + float GetVisibleKeyboardHeight() + { + if (!TouchScreenKeyboard.visible) + return 0f; + + Rect area = TouchScreenKeyboard.area; + return area.height > 0f ? area.height : 0f; + } + + void UpdateTypingPreviewFromInput() + { + if (typingPreviewRoot == null || typingPreviewText == null || inputField == null) + return; + + string body = ExtractMessageBodyFromVisual(inputField.text ?? "")?.Trim() ?? ""; + bool shouldShow = inputField.isFocused && !string.IsNullOrEmpty(body); + typingPreviewRoot.SetActive(shouldShow); + if (shouldShow) + typingPreviewText.text = body; + } + /// /// C++: GetHostPlayer()->GetPack()->GetItemTotalNum(id) — đếm túi chính. ///