From aaec7346a7532b098c0888701d584cda0011a739 Mon Sep 17 00:00:00 2001 From: nextestudios <47460003+nextestudios@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:12:47 -0300 Subject: [PATCH] style: legibility and handheld density Context texts move from FontCaption to FontBody (badge line, dual-pane caption, inactive pane), FontCaption is 16 px on the compact tier and the folder picker gets a title row. Compact-tier comfortable rows are ~64 px, the first launch on a handheld starts in compact density (never over a saved choice), the footer drops non-essential prompts instead of wrapping and the path root chip stays visible. Details-panel paths are middle-ellipsized and 4K Home folder cards drop the path. The on-screen keyboard starts on the first letter, only the focused key is outlined and handhelds show the essential prompts. Light theme: dark-name logo variant without the plate and key fill tokens checked for contrast. Unmarked focused item shows an empty box. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.en-US.md | 2 + CHANGELOG.md | 2 + assets/controlfs-logo-text-light-900.png | Bin 0 -> 91961 bytes docs/GUIDE.md | 6 +-- docs/GUIDE.pt-BR.md | 6 +-- docs/TESTING.md | 10 ++++ docs/ui-redesign.md | 3 +- src/ControlFS.App/ControlFS.App.csproj | 2 + .../Controls/EntryRowTemplate.cs | 21 +++++--- src/ControlFS.App/Resources/Branding.cs | 30 ++++++----- src/ControlFS.App/Resources/Theme.cs | 13 +++-- src/ControlFS.App/Views/DetailsPanelView.cs | 18 ++++++- src/ControlFS.App/Views/HomeView.cs | 7 ++- src/ControlFS.App/Views/MainWindow.cs | 50 +++++++++++++++--- src/ControlFS.App/Views/ModalView.cs | 18 +++++-- src/ControlFS.App/Views/OnboardingView.cs | 12 ++--- src/ControlFS.App/Views/PaneView.cs | 4 +- src/ControlFS.App/Views/TopBarView.cs | 30 +++++++++-- .../AppController.Browser.cs | 18 +++++++ .../AppController.Details.cs | 5 +- src/ControlFS.Application/AppController.cs | 1 + src/ControlFS.Core/Appearance/ThemePalette.cs | 11 ++-- .../Contracts/ISettingsStore.cs | 3 +- src/ControlFS.Core/Text/PathEllipsis.cs | 46 ++++++++++++++++ src/ControlFS.Core/Text/VirtualKeyboard.cs | 12 +++++ .../Settings/JsonSettingsStore.cs | 5 +- .../Application/DensityJourneyTests.cs | 29 ++++++++++ .../Application/DetailsPanelJourneyTests.cs | 4 +- .../Core/PathEllipsisTests.cs | 17 ++++++ .../Core/ThemeContrastTests.cs | 9 ++++ .../Core/VirtualKeyboardTests.cs | 2 + 31 files changed, 328 insertions(+), 68 deletions(-) create mode 100644 assets/controlfs-logo-text-light-900.png create mode 100644 src/ControlFS.Core/Text/PathEllipsis.cs create mode 100644 tests/ControlFS.UnitTests/Core/PathEllipsisTests.cs diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index ea92a074..f8892dcc 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -4,6 +4,7 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor ## [Unreleased] ### Improvements +- **Legibility and handheld density:** context texts that were 14 px — the line above the list (archive, Git, search), "PAINEL ESQUERDO · ATIVO" (left pane · active) in two-pane mode and the inactive pane — are now body size, and on handhelds no informational text is below 16 px. In the folder picker that line becomes the screen title ("Copiar para… · escolha a pasta"). At 720p/800p comfortable rows are ~64 px (was 88: only ~5 fit) and the first launch on a handheld starts in the compact list, never changing a saved choice; the bottom bar no longer wraps (the less essential prompts are left out; Abrir, Voltar, Ações and Menu stay) and the path root ("Meu computador") no longer disappears with large text. Long paths in the details panel lose the middle, not the end ("C:\…\2026\Relatórios trimestrais"), and the main folder cards don't repeat the path on a 4K TV. The on-screen keyboard starts on the first letter, only the focused key has an outline (Done stands out by its text color) and handhelds show only the essential prompts. In the light theme the logo uses a version with a dark name (no dark plate behind it) and keys stand out from the panel (contrast checked by tests). - **Notices that don't push the list, no dialog after a clean operation, visible progress:** transient notices ("Exibição em grade.", "Lista compacta.", why an option is unavailable) show as a pop-up in the bottom-right corner at body text size and fade after about 3 seconds; the bottom bar no longer changes height (it used to grow up to 124 px and shrink the list). The notice never covers the focused item (it moves to the top corner when needed) and is read by Narrator. A copy, move or delete without any problem no longer opens a dialog you have to close: the notice says "3 itens copiados · Menu → Desfazer" (3 items copied · Menu → Undo); warnings, failures and cancels still open the result, which also stays in Menu → Operações. While anything runs, a thin bar under the title strip shows the overall progress and the strip shows the percentage; Menu → **Operações** splits **Em andamento** (in progress: state icon, percentage and items, updating while the menu is open) from **Histórico** (history). The search summary and the count of items without permission move to the line above the list. - **Search from home:** **Select/View** on the home screen searches the main folders (Downloads, Documents, Desktop, Pictures, Videos, Music) with the same engine as a folder search (never follows links; East/B cancels; Back returns home). In This PC, search looks in the focused drive. - **Empty lists with a next step:** "Lixeira vazia" (Recycle Bin empty), "Compactado vazio" (empty archive), "Pasta vazia — Y Ações: Colar / Nova pasta" (empty folder — paste / new folder; paste only when something is copied) and, for a search with no results, the filters and a new search, with the active controller's buttons. @@ -14,6 +15,7 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor - **Settings in grids** (#227): in each group of Configurações, short settings (view, density, details panel, theme, accent color, sort, hidden items, search in subfolders, recents, restore tabs, suggestions, confirm button, button labels, Fluidez) are now tiles with an icon, a name and the current value; settings with long descriptions or that open another screen stay in a list. The controller moves in 2D between the grids and the lists. ### Fixes +- Details panel: "Este item não está marcado" (this item isn't marked) showed the amber marked symbol; it now shows an empty box. - Welcome and tutorial texts name the buttons of the controller in use (LB/RB on Xbox, L1/R1 on PlayStation, L/R on Nintendo), like the glyphs. - Item menus open on the first available tile of the grid instead of a list row below it (archives still open on **Extrair para**). Actions about the open folder (favorites, Colar, Nova pasta aqui, Abrir terminal aqui) sit in a titled **Nesta pasta (name)** group so they don't read as actions on the file. - Dialogs no longer show the same text on two buttons ("A Cancelar · B Cancelar"): with the focus on the back option, the footer shows the next option. diff --git a/CHANGELOG.md b/CHANGELOG.md index a40186eb..8475c294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH ## [Unreleased] ### Melhorias +- **Legibilidade e densidade nos portáteis:** textos de contexto que estavam em 14 px — a linha acima da lista (compactado, Git, busca), "PAINEL ESQUERDO · ATIVO" com dois painéis e o painel inativo — passam ao tamanho do corpo, e nos portáteis nenhum texto informativo fica abaixo de 16 px. No seletor de pasta, a linha vira o título da tela ("Copiar para… · escolha a pasta"). Em 720p/800p as linhas confortáveis ficam com ~64 px (antes 88: só ~5 cabiam) e a primeira abertura num portátil já começa na lista compacta, sem nunca mudar uma escolha salva; o rodapé não quebra mais linha (as legendas menos essenciais saem; Abrir, Voltar, Ações e Menu ficam) e a raiz do caminho ("Meu computador") não some mais com texto grande. Caminhos longos no painel de detalhes perdem o meio, não o fim ("C:\…\2026\Relatórios trimestrais"), e os cartões das pastas principais não repetem o caminho numa TV 4K. O teclado virtual começa na primeira letra, só a tecla focada tem contorno (Concluir se distingue pela cor do texto) e, nos portáteis, mostra só as legendas essenciais. No tema claro, o logo usa uma versão com o nome escuro (sem a placa escura atrás) e as teclas se destacam do painel (contraste conferido nos testes). - **Avisos que não empurram a lista, fim de operação sem diálogo e andamento à vista:** os avisos do momento ("Exibição em grade.", "Lista compacta.", o motivo de uma opção indisponível) aparecem num aviso flutuante no canto inferior direito, em letra do tamanho do corpo, e somem sozinhos depois de uns 3 segundos; o rodapé não muda mais de altura (antes crescia até 124 px e encolhia a lista). O aviso nunca fica por cima do item em foco (sobe para o canto de cima quando precisa) e é lido pelo Narrador. Uma cópia, movimentação ou exclusão sem nenhum problema não abre mais um diálogo para fechar: o aviso diz "3 itens copiados · Menu → Desfazer"; avisos, falhas e cancelamentos continuam abrindo o resultado, que fica também em Menu → Operações. Enquanto algo roda, uma barra fina sob a faixa do título mostra o andamento geral e a faixa mostra o percentual; Menu → **Operações** separa **Em andamento** (ícone do estado, percentual e itens, atualizados com o menu aberto) de **Histórico**. O resumo da busca e os itens sem permissão passam para a linha acima da lista. - **Buscar no início:** **Select/View** na tela inicial busca nas pastas principais (Downloads, Documentos, Área de trabalho, Imagens, Vídeos, Músicas) com o mesmo motor da busca numa pasta (nunca segue links; Leste/B cancela; Voltar retorna ao início). Em Meu computador, a busca procura na unidade em foco. - **Listas vazias com o próximo passo:** "Lixeira vazia", "Compactado vazio", "Pasta vazia — Y Ações: Colar / Nova pasta" (Colar só com algo copiado) e, na busca sem resultados, os filtros e uma nova busca, com os botões do controle em uso. @@ -14,6 +15,7 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH - **Configurações em grades** (#227): em cada grupo, os ajustes curtos (exibição, densidade, painel de detalhes, tema, cor de destaque, ordenação, itens ocultos, busca em subpastas, recentes, restaurar abas, sugestões, confirmar com, legendas, Fluidez) viram blocos com ícone, nome e valor atual; os de descrição longa e os que abrem outra tela continuam em lista. O controle anda em 2D entre as grades e as listas. ### Correções +- Painel de detalhes: "Este item não está marcado" mostrava o símbolo âmbar de marcado; agora mostra uma caixa vazia. - Os textos das boas-vindas e do tutorial citam os botões do controle em uso (LB/RB no Xbox, L1/R1 no PlayStation, L/R no Nintendo), como os ícones. - Menus de item abrem no primeiro bloco disponível da grade, e não numa linha da lista abaixo dela (compactados continuam abrindo em **Extrair para**). As ações sobre a pasta aberta (favoritos, Colar, Nova pasta aqui, Abrir terminal aqui) ficam no grupo **Nesta pasta (nome)**, com título, para não parecerem ações do arquivo. - Diálogos não mostram mais o mesmo texto em dois botões ("A Cancelar · B Cancelar"): com o foco na opção de voltar, o rodapé mostra a próxima opção. diff --git a/assets/controlfs-logo-text-light-900.png b/assets/controlfs-logo-text-light-900.png new file mode 100644 index 0000000000000000000000000000000000000000..98c4902ef26066bc1eafc50c28a7646909db963b GIT binary patch literal 91961 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 literal 0 HcmV?d00001 diff --git a/docs/GUIDE.md b/docs/GUIDE.md index fc83cde3..0ec9e047 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -27,9 +27,9 @@ Buttons follow **physical position** (SDL3 convention), not printed letters. Only one controller drives the app at a time: the first one to press a button. Pressing a button on another controller (while the active one isn't holding anything) makes it the active one. With the window in the background, input is ignored. The menu has "Confirm with: bottom/right button" and the label style: **automatic** (default: follows the family of the controller in use: Xbox, PlayStation, Nintendo or generic) or fixed to generic, Xbox, PlayStation or Nintendo. -The bottom bar shows the buttons of the controller in use (for example `A Open` on Xbox, with the Xbox face colors: A green, B red, X blue, Y yellow; `✕ Open` on PlayStation) and switches as soon as you use another controller. When you use the keyboard, it shows keyboard keys instead (`Enter Open`, `Esc Back`) until you press a controller button again. +The bottom bar shows the buttons of the controller in use (for example `A Open` on Xbox, with the Xbox face colors: A green, B red, X blue, Y yellow; `✕ Open` on PlayStation) and switches as soon as you use another controller. When you use the keyboard, it shows keyboard keys instead (`Enter Open`, `Esc Back`) until you press a controller button again. The bar always stays one line: when the prompts don't fit (handhelds, large Windows text), the less essential ones are left out from the end — **Abrir**, **Voltar**, **Ações** and **Menu** always stay, and everything else still works and is in the menus. -The on-screen keyboard has the text field on top, four character rows, a function row (`⇧` Shift · `ABC` letters · `@#:` symbols · space · `⌫`) and a bottom row (cursor `◀ ▶` · `…` more · Cancel · **Done**). `…` opens accents, **Select all** (`Sel. tudo`), **Clear** and the PT-BR/EN switch. Selected text is highlighted and underlined: typing replaces it, `⌫` deletes it and `◀ ▶` just drop the selection (Ctrl+A on a physical keyboard selects everything). Rename opens with the name before the extension already selected, so typing `novo` on `example-file.zip` gives `novo.zip`. Shift: one press capitalizes the next letter, a second press locks caps (`⇪`), a third turns it off. Keys that a field doesn't accept (e.g. `\ / : * ? " < > |` in file names) are dimmed. Holding West, LB/RB, or South on `⌫ ◀ ▶` repeats (it speeds up the longer you hold); Done and the other keys never repeat. The cursor is the accent-colored bar in the text field; LT/RT (or Home/End) jump to the start or end, and moving it never changes the text, page or Shift. +The on-screen keyboard opens with the focus on the first letter (`q`), not on the number row. It has the text field on top, four character rows, a function row (`⇧` Shift · `ABC` letters · `@#:` symbols · space · `⌫`) and a bottom row (cursor `◀ ▶` · `…` more · Cancel · **Done**). `…` opens accents, **Select all** (`Sel. tudo`), **Clear** and the PT-BR/EN switch. Selected text is highlighted and underlined: typing replaces it, `⌫` deletes it and `◀ ▶` just drop the selection (Ctrl+A on a physical keyboard selects everything). Rename opens with the name before the extension already selected, so typing `novo` on `example-file.zip` gives `novo.zip`. Shift: one press capitalizes the next letter, a second press locks caps (`⇪`), a third turns it off. Keys that a field doesn't accept (e.g. `\ / : * ? " < > |` in file names) are dimmed. Only the focused key has an outline; **Done** is marked by its accent-colored text. On handhelds the prompts under the keyboard show only the essentials (Select, Delete, Shift, Symbols, Done, Cancel); LB/RB and LT/RT still move the cursor. Holding West, LB/RB, or South on `⌫ ◀ ▶` repeats (it speeds up the longer you hold); Done and the other keys never repeat. The cursor is the accent-colored bar in the text field; LT/RT (or Home/End) jump to the start or end, and moving it never changes the text, page or Shift. **Suggestions:** in name, search and path fields a strip above the keys suggests matching names (case and accents don't matter: `rel` finds `Relatório 2026`): names you typed before and names in the current folder; for paths, your favorites and recent folders. Press Up from the first row to reach the strip, Left/Right to pick, South to use it (it replaces the text), Down to go back to the keys; Up again continues to the last row. Everything stays on this PC; password fields never show or store suggestions. Menu → Configurações (settings) → **Sugestões do teclado** turns them off and erases the typed history. @@ -138,7 +138,7 @@ The list sits in a card with a column header: mark box, **Nome** (name), **Tipo* On the right of the list is the **details panel** for the focused item: a large icon (or the image thumbnail), name, type and real data — for a folder, the path, how many items and how much space are inside (summed in the background, only for the focused folder, with "Calculando…"); for a file, size and date; for an image, format and dimensions; for a ZIP or 7z archive, how many files it holds (without extracting); for a drive, file system, capacity, free space and the usage bar. With marked items, it shows how many and their total size. The grid has the same panel on its right (in folders, home, This PC, search, archives and the Recycle Bin): it follows the focused tile without taking focus, and the grid uses fewer columns to make room. With marked items the panel also says whether the focused item is one of them; when focus is on the top bar it only summarizes the marked items ("Nenhum item em foco"). On handhelds (1280×720/800) and narrow windows the panel steps aside by default; Actions → Properties shows the same data. Menu → Configurações → **Painel de detalhes: visível/oculto** (details panel: shown/hidden) toggles it; the list and the grid each remember their own choice (saved like the view and density), and on a handheld showing it gives the grid fewer columns instead of covering it. -Menu → Configurações → **List density** switches between **comfortable** (tall rows, for the TV) and **compact** (short rows, more items on screen), with the same columns. In a narrow window the type column goes first. The choice is saved. +Menu → Configurações → **List density** switches between **comfortable** (tall rows, for the TV) and **compact** (short rows, more items on screen), with the same columns. In a narrow window the type column goes first. The choice is saved. On a handheld (1280×720/800) comfortable rows are shorter (about 64 px), and the very first launch there starts in **compact**; once you pick a density, ControlFS never changes it. Long paths in the details panel lose their middle (`C:\…\2026\Relatórios trimestrais`), keeping the drive and the last folders. Menu → Configurações → **View** (or **R3**, pressing the right stick, or **Ctrl+G** on the keyboard) switches between the **list** and a **grid** of cards (large icon, name, type and size, states and, for folders, a chevron), in folders, search results, archives, the Recycle Bin and the home screen. Columns follow the width left for the grid: 3 at 1080p (2 with the details panel beside it), 2 on a handheld (1 if you show the panel there), 1 in a narrow window, 4 on a 4K TV (one more in compact density). In the grid the D-pad and stick move up, down, left and right between tiles: left/right continue onto the previous/next row at the ends, and down onto a shorter last row lands on its last item. The triggers page one screen of rows. Since left no longer goes to the parent folder in the grid, use Back or the path bar (LB). Density applies to the grid too (compact = smaller tiles), and switching views keeps the focused item. The R3 prompt names the action: **Ver em grade** (view as grid) in the list, **Ver em lista** (view as list) in the grid. diff --git a/docs/GUIDE.pt-BR.md b/docs/GUIDE.pt-BR.md index 5378dc3b..009d6817 100644 --- a/docs/GUIDE.pt-BR.md +++ b/docs/GUIDE.pt-BR.md @@ -27,9 +27,9 @@ Os botões seguem a **posição física** (convenção do SDL3), não as letras Só um controle comanda o app por vez: o primeiro a apertar um botão. Apertar um botão em outro controle (com o ativo solto) passa o comando para ele. Com a janela em segundo plano, a entrada é ignorada. O menu tem "Confirmar com: botão inferior/direito" e o estilo das legendas: **automáticas** (padrão: seguem a família do controle em uso: Xbox, PlayStation, Nintendo ou genérico) ou fixas em genérico, Xbox, PlayStation ou Nintendo. -O rodapé mostra os botões do controle em uso (por exemplo `A Abrir` no Xbox, com as cores das faces do Xbox: A verde, B vermelho, X azul, Y amarelo; `✕ Abrir` no PlayStation) e troca assim que você usa outro controle. Ao usar o teclado, mostra as teclas (`Enter Abrir`, `Esc Voltar`) até você apertar um botão do controle de novo. +O rodapé mostra os botões do controle em uso (por exemplo `A Abrir` no Xbox, com as cores das faces do Xbox: A verde, B vermelho, X azul, Y amarelo; `✕ Abrir` no PlayStation) e troca assim que você usa outro controle. Ao usar o teclado, mostra as teclas (`Enter Abrir`, `Esc Voltar`) até você apertar um botão do controle de novo. O rodapé fica sempre numa linha: quando as legendas não cabem (portáteis, texto grande do Windows), as menos essenciais saem do fim para o começo — **Abrir**, **Voltar**, **Ações** e **Menu** ficam sempre, e o resto continua funcionando e está nos menus. -O teclado virtual tem o campo de texto em cima, quatro linhas de caracteres, uma linha de funções (`⇧` Maiúsculas · `ABC` letras · `@#:` símbolos · espaço · `⌫`) e uma linha inferior (cursor `◀ ▶` · `…` mais · Cancelar · **Concluir**). `…` abre os acentos, **Selecionar tudo** (`Sel. tudo`), **Limpar** e a troca PT-BR/EN. O texto selecionado fica destacado e sublinhado: digitar o substitui, `⌫` o apaga e `◀ ▶` só desfazem a seleção (Ctrl+A no teclado físico seleciona tudo). Renomear já abre com o nome antes da extensão selecionado: digitar `novo` em `example-file.zip` resulta em `novo.zip`. Maiúsculas: um toque deixa a próxima letra maiúscula, o segundo trava (`⇪`), o terceiro desliga. Teclas que o campo não aceita (ex.: `\ / : * ? " < > |` em nomes de arquivo) ficam apagadas. Segurar Oeste, LB/RB ou Sul sobre `⌫ ◀ ▶` repete (e acelera quanto mais tempo segurar); Concluir e as demais teclas nunca repetem. O cursor é a barra na cor de destaque no campo de texto; LT/RT (ou Home/End) levam ao início ou ao fim, e movê-lo nunca muda o texto, a página nem as maiúsculas. +O teclado virtual abre com o foco na primeira letra (`q`), não na linha dos números. Ele tem o campo de texto em cima, quatro linhas de caracteres, uma linha de funções (`⇧` Maiúsculas · `ABC` letras · `@#:` símbolos · espaço · `⌫`) e uma linha inferior (cursor `◀ ▶` · `…` mais · Cancelar · **Concluir**). `…` abre os acentos, **Selecionar tudo** (`Sel. tudo`), **Limpar** e a troca PT-BR/EN. O texto selecionado fica destacado e sublinhado: digitar o substitui, `⌫` o apaga e `◀ ▶` só desfazem a seleção (Ctrl+A no teclado físico seleciona tudo). Renomear já abre com o nome antes da extensão selecionado: digitar `novo` em `example-file.zip` resulta em `novo.zip`. Maiúsculas: um toque deixa a próxima letra maiúscula, o segundo trava (`⇪`), o terceiro desliga. Teclas que o campo não aceita (ex.: `\ / : * ? " < > |` em nomes de arquivo) ficam apagadas. Só a tecla focada tem contorno; **Concluir** se destaca pelo texto na cor de destaque. Nos portáteis, as legendas sob o teclado mostram só as essenciais (Selecionar, Apagar, Maiúsculas, Símbolos, Concluir, Cancelar); LB/RB e LT/RT continuam movendo o cursor. Segurar Oeste, LB/RB ou Sul sobre `⌫ ◀ ▶` repete (e acelera quanto mais tempo segurar); Concluir e as demais teclas nunca repetem. O cursor é a barra na cor de destaque no campo de texto; LT/RT (ou Home/End) levam ao início ou ao fim, e movê-lo nunca muda o texto, a página nem as maiúsculas. **Sugestões:** em campos de nome, busca e caminho, uma faixa acima das teclas sugere nomes que combinam (maiúsculas e acentos não importam: `rel` acha `Relatório 2026`): nomes que você já digitou e nomes da pasta atual; em caminhos, seus favoritos e pastas recentes. Aperte cima na primeira linha para chegar à faixa, esquerda/direita para escolher, Sul para usar (substitui o texto) e baixo para voltar às teclas; cima de novo segue até a última linha. Tudo fica neste PC; campos de senha nunca mostram nem guardam sugestões. Menu → Configurações → **Sugestões do teclado** desliga e apaga o histórico digitado. @@ -138,7 +138,7 @@ O item focado tem um anel de destaque (fundo azul, borda ciano, seta em destaque Norte → **Marcar todos (N)** marca todos os itens da pasta ou do compactado (nunca unidades, pastas especiais ou entradas bloqueadas); **Limpar marcação (N)** desmarca, assim como o Leste. -Menu → Configurações → **Densidade da lista** alterna entre **confortável** (linhas altas, para a TV) e **compacta** (linhas baixas, mais itens na tela), com as mesmas colunas. Numa janela estreita a coluna de tipo sai primeiro. A escolha fica salva. +Menu → Configurações → **Densidade da lista** alterna entre **confortável** (linhas altas, para a TV) e **compacta** (linhas baixas, mais itens na tela), com as mesmas colunas. Numa janela estreita a coluna de tipo sai primeiro. A escolha fica salva. Num portátil (1280×720/800) as linhas confortáveis são mais baixas (uns 64 px), e a primeiríssima abertura nele já começa na **compacta**; depois que você escolhe uma densidade, o ControlFS nunca a muda. Caminhos longos no painel de detalhes perdem o meio (`C:\…\2026\Relatórios trimestrais`), mantendo a unidade e as últimas pastas. Menu → Configurações → **Exibição** (ou **R3**, apertando o analógico direito, ou **Ctrl+G** no teclado) alterna entre **lista** e **grade** de cartões (ícone grande, nome, tipo e tamanho, estados e, nas pastas, a seta), nas pastas, na busca, nos compactados, na Lixeira e na tela inicial. As colunas acompanham a largura que sobra para a grade: 3 em 1080p (2 com o painel de detalhes ao lado), 2 num portátil (1 se o painel for mostrado lá), 1 numa janela estreita, 4 numa TV 4K (uma a mais na densidade compacta). Na grade, o direcional e o analógico andam para cima, baixo, esquerda e direita entre os blocos: esquerda/direita continuam na linha anterior/seguinte nas pontas, e descer para uma última linha mais curta vai ao último item. Os gatilhos paginam uma tela de linhas. Como a esquerda não sobe de pasta na grade, use Voltar ou a barra de caminho (LB). A densidade vale também para a grade (compacta = blocos menores), e trocar de exibição mantém o item focado. A legenda do R3 diz a ação: **Ver em grade** na lista e **Ver em lista** na grade. diff --git a/docs/TESTING.md b/docs/TESTING.md index 1002e3e6..789d84f8 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -602,6 +602,16 @@ As capturas (`0-title-bar-dark`, `0b-title-bar-light`) mostram só o XAML, com b - [ ] Cópia grande: a barra fina sob a faixa do título avança; a faixa mostra o percentual; Menu → Operações mostra **Em andamento** com ícone e percentual que se atualizam com o menu aberto, e a operação passa para **Histórico** ao terminar. - [ ] Tema claro: o aviso e a barra têm contraste suficiente. +## Legibilidade e densidade nos portáteis (auditoria de UX, P1-7/P2-6/P2-7/P2-11/P2-12) — não validado em hardware + +- [ ] Steam Deck / ROG Ally (1280×800) na primeira abertura (sem `settings.json`): a lista começa compacta; trocar para confortável e reabrir mantém a confortável. Num PC 1080p a primeira abertura continua confortável. +- [ ] 1280×720 confortável: linhas de ~64 px (cerca de 7–8 linhas visíveis), nada cortado no nome nem na segunda linha. +- [ ] 1280×800 com texto do Windows em 150%: o rodapé fica numa linha (Abrir, Voltar, Ações e Menu sempre) e o chip "Meu computador" continua visível numa pasta funda. +- [ ] De longe (sofá): linha acima da lista, "PAINEL ESQUERDO · ATIVO" e o título do seletor de pasta legíveis. +- [ ] Painel de detalhes com um caminho longo: aparece `C:\…\pasta\final`, nunca cortado no fim. +- [ ] Teclado virtual: abre no "q"; só a tecla focada tem contorno; Concluir não parece focado; em 720p as legendas cabem numa linha. +- [ ] Tema claro: o logo com o nome escuro fica nítido no cabeçalho (sem bloco escuro) e as teclas se distinguem do painel num monitor comum e numa TV. + ## Vídeo (#61, #170) — não validado em hardware - [ ] Sul num MP4 (H.264/AAC) pela lista e pela grade: a janela entra em tela cheia e o vídeo toca; a sobreposição some em diff --git a/docs/ui-redesign.md b/docs/ui-redesign.md index 34fa7f6f..08d5d884 100644 --- a/docs/ui-redesign.md +++ b/docs/ui-redesign.md @@ -45,7 +45,7 @@ hardware, visual or timing checks that CI can't prove. size and presenter changes. The header reserves the caption inset (`RightInset`) plus the full screen button on the right. Windows without title bar customization keep the system bar. Captures (`--render-screens`) can't show the real caption buttons: they reserve 3 × 46 px and draw look-alike glyphs. - No separate logo row: logo, tabs, controller/device and operation status all live in this strip (status at `FontBody`, + No separate logo row (and no plate behind the logo: the light theme uses the dark-name variant): logo, tabs, controller/device and operation status all live in this strip (status at `FontBody`, one line each, so the strip height stays the logo's). The full screen button shows "exit full screen" while active. - **Header**: official logo with text (`assets/controlfs-logo-text-900.png`, copied as `controlfs-logo.png`) on every screen; the icon-only logo stays for the exe, taskbar and installer. Right side: active controller and running @@ -318,6 +318,7 @@ Settings_live_in_Configuracoes_…` checks every moved entry is there; `Driver.C | Text preview (encodings, limits, binary refused) | modal | `TextPreviewJourneyTests`, `TextPreviewTests` (2); Manual "(#58)" | | PDF preview (#59): South on .pdf / North → Visualizar PDF; LB/RB and Left/Right pages, RT/LT zoom, pan while zoomed, South fits, East closes; password on the masked keyboard with retry; content check, 200 MB / 5,000 pages / 3072 px / 20 s limits | modal over the shell | `PdfPreviewJourneyTests` (3), `PdfRendererIntegrationTests` (Windows.Data.Pdf, page 1 of a generated PDF); Screen `mp-pdf-preview`; Manual "Visualização de PDF (#59)" | | On-screen keyboard: PT-BR/EN, shift/caps, numbers, symbols, accents, space, backspace (repeat), clear, caret, selection, OK/cancel, name/path/password fields, masking/reveal, controller navigation | modal; footer shows Selecionar/Apagar/…/Concluir/Cancelar | `VirtualKeyboardTests` (13), `HintJourneyTests::On_screen_keyboard_…`, UIA | +| Legibility and handheld density (UX audit P1-7/P2-6/P2-7/P2-11/P2-12/P3): badge line, "PAINEL … · ATIVO" and the inactive pane title/path at `FontBody`; `FontCaption` = 16 px on the Compact tier; folder picker badge is a title row (`FontTitle`, "Copiar para… · escolha a pasta"); Compact-tier comfortable rows ~64 px (icon 40, title 20, state 16); first launch on the Compact tier starts in compact density (`SettingsLoadResult.FirstRun` + `AppController.ApplyFirstRunDensity`, never over a saved/session choice); footer never wraps (`MainWindow.FitFooter` drops non-essential prompts from the end, keeps Confirm/Back/Actions/Menu); path root chip always visible (current crumb label shrinks instead of scrolling); details-panel paths middle-ellipsized (`PathEllipsis.Middle`); Home known-folder cards drop the path on the Large tier (middle-ellipsized elsewhere); keyboard starts on the first letter row, only the focused key has an outline (Done = accent text), Compact tier shows 6–7 keyboard prompts (no cursor/start/end prompts); light theme: dark-name logo variant (`assets/controlfs-logo-text-light-900.png`, no plate), `KeyFill`/`KeyFunctionFill` tokens; unmarked focused item = empty box (`DetailsIcon.Unmarked`) | shell, details panel, Home cards, on-screen keyboard | `DensityJourneyTests::First_run_on_a_handheld_…`, `PathEllipsisTests`, `VirtualKeyboardTests::Vertical_navigation_…` (initial focus), `ThemeContrastTests` (key vs panel, accent on function keys), `DetailsPanelJourneyTests` (Unmarked); Screens `1c`, `2-folder`, `2b`, `2d`, `2h`, `3-folder-compact`, `5-keyboard`, `7-light-folder`, `7c`, `m8`; Manual "Legibilidade e densidade nos portáteis" | | Dialogs name the focused choice; destructive dialogs start on the safe option; with the focus on the Back option itself (Cancelar/Fechar), the footer shows the next option on Right instead of repeating it on Confirm | modal | `HintJourneyTests::Dialogs_and_menus_…`, UIA | | Operation result dialogs replace the "…iniciada" status line (the footer never contradicts "Extração concluída"); a recycled delete's status names Menu → Desfazer | result dialog; status line | `UndoJourneyTests::Undo_of_a_recycle_…` | | Modal system (#172): every menu option and dialog button has an icon; destructive ones flagged, red + warning symbol, never the initial focus; input never reaches the screen under a modal (buttons, list clicks, tabs); nested modals close one at a time and focus returns; prompts and status inside the panel; solid panel with transparency off/high contrast | every modal | `ModalSystemJourneyTests` (4), UIA; Screens `m1`–`m9`, `icons/action-icons`; Manual "Modais (#172)" | diff --git a/src/ControlFS.App/ControlFS.App.csproj b/src/ControlFS.App/ControlFS.App.csproj index 5963973e..2ecefbe1 100644 --- a/src/ControlFS.App/ControlFS.App.csproj +++ b/src/ControlFS.App/ControlFS.App.csproj @@ -54,6 +54,8 @@ + + diff --git a/src/ControlFS.App/Controls/EntryRowTemplate.cs b/src/ControlFS.App/Controls/EntryRowTemplate.cs index 62056930..5fcf4d21 100644 --- a/src/ControlFS.App/Controls/EntryRowTemplate.cs +++ b/src/ControlFS.App/Controls/EntryRowTemplate.cs @@ -71,6 +71,9 @@ public sealed record ListColumns(bool Compact, bool Mark, bool Type, double Mark public string Key => $"{Compact}|{Mark}|{Type}|{Theme.Layout}|{Theme.SimulatedTextScale}"; } + /// Faixa dos portáteis (720p/800p, janelas pequenas): linhas mais baixas na densidade confortável. + private static bool Handheld => Theme.Layout.Tier == Core.Layout.LayoutTier.Compact; + /// Medidas das colunas para a largura da lista. public static ListColumns Columns(ListDensity density, double listWidth, bool mark) { @@ -78,8 +81,8 @@ public static ListColumns Columns(ListDensity density, double listWidth, bool ma var columns = compact ? new ListColumns(true, mark, true, Theme.Scaled(32), Theme.Scaled(28), Theme.Scaled(170), Theme.Scaled(110), Theme.Scaled(180), Theme.Scaled(22), Theme.Space(12), new Thickness(Theme.Space(14), Theme.Space(2), Theme.Space(14), Theme.Space(2)), Theme.Scaled(44)) - : new ListColumns(false, mark, true, Theme.Scaled(44), Theme.Scaled(60), Theme.Scaled(200), Theme.Scaled(140), Theme.Scaled(220), Theme.Scaled(28), - Theme.Space(18), new Thickness(Theme.Space(18), Theme.Space(6), Theme.Space(22), Theme.Space(6)), Theme.Scaled(88)); + : new ListColumns(false, mark, true, Theme.Scaled(44), Theme.Scaled(Handheld ? 48 : 60), Theme.Scaled(200), Theme.Scaled(140), Theme.Scaled(220), Theme.Scaled(28), + Theme.Space(18), new Thickness(Theme.Space(18), Theme.Space(Handheld ? 2 : 6), Theme.Space(22), Theme.Space(Handheld ? 2 : 6)), Theme.Scaled(Handheld ? 64 : 88)); // O nome precisa de espaço para ser lido: sem ele, o tipo sai da linha. return listWidth > 0 && listWidth - columns.FixedWidth < Theme.Scaled(compact ? 260 : 320) ? columns with { Type = false } : columns; } @@ -111,15 +114,19 @@ int Add(double? width) private static string ListXaml(ListColumns c) { var (mark, icon, name, type, size, date, chevron, definitions) = ColumnLayout(c); - var titleSize = c.Compact ? 17 : 22; - var columnSize = c.Compact ? 15 : 19; - var iconSize = c.Compact ? 24 : 52; + // Portáteis (auditoria de UX, P2-7): a confortável fica com ~64 px por linha (antes 88: só ~5 linhas em 720p) e + // nenhum texto informativo abaixo de 16 px. + var handheld = Handheld; + var titleSize = c.Compact ? 17 : handheld ? 20 : 22; + var columnSize = c.Compact ? (handheld ? 16 : 15) : 19; + var stateSize = handheld ? 16 : c.Compact ? 13 : 15; + var iconSize = c.Compact ? 24 : handheld ? 40 : 52; string Cell(int column, string xname, string extra = "") => $""; var states = $"" + - $"" + - $"" + + $"" + + $"" + ""; var title = $""; var nameCell = c.Compact diff --git a/src/ControlFS.App/Resources/Branding.cs b/src/ControlFS.App/Resources/Branding.cs index 108c1a4e..d7596e68 100644 --- a/src/ControlFS.App/Resources/Branding.cs +++ b/src/ControlFS.App/Resources/Branding.cs @@ -2,23 +2,29 @@ namespace ControlFS.App.Resources; -/// Logo oficial (assets/controlfs-logo-text-900.png, copiada como controlfs-logo.png) carregada por stream. +/// +/// Logo oficial (assets/controlfs-logo-text-900.png, copiada como controlfs-logo.png) carregada por stream. No tema claro, +/// a variante com o nome escuro (assets/controlfs-logo-text-light-900.png, as mesmas formas com as letras prateadas em +/// azul-marinho): sem a placa escura atrás do logo, que pesava no cabeçalho claro (auditoria de UX, P2-12). +/// public static class Branding { private static BitmapImage? _logo; + private static BitmapImage? _logoLight; - public static BitmapImage? Logo + public static BitmapImage? Logo => _logo ??= Load("controlfs-logo.png"); + + /// Logo do tema: a variante escura no claro (se faltar, a de sempre). + public static BitmapImage? LogoFor(bool dark) => dark ? Logo : (_logoLight ??= Load("controlfs-logo-light.png")) ?? Logo; + + private static BitmapImage? Load(string file) { - get - { - if (_logo is not null) return _logo; - var path = Path.Join(AppContext.BaseDirectory, "controlfs-logo.png"); - if (!File.Exists(path)) return null; - var image = new BitmapImage(); - var stream = File.OpenRead(path); - _ = LoadAsync(image, stream); - return _logo = image; - } + var path = Path.Join(AppContext.BaseDirectory, file); + if (!File.Exists(path)) return null; + var image = new BitmapImage(); + var stream = File.OpenRead(path); + _ = LoadAsync(image, stream); + return image; } private static async Task LoadAsync(BitmapImage image, Stream stream) diff --git a/src/ControlFS.App/Resources/Theme.cs b/src/ControlFS.App/Resources/Theme.cs index d802a9ed..8d1379de 100644 --- a/src/ControlFS.App/Resources/Theme.cs +++ b/src/ControlFS.App/Resources/Theme.cs @@ -55,9 +55,6 @@ public static class Theme public static readonly SolidColorBrush Scrim = new(ToColor(Palette.Scrim)); public static readonly SolidColorBrush Transparent = new(Colors.Transparent); - /// Placa atrás do logo (o nome no logo é claro): transparente no escuro, azul-marinho no claro. - public static readonly SolidColorBrush LogoPlate = new(ToColor(Palette.LogoPlate)); - /// Brilho no alto do painel de detalhes (degradê). public static Color DetailsGlowColor => ToColor(Palette.DetailsGlow); @@ -92,11 +89,12 @@ public static bool Apply(ThemePalette palette) Set(ModalEdge, palette.ModalEdge); Set(ModalDivider, palette.ModalDivider); Set(ModalInset, palette.ModalInset); + Set(KeyFill, palette.KeyFill); + Set(KeyFunctionFill, palette.KeyFunctionFill); Set(FocusText, palette.FocusText); Set(DangerFill, palette.DangerFill); Set(DisabledFill, palette.DisabledFill); Set(Success, palette.Success); - Set(LogoPlate, palette.LogoPlate); return true; } @@ -146,7 +144,8 @@ public static bool SetLayout(LayoutProfile profile, Size viewport, double simula public static double SpaceL => Space(24); public static double SpaceXl => Space(40); - public static double FontCaption => Font(14); + /// Legendas e textos de apoio: 14 px, e nunca menos de 16 px nos portáteis (tela pequena, lida de longe). + public static double FontCaption => Font(Layout.Tier == Core.Layout.LayoutTier.Compact ? 16 : 14); public static double FontBody => Font(18); public static double FontItem => Font(20); public static double FontTitle => Font(26); @@ -256,6 +255,10 @@ public static Brush ModalPanel() => SolidSurfaces public static readonly SolidColorBrush ModalDivider = new(ToColor(Palette.ModalDivider)); public static readonly SolidColorBrush ModalInset = new(ToColor(Palette.ModalInset)); + /// Teclas do teclado virtual: de caractere (mais marcadas) e de função, sempre distintas do painel. + public static readonly SolidColorBrush KeyFill = new(ToColor(Palette.KeyFill)); + public static readonly SolidColorBrush KeyFunctionFill = new(ToColor(Palette.KeyFunctionFill)); + /// /// Opção focada: preenchida com o destaque, negrito e um pouco maior; o texto por cima é escuro no tema escuro e /// branco no claro (contraste ≥ 4,5:1 em todas as cores de destaque). Perigosa focada: vermelho. Indisponível focada: diff --git a/src/ControlFS.App/Views/DetailsPanelView.cs b/src/ControlFS.App/Views/DetailsPanelView.cs index 618c3961..ba9dd3c8 100644 --- a/src/ControlFS.App/Views/DetailsPanelView.cs +++ b/src/ControlFS.App/Views/DetailsPanelView.cs @@ -5,6 +5,7 @@ using ControlFS.Application; using ControlFS.Core.Contracts; using ControlFS.Core.Models; +using ControlFS.Core.Text; using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Automation; @@ -70,7 +71,7 @@ public void ApplyLayout() public void Render(ItemDetails? details, IReadOnlySet specialFolders) { - var key = Key(details); + var key = Key(details) + "\u001F" + _card.Width.ToString(System.Globalization.CultureInfo.InvariantCulture); if (key == _shownKey) return; _shownKey = key; if (_iconImage is not null) _icons.Cancel(_iconImage); @@ -99,7 +100,8 @@ public void Render(ItemDetails? details, IReadOnlySet specialFolders) _stack.Children.Add(new TextBlock { Text = details.Subtitle, FontSize = Theme.Font(20), Foreground = Theme.TextMuted, TextWrapping = TextWrapping.Wrap }); var lines = new StackPanel { Spacing = Theme.Space(18), Margin = new Thickness(0, Theme.Space(24), 0, 0) }; - foreach (var line in details.Lines) lines.Children.Add(Line(line)); + var pathChars = PathBudget(); + foreach (var line in details.Lines) lines.Children.Add(Line(line.Icon == DetailsIcon.Location ? line with { Value = PathEllipsis.Middle(line.Value, pathChars) } : line)); if (details.Volume is { TotalBytes: > 0 } volume) lines.Children.Insert(Math.Min(1, lines.Children.Count), UsageBar(volume.UsedFraction)); _stack.Children.Add(lines); if (details.Note is { Length: > 0 } note) @@ -108,6 +110,17 @@ public void Render(ItemDetails? details, IReadOnlySet specialFolders) .Concat(details.Lines.Select(l => l.Label is null ? l.Value : $"{l.Label} {l.Value}")))); } + /// + /// Quantos caracteres do caminho cabem em duas linhas do valor (estimativa pela largura do painel e pelo tamanho da + /// letra): o caminho perde o meio, nunca a unidade nem a pasta final. + /// + private int PathBudget() + { + var width = double.IsFinite(_card.Width) ? _card.Width : Theme.Scaled(400); + var text = width - _stack.Padding.Left - _stack.Padding.Right - Theme.Scaled(30) - Theme.Space(18); + return Math.Max(24, (int)(2 * text / (Theme.Font(19) * 0.56))); + } + /// Muda quando qualquer texto, a miniatura ou o uso mudam (nada é refeito à toa a cada Render). private static string Key(ItemDetails? details) => details is null ? "-" : string.Join("\u001F", new[] { @@ -216,6 +229,7 @@ private static Grid Line(DetailsLine line) DetailsIcon.Compression => "", DetailsIcon.Lock => "", DetailsIcon.Marked => "", + DetailsIcon.Unmarked => "", // caixa vazia DetailsIcon.Warning => "", _ => "", }; diff --git a/src/ControlFS.App/Views/HomeView.cs b/src/ControlFS.App/Views/HomeView.cs index 425a10e1..42ac1e05 100644 --- a/src/ControlFS.App/Views/HomeView.cs +++ b/src/ControlFS.App/Views/HomeView.cs @@ -4,6 +4,7 @@ using ControlFS.Application.State; using ControlFS.Core.Contracts; using ControlFS.Core.Models; +using ControlFS.Core.Text; using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Automation; @@ -235,7 +236,11 @@ private Card BuildCard(FileEntry place, int index, HomeSectionKind kind, bool fi text.Children.Add(Line(place.Name, Theme.Font(24), place.IsBlocked ? Theme.Danger : Theme.Text)); TextBlock? stats = null; if (drive && place.Volume is { TotalBytes: > 0 } volume) text.Children.Add(UsageBar(volume.UsedFraction)); - text.Children.Add(Line(primary, Theme.Font(19), place.IsBlocked ? Theme.Danger : Theme.TextMuted)); + // Pastas principais: o caminho completo é ruído (todo mundo sabe onde fica "Documentos"); na TV grande ele sai e o + // cartão fica com nome e conteúdo; nos demais, perde o meio e mantém a unidade e a pasta (auditoria de UX, P2-6). + var knownPath = place.Kind == EntryKind.KnownFolder && place.FullPath is { } full && primary == full; + if (!(knownPath && secondary is not null && Theme.Layout.Tier == Core.Layout.LayoutTier.Large)) + text.Children.Add(Line(knownPath ? PathEllipsis.Middle(primary, 36) : primary, Theme.Font(19), place.IsBlocked ? Theme.Danger : Theme.TextMuted)); if (secondary is not null) { var line = Line(secondary, Theme.Font(19), Theme.TextMuted); diff --git a/src/ControlFS.App/Views/MainWindow.cs b/src/ControlFS.App/Views/MainWindow.cs index 4d8f758d..ca2b20be 100644 --- a/src/ControlFS.App/Views/MainWindow.cs +++ b/src/ControlFS.App/Views/MainWindow.cs @@ -42,6 +42,9 @@ public sealed class MainWindow : Window private readonly Windows.UI.ViewManagement.UISettings _uiSettings = new(); private readonly Windows.UI.ViewManagement.AccessibilitySettings _accessibility = new(); private bool _layoutPinned; + + /// Modo de capturas (--render-screens): nenhum padrão depende do tamanho da tela do runner. + private readonly bool _captures; private readonly ShellIconProvider _iconProvider = new(); private readonly DriveWatcher _drives = new(); private readonly IconLoader _icons; @@ -54,8 +57,8 @@ public sealed class MainWindow : Window private readonly StackPanel _tabStrip = new() { Orientation = Orientation.Horizontal, Spacing = Theme.SpaceXs, VerticalAlignment = VerticalAlignment.Center }; private readonly StackPanel _tabs = new() { Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Center }; private readonly TextBlock _badge = new() { FontSize = Theme.FontCaption, Foreground = Theme.Accent, TextTrimming = TextTrimming.CharacterEllipsis }; - /// Placa atrás do logo: transparente no tema escuro; escura no claro (o nome no logo é claro, #37). - private readonly Border _logoPlate = new() { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, Background = Theme.LogoPlate }; + /// Área do logo (sem placa: no tema claro vale a variante com o nome escuro, P2-12 da auditoria de UX). + private readonly Border _logoPlate = new() { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center }; private readonly Image _logo = new() { Height = 44, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, Stretch = Stretch.Uniform }; private readonly IconLoader _navIcons; private readonly IconLoader _cardIcons; @@ -119,6 +122,7 @@ internal MainWindow(string? dataDirectory) Title = "ControlFS"; var icon = Path.Join(AppContext.BaseDirectory, "controlfs.ico"); if (File.Exists(icon)) AppWindow.SetIcon(icon); + _captures = dataDirectory is not null; var data = dataDirectory ?? AppPaths.DataDirectory; var settingsStore = new JsonSettingsStore(data); _updates = dataDirectory is null ? GitHubReleaseUpdateService.CreateDefault(AppPaths.IsInstalled, Path.Join(data, "updates")) : null; @@ -509,7 +513,7 @@ private void Render() var pane = _app.ActivePane; var badge = _app.Screen switch { - Screen.FolderPicker => "ESCOLHER PASTA · " + _app.PickerTitle, + Screen.FolderPicker => _app.PickerTitle + " · escolha a pasta", Screen.Browser when pane.Location is ArchiveLocation => "COMPACTADO · SOMENTE LEITURA" + (_app.ArchiveSummary is { } summary ? " · " + summary : string.Empty), Screen.Browser when _app.GitSummary is { } git => git, _ => string.Empty, @@ -520,6 +524,11 @@ private void Render() : pane.InaccessibleCount > 0 ? $"{Plural.Of(pane.InaccessibleCount, "item", "itens")} sem permissão de leitura {Plural.Word(pane.InaccessibleCount, "foi omitido", "foram omitidos")}." : null; if (!string.IsNullOrEmpty(context)) badge = badge.Length > 0 ? badge + " · " + context : context; + // Seletor de pasta: a linha vira o título da tela ("Copiar para…"), grande como um título, não uma legenda. + var picker = _app.Screen == Screen.FolderPicker; + _badge.FontSize = picker ? Theme.FontTitle : Theme.FontBody; + _badge.FontWeight = picker ? FontWeights.SemiBold : FontWeights.Normal; + _badge.Foreground = picker ? Theme.Text : Theme.Accent; if (badge != _badge.Text) { _badge.Text = badge; @@ -779,8 +788,11 @@ private void RenderFooter() foreach (var prompt in _app.Prompts) { if (regionsShownAbove && prompt.Action is InputAction.PreviousRegion or InputAction.NextRegion) continue; - _hints.Children.Add(ModalView.PromptChip(prompt, glyphHeight, FooterLabelSize)); + var chip = ModalView.PromptChip(prompt, glyphHeight, FooterLabelSize); + chip.Tag = prompt.Action; + _hints.Children.Add(chip); } + FitFooter(); // Com um modal aberto, as legendas ficam no próprio painel: as do rodapé somem sem mudar a altura dele. _hints.Opacity = _app.TopModal is null ? 1 : 0; @@ -801,6 +813,28 @@ private void RenderFooter() Announce(); } + /// + /// O rodapé nunca quebra linha (auditoria de UX, P2-7): se as legendas não cabem numa linha, as menos essenciais saem + /// do fim para o começo; Abrir, Voltar, Ações e Menu ficam sempre. Tudo continua funcionando e está nos menus. + /// + private void FitFooter() + { + var available = Theme.Viewport.Width - _footer.Padding.Left - _footer.Padding.Right - 1; + var infinite = new Windows.Foundation.Size(double.PositiveInfinity, double.PositiveInfinity); + var chips = _hints.Children.OfType().ToList(); + var widths = chips.Select(c => + { + c.Measure(infinite); + return c.DesiredSize.Width; + }).ToArray(); + double Row() => chips.Select((c, i) => c.Visibility == Visibility.Visible ? widths[i] + _hints.HorizontalSpacing : 0).Sum() - _hints.HorizontalSpacing; + for (var i = chips.Count - 1; i >= 0 && Row() > available; i--) + if (chips[i].Tag is InputAction action && !IsEssentialPrompt(action)) chips[i].Visibility = Visibility.Collapsed; + } + + private static bool IsEssentialPrompt(InputAction action) => + action is InputAction.Confirm or InputAction.Back or InputAction.OpenContextMenu or InputAction.OpenAppMenu; + /// Glifos do rodapé grandes o bastante para ler a distância (a referência usa botões redondos de ~2,5× o texto). private static double FooterGlyphHeight => Math.Round(Theme.FontBody * (Theme.Layout.Tier == Core.Layout.LayoutTier.Compact ? 1.9 : 2.3)); @@ -934,6 +968,8 @@ private void UpdateLayoutProfile() var size = xamlRoot.Size; var profile = LayoutBreakpoints.Select(size.Width, size.Height, xamlRoot.RasterizationScale, _uiSettings.TextScaleFactor); var previous = Theme.Viewport; + // Primeira abertura num portátil: a lista começa compacta (só uma vez; nunca sobre uma escolha salva). + if (!_captures) _app.ApplyFirstRunDensity(profile.Tier == Core.Layout.LayoutTier.Compact); if (Theme.SetLayout(profile, size)) { ApplyLayout(); @@ -1052,14 +1088,14 @@ private void ApplyLayout() _headerRight.MaxWidth = Math.Max(240, Theme.Viewport.Width * 0.35); // as abas nunca são espremidas pelo status _logo.Height = Theme.Layout.LogoHeight; _logoPlate.CornerRadius = new CornerRadius(Theme.Scaled(12)); - _logoPlate.Padding = Theme.IsDark ? new Thickness(0) : new Thickness(Theme.SpaceS, Theme.SpaceXs, Theme.SpaceM, Theme.SpaceXs); + _logo.Source = Branding.LogoFor(Theme.IsDark); _tabStrip.Spacing = Theme.SpaceXs; _tabs.Spacing = Theme.SpaceS; _badge.Margin = new Thickness(Theme.SpaceL + Theme.SpaceS, 0, Theme.SpaceL, Theme.SpaceS); _topBar.ApplyLayout(); _home.ApplyLayout(); _home.SetTrailingGutter(_detailsShown ? DetailsGap : null); - _badge.FontSize = Theme.FontCaption; + _badge.FontSize = Theme.FontBody; // contexto do local (compactado, Git, busca): lido de longe, não uma legenda _toast.ApplyLayout(); _progress.Height = Theme.Space(4); _progress.Background = Theme.Border; @@ -1074,7 +1110,7 @@ private void ApplyLayout() _details.Root.Margin = new Thickness(0, Theme.Space(20), Theme.SpaceL, Theme.Space(24)); _details.ApplyLayout(); _paneView.ApplyLayout(); - _paneCaption.FontSize = Theme.FontCaption; + _paneCaption.FontSize = Theme.FontBody; _paneCaption.Foreground = Theme.Accent; _paneCaption.Margin = new Thickness(Theme.Space(18), Theme.Space(4), Theme.Space(18), Theme.Space(6)); // Dois painéis só onde cabem com o nome legível: portáteis e janelas estreitas ficam com um (a escolha continua salva). diff --git a/src/ControlFS.App/Views/ModalView.cs b/src/ControlFS.App/Views/ModalView.cs index b6168185..9dc8936a 100644 --- a/src/ControlFS.App/Views/ModalView.cs +++ b/src/ControlFS.App/Views/ModalView.cs @@ -308,12 +308,21 @@ private static StackPanel Footer(AppController app, bool fadedHints = false) line.Children.Add(new TextBlock { Text = status, FontSize = Theme.FontBody, Foreground = Theme.Text, TextWrapping = TextWrapping.Wrap, VerticalAlignment = VerticalAlignment.Center }); footer.Children.Add(line); } - var bar = PromptBar(app.Prompts, PromptGlyphHeight, Theme.FontBody); + var bar = PromptBar(app.Prompts, PromptGlyphHeight, Theme.FontBody, KeyboardSecondaryPrompt(app)); if (fadedHints) FadeHints(bar); footer.Children.Add(bar); return footer; } + /// + /// Teclado virtual nos portáteis: as legendas ficam nas essenciais (Selecionar, Apagar, Maiúsculas, Símbolos, Concluir, + /// Cancelar); mover o cursor e ir ao início/fim continuam funcionando, só não ocupam uma segunda linha. + /// + private static Func? KeyboardSecondaryPrompt(AppController app) => + app.TopModal is KeyboardModal && Theme.Layout.Tier == Core.Layout.LayoutTier.Compact + ? p => p.Action is InputAction.PreviousRegion or InputAction.NextRegion or InputAction.PageUp or InputAction.PageDown + : null; + /// Se as legendas já estavam recolhidas no quadro anterior (o modal da imagem é refeito a cada quadro). private static bool _hintsFaded; @@ -779,7 +788,7 @@ private static Border DialogRow(AppController app, DialogModal dialog, int index private static Border BuildAbout(AppController app, AboutModal about) { var stack = new StackPanel { Spacing = Theme.SpaceM }; - if (Branding.Logo is { } logo) + if (Branding.LogoFor(Theme.IsDark) is { } logo) { var image = new Image { Source = logo, Height = Theme.Scaled(64), HorizontalAlignment = HorizontalAlignment.Left, Stretch = Stretch.Uniform }; AutomationProperties.SetName(image, "ControlFS"); @@ -988,8 +997,9 @@ private static Border KeyCell(AppController app, VirtualKeyboard kb, int r, int // Tecla focada: preenchida (como a opção focada dos menus), em negrito e um pouco maior. var cell = new Border { - Background = focused ? (enabled ? Theme.FocusFill : Theme.DisabledFill) : key.IsFunction ? Theme.ModalInset : Theme.ModalDivider, - BorderBrush = focused ? Theme.Text : isDone ? Theme.Accent : Theme.Transparent, + Background = focused ? (enabled ? Theme.FocusFill : Theme.DisabledFill) : key.IsFunction ? Theme.KeyFunctionFill : Theme.KeyFill, + // Só a tecla focada tem contorno: Concluir se distingue pelo texto em destaque, nunca por algo parecido com o foco. + BorderBrush = focused ? Theme.Text : Theme.Transparent, BorderThickness = Theme.Hairline, CornerRadius = new CornerRadius(Theme.Scaled(10)), Child = new TextBlock diff --git a/src/ControlFS.App/Views/OnboardingView.cs b/src/ControlFS.App/Views/OnboardingView.cs index 89c56434..30ae211c 100644 --- a/src/ControlFS.App/Views/OnboardingView.cs +++ b/src/ControlFS.App/Views/OnboardingView.cs @@ -52,18 +52,12 @@ private static Grid OnboardingScreen(AppController app, OnboardingModal modal) var top = new Grid { Margin = new Thickness(0, 0, CaptionReserve, 0) }; top.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); top.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); - if (Branding.Logo is { } logo) + if (Branding.LogoFor(Theme.IsDark) is { } logo) { + // Tema claro: a variante com o nome escuro, sem placa atrás (a mesma do cabeçalho). var image = new Image { Source = logo, Height = Theme.Layout.LogoHeight, HorizontalAlignment = HorizontalAlignment.Left, Stretch = Stretch.Uniform }; AutomationProperties.SetName(image, "ControlFS"); - top.Children.Add(new Border - { - Child = image, - HorizontalAlignment = HorizontalAlignment.Left, - Background = Theme.LogoPlate, - CornerRadius = new CornerRadius(Theme.Scaled(12)), - Padding = Theme.IsDark ? new Thickness(0) : new Thickness(Theme.SpaceS, Theme.SpaceXs, Theme.SpaceM, Theme.SpaceXs), - }); + top.Children.Add(image); } var progress = new StackPanel { Orientation = Orientation.Horizontal, Spacing = Theme.SpaceS, VerticalAlignment = VerticalAlignment.Center }; progress.Children.Add(new TextBlock diff --git a/src/ControlFS.App/Views/PaneView.cs b/src/ControlFS.App/Views/PaneView.cs index 706e0dc6..a3905cfd 100644 --- a/src/ControlFS.App/Views/PaneView.cs +++ b/src/ControlFS.App/Views/PaneView.cs @@ -87,8 +87,8 @@ public void ApplyLayout() _root.Padding = new Thickness(0, Theme.Space(10), 0, Theme.Space(10)); _title.Margin = _path.Margin = new Thickness(Theme.Space(18), 0, Theme.Space(18), 0); _path.Margin = new Thickness(Theme.Space(18), 0, Theme.Space(18), Theme.Space(8)); - _title.FontSize = Theme.FontCaption; - _path.FontSize = Theme.FontCaption; + _title.FontSize = Theme.FontBody; // "PAINEL DIREITO · L3 ou Tab para ativar": lido de longe (auditoria de UX, P1-7) + _path.FontSize = Theme.FontBody; _empty.FontSize = Theme.FontBody; _title.Foreground = _path.Foreground = _empty.Foreground = Theme.TextMuted; _list.Padding = new Thickness(Theme.Space(10), 0, Theme.Space(10), 0); diff --git a/src/ControlFS.App/Views/TopBarView.cs b/src/ControlFS.App/Views/TopBarView.cs index 5a61e28b..91d49bf4 100644 --- a/src/ControlFS.App/Views/TopBarView.cs +++ b/src/ControlFS.App/Views/TopBarView.cs @@ -40,8 +40,10 @@ internal sealed class TopBarView private readonly List _crumbRings = []; private readonly List _crumbGlows = []; private readonly List _crumbSeparators = []; + private readonly List _crumbLabels = []; private readonly TextBlock _overflow = new() { Text = "…", VerticalAlignment = VerticalAlignment.Center }; private double[] _crumbWidths = []; + private double[] _labelWidths = []; private double _pathMax = double.PositiveInfinity; private readonly List _quickRings = []; private readonly List _quickLabels = []; @@ -157,6 +159,7 @@ private void Fit() var path = _crumbs.DesiredSize.Width; _crumbWidths = [.. _crumbGlows.Select((g, i) => g.DesiredSize.Width + _crumbs.Spacing + (i > 0 ? _crumbSeparators[i - 1].DesiredSize.Width + _crumbSeparators[i - 1].Margin.Left + _crumbSeparators[i - 1].Margin.Right + _crumbs.Spacing : 0))]; + _labelWidths = [.. _crumbLabels.Select(l => l.DesiredSize.Width)]; var labeled = _quick.DesiredSize.Width; _iconsOnly = _quickLabels.Count > 0 && path + labeled > bar; var widestLabel = 0.0; @@ -171,7 +174,10 @@ private void Fit() } // O focado mostra o nome; o glifo do R1 usa essa mesma folga (sem ela, o caminho perderia um segmento). var quickWidth = _quickLabels.Count == 0 ? 0 : _quick.DesiredSize.Width + widestLabel; - var max = Math.Max(Theme.Scaled(240), bar - quickWidth); + // A raiz, o "…" e um pedaço da pasta atual sempre cabem (auditoria de UX, P2-7): com pouco espaço, quem cede são + // os atalhos (que rolam até o focado), nunca a raiz do caminho. + var rootAndCurrent = _crumbWidths.Length > 1 ? _crumbWidths[0] + (Theme.FontBody * 1.2) + _crumbs.Spacing + Theme.Scaled(150) : 0; + var max = Math.Max(Math.Max(Theme.Scaled(240), rootAndCurrent), bar - quickWidth); _pathMax = double.IsFinite(max) ? max : double.PositiveInfinity; // antes da primeira faixa de layout há medidas NaN _crumbScroll.MaxWidth = _pathMax; } @@ -203,6 +209,16 @@ private void LayoutCrumbs(int focus) } } _overflow.Visibility = start > 1 ? Visibility.Visible : Visibility.Collapsed; + // A raiz nunca sai de vista (auditoria de UX, P2-7): se nem raiz + "…" + a pasta mostrada cabem (portátil com + // texto grande), o nome dessa pasta é cortado no fim em vez de a barra rolar e esconder a raiz. + if (_crumbLabels.Count == count && _labelWidths.Length == count) + { + foreach (var label in _crumbLabels) label.MaxWidth = CrumbLabelMax; + var shown = _crumbWidths[0] + (start > 1 ? (Theme.FontBody * 1.2) + _crumbs.Spacing : 0); + for (var i = start; i <= end; i++) shown += _crumbWidths[i]; + if (count > 1 && shown > _pathMax && double.IsFinite(_pathMax)) + _crumbLabels[end].MaxWidth = Math.Max(Theme.Scaled(60), _labelWidths[end] - (shown - _pathMax)); + } for (var i = 1; i < count; i++) { var visible = i >= start && i <= end ? Visibility.Visible : Visibility.Collapsed; @@ -213,6 +229,9 @@ private void LayoutCrumbs(int focus) private static double FontSize => Theme.FontCaption + 1; + /// Largura máxima do nome de um segmento do caminho. + private static double CrumbLabelMax => Theme.Scaled(220); + /// Glifo do ombro (LB/RB, L1/R1, L/R) da família em uso; sem controle, a tecla (Ctrl+←/Ctrl+→). private static UIElement Shoulder(Application.Prompts.ControllerPrompt prompt) => prompt is { Button: { } button, Family: { } family } ? ControllerGlyphs.Create(button, family, Math.Round(Theme.FontBody * 1.5)) @@ -239,6 +258,7 @@ private void Build(IReadOnlyList crumbs, IReadOnlyList crumbs, IReadOnlyList view == ViewMode.Grid ? "grade" : "lista"; /// Alterna a densidade da lista (preferência salva): confortável para TV, compacta para ver mais itens. + /// Primeira abertura: a densidade ainda pode seguir o aparelho (só até a primeira decisão). + private bool _firstRunDensity; + + /// + /// Portáteis (auditoria de UX, P2-7): na primeira abertura num aparelho da faixa compacta (720p/800p), a lista começa + /// compacta, que mostra o dobro de linhas. Só vale uma vez e nunca sobrepõe uma escolha salva ou feita nesta sessão; a + /// janela chama quando conhece a faixa de layout. + /// + public void ApplyFirstRunDensity(bool handheld) + { + if (!_firstRunDensity) return; + _firstRunDensity = false; + if (!handheld || Settings.Density == ListDensity.Compact) return; + UpdateSettings(s => s with { Density = ListDensity.Compact }); + RaiseChanged(); + } + internal void ToggleDensity() { + _firstRunDensity = false; UpdateSettings(s => s with { Density = s.Density == ListDensity.Compact ? ListDensity.Comfortable : ListDensity.Compact }); StatusMessage = $"Lista {DensityName(Settings.Density)}."; } diff --git a/src/ControlFS.Application/AppController.Details.cs b/src/ControlFS.Application/AppController.Details.cs index 4cf925bb..dc027558 100644 --- a/src/ControlFS.Application/AppController.Details.cs +++ b/src/ControlFS.Application/AppController.Details.cs @@ -36,6 +36,9 @@ public enum DetailsIcon Compression, Lock, Marked, + + /// Há itens marcados, mas não o item em foco: caixa vazia (nunca o símbolo de marcado). + Unmarked, Info, Warning, } @@ -303,7 +306,7 @@ private void AddMarkedSummary(List lines, FileEntry focused) var text = marked.Count == 1 ? "1 item marcado" : string.Create(EntryText.Culture, $"{marked.Count:N0} itens marcados"); if (bytes > 0) text += " · " + EntryText.Size(bytes) + (marked.Any(e => e.IsContainer) ? " em arquivos" : string.Empty); var focusedMarked = marked.Any(e => e.Id == focused.Id); - lines.Add(new DetailsLine(DetailsIcon.Marked, focusedMarked ? "Este item está marcado" : "Este item não está marcado", text)); + lines.Add(new DetailsLine(focusedMarked ? DetailsIcon.Marked : DetailsIcon.Unmarked, focusedMarked ? "Este item está marcado" : "Este item não está marcado", text)); } /// Arquivos, pastas e bytes declarados sob uma pasta do compactado (a árvore já está na memória). diff --git a/src/ControlFS.Application/AppController.cs b/src/ControlFS.Application/AppController.cs index fbb4d54d..7f3ae84e 100644 --- a/src/ControlFS.Application/AppController.cs +++ b/src/ControlFS.Application/AppController.cs @@ -119,6 +119,7 @@ public void Start() var loaded = _settingsStore.Load(); Settings = loaded.Settings; StatusMessage = loaded.Notice; + _firstRunDensity = loaded.FirstRun; } LoadControllerProfiles(); LoadHistory(); diff --git a/src/ControlFS.Core/Appearance/ThemePalette.cs b/src/ControlFS.Core/Appearance/ThemePalette.cs index 09d2d235..968f1ad9 100644 --- a/src/ControlFS.Core/Appearance/ThemePalette.cs +++ b/src/ControlFS.Core/Appearance/ThemePalette.cs @@ -52,8 +52,11 @@ public sealed record ThemePalette( uint DangerFill, uint DisabledFill, uint DetailsGlow, - uint LogoPlate) + uint KeyFill, + uint KeyFunctionFill) { + // KeyFill/KeyFunctionFill: teclas de caractere e de função do teclado virtual, visíveis contra o painel do modal. + /// Halo translúcido em volta do anel de foco: o destaque com 1/3 de opacidade. public uint FocusGlow => (Accent & 0x00FFFFFF) | 0x55000000; } @@ -85,7 +88,8 @@ public static ThemePalette Build(bool dark, AccentColor accent) Selected: 0xFFF2C14E, Danger: 0xFFFF7A6E, Success: 0xFF4CD98A, Scrim: 0xC802070C, ModalTint: 0xFF0C1B2A, ModalSolid: 0xFF0D1C2B, ModalScrimGlass: 0xB402070C, ModalScrimSolid: 0xE602070C, ModalEdge: 0x38FFFFFF, ModalDivider: 0x24FFFFFF, ModalInset: 0x1AFFFFFF, - FocusText: 0xFF03101A, DangerFill: 0xFFFF8A7F, DisabledFill: 0xFF2A3E52, DetailsGlow: 0xFF0E2A45, LogoPlate: 0x00000000) + FocusText: 0xFF03101A, DangerFill: 0xFFFF8A7F, DisabledFill: 0xFF2A3E52, DetailsGlow: 0xFF0E2A45, + KeyFill: 0x2EFFFFFF, KeyFunctionFill: 0x1AFFFFFF) : new ThemePalette(false, accent, Background: 0xFFF3F6FA, Surface: 0xFFE8EEF5, SurfaceRaised: 0xFFFFFFFF, Border: 0xFFC9D6E3, Text: 0xFF0B1724, TextMuted: 0xFF4A5A6C, TextDisabled: 0xFF8595A6, @@ -93,7 +97,8 @@ public static ThemePalette Build(bool dark, AccentColor accent) Selected: 0xFF8A5A00, Danger: 0xFFB42318, Success: 0xFF1A7F37, Scrim: 0x990B1724, ModalTint: 0xFFF7FAFD, ModalSolid: 0xFFFFFFFF, ModalScrimGlass: 0x800B1724, ModalScrimSolid: 0xB00B1724, ModalEdge: 0x330B1724, ModalDivider: 0x1F0B1724, ModalInset: 0x0F0B1724, - FocusText: 0xFFFFFFFF, DangerFill: 0xFFC42B1C, DisabledFill: 0xFFDCE3EA, DetailsGlow: 0xFFDCE8F5, LogoPlate: 0xFF0B1724); + FocusText: 0xFFFFFFFF, DangerFill: 0xFFC42B1C, DisabledFill: 0xFFDCE3EA, DetailsGlow: 0xFFDCE8F5, + KeyFill: 0xFFC8D3E0, KeyFunctionFill: 0xFFE6ECF2); } /// Tema efetivo: segue o Windows (). diff --git a/src/ControlFS.Core/Contracts/ISettingsStore.cs b/src/ControlFS.Core/Contracts/ISettingsStore.cs index 0106044a..1634eae9 100644 --- a/src/ControlFS.Core/Contracts/ISettingsStore.cs +++ b/src/ControlFS.Core/Contracts/ISettingsStore.cs @@ -116,7 +116,8 @@ public sealed record AppSettings public bool? OnboardingCompleted { get; init; } } -public sealed record SettingsLoadResult(AppSettings Settings, bool RecoveredFromCorruption, string? Notice); +/// Nenhuma preferência salva ainda (primeira abertura): padrões que dependem do aparelho podem valer. +public sealed record SettingsLoadResult(AppSettings Settings, bool RecoveredFromCorruption, string? Notice, bool FirstRun = false); public interface ISettingsStore { diff --git a/src/ControlFS.Core/Text/PathEllipsis.cs b/src/ControlFS.Core/Text/PathEllipsis.cs new file mode 100644 index 00000000..5136bf88 --- /dev/null +++ b/src/ControlFS.Core/Text/PathEllipsis.cs @@ -0,0 +1,46 @@ +namespace ControlFS.Core.Text; + +/// +/// Caminho longo encurtado no meio (auditoria de UX, P2-6): a unidade (ou o compartilhamento de rede) e as últimas pastas +/// ficam; o que some é o meio, trocado por "…". Cortar só o fim escondia justamente a pasta que interessa +/// ("C:\Users\…\Relatórios trimestrai…"). +/// +public static class PathEllipsis +{ + public const string Mark = "…"; + + /// + /// com no máximo caracteres quando possível: raiz + "…" + as pastas + /// finais que cabem. A última parte fica sempre inteira (quem desenha ainda corta o fim se ela sozinha não couber). + /// + public static string Middle(string path, int maxChars) + { + if (string.IsNullOrEmpty(path) || path.Length <= maxChars) return path; + var separator = path.Contains('\\', StringComparison.Ordinal) ? '\\' : '/'; + var root = Root(path, separator); + var parts = path[root.Length..].Split(separator, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length <= 1) return path; + var tail = parts[^1]; + var prefix = root + Mark + separator; + for (var i = parts.Length - 2; i >= 1; i--) + { + var longer = parts[i] + separator + tail; + if (prefix.Length + longer.Length > maxChars) break; + tail = longer; + } + return prefix + tail; + } + + /// "C:\", "\\servidor\pasta\" ou "/": o que identifica onde o caminho está. + private static string Root(string path, char separator) + { + if (path.StartsWith(@"\\", StringComparison.Ordinal)) + { + var server = path.IndexOf('\\', 2); + var share = server < 0 ? -1 : path.IndexOf('\\', server + 1); + return share < 0 ? path : path[..(share + 1)]; + } + if (path.Length >= 3 && path[1] == ':' && path[2] == separator) return path[..3]; + return path[0] == separator ? path[..1] : string.Empty; + } +} diff --git a/src/ControlFS.Core/Text/VirtualKeyboard.cs b/src/ControlFS.Core/Text/VirtualKeyboard.cs index faab016e..6aac6cba 100644 --- a/src/ControlFS.Core/Text/VirtualKeyboard.cs +++ b/src/ControlFS.Core/Text/VirtualKeyboard.cs @@ -62,6 +62,18 @@ public VirtualKeyboard(TextFieldKind kind, string title, string initialText = "" Caret = Math.Clamp(initialCaret ?? _length, 0, _length); if (initialSelection is { } selection) Select(selection.Start, selection.Length); Rebuild(); + Row = FirstLetterRow(); + } + + /// + /// Foco inicial: a primeira tecla de letra (o "q"), não a linha de números — quem abre o teclado quase sempre começa + /// por uma letra (auditoria de UX, P2-11). + /// + private int FirstLetterRow() + { + for (var r = 0; r < Rows.Count; r++) + if (Rows[r] is [{ Kind: KeyKind.Character, Text: [var c] }, ..] && char.IsLetter(c)) return r; + return 0; } public TextFieldKind Kind { get; } diff --git a/src/ControlFS.Infrastructure.Windows/Settings/JsonSettingsStore.cs b/src/ControlFS.Infrastructure.Windows/Settings/JsonSettingsStore.cs index 4c50566f..99acfe20 100644 --- a/src/ControlFS.Infrastructure.Windows/Settings/JsonSettingsStore.cs +++ b/src/ControlFS.Infrastructure.Windows/Settings/JsonSettingsStore.cs @@ -26,8 +26,9 @@ public static string DefaultDirectory() => public SettingsLoadResult Load() { - // Sem arquivo: primeira execução; as boas-vindas ficam pendentes até serem vistas ou puladas (#231). - if (!File.Exists(FilePath)) return new(new AppSettings { OnboardingCompleted = false }, false, null); + // Sem arquivo: primeira execução; as boas-vindas ficam pendentes até serem vistas ou puladas (#231) e padrões que + // dependem do aparelho (densidade nos portáteis) podem valer. + if (!File.Exists(FilePath)) return new(new AppSettings { OnboardingCompleted = false }, false, null, FirstRun: true); try { var info = new FileInfo(FilePath); diff --git a/tests/ControlFS.UnitTests/Application/DensityJourneyTests.cs b/tests/ControlFS.UnitTests/Application/DensityJourneyTests.cs index fbaf7df9..7c299aba 100644 --- a/tests/ControlFS.UnitTests/Application/DensityJourneyTests.cs +++ b/tests/ControlFS.UnitTests/Application/DensityJourneyTests.cs @@ -18,6 +18,35 @@ public void Dispose() _data.Dispose(); } + [Fact] + public void First_run_on_a_handheld_starts_compact_but_never_overrides_a_saved_choice() => UiContext.Run(async () => + { + // Auditoria de UX (P2-7): em 720p/800p a lista confortável mostrava ~5 linhas. + var store = new JsonSettingsStore(_data.Path); + var app = new AppController(new TestFileSystem(_tmp.Path), new ArchiveService(), store); + app.Start(); + app.ApplyFirstRunDensity(handheld: true); + Assert.Equal(ListDensity.Compact, app.Settings.Density); + + // A pessoa volta para a confortável: nenhuma abertura seguinte no portátil muda isso. + var d = new Driver(app); + d.Press(InputAction.OpenAppMenu); + await d.ChooseMenu("Densidade da lista: compacta"); + Assert.Equal(ListDensity.Comfortable, app.Settings.Density); + var relaunched = new AppController(new TestFileSystem(_tmp.Path), new ArchiveService(), store); + relaunched.Start(); + relaunched.ApplyFirstRunDensity(handheld: true); + Assert.Equal(ListDensity.Comfortable, relaunched.Settings.Density); + + // Primeira abertura fora dos portáteis: o padrão continua o de sempre. + using var other = new TempDir(); + var desktop = new AppController(new TestFileSystem(_tmp.Path), new ArchiveService(), new JsonSettingsStore(other.Path)); + desktop.Start(); + desktop.ApplyFirstRunDensity(handheld: false); + desktop.ApplyFirstRunDensity(handheld: true); // só a primeira faixa conhecida vale + Assert.Equal(ListDensity.Comfortable, desktop.Settings.Density); + }); + [Fact] public void List_density_is_chosen_in_the_menu_and_persists_across_launches() => UiContext.Run(async () => { diff --git a/tests/ControlFS.UnitTests/Application/DetailsPanelJourneyTests.cs b/tests/ControlFS.UnitTests/Application/DetailsPanelJourneyTests.cs index 6c4cacf8..4eb4ca4d 100644 --- a/tests/ControlFS.UnitTests/Application/DetailsPanelJourneyTests.cs +++ b/tests/ControlFS.UnitTests/Application/DetailsPanelJourneyTests.cs @@ -236,7 +236,9 @@ public void Grid_panel_follows_focus_summarizes_marks_without_a_focused_item_and var marked = app.Details!.Lines.Single(l => l.Icon == DetailsIcon.Marked); Assert.Equal(("c.bin", "Este item está marcado", "2 itens marcados · 3 KB"), (app.Details!.Title, marked.Label, marked.Value)); await d.FocusItem("Jogos"); - Assert.Equal("Este item não está marcado", app.Details!.Lines.Single(l => l.Icon == DetailsIcon.Marked).Label); + // Caixa vazia, não o símbolo de marcado (auditoria de UX, P3). + Assert.Equal("Este item não está marcado", app.Details!.Lines.Single(l => l.Icon == DetailsIcon.Unmarked).Label); + Assert.DoesNotContain(app.Details!.Lines, l => l.Icon == DetailsIcon.Marked); // Foco fora da grade (barra superior): só o resumo dos marcados, sem apontar nenhum item como focado. d.Press(InputAction.PreviousRegion); diff --git a/tests/ControlFS.UnitTests/Core/PathEllipsisTests.cs b/tests/ControlFS.UnitTests/Core/PathEllipsisTests.cs new file mode 100644 index 00000000..d6dfdc4c --- /dev/null +++ b/tests/ControlFS.UnitTests/Core/PathEllipsisTests.cs @@ -0,0 +1,17 @@ +using ControlFS.Core.Text; + +namespace ControlFS.UnitTests.Core; + +/// Auditoria de UX (P2-6): o caminho longo perde o meio, nunca a unidade nem a pasta final. +public class PathEllipsisTests +{ + [Fact] + public void Long_paths_keep_the_root_and_the_last_folders() + { + const string path = @"C:\Users\erick\Documents\Empresa\Financeiro\2026\Relatórios trimestrais"; + Assert.Equal(path, PathEllipsis.Middle(path, 200)); + Assert.Equal(@"C:\…\2026\Relatórios trimestrais", PathEllipsis.Middle(path, 34)); + Assert.Equal(@"C:\…\Relatórios trimestrais", PathEllipsis.Middle(path, 10)); // a pasta final fica inteira + Assert.Equal(@"\\nas\filmes\…\Relatórios trimestrais", PathEllipsis.Middle(@"\\nas\filmes\Séries\2026\Relatórios trimestrais", 38)); + } +} diff --git a/tests/ControlFS.UnitTests/Core/ThemeContrastTests.cs b/tests/ControlFS.UnitTests/Core/ThemeContrastTests.cs index 7fd94f7b..585b18c5 100644 --- a/tests/ControlFS.UnitTests/Core/ThemeContrastTests.cs +++ b/tests/ControlFS.UnitTests/Core/ThemeContrastTests.cs @@ -37,6 +37,15 @@ public void Every_theme_and_accent_meets_the_contrast_targets(bool dark, AccentC Text(p.FocusText, p.DangerFill, "opção perigosa focada"); Text(p.TextMuted, p.DisabledFill, "opção indisponível focada"); Text(p.Text, ThemeContrast.Over(p.ModalInset, p.ModalSolid), "informação no painel"); + + // Teclado virtual (auditoria de UX, P2-12): as teclas se destacam do painel e o texto delas continua legível. + var key = ThemeContrast.Over(p.KeyFill, p.ModalSolid); + var function = ThemeContrast.Over(p.KeyFunctionFill, p.ModalSolid); + Assert.True(ThemeContrast.Ratio(key, p.ModalSolid) >= 1.5, $"tecla/painel: {ThemeContrast.Ratio(key, p.ModalSolid):0.00}"); + Assert.True(ThemeContrast.Ratio(function, p.ModalSolid) >= 1.15, $"tecla de função/painel: {ThemeContrast.Ratio(function, p.ModalSolid):0.00}"); + Text(p.Text, key, "letra na tecla"); + Text(p.Text, function, "texto na tecla de função"); + Text(p.Accent, function, "Concluir e página atual (destaque na tecla de função)"); Assert.Equal(dark, p.IsDark); } diff --git a/tests/ControlFS.UnitTests/Core/VirtualKeyboardTests.cs b/tests/ControlFS.UnitTests/Core/VirtualKeyboardTests.cs index b402fe63..2326224d 100644 --- a/tests/ControlFS.UnitTests/Core/VirtualKeyboardTests.cs +++ b/tests/ControlFS.UnitTests/Core/VirtualKeyboardTests.cs @@ -184,6 +184,8 @@ public void Cancel_discards_without_applying() public void Vertical_navigation_keeps_column_across_wide_keys() { var kb = new VirtualKeyboard(TextFieldKind.Generic, "t"); + Assert.Equal("q", kb.FocusedKey.Text); // começa na primeira letra, não no "1" (P2-11) + kb.Handle(InputAction.NavigateUp); // linha dos números // Coluna 7 ("8") desce pelas letras até a barra de espaço (colunas 6-8) e segue para Concluir (colunas 6-10). for (var i = 0; i < 7; i++) kb.Handle(InputAction.NavigateRight); for (var i = 0; i < 4; i++) kb.Handle(InputAction.NavigateDown);