슬래시 입력 UX 보강: 포커스/키 처리 통합 및 휠 fallback 추가
Some checks failed
Release Gate / gate (push) Has been cancelled

- InputBox_PreviewKeyDown에서 슬래시 네비게이션 키를 우선 처리

- TryHandleSlashNavigationKey로 창/입력창 키 경로 통합(Up/Down/Pg/Home/End/Tab/Enter/Esc)

- 가시 항목 0일 때 SlashScrollViewer 오프셋 fallback 추가

- README/DEVELOPMENT 이력(2026-04-04 16:18 KST) 동기화
This commit is contained in:
2026-04-04 16:18:44 +09:00
parent 64be0e082d
commit 3b9938e01b
3 changed files with 71 additions and 31 deletions

View File

@@ -4923,6 +4923,9 @@ public partial class ChatWindow : Window
private async void InputBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (TryHandleSlashNavigationKey(e))
return;
// Ctrl+V: 클립보드 이미지 붙여넣기
if (e.Key == Key.V && Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
{
@@ -5571,6 +5574,13 @@ public partial class ChatWindow : Window
if (_slashPalette.Matches.Count == 0)
return;
if (GetVisibleSlashOrderedIndices().Count == 0)
{
if (SlashScrollViewer != null)
SlashScrollViewer.ScrollToVerticalOffset(Math.Max(0, SlashScrollViewer.VerticalOffset - delta / 3.0));
return;
}
var steps = Math.Max(1, Math.Abs(delta) / 120);
var direction = delta > 0 ? -1 : 1;
for (var i = 0; i < steps; i++)
@@ -10902,61 +10912,69 @@ public partial class ChatWindow : Window
}
// 슬래시 명령 팝업 키 처리
if (SlashPopup.IsOpen)
if (TryHandleSlashNavigationKey(e))
return;
if (PermissionPopup.IsOpen && e.Key == Key.Escape)
{
if (e.Key == Key.Escape)
{
PermissionPopup.IsOpen = false;
e.Handled = true;
}
}
private bool TryHandleSlashNavigationKey(KeyEventArgs e)
{
if (!SlashPopup.IsOpen)
return false;
switch (e.Key)
{
case Key.Escape:
SlashPopup.IsOpen = false;
_slashPalette.SelectedIndex = -1;
e.Handled = true;
}
else if (e.Key == Key.Up)
{
SlashPopup_ScrollByDelta(120); // 위로 1칸
return true;
case Key.Up:
SlashPopup_ScrollByDelta(120);
e.Handled = true;
}
else if (e.Key == Key.Down)
{
SlashPopup_ScrollByDelta(-120); // 아래로 1칸
return true;
case Key.Down:
SlashPopup_ScrollByDelta(-120);
e.Handled = true;
}
else if (e.Key == Key.PageUp)
{
return true;
case Key.PageUp:
SlashPopup_ScrollByDelta(600);
e.Handled = true;
}
else if (e.Key == Key.PageDown)
{
return true;
case Key.PageDown:
SlashPopup_ScrollByDelta(-600);
e.Handled = true;
}
else if (e.Key == Key.Home)
return true;
case Key.Home:
{
var visible = GetVisibleSlashOrderedIndices();
_slashPalette.SelectedIndex = visible.Count > 0 ? visible[0] : GetFirstVisibleSlashIndex(_slashPalette.Matches);
UpdateSlashSelectionVisualState();
EnsureSlashSelectionVisible();
e.Handled = true;
return true;
}
else if (e.Key == Key.End)
case Key.End:
{
var visible = GetVisibleSlashOrderedIndices();
_slashPalette.SelectedIndex = visible.Count > 0 ? visible[^1] : GetFirstVisibleSlashIndex(_slashPalette.Matches);
UpdateSlashSelectionVisualState();
EnsureSlashSelectionVisible();
e.Handled = true;
return true;
}
else if (e.Key == Key.Enter && _slashPalette.SelectedIndex >= 0)
{
e.Handled = true;
case Key.Tab when _slashPalette.SelectedIndex >= 0:
case Key.Enter when _slashPalette.SelectedIndex >= 0:
ExecuteSlashSelectedItem();
}
}
if (PermissionPopup.IsOpen && e.Key == Key.Escape)
{
PermissionPopup.IsOpen = false;
e.Handled = true;
e.Handled = true;
return true;
default:
return false;
}
}