diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 4a7cc71..2ca86a7 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -4,6 +4,9 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor ## [Unreleased] +### Fixes +- Session menu: ← and → move across the open-window cards by position without failing; nothing moves past the last card of a row. (#133) + ## [1.6.0-alpha.5] ### Fixes diff --git a/CHANGELOG.md b/CHANGELOG.md index 5074c8d..00dace5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH ## [Unreleased] +### Correções +- Menu da sessão: ← e → percorrem os cartões das janelas abertas pela posição, sem falhar; no último cartão da linha nada se move. (#133) + ## [1.6.0-alpha.5] ### Correções diff --git a/docs/TESTING.md b/docs/TESTING.md index 6918d61..fc0e594 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -331,6 +331,7 @@ Pré-condições: 1.6 instalada por cima da 1.5, com a configuração da 1.5 (te - [ ] Durante o jogo, Share + △ (Select + Y) abre o overlay; → em uma opção lateral leva ao primeiro cartão de janela e ✕ ativa a janela escolhida. Resultado: ______ - [ ] No primeiro cartão de uma coluna, ← retorna à opção lateral de origem; ↑/↓ e ←/→ navegam pelos demais cartões, inclusive com rolagem. Resultado: ______ - [ ] Com seletor de sessão ou confirmação de fechamento aberto, as direções ficam dentro do diálogo. Sem janelas abertas, → não perde o foco. Resultado: ______ +- [ ] Com **3 ou mais janelas** abertas (uma linha de cartões): → e ← percorrem a linha cartão a cartão; → no último cartão não faz nada (não pula para outro lugar); ← no primeiro volta à opção lateral de origem. Com várias linhas: → e ← ficam na linha do cartão. Resultado: ______ - [ ] Os cartões não exibem o botão X no canto. Quadrado/Delete continuam pedindo confirmação para fechar a janela selecionada. Resultado: ______ - [ ] A logo fornecida do ControlFS aparece no painel fixo do overlay, tanto instalado quanto ausente, inclusive no aplicativo publicado. Resultado: ______ - [ ] Com overlay aberto ou outro aplicativo em primeiro plano, a tela Console ao fundo não reage ao DualShock. Resultado: ______ diff --git a/src/ConsoleMode/SessionMenuWindow.xaml.cs b/src/ConsoleMode/SessionMenuWindow.xaml.cs index be3ca35..d5439dd 100644 --- a/src/ConsoleMode/SessionMenuWindow.xaml.cs +++ b/src/ConsoleMode/SessionMenuWindow.xaml.cs @@ -93,6 +93,13 @@ public SessionMenuWindow(MainViewModel viewModel, ScreenRect? target) UiSounds.Play(UiSound.Move); return true; } + // Along a row of window cards: pick the neighbour by position, not by the spatial search. + if (!_editingVolume && direction is (FocusNavigationDirection.Left or FocusNavigationDirection.Right) + && !ViewModel.IsSessionPickerOpen && MoveAcrossWindowCards(direction)) + { + UiSounds.Play(UiSound.Move); + return true; + } // Cross into the grid explicitly; spatial focus may prefer another sidebar row. if (!_editingVolume && direction == FocusNavigationDirection.Right && !ViewModel.IsSessionPickerOpen && Root.XamlRoot is { } root @@ -476,6 +483,48 @@ private bool FocusFirstWindowCard() return false; } + /// + /// Left/right between window cards by position: the card on the same row with the next X to that side. + /// The XY focus search can miss the neighbour (cards scaled while focused, clipped or scrolled in the + /// panel), which left the grid impossible to cross sideways. The end of a row is an end: Right on the last + /// card is consumed so the search can't jump somewhere else; Left on the first column is the caller's + /// (back to the side panel). False when the focus is not on a window card. + /// + private bool MoveAcrossWindowCards(FocusNavigationDirection direction) + { + if (Root.XamlRoot is not { } root + || FocusManager.GetFocusedElement(root) is not FrameworkElement { DataContext: SwitchWindowItem } current) + return false; + + Windows.Foundation.Point Origin(FrameworkElement e) => e.TransformToVisual(SwitcherList).TransformPoint(new Windows.Foundation.Point(0, 0)); + var from = Origin(current); + var rowTolerance = Math.Max(current.ActualHeight / 2, 1); + + Control? best = null; + var bestX = 0d; + for (var i = 0; i < ViewModel.SwitcherWindows.Count; i++) + { + if (SwitcherList.ContainerFromIndex(i) is not { } container + || FocusManager.FindFirstFocusableElement(container) is not Control card + || ReferenceEquals(card, current)) + continue; + var at = Origin(card); + if (Math.Abs(at.Y - from.Y) > rowTolerance) continue; + var dx = at.X - from.X; + if (direction == FocusNavigationDirection.Right ? dx <= 1 : dx >= -1) continue; + if (best is null || (direction == FocusNavigationDirection.Right ? at.X < bestX : at.X > bestX)) + { + best = card; + bestX = at.X; + } + } + + if (best is null) return direction == FocusNavigationDirection.Right; + best.Focus(FocusState.Keyboard); + best.StartBringIntoView(); + return true; + } + /// True when the focus is on a window card with no card to its left (the first column of the grid). private bool IsOnLeftmostCard() {