Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
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]
### What's new
- **Hover feedback** (#295): menu and Configurações blocks and rows (including the icon-only tiles), tabs, shortcuts, the path bar, cards, list and grid rows, on-screen keyboard keys and column headers now lighten slightly (darken in the light theme) under the pointer, always in a neutral tone and never in the accent colour so it can't be mistaken for controller/keyboard focus. The focused item gets no hover, and it only shows while the mouse recently moved: scrolling the list with the controller under a stationary pointer lights nothing (the reason the system highlight was removed in #182).
### Changes
- **Controller sounds replaced by a professional CC0 set** (#287): the synthesized cues were replaced by four short, soft sounds from Kenney's "Interface Sounds" pack (CC0, free redistribution; origin and licence documented in THIRD_PARTY_NOTICES.md and assets/sounds): move, mark, confirm and back, trimmed, click-free and at the same energy. The "Sons do controle" setting (off/low/medium/high/max) still applies on top. The PS5 interface was only a feel reference.
- **Settings as one wide column of icon-only tiles** (#293, replaces the two-column design of 0.14): every setting is now an icon-only tile, with no text beside it, in groups inside a single wide column (no parallel columns). The focused tile fills in and its **name, value and what it does** show in the panel's fixed reading area (and in a hover tooltip); Narrator still reads name and value. L1/R1 jump between groups and Configurações stays the first tile of the Menu. No setting was removed or changed behavior.

## [0.14.0-alpha.1]
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
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]
### Novidades
- **Retorno ao passar o mouse** (#295): blocos e linhas de menu e de Configurações (inclusive os blocos só de ícone), abas, atalhos, caminho, cartões, linhas da lista e da grade, teclas do teclado virtual e cabeçalhos de coluna agora clareiam de leve (escurecem no tema claro) quando o ponteiro está em cima, sempre num tom neutro, nunca na cor de destaque, para não se confundir com o foco do controle/teclado. O item focado não recebe hover, e ele só aparece enquanto o mouse se mexeu há pouco: rolar a lista com o controle sob um ponteiro parado não acende nada (o motivo de o destaque do sistema ter saído na #182).
### Mudanças
- **Sons do controle trocados por um conjunto profissional CC0** (#287): os toques sintetizados foram substituídos por quatro sons curtos e suaves do pacote "Interface Sounds" de Kenney (licença CC0, redistribuição livre; origem e licença documentadas em THIRD_PARTY_NOTICES.md e assets/sounds): mover, marcar, confirmar e voltar, aparados, sem estalo e com a mesma energia. O ajuste "Sons do controle" (desligado/baixo/médio/alto/máximo) continua valendo sobre eles. A interface do PS5 foi só referência de sensação.
- **Configurações numa coluna larga, só com ícones** (#293, refaz o desenho em duas colunas da 0.14): todos os ajustes viram blocos só de ícone, sem texto ao lado, em grupos numa única coluna larga (sem colunas paralelas). O bloco em foco se preenche e o **nome, o valor e o que o ajuste faz** aparecem na área de leitura fixa do painel (e numa dica ao passar o mouse); o Narrador continua lendo nome e valor. L1/R1 pulam de grupo e Configurações segue como primeiro bloco do Menu. Nenhum ajuste foi removido ou mudou de comportamento.

## [0.14.0-alpha.1]
Expand Down
11 changes: 7 additions & 4 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,12 @@ AGPL-3.0-only deste código e não podem ser reutilizadas sem permissão dos don
Sons do controle
------------------------------------------------------------------------------

Os toques de mover, confirmar, voltar e marcar (src/ControlFS.Core/Audio/ToneSynth.cs) são sintetizados em código neste
projeto: ondas senoidais com dois harmônicos e um envelope próprio. Nenhum arquivo de áudio, biblioteca de sons ou
amostra de terceiros é incluído, então não há licença de áudio externa: os sons valem sob a AGPL-3.0-only do projeto. A
interface do PS5 é apenas referência de sensação (sons curtos, suaves e limpos); nada da Sony foi copiado ou imitado nota por nota.
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.

O Windows e seus serviços não fazem parte deste código aberto.
22 changes: 22 additions & 0 deletions assets/sounds/LICENSE-Kenney-CC0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@


Interface Sounds (1.0)

Created/distributed by Kenney (www.kenney.nl)
Creation date: 11-02-2020

------------------------------

License: (Creative Commons Zero, CC0)
http://creativecommons.org/publicdomain/zero/1.0/

This content is free to use in personal, educational and commercial projects.
Support us by crediting Kenney or www.kenney.nl (this is not mandatory)

------------------------------

Donate: http://support.kenney.nl
Patreon: http://patreon.com/kenney/

Follow on Twitter for updates:
http://twitter.com/KenneyNL
Expand Down
Binary file added assets/sounds/back.wav
Binary file not shown.
Binary file added assets/sounds/confirm.wav
Binary file not shown.
Binary file added assets/sounds/move.wav
Binary file not shown.
Binary file added assets/sounds/select.wav
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ The `controlfs://` scheme is registered in Windows by the installer (and re-regi

## Controller sounds

Menu → Configurações → **Sons do controle** plays short, soft cues for what you do with the controller: a tick when focus moves, a rising note for confirm/open, a falling one for back and a small blip when you mark an item. They are **on by default at medium volume**; choose **desligados** (off), **baixo**, **médio**, **alto** or **máximo** (your choice is kept across restarts and updates). They play only for controller actions (never the keyboard), don't repeat faster than about 12 times a second when you hold the D-pad, use the default Windows audio output and never change a button or how navigation works. The sounds are generated inside the app (no audio files).
Menu → Configurações → **Sons do controle** plays short, soft cues for what you do with the controller: a tick when focus moves, a rising note for confirm/open, a falling one for back and a small blip when you mark an item. They are **on by default at medium volume**; choose **desligados** (off), **baixo**, **médio**, **alto** or **máximo** (your choice is kept across restarts and updates). They play only for controller actions (never the keyboard), don't repeat faster than about 12 times a second when you hold the D-pad, use the default Windows audio output and never change a button or how navigation works. The four sounds are short CC0 cues from Kenney's "Interface Sounds" pack, embedded in the app (licence in THIRD_PARTY_NOTICES.md).

## Screen readers

Expand Down
2 changes: 1 addition & 1 deletion docs/GUIDE.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ O esquema `controlfs://` é registrado no Windows pelo instalador (e de novo ao

## Sons do controle

Menu → Configurações → **Sons do controle** toca sons curtos e suaves para o que você faz com o controle: um tique ao mover o foco, uma nota que sobe ao confirmar/abrir, uma que desce ao voltar e um toque pequeno ao marcar um item. Vêm **ligados em volume médio**; escolha **desligados**, **baixo**, **médio**, **alto** ou **máximo** (a escolha fica salva ao reabrir e ao atualizar). Tocam só nas ações do controle (nunca do teclado), não repetem mais que umas 12 vezes por segundo ao segurar o direcional, usam a saída de áudio padrão do Windows e nunca mudam um botão nem a navegação. Os sons são gerados dentro do app (sem arquivos de áudio).
Menu → Configurações → **Sons do controle** toca sons curtos e suaves para o que você faz com o controle: um tique ao mover o foco, uma nota que sobe ao confirmar/abrir, uma que desce ao voltar e um toque pequeno ao marcar um item. Vêm **ligados em volume médio**; escolha **desligados**, **baixo**, **médio**, **alto** ou **máximo** (a escolha fica salva ao reabrir e ao atualizar). Tocam só nas ações do controle (nunca do teclado), não repetem mais que umas 12 vezes por segundo ao segurar o direcional, usam a saída de áudio padrão do Windows e nunca mudam um botão nem a navegação. Os quatro sons são toques curtos CC0 do pacote "Interface Sounds" de Kenney, embutidos no app (licença em THIRD_PARTY_NOTICES.md).

## Leitores de tela

Expand Down
5 changes: 5 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -866,3 +866,8 @@ Só a CI (WinRAR 7.23 pelo winget, arquivo criado pelo Rar.exe real e extraído
- [ ] Instalação nova (ou apagar `settings.json`): os sons já tocam ao navegar com o controle, sem ligar nada. Em Configurações → Sons do controle aparece "médio".
- [ ] Escolher "desligados", fechar e reabrir (e atualizar): continua mudo. Escolher "baixo": continua baixo.
- [ ] Ouvir os quatro sons (mover, confirmar, voltar, marcar) em fones e em alto-falante: limpos, sem estalo, do mesmo volume, distintos entre si; segurar o direcional não vira chiado; "baixo" é realmente baixo.

## Hover e sons CC0 (#295, #287) — não validado em hardware
- [ ] Mouse em cima de um bloco de Configurações, de uma linha de menu, de uma aba, de um atalho e de uma linha da lista: o item clareia de leve (escurece no tema claro), sem cor de destaque; ao tirar o mouse volta. O item focado não muda.
- [ ] Mover o foco com o controle/teclado sobre uma lista com o ponteiro parado: nenhuma linha "acende" por hover (só o foco de sempre).
- [ ] Os quatro sons novos (mover, marcar, confirmar, voltar) agradam e têm volume parecido; "baixo" é realmente baixo.
117 changes: 117 additions & 0 deletions src/ControlFS.App/Controls/Hover.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System.Runtime.CompilerServices;
using ControlFS.App.Resources;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;

namespace ControlFS.App.Controls;

/// <summary>
/// Retorno de "mouse em cima" (#295): um clareado suave (escurecido no tema claro), neutro, nunca na cor de destaque, para que o
/// que está sob o ponteiro nunca se pareça com o foco do controle/teclado (#182: o destaque do sistema sumiu justamente porque a
/// lista rolava sob o ponteiro parado e parecia um segundo foco). Por isso só aparece enquanto o mouse se mexeu há pouco
/// (<see cref="NoteMove"/>): rolar ou mover o foco com o controle debaixo de um ponteiro parado não acende nada. O elemento
/// focado nunca recebe hover (o foco manda), e o fundo de antes volta ao sair. Sem animação própria além da troca curta do fundo.
/// </summary>
internal static class Hover
{
/// <summary>Por quanto tempo depois de mexer o mouse um elemento novo sob o ponteiro ainda conta como "em cima".</summary>
private static readonly TimeSpan Window = TimeSpan.FromSeconds(1.5);

private static long _lastMove = long.MinValue;

/// <summary>Opacidade de um elemento sob o ponteiro quando o fundo dele não tem um tom de hover próprio (teclas, chips).</summary>
private const double DimmedOpacity = 0.82;

private sealed class State
{
public bool Focused;
public bool Card;
public Brush? Rest;
public bool Hovering;
}

private static readonly ConditionalWeakTable<Border, State> Tracked = [];

/// <summary>A janela chama a cada movimento do ponteiro: só mouse e caneta (toque não tem "em cima").</summary>
public static void NoteMove(PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType != PointerDeviceType.Touch) _lastMove = Environment.TickCount64;
}

private static bool IsMouseActive(PointerRoutedEventArgs e) =>
e.Pointer.PointerDeviceType != PointerDeviceType.Touch && Environment.TickCount64 - _lastMove < Window.TotalMilliseconds;

/// <summary>
/// Anel de foco (linhas, abas, atalhos, cartões): registra o tratamento uma vez por elemento (elementos reciclados não
/// acumulam tratadores) e guarda se ele está focado. Chamado por <see cref="Theme.ApplyFocus"/> e <see cref="Theme.ApplyCardFocus"/>.
/// </summary>
public static void Track(Border border, bool focused, bool card)
{
var state = Tracked.GetValue(border, b =>
{
var created = new State();
b.PointerEntered += (_, e) =>
{
if (created.Focused || !IsMouseActive(e)) return;
created.Rest = b.Background;
created.Hovering = true;
b.Background = created.Card ? Theme.HoverCard : Theme.HoverWash;
};
b.PointerExited += (_, _) => Release(b, created);
b.PointerCanceled += (_, _) => Release(b, created);
return created;
});
state.Focused = focused;
state.Card = card;
state.Hovering = false; // quem chama acabou de pôr o fundo dele: o hover anterior já não vale
}

private static void Release(Border border, State state)
{
if (!state.Hovering) return;
state.Hovering = false;
if (!state.Focused) border.Background = state.Rest;
}

/// <summary>
/// Elemento reconstruído a cada mudança (blocos e linhas de menu, teclas): <paramref name="hover"/> é o fundo sob o ponteiro;
/// sem um, o elemento inteiro fica um pouco mais apagado. Não faz nada se <paramref name="active"/> for falso (focado, indisponível).
/// </summary>
public static void Attach(Border element, bool active, Brush? hover = null)
{
if (!active) return;
Brush? rest = null;
element.PointerEntered += (_, e) =>
{
if (!IsMouseActive(e)) return;
if (hover is null) element.Opacity = DimmedOpacity;
else
{
rest = element.Background;
element.Background = hover;
}
};
void Leave(object? sender, PointerRoutedEventArgs e)
{
if (hover is null) element.Opacity = 1;
else if (rest is not null) element.Background = rest;
rest = null;
}
element.PointerExited += Leave;
element.PointerCanceled += Leave;
}

/// <summary>Qualquer elemento clicável sem fundo próprio (células de cabeçalho, chips): só a opacidade muda.</summary>
public static void AttachDim(FrameworkElement element)
{
element.PointerEntered += (_, e) =>
{
if (IsMouseActive(e)) element.Opacity = DimmedOpacity;
};
element.PointerExited += (_, _) => element.Opacity = 1;
element.PointerCanceled += (_, _) => element.Opacity = 1;
}
}
22 changes: 22 additions & 0 deletions src/ControlFS.App/Resources/Theme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ public static bool Apply(ThemePalette palette)
Set(ModalEdge, palette.ModalEdge);
Set(ModalDivider, palette.ModalDivider);
Set(ModalInset, palette.ModalInset);
HoverWash.Color = Hover(palette, palette.Text, 0x1A);
HoverInset.Color = Blend(palette.ModalInset, palette.Text, 0.12);
HoverCard.Color = Blend(palette.SurfaceRaised, palette.Text, 0.09);
Set(KeyFill, palette.KeyFill);
Set(KeyFunctionFill, palette.KeyFunctionFill);
Set(FocusText, palette.FocusText);
Expand Down Expand Up @@ -170,6 +173,7 @@ public static void ApplyFocus(Microsoft.UI.Xaml.Controls.Border border, bool foc
border.BorderThickness = FocusRing;
border.BorderBrush = focused ? Accent : Transparent;
border.Background = focused ? AccentSoft : Transparent;
ControlFS.App.Controls.Hover.Track(border, focused, card: false);
if (border.Parent is Microsoft.UI.Xaml.Controls.Border { Tag: GlowTag } glow) glow.BorderBrush = focused ? FocusGlow : Transparent;
}

Expand All @@ -188,6 +192,7 @@ public static void ApplyCardFocus(Microsoft.UI.Xaml.Controls.Border card, bool f
card.BorderThickness = FocusRing;
card.BorderBrush = focused ? Accent : Border;
card.Background = focused ? AccentSoft : SurfaceRaised;
ControlFS.App.Controls.Hover.Track(card, focused, card: true);
if (card.Parent is Microsoft.UI.Xaml.Controls.Border { Tag: GlowTag } glow)
{
glow.BorderBrush = focused ? FocusGlow : Transparent;
Expand Down Expand Up @@ -255,6 +260,23 @@ public static Brush ModalPanel() => SolidSurfaces
public static readonly SolidColorBrush ModalDivider = new(ToColor(Palette.ModalDivider));
public static readonly SolidColorBrush ModalInset = new(ToColor(Palette.ModalInset));

/// <summary>
/// "Mouse em cima" (#295): neutro e suave, derivado do texto do tema (clareia no escuro, escurece no claro), nunca na cor de
/// destaque (essa é do foco). <see cref="HoverWash"/> vai sobre fundo transparente (linhas), <see cref="HoverInset"/> no lugar do
/// fundo de blocos dos modais e <see cref="HoverCard"/> no de cartões.
/// </summary>
public static readonly SolidColorBrush HoverWash = new(Hover(Palette, Palette.Text, 0x1A));
public static readonly SolidColorBrush HoverInset = new(Blend(Palette.ModalInset, Palette.Text, 0.12));
public static readonly SolidColorBrush HoverCard = new(Blend(Palette.SurfaceRaised, Palette.Text, 0.09));

private static Color Hover(ThemePalette _, uint text, byte alpha) => ToColor((text & 0x00FFFFFF) | ((uint)alpha << 24));

private static Color Blend(uint from, uint to, double t)
{
byte Mix(int shift) => (byte)Math.Round((((from >> shift) & 0xFF) * (1 - t)) + (((to >> shift) & 0xFF) * t));
return ToColor(0xFF000000u | ((uint)Mix(16) << 16) | ((uint)Mix(8) << 8) | Mix(0));
}

/// <summary>Teclas do teclado virtual: de caractere (mais marcadas) e de função, sempre distintas do painel.</summary>
public static readonly SolidColorBrush KeyFill = new(ToColor(Palette.KeyFill));
public static readonly SolidColorBrush KeyFunctionFill = new(ToColor(Palette.KeyFunctionFill));
Expand Down
1 change: 1 addition & 0 deletions src/ControlFS.App/Views/ListHeaderView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ private void Title(SortField field, string text, int column, double fontSize)
cell.Children.Add(label);
cell.Children.Add(arrow);
cell.Tapped += (_, _) => _app.PointerSortBy(field);
Controls.Hover.AttachDim(cell);
Grid.SetColumn(cell, column);
_grid.Children.Add(cell);
_titles[field] = (label, arrow, cell);
Expand Down
2 changes: 2 additions & 0 deletions src/ControlFS.App/Views/MainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ internal MainWindow(string? dataDirectory)
AppLog.Info("MainWindow: serviços criados; montando layout");
Content = _root;
_root.Content = _layout = BuildLayout();
// Hover (#295): só vale enquanto o mouse se mexeu há pouco; o controle rolando a lista sob um ponteiro parado não acende nada.
_root.AddHandler(UIElement.PointerMovedEvent, new Microsoft.UI.Xaml.Input.PointerEventHandler((_, e) => Controls.Hover.NoteMove(e)), handledEventsToo: true);
AppLog.Info($"MainWindow: layout montado ({startup.ElapsedMilliseconds} ms)");
_root.PreviewKeyDown += (_, e) =>
{
Expand Down
Loading
Loading