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
2 changes: 2 additions & 0 deletions CHANGELOG.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ English (US) release notes, mirroring CHANGELOG.md (Brazilian Portuguese). Befor

## [Unreleased]
### What's new
- **Theme choice in the welcome**: a new step, "Escuro ou claro?" (dark or light), with **Escuro** (dark), **Claro** (light) and **Automático (segue o Windows)** (follows Windows). The screen changes at once so you can see it, focus starts on the current choice, and you can change it later in Menu → Configurações → Tema. The welcome goes from five to six steps (the theme setting moved out of "O básico").
### What's new
- **Controller sounds** (#276): Menu → Configurações → **Sons do controle** (off, low, medium, high, max). Short, soft cues synthesized inside the app, distinct for moving focus, confirming/opening, going back and marking. They play only for controller actions (never the keyboard), don't machine-gun when you hold the D-pad, use the Windows audio output and don't change any button or navigation. Off by default; without a sound device nothing happens.
### What's new
- **Automation and `controlfs://` protocol**: support for `controlfs://start`, `controlfs://stop`, and `controlfs://show` links, as well as command-line arguments (`--start`, `--stop`, `--show`, `--close`), enabling seamless external integration with game launchers and frontends (such as Console Mode), Stream Deck buttons, and automation scripts. Invoking `controlfs://start` when the app is already open restores and brings the window to the foreground without launching duplicate instances; `controlfs://stop` cleanly exits the app; with copies or other operations running it doesn't cancel them by itself and shows the exit confirmation. The protocol is registered by the installer; the portable version writes nothing to the registry (the command-line flags work there). After an update, the app relaunched by the installer waits for the old instance to leave.
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Notas em português do Brasil; a versão em inglês (Estados Unidos) fica em `CH

## [Unreleased]
### Novidades
- **Escolha do tema nas boas-vindas**: um passo novo, "Escuro ou claro?", com **Escuro**, **Claro** e **Automático (segue o Windows)**. A tela muda na hora para você ver, o foco já começa na escolha atual e dá para trocar depois em Menu → Configurações → Tema. As boas-vindas passam de cinco para seis passos (o ajuste de tema saiu do passo "O básico").
### Novidades
- **Sons do controle** (#276): Menu → Configurações → **Sons do controle** (desligados, baixo, médio, alto, máximo). Toques curtos e suaves, desenhados no próprio app, distintos para mover o foco, confirmar/abrir, voltar e marcar. Tocam só nas ações do controle (nunca do teclado), não repetem feito metralhadora ao segurar o direcional, usam a saída de áudio do Windows e não mudam nenhum botão nem a navegação. Vem desligado; sem placa de som, nada acontece.
### Novidades
- **Automação e protocolo `controlfs://`**: suporte aos links `controlfs://start`, `controlfs://stop` e `controlfs://show`, bem como argumentos de linha de comando (`--start`, `--stop`, `--show`, `--close`), facilitando a integração externa com launchers e frontends (como o Console Mode), botões de Stream Deck e scripts. Acionar `controlfs://start` quando o app já está aberto restaura e traz a janela para o primeiro plano sem abrir instâncias duplicadas; `controlfs://stop` encerra o app de forma limpa; havendo cópias ou outras operações em andamento, ele não as cancela sozinho e mostra a confirmação de saída. O protocolo é registrado pelo instalador; a versão portátil não escreve nada no registro (as opções de linha de comando funcionam nela). Depois de uma atualização, o app reaberto pelo instalador espera a instância antiga sair.
Expand Down
2 changes: 1 addition & 1 deletion docs/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Important confirmations (delete, replace, undo) are answered on the PC: from the

## First launch: welcome and guided tutorial

The first time ControlFS opens, a full-screen **welcome** walks you through five steps: welcome; how the controller works (the real buttons of the controller in use — Open, Back, Actions, Menu, Search, Mark, L1/R1 for the top bar, L2/R2 for tabs, R3 for the view; press any button on your controller and the prompts switch to it); the basics, applied at once (confirm with the bottom or right button, button labels, theme, list/grid, Fluidez); privacy (everything stays on this PC; the update check is optional; the phone link is off until you open it); and **Quer fazer o tutorial guiado?** — **Começar tutorial** or **Agora não**.
The first time ControlFS opens, a full-screen **welcome** walks you through six steps: welcome; how the controller works (the real buttons of the controller in use — Open, Back, Actions, Menu, Search, Mark, L1/R1 for the top bar, L2/R2 for tabs, R3 for the view; press any button on your controller and the prompts switch to it); **dark or light?** (Escuro, Claro or Automático, which follows Windows; the screen changes at once so you can see it); the basics, applied at once (confirm with the bottom or right button, button labels, list/grid, Fluidez); privacy (everything stays on this PC; the update check is optional; the phone link is off until you open it); and **Quer fazer o tutorial guiado?** — **Começar tutorial** or **Agora não**.

| Control | Welcome |
|---|---|
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 @@ -82,7 +82,7 @@ Confirmações importantes (excluir, substituir, desfazer) são respondidas no P

## Primeira vez: boas-vindas e tutorial guiado

Na primeira vez que o ControlFS abre, as **boas-vindas** em tela cheia mostram cinco passos: boas-vindas; como o controle funciona (os botões de verdade do controle em uso — Abrir, Voltar, Ações, Menu, Buscar, Marcar, L1/R1 da barra superior, L2/R2 das abas, R3 da exibição; aperte qualquer botão do seu controle e as legendas mudam para ele); o básico, que vale na hora (confirmar com o botão inferior ou direito, legendas, tema, lista/grade, Fluidez); privacidade (tudo fica neste PC; procurar atualizações é opcional; o celular como controle fica desligado até você abrir); e **Quer fazer o tutorial guiado?** — **Começar tutorial** ou **Agora não**.
Na primeira vez que o ControlFS abre, as **boas-vindas** em tela cheia mostram seis passos: boas-vindas; como o controle funciona (os botões de verdade do controle em uso — Abrir, Voltar, Ações, Menu, Buscar, Marcar, L1/R1 da barra superior, L2/R2 das abas, R3 da exibição; aperte qualquer botão do seu controle e as legendas mudam para ele); **escuro ou claro?** (Escuro, Claro ou Automático, que segue o Windows; a tela muda na hora para você ver); o básico, que vale na hora (confirmar com o botão inferior ou direito, legendas, lista/grade, Fluidez); privacidade (tudo fica neste PC; procurar atualizações é opcional; o celular como controle fica desligado até você abrir); e **Quer fazer o tutorial guiado?** — **Começar tutorial** ou **Agora não**.

| Controle | Boas-vindas |
|---|---|
Expand Down
2 changes: 1 addition & 1 deletion docs/input-and-focus.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ com foco em "Extrair para \"nome\"". O rótulo usa a extensão (rápido); a aç
analógico) move o foco entre as opções do passo dando a volta; Sul escolhe ou troca o ajuste em foco (o foco fica
nele); Leste e L1 voltam um passo (no primeiro, nada); R1 avança; Start/Menu pula tudo sem confirmação. Trocar
"Confirmar com" vale na hora: o próximo Confirmar já é o outro botão. Mouse: clique numa opção (`PointerChooseModalOption`)
ou numa legenda (a mesma ação). Narrador: "Boas-vindas, passo N de 5: título. texto" e a opção em foco com a descrição.
ou numa legenda (a mesma ação). Narrador: "Boas-vindas, passo N de 6: título. texto" e a opção em foco com a descrição.
- Só aparecem na primeira execução da janela real (`AppController.OfferOnboarding` e `AppSettings.OnboardingCompleted`
false, gravado pelo `JsonSettingsStore` ao criar as preferências). Preferências de versões anteriores (sem o campo) contam
como vistas; testes, `--render-screens` e `--no-onboarding` nunca as mostram sem pedir.
Expand Down
2 changes: 2 additions & 0 deletions src/ControlFS.App/Diagnostics/ScreenRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,8 @@ private static async Task CaptureOnboardingAsync(AppController app, MainWindow w
app.Handle(InputAction.NextRegion);
await CaptureAsync(stage, target, dir, "o2-onboarding-controls", window);
app.Handle(InputAction.NextRegion);
await CaptureAsync(stage, target, dir, "o2b-onboarding-theme", window);
app.Handle(InputAction.NextRegion);
app.Handle(InputAction.NavigateDown); // foco em "Legendas", com a descrição
await CaptureAsync(stage, target, dir, "o3-onboarding-basics", window);
app.Handle(InputAction.NextRegion);
Expand Down
1 change: 1 addition & 0 deletions src/ControlFS.App/Views/OnboardingView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ private static StackPanel OnboardingIntro(OnboardingModal modal, bool compact)
var icon = modal.Step switch
{
OnboardingStep.Controls => ActionIcon.Controller,
OnboardingStep.Theme => ActionIcon.Theme,
OnboardingStep.Basics => ActionIcon.Settings,
OnboardingStep.Privacy => ActionIcon.Password,
OnboardingStep.Tutorial => ActionIcon.Tutorial,
Expand Down
27 changes: 20 additions & 7 deletions src/ControlFS.Application/AppController.Onboarding.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using ControlFS.Core.Appearance;
using ControlFS.Application.State;
using ControlFS.Core.Actions;
using ControlFS.Core.Contracts;
Expand Down Expand Up @@ -118,12 +119,17 @@ private void LoadOnboardingStep(OnboardingModal modal, int index, bool keepFocus
modal.ControlLegend = [];
void Reload() => LoadOnboardingStep(modal, modal.StepIndex, keepFocus: true);
var next = new OnboardingOption("Continuar", () => NextOnboardingStep(modal), ActionIcon.Resume);
OnboardingOption ThemeOption(string label, ThemeMode mode, string detail) => new(label, () =>
{
SetTheme(mode); // vale na hora: a janela recebe SettingsChanged e se redesenha
Reload();
}, Settings.Theme == mode ? ActionIcon.RadioOn : ActionIcon.RadioOff, Detail: detail);
switch (modal.Step)
{
case OnboardingStep.Welcome:
modal.StepTitle = "Boas-vindas ao ControlFS";
modal.StepBody = "Um gerenciador de arquivos feito para o controle: navegue, abra, copie e extraia do sofá, sem mouse nem teclado. "
+ "São cinco passos curtos; Menu pula tudo quando quiser.";
+ "São seis passos curtos; Menu pula tudo quando quiser.";
modal.Options = [new("Começar", () => NextOnboardingStep(modal), ActionIcon.Resume)];
break;
case OnboardingStep.Controls:
Expand All @@ -148,6 +154,18 @@ private void LoadOnboardingStep(OnboardingModal modal, int index, bool keepFocus
];
modal.Options = [next];
break;
case OnboardingStep.Theme:
modal.StepTitle = "Escuro ou claro?";
modal.StepBody = "A tela muda na hora para você ver. Dá para trocar depois em Menu → Configurações → Tema.";
modal.Options =
[
ThemeOption("Escuro", ThemeMode.Dark, "Fundo escuro, bom para ambientes com pouca luz e para jogar à noite."),
ThemeOption("Claro", ThemeMode.Light, "Fundo claro, bom com a sala iluminada ou no sol."),
ThemeOption("Automático (segue o Windows)", ThemeMode.System, "Acompanha o modo claro ou escuro que o Windows está usando."),
next,
];
if (!keepFocus) focus = Math.Max(0, (int)(Settings.Theme switch { ThemeMode.Dark => 0, ThemeMode.Light => 1, _ => 2 }));
break;
case OnboardingStep.Basics:
var south = Settings.Convention == ConfirmBackConvention.SouthConfirms;
modal.StepTitle = "O básico, do seu jeito";
Expand All @@ -164,11 +182,6 @@ private void LoadOnboardingStep(OnboardingModal modal, int index, bool keepFocus
UpdateSettings(s => s with { LabelStyle = (ButtonLabelStyle)(((int)s.LabelStyle + 1) % 5) });
Reload();
}, ActionIcon.Labels, LabelStyleName(Settings.LabelStyle), "Automáticas seguem o controle em uso (Xbox, PlayStation, Nintendo)."),
new($"Tema: {ThemeName(Settings.Theme)}", () =>
{
CycleTheme();
Reload();
}, ActionIcon.Theme, ThemeName(Settings.Theme), "Automático segue o modo claro ou escuro do Windows."),
new($"Exibição: {ViewName(Settings.View)}", () =>
{
ToggleView();
Expand Down Expand Up @@ -209,7 +222,7 @@ private void LoadOnboardingStep(OnboardingModal modal, int index, bool keepFocus
];
break;
}
modal.FocusIndex = keepFocus ? Math.Clamp(focus, 0, Math.Max(0, modal.Options.Count - 1)) : 0;
modal.FocusIndex = keepFocus || modal.Step == OnboardingStep.Theme ? Math.Clamp(focus, 0, Math.Max(0, modal.Options.Count - 1)) : 0;
}

/// <summary>Ajuda (Menu → Ajuda): tutorial guiado, boas-vindas e Sobre.</summary>
Expand Down
1 change: 1 addition & 0 deletions src/ControlFS.Application/State/Onboarding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum OnboardingStep
{
Welcome,
Controls,
Theme,
Basics,
Privacy,
Tutorial,
Expand Down
26 changes: 19 additions & 7 deletions tests/ControlFS.UnitTests/Application/OnboardingJourneyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void First_run_walks_the_steps_with_the_controller_applies_settings_at_on
var onboarding = Assert.IsType<OnboardingModal>(app.TopModal);
Assert.Equal(OnboardingStep.Welcome, onboarding.Step);
Assert.Contains(app.Hints, h => h is { Action: InputAction.OpenAppMenu, Label: "Pular" });
Assert.StartsWith("Boas-vindas, passo 1 de 5", app.DescribeFocus().Context, StringComparison.Ordinal); // Narrador
Assert.StartsWith("Boas-vindas, passo 1 de 6", app.DescribeFocus().Context, StringComparison.Ordinal); // Narrador

d.Press(InputAction.Confirm); // Começar
Assert.Equal(OnboardingStep.Controls, onboarding.Step);
Expand All @@ -46,17 +46,29 @@ public void First_run_walks_the_steps_with_the_controller_applies_settings_at_on
Assert.Equal(OnboardingStep.Welcome, onboarding.Step);
d.Press(InputAction.NextRegion);
d.Press(InputAction.Confirm); // Continuar
Assert.Equal(OnboardingStep.Basics, onboarding.Step);
Assert.Equal(OnboardingStep.Theme, onboarding.Step);

// Ajuste com efeito imediato: o tema troca na hora, a janela recebe e o foco continua no ajuste.
d.Press(InputAction.NavigateDown);
d.Press(InputAction.NavigateDown);
Assert.StartsWith("Tema:", onboarding.FocusedOption!.Label, StringComparison.Ordinal);
// Escolha de tema: automático de início (foco nele); Claro e Escuro valem na hora, a janela recebe e o passo continua.
Assert.Equal("Automático (segue o Windows)", onboarding.FocusedOption!.Label);
d.Press(InputAction.NavigateUp);
d.Press(InputAction.NavigateUp);
Assert.Equal("Escuro", onboarding.FocusedOption!.Label);
d.Press(InputAction.Confirm);
Assert.Equal(ThemeMode.Dark, app.Settings.Theme);
Assert.Equal(ThemeMode.Dark, themes[^1]);
Assert.Equal("Tema: escuro", onboarding.FocusedOption!.Label);
Assert.Equal(ActionIcon.RadioOn, onboarding.FocusedOption!.Icon);
Assert.Equal(ActionIcon.RadioOff, onboarding.Options[1].Icon);
Assert.Same(onboarding, app.TopModal);
d.Press(InputAction.NavigateDown);
d.Press(InputAction.Confirm); // Claro
Assert.Equal(ThemeMode.Light, app.Settings.Theme);
d.Press(InputAction.NavigateUp);
d.Press(InputAction.Confirm); // Escuro de novo
Assert.Equal(ThemeMode.Dark, app.Settings.Theme);

d.Press(InputAction.NextRegion);
Assert.Equal(OnboardingStep.Basics, onboarding.Step);
Assert.DoesNotContain(onboarding.Options, o => o.Label.StartsWith("Tema:", StringComparison.Ordinal)); // o tema tem passo próprio

d.Press(InputAction.NextRegion); // privacidade
d.Press(InputAction.NextRegion); // convite para o tutorial
Expand Down
2 changes: 1 addition & 1 deletion tests/ControlFS.UnitTests/Application/PromoJourneyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void Shows_once_after_the_welcome_and_the_tutorial_opens_links_only_on_co

// Primeiro as boas-vindas; a tela da equipe não aparece por cima delas.
Assert.IsType<OnboardingModal>(app.TopModal);
for (var i = 0; i < 4; i++) d.Press(InputAction.NextRegion);
for (var i = 0; i < 5; i++) d.Press(InputAction.NextRegion);
Assert.Equal(OnboardingStep.Tutorial, ((OnboardingModal)app.TopModal!).Step);
d.Press(InputAction.Confirm); // "Começar tutorial": a tela da equipe espera o tutorial terminar
Assert.Null(app.TopModal);
Expand Down
Loading