diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 4dda22d..c876b19 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -3,6 +3,9 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Before publishing a version, add a `## [VERSION]` section to **both** files: the release workflow uses the section matching the tag and fails if either is missing. ## [Unreleased] +### Changes +- **Settings: every setting with an icon and its name below** (#293, adjusts the icon-only design of 0.15): still one wide column, but each tile now shows the **icon with its name directly below**, so you can identify a setting without relying on the description. The current value shows in the focused tile's reading area ("Name: value" highlighted, what it does below) and in a hover tooltip. Every setting stays reachable with controller, mouse and keyboard; none was removed. +- **Controller sounds softer and rounder, in the console style** (#287): the four sounds were replaced by others from two Kenney packs (CC0: "UI Audio" and "Interface Sounds"), chosen for a soft timbre and gentle attack (confirm rises in pitch), with a filter that takes off the harsh highs, click-free and at the same energy. Origin and licence in THIRD_PARTY_NOTICES.md and assets/sounds. No Sony sound was used: the PS5 interface is only a feel reference. ## [0.15.0-alpha.1] ### What's new diff --git a/CHANGELOG.md b/CHANGELOG.md index 620fab0..cc6f9d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CHANGELOG.en-US.md`. Antes de publicar uma versão, adicione uma seção `## [VERSÃO]` **nos dois arquivos**. O workflow de release usa a seção da tag e falha se faltar alguma. ## [Unreleased] +### Mudanças +- **Configurações: cada ajuste com ícone e nome embaixo** (#293, ajusta o desenho só de ícones da 0.15): continua uma coluna larga, mas cada bloco agora mostra o **ícone com o nome logo abaixo**, para identificar o ajuste sem depender da descrição. O valor atual aparece na leitura do bloco em foco ("Nome: valor" em destaque e o que ele faz embaixo) e numa dica ao passar o mouse. Todos os ajustes seguem alcançáveis por controle, mouse e teclado; nenhum foi removido. +- **Sons do controle mais suaves e redondos, na linha dos de console** (#287): os quatro sons foram trocados por outros de dois pacotes de Kenney em CC0 ("UI Audio" e "Interface Sounds"), escolhidos pelo timbre macio e de ataque suave (confirmar sobe de tom), com um filtro que tira o brilho agudo, sem estalo e com a mesma energia. Origem e licença em THIRD_PARTY_NOTICES.md e assets/sounds. Nenhum som da Sony foi usado: a interface do PS5 é só referência de sensação. ## [0.15.0-alpha.1] ### Novidades diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index c7d8739..d6b28e8 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -101,12 +101,13 @@ AGPL-3.0-only deste código e não podem ser reutilizadas sem permissão dos don Sons do controle ------------------------------------------------------------------------------ -Os quatro toques de mover, confirmar, voltar e marcar (assets/sounds/*.wav, embutidos no aplicativo) vêm do pacote -"Interface Sounds" de Kenney (https://kenney.nl/assets/interface-sounds), licença Creative Commons Zero (CC0 1.0, domínio -público): uso pessoal, educacional e comercial e redistribuição livres, sem exigir crédito (o crédito a Kenney é voluntário e -está aqui). Usados: select_002 (mover), glass_006 (marcar), confirmation_001 (confirmar) e back_004 (voltar), aparados, com fade -de 2 ms e mesma energia por tools/make-sounds.py. A licença original está em assets/sounds/LICENSE-Kenney-CC0.txt. Por ser -CC0, não limita a AGPL-3.0-only do restante do projeto. A interface do PS5 é apenas referência de sensação; nenhum som da Sony -foi usado ou imitado. +Os quatro toques de mover, marcar, confirmar e voltar (assets/sounds/*.wav, embutidos no aplicativo) vêm de dois pacotes de +Kenney (https://kenney.nl), ambos Creative Commons Zero (CC0 1.0, domínio público): uso pessoal, educacional e comercial e +redistribuição livres, sem exigir crédito (o crédito a Kenney é voluntário e está aqui). Do pacote "UI Audio": rollover5 (mover). +Do pacote "Interface Sounds": glass_002 (marcar), maximize_009 (confirmar) e back_002 (voltar). Foram aparados, suavizados com +um filtro passa-baixa, com fades e mesma energia por tools/make-sounds.py. As licenças originais estão em +assets/sounds/LICENSE-Kenney-CC0.txt e LICENSE-Kenney-UI-Audio-CC0.txt. Por serem CC0, não limitam a AGPL-3.0-only do restante +do projeto. A interface do PS5 é apenas referência de sensação (toques curtos, redondos e suaves, confirmar subindo e voltar +descendo); nenhum som da Sony foi usado ou imitado. O Windows e seus serviços não fazem parte deste código aberto. diff --git a/assets/sounds/LICENSE-Kenney-UI-Audio-CC0.txt b/assets/sounds/LICENSE-Kenney-UI-Audio-CC0.txt new file mode 100644 index 0000000..4ff46a6 --- /dev/null +++ b/assets/sounds/LICENSE-Kenney-UI-Audio-CC0.txt @@ -0,0 +1,21 @@ + + + UI SFX Set + + by Kenney Vleugels (Kenney.nl) + + ------------------------------ + + License (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + You may use these assets in personal and commercial projects. + Credit (Kenney or www.kenney.nl) would be nice but is not mandatory. + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + + Follow on Twitter for updates: + @KenneyNL \ No newline at end of file diff --git a/assets/sounds/back.wav b/assets/sounds/back.wav index 6239ee8..1722ea1 100644 Binary files a/assets/sounds/back.wav and b/assets/sounds/back.wav differ diff --git a/assets/sounds/confirm.wav b/assets/sounds/confirm.wav index 17be3c8..48ff6a9 100644 Binary files a/assets/sounds/confirm.wav and b/assets/sounds/confirm.wav differ diff --git a/assets/sounds/move.wav b/assets/sounds/move.wav index 838fa72..9a094e8 100644 Binary files a/assets/sounds/move.wav and b/assets/sounds/move.wav differ diff --git a/assets/sounds/select.wav b/assets/sounds/select.wav index ee558ea..a024612 100644 Binary files a/assets/sounds/select.wav and b/assets/sounds/select.wav differ diff --git a/docs/GUIDE.md b/docs/GUIDE.md index 499287a..f1158e3 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -45,7 +45,7 @@ Item actions (North) and the app Menu (Start) open with the most used actions as In dialogs, the footer says what each button does. With the focus on the back option itself (**Cancelar**, **Fechar**), the footer shows the next option on Right instead of repeating the same thing on both buttons. -**Option pickers:** a setting with several values that the tile or row doesn't show (**Exibição**, **Densidade da lista**, **Tema**, **Cor de destaque**, **Ordenar por**, **Confirmar com**, **Legendas**, **Fluidez**) opens a picker listing every choice, each with a short explanation, the current one marked with a filled radio icon and the word "atual" and the focus on it. South/Enter or a click on a choice applies it and returns to Configurações on the same setting; Back (or Esc) closes the picker without changing anything. Simple on/off settings and settings that open their own screen stay one press. The same picker is used for the size and date filters of a search and the mode of batch rename. All settings live in Menu → **Configurações** (settings), grouped under **Exibição** (view, density, details panel, sort, hidden items), **Busca e privacidade** (search in subfolders, recents, keyboard suggestions), **Controles** (confirm button, button labels, Fluidez (controller read rate), Sons do controle (controller sounds), Leve em segundo plano (light in the background), gyro aiming (experimental), active controller, controller test, controllers without a profile) and **ControlFS** (updates). The settings are one wide column of **icon-only tiles**, in groups: no text sits beside an icon. The focused tile fills with the accent colour (red if dangerous, grey if unavailable) and the reading area under the grid shows its **name and current value** in large type, with what it does below (a mouse hover shows the same in a tooltip; Narrator reads the name and value). Values are states, never verbs, and on/off settings say **ligado**/**desligado** (on/off); the same setting has the same name and value everywhere. Left/Right move between tiles, Up/Down between rows and, past the last row, into the next group (same column); **L1/R1** jump group to group. Menu → **Configurações** is the first tile of the Menu. Every window of the app keeps its size while you use it: the panel never widens, narrows, grows or shrinks as focus moves, values change, a message appears or you switch options (for example ZIP, TAR.GZ and 7z in the Compress window are the same size); long texts wrap inside it, and the description area is as tall as the longest description. Changing a setting keeps Configurações open with the new value and focus on the same setting, so you can change several in a row; Back closes it. +**Option pickers:** a setting with several values that the tile or row doesn't show (**Exibição**, **Densidade da lista**, **Tema**, **Cor de destaque**, **Ordenar por**, **Confirmar com**, **Legendas**, **Fluidez**) opens a picker listing every choice, each with a short explanation, the current one marked with a filled radio icon and the word "atual" and the focus on it. South/Enter or a click on a choice applies it and returns to Configurações on the same setting; Back (or Esc) closes the picker without changing anything. Simple on/off settings and settings that open their own screen stay one press. The same picker is used for the size and date filters of a search and the mode of batch rename. All settings live in Menu → **Configurações** (settings), grouped under **Exibição** (view, density, details panel, sort, hidden items), **Busca e privacidade** (search in subfolders, recents, keyboard suggestions), **Controles** (confirm button, button labels, Fluidez (controller read rate), Sons do controle (controller sounds), Leve em segundo plano (light in the background), gyro aiming (experimental), active controller, controller test, controllers without a profile) and **ControlFS** (updates). The settings are one wide column of **tiles with an icon and its name directly below**, in groups. The focused tile fills with the accent colour (red if dangerous, grey if unavailable) and the reading area under the grid shows its **name and current value** in large type, with what it does below (a mouse hover shows the same in a tooltip; Narrator reads the name and value). Values are states, never verbs, and on/off settings say **ligado**/**desligado** (on/off); the same setting has the same name and value everywhere. Left/Right move between tiles, Up/Down between rows and, past the last row, into the next group (same column); **L1/R1** jump group to group. Menu → **Configurações** is the first tile of the Menu. Every window of the app keeps its size while you use it: the panel never widens, narrows, grows or shrinks as focus moves, values change, a message appears or you switch options (for example ZIP, TAR.GZ and 7z in the Compress window are the same size); long texts wrap inside it, and the description area is as tall as the longest description. Changing a setting keeps Configurações open with the new value and focus on the same setting, so you can change several in a row; Back closes it. ### Controller test diff --git a/docs/GUIDE.pt-BR.md b/docs/GUIDE.pt-BR.md index 1525fb0..782ad93 100644 --- a/docs/GUIDE.pt-BR.md +++ b/docs/GUIDE.pt-BR.md @@ -45,7 +45,7 @@ As ações do item (Norte) e o Menu do app (Start) abrem com as ações mais usa Nos diálogos, o rodapé diz o que cada botão faz. Com o foco na própria opção de voltar (**Cancelar**, **Fechar**), o rodapé mostra a próxima opção na seta para a direita em vez de repetir a mesma coisa nos dois botões. -**Seletor de opções:** um ajuste com vários valores que o bloco ou a linha não mostram (**Exibição**, **Densidade da lista**, **Tema**, **Cor de destaque**, **Ordenar por**, **Confirmar com**, **Legendas**, **Fluidez**) abre um seletor com todas as escolhas, cada uma com uma explicação curta, a atual marcada com um botão de opção cheio e a palavra "atual" e o foco nela. Sul/Enter ou um clique numa escolha a aplica e volta a Configurações no mesmo ajuste; Voltar (ou Esc) fecha o seletor sem mudar nada. Ligar/desligar simples e ajustes que abrem a própria tela continuam de um toque. O mesmo seletor vale para o tamanho e a data dos filtros da busca e para o modo de Renomear em lote. Todos os ajustes ficam em Menu → **Configurações**, em grupos: **Exibição** (lista/grade, densidade, painel de detalhes, ordenação, itens ocultos), **Busca e privacidade** (busca em subpastas, recentes, sugestões do teclado), **Controles** (botão de confirmar, legendas, Fluidez, Sons do controle, Leve em segundo plano, mira por giroscópio (experimental), controle ativo, teste de controles, controles sem perfil) e **ControlFS** (atualizações). Os ajustes são uma coluna larga de **blocos só de ícone**, em grupos: nenhum texto fica ao lado de um ícone. O bloco em foco se preenche com a cor de destaque (vermelho se perigoso, cinza se indisponível) e a área de leitura sob a grade mostra o **nome e o valor atual** em letra grande, com o que o ajuste faz logo abaixo (o mouse mostra o mesmo numa dica; o Narrador lê o nome e o valor). Os valores são estados, nunca verbos, e ajustes de ligar/desligar dizem **ligado**/**desligado**; o mesmo ajuste tem o mesmo nome e valor em qualquer tela. Esquerda/direita andam entre os blocos, cima/baixo entre as linhas e, depois da última, entram no grupo seguinte (mesma coluna); **L1/R1** pulam de grupo em grupo. Menu → **Configurações** é o primeiro bloco do Menu. Toda janela do app mantém o tamanho enquanto você a usa: o painel não alarga, estreita, cresce nem encolhe quando o foco se move, um valor muda, aparece um aviso ou você troca de opção (por exemplo, ZIP, TAR.GZ e 7z na janela Compactar têm o mesmo tamanho); textos longos quebram linha dentro dele, e a área de descrição tem a altura da descrição mais longa. Mudar um ajuste mantém Configurações aberto com o valor novo e o foco no mesmo ajuste, para mudar vários seguidos; Voltar fecha. +**Seletor de opções:** um ajuste com vários valores que o bloco ou a linha não mostram (**Exibição**, **Densidade da lista**, **Tema**, **Cor de destaque**, **Ordenar por**, **Confirmar com**, **Legendas**, **Fluidez**) abre um seletor com todas as escolhas, cada uma com uma explicação curta, a atual marcada com um botão de opção cheio e a palavra "atual" e o foco nela. Sul/Enter ou um clique numa escolha a aplica e volta a Configurações no mesmo ajuste; Voltar (ou Esc) fecha o seletor sem mudar nada. Ligar/desligar simples e ajustes que abrem a própria tela continuam de um toque. O mesmo seletor vale para o tamanho e a data dos filtros da busca e para o modo de Renomear em lote. Todos os ajustes ficam em Menu → **Configurações**, em grupos: **Exibição** (lista/grade, densidade, painel de detalhes, ordenação, itens ocultos), **Busca e privacidade** (busca em subpastas, recentes, sugestões do teclado), **Controles** (botão de confirmar, legendas, Fluidez, Sons do controle, Leve em segundo plano, mira por giroscópio (experimental), controle ativo, teste de controles, controles sem perfil) e **ControlFS** (atualizações). Os ajustes são uma coluna larga de **blocos com o ícone e o nome logo abaixo**, em grupos. O bloco em foco se preenche com a cor de destaque (vermelho se perigoso, cinza se indisponível) e a área de leitura sob a grade mostra o **nome e o valor atual** em letra grande, com o que o ajuste faz logo abaixo (o mouse mostra o mesmo numa dica; o Narrador lê o nome e o valor). Os valores são estados, nunca verbos, e ajustes de ligar/desligar dizem **ligado**/**desligado**; o mesmo ajuste tem o mesmo nome e valor em qualquer tela. Esquerda/direita andam entre os blocos, cima/baixo entre as linhas e, depois da última, entram no grupo seguinte (mesma coluna); **L1/R1** pulam de grupo em grupo. Menu → **Configurações** é o primeiro bloco do Menu. Toda janela do app mantém o tamanho enquanto você a usa: o painel não alarga, estreita, cresce nem encolhe quando o foco se move, um valor muda, aparece um aviso ou você troca de opção (por exemplo, ZIP, TAR.GZ e 7z na janela Compactar têm o mesmo tamanho); textos longos quebram linha dentro dele, e a área de descrição tem a altura da descrição mais longa. Mudar um ajuste mantém Configurações aberto com o valor novo e o foco no mesmo ajuste, para mudar vários seguidos; Voltar fecha. ### Teste de controles diff --git a/docs/ui-redesign.md b/docs/ui-redesign.md index 9702439..71d8449 100644 --- a/docs/ui-redesign.md +++ b/docs/ui-redesign.md @@ -221,7 +221,7 @@ added line. | Where | Pickers | Stays one press | |---|---|---| | Compactar | Formato, Compressão | Nome (keyboard), Compactar | -| Configurações (uma coluna larga de blocos só de ícone, #293; L1/R1 pulam de grupo; primeiro bloco do Menu) | Exibição, Densidade da lista, Tema, Cor de destaque, Ordenar por, Confirmar com, Legendas, Fluidez | Ordem, Painel de detalhes, Itens ocultos, Tela cheia, Busca em subpastas, Recentes, Restaurar abas, Sugestões do teclado, Segundo plano, Mira por giroscópio (on/off or symmetric two-way toggles); Controle ativo, Teste de controles, Controles sem perfil, Atualizações (they open their own screen) | +| Configurações (uma coluna larga de blocos com ícone e nome, #293; L1/R1 pulam de grupo; primeiro bloco do Menu) | Exibição, Densidade da lista, Tema, Cor de destaque, Ordenar por, Confirmar com, Legendas, Fluidez | Ordem, Painel de detalhes, Itens ocultos, Tela cheia, Busca em subpastas, Recentes, Restaurar abas, Sugestões do teclado, Segundo plano, Mira por giroscópio (on/off or symmetric two-way toggles); Controle ativo, Teste de controles, Controles sem perfil, Atualizações (they open their own screen) | | Filtros da busca | Tamanho, Modificado | Tipos (checkboxes) | | Renomear em lote | Modo, Converter para | Dígitos (1–6, a stepper), text fields | diff --git a/src/ControlFS.App/Views/ModalView.cs b/src/ControlFS.App/Views/ModalView.cs index 4065e92..851a767 100644 --- a/src/ControlFS.App/Views/ModalView.cs +++ b/src/ControlFS.App/Views/ModalView.cs @@ -645,7 +645,7 @@ private static Border BuildMenu(AppController app, MenuModal menu, out Action up // Descrição fixa da opção em foco (#227): a altura já é a da descrição mais longa; uma linha, no mínimo, se houver alguma. var lineHeight = Math.Ceiling(Theme.FontCaption * 1.4); - var description = new TextSlot(menu.AllDescriptions, text => menu.IconOnly ? IconReadout(text) : (FrameworkElement)new TextBlock { Text = text, FontSize = Theme.FontCaption, Foreground = Theme.TextMuted, TextWrapping = TextWrapping.Wrap }); + var description = new TextSlot(menu.AllDescriptions, text => menu.IconGrid ? IconReadout(text) : (FrameworkElement)new TextBlock { Text = text, FontSize = Theme.FontCaption, Foreground = Theme.TextMuted, TextWrapping = TextWrapping.Wrap }); UIElement? below = null; if (!menu.IsPicker && menu.AllDescriptions.Any()) { @@ -686,7 +686,7 @@ void Replace(int index) } /// - /// Leitura do ajuste em foco numa grade só de ícones (#293): "Nome: valor" em destaque e, embaixo, o que ele faz. Fica sempre no + /// Leitura do ajuste em foco numa grade de ícones com nome (#293): "Nome: valor" em destaque e, embaixo, o que ele faz. Fica sempre no /// mesmo lugar e tem a altura do texto mais longo, então mover o foco nunca redimensiona o painel. /// private static StackPanel IconReadout(string text) @@ -716,8 +716,7 @@ private static Border MenuTile(AppController app, MenuModal menu, int index) var iconInk = focused && enabled ? Theme.FocusText : !enabled ? (focused ? Theme.TextMuted : Theme.TextDisabled) : destructive ? Theme.Danger : Theme.Accent; var content = new StackPanel { Spacing = Theme.Space(6), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; - content.Children.Add(Glyph(ActionIcons.Glyph(item.Icon), Theme.Font(menu.IconOnly ? 30 : 24), iconInk)); - if (menu.IconOnly) return IconTile(app, menu, index, content, fill, focused, enabled, destructive, grid); + content.Children.Add(Glyph(ActionIcons.Glyph(item.Icon), Theme.Font(menu.IconGrid ? 28 : 24), iconInk)); var text = new TextBlock { Text = item.TileLabel, @@ -736,7 +735,7 @@ private static Border MenuTile(AppController app, MenuModal menu, int index) AutomationProperties.SetName(text, item.Label); } content.Children.Add(WeightStable(text, focused)); - if (item.Value is { Length: > 0 } value) + if (item.Value is { Length: > 0 } value && !menu.IconGrid) // na grade de Configurações o valor fica na leitura do foco { content.Spacing = Theme.Space(4); content.Children.Add(new TextBlock @@ -759,7 +758,7 @@ private static Border MenuTile(AppController app, MenuModal menu, int index) Background = fill, CornerRadius = Theme.RowRadius, Padding = new Thickness(Theme.Space(4), Theme.Space(10), Theme.Space(4), Theme.Space(8)), - MinHeight = Theme.Scaled(78), + MinHeight = Theme.Scaled(menu.IconGrid ? 88 : 78), // Bloco indisponível focado: o preenchimento cinza é discreto, então o contorno ciano mostra onde está o foco. BorderBrush = focused && !enabled ? Theme.Accent : null, BorderThickness = focused && !enabled ? Theme.FocusRing : default, @@ -773,41 +772,7 @@ private static Border MenuTile(AppController app, MenuModal menu, int index) KeepInView(tile); } AutomationProperties.SetName(tile, item.Label + (enabled ? string.Empty : ", indisponível") + (destructive ? ", ação perigosa" : string.Empty)); - tile.Tapped += (_, _) => app.PointerChooseModalOption(index); - Hover.Attach(tile, active: !focused && enabled, Theme.HoverInset); - return tile; - } - - /// - /// Bloco só de ícone (#293): nenhum texto fixo. O foco é o preenchimento cheio (ciano; vermelho se perigoso; cinza se - /// indisponível) mais o leve aumento, e o nome, o valor e a descrição aparecem na leitura fixa do painel. O Narrador ouve o - /// rótulo por extenso (nome e valor) e quem usa o mouse vê o mesmo numa dica. - /// - private static Border IconTile(AppController app, MenuModal menu, int index, UIElement content, Brush fill, bool focused, bool enabled, bool destructive, MenuGrid grid) - { - var item = menu.Items[index]; - var tile = new Border - { - Child = content, - Background = fill, - CornerRadius = Theme.RowRadius, - Padding = new Thickness(Theme.Space(4)), - MinHeight = Theme.Scaled(72), - BorderBrush = focused && !enabled ? Theme.Accent : null, - BorderThickness = focused && !enabled ? Theme.FocusRing : default, - }; - Grid.SetRow(tile, grid.Row(index)); - Grid.SetColumn(tile, grid.Column(index)); - if (focused) - { - tile.RenderTransformOrigin = new Point(0.5, 0.5); - tile.RenderTransform = new ScaleTransform { ScaleX = 1.05, ScaleY = 1.05 }; - AutomationProperties.SetAutomationId(tile, FocusedOptionId); - KeepInView(tile); - } - var name = item.Label + (enabled ? string.Empty : ", indisponível") + (destructive ? ", ação perigosa" : string.Empty); - AutomationProperties.SetName(tile, name); - ToolTipService.SetToolTip(tile, item.Label); + if (menu.IconGrid) ToolTipService.SetToolTip(tile, item.Label); // nome e valor também para o mouse tile.Tapped += (_, _) => app.PointerChooseModalOption(index); Hover.Attach(tile, active: !focused && enabled, Theme.HoverInset); return tile; diff --git a/src/ControlFS.Application/AppController.Browser.cs b/src/ControlFS.Application/AppController.Browser.cs index aba296c..e2cb719 100644 --- a/src/ControlFS.Application/AppController.Browser.cs +++ b/src/ControlFS.Application/AppController.Browser.cs @@ -611,7 +611,7 @@ .. UndoMenuItems(), /// (controle ativo, teste, joysticks, atualizações) fecham Configurações e abrem essa tela, como os outros submenus. /// internal void ShowSettings() => - PushModal(new MenuModal("Configurações", SettingsItems(), sectionGrids: true, iconOnly: true) { Icon = ActionIcon.Settings, Reload = SettingsItems }); + PushModal(new MenuModal("Configurações", SettingsItems(), sectionGrids: true, iconGrid: true) { Icon = ActionIcon.Settings, Reload = SettingsItems }); private List SettingsItems() { @@ -652,7 +652,7 @@ private List SettingsItems() Icon: ActionIcon.Hidden, Section: view, KeepOpen: true, Placement: tile, ShortLabel: "Ocultos", Value: hidden), new($"Tela cheia: {OnOff(Settings.FullScreen)}", ToggleFullScreen, Detail: "Ocupa a tela inteira, sem a barra do Windows; fica salva para a próxima vez. Também F11 ou o botão ao lado de minimizar.", - Icon: Settings.FullScreen ? ActionIcon.ExitFullScreen : ActionIcon.FullScreen, Section: view, KeepOpen: true), + Icon: Settings.FullScreen ? ActionIcon.ExitFullScreen : ActionIcon.FullScreen, Section: view, KeepOpen: true, ShortLabel: "Tela cheia"), new($"Busca em subpastas: {subfolders}", () => SearchIncludesSubfolders = !SearchIncludesSubfolders, Detail: "Vale para a próxima busca (Select/View).", Icon: ActionIcon.Subfolders, Section: privacy, KeepOpen: true, Placement: tile, ShortLabel: "Subpastas", Value: subfolders), @@ -666,7 +666,7 @@ private List SettingsItems() Detail: "Nomes digitados antes e desta pasta, só neste computador; nunca em senhas. Desligar apaga o histórico.", Icon: ActionIcon.Keyboard, Section: privacy, KeepOpen: true, Placement: tile, ShortLabel: "Sugestões", Value: OnOff(Settings.KeyboardSuggestions)), new("Apagar onde os vídeos pararam", ForgetAllPositions, PlaybackPositions is null ? "Posições não são lembradas nesta compilação." : null, - Detail: "O reprodutor lembra onde cada vídeo parou (só neste computador, sem guardar nomes) para oferecer \"Continuar\".", Icon: ActionIcon.Erase, Section: privacy, KeepOpen: true), + Detail: "O reprodutor lembra onde cada vídeo parou (só neste computador, sem guardar nomes) para oferecer \"Continuar\".", Icon: ActionIcon.Erase, Section: privacy, KeepOpen: true, ShortLabel: "Apagar posições"), ChoiceRow("Confirmar com", Settings.Convention, ConventionChoices, c => UpdateSettings(s => s with { Convention = c }), ActionIcon.Accept, section: controls, placement: tile, shortLabel: "Confirmar", context: settingsContext, detail: "Troca comportamento e legendas de confirmar/voltar."), ChoiceRow("Legendas", Settings.LabelStyle, LabelStyleChoices, style => UpdateSettings(s => s with { LabelStyle = style }), ActionIcon.Labels, section: controls, placement: tile, @@ -685,13 +685,13 @@ private List SettingsItems() Icon: ActionIcon.Settings, Section: controls, KeepOpen: true, Placement: tile, ShortLabel: "Segundo plano", Value: OnOff(Settings.LightInBackground)), new($"Mira por giroscópio no teclado: {OnOff(Settings.GyroKeyboard)} (experimental)", ToggleGyroKeyboard, Detail: "Controles com giroscópio (ex.: DualSense): no teclado virtual, gire ou incline o controle para apontar as teclas; R3 recentraliza. O direcional continua funcionando.", - Icon: ActionIcon.Keyboard, Section: controls, KeepOpen: true), + Icon: ActionIcon.Keyboard, Section: controls, KeepOpen: true, ShortLabel: "Giroscópio"), new(ActiveControllerMenuLabel, ShowActiveControllerMenu, _diagnostics is null ? "Controles indisponíveis nesta compilação." : null, - Detail: "Escolha qual controle comanda o ControlFS (ex.: Steam Input ou DS4Windows duplicando o controle).", Icon: ActionIcon.Controller, Section: controls), + Detail: "Escolha qual controle comanda o ControlFS (ex.: Steam Input ou DS4Windows duplicando o controle).", Icon: ActionIcon.Controller, Section: controls, ShortLabel: "Controle ativo"), new("Teste de controles…", ShowControllerTest, _diagnostics is null ? "Controles indisponíveis nesta compilação." : null, Detail: "Mostra cada botão e a ação que ele produz; copia um relatório para a issue #78.", Icon: ActionIcon.ControllerTest, Section: controls), - new("Controles sem perfil…", ShowControllersMenu, Detail: "Configurar joysticks que não são reconhecidos como gamepad.", Icon: ActionIcon.ControllerSetup, Section: controls), - new(UpdateMenuLabel, ShowUpdatesMenu, _updates is null ? "Atualizações indisponíveis nesta compilação." : null, Icon: ActionIcon.Update, Section: app), + new("Controles sem perfil…", ShowControllersMenu, Detail: "Configurar joysticks que não são reconhecidos como gamepad.", Icon: ActionIcon.ControllerSetup, Section: controls, ShortLabel: "Sem perfil"), + new(UpdateMenuLabel, ShowUpdatesMenu, _updates is null ? "Atualizações indisponíveis nesta compilação." : null, Icon: ActionIcon.Update, Section: app, ShortLabel: "Atualizações"), new("Rever boas-vindas", ShowOnboarding, Detail: "Controles, ajustes básicos, privacidade e o convite para o tutorial guiado.", Icon: ActionIcon.Help, Section: app), ]; } diff --git a/src/ControlFS.Application/State/Modals.cs b/src/ControlFS.Application/State/Modals.cs index 9cd9558..07b5f1b 100644 --- a/src/ControlFS.Application/State/Modals.cs +++ b/src/ControlFS.Application/State/Modals.cs @@ -118,17 +118,17 @@ public sealed class MenuModal : Modal private readonly bool _sectionGrids; - /// Blocos por linha numa grade só de ícones (Configurações, #293): largos o bastante para o ícone e o toque. + /// Blocos por linha numa grade de ícones com nome (Configurações, #293): largos o bastante para o ícone e o toque. public const int IconColumns = 6; /// Menos blocos que isto numa linha nunca fica mais largo que o bloco de quatro: grupos curtos não esticam. public const int IconMinColumns = 4; - public MenuModal(string title, IReadOnlyList items, bool sectionGrids = false, bool iconOnly = false) : base(title) + public MenuModal(string title, IReadOnlyList items, bool sectionGrids = false, bool iconGrid = false) : base(title) { Icon = ActionIcon.Menu; _sectionGrids = sectionGrids; - IconOnly = sectionGrids && iconOnly; + IconGrid = sectionGrids && iconGrid; Arrange(items); FocusIndex = items.Count == 0 ? 0 : InitialFocus(items[0]); } @@ -149,10 +149,10 @@ private int InitialFocus(MenuItem first) public int FocusIndex { get; internal set; } /// - /// Todos os itens são blocos só de ícone, numa coluna larga (Configurações, #293): sem texto fixo ao lado; o nome, o valor e + /// Todos os itens são blocos com o ícone e o nome curto logo abaixo, numa coluna larga (Configurações, #293); o valor atual, o nome por extenso e /// o que o ajuste faz aparecem na área de leitura do painel quando o bloco recebe o foco (e numa dica ao passar o mouse). /// - public bool IconOnly { get; } + public bool IconGrid { get; } /// /// Onde começa cada grupo de Configurações (índice do primeiro item), na ordem de : R1/L1 pulam de um @@ -198,9 +198,9 @@ public IReadOnlyList SectionStarts /// repetição do que o bloco já mostra (#227). Um ajuste já mostra nome curto e valor: a linha só explica. Uma ação com /// rótulo curto ganha o nome completo antes (ex.: "Colar 2 itens (mover)"). /// - public string TileCaption => IconOnly ? IconCaption(FocusIndex) : IsQuick(FocusIndex) ? Caption(FocusIndex) : string.Empty; + public string TileCaption => IconGrid ? IconCaption(FocusIndex) : IsQuick(FocusIndex) ? Caption(FocusIndex) : string.Empty; - /// Em grade só de ícones: "Nome: valor" numa linha e o que faz (ou por que não está disponível) na seguinte. + /// Na grade de ícones com nome: "Nome: valor" numa linha e o que faz (ou por que não está disponível) na seguinte. private string IconCaption(int index) { var item = Items[index]; @@ -223,7 +223,7 @@ private string Caption(int index) public string DescriptionOf(int index) { if (index < 0 || index >= Items.Count) return string.Empty; - if (IconOnly) return IconCaption(index); + if (IconGrid) return IconCaption(index); if (IsQuick(index)) return Caption(index); var item = Items[index]; return !item.IsEnabled ? "Indisponível: " + item.DisabledReason : item.Detail ?? string.Empty; @@ -239,7 +239,7 @@ public string DescriptionOf(int index) public IEnumerable AllDescriptions => Enumerable.Range(0, Items.Count).Select(DescriptionOf).Where(t => t.Length > 0).Distinct(); /// Menu com grade ou seletor de opções: ; só lista: . Nunca pelo item em foco (#227). - public override ModalSize Size => IconOnly ? ModalSize.Wide : Grids.Count > 0 || IsPicker ? ModalSize.Medium : ModalSize.Compact; + public override ModalSize Size => IconGrid ? ModalSize.Wide : Grids.Count > 0 || IsPicker ? ModalSize.Medium : ModalSize.Compact; /// /// Seletor de opções (#261): cada item é uma alternativa de um valor; é a atual (marcada com @@ -291,14 +291,14 @@ private void Arrange(IReadOnlyList items) var grids = new List(); for (var i = 0; i < items.Count;) { - if (!IconOnly && !items[i].IsQuick) + if (!IconGrid && !items[i].IsQuick) { i++; continue; } var start = i; - while (i < items.Count && (IconOnly || items[i].IsQuick) && items[i].Section == items[start].Section) i++; - grids.Add(IconOnly ? new MenuGrid(start, i - start, IconColumns, IconMinColumns) : new MenuGrid(start, i - start)); + while (i < items.Count && (IconGrid || items[i].IsQuick) && items[i].Section == items[start].Section) i++; + grids.Add(IconGrid ? new MenuGrid(start, i - start, IconColumns, IconMinColumns) : new MenuGrid(start, i - start)); } Grids = grids; return; diff --git a/tests/ControlFS.UnitTests/Application/ModalSystemJourneyTests.cs b/tests/ControlFS.UnitTests/Application/ModalSystemJourneyTests.cs index ed635f5..7bc2b42 100644 --- a/tests/ControlFS.UnitTests/Application/ModalSystemJourneyTests.cs +++ b/tests/ControlFS.UnitTests/Application/ModalSystemJourneyTests.cs @@ -268,7 +268,7 @@ public void Settings_live_in_Configuracoes_and_a_toggle_keeps_it_open() => UiCon }); [Fact] - public void Settings_is_one_column_of_icon_only_grids_per_section_navigated_in_two_dimensions() => UiContext.Run(async () => + public void Settings_is_one_column_of_labeled_icon_grids_per_section_navigated_in_two_dimensions() => UiContext.Run(async () => { // #293: todos os ajustes são blocos só de ícone na grade do seu grupo, numa coluna larga; o foco revela nome e valor. var d = Boot(); @@ -277,10 +277,16 @@ public void Settings_is_one_column_of_icon_only_grids_per_section_navigated_in_t await d.Idle(); app.ShowSettings(); var settings = await d.WaitMenu(); - Assert.True(settings.IconOnly); + Assert.True(settings.IconGrid); Assert.Equal(ModalSize.Wide, settings.Size); // uma coluna larga (sem colunas paralelas) Assert.Equal(4, settings.Grids.Count); Assert.Equal(settings.Items.Count, settings.Grids.Sum(g => g.Count)); // nenhum ajuste fora da grade (nada de linhas de texto) + // Cada bloco leva o nome curto sob o ícone, sem o valor (o valor fica na leitura do foco): nada de rótulo longo ou com ":". + Assert.All(settings.Items, i => + { + Assert.InRange(i.TileLabel.Length, 3, 24); + Assert.DoesNotContain(':', i.TileLabel); + }); var view = settings.Grids[0]; Assert.Equal(9, view.Count); Assert.Equal((5, 2), (view.Columns, view.Rows)); diff --git a/tools/make-sounds.py b/tools/make-sounds.py index b9c159a..449af86 100644 --- a/tools/make-sounds.py +++ b/tools/make-sounds.py @@ -1,46 +1,65 @@ #!/usr/bin/env python3 """ -Gera assets/sounds/*.wav (os sons do controle, #276/#287) a partir do pacote "Interface Sounds" de Kenney (CC0). +Gera assets/sounds/*.wav (os sons do controle, #276/#287) a partir de dois pacotes de Kenney em CC0: "Interface Sounds" e "UI Audio". Só é preciso rodar de novo para trocar um som; o app usa os WAV já prontos. Requer `pip install soundfile numpy`. -Uso: python tools/make-sounds.py assets/sounds +Uso: python tools/make-sounds.py