fix(ui): Scale every hardcoded pixel size to the actual display DPI
At Windows display scaling above 100% (125%/150%, common on laptops), fonts grew with the scale but hardcoded pixel sizes did not: the Log/Script/Stop buttons on each session were truncated, the AI Chat toolbar button lost its bottom edge at 150%, and absolute-coordinate dialogs (Quick Connect / New Connection) could clip or overlap their fields. Root cause: MainForm's AutoScaleMode.Dpi scales only the controls that exist when the form initializes. Almost all of this app's UI is created at runtime (SessionPage on open, views built in constructors after InitializeComponent), so nothing was ever scaled — verified at 125%: the 250px sidebar stayed 250px. Fix, in order of preference: - New Theme.Dpi(Control, px) extension (px * DeviceDpi/96, same approach as the existing ActivityRail.ItemSize) applied to every hardcoded dimension: sidebar, toolbars, tab strips, Settings inputs and grids, PDU status grid, AI chat pane, Ctrl+F search bar, DarkScrollBar width. - Buttons whose text changes at runtime (Log <-> Logging, Log All <-> Logging All) now size from measured text width (SessionPage.BarButtonWidth) or plain AutoSize, so they can never truncate at any scale. - Multi-line instruction labels switch to AutoSize instead of fixed frames. - Absolute-coordinate dialogs get DarkDialog.ApplyDpiScale(): one recursive Control.Scale(DeviceDpi/96) at the end of each constructor. - MainForm moves to AutoScaleMode.None with ClientSize/MinimumSize scaled manually — we own scaling explicitly, so a future .NET change to auto-scale semantics cannot double-scale us. At 100% the conversion is the identity, so existing setups are unchanged. Known limitation: changing the system scale while running needs an app restart (no WM_DPICHANGED re-layout). Also rides along in AboutView: the v0.7.2 changelog entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKF7C75bhA4ArQhyMpnsxU
This commit is contained in:
@@ -20,7 +20,7 @@ public sealed class AboutView : UserControl
|
||||
// ── Main layout: left fixed + right scrollable ──
|
||||
var left = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Left, Width = 380, FlowDirection = FlowDirection.TopDown,
|
||||
Dock = DockStyle.Left, Width = this.Dpi(380), FlowDirection = FlowDirection.TopDown,
|
||||
WrapContents = false, AutoScroll = true, Padding = new Padding(20),
|
||||
BackColor = Theme.WorkspaceBack
|
||||
};
|
||||
@@ -105,10 +105,10 @@ public sealed class AboutView : UserControl
|
||||
{
|
||||
FlowDirection = FlowDirection.TopDown, WrapContents = false,
|
||||
AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(width, height),
|
||||
Margin = new Padding(0, 0, 0, 12),
|
||||
BackColor = Theme.TabBack, Padding = new Padding(12)
|
||||
};
|
||||
card.MinimumSize = new Size(card.Dpi(width), card.Dpi(height));
|
||||
build(card);
|
||||
return card;
|
||||
}
|
||||
@@ -118,9 +118,10 @@ public sealed class AboutView : UserControl
|
||||
{
|
||||
var card = new Panel
|
||||
{
|
||||
Width = 340, Height = 300, Margin = new Padding(0, 0, 0, 12),
|
||||
Margin = new Padding(0, 0, 0, 12),
|
||||
BackColor = Theme.TabBack, Padding = new Padding(12)
|
||||
};
|
||||
card.Size = new Size(card.Dpi(340), card.Dpi(300));
|
||||
var flow = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill, FlowDirection = FlowDirection.TopDown,
|
||||
@@ -133,7 +134,7 @@ public sealed class AboutView : UserControl
|
||||
{
|
||||
flow.Controls.Add(new PictureBox
|
||||
{
|
||||
Width = 312, Height = 268, Margin = new Padding(0, 4, 0, 4),
|
||||
Width = card.Dpi(312), Height = card.Dpi(268), Margin = new Padding(0, 4, 0, 4),
|
||||
SizeMode = PictureBoxSizeMode.Zoom,
|
||||
BackColor = Theme.TabBack, Image = iconImg.ToBitmap()
|
||||
});
|
||||
@@ -164,7 +165,7 @@ public sealed class AboutView : UserControl
|
||||
};
|
||||
row.Controls.Add(new Label
|
||||
{
|
||||
Text = label, AutoSize = true, MinimumSize = new Size(70, 0),
|
||||
Text = label, AutoSize = true, MinimumSize = new Size(row.Dpi(70), 0),
|
||||
ForeColor = Theme.TextDim, Font = Theme.UiFont,
|
||||
TextAlign = ContentAlignment.MiddleLeft, Margin = new Padding(0, 0, 8, 0)
|
||||
});
|
||||
@@ -183,6 +184,13 @@ public sealed class AboutView : UserControl
|
||||
|
||||
private static readonly ChangelogEntry[] Changelog =
|
||||
[
|
||||
new("0.7.2", new DateOnly(2026, 7, 16), "The whole UI now scales correctly at 125% / 150% display scaling",
|
||||
[
|
||||
"Fixed: at Windows display scaling above 100%, text grew with the scale but many buttons and panels didn't — the Log / Script / Stop buttons on each session got clipped, the AI Chat toolbar button lost its bottom edge at 150%, and dialogs like Quick Connect could overlap their fields.",
|
||||
"Every fixed pixel size in the app — sidebars, toolbars, tab strips, settings inputs, tables, dialogs, the Ctrl+F search bar, even the scrollbar width — now converts to your actual display scale, the same way a button's text does.",
|
||||
"Buttons that show changing text (like ⏺ Log / ⏺ Logging) now size themselves from the actual rendered text width, so they can never truncate again at any scale.",
|
||||
"Note: if you change the Windows scaling while ETTerms is running, restart the app to re-layout at the new scale.",
|
||||
]),
|
||||
new("0.7.1", new DateOnly(2026, 7, 16), "Scripts no longer hang when a booting device swallows a command",
|
||||
[
|
||||
"Fixed: a device that is still booting can silently throw away what you send it, the moment its shell takes over the console — the line never echoes and never runs, so the wait after it sat there forever and your test rig was dead until someone noticed. Adding a pause before the send only made it rarer: in one overnight 45-cycle power-cycle run, 3 cycles still hung this way.",
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class AiChatView : UserControl
|
||||
_web = new WebView2 { Dock = DockStyle.Fill };
|
||||
|
||||
// ── 底部控制列:輸入框(Fill) + 右下角欄(模型下拉在上、Send 在下) ──
|
||||
var bottom = new Panel { Dock = DockStyle.Bottom, Height = 96, BackColor = Theme.RailBack, Padding = new Padding(10, 8, 10, 8) };
|
||||
var bottom = new Panel { Dock = DockStyle.Bottom, Height = this.Dpi(96), BackColor = Theme.RailBack, Padding = new Padding(10, 8, 10, 8) };
|
||||
|
||||
_input = new TextBox
|
||||
{
|
||||
@@ -60,9 +60,9 @@ public sealed class AiChatView : UserControl
|
||||
if (e.KeyCode == Keys.Enter && !e.Shift && !_running) { e.Handled = e.SuppressKeyPress = true; OnSend(); }
|
||||
};
|
||||
|
||||
var rightCol = new Panel { Dock = DockStyle.Right, Width = 178, BackColor = Theme.RailBack, Padding = new Padding(8, 0, 0, 0) };
|
||||
var rightCol = new Panel { Dock = DockStyle.Right, Width = this.Dpi(178), BackColor = Theme.RailBack, Padding = new Padding(8, 0, 0, 0) };
|
||||
|
||||
var modelWrap = new Panel { Dock = DockStyle.Top, Height = 24, BackColor = Theme.RailBack };
|
||||
var modelWrap = new Panel { Dock = DockStyle.Top, Height = this.Dpi(26), BackColor = Theme.RailBack };
|
||||
_modelBox = new ComboBox
|
||||
{
|
||||
Dock = DockStyle.Fill, DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
@@ -78,7 +78,7 @@ public sealed class AiChatView : UserControl
|
||||
};
|
||||
var refreshBtn = new Button
|
||||
{
|
||||
Text = "↻", Dock = DockStyle.Right, Width = 24, FlatStyle = FlatStyle.Flat,
|
||||
Text = "↻", Dock = DockStyle.Right, Width = this.Dpi(24), FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.TextDim, BackColor = Theme.TabBack, Font = Theme.UiFont, Cursor = Cursors.Hand
|
||||
};
|
||||
refreshBtn.FlatAppearance.BorderColor = Theme.Border;
|
||||
@@ -86,7 +86,7 @@ public sealed class AiChatView : UserControl
|
||||
modelWrap.Controls.Add(_modelBox); // Fill
|
||||
modelWrap.Controls.Add(refreshBtn); // Right
|
||||
|
||||
var gap = new Panel { Dock = DockStyle.Top, Height = 6, BackColor = Theme.RailBack };
|
||||
var gap = new Panel { Dock = DockStyle.Top, Height = this.Dpi(6), BackColor = Theme.RailBack };
|
||||
|
||||
_send = new Button
|
||||
{
|
||||
@@ -105,7 +105,7 @@ public sealed class AiChatView : UserControl
|
||||
|
||||
_hint = new Label
|
||||
{
|
||||
Dock = DockStyle.Top, Height = 40, BackColor = Color.FromArgb(60, 50, 30), ForeColor = Theme.Text,
|
||||
Dock = DockStyle.Top, Height = this.Dpi(40), BackColor = Color.FromArgb(60, 50, 30), ForeColor = Theme.Text,
|
||||
Font = Theme.UiFont, TextAlign = ContentAlignment.MiddleCenter, Visible = false,
|
||||
Text = "尚未設定 AI Provider — 到 Settings → AI Assistant 填入 Base URL 與 API Key。"
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class ConnectionSidebar : UserControl
|
||||
|
||||
public ConnectionSidebar()
|
||||
{
|
||||
Width = 250;
|
||||
Width = this.Dpi(250);
|
||||
Dock = DockStyle.Left;
|
||||
BackColor = Theme.SidebarBack;
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class ConnectionSidebar : UserControl
|
||||
// 高度 40 與右側 Workspace 工具列對齊,讓「CONNECTIONS / 連線清單」與右側分頁列同一條基準線
|
||||
var tabBar = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top, Height = 40, BackColor = Theme.RailBack,
|
||||
Dock = DockStyle.Top, Height = this.Dpi(40), BackColor = Theme.RailBack,
|
||||
Padding = new Padding(4, 7, 4, 0), WrapContents = false
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ public sealed class ConnectionSidebar : UserControl
|
||||
var b = new Button
|
||||
{
|
||||
Text = text, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(70, 24), Padding = new Padding(8, 2, 8, 2),
|
||||
MinimumSize = new Size(this.Dpi(70), this.Dpi(24)), Padding = new Padding(8, 2, 8, 2),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont,
|
||||
Margin = new Padding(0, 0, 4, 0), Cursor = Cursors.Hand
|
||||
@@ -90,7 +90,7 @@ public sealed class ConnectionSidebar : UserControl
|
||||
tabBar.Controls.Add(sftpBtn);
|
||||
|
||||
// ── Sessions panel content ──
|
||||
var header = new Panel { Dock = DockStyle.Top, Height = 34, BackColor = Theme.SidebarBack };
|
||||
var header = new Panel { Dock = DockStyle.Top, Height = this.Dpi(34), BackColor = Theme.SidebarBack };
|
||||
var title = new Label
|
||||
{
|
||||
Text = "CONNECTIONS",
|
||||
@@ -108,7 +108,7 @@ public sealed class ConnectionSidebar : UserControl
|
||||
header.Controls.Add(btnNewFolder);
|
||||
header.Controls.Add(btnNewConn);
|
||||
|
||||
var searchHost = new Panel { Dock = DockStyle.Top, Height = 32, BackColor = Theme.SidebarBack, Padding = new Padding(8, 2, 8, 4) };
|
||||
var searchHost = new Panel { Dock = DockStyle.Top, Height = this.Dpi(32), BackColor = Theme.SidebarBack, Padding = new Padding(8, 2, 8, 4) };
|
||||
_search = new TextBox
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
@@ -125,7 +125,7 @@ public sealed class ConnectionSidebar : UserControl
|
||||
{
|
||||
Text = "▷ Quick Connect",
|
||||
Dock = DockStyle.Top,
|
||||
Height = 34,
|
||||
Height = this.Dpi(34),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Color.White,
|
||||
BackColor = Theme.Accent,
|
||||
@@ -136,22 +136,22 @@ public sealed class ConnectionSidebar : UserControl
|
||||
quick.FlatAppearance.BorderSize = 0;
|
||||
quick.FlatAppearance.MouseOverBackColor = Theme.AccentDim;
|
||||
quick.Click += (_, _) => QuickConnect();
|
||||
var quickHost = new Panel { Dock = DockStyle.Top, Height = 42, BackColor = Theme.SidebarBack, Padding = new Padding(8, 4, 8, 4) };
|
||||
var quickHost = new Panel { Dock = DockStyle.Top, Height = this.Dpi(42), BackColor = Theme.SidebarBack, Padding = new Padding(8, 4, 8, 4) };
|
||||
quickHost.Controls.Add(quick);
|
||||
|
||||
var shellBtn = new Button
|
||||
{
|
||||
Text = "🖥 Local Shell", Dock = DockStyle.Top, Height = 30,
|
||||
Text = "🖥 Local Shell", Dock = DockStyle.Top, Height = this.Dpi(30),
|
||||
FlatStyle = FlatStyle.Flat, ForeColor = Theme.Text, BackColor = Theme.TabBack,
|
||||
Font = Theme.UiFont, Cursor = Cursors.Hand
|
||||
};
|
||||
shellBtn.FlatAppearance.BorderColor = Theme.Border;
|
||||
shellBtn.FlatAppearance.MouseOverBackColor = Theme.Hover;
|
||||
shellBtn.Click += (_, _) => OpenLocalShell();
|
||||
var shellHost = new Panel { Dock = DockStyle.Top, Height = 36, BackColor = Theme.SidebarBack, Padding = new Padding(8, 2, 8, 4) };
|
||||
var shellHost = new Panel { Dock = DockStyle.Top, Height = this.Dpi(36), BackColor = Theme.SidebarBack, Padding = new Padding(8, 2, 8, 4) };
|
||||
shellHost.Controls.Add(shellBtn);
|
||||
|
||||
var toolbar = new Panel { Dock = DockStyle.Top, Height = 28, BackColor = Theme.SidebarBack };
|
||||
var toolbar = new Panel { Dock = DockStyle.Top, Height = this.Dpi(28), BackColor = Theme.SidebarBack };
|
||||
var btnExpand = IconButton("⊞", "Expand All", (_, _) => SetAllExpanded(true));
|
||||
var btnCollapse = IconButton("⊟", "Collapse All", (_, _) => SetAllExpanded(false));
|
||||
btnExpand.Dock = DockStyle.Right;
|
||||
@@ -171,8 +171,8 @@ public sealed class ConnectionSidebar : UserControl
|
||||
ShowRootLines = true,
|
||||
ShowPlusMinus = true,
|
||||
FullRowSelect = true,
|
||||
ItemHeight = 26,
|
||||
Indent = 18,
|
||||
ItemHeight = this.Dpi(26),
|
||||
Indent = this.Dpi(18),
|
||||
AllowDrop = true
|
||||
};
|
||||
_tree.NodeMouseDoubleClick += OnNodeDoubleClick;
|
||||
@@ -213,15 +213,15 @@ public sealed class ConnectionSidebar : UserControl
|
||||
// Connection row
|
||||
var connRow = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top, Height = 30, BackColor = Theme.SidebarBack,
|
||||
Dock = DockStyle.Top, Height = this.Dpi(30), BackColor = Theme.SidebarBack,
|
||||
Padding = new Padding(4, 4, 4, 0), WrapContents = false
|
||||
};
|
||||
var sshLabel = new Label { Text = "SSH:", AutoSize = true, ForeColor = Theme.TextDim, Font = Theme.UiFont, Margin = new Padding(0, 4, 4, 0) };
|
||||
var sshCombo = new ComboBox { Width = 120, DropDownStyle = ComboBoxStyle.DropDownList, BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont };
|
||||
var sshCombo = new ComboBox { Width = this.Dpi(120), DropDownStyle = ComboBoxStyle.DropDownList, BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont };
|
||||
var connectBtn = new Button
|
||||
{
|
||||
Text = "Connect", AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(64, 24), Padding = new Padding(8, 2, 8, 2),
|
||||
MinimumSize = new Size(this.Dpi(64), this.Dpi(24)), Padding = new Padding(8, 2, 8, 2),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont, Cursor = Cursors.Hand, Margin = new Padding(4, 0, 0, 0)
|
||||
};
|
||||
@@ -233,7 +233,7 @@ public sealed class ConnectionSidebar : UserControl
|
||||
// Path bar
|
||||
_sftpPath = new TextBox
|
||||
{
|
||||
Dock = DockStyle.Top, Height = 24, Text = "/",
|
||||
Dock = DockStyle.Top, Height = this.Dpi(24), Text = "/",
|
||||
BackColor = Theme.TabBack, ForeColor = Theme.Accent, Font = Theme.UiFont, BorderStyle = BorderStyle.FixedSingle
|
||||
};
|
||||
_sftpPath.KeyDown += (_, e) => { if (e.KeyCode == Keys.Enter && _sftp?.IsConnected == true) SftpNavigate(_sftpPath.Text); };
|
||||
@@ -245,8 +245,8 @@ public sealed class ConnectionSidebar : UserControl
|
||||
BackColor = Theme.SidebarBack, ForeColor = Theme.Text, Font = Theme.UiFont,
|
||||
BorderStyle = BorderStyle.None, HeaderStyle = ColumnHeaderStyle.Nonclickable
|
||||
};
|
||||
_sftpList.Columns.Add("Name", 150);
|
||||
_sftpList.Columns.Add("Size", 60, HorizontalAlignment.Right);
|
||||
_sftpList.Columns.Add("Name", this.Dpi(150));
|
||||
_sftpList.Columns.Add("Size", this.Dpi(60), HorizontalAlignment.Right);
|
||||
_sftpList.DoubleClick += (_, _) =>
|
||||
{
|
||||
if (_sftpList.SelectedItems.Count == 0 || _sftp?.IsConnected != true) return;
|
||||
@@ -651,8 +651,8 @@ public sealed class ConnectionSidebar : UserControl
|
||||
var b = new Button
|
||||
{
|
||||
Text = glyph,
|
||||
Width = 34,
|
||||
Height = 34,
|
||||
Width = this.Dpi(34),
|
||||
Height = this.Dpi(34),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.TextDim,
|
||||
BackColor = Theme.SidebarBack,
|
||||
|
||||
@@ -59,6 +59,7 @@ public sealed class ConnectionEditDialog : DarkDialog
|
||||
|
||||
_type.SelectedIndex = (existing?.Type ?? ConnectionType.Ssh) == ConnectionType.Ssh ? 0 : 1;
|
||||
ToggleType();
|
||||
ApplyDpiScale();
|
||||
}
|
||||
|
||||
private void ToggleType()
|
||||
|
||||
@@ -12,6 +12,7 @@ public abstract class DarkDialog : Form
|
||||
BackColor = Theme.SidebarBack;
|
||||
ForeColor = Theme.Text;
|
||||
Font = Theme.UiFont;
|
||||
AutoScaleMode = AutoScaleMode.None; // 縮放由 ApplyDpiScale() 統一處理,避免雙重縮放
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
MaximizeBox = false;
|
||||
@@ -19,6 +20,17 @@ public abstract class DarkDialog : Form
|
||||
ShowInTaskbar = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 依目前 DPI 一次縮放整個對話框(含所有子控制項的絕對座標),
|
||||
/// 於衍生類別建構子「最後」呼叫。對話框以 96-DPI 座標排版,
|
||||
/// 字型是點數會自己隨 DPI 放大,座標不縮放在 125%/150% 就會裁切、疊字。
|
||||
/// </summary>
|
||||
protected void ApplyDpiScale()
|
||||
{
|
||||
float f = DeviceDpi / 96f;
|
||||
if (Math.Abs(f - 1f) > 0.01f) Scale(new SizeF(f, f));
|
||||
}
|
||||
|
||||
protected override void OnHandleCreated(EventArgs e)
|
||||
{
|
||||
base.OnHandleCreated(e);
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed class TextPromptDialog : DarkDialog
|
||||
Controls.AddRange(new Control[] { lbl, _input, ok, cancel });
|
||||
AcceptButton = ok;
|
||||
CancelButton = cancel;
|
||||
ApplyDpiScale();
|
||||
}
|
||||
|
||||
/// <summary>顯示對話框;回傳輸入值,取消或空白回傳 null。</summary>
|
||||
|
||||
Generated
+7
-6
@@ -18,12 +18,13 @@ partial class MainForm
|
||||
{
|
||||
SuspendLayout();
|
||||
|
||||
// 用 Dpi 自動縮放:尺寸縮放比例 = 螢幕 DPI 比例,與點數字型的實際渲染比例一致,
|
||||
// 避免「字放大了、容器沒放大」造成的高 DPI 文字/按鈕被裁切。
|
||||
AutoScaleDimensions = new SizeF(96F, 96F);
|
||||
AutoScaleMode = AutoScaleMode.Dpi;
|
||||
ClientSize = new Size(1100, 700);
|
||||
MinimumSize = new Size(720, 480);
|
||||
// DPI 縮放由我們自己管(Theme.Dpi() + AutoSize),不靠 Form 的自動縮放:
|
||||
// AutoScaleMode.Dpi 只在 Form 初始化當下對「已存在」的控制項做一次,
|
||||
// 執行期才建立的 SessionPage / 各 view 完全吃不到(實測 125%/150% 皆未縮放),
|
||||
// 留著反而有「哪天開始生效 → 與 Dpi() 雙重縮放」的風險。
|
||||
AutoScaleMode = AutoScaleMode.None;
|
||||
ClientSize = new Size(this.Dpi(1100), this.Dpi(700));
|
||||
MinimumSize = new Size(this.Dpi(720), this.Dpi(480));
|
||||
BackColor = Theme.WorkspaceBack;
|
||||
ForeColor = Theme.Text;
|
||||
Font = Theme.UiFont;
|
||||
|
||||
@@ -16,7 +16,7 @@ public sealed class SettingsView : UserControl
|
||||
// Use a custom tab strip + panel swapping instead of TabControl to avoid white borders
|
||||
var tabBar = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top, Height = 32, BackColor = Theme.RailBack,
|
||||
Dock = DockStyle.Top, Height = this.Dpi(32), BackColor = Theme.RailBack,
|
||||
Padding = new Padding(4, 4, 4, 0), WrapContents = false
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ public sealed class SettingsView : UserControl
|
||||
var b = new Button
|
||||
{
|
||||
Text = text, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(70, 26), Padding = new Padding(10, 2, 10, 2),
|
||||
MinimumSize = new Size(this.Dpi(70), this.Dpi(26)), Padding = new Padding(10, 2, 10, 2),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont,
|
||||
Margin = new Padding(0, 0, 4, 0), Cursor = Cursors.Hand
|
||||
@@ -76,18 +76,18 @@ public sealed class SettingsView : UserControl
|
||||
WrapContents = false, BackColor = Theme.WorkspaceBack, AutoScroll = true
|
||||
};
|
||||
|
||||
var font = new ComboBox { Width = 200, DropDownStyle = ComboBoxStyle.DropDown, BackColor = Theme.TabBack, ForeColor = Theme.Text, FlatStyle = FlatStyle.Flat };
|
||||
var font = new ComboBox { Width = this.Dpi(200), DropDownStyle = ComboBoxStyle.DropDown, BackColor = Theme.TabBack, ForeColor = Theme.Text, FlatStyle = FlatStyle.Flat };
|
||||
font.Items.AddRange(new object[] { "Cascadia Mono", "Consolas", "Courier New", "Lucida Console", "JetBrains Mono" });
|
||||
font.Text = s.FontFamily;
|
||||
|
||||
var fontSize = new NumericUpDown { Width = 80, Minimum = 8, Maximum = 24, DecimalPlaces = 1, Increment = 0.5m, Value = (decimal)s.FontSize, BackColor = Theme.TabBack, ForeColor = Theme.Text, BorderStyle = BorderStyle.FixedSingle };
|
||||
var scrollback = new NumericUpDown { Width = 100, Minimum = 500, Maximum = 50000, Increment = 500, Value = s.ScrollbackLines, BackColor = Theme.TabBack, ForeColor = Theme.Text, BorderStyle = BorderStyle.FixedSingle };
|
||||
var fontSize = new NumericUpDown { Width = this.Dpi(80), Minimum = 8, Maximum = 24, DecimalPlaces = 1, Increment = 0.5m, Value = (decimal)s.FontSize, BackColor = Theme.TabBack, ForeColor = Theme.Text, BorderStyle = BorderStyle.FixedSingle };
|
||||
var scrollback = new NumericUpDown { Width = this.Dpi(100), Minimum = 500, Maximum = 50000, Increment = 500, Value = s.ScrollbackLines, BackColor = Theme.TabBack, ForeColor = Theme.Text, BorderStyle = BorderStyle.FixedSingle };
|
||||
|
||||
var scheme = new ComboBox { Width = 150, DropDownStyle = ComboBoxStyle.DropDownList, BackColor = Theme.TabBack, ForeColor = Theme.Text, FlatStyle = FlatStyle.Flat };
|
||||
var scheme = new ComboBox { Width = this.Dpi(150), DropDownStyle = ComboBoxStyle.DropDownList, BackColor = Theme.TabBack, ForeColor = Theme.Text, FlatStyle = FlatStyle.Flat };
|
||||
scheme.Items.AddRange(new object[] { "Dark", "Solarized Dark", "Monokai" });
|
||||
scheme.Text = s.ColorScheme;
|
||||
|
||||
var newline = new ComboBox { Width = 120, DropDownStyle = ComboBoxStyle.DropDownList, BackColor = Theme.TabBack, ForeColor = Theme.Text, FlatStyle = FlatStyle.Flat };
|
||||
var newline = new ComboBox { Width = this.Dpi(120), DropDownStyle = ComboBoxStyle.DropDownList, BackColor = Theme.TabBack, ForeColor = Theme.Text, FlatStyle = FlatStyle.Flat };
|
||||
newline.Items.AddRange(new object[] { "\\r\\n", "\\r", "\\n" });
|
||||
newline.Text = s.DefaultNewLine;
|
||||
|
||||
@@ -109,17 +109,17 @@ public sealed class SettingsView : UserControl
|
||||
// Shell settings
|
||||
flow.Controls.Add(new Label { Text = "Shell Settings", AutoSize = true, ForeColor = Theme.Accent, Font = Theme.UiFontBold, Margin = new Padding(0, 0, 0, 4) });
|
||||
|
||||
var shellType = new ComboBox { Width = 150, DropDownStyle = ComboBoxStyle.DropDownList, BackColor = Theme.TabBack, ForeColor = Theme.Text, FlatStyle = FlatStyle.Flat };
|
||||
var shellType = new ComboBox { Width = this.Dpi(150), DropDownStyle = ComboBoxStyle.DropDownList, BackColor = Theme.TabBack, ForeColor = Theme.Text, FlatStyle = FlatStyle.Flat };
|
||||
shellType.Items.AddRange(new object[] { "PowerShell", "Bash", "Cmd" });
|
||||
shellType.Text = s.ShellType;
|
||||
flow.Controls.Add(MakeRow("Terminal Shell", shellType));
|
||||
|
||||
// 用一個帶邊框的容器包住「輸入框 + 瀏覽鈕」,讓兩者看起來像同一個欄位
|
||||
var dirPanel = new Panel { Width = InputWidth, Height = 24, BackColor = Theme.TabBack, BorderStyle = BorderStyle.FixedSingle };
|
||||
var dirPanel = new Panel { Width = this.Dpi(InputWidth), Height = this.Dpi(24), BackColor = Theme.TabBack, BorderStyle = BorderStyle.FixedSingle };
|
||||
var shellDir = new TextBox { BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont, Text = s.ShellStartupDir, BorderStyle = BorderStyle.None };
|
||||
var browseBtn = new Button
|
||||
{
|
||||
Text = "…", Width = 26, FlatStyle = FlatStyle.Flat,
|
||||
Text = "…", Width = this.Dpi(26), FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.TextDim, BackColor = Theme.TabBack, Font = Theme.UiFont, Cursor = Cursors.Hand
|
||||
};
|
||||
// 無邊框、暗色文字、與輸入框同底色 → 不再突兀
|
||||
@@ -143,7 +143,7 @@ public sealed class SettingsView : UserControl
|
||||
flow.Controls.Add(MakeSpacer(12));
|
||||
|
||||
// ── Live Preview ──
|
||||
var preview = new Panel { Width = 460, Height = 100, BackColor = Color.FromArgb(20, 20, 24), Margin = new Padding(0, 0, 0, 8) };
|
||||
var preview = new Panel { Width = this.Dpi(460), Height = this.Dpi(100), BackColor = Color.FromArgb(20, 20, 24), Margin = new Padding(0, 0, 0, 8) };
|
||||
var previewLabel = new Label
|
||||
{
|
||||
Dock = DockStyle.Fill, BackColor = Color.FromArgb(20, 20, 24), ForeColor = Color.FromArgb(200, 200, 200),
|
||||
@@ -204,7 +204,7 @@ public sealed class SettingsView : UserControl
|
||||
{
|
||||
Text = "Keywords below are highlighted in red in every terminal (case-insensitive).\n" +
|
||||
"When a keyword appears in a background tab, that tab's dot turns red until you open it.",
|
||||
AutoSize = false, Width = 520, Height = 34,
|
||||
AutoSize = true,
|
||||
ForeColor = Theme.TextDim, Font = Theme.UiFont, Margin = new Padding(0, 0, 0, 8)
|
||||
});
|
||||
|
||||
@@ -218,7 +218,7 @@ public sealed class SettingsView : UserControl
|
||||
// 規則清單:每列 = 啟用勾選 + 關鍵字(可直接編輯)
|
||||
var grid = new DataGridView
|
||||
{
|
||||
Width = 420, Height = 240,
|
||||
Width = this.Dpi(420), Height = this.Dpi(240),
|
||||
BackgroundColor = Theme.WorkspaceBack, ForeColor = Theme.Text, GridColor = Theme.Border,
|
||||
BorderStyle = BorderStyle.None, CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal,
|
||||
DefaultCellStyle = { BackColor = Theme.TabBack, ForeColor = Theme.Text, SelectionBackColor = Theme.Hover, SelectionForeColor = Theme.Text },
|
||||
@@ -227,9 +227,9 @@ public sealed class SettingsView : UserControl
|
||||
EnableHeadersVisualStyles = false, RowHeadersVisible = false,
|
||||
AllowUserToAddRows = false, AllowUserToDeleteRows = false,
|
||||
AllowUserToResizeRows = false, SelectionMode = DataGridViewSelectionMode.FullRowSelect,
|
||||
Font = Theme.UiFont, RowTemplate = { Height = 24 }, Margin = new Padding(0, 0, 0, 8)
|
||||
Font = Theme.UiFont, RowTemplate = { Height = this.Dpi(24) }, Margin = new Padding(0, 0, 0, 8)
|
||||
};
|
||||
grid.Columns.Add(new DataGridViewCheckBoxColumn { Name = "On", HeaderText = "On", Width = 44 });
|
||||
grid.Columns.Add(new DataGridViewCheckBoxColumn { Name = "On", HeaderText = "On", Width = this.Dpi(44) });
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "Keyword", HeaderText = "Keyword",
|
||||
@@ -239,7 +239,7 @@ public sealed class SettingsView : UserControl
|
||||
flow.Controls.Add(grid);
|
||||
|
||||
// 新增 / 移除
|
||||
var newKw = new TextBox { Width = 220, BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont, BorderStyle = BorderStyle.FixedSingle };
|
||||
var newKw = new TextBox { Width = this.Dpi(220), BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont, BorderStyle = BorderStyle.FixedSingle };
|
||||
var addBtn = MakeButton("Add", Theme.Accent);
|
||||
var removeBtn = MakeButton("Remove Selected", Color.FromArgb(210, 120, 120));
|
||||
|
||||
@@ -315,33 +315,33 @@ public sealed class SettingsView : UserControl
|
||||
"Leave blank to keep the AI Assistant disabled. Pick the model from the dropdown inside the\n" +
|
||||
"AI Chat pane (it lists what your endpoint offers). The model must support function calling.\n" +
|
||||
"The API key is stored in Windows Credential Manager, never in settings.json or the app.",
|
||||
AutoSize = false, Width = 620, Height = 68,
|
||||
AutoSize = true,
|
||||
ForeColor = Theme.TextDim, Font = Theme.UiFont, Margin = new Padding(0, 0, 0, 10)
|
||||
});
|
||||
|
||||
var baseUrl = new TextBox
|
||||
{
|
||||
Width = 380, BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont,
|
||||
Width = this.Dpi(380), BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont,
|
||||
BorderStyle = BorderStyle.FixedSingle, Text = s.AiBaseUrl,
|
||||
PlaceholderText = "http://localhost:11434/v1"
|
||||
};
|
||||
var apiKey = new TextBox
|
||||
{
|
||||
Width = 380, BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont,
|
||||
Width = this.Dpi(380), BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont,
|
||||
BorderStyle = BorderStyle.FixedSingle, UseSystemPasswordChar = true,
|
||||
Text = CredentialVault.Get("ETTerms/AiApiKey") ?? "",
|
||||
PlaceholderText = "(stored in Credential Manager)"
|
||||
};
|
||||
var sysPrompt = new TextBox
|
||||
{
|
||||
Width = 560, Height = 70, Multiline = true, BackColor = Theme.TabBack, ForeColor = Theme.Text,
|
||||
Width = this.Dpi(560), Height = this.Dpi(70), Multiline = true, BackColor = Theme.TabBack, ForeColor = Theme.Text,
|
||||
Font = Theme.UiFont, BorderStyle = BorderStyle.FixedSingle, Text = s.AiSystemPrompt,
|
||||
PlaceholderText = "(optional) override the assistant persona / system prompt"
|
||||
};
|
||||
|
||||
var maxRounds = new NumericUpDown
|
||||
{
|
||||
Width = 100, Minimum = 0, Maximum = 100000, Increment = 10, Value = s.AiMaxToolRounds,
|
||||
Width = this.Dpi(100), Minimum = 0, Maximum = 100000, Increment = 10, Value = s.AiMaxToolRounds,
|
||||
BackColor = Theme.TabBack, ForeColor = Theme.Text, BorderStyle = BorderStyle.FixedSingle
|
||||
};
|
||||
|
||||
@@ -352,7 +352,7 @@ public sealed class SettingsView : UserControl
|
||||
{
|
||||
Text = "How many tool calls the assistant may chain per message before it stops (a runaway-loop guard).\n" +
|
||||
"0 = unlimited — for long automation runs. Every round costs tokens; press Stop in the chat to abort.",
|
||||
AutoSize = false, Width = 620, Height = 34,
|
||||
AutoSize = true,
|
||||
ForeColor = Theme.TextDim, Font = Theme.UiFont, Margin = new Padding(0, 0, 0, 6)
|
||||
});
|
||||
flow.Controls.Add(MakeSpacer(4));
|
||||
@@ -368,7 +368,7 @@ public sealed class SettingsView : UserControl
|
||||
{
|
||||
Text = "Tools the assistant can call: serial send/read (via the GUI's open Serial session, shown as [AI]),\n" +
|
||||
"and PDU control over SNMP. Turning an outlet off / power-cycling always asks you to confirm.",
|
||||
AutoSize = false, Width = 620, Height = 36,
|
||||
AutoSize = true,
|
||||
ForeColor = Theme.TextDim, Font = Theme.UiFont, Margin = new Padding(0, 0, 0, 8)
|
||||
});
|
||||
|
||||
@@ -416,7 +416,7 @@ public sealed class SettingsView : UserControl
|
||||
"user-level config. Serial: ETTerms owns the COM port, the AI drives it through a\n" +
|
||||
"local named pipe (open a Serial session first). PDU: the AI controls outlets\n" +
|
||||
"directly over SNMP — no GUI session required.",
|
||||
AutoSize = false, Width = 600, Height = 64,
|
||||
AutoSize = true,
|
||||
ForeColor = Theme.TextDim, Font = Theme.UiFont, Margin = new Padding(0, 0, 0, 8)
|
||||
});
|
||||
|
||||
@@ -426,7 +426,7 @@ public sealed class SettingsView : UserControl
|
||||
flow.Controls.Add(new Label
|
||||
{
|
||||
Text = $"{name}: {exe}",
|
||||
AutoSize = false, Width = 600, Height = 20,
|
||||
AutoSize = true,
|
||||
ForeColor = exists ? Theme.SerialColor : Color.FromArgb(210, 150, 120),
|
||||
Font = Theme.UiFont, Margin = new Padding(0, 0, 0, 2)
|
||||
});
|
||||
@@ -436,7 +436,7 @@ public sealed class SettingsView : UserControl
|
||||
flow.Controls.Add(new Label
|
||||
{
|
||||
Text = "⚠ Some servers not built yet — publish the app (or build the MCP projects). Setup still writes the expected paths.",
|
||||
AutoSize = false, Width = 600, Height = 20,
|
||||
AutoSize = true,
|
||||
ForeColor = Color.FromArgb(210, 150, 120), Font = Theme.UiFont, Margin = new Padding(0, 0, 0, 4)
|
||||
});
|
||||
}
|
||||
@@ -455,7 +455,7 @@ public sealed class SettingsView : UserControl
|
||||
{
|
||||
var card = new Panel
|
||||
{
|
||||
Width = 600, Height = 196, BackColor = Theme.TabBack,
|
||||
Width = this.Dpi(600), Height = this.Dpi(196), BackColor = Theme.TabBack,
|
||||
Padding = new Padding(14), Margin = new Padding(0, 0, 0, 4)
|
||||
};
|
||||
var col = new FlowLayoutPanel
|
||||
@@ -473,7 +473,7 @@ public sealed class SettingsView : UserControl
|
||||
var pathLbl = new Label
|
||||
{
|
||||
Text = $"Config: {McpRegistrar.ConfigPath(target)}",
|
||||
AutoSize = false, Width = 560, Height = 18,
|
||||
AutoSize = true,
|
||||
ForeColor = Theme.TextDim, Font = Theme.UiFont, Margin = new Padding(0, 0, 0, 6)
|
||||
};
|
||||
|
||||
@@ -495,7 +495,7 @@ public sealed class SettingsView : UserControl
|
||||
};
|
||||
var verifyBox = new TextBox
|
||||
{
|
||||
Multiline = true, ReadOnly = true, Width = 560, Height = 56,
|
||||
Multiline = true, ReadOnly = true, Width = this.Dpi(560), Height = this.Dpi(56),
|
||||
BackColor = Color.FromArgb(20, 20, 24), ForeColor = Color.FromArgb(200, 200, 200),
|
||||
BorderStyle = BorderStyle.FixedSingle, Font = new Font("Cascadia Mono", 9f),
|
||||
Text = McpRegistrar.VerifyHint(target)
|
||||
@@ -571,11 +571,11 @@ public sealed class SettingsView : UserControl
|
||||
};
|
||||
var lbl = new Label
|
||||
{
|
||||
Text = label, AutoSize = true, MinimumSize = new Size(LabelWidth, 0),
|
||||
Text = label, AutoSize = true, MinimumSize = new Size(row.Dpi(LabelWidth), 0),
|
||||
ForeColor = Theme.Text, Font = Theme.UiFont,
|
||||
TextAlign = ContentAlignment.MiddleLeft, Margin = new Padding(0, 6, 8, 0)
|
||||
};
|
||||
if (control.Width <= 0) control.Width = InputWidth;
|
||||
if (control.Width <= 0) control.Width = row.Dpi(InputWidth);
|
||||
row.Controls.Add(lbl);
|
||||
row.Controls.Add(control);
|
||||
return row;
|
||||
@@ -586,11 +586,12 @@ public sealed class SettingsView : UserControl
|
||||
var b = new Button
|
||||
{
|
||||
Text = text, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(90, 28), Padding = new Padding(10, 2, 10, 2),
|
||||
Padding = new Padding(10, 2, 10, 2),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont,
|
||||
Cursor = Cursors.Hand, Margin = new Padding(8, 0, 0, 0)
|
||||
};
|
||||
b.MinimumSize = new Size(b.Dpi(90), b.Dpi(28));
|
||||
b.FlatAppearance.BorderColor = borderColor;
|
||||
b.FlatAppearance.MouseOverBackColor = Theme.Hover;
|
||||
return b;
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class StatusView : UserControl
|
||||
|
||||
var tabBar = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top, Height = 32, BackColor = Theme.RailBack,
|
||||
Dock = DockStyle.Top, Height = this.Dpi(32), BackColor = Theme.RailBack,
|
||||
Padding = new Padding(4, 4, 4, 0), WrapContents = false
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ public sealed class StatusView : UserControl
|
||||
var b = new Button
|
||||
{
|
||||
Text = text, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(70, 26), Padding = new Padding(10, 2, 10, 2),
|
||||
MinimumSize = new Size(this.Dpi(70), this.Dpi(26)), Padding = new Padding(10, 2, 10, 2),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont,
|
||||
Margin = new Padding(0, 0, 4, 0), Cursor = Cursors.Hand
|
||||
@@ -75,11 +75,11 @@ public sealed class StatusView : UserControl
|
||||
};
|
||||
|
||||
// Connection row
|
||||
var ipBox = new TextBox { Width = 160, Text = "192.168.1.21", BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont };
|
||||
var ipBox = new TextBox { Width = this.Dpi(160), Text = "192.168.1.21", BackColor = Theme.TabBack, ForeColor = Theme.Text, Font = Theme.UiFont };
|
||||
var connectBtn = MakeButton("Connect", Theme.SerialColor);
|
||||
var statusLabel = new Label { AutoSize = true, Text = "Disconnected", ForeColor = Theme.TextDim, Font = Theme.UiFont, Margin = new Padding(8, 8, 0, 0) };
|
||||
|
||||
var connRow = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, Width = 500, Height = 36, WrapContents = false, Margin = new Padding(0, 0, 0, 8) };
|
||||
var connRow = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, AutoSize = true, WrapContents = false, Margin = new Padding(0, 0, 0, 8) };
|
||||
connRow.Controls.Add(new Label { Text = "PDU IP:", AutoSize = true, ForeColor = Theme.Text, Font = Theme.UiFont, Margin = new Padding(0, 6, 8, 0) });
|
||||
connRow.Controls.Add(ipBox);
|
||||
connRow.Controls.Add(connectBtn);
|
||||
@@ -89,7 +89,7 @@ public sealed class StatusView : UserControl
|
||||
// Port status grid
|
||||
var grid = new DataGridView
|
||||
{
|
||||
Width = 480, Height = 310, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
|
||||
Width = this.Dpi(480), Height = this.Dpi(310), AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
|
||||
BackgroundColor = Theme.WorkspaceBack, ForeColor = Theme.Text, GridColor = Theme.Border,
|
||||
BorderStyle = BorderStyle.None, CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal,
|
||||
DefaultCellStyle = { BackColor = Theme.TabBack, ForeColor = Theme.Text, SelectionBackColor = Theme.Hover, SelectionForeColor = Theme.Text },
|
||||
@@ -99,7 +99,7 @@ public sealed class StatusView : UserControl
|
||||
AllowUserToAddRows = false, AllowUserToDeleteRows = false, ReadOnly = true,
|
||||
AllowUserToResizeRows = false, SelectionMode = DataGridViewSelectionMode.FullRowSelect,
|
||||
ScrollBars = ScrollBars.None, Font = Theme.UiFont,
|
||||
RowTemplate = { Height = 24 },
|
||||
RowTemplate = { Height = this.Dpi(24) },
|
||||
Margin = new Padding(0, 8, 0, 8)
|
||||
};
|
||||
grid.Columns.Add("Port", "Port");
|
||||
@@ -274,11 +274,12 @@ public sealed class StatusView : UserControl
|
||||
var b = new Button
|
||||
{
|
||||
Text = text, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(90, 28), Padding = new Padding(10, 2, 10, 2),
|
||||
Padding = new Padding(10, 2, 10, 2),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont,
|
||||
Cursor = Cursors.Hand, Margin = new Padding(8, 0, 0, 0)
|
||||
};
|
||||
b.MinimumSize = new Size(b.Dpi(90), b.Dpi(28));
|
||||
b.FlatAppearance.BorderColor = borderColor;
|
||||
b.FlatAppearance.MouseOverBackColor = Theme.Hover;
|
||||
return b;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ETTerms.App;
|
||||
|
||||
@@ -27,4 +28,12 @@ public static class Theme
|
||||
|
||||
public static readonly Font UiFont = new("Segoe UI", 9.5f);
|
||||
public static readonly Font UiFontBold = new("Segoe UI", 9.5f, FontStyle.Bold);
|
||||
|
||||
/// <summary>
|
||||
/// 96-DPI 設計像素 → 控制項目前 DPI 的實際像素。
|
||||
/// 執行期建立的控制項不會被 Form 的自動縮放處理(那只發生在 Form 初始化當下),
|
||||
/// 字型以點數指定會自己隨 DPI 放大,因此所有寫死的像素尺寸都必須經過這裡換算,
|
||||
/// 否則 125%/150% 時就是「字大了、框沒大」的裁切(作法同 ActivityRail.ItemSize)。
|
||||
/// </summary>
|
||||
public static int Dpi(this Control c, int px) => (int)Math.Round(px * (c.DeviceDpi / 96.0));
|
||||
}
|
||||
|
||||
@@ -44,9 +44,10 @@ public sealed class WorkspaceView : UserControl
|
||||
private bool _dragging; // 是否已進入拖曳
|
||||
private const int DragThreshold = 5;
|
||||
|
||||
private const int StripH = 30;
|
||||
private const int TabW = 180;
|
||||
private const int CloseSz = 14;
|
||||
// Tab 列尺寸:owner-draw 不會被自動縮放,需依 DPI 換算(同 ActivityRail.ItemSize)
|
||||
private int StripH => this.Dpi(30);
|
||||
private int TabW => this.Dpi(180);
|
||||
private int CloseSz => this.Dpi(14);
|
||||
|
||||
private static readonly (string label, int r, int c)[] Presets =
|
||||
{ ("1×1", 1, 1), ("1×2", 1, 2), ("2×1", 2, 1), ("2×2", 2, 2), ("2×3", 2, 3), ("3×3", 3, 3) };
|
||||
@@ -58,7 +59,7 @@ public sealed class WorkspaceView : UserControl
|
||||
|
||||
// 頂部工具列容器:左側為 Layout/Run 群組(Fill),右側為 Log All(Dock Right)
|
||||
// 高度 40 與左側 ConnectionSidebar 的 tab bar 對齊,且容得下 AutoSize 按鈕(不被裁切)
|
||||
var topBar = new Panel { Dock = DockStyle.Top, Height = 40, BackColor = Theme.RailBack };
|
||||
var topBar = new Panel { Dock = DockStyle.Top, Height = this.Dpi(40), BackColor = Theme.RailBack };
|
||||
|
||||
_toolbar = new FlowLayoutPanel
|
||||
{
|
||||
@@ -268,7 +269,7 @@ public sealed class WorkspaceView : UserControl
|
||||
: $"{icon} {s.Title} [{glabel}]";
|
||||
var lbl = new Label
|
||||
{
|
||||
Dock = DockStyle.Bottom, Height = 22,
|
||||
Dock = DockStyle.Bottom, Height = this.Dpi(22),
|
||||
Text = labelText,
|
||||
ForeColor = s == _active ? Theme.Text : Theme.TextDim,
|
||||
BackColor = Theme.RailBack, Font = Theme.UiFont,
|
||||
@@ -293,7 +294,7 @@ public sealed class WorkspaceView : UserControl
|
||||
foreach (var s in _sessions)
|
||||
{
|
||||
s.TabBounds = new Rectangle(x, 0, TabW, StripH);
|
||||
s.CloseRect = new Rectangle(s.TabBounds.Right - CloseSz - 6, (StripH - CloseSz) / 2, CloseSz, CloseSz);
|
||||
s.CloseRect = new Rectangle(s.TabBounds.Right - CloseSz - this.Dpi(6), (StripH - CloseSz) / 2, CloseSz, CloseSz);
|
||||
x += TabW;
|
||||
}
|
||||
}
|
||||
@@ -400,9 +401,11 @@ public sealed class WorkspaceView : UserControl
|
||||
// 警示中的背景分頁:型別圓點改紅色,切過去看時清除。AI 分頁用 accent 紫。
|
||||
Color dotColor = s.Alert ? Color.FromArgb(235, 85, 85)
|
||||
: s.IsAi ? Theme.Accent : s.IsSsh ? Theme.SshColor : Theme.SerialColor;
|
||||
int dotSz = this.Dpi(8);
|
||||
using (var dot = new SolidBrush(dotColor))
|
||||
g.FillEllipse(dot, s.TabBounds.Left + 9, StripH / 2 - 4, 8, 8);
|
||||
var tr = new Rectangle(s.TabBounds.Left + 22, s.TabBounds.Top, s.TabBounds.Width - 22 - CloseSz - 10, StripH);
|
||||
g.FillEllipse(dot, s.TabBounds.Left + this.Dpi(9), (StripH - dotSz) / 2, dotSz, dotSz);
|
||||
int textL = this.Dpi(22);
|
||||
var tr = new Rectangle(s.TabBounds.Left + textL, s.TabBounds.Top, s.TabBounds.Width - textL - CloseSz - this.Dpi(10), StripH);
|
||||
TextRenderer.DrawText(g, s.Title, Theme.UiFont, tr, active ? Theme.Text : Theme.TextDim,
|
||||
TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis);
|
||||
TextRenderer.DrawText(g, "✕", Theme.UiFont, s.CloseRect, hover ? Theme.Text : Theme.TextDim,
|
||||
@@ -416,7 +419,7 @@ public sealed class WorkspaceView : UserControl
|
||||
var b = new Button
|
||||
{
|
||||
Text = label, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(40, 26), Padding = new Padding(8, 2, 8, 2),
|
||||
MinimumSize = new Size(this.Dpi(40), this.Dpi(26)), Padding = new Padding(8, 2, 8, 2),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont,
|
||||
Margin = new Padding(3, 0, 3, 0), Cursor = Cursors.Hand
|
||||
@@ -427,12 +430,12 @@ public sealed class WorkspaceView : UserControl
|
||||
return b;
|
||||
}
|
||||
|
||||
private static Button MakeActionButton(string text, int minWidth, int leftMargin, EventHandler onClick)
|
||||
private Button MakeActionButton(string text, int minWidth, int leftMargin, EventHandler onClick)
|
||||
{
|
||||
var b = new Button
|
||||
{
|
||||
Text = text, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
MinimumSize = new Size(minWidth, 26), Padding = new Padding(10, 2, 10, 2),
|
||||
MinimumSize = new Size(this.Dpi(minWidth), this.Dpi(26)), Padding = new Padding(10, 2, 10, 2),
|
||||
FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont,
|
||||
Margin = new Padding(leftMargin, 0, 3, 0), Cursor = Cursors.Hand
|
||||
@@ -474,8 +477,7 @@ public sealed class WorkspaceView : UserControl
|
||||
|
||||
private void SetLogAllActive(bool on)
|
||||
{
|
||||
_logAll.Text = on ? "⏺ Logging All" : "⏺ Log All";
|
||||
_logAll.Width = on ? 110 : 96;
|
||||
_logAll.Text = on ? "⏺ Logging All" : "⏺ Log All"; // AutoSize 按鈕,寬度隨字自動調
|
||||
_logAll.ForeColor = on ? Theme.SerialColor : Theme.Text;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ public sealed class SessionPage : UserControl
|
||||
Dock = DockStyle.Fill;
|
||||
|
||||
// ── 頂部腳本列 ──
|
||||
var bar = new Panel { Dock = DockStyle.Top, Height = 24, BackColor = Theme.RailBack };
|
||||
var bar = new Panel { Dock = DockStyle.Top, Height = this.Dpi(24), BackColor = Theme.RailBack };
|
||||
_status = new Label
|
||||
{
|
||||
Dock = DockStyle.Fill, Text = "Idle",
|
||||
@@ -84,7 +84,7 @@ public sealed class SessionPage : UserControl
|
||||
_stop = MakeBarButton("■ Stop", (_, _) => _runner.Cancel());
|
||||
_stop.Enabled = false;
|
||||
_log = MakeBarButton("⏺ Log", OnToggleLog);
|
||||
_log.Width = 92; // 容納 "⏺ Logging" 不被截字
|
||||
_log.Width = BarButtonWidth("⏺ Logging"); // 取切換後較長的字,不被截字
|
||||
bar.Controls.Add(_status); // Fill 先加
|
||||
bar.Controls.Add(_log); // Right(最左:Log)
|
||||
bar.Controls.Add(_run); // Right(中:Script)
|
||||
@@ -185,11 +185,15 @@ public sealed class SessionPage : UserControl
|
||||
else action();
|
||||
}
|
||||
|
||||
private static Button MakeBarButton(string text, EventHandler onClick)
|
||||
/// <summary>依實際字寬(隨 DPI 放大)算按鈕寬度,固定像素在 125%/150% 會截字。</summary>
|
||||
private int BarButtonWidth(string text) =>
|
||||
TextRenderer.MeasureText(text, Theme.UiFont).Width + this.Dpi(18);
|
||||
|
||||
private Button MakeBarButton(string text, EventHandler onClick)
|
||||
{
|
||||
var b = new Button
|
||||
{
|
||||
Text = text, Dock = DockStyle.Right, Width = 72, FlatStyle = FlatStyle.Flat,
|
||||
Text = text, Dock = DockStyle.Right, Width = BarButtonWidth(text), FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Theme.Text, BackColor = Theme.TabBack, Font = Theme.UiFont, Cursor = Cursors.Hand
|
||||
};
|
||||
b.FlatAppearance.BorderColor = Theme.Border;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Windows.Forms;
|
||||
using ETTerms.App;
|
||||
|
||||
namespace ETTerms.Terminal;
|
||||
|
||||
@@ -44,7 +45,7 @@ public sealed class DarkScrollBar : Control
|
||||
// 導致 TerminalView 收不到鍵盤輸入(打字 / Enter 全失效),需重開 session 才恢復。
|
||||
SetStyle(ControlStyles.Selectable, false);
|
||||
TabStop = false;
|
||||
Width = 12;
|
||||
Width = this.Dpi(12);
|
||||
BackColor = TrackColor;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using ETTerms.App;
|
||||
using ETTerms.Infrastructure;
|
||||
|
||||
namespace ETTerms.Terminal;
|
||||
@@ -440,7 +441,7 @@ public sealed class TerminalView : UserControl
|
||||
if (_searchPanel != null) return;
|
||||
|
||||
var back = Color.FromArgb(32, 32, 38);
|
||||
_searchPanel = new Panel { Size = new Size(268, 30), BackColor = back, Visible = false };
|
||||
_searchPanel = new Panel { Size = new Size(this.Dpi(268), this.Dpi(30)), BackColor = back, Visible = false };
|
||||
_searchPanel.Paint += (_, pe) =>
|
||||
{
|
||||
using var pen = new Pen(Color.FromArgb(58, 58, 66));
|
||||
@@ -449,7 +450,7 @@ public sealed class TerminalView : UserControl
|
||||
|
||||
_searchBox = new TextBox
|
||||
{
|
||||
Bounds = new Rectangle(6, 5, 130, 20), BorderStyle = BorderStyle.None,
|
||||
Bounds = new Rectangle(this.Dpi(6), this.Dpi(5), this.Dpi(130), this.Dpi(20)), BorderStyle = BorderStyle.None,
|
||||
BackColor = back, ForeColor = Color.FromArgb(222, 222, 226), Font = new Font("Segoe UI", 9.5f)
|
||||
};
|
||||
_searchBox.TextChanged += (_, _) => RunSearch();
|
||||
@@ -463,7 +464,7 @@ public sealed class TerminalView : UserControl
|
||||
|
||||
_searchCount = new Label
|
||||
{
|
||||
Bounds = new Rectangle(138, 7, 56, 16), Text = "",
|
||||
Bounds = new Rectangle(this.Dpi(138), this.Dpi(7), this.Dpi(56), this.Dpi(16)), Text = "",
|
||||
ForeColor = Color.FromArgb(150, 150, 158), BackColor = back,
|
||||
Font = new Font("Segoe UI", 8.5f), TextAlign = ContentAlignment.MiddleRight
|
||||
};
|
||||
@@ -472,7 +473,7 @@ public sealed class TerminalView : UserControl
|
||||
{
|
||||
var b = new Button
|
||||
{
|
||||
Bounds = new Rectangle(x, 4, 22, 22), Text = text, FlatStyle = FlatStyle.Flat,
|
||||
Bounds = new Rectangle(this.Dpi(x), this.Dpi(4), this.Dpi(22), this.Dpi(22)), Text = text, FlatStyle = FlatStyle.Flat,
|
||||
ForeColor = Color.FromArgb(180, 180, 188), BackColor = back,
|
||||
Font = new Font("Segoe UI", 8.5f), TabStop = false, Cursor = Cursors.Hand
|
||||
};
|
||||
@@ -493,7 +494,7 @@ public sealed class TerminalView : UserControl
|
||||
private void PositionSearchPanel()
|
||||
{
|
||||
if (_searchPanel == null) return;
|
||||
_searchPanel.Location = new Point(Math.Max(0, ContentWidth - _searchPanel.Width - 8), 6);
|
||||
_searchPanel.Location = new Point(Math.Max(0, ContentWidth - _searchPanel.Width - this.Dpi(8)), this.Dpi(6));
|
||||
}
|
||||
|
||||
/// <summary>重掃整個 buffer(scrollback + 畫面)建立命中清單,並跳到最靠近底部的命中。</summary>
|
||||
|
||||
Reference in New Issue
Block a user