스크롤 맨아래 이동 버튼 위치와 입력바 연동 보정
AX Agent의 스크롤 맨아래 이동 FAB가 입력창 아래로 잘려 보이던 문제를 수정했습니다. ChatWindow.xaml에서 버튼을 Grid.RowSpan=2로 옮기고 기본 하단 여백을 높여 메시지 영역과 입력 바를 함께 기준으로 배치되도록 맞췄습니다. ChatWindow.xaml.cs에는 UpdateScrollToBottomFabPosition()을 추가해 ComposerShell 높이, 입력창 크기 변화, 창 리사이즈 시 FAB 하단 margin을 자동 계산하도록 연결했습니다. 함께 Loaded 구간의 InputBox/InputBorder 이벤트 연결을 null-safe로 정리해 경고 없이 같은 UI 흐름을 유지하도록 보강했습니다. 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_scroll_to_bottom_fab\ -p:IntermediateOutputPath=obj\verify_scroll_to_bottom_fab\ (경고 0 / 오류 0), dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter ChatWindowSlashPolicyTests -p:OutputPath=bin\verify_scroll_to_bottom_fab_tests\ -p:IntermediateOutputPath=obj\verify_scroll_to_bottom_fab_tests\ (통과 59)
This commit is contained in:
@@ -1764,10 +1764,10 @@
|
||||
</ListBox>
|
||||
|
||||
<!-- ── 스크롤투바텀 FAB (Claude 스타일) ── -->
|
||||
<Button x:Name="ScrollToBottomFab" Grid.Row="3"
|
||||
<Button x:Name="ScrollToBottomFab" Grid.Row="3" Grid.RowSpan="2"
|
||||
Visibility="Collapsed"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
Margin="0,0,28,16" Width="36" Height="36"
|
||||
Margin="0,0,28,96" Width="36" Height="36"
|
||||
Style="{StaticResource GhostBtn}"
|
||||
Click="ScrollToBottomFab_Click"
|
||||
ToolTip="아래로 스크롤" Cursor="Hand"
|
||||
|
||||
@@ -562,7 +562,13 @@ public partial class ChatWindow : Window
|
||||
UpdateTopicPresetScrollMode();
|
||||
UpdateResponsiveChatLayout();
|
||||
UpdateInputBoxHeight();
|
||||
InputBox.Focus();
|
||||
UpdateScrollToBottomFabPosition();
|
||||
if (ComposerShell != null)
|
||||
ComposerShell.SizeChanged += (_, _) => UpdateScrollToBottomFabPosition();
|
||||
if (InputBox != null)
|
||||
InputBox.SizeChanged += (_, _) => UpdateScrollToBottomFabPosition();
|
||||
SizeChanged += (_, _) => UpdateScrollToBottomFabPosition();
|
||||
InputBox?.Focus();
|
||||
// ── 무거운 작업은 유휴 시점에 비동기 실행 ──
|
||||
// A-1: 패널 이벤트 위임 1회 초기화 — 개별 람다 대신 부모 레벨에서 처리
|
||||
InitConversationPanelDelegation();
|
||||
@@ -618,59 +624,65 @@ public partial class ChatWindow : Window
|
||||
// 입력 바 포커스 글로우 효과
|
||||
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
var inputFocusBorderBrush = TryFindResource("InputFocusBorderColor") as Brush ?? borderBrush;
|
||||
InputBox.GotFocus += (_, _) => InputBorder.BorderBrush = inputFocusBorderBrush;
|
||||
InputBox.LostFocus += (_, _) => InputBorder.BorderBrush = borderBrush;
|
||||
if (InputBox != null && InputBorder != null)
|
||||
{
|
||||
InputBox.GotFocus += (_, _) => InputBorder.BorderBrush = inputFocusBorderBrush;
|
||||
InputBox.LostFocus += (_, _) => InputBorder.BorderBrush = borderBrush;
|
||||
}
|
||||
|
||||
// 드래그 앤 드롭 파일 첨부 — Claude Desktop 스타일
|
||||
InputBorder.AllowDrop = true;
|
||||
Brush? _dragOverOriginalBorder = null;
|
||||
InputBorder.DragEnter += (_, de) =>
|
||||
if (InputBorder != null)
|
||||
{
|
||||
if (de.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
InputBorder.AllowDrop = true;
|
||||
Brush? _dragOverOriginalBorder = null;
|
||||
InputBorder.DragEnter += (_, de) =>
|
||||
{
|
||||
_dragOverOriginalBorder = InputBorder.BorderBrush;
|
||||
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
InputBorder.BorderBrush = accent;
|
||||
InputBorder.BorderThickness = new Thickness(2);
|
||||
}
|
||||
};
|
||||
InputBorder.DragLeave += (_, _) =>
|
||||
{
|
||||
if (_dragOverOriginalBorder != null)
|
||||
if (de.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
{
|
||||
_dragOverOriginalBorder = InputBorder.BorderBrush;
|
||||
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
InputBorder.BorderBrush = accent;
|
||||
InputBorder.BorderThickness = new Thickness(2);
|
||||
}
|
||||
};
|
||||
InputBorder.DragLeave += (_, _) =>
|
||||
{
|
||||
InputBorder.BorderBrush = _dragOverOriginalBorder;
|
||||
InputBorder.BorderThickness = new Thickness(1);
|
||||
_dragOverOriginalBorder = null;
|
||||
}
|
||||
};
|
||||
InputBorder.DragOver += (_, de) =>
|
||||
{
|
||||
de.Effects = de.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
|
||||
de.Handled = true;
|
||||
};
|
||||
InputBorder.Drop += (_, de) =>
|
||||
{
|
||||
// 드래그 오버 하이라이트 복원
|
||||
if (_dragOverOriginalBorder != null)
|
||||
if (_dragOverOriginalBorder != null)
|
||||
{
|
||||
InputBorder.BorderBrush = _dragOverOriginalBorder;
|
||||
InputBorder.BorderThickness = new Thickness(1);
|
||||
_dragOverOriginalBorder = null;
|
||||
}
|
||||
};
|
||||
InputBorder.DragOver += (_, de) =>
|
||||
{
|
||||
InputBorder.BorderBrush = _dragOverOriginalBorder;
|
||||
InputBorder.BorderThickness = new Thickness(1);
|
||||
_dragOverOriginalBorder = null;
|
||||
}
|
||||
de.Effects = de.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None;
|
||||
de.Handled = true;
|
||||
};
|
||||
InputBorder.Drop += (_, de) =>
|
||||
{
|
||||
// 드래그 오버 하이라이트 복원
|
||||
if (_dragOverOriginalBorder != null)
|
||||
{
|
||||
InputBorder.BorderBrush = _dragOverOriginalBorder;
|
||||
InputBorder.BorderThickness = new Thickness(1);
|
||||
_dragOverOriginalBorder = null;
|
||||
}
|
||||
|
||||
if (de.Data.GetData(DataFormats.FileDrop) is string[] files && files.Length > 0)
|
||||
{
|
||||
// AI 액션 팝업은 Cowork/Code 탭에서만, 그 외에는 항상 단순 첨부
|
||||
var enableAi = _settings.Settings.Llm.EnableDragDropAiActions
|
||||
&& _activeTab is "Cowork" or "Code";
|
||||
if (enableAi && files.Length <= 5)
|
||||
ShowDropActionMenu(files);
|
||||
else
|
||||
foreach (var f in files) AddAttachedFile(f);
|
||||
if (de.Data.GetData(DataFormats.FileDrop) is string[] files && files.Length > 0)
|
||||
{
|
||||
// AI 액션 팝업은 Cowork/Code 탭에서만, 그 외에는 항상 단순 첨부
|
||||
var enableAi = _settings.Settings.Llm.EnableDragDropAiActions
|
||||
&& _activeTab is "Cowork" or "Code";
|
||||
if (enableAi && files.Length <= 5)
|
||||
ShowDropActionMenu(files);
|
||||
else
|
||||
foreach (var f in files) AddAttachedFile(f);
|
||||
|
||||
InputBox.Focus();
|
||||
}
|
||||
};
|
||||
InputBox?.Focus();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 스킬 시스템 초기화
|
||||
if (_settings.Settings.Llm.EnableSkillSystem)
|
||||
@@ -684,16 +696,19 @@ public partial class ChatWindow : Window
|
||||
SlashChipClose.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
HideSlashChip(restoreText: true);
|
||||
InputBox.Focus();
|
||||
InputBox?.Focus();
|
||||
};
|
||||
|
||||
// InputBox에서 슬래시 팝업 열린 상태로 마우스 휠 → 팝업 스크롤
|
||||
InputBox.PreviewMouseWheel += (_, me) =>
|
||||
if (InputBox != null)
|
||||
{
|
||||
if (!SlashPopup.IsOpen) return;
|
||||
me.Handled = true;
|
||||
SlashPopup_ScrollByDelta(me.Delta);
|
||||
};
|
||||
InputBox.PreviewMouseWheel += (_, me) =>
|
||||
{
|
||||
if (!SlashPopup.IsOpen) return;
|
||||
me.Handled = true;
|
||||
SlashPopup_ScrollByDelta(me.Delta);
|
||||
};
|
||||
}
|
||||
|
||||
// 탭 UI 초기 상태
|
||||
UpdateFolderBar();
|
||||
@@ -979,7 +994,10 @@ public partial class ChatWindow : Window
|
||||
var atBottom = GetTranscriptVerticalOffset() >= GetTranscriptScrollableHeight() - 120;
|
||||
_userScrolled = !atBottom;
|
||||
if (ScrollToBottomFab != null)
|
||||
{
|
||||
UpdateScrollToBottomFabPosition();
|
||||
ScrollToBottomFab.Visibility = _userScrolled ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToBottomFab_Click(object sender, RoutedEventArgs e)
|
||||
@@ -987,6 +1005,16 @@ public partial class ChatWindow : Window
|
||||
ForceScrollToEnd();
|
||||
}
|
||||
|
||||
private void UpdateScrollToBottomFabPosition()
|
||||
{
|
||||
if (ScrollToBottomFab == null)
|
||||
return;
|
||||
|
||||
var composerHeight = ComposerShell?.ActualHeight ?? 0;
|
||||
var bottomOffset = Math.Max(96, composerHeight + 18);
|
||||
ScrollToBottomFab.Margin = new Thickness(0, 0, 28, bottomOffset);
|
||||
}
|
||||
|
||||
private void AutoScrollIfNeeded()
|
||||
{
|
||||
if (!_userScrolled)
|
||||
|
||||
Reference in New Issue
Block a user