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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
Тихий** (тишина). Поменялись местами и рисунок, и анимация (покачивание листа ↔ мерцание звёзд),
и акцент ячейки в панели (зелёный ↔ сизый) — в панели, OSD и трее. Первопричина была в
двусмысленной строке спецификации иконок «лист (эко/тихий)» — она переписана однозначно (XIC-14).
- **Окно настроек не переживало смену разрешения или масштаба экрана**: размеры шрифтов
оставались от старого DPI и расходились с разметкой — заметнее всего обрезался пояснительный
текст под названиями опций (высота карточки считается под него один раз при постройке).
Частый сценарий — удалённое подключение, которое подгоняет разрешение под клиента. Теперь окно
пересобирается под новый масштаб: ловим и `WM_DPICHANGED` (как это давно делают панель и
«Монитор»), и смену разрешения через `DisplaySettingsChanged`. Заодно шрифт окна переведён на
`ScaledFonts` — раньше он один задавался в пунктах мимо общего масштабирования (XIC-15).

## [0.7.0] — 2026-07-28

Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ WMI-событий). `Program.cs`: single-instance mutex → DI-контейне
`FlyoutForm` + `FlyoutPalette` (общая база флайаутов: borderless tool-window, Region, Esc;
палитра — единственный источник тёмных цветов флайаутов), `FormChrome` (DWM-тёмный
заголовок + WM_SETREDRAW), `ModeUi` (режим → ключ локализации / вид OSD / акцент),
`SettingsForm` (хост окна настроек: хром, навигация, пересборка на каждый показ),
`SettingsForm` (хост окна настроек: хром, навигация, пересборка на каждый показ — а также на
смену темы, DPI и разрешения экрана: шрифты и `Sc()` снимаются в момент постройки),
`SettingsActions` (сумка колбэков в контроллер; поля `required` — забытый mount ловится
компилятором, CS9035), `ToggleSwitch` (рисованный Win11-тумблер),
`ScaledFonts` (**шрифты только отсюда** — пиксельные под DeviceDpi, иначе после смены
Expand Down
2 changes: 1 addition & 1 deletion src/Ui/Settings/AboutTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public AboutTab(SettingsToolkit ui) : base(ui)
ico.Region = new Region(Draw.Rounded(new RectangleF(0, 0, ui.Sc(60), ui.Sc(60)), ui.Sc(12)));
hero.Controls.Add(ico);
var name = new Label { Text = "XiControl", Font = ui.NameFont, AutoSize = true, ForeColor = ui.T.Text, BackColor = Color.Transparent, Location = new Point(ui.Sc(74), ui.Sc(10)) };
var ver = new Label { Text = $"{Loc.T("settings.version")} {AppVersion()} · GPLv3", Tag = "dim", AutoSize = true, ForeColor = ui.T.Text2, BackColor = Color.Transparent, Location = new Point(ui.Sc(74), ui.Sc(40)) };
var ver = new Label { Text = $"{Loc.T("settings.version")} {AppVersion()} · GPLv3", Tag = "dim", AutoSize = true, ForeColor = ui.T.Text2, BackColor = Color.Transparent, Font = ui.NoteFont, Location = new Point(ui.Sc(74), ui.Sc(40)) };
hero.Controls.Add(name); hero.Controls.Add(ver);
Controls.Add(hero);

Expand Down
5 changes: 3 additions & 2 deletions src/Ui/Settings/SettingsToolkit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,9 @@ public void AddKv(Panel p, string key, string val)
{
var row = new Panel { Width = RowW, Height = Sc(34), BackColor = T.WinBg, Margin = new Padding(0) };
row.Paint += (_, e) => { using var pen = new Pen(T.Border); e.Graphics.DrawLine(pen, 0, row.Height - 1, RowW, row.Height - 1); };
row.Controls.Add(new Label { Text = Loc.T(key), Tag = "dim", AutoSize = true, ForeColor = T.Text2, BackColor = Color.Transparent, Location = new Point(Sc(2), Sc(9)) });
row.Controls.Add(new Label { Text = val, AutoSize = true, ForeColor = T.Text, BackColor = Color.Transparent, Location = new Point(Sc(180), Sc(9)) });
// Font задаём явно: наследованный ambient не пересчитывался бы вместе с Sc() при смене DPI
row.Controls.Add(new Label { Text = Loc.T(key), Tag = "dim", AutoSize = true, ForeColor = T.Text2, BackColor = Color.Transparent, Font = NoteFont, Location = new Point(Sc(2), Sc(9)) });
row.Controls.Add(new Label { Text = val, AutoSize = true, ForeColor = T.Text, BackColor = Color.Transparent, Font = NoteFont, Location = new Point(Sc(180), Sc(9)) });
p.Controls.Add(row);
}

Expand Down
75 changes: 67 additions & 8 deletions src/Ui/SettingsForm.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using XiControl.Config;
using Microsoft.Win32;
using XiControl.Config;
using XiControl.Localization;
using XiControl.SystemIntegration;
using XiControl.Ui.Settings;

namespace XiControl.Ui;
Expand All @@ -26,6 +28,10 @@ public sealed class SettingsForm : Form
private readonly List<Panel?> _panes = [];
private int _tab;

// смена разрешения/масштаба сыплется пачкой событий — гасим дребезг и пересобираем один раз
// (шов IAppTimer тут не нужен: окно юнит-тестами не покрывается)
private readonly UiTimer _rescale = new() { Interval = 250 };

// MifsClient сюда сознательно не передаётся: окно железо не трогает — все «умные»
// операции идут через колбэки SettingsActions в TrayApp/AppController.
public SettingsForm(AppConfig cfg, SettingsActions act)
Expand All @@ -40,16 +46,33 @@ public SettingsForm(AppConfig cfg, SettingsActions act)
StartPosition = FormStartPosition.Manual; // позицию считаем сами в Popup (всегда центр)
KeyPreview = true;
DoubleBuffered = true;
Font = new Font("Segoe UI", 9f);
// масштабируем себя сами (Sc + ScaledFonts от DeviceDpi) — иначе WinForms домасштабирует
// поверх наших размеров, и геометрия разъедется вдвойне
AutoScaleMode = AutoScaleMode.None;
try { Icon = Icon.ExtractAssociatedIcon(Environment.ProcessPath!); } catch { /* иконки нет — не критично */ }

_ = Handle; // форсируем хэндл (нужен DeviceDpi)
_ui = new SettingsToolkit(this, SettingsTheme.Load());
ClientSize = new Size(_ui.Sc(824), _ui.Sc(700));

// разрешение могло смениться и без смены DPI (удалённый рабочий стол подгоняет его
// под клиента) — WM_DPICHANGED тогда не приходит, а пересобраться всё равно надо
_rescale.Tick += () => { _rescale.Stop(); if (Visible) Rescale(); };
SystemEvents.DisplaySettingsChanged += OnDisplaySettingsChanged;

BuildAll();
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
SystemEvents.DisplaySettingsChanged -= OnDisplaySettingsChanged;
_rescale.Dispose();
}
base.Dispose(disposing);
}

