diff --git a/dnSpy/dnSpy.Contracts.DnSpy/App/IAppInfoBar.cs b/dnSpy/dnSpy.Contracts.DnSpy/App/IAppInfoBar.cs
new file mode 100644
index 0000000000..c43537874b
--- /dev/null
+++ b/dnSpy/dnSpy.Contracts.DnSpy/App/IAppInfoBar.cs
@@ -0,0 +1,33 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+namespace dnSpy.Contracts.App {
+ ///
+ /// App info bar
+ ///
+ public interface IAppInfoBar {
+ ///
+ /// Shows a new info bar element of the specific icon with the given message and interactions.
+ ///
+ /// The message to display
+ /// The icon of message
+ /// Possible interactions on the element
+ public IInfoBarElement Show(string message, InfoBarIcon icon = InfoBarIcon.Information, params InfoBarInteraction[] interactions);
+ }
+}
diff --git a/dnSpy/dnSpy.Contracts.DnSpy/App/IAppWindow.cs b/dnSpy/dnSpy.Contracts.DnSpy/App/IAppWindow.cs
index c0dc4caead..eee77a4197 100644
--- a/dnSpy/dnSpy.Contracts.DnSpy/App/IAppWindow.cs
+++ b/dnSpy/dnSpy.Contracts.DnSpy/App/IAppWindow.cs
@@ -53,6 +53,11 @@ public interface IAppWindow {
///
IAppStatusBar StatusBar { get; }
+ ///
+ /// Gets the instance
+ ///
+ IAppInfoBar InfoBar { get; }
+
///
/// true if the app has been loaded
///
diff --git a/dnSpy/dnSpy.Contracts.DnSpy/App/IInfoBarElement.cs b/dnSpy/dnSpy.Contracts.DnSpy/App/IInfoBarElement.cs
new file mode 100644
index 0000000000..9344ffba53
--- /dev/null
+++ b/dnSpy/dnSpy.Contracts.DnSpy/App/IInfoBarElement.cs
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+namespace dnSpy.Contracts.App {
+ ///
+ /// A single element in the info bar
+ ///
+ public interface IInfoBarElement {
+ ///
+ /// Message being displayed as part of this info bar element
+ ///
+ string Message { get; }
+
+ ///
+ /// Icon being displayed as part of this info bar element
+ ///
+ InfoBarIcon Icon { get; }
+
+ ///
+ /// Closes the info bar element
+ ///
+ void Close();
+ }
+}
diff --git a/dnSpy/dnSpy.Contracts.DnSpy/App/IInfoBarInteractionContext.cs b/dnSpy/dnSpy.Contracts.DnSpy/App/IInfoBarInteractionContext.cs
new file mode 100644
index 0000000000..fc75af2eac
--- /dev/null
+++ b/dnSpy/dnSpy.Contracts.DnSpy/App/IInfoBarInteractionContext.cs
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+namespace dnSpy.Contracts.App {
+ ///
+ /// Context passed to an interaction
+ ///
+ public interface IInfoBarInteractionContext {
+ ///
+ /// Interaction text
+ ///
+ string InteractionText { get; }
+
+ ///
+ /// Closes the owning this interaction
+ ///
+ void CloseElement();
+
+ ///
+ /// Removes the interaction from the
+ ///
+ void RemoveInteraction();
+ }
+}
diff --git a/dnSpy/dnSpy.Contracts.DnSpy/App/InfoBarIcon.cs b/dnSpy/dnSpy.Contracts.DnSpy/App/InfoBarIcon.cs
new file mode 100644
index 0000000000..25c8429f56
--- /dev/null
+++ b/dnSpy/dnSpy.Contracts.DnSpy/App/InfoBarIcon.cs
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+namespace dnSpy.Contracts.App {
+ ///
+ /// Icon displayed in the info bar
+ ///
+ public enum InfoBarIcon {
+ ///
+ /// Information icon
+ ///
+ Information,
+
+ ///
+ /// Warning icon
+ ///
+ Warning,
+
+ ///
+ /// Error icon
+ ///
+ Error
+ }
+}
diff --git a/dnSpy/dnSpy.Contracts.DnSpy/App/InfoBarInteraction.cs b/dnSpy/dnSpy.Contracts.DnSpy/App/InfoBarInteraction.cs
new file mode 100644
index 0000000000..340539a887
--- /dev/null
+++ b/dnSpy/dnSpy.Contracts.DnSpy/App/InfoBarInteraction.cs
@@ -0,0 +1,45 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using System;
+
+namespace dnSpy.Contracts.App {
+ ///
+ /// Interaction on an
+ ///
+ public readonly struct InfoBarInteraction {
+ ///
+ /// Interaction text
+ ///
+ public string Text { get; }
+
+ ///
+ /// Action to perform when the interaction is clicked
+ ///
+ public Action Action { get; }
+
+ ///
+ /// Creates a new
+ ///
+ public InfoBarInteraction(string text, Action action) {
+ Text = text;
+ Action = action;
+ }
+ }
+}
diff --git a/dnSpy/dnSpy.Contracts.DnSpy/Themes/ColorType.cs b/dnSpy/dnSpy.Contracts.DnSpy/Themes/ColorType.cs
index ab5685e65e..83d291d3c5 100644
--- a/dnSpy/dnSpy.Contracts.DnSpy/Themes/ColorType.cs
+++ b/dnSpy/dnSpy.Contracts.DnSpy/Themes/ColorType.cs
@@ -789,6 +789,11 @@ public enum ColorType : uint {
HyperlinkNormal,
HyperlinkMouseOver,
HyperlinkDisabled,
+ InfoBarBackground,
+ InfoBarText,
+ InfoBarInteractionText,
+ InfoBarCloseButton,
+ InfoBarCloseButtonHover,
// Add new color types before this comment
diff --git a/dnSpy/dnSpy/MainApp/AppInfoBar.cs b/dnSpy/dnSpy/MainApp/AppInfoBar.cs
new file mode 100644
index 0000000000..c7caa918e1
--- /dev/null
+++ b/dnSpy/dnSpy/MainApp/AppInfoBar.cs
@@ -0,0 +1,42 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using System.ComponentModel.Composition;
+using dnSpy.Contracts.App;
+using dnSpy.Controls;
+
+namespace dnSpy.MainApp {
+ [Export]
+ sealed class AppInfoBar : IAppInfoBar, IStackedContentChild {
+ readonly InfoBar infoBar;
+ readonly InfoBarVM infoBarVM;
+
+ public object UIObject => infoBar;
+
+ public AppInfoBar() => infoBar = new InfoBar { DataContext = infoBarVM = new InfoBarVM() };
+
+ public IInfoBarElement Show(string message, InfoBarIcon icon = InfoBarIcon.Information, params InfoBarInteraction[] interactions) {
+ var notification = new InfoBarElementVM(infoBarVM, message, icon);
+ foreach (var interaction in interactions)
+ notification.AddInteraction(interaction.Text, interaction.Action);
+ infoBarVM.Elements.Insert(0, notification);
+ return notification;
+ }
+ }
+}
diff --git a/dnSpy/dnSpy/MainApp/AppWindow.cs b/dnSpy/dnSpy/MainApp/AppWindow.cs
index b7135002e9..50883b5ef0 100644
--- a/dnSpy/dnSpy/MainApp/AppWindow.cs
+++ b/dnSpy/dnSpy/MainApp/AppWindow.cs
@@ -42,6 +42,9 @@ sealed class AppWindow : IAppWindow, IDsLoaderContentProvider {
public IAppStatusBar StatusBar => statusBar;
readonly AppStatusBar statusBar;
+ public IAppInfoBar InfoBar => infoBar;
+ readonly AppInfoBar infoBar;
+
Window IAppWindow.MainWindow => mainWindow!;
internal MainWindow MainWindow => mainWindow!;
MainWindow? mainWindow;
@@ -96,7 +99,7 @@ public void Write() {
readonly MainWindowControl mainWindowControl;
[ImportingConstructor]
- AppWindow(ISettingsService settingsService, IDocumentTabService documentTabService, AppToolBar appToolBar, MainWindowControl mainWindowControl, IWpfCommandService wpfCommandService) {
+ AppWindow(ISettingsService settingsService, IDocumentTabService documentTabService, AppToolBar appToolBar, AppInfoBar infoBar, MainWindowControl mainWindowControl, IWpfCommandService wpfCommandService) {
assemblyInformationalVersion = CalculateAssemblyInformationalVersion(GetType().Assembly);
uiSettings = new UISettings(settingsService);
uiSettings.Read();
@@ -104,6 +107,7 @@ public void Write() {
this.documentTabService = documentTabService;
statusBar = new AppStatusBar();
this.appToolBar = appToolBar;
+ this.infoBar = infoBar;
this.mainWindowControl = mainWindowControl;
this.wpfCommandService = wpfCommandService;
mainWindowCommands = wpfCommandService.GetCommands(ControlConstants.GUID_MAINWINDOW);
@@ -124,6 +128,7 @@ static string CalculateAssemblyInformationalVersion(Assembly asm) {
public Window InitializeMainWindow() {
var sc = new StackedContent(false);
sc.AddChild(appToolBar, StackedContentChildInfo.CreateVertical(new GridLength(0, GridUnitType.Auto)));
+ sc.AddChild(infoBar, StackedContentChildInfo.CreateVertical(new GridLength(0, GridUnitType.Auto)));
sc.AddChild(stackedContent, StackedContentChildInfo.CreateVertical(new GridLength(1, GridUnitType.Star)));
sc.AddChild(statusBar, StackedContentChildInfo.CreateVertical(new GridLength(0, GridUnitType.Auto)));
mainWindow = new MainWindow(sc.UIObject);
diff --git a/dnSpy/dnSpy/MainApp/InfoBar.xaml b/dnSpy/dnSpy/MainApp/InfoBar.xaml
new file mode 100644
index 0000000000..40d5f653a3
--- /dev/null
+++ b/dnSpy/dnSpy/MainApp/InfoBar.xaml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dnSpy/dnSpy/MainApp/InfoBar.xaml.cs b/dnSpy/dnSpy/MainApp/InfoBar.xaml.cs
new file mode 100644
index 0000000000..702e51e07f
--- /dev/null
+++ b/dnSpy/dnSpy/MainApp/InfoBar.xaml.cs
@@ -0,0 +1,27 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+
+using System.Windows.Controls;
+
+namespace dnSpy.MainApp {
+ public partial class InfoBar : UserControl {
+ public InfoBar() => InitializeComponent();
+ }
+}
diff --git a/dnSpy/dnSpy/MainApp/InfoBarVM.cs b/dnSpy/dnSpy/MainApp/InfoBarVM.cs
new file mode 100644
index 0000000000..efcafcfcec
--- /dev/null
+++ b/dnSpy/dnSpy/MainApp/InfoBarVM.cs
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using System;
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+using dnSpy.Contracts.App;
+using dnSpy.Contracts.Images;
+using dnSpy.Contracts.MVVM;
+
+namespace dnSpy.MainApp {
+ sealed class InfoBarVM : ViewModelBase {
+ public ObservableCollection Elements { get; } = new ObservableCollection();
+
+ public ICommand RemoveElementCommand { get; }
+
+ public InfoBarVM() => RemoveElementCommand = new RelayCommand(RemoveElement);
+
+ internal void RemoveElement(object? obj) {
+ if (obj is not InfoBarElementVM notification)
+ return;
+ Elements.Remove(notification);
+ }
+ }
+
+ sealed class InfoBarElementVM : ViewModelBase, IInfoBarElement {
+ readonly InfoBarVM parent;
+
+ public string Message { get; }
+
+ public InfoBarIcon Icon { get; }
+
+ public ImageReference Image => Icon switch {
+ InfoBarIcon.Information => DsImages.StatusInformation,
+ InfoBarIcon.Warning => DsImages.StatusWarning,
+ InfoBarIcon.Error => DsImages.StatusError,
+ _ => DsImages.QuestionMark
+ };
+
+ public ObservableCollection Interactions { get; } = new ObservableCollection();
+
+ public InfoBarElementVM(InfoBarVM parent, string message, InfoBarIcon icon) {
+ this.parent = parent;
+ Message = message;
+ Icon = icon;
+ }
+
+ public void Close() => parent.RemoveElement(this);
+
+ public void AddInteraction(string text, Action action) => Interactions.Add(new InfoBarInteractionVM(this, text, action));
+
+ public void RemoveInteraction(InfoBarInteractionVM interaction) => Interactions.Remove(interaction);
+ }
+
+ sealed class InfoBarInteractionVM : ViewModelBase {
+ internal readonly InfoBarElementVM parent;
+
+ public string Text { get; }
+ public ICommand ActionCommand { get; }
+
+ public InfoBarInteractionVM(InfoBarElementVM parent, string text, Action action) {
+ this.parent = parent;
+ Text = text;
+ ActionCommand = new RelayCommand(vm => {
+ if (vm is not InfoBarInteractionVM interactionVM)
+ return;
+ action(new NotificationInteractionContext(interactionVM));
+ });
+ }
+ }
+
+ sealed class NotificationInteractionContext : IInfoBarInteractionContext {
+ readonly InfoBarInteractionVM _interactionVm;
+
+ public string InteractionText => _interactionVm.Text;
+
+ public NotificationInteractionContext(InfoBarInteractionVM interactionVm) => _interactionVm = interactionVm;
+
+ public void CloseElement() => _interactionVm.parent.Close();
+
+ public void RemoveInteraction() => _interactionVm.parent.RemoveInteraction(_interactionVm);
+ }
+}
diff --git a/dnSpy/dnSpy/Themes/ColorInfos.cs b/dnSpy/dnSpy/Themes/ColorInfos.cs
index 5182b678d8..e6fce28ad3 100644
--- a/dnSpy/dnSpy/Themes/ColorInfos.cs
+++ b/dnSpy/dnSpy/Themes/ColorInfos.cs
@@ -1877,6 +1877,26 @@ static class ColorInfos {
DefaultBackground = "#FF6D6D6D",
BackgroundResourceKey = "HyperlinkDisabled",
},
+ new BrushColorInfo(ColorType.InfoBarBackground, "") {
+ DefaultBackground = "#FFFDFBAC",
+ BackgroundResourceKey = "InfoBarBackground",
+ },
+ new BrushColorInfo(ColorType.InfoBarText, "") {
+ DefaultBackground = "#FF000000",
+ BackgroundResourceKey = "InfoBarText",
+ },
+ new BrushColorInfo(ColorType.InfoBarInteractionText, "") {
+ DefaultBackground = "#FF3399FF",
+ BackgroundResourceKey = "InfoBarInteractionText",
+ },
+ new BrushColorInfo(ColorType.InfoBarCloseButton, "") {
+ DefaultBackground = "#FF696969",
+ BackgroundResourceKey = "InfoBarCloseButton",
+ },
+ new BrushColorInfo(ColorType.InfoBarCloseButtonHover, "") {
+ DefaultBackground = "#FF000000",
+ BackgroundResourceKey = "InfoBarCloseButtonHover",
+ },
new BrushColorInfo(ColorType.LineNumber, "Line number"),
new BrushColorInfo(ColorType.ReplLineNumberInput1, "REPL line number #1 (input)"),
new BrushColorInfo(ColorType.ReplLineNumberInput2, "REPL line number #2 (input)"),
diff --git a/dnSpy/dnSpy/Themes/blue.dntheme b/dnSpy/dnSpy/Themes/blue.dntheme
index f3c9f507e4..cfbc2aa701 100644
--- a/dnSpy/dnSpy/Themes/blue.dntheme
+++ b/dnSpy/dnSpy/Themes/blue.dntheme
@@ -749,5 +749,10 @@
+
+
+
+
+
diff --git a/dnSpy/dnSpy/Themes/dark.dntheme b/dnSpy/dnSpy/Themes/dark.dntheme
index 8a6f724791..8e956e70af 100644
--- a/dnSpy/dnSpy/Themes/dark.dntheme
+++ b/dnSpy/dnSpy/Themes/dark.dntheme
@@ -749,5 +749,10 @@
+
+
+
+
+
diff --git a/dnSpy/dnSpy/Themes/light.dntheme b/dnSpy/dnSpy/Themes/light.dntheme
index 815bb335d3..929b1dc6ca 100644
--- a/dnSpy/dnSpy/Themes/light.dntheme
+++ b/dnSpy/dnSpy/Themes/light.dntheme
@@ -317,5 +317,10 @@
+
+
+
+
+