diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index d323bd5..0f4df29 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -34,7 +34,7 @@ concurrency: jobs: native: - uses: SimonCropp/PlatformHeads/.github/workflows/build-native.yml@0.11.0 + uses: SimonCropp/PlatformHeads/.github/workflows/build-native.yml@0.12.0 permissions: contents: write pull-requests: write diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 62c3263..356df52 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -14,7 +14,7 @@ include(FetchContent) FetchContent_Declare( platformheads GIT_REPOSITORY https://github.com/SimonCropp/PlatformHeads.git - GIT_TAG 0.11.0 + GIT_TAG 0.12.0 GIT_SHALLOW ON) FetchContent_MakeAvailable(platformheads) include(${platformheads_SOURCE_DIR}/cmake/PlatformHeads.cmake) diff --git a/native/swift/Package.swift b/native/swift/Package.swift index 9285fc8..bf7c229 100644 --- a/native/swift/Package.swift +++ b/native/swift/Package.swift @@ -12,7 +12,7 @@ let package = Package( dependencies: [ // What every AppKit head does underneath its drawing, shared with DiffEngine's viewer. The // sixth pin of PlatformHeads, moved with the other five. - .package(url: "https://github.com/SimonCropp/PlatformHeads", exact: "0.11.0") + .package(url: "https://github.com/SimonCropp/PlatformHeads", exact: "0.12.0") ], targets: [ // Exists only to import the ABI. Swift does not guarantee struct layout, so the structs diff --git a/src/BuildMonitor.Core/App/UpdateOutcome.cs b/src/BuildMonitor.Core/App/UpdateOutcome.cs index 5b84c9f..14153bd 100644 --- a/src/BuildMonitor.Core/App/UpdateOutcome.cs +++ b/src/BuildMonitor.Core/App/UpdateOutcome.cs @@ -1,9 +1,9 @@ /// -/// How the last update went, for the tray it started again to report, worded from PlatformHeads' -/// . The update runs in a shell nobody sees, after the tray has -/// exited, so without this there is nothing to see at either end of it: the icon goes away and one -/// comes back, and whether that is the new version or the old one after a failure looks exactly -/// the same. +/// How the last update went, for the tray it started again to report, through PlatformHeads' +/// . The update runs in a shell nobody sees, after the tray +/// has exited, so without this there is nothing to see at either end of it: the icon goes away and +/// one comes back, and whether that is the new version or the old one after a failure looks +/// exactly the same. /// static class UpdateOutcome { @@ -13,41 +13,22 @@ static class UpdateOutcome /// outcome is reported once rather than at every start, and the whole output goes to the log, /// since a notification has room for a line of it. /// - public static Notification? Take(string path) - { - ToolUpdateOutcome? outcome; - try - { - outcome = ToolUpdateOutcome.Take(path); - } - catch (Exception exception) when (exception is IOException or UnauthorizedAccessException) - { - Log.Warning(exception, "Could not read the update outcome at {Path}", path); - return null; - } - - if (outcome is null) - { - return null; - } + public static Notification? Take(string path) => + ToolUpdateOutcome.Report( + path, + "BuildMonitor", + VersionReader.VersionString, + Taken, + _ => Log.Warning(_, "Could not read the update outcome at {Path}", path)); + static void Taken(ToolUpdateOutcome outcome) + { if (outcome.Succeeded) { Log.Information("Update finished. dotnet tool update said:\n{Output}", outcome.Output); - return Describe(); + return; } Log.Warning("Update failed, so {Version} is still the one installed. dotnet tool update said:\n{Output}", VersionReader.VersionString, outcome.Output); - return Describe(outcome.Output); } - - /// - /// The version rather than what the tool said, because this is the tray the update started: it - /// is the installed one, whether the update moved it or found it already current. - /// - public static Notification Describe() => - new("BuildMonitor updated", $"Now running {VersionReader.VersionString}.", Kind: NotificationKind.Info); - - public static Notification Describe(string output) => - new("Update failed", new ToolUpdateOutcome(false, output).Reason); } diff --git a/src/BuildMonitor.Native/Native/BmStructs.cs b/src/BuildMonitor.Native/Native/BmStructs.cs index 4d6efd3..7962f56 100644 --- a/src/BuildMonitor.Native/Native/BmStructs.cs +++ b/src/BuildMonitor.Native/Native/BmStructs.cs @@ -231,7 +231,5 @@ static class BmFlags public const int RowMember = 1 << 3; public const int FieldEnabled = 1 << 0; public const int ButtonEnabled = 1 << 0; - public const int TrayEnabled = 1 << 0; - public const int TraySeparator = 1 << 1; public const int MenuSeparatorAbove = 1 << 0; } diff --git a/src/BuildMonitor.Native/Native/ScreenPayload.cs b/src/BuildMonitor.Native/Native/ScreenPayload.cs index e93250d..768773e 100644 --- a/src/BuildMonitor.Native/Native/ScreenPayload.cs +++ b/src/BuildMonitor.Native/Native/ScreenPayload.cs @@ -266,7 +266,9 @@ public void Build(Screen source) Id = Add(item.Id), Label = Add(item.Label), Icon = Add(item.IconName ?? ""), - Flags = (item.Enabled ? BmFlags.TrayEnabled : 0) | (item.Separator ? BmFlags.TraySeparator : 0) + // PlatformHeads' TrayMenuFlags, whose first two are BM_TRAY_ENABLED and + // BM_TRAY_SEPARATOR. The ABI carries no checks, so the rest stay behind. + Flags = (int) (item.Flags & (TrayMenuFlags.Enabled | TrayMenuFlags.Separator)) }); } } diff --git a/src/BuildMonitor.Native/runtimes/osx-arm64/native/libbuildmonitor_ui.dylib b/src/BuildMonitor.Native/runtimes/osx-arm64/native/libbuildmonitor_ui.dylib index 0e70c91..a4a3689 100644 Binary files a/src/BuildMonitor.Native/runtimes/osx-arm64/native/libbuildmonitor_ui.dylib and b/src/BuildMonitor.Native/runtimes/osx-arm64/native/libbuildmonitor_ui.dylib differ diff --git a/src/BuildMonitor.Native/runtimes/osx-x64/native/libbuildmonitor_ui.dylib b/src/BuildMonitor.Native/runtimes/osx-x64/native/libbuildmonitor_ui.dylib index 0e70c91..a4a3689 100644 Binary files a/src/BuildMonitor.Native/runtimes/osx-x64/native/libbuildmonitor_ui.dylib and b/src/BuildMonitor.Native/runtimes/osx-x64/native/libbuildmonitor_ui.dylib differ diff --git a/src/BuildMonitor.Tests/UpdateOutcomeTests.cs b/src/BuildMonitor.Tests/UpdateOutcomeTests.cs index d69489b..2653dde 100644 --- a/src/BuildMonitor.Tests/UpdateOutcomeTests.cs +++ b/src/BuildMonitor.Tests/UpdateOutcomeTests.cs @@ -21,7 +21,7 @@ public async Task TheFailureIsReportedOnceAndThenRemoved() await File.WriteAllTextAsync(path, locked, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); var notification = UpdateOutcome.Take(path); - await Assert.That(notification).IsEqualTo(new("Update failed", """Failed to uninstall tool package 'buildmonitor': Access to the path 'C:\Users\me\.dotnet\tools\.store\buildmonitor\0.1.0-beta.6' is denied.""")); + await Assert.That(notification).IsEqualTo(new("BuildMonitor did not update", """Failed to uninstall tool package 'buildmonitor': Access to the path 'C:\Users\me\.dotnet\tools\.store\buildmonitor\0.1.0-beta.6' is denied.""")); await Assert.That(File.Exists(path)).IsFalse(); await Assert.That(UpdateOutcome.Take(path)).IsNull(); } @@ -54,7 +54,7 @@ public async Task OutputWithNoStatusLineIsAFailure() { using var path = new TempFile(); await File.WriteAllTextAsync(path, "Tool 'buildmonitor' failed to update due to the following:\nIt did not work.\n"); - await Assert.That(UpdateOutcome.Take(path)).IsEqualTo(new("Update failed", "It did not work.")); + await Assert.That(UpdateOutcome.Take(path)).IsEqualTo(new("BuildMonitor did not update", "It did not work.")); } [Test] @@ -66,11 +66,21 @@ public async Task NoFileMeansNothingToReport() => /// at the end are what the shells leave. /// [Test] - public async Task TheReasonIsTheLastLineWritten() => - await Assert.That(UpdateOutcome.Describe("Tool 'buildmonitor' failed to update due to the following:\r\nUnable to load the service index for source https://api.nuget.org/v3/index.json.\r\n\r\n").Message) + public async Task TheReasonIsTheLastLineWritten() + { + using var path = new TempFile(); + await File.WriteAllTextAsync(path, "failed\r\nTool 'buildmonitor' failed to update due to the following:\r\nUnable to load the service index for source https://api.nuget.org/v3/index.json.\r\n\r\n"); + + await Assert.That(UpdateOutcome.Take(path)?.Message) .IsEqualTo("Unable to load the service index for source https://api.nuget.org/v3/index.json."); + } [Test] - public async Task NoOutputStillSaysTheUpdateFailed() => - await Assert.That(UpdateOutcome.Describe(" \n")).IsEqualTo(new("Update failed", "dotnet tool update failed without saying why.")); + public async Task NoOutputStillSaysTheUpdateFailed() + { + using var path = new TempFile(); + await File.WriteAllTextAsync(path, "failed\n \n"); + + await Assert.That(UpdateOutcome.Take(path)).IsEqualTo(new("BuildMonitor did not update", "dotnet tool update failed without saying why.")); + } } diff --git a/src/BuildMonitor.Windows/MenuColours.cs b/src/BuildMonitor.Windows/MenuColours.cs deleted file mode 100644 index befea1b..0000000 --- a/src/BuildMonitor.Windows/MenuColours.cs +++ /dev/null @@ -1,20 +0,0 @@ -/// -/// Dark menus. Assigned per strip rather than through ToolStripManager.Renderer, which is -/// process wide state a test host would share. -/// -sealed class MenuColours : ProfessionalColorTable -{ - public override Color MenuItemSelected => Palette.SelectedRow; - public override Color MenuItemSelectedGradientBegin => Palette.SelectedRow; - public override Color MenuItemSelectedGradientEnd => Palette.SelectedRow; - public override Color MenuItemPressedGradientBegin => Palette.SelectedRow; - public override Color MenuItemPressedGradientEnd => Palette.SelectedRow; - public override Color MenuItemBorder => Palette.Border; - public override Color MenuBorder => Palette.Border; - public override Color ToolStripDropDownBackground => Palette.Surface; - public override Color ImageMarginGradientBegin => Palette.Surface; - public override Color ImageMarginGradientMiddle => Palette.Surface; - public override Color ImageMarginGradientEnd => Palette.Surface; - public override Color SeparatorDark => Palette.Border; - public override Color SeparatorLight => Palette.Border; -} diff --git a/src/BuildMonitor.Windows/MenuRenderer.cs b/src/BuildMonitor.Windows/MenuRenderer.cs deleted file mode 100644 index c0eedbd..0000000 --- a/src/BuildMonitor.Windows/MenuRenderer.cs +++ /dev/null @@ -1,14 +0,0 @@ -sealed class MenuRenderer() : ToolStripProfessionalRenderer(new MenuColours()) -{ - protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs args) - { - args.TextColor = args.Item.Enabled ? Palette.Text : Palette.Dim; - base.OnRenderItemText(args); - } - - protected override void OnRenderArrow(ToolStripArrowRenderEventArgs args) - { - args.ArrowColor = Palette.Text; - base.OnRenderArrow(args); - } -} diff --git a/src/BuildMonitor.Windows/MenuTheme.cs b/src/BuildMonitor.Windows/MenuTheme.cs index 68b4db5..f05b36c 100644 --- a/src/BuildMonitor.Windows/MenuTheme.cs +++ b/src/BuildMonitor.Windows/MenuTheme.cs @@ -1,9 +1,25 @@ +/// +/// Dark or light menus, drawn by PlatformHeads' . Applied each +/// time a menu opens, so the renderer is made from the palette as it is then, and a menu follows a +/// change of theme as it did when the renderer read the palette itself. +/// static class MenuTheme { public static void Apply(ToolStrip strip) { - strip.Renderer = new MenuRenderer(); + strip.Renderer = new PaletteMenuRenderer(Colours()); strip.BackColor = Palette.Surface; strip.ForeColor = Palette.Text; } + + static MenuPalette Colours() => + new( + Background: Palette.Surface, + Border: Palette.Border, + ItemBorder: Palette.Border, + Selected: Palette.SelectedRow, + Pressed: Palette.SelectedRow, + Separator: Palette.Border, + Text: Palette.Text, + Dim: Palette.Dim); } diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 77d17f5..222d675 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -14,10 +14,10 @@ - - - - + + + +