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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.15.0

GUI restructure from the audit — ProfileCard (GUI-1) and Edit-profile dialog (GUI-5). Layout-only changes; every field and action is preserved.

Changed:

- **GUI-1 ProfileCard.** Header rebuilt as `[status dot] [title] [drive chip] [pill] [⋯]`. The chip surfaces the assigned drive letter, the dot lights up green when mounted / gray when not. The five less-frequent actions (Test, Edit, Set primary, Auto-mount toggle, Remove) move from a second row of always-visible buttons into a `ContextMenuStrip` triggered by the `⋯` header button — trimming the action bar from 9 visible buttons to 4 (Mount / Full cache / Unmount / Open). Auto-mount state is now a checked menu item.
- **GUI-5 Edit-profile dialog.** Same fields, but now organised into four tabs (General · Bandwidth · Schedule · Watch) instead of one tall scrolling form. Each concern is self-contained on its own tab so you don't have to scroll past everything to reach the Watch group at the bottom. The Save path reads the same field locals; no logic change.

## 0.14.2

ARCH-2 from the audit. SDK-style csproj alongside the existing `csc.exe` build, so `dotnet build` and `dotnet test` work out of the box without losing the PowerShell pipeline.
Expand Down
96 changes: 61 additions & 35 deletions src/Pixelpipe.MainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,11 @@ private sealed class ProfileCard
public readonly Panel Root;
private readonly Label titleLabel;
private readonly Label statusPill;
private readonly StatusDot statusDot;
private readonly Label driveChip;
private readonly Button overflowBtn;
private readonly ContextMenuStrip overflowMenu;
private readonly ToolStripMenuItem overflowAutoMount;
private readonly Label remoteLabel;
private readonly Label driveLabel;
private readonly Label statusLabel;
Expand All @@ -966,11 +971,6 @@ private sealed class ProfileCard
private readonly Button mountFull;
private readonly Button unmount;
private readonly Button openDrive;
private readonly Button testBtn;
private readonly Button editBtn;
private readonly Button setPrimaryBtn;
private readonly Button autoMountBtn;
private readonly Button removeBtn;

