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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
на смену питания — у сети и батареи режим свой. Пока служба Xiaomi запущена, переключатель
неактивен: она делает то же сама. Выключили — режим питания возвращается в сбалансированный,
ручной выбор в параметрах Windows не трогаем. По умолчанию выключено (XIC-96).
- **Прозрачность уведомлений.** Настройки → Уведомления → «Прозрачность»: от «Нет» до 80 %
шагом 10. Выбор сразу показывает пример, затухание идёт от выбранного уровня так же плавно.
По умолчанию уведомления непрозрачные, как раньше. Просили в
[#90](https://github.com/Oksion/XiControl/issues/90) (XIC-98).

### Исправлено
- **Вебхук «заряд дошёл до порога» больше не обрывает «В дорогу».** Во время разового заряда до
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ hidden if refresh-rate control is off), Touchpad (bottom dead zone, edge sliders
strength and click force, force press), Performance (mode visibility,
separately for AC and battery, with a switch above the list; startup mode, power profiles),
Keys (remapping), Notifications (where popups appear, how long
they stay, which lock-key popups to show), HTTP API (port, token, permissions), Drivers (check with Xiaomi on a button) and About
they stay, transparency, which lock-key popups to show), HTTP API (port, token, permissions), Drivers (check with Xiaomi on a button) and About
(version, model with the board code, BIOS, serial number — masked by default, click to reveal).
The quick toggles (charge, "travel", owl, refresh rate, Monitor,
mode) stay in the tray menu and the panel.
Expand Down
2 changes: 1 addition & 1 deletion README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ dotnet publish src/XiControl.csproj -c Release -r win-x64 --self-contained -p:Pu
Тачпад (мёртвая зона у нижнего края, края-ползунки, сила вибрации и усилие нажатия, сильное нажатие),
Производительность (видимость режимов — отдельно для сети и для батареи, переключатель наверху
списка; режим при старте, профили питания), Клавиши
(переназначение), Уведомления (положение подсказок на экране, сколько висят, какие из
(переназначение), Уведомления (положение подсказок на экране, сколько висят, прозрачность, какие из
клавиш-фиксаторов показывать), HTTP API (порт, токен, разрешения), Драйверы (проверка у Xiaomi по кнопке) и О программе (версия, модель с кодом
платы, BIOS, серийный номер — по умолчанию замаскирован, клик раскрывает). Быстрые
переключатели (заряд, «В дорогу», сова, герцовка, «Монитор», режим) остаются в меню трея и панели.
Expand Down
4 changes: 4 additions & 0 deletions src/Config/AppConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ public sealed class AppConfig
/// <summary>Сколько OSD висит до затухания. Дефолт 2800 мс («Авто»-режим — на 600 мс дольше).</summary>
public int OsdDurationMs { get; set; } = 2800;

/// <summary>Прозрачность OSD, % (XIC-98): 0 — непрозрачная карточка (по умолчанию), до 80.
/// Правленное руками зажимается в 0..80 при применении (<see cref="Ui.OsdOpacity"/>).</summary>
public int OsdTransparencyPercent { get; set; } = 0;

/// <summary>
/// Где на экране показывать OSD. <see cref="Ui.OsdPosition.Center"/> — как было всегда:
/// по центру ширины, чуть ниже середины по высоте. Остальные восемь позиций прижимают
Expand Down
3 changes: 3 additions & 0 deletions src/Localization/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@
"settings.osd.duration": "How long it stays",
"settings.osd.duration.desc": "Time before the popup starts to fade.",
"settings.osd.duration.sec": "{0:0.#} s",
"settings.osd.transparency": "Transparency",
"settings.osd.transparency.desc": "How much of what is underneath shows through the notification. Picking a value shows an example right away.",
"settings.osd.transparency.none": "None",
"settings.osd.preview": "Try it",
"settings.osd.preview.desc": "Show a popup right now — position and duration as they will be.",
"settings.osd.preview.btn": "Show example",
Expand Down
3 changes: 3 additions & 0 deletions src/Localization/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@
"settings.osd.duration": "Сколько висит",
"settings.osd.duration.desc": "Время до того, как подсказка начнёт гаснуть.",
"settings.osd.duration.sec": "{0:0.#} с",
"settings.osd.transparency": "Прозрачность",
"settings.osd.transparency.desc": "Насколько сквозь уведомление просвечивает то, что под ним. При выборе сразу показывается пример.",
"settings.osd.transparency.none": "Нет",
"settings.osd.preview": "Проверить",
"settings.osd.preview.desc": "Показать подсказку прямо сейчас — видно положение и длительность.",
"settings.osd.preview.btn": "Показать пример",
Expand Down
3 changes: 3 additions & 0 deletions src/Localization/lang/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@
"settings.osd.duration": "显示时长",
"settings.osd.duration.desc": "提示开始淡出前停留的时间。",
"settings.osd.duration.sec": "{0:0.#} 秒",
"settings.osd.transparency": "透明度",
"settings.osd.transparency.desc": "通知下方的内容透出多少。选择后会立即显示示例。",
"settings.osd.transparency.none": "无",
"settings.osd.preview": "试一下",
"settings.osd.preview.desc": "立即显示一次提示——位置和时长与实际一致。",
"settings.osd.preview.btn": "显示示例",
Expand Down
8 changes: 6 additions & 2 deletions src/Ui/OsdForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public sealed class OsdForm : Form
/// <summary>Где показывать карточку (сетка 3×3). Настраивается на вкладке «Уведомления»,
/// TrayApp выставляет её на старте и после каждой правки.</summary>
public OsdPosition Position { get; set; } = OsdPosition.Center;

// Прозрачность карточки (XIC-98): с какой непрозрачности показываем и с какой гасим.
private double _baseOpacity = 1.0;
public void SetTransparency(int percent) => _baseOpacity = OsdOpacity.Base(percent);
private int DisplayMsAuto => _displayMs + 600;

private readonly System.Windows.Forms.Timer _display = new() { Interval = 2800 };
Expand Down Expand Up @@ -69,7 +73,7 @@ public OsdForm()
_display.Tick += (_, _) => { _display.Stop(); _fade.Start(); };
_fade.Tick += (_, _) =>
{
Opacity -= 0.08;
Opacity -= OsdOpacity.FadeStep(_baseOpacity); // от базовой — те же ~200 мс, без скачка
if (Opacity <= 0.01) { _fade.Stop(); _gauge.Stop(); Hide(); }
};
_gauge.Tick += (_, _) => { _gaugeT += 0.03f; Invalidate(); };
Expand Down Expand Up @@ -135,7 +139,7 @@ public void Flash(OsdKind kind, string title, string? subtitle = null, ChargeBad
Location = OsdPlacement.Locate(wa, new Size(w, h), Position, Sc(OsdPlacement.Margin));

_display.Stop(); _fade.Stop();
Opacity = 1.0;
Opacity = _baseOpacity;
_gauge.Stop();
// для «Авто» показываем дольше — стрелка успевает плавно «настроиться»
_display.Interval = kind == OsdKind.Auto ? DisplayMsAuto : _displayMs;
Expand Down
23 changes: 23 additions & 0 deletions src/Ui/OsdPlacement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,26 @@ public static Screen TargetScreen()
[DllImport("user32.dll")]
private static extern nint GetForegroundWindow();
}

/// <summary>
/// Прозрачность OSD (XIC-98, просили в GitHub #90). Чистая математика — под тестами; сама форма
/// только применяет. По умолчанию 0 — карточка непрозрачна, как всегда.
/// </summary>
public static class OsdOpacity
{
/// <summary>Потолок: при 80% карточка видна хотя бы на пятую часть.</summary>
public const int MaxPercent = 80;

/// <summary>Шаг затухания непрозрачной карточки за тик (16 мс) — исторический, ~200 мс до нуля.</summary>
public const double FullFadeStep = 0.08;

/// <summary>Процент из config.json — его правят руками, поэтому зажимаем в 0..80.</summary>
public static int Clamp(int percent) => Math.Clamp(percent, 0, MaxPercent);

/// <summary>С какой непрозрачности карточка показывается: 40% прозрачности → 0.6.</summary>
public static double Base(int percent) => 1.0 - Clamp(percent) / 100.0;

/// <summary>Шаг затухания от базовой непрозрачности: пропорционально меньше, чтобы
/// полупрозрачная карточка гасла те же ~200 мс, а не быстрее непрозрачной.</summary>
public static double FadeStep(double baseOpacity) => FullFadeStep * baseOpacity;
}
14 changes: 14 additions & 0 deletions src/Ui/Settings/OsdTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public OsdTab(SettingsToolkit ui, AppConfig cfg, SettingsActions act, Action reb
ui.AddRow(this, "settings.osd.duration", "settings.osd.duration.desc",
DurationCombo(ui, cfg.OsdDurationMs, act.SetOsdDuration));

ui.AddRow(this, "settings.osd.transparency", "settings.osd.transparency.desc",
TransparencyCombo(ui, cfg.OsdTransparencyPercent, act.SetOsdTransparency));

// ширину меряем сами: AutoSize у кнопки срабатывает позже, чем карточка считает
// раскладку, и кнопка выходит микроскопической (та же грабля, что в DisplayTab)
var preview = ui.LinkButton("settings.osd.preview.btn", act.PreviewOsd);
Expand All @@ -53,6 +56,17 @@ private void AddLock(SettingsToolkit ui, SettingsActions act, AppConfig cfg, Loc
=> ui.AddRow(this, titleKey, titleKey + ".desc",
ui.Toggle(!cfg.HiddenLockOsd.Contains(key), on => act.SetLockOsd(key, on)));

/// <summary>Прозрачность списком, как остальные проценты в настройках (XIC-98): «Нет», 10…80%.
/// Значение из config.json вне шага (правили руками) не теряем — добавляем его, уже зажатое.</summary>
private static ComboBox TransparencyCombo(SettingsToolkit ui, int current, Action<int> apply)
{
current = OsdOpacity.Clamp(current);
int[] steps = [.. Enumerable.Range(0, OsdOpacity.MaxPercent / 10 + 1).Select(i => i * 10)];
int[] items = steps.Contains(current) ? steps : [.. steps.Append(current).Order()];
string[] names = [.. items.Select(p => p == 0 ? Loc.T("settings.osd.transparency.none") : $"{p}%")];
return ui.Combo(names, Array.IndexOf(items, current), i => apply(items[i]), ui.Sc(150));
}

/// <summary>Длительность в секундах — человек думает в них, а не в миллисекундах.
/// Значение из config.json вне списка (правили руками) не теряется: добавляем его как есть.</summary>
private static ComboBox DurationCombo(SettingsToolkit ui, int current, Action<int> apply)
Expand Down
1 change: 1 addition & 0 deletions src/Ui/SettingsActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public sealed class SettingsActions
public required Action TrayMetricApplied; // индикатор в трее (XIC-35): вкл/выкл/метрика/период изменились
public required Action<OsdPosition> SetOsdPosition; // где показывать OSD (сетка 3×3)
public required Action<int> SetOsdDuration; // сколько висит, мс
public required Action<int> SetOsdTransparency; // прозрачность OSD, % (0 — нет; XIC-98)
public required Action<LockOsd, bool> SetLockOsd; // показывать ли плашку фиксатора
public required Action PreviewOsd; // «Показать пример» — настоящий OSD настоящим путём
}
3 changes: 3 additions & 0 deletions src/Ui/TrayApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,8 @@ private void OpenSettings()
TrayMetricApplied = TrayMetricApplied,
SetOsdPosition = p => { _cfg.OsdPosition = p; _cfg.Save(); OsdApplied(); },
SetOsdDuration = ms => { _cfg.OsdDurationMs = ms; _cfg.Save(); OsdApplied(); },
// прозрачность сразу видна на примере — выбирают глазами (XIC-98)
SetOsdTransparency = pct => { _cfg.OsdTransparencyPercent = OsdOpacity.Clamp(pct); _cfg.Save(); PreviewOsd(); },
SetLockOsd = SetLockOsd,
PreviewOsd = PreviewOsd,
};
Expand Down Expand Up @@ -894,6 +896,7 @@ private void OsdApplied()
{
_osd.DurationMs = _cfg.OsdDurationMs;
_osd.Position = _cfg.OsdPosition;
_osd.SetTransparency(_cfg.OsdTransparencyPercent);
}

// Показывать ли плашку этого фиксатора. Храним СКРЫТЫЕ (как HiddenModes у режимов):
Expand Down
40 changes: 40 additions & 0 deletions tests/XiControl.Tests/OsdOpacityTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using FluentAssertions;
using XiControl.Ui;
using Xunit;

namespace XiControl.Tests;

/// <summary>Прозрачность OSD (XIC-98): зажим ручной правки, базовая непрозрачность и затухание.</summary>
public class OsdOpacityTests
{
[Theory]
[InlineData(-10, 0)]
[InlineData(0, 0)]
[InlineData(40, 40)]
[InlineData(80, 80)]
[InlineData(95, 80)] // правили config.json руками — карточка не должна исчезнуть
public void Процент_зажимается_в_0_80(int raw, int expected) =>
OsdOpacity.Clamp(raw).Should().Be(expected);

[Fact]
public void По_умолчанию_карточка_непрозрачна() =>
OsdOpacity.Base(new Config.AppConfig().OsdTransparencyPercent).Should().Be(1.0);

[Theory]
[InlineData(40, 0.6)]
[InlineData(80, 0.2)]
[InlineData(200, 0.2)] // выше потолка — как 80%
public void Базовая_непрозрачность(int percent, double expected) =>
OsdOpacity.Base(percent).Should().BeApproximately(expected, 1e-9);

[Theory]
[InlineData(0)]
[InlineData(40)]
[InlineData(80)]
public void Затухание_длится_одинаково_при_любой_прозрачности(int percent)
{
// сколько тиков от базовой непрозрачности до нуля — столько же, сколько у непрозрачной
double b = OsdOpacity.Base(percent);
(b / OsdOpacity.FadeStep(b)).Should().BeApproximately(1.0 / OsdOpacity.FullFadeStep, 1e-9);
}
}
Loading