/// <summary>
/// Открыть окно. Содержимое пересобирается при каждом открытии: настройки могли смениться
/// из трея/панели, пока окно было спрятано (авто-герцовка и т.п.), тема — тоже. Пересборка
Expand All @@ -58,18 +81,50 @@ public SettingsForm(AppConfig cfg, SettingsActions act)
public void Popup()
{
BuildAll();
// высота — чтобы вкладки влезали без прокрутки, но не выше рабочей области экрана;
// окно каждый раз открывается по центру экрана с курсором
var wa = Screen.FromPoint(Cursor.Position).WorkingArea;
int h = Math.Min(_ui.Sc(700), wa.Height - _ui.Sc(80));
if (ClientSize.Height != h) ClientSize = new Size(_ui.Sc(824), h);
Location = new Point(wa.Left + (wa.Width - Width) / 2, wa.Top + (wa.Height - Height) / 2);
FitToScreen(Screen.FromPoint(Cursor.Position)); // открываемся по центру экрана с курсором
Show();
if (WindowState == FormWindowState.Minimized) WindowState = FormWindowState.Normal;
Activate();
BringToFront();
}

/// <summary>Разместить окно по центру экрана: высота — чтобы вкладки влезали без прокрутки,
/// но не выше рабочей области (она меняется вместе с разрешением).</summary>
private void FitToScreen(Screen screen)
{
var wa = screen.WorkingArea;
var size = new Size(_ui.Sc(824), Math.Min(_ui.Sc(700), wa.Height - _ui.Sc(80)));
if (ClientSize != size) ClientSize = size;
Location = new Point(wa.Left + (wa.Width - Width) / 2, wa.Top + (wa.Height - Height) / 2);
}

/// <summary>Пересобрать окно под текущий DPI/разрешение. Sc() и шрифты у нас пиксельные и
/// снимаются в момент постройки: без пересборки текст остаётся в старом масштабе и расходится
/// с разметкой — заметнее всего на пояснениях, высота карточки под них считается один раз
/// по DescFont (SettingsToolkit.AddRow). Механика та же, что у смены темы: пересборка дешёвая.</summary>
private void Rescale()
{
BuildAll();
FitToScreen(Screen.FromControl(this));
}

/// <summary>Масштаб сменился или окно переехало на монитор с другим DPI — пересобрать
/// (для флайаутов это же делает FlyoutForm.OnDpiRescaled).</summary>
protected override void OnDpiChanged(DpiChangedEventArgs e)
{
base.OnDpiChanged(e);
Rescale();
}

// Сменилось разрешение/раскладка мониторов. Событие приходит из системного потока и пачкой —
// маршалим в UI и гасим дребезг. Спрятанное окно не трогаем: его пересоберёт следующий Popup.
private void OnDisplaySettingsChanged(object? sender, EventArgs e)
{
if (!IsHandleCreated || IsDisposed || !Visible) return;
try { BeginInvoke(new Action(() => { _rescale.Stop(); _rescale.Start(); })); }
catch (ObjectDisposedException) { /* окно закрылось, пока событие шло к нам */ }
}

protected override void OnFormClosing(FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
Expand Down Expand Up @@ -139,6 +194,10 @@ private void BuildAllCore()
_panes.Clear();
_factories.Clear();

// ambient-шрифт формы — тоже из ScaledFonts: контролы без явного Font наследуют его,
// а пунктовый шрифт при смене DPI разъезжается с нашими пиксельными
Font = _ui.NoteFont;

_host = new Panel { Dock = DockStyle.Fill, BackColor = _ui.T.WinBg, Tag = "host" };
Controls.Add(_host);

Expand Down
Loading