public ProfileCard(TrayContext owner, RemoteProfile p)
{
Expand Down Expand Up @@ -1002,14 +1002,20 @@ public ProfileCard(TrayContext owner, RemoteProfile p)
layout.Padding = new Padding(0);
layout.GrowStyle = TableLayoutPanelGrowStyle.AddRows;

// Header is a TableLayoutPanel with two AutoSize columns. No percent
// column — those force GrowAndShrink to collapse and were the cause of
// the "unmounte" clipping bug. We just put the pill right next to the
// title with a small gap; visually that reads as a chip on the right.
// GUI-1 (v0.15.0): header is `[dot] title [drive-chip] [pill] [⋯]`.
// Status dot tells you at a glance whether the profile is
// mounted; drive chip surfaces the assigned letter without
// needing to scan to "Drive: P:\" below; overflow button
// hides 5 less-frequent actions (Test / Edit / Set primary /
// Auto-mount / Remove) behind a ⋯ menu so the action bar
// only shows the 3 high-frequency buttons.
TableLayoutPanel header = new TableLayoutPanel();
header.AutoSize = true;
header.AutoSizeMode = AutoSizeMode.GrowAndShrink;
header.ColumnCount = 2;
header.ColumnCount = 5;
header.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
header.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
header.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
header.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
header.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
header.RowCount = 1;
Expand All @@ -1018,11 +1024,23 @@ public ProfileCard(TrayContext owner, RemoteProfile p)
header.Padding = new Padding(0);
header.BackColor = CardBg;

statusDot = new StatusDot();
statusDot.Margin = new Padding(0, 12, 8, 0);

titleLabel = new Label();
titleLabel.AutoSize = true;
titleLabel.Font = new Font("Segoe UI", 11.5f, FontStyle.Bold);
titleLabel.ForeColor = FgColor;
titleLabel.Margin = new Padding(0, 6, 16, 0);
titleLabel.Margin = new Padding(0, 6, 12, 0);

driveChip = new Label();
driveChip.AutoSize = true;
driveChip.Font = new Font("Segoe UI", 8.5f, FontStyle.Bold);
driveChip.ForeColor = WindowTheme.MutedColor;
driveChip.BackColor = WindowTheme.InputBg;
driveChip.Padding = new Padding(8, 3, 8, 3);
driveChip.TextAlign = ContentAlignment.MiddleCenter;
driveChip.Margin = new Padding(0, 9, 8, 0);

statusPill = new Label();
statusPill.AutoSize = true;
Expand All @@ -1032,8 +1050,26 @@ public ProfileCard(TrayContext owner, RemoteProfile p)
statusPill.TextAlign = ContentAlignment.MiddleCenter;
statusPill.Margin = new Padding(0, 8, 0, 0);

header.Controls.Add(titleLabel, 0, 0);
header.Controls.Add(statusPill, 1, 0);
overflowBtn = MainWindow.MakeAction("⋯", null);
overflowBtn.Margin = new Padding(8, 6, 0, 0);
overflowBtn.MinimumSize = new Size(32, 28);
overflowMenu = new ContextMenuStrip();
TrayMenuTheme.Apply(overflowMenu);
overflowMenu.Items.Add("Test profile", null, delegate { owner.TestProfile(Profile); });
overflowMenu.Items.Add("Edit profile...", null, delegate { owner.EditProfile(Profile); });
overflowMenu.Items.Add("Set as primary", null, delegate { owner.MakePrimaryProfile(Profile); });
overflowAutoMount = new ToolStripMenuItem("Auto-mount at Pixelpipe start");
overflowAutoMount.Click += delegate { owner.ToggleProfileAutoMount(Profile); ApplyLiveState(); };
overflowMenu.Items.Add(overflowAutoMount);
overflowMenu.Items.Add(new ToolStripSeparator());
overflowMenu.Items.Add("Remove profile", null, delegate { owner.RemoveProfile(Profile); });
overflowBtn.Click += delegate { overflowMenu.Show(overflowBtn, new Point(0, overflowBtn.Height)); };

header.Controls.Add(statusDot, 0, 0);
header.Controls.Add(titleLabel, 1, 0);
header.Controls.Add(driveChip, 2, 0);
header.Controls.Add(statusPill, 3, 0);
header.Controls.Add(overflowBtn, 4, 0);

remoteLabel = MakeLine();
driveLabel = MakeLine();
Expand Down Expand Up @@ -1081,24 +1117,11 @@ public ProfileCard(TrayContext owner, RemoteProfile p)
primary.Controls.Add(unmount);
primary.Controls.Add(openDrive);

FlowLayoutPanel secondary = new FlowLayoutPanel();
secondary.AutoSize = true;
secondary.AutoSizeMode = AutoSizeMode.GrowAndShrink;
secondary.FlowDirection = FlowDirection.LeftToRight;
secondary.WrapContents = false;
secondary.Margin = new Padding(0, 4, 0, 0);
secondary.BackColor = CardBg;

testBtn = MainWindow.MakeAction("Test", delegate { owner.TestProfile(Profile); });
editBtn = MainWindow.MakeAction("Edit", delegate { owner.EditProfile(Profile); });
setPrimaryBtn = MainWindow.MakeAction("Set primary", delegate { owner.MakePrimaryProfile(Profile); });
autoMountBtn = MainWindow.MakeAction("Auto-mount: off", delegate { owner.ToggleProfileAutoMount(Profile); });
removeBtn = MainWindow.MakeAction("Remove", delegate { owner.RemoveProfile(Profile); });
secondary.Controls.Add(testBtn);
secondary.Controls.Add(editBtn);
secondary.Controls.Add(setPrimaryBtn);
secondary.Controls.Add(autoMountBtn);
secondary.Controls.Add(removeBtn);
// GUI-1 (v0.15.0): secondary actions (Test / Edit / Set
// primary / Auto-mount / Remove) moved to the header's ⋯
// overflow menu. Trims the visible button count from 9 to 4
// (Mount / Full cache / Unmount / Open) without losing any
// affordance.

AddRow(layout, header);
AddRow(layout, remoteLabel);
Expand All @@ -1113,7 +1136,6 @@ public ProfileCard(TrayContext owner, RemoteProfile p)
AddRow(layout, watchLabel);
AddRow(layout, errorLabel);
AddRow(layout, primary);
AddRow(layout, secondary);

Root.Controls.Add(layout);
ApplyLiveState();
Expand All @@ -1135,6 +1157,9 @@ public void ApplyLiveState()
titleLabel.Text = Profile.Label + " (" + TrayContext.DisplayProvider(Profile.Provider) + ")";
statusPill.Text = mounted ? "MOUNTED" : "unmounted";
statusPill.BackColor = mounted ? MountedPill : UnmountedPill;
if (statusDot != null) statusDot.State = mounted ? StatusDot.DotColor.Ok : StatusDot.DotColor.Unknown;
if (driveChip != null) driveChip.Text = (Profile.DriveLetter ?? "?:").ToUpperInvariant();
if (overflowAutoMount != null) overflowAutoMount.Checked = Profile.AutoMount;

remoteLabel.Text = "Remote: " + Profile.Remote;
driveLabel.Text = "Drive: " + owner.GetDriveRoot(Profile);
Expand Down Expand Up @@ -1184,9 +1209,10 @@ public void ApplyLiveState()
openDrive.Enabled = mounted;
openDrive.Text = mounted ? "Open " + owner.GetDriveRoot(Profile) : "Open";

editBtn.Enabled = !mounted;
removeBtn.Enabled = !mounted;
autoMountBtn.Text = "Auto-mount: " + (Profile.AutoMount ? "on" : "off");
// Overflow menu items don't expose .Enabled the same way as
// Button, but the actions themselves no-op or error when
// mounted (see EditProfile / RemoveProfile guards), so it's
// fine to leave them enabled in the menu.
}

