mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-04-30 23:34:09 +02:00
Create an API for picking which screen notifications are displayed on (WIP)
This commit is contained in:
parent
095c23b472
commit
c2b5cf23cf
lib
TweetLib.Api
Data/Notification
Service
TweetLib.Core/Systems/Configuration
windows/TweetDuck
@ -0,0 +1,28 @@
|
|||||||
|
namespace TweetLib.Api.Data.Notification {
|
||||||
|
/// <summary>
|
||||||
|
/// Allows extensions to decide which screen to use for notifications.
|
||||||
|
///
|
||||||
|
/// Every registered provider becomes available in the Options dialog and has to be explicitly selected by the user. Only one provider
|
||||||
|
/// can be active at any given time.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDesktopNotificationScreenProvider {
|
||||||
|
/// <summary>
|
||||||
|
/// A unique identifier of this provider. Only needs to be unique within the scope of this plugin.
|
||||||
|
/// </summary>
|
||||||
|
Resource Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text displayed in the user interface.
|
||||||
|
/// </summary>
|
||||||
|
string DisplayName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a screen that will be used to display the next desktop notification.
|
||||||
|
///
|
||||||
|
/// If the return value is <c>null</c> or a screen that is not present in <see cref="IScreenLayout.AllScreens" />, desktop
|
||||||
|
/// notifications will be temporarily paused and this method will be called again after an unspecified amount of time (but
|
||||||
|
/// not sooner than 1 second since the last call).
|
||||||
|
/// </summary>
|
||||||
|
IScreen? PickScreen(IScreenLayout layout);
|
||||||
|
}
|
||||||
|
}
|
7
lib/TweetLib.Api/Data/Notification/IScreen.cs
Normal file
7
lib/TweetLib.Api/Data/Notification/IScreen.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace TweetLib.Api.Data.Notification {
|
||||||
|
public interface IScreen {
|
||||||
|
ScreenBounds Bounds { get; }
|
||||||
|
string Name { get; }
|
||||||
|
bool IsPrimary { get; }
|
||||||
|
}
|
||||||
|
}
|
9
lib/TweetLib.Api/Data/Notification/IScreenLayout.cs
Normal file
9
lib/TweetLib.Api/Data/Notification/IScreenLayout.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace TweetLib.Api.Data.Notification {
|
||||||
|
public interface IScreenLayout {
|
||||||
|
IScreen PrimaryScreen { get; }
|
||||||
|
IScreen TweetDuckScreen { get; }
|
||||||
|
List<IScreen> AllScreens { get; }
|
||||||
|
}
|
||||||
|
}
|
18
lib/TweetLib.Api/Data/Notification/ScreenBounds.cs
Normal file
18
lib/TweetLib.Api/Data/Notification/ScreenBounds.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace TweetLib.Api.Data.Notification {
|
||||||
|
public readonly struct ScreenBounds {
|
||||||
|
public int X1 { get; }
|
||||||
|
public int Y1 { get; }
|
||||||
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
|
||||||
|
public int X2 => X1 + Width;
|
||||||
|
public int Y2 => Y1 + Height;
|
||||||
|
|
||||||
|
public ScreenBounds(int x1, int y1, int width, int height) {
|
||||||
|
X1 = x1;
|
||||||
|
Y1 = y1;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
lib/TweetLib.Api/Service/INotificationService.cs
Normal file
7
lib/TweetLib.Api/Service/INotificationService.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
using TweetLib.Api.Data.Notification;
|
||||||
|
|
||||||
|
namespace TweetLib.Api.Service {
|
||||||
|
public interface INotificationService : ITweetDuckService {
|
||||||
|
void RegisterDesktopNotificationScreenProvider(IDesktopNotificationScreenProvider provider);
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
namespace TweetLib.Core.Systems.Configuration {
|
namespace TweetLib.Core.Systems.Configuration {
|
||||||
public abstract class ConfigManager {
|
public abstract class ConfigManager {
|
||||||
protected static TypeConverterRegistry ConverterRegistry { get; } = new ();
|
public static TypeConverterRegistry ConverterRegistry { get; } = new ();
|
||||||
|
|
||||||
static ConfigManager() {
|
static ConfigManager() {
|
||||||
ConverterRegistry.Register(typeof(WindowState), new BasicTypeConverter<WindowState> {
|
ConverterRegistry.Register(typeof(WindowState), new BasicTypeConverter<WindowState> {
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using TweetDuck.Application.Service;
|
||||||
using TweetLib.Api;
|
using TweetLib.Api;
|
||||||
using TweetLib.Api.Data;
|
using TweetLib.Api.Data;
|
||||||
|
using TweetLib.Api.Service;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
|
|
||||||
namespace TweetDuck.Application {
|
namespace TweetDuck.Application {
|
||||||
static class ApiServices {
|
static class ApiServices {
|
||||||
|
public static NotificationService Notifications { get; } = new NotificationService();
|
||||||
|
|
||||||
public static void Register() {
|
public static void Register() {
|
||||||
|
App.Api.RegisterService<INotificationService>(Notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static NamespacedResource Namespace(Resource path) {
|
internal static NamespacedResource Namespace(Resource path) {
|
||||||
|
33
windows/TweetDuck/Application/Service/NotificationService.cs
Normal file
33
windows/TweetDuck/Application/Service/NotificationService.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using TweetLib.Api.Data;
|
||||||
|
using TweetLib.Api.Data.Notification;
|
||||||
|
using TweetLib.Api.Service;
|
||||||
|
|
||||||
|
namespace TweetDuck.Application.Service {
|
||||||
|
sealed class NotificationService : INotificationService {
|
||||||
|
private readonly List<NamespacedProvider> desktopNotificationScreenProviders = new ();
|
||||||
|
|
||||||
|
public void RegisterDesktopNotificationScreenProvider(IDesktopNotificationScreenProvider provider) {
|
||||||
|
desktopNotificationScreenProviders.Add(new NamespacedProvider(ApiServices.Namespace(provider.Id), provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NamespacedProvider> GetDesktopNotificationScreenProviders() {
|
||||||
|
return desktopNotificationScreenProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class NamespacedProvider : IDesktopNotificationScreenProvider {
|
||||||
|
public NamespacedResource NamespacedId { get; }
|
||||||
|
|
||||||
|
private readonly IDesktopNotificationScreenProvider provider;
|
||||||
|
|
||||||
|
public NamespacedProvider(NamespacedResource id, IDesktopNotificationScreenProvider provider) {
|
||||||
|
this.NamespacedId = id;
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource Id => provider.Id;
|
||||||
|
public string DisplayName => provider.DisplayName;
|
||||||
|
public IScreen? PickScreen(IScreenLayout layout) => provider.PickScreen(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,15 +22,7 @@ abstract partial class FormNotificationBase : Form {
|
|||||||
|
|
||||||
protected virtual Point PrimaryLocation {
|
protected virtual Point PrimaryLocation {
|
||||||
get {
|
get {
|
||||||
Screen screen;
|
Screen screen = Config.NotificationDisplay.PickScreen(owner);
|
||||||
|
|
||||||
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length) {
|
|
||||||
screen = Screen.AllScreens[Config.NotificationDisplay - 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
screen = Screen.FromControl(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
int edgeDist = Config.NotificationEdgeDistance;
|
int edgeDist = Config.NotificationEdgeDistance;
|
||||||
|
|
||||||
switch (Config.NotificationPosition) {
|
switch (Config.NotificationPosition) {
|
||||||
|
173
windows/TweetDuck/Browser/Notification/NotificationScreen.cs
Normal file
173
windows/TweetDuck/Browser/Notification/NotificationScreen.cs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using TweetDuck.Application;
|
||||||
|
using TweetDuck.Application.Service;
|
||||||
|
using TweetLib.Api.Data;
|
||||||
|
using TweetLib.Api.Data.Notification;
|
||||||
|
using TweetLib.Utils.Serialization.Converters;
|
||||||
|
using TweetLib.Utils.Static;
|
||||||
|
|
||||||
|
namespace TweetDuck.Browser.Notification {
|
||||||
|
abstract class NotificationScreen {
|
||||||
|
public static List<NotificationScreen> All {
|
||||||
|
get {
|
||||||
|
var list = new List<NotificationScreen> {
|
||||||
|
SameAsTweetDuck.Instance
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int index = 1; index <= Screen.AllScreens.Length; index++) {
|
||||||
|
list.Add(new Static(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var provider in ApiServices.Notifications.GetDesktopNotificationScreenProviders()) {
|
||||||
|
list.Add(new Provided(provider.NamespacedId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract string DisplayName { get; }
|
||||||
|
|
||||||
|
private NotificationScreen() {}
|
||||||
|
|
||||||
|
public abstract Screen PickScreen(FormBrowser mainWindow);
|
||||||
|
|
||||||
|
protected abstract string Serialize();
|
||||||
|
|
||||||
|
public sealed class SameAsTweetDuck : NotificationScreen {
|
||||||
|
public static SameAsTweetDuck Instance { get; } = new SameAsTweetDuck();
|
||||||
|
|
||||||
|
public override string DisplayName => "(Same as TweetDuck)";
|
||||||
|
|
||||||
|
private SameAsTweetDuck() {}
|
||||||
|
|
||||||
|
public override Screen PickScreen(FormBrowser mainWindow) {
|
||||||
|
return Screen.FromControl(mainWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Serialize() {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) {
|
||||||
|
return obj is SameAsTweetDuck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() {
|
||||||
|
return 1828695039;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Static : NotificationScreen {
|
||||||
|
public override string DisplayName {
|
||||||
|
get {
|
||||||
|
Screen? screen = Screen;
|
||||||
|
if (screen == null) {
|
||||||
|
return $"Unknown ({screenIndex})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return screen.DeviceName.TrimStart('\\', '.') + $" ({screen.Bounds.Width}x{screen.Bounds.Height})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Screen? Screen => screenIndex >= 1 && screenIndex <= Screen.AllScreens.Length ? Screen.AllScreens[screenIndex - 1] : null;
|
||||||
|
|
||||||
|
private readonly int screenIndex;
|
||||||
|
|
||||||
|
public Static(int screenIndex) {
|
||||||
|
this.screenIndex = screenIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Screen PickScreen(FormBrowser mainWindow) {
|
||||||
|
return Screen ?? SameAsTweetDuck.Instance.PickScreen(mainWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Serialize() {
|
||||||
|
return screenIndex.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) {
|
||||||
|
return obj is Static other && screenIndex == other.screenIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() {
|
||||||
|
return 31 * screenIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Provided : NotificationScreen {
|
||||||
|
public override string DisplayName => Provider?.DisplayName ?? $"Unknown ({resource})";
|
||||||
|
|
||||||
|
private readonly NamespacedResource resource;
|
||||||
|
|
||||||
|
private NotificationService.NamespacedProvider? provider;
|
||||||
|
private NotificationService.NamespacedProvider? Provider => provider ??= ApiServices.Notifications.GetDesktopNotificationScreenProviders().Find(p => p.NamespacedId == resource);
|
||||||
|
|
||||||
|
public Provided(NamespacedResource resource) {
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Screen PickScreen(FormBrowser mainWindow) {
|
||||||
|
IScreen? pick = Provider?.PickScreen(new WindowsFormsScreenLayout(mainWindow));
|
||||||
|
return pick is WindowsFormsScreen screen ? screen.Screen : SameAsTweetDuck.Instance.PickScreen(mainWindow); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Serialize() {
|
||||||
|
return resource.Namespace + ":" + resource.Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) {
|
||||||
|
return obj is Provided other && resource == other.resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode() {
|
||||||
|
return resource.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class WindowsFormsScreenLayout : IScreenLayout {
|
||||||
|
public IScreen PrimaryScreen => new WindowsFormsScreen(Screen.PrimaryScreen);
|
||||||
|
public IScreen TweetDuckScreen => new WindowsFormsScreen(Screen.FromControl(mainWindow));
|
||||||
|
public List<IScreen> AllScreens => Screen.AllScreens.Select(static screen => new WindowsFormsScreen(screen)).ToList<IScreen>();
|
||||||
|
|
||||||
|
private readonly FormBrowser mainWindow;
|
||||||
|
|
||||||
|
public WindowsFormsScreenLayout(FormBrowser mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class WindowsFormsScreen : IScreen {
|
||||||
|
public Screen Screen { get; }
|
||||||
|
public ScreenBounds Bounds { get; }
|
||||||
|
public string Name => Screen.DeviceName;
|
||||||
|
public bool IsPrimary => Screen.Primary;
|
||||||
|
|
||||||
|
public WindowsFormsScreen(Screen screen) {
|
||||||
|
this.Screen = screen;
|
||||||
|
this.Bounds = new ScreenBounds(screen.Bounds.X, screen.Bounds.Y, screen.Bounds.Width, screen.Bounds.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly BasicTypeConverter<NotificationScreen> Converter = new() {
|
||||||
|
ConvertToString = static value => value.Serialize(),
|
||||||
|
ConvertToObject = static value => {
|
||||||
|
if (value == "0") {
|
||||||
|
return SameAsTweetDuck.Instance;
|
||||||
|
}
|
||||||
|
else if (int.TryParse(value, out int index)) {
|
||||||
|
return new Static(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = StringUtils.SplitInTwo(value, ':');
|
||||||
|
if (resource != null) {
|
||||||
|
return new Provided(new NamespacedResource(new Resource(resource.Value.before), new Resource(resource.Value.after)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return SameAsTweetDuck.Instance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Browser;
|
||||||
|
using TweetDuck.Browser.Notification;
|
||||||
using TweetDuck.Controls;
|
using TweetDuck.Controls;
|
||||||
using TweetLib.Core;
|
using TweetLib.Core;
|
||||||
using TweetLib.Core.Application;
|
using TweetLib.Core.Application;
|
||||||
@ -66,7 +67,7 @@ sealed class UserConfig : BaseConfig<UserConfig>, IAppUserConfiguration {
|
|||||||
|
|
||||||
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
|
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
|
||||||
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
|
||||||
public int NotificationDisplay { get; set; } = 0;
|
public NotificationScreen NotificationDisplay { get; set; } = NotificationScreen.SameAsTweetDuck.Instance;
|
||||||
public int NotificationEdgeDistance { get; set; } = 8;
|
public int NotificationEdgeDistance { get; set; } = 8;
|
||||||
public int NotificationWindowOpacity { get; set; } = 100;
|
public int NotificationWindowOpacity { get; set; } = 100;
|
||||||
|
|
||||||
|
@ -86,13 +86,21 @@ public TabSettingsNotifications(FormNotificationExample notification) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
|
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
|
||||||
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
|
|
||||||
|
|
||||||
foreach (Screen screen in Screen.AllScreens) {
|
bool foundScreen = false;
|
||||||
comboBoxDisplay.Items.Add($"{screen.DeviceName.TrimStart('\\', '.')} ({screen.Bounds.Width}x{screen.Bounds.Height})");
|
|
||||||
|
foreach (var screen in NotificationScreen.All) {
|
||||||
|
comboBoxDisplay.Items.Add(new NotificationScreenItem(screen));
|
||||||
|
if (screen.Equals(Config.NotificationDisplay)) {
|
||||||
|
comboBoxDisplay.SelectedIndex = comboBoxDisplay.Items.Count - 1;
|
||||||
|
foundScreen = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count - 1, Config.NotificationDisplay);
|
if (!foundScreen) {
|
||||||
|
comboBoxDisplay.Items.Add(new NotificationScreenItem(Config.NotificationDisplay));
|
||||||
|
comboBoxDisplay.SelectedIndex = comboBoxDisplay.Items.Count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
||||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
|
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
|
||||||
@ -279,7 +287,7 @@ private void radioLocCustom_Click(object? sender, EventArgs e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void comboBoxDisplay_SelectedValueChanged(object? sender, EventArgs e) {
|
private void comboBoxDisplay_SelectedValueChanged(object? sender, EventArgs e) {
|
||||||
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
|
Config.NotificationDisplay = ((NotificationScreenItem) comboBoxDisplay.SelectedItem).Screen;
|
||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +297,21 @@ private void trackBarEdgeDistance_ValueChanged(object? sender, EventArgs e) {
|
|||||||
notification.ShowExampleNotification(false);
|
notification.ShowExampleNotification(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class NotificationScreenItem {
|
||||||
|
public NotificationScreen Screen { get; }
|
||||||
|
|
||||||
|
private readonly string displayName;
|
||||||
|
|
||||||
|
public NotificationScreenItem(NotificationScreen screen) {
|
||||||
|
this.Screen = screen;
|
||||||
|
this.displayName = screen.DisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Size
|
#region Size
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using TweetDuck.Application;
|
using TweetDuck.Application;
|
||||||
using TweetDuck.Browser;
|
using TweetDuck.Browser;
|
||||||
using TweetDuck.Browser.Base;
|
using TweetDuck.Browser.Base;
|
||||||
|
using TweetDuck.Browser.Notification;
|
||||||
using TweetDuck.Configuration;
|
using TweetDuck.Configuration;
|
||||||
using TweetDuck.Dialogs;
|
using TweetDuck.Dialogs;
|
||||||
using TweetDuck.Management;
|
using TweetDuck.Management;
|
||||||
@ -91,6 +92,7 @@ private sealed class Setup : IAppSetup {
|
|||||||
public string? ResourceRewriteRules => Arguments.GetValue(Arguments.ArgFreeze);
|
public string? ResourceRewriteRules => Arguments.GetValue(Arguments.ArgFreeze);
|
||||||
|
|
||||||
public ConfigManager CreateConfigManager(string storagePath) {
|
public ConfigManager CreateConfigManager(string storagePath) {
|
||||||
|
ConfigManager.ConverterRegistry.Register(typeof(NotificationScreen), NotificationScreen.Converter);
|
||||||
return new ConfigManager<UserConfig, SystemConfig>(storagePath, Config);
|
return new ConfigManager<UserConfig, SystemConfig>(storagePath, Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user