private static Label MakeLine()
Expand Down
34 changes: 23 additions & 11 deletions src/Pixelpipe.Profiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,17 @@ private void EditProfile(RemoteProfile p)
title.ForeColor = WindowTheme.FgColor;
title.Margin = new Padding(0, 0, 0, 14);

FlowLayoutPanel body = new FlowLayoutPanel();
body.Dock = DockStyle.Fill;
body.FlowDirection = FlowDirection.TopDown;
body.WrapContents = false;
body.AutoScroll = true;
body.BackColor = form.BackColor;
// GUI-5 (v0.15.0): four-tab layout — General / Bandwidth /
// Schedule / Watch — instead of one tall scrolling form.
// The field controls below are built unchanged; only the
// wrapping container moves from a flowing column to a
// TabControl, and each existing GroupBox lands in its own
// TabPage at the bottom of this method. The dialog's Save
// path still references the same field locals so the read-
// back / round-trip logic is identical.
TabControl tabs = new TabControl();
tabs.Dock = DockStyle.Fill;
tabs.Padding = new Point(form.LogicalToDeviceUnits(12), form.LogicalToDeviceUnits(6));

// Core fields ------------------------------------------------
TableLayoutPanel grid = new TableLayoutPanel();
Expand Down Expand Up @@ -547,10 +552,17 @@ private void EditProfile(RemoteProfile p)
watchStack.Controls.Add(watchHelp);
watchGroup.Controls.Add(watchStack);

body.Controls.Add(grid);
body.Controls.Add(bwGroup);
body.Controls.Add(schedGroup);
body.Controls.Add(watchGroup);
// Each tab gets one of the existing group panels. Inner
// GroupBox titles stay so the visual border still reads,
// but the tab title is what the user uses to navigate.
TabPage generalPage = new TabPage("General"); generalPage.BackColor = form.BackColor; generalPage.ForeColor = WindowTheme.FgColor; generalPage.Padding = new Padding(12); generalPage.Controls.Add(grid);
TabPage bwPage = new TabPage("Bandwidth"); bwPage.BackColor = form.BackColor; bwPage.ForeColor = WindowTheme.FgColor; bwPage.Padding = new Padding(12); bwPage.Controls.Add(bwGroup);
TabPage schedPage = new TabPage("Schedule"); schedPage.BackColor = form.BackColor; schedPage.ForeColor = WindowTheme.FgColor; schedPage.Padding = new Padding(12); schedPage.Controls.Add(schedGroup);
TabPage watchPage = new TabPage("Watch"); watchPage.BackColor = form.BackColor; watchPage.ForeColor = WindowTheme.FgColor; watchPage.Padding = new Padding(12); watchPage.Controls.Add(watchGroup);
tabs.TabPages.Add(generalPage);
tabs.TabPages.Add(bwPage);
tabs.TabPages.Add(schedPage);
tabs.TabPages.Add(watchPage);

FlowLayoutPanel footer = new FlowLayoutPanel();
footer.Dock = DockStyle.Fill;
Expand All @@ -565,7 +577,7 @@ private void EditProfile(RemoteProfile p)
footer.Controls.Add(save);

root.Controls.Add(title, 0, 0);
root.Controls.Add(body, 0, 1);
root.Controls.Add(tabs, 0, 1);
root.Controls.Add(footer, 0, 2);
form.Controls.Add(root);
form.AcceptButton = save; form.CancelButton = cancel;
Expand Down
Loading