Skip to content

Commit 8046d22

Browse files
committed
feat(screenshot): add visual notification for screenshot capture
- Add ShowNotification setting to enable/disable capture notifications - Implement notification window with image thumbnail and capture type label - Support Windows light/dark mode for notification styling - Play Windows notification sound on capture - Display notification at bottom-right with fade-in/out effects and auto-close timer - Integrate notification calls after each screenshot capture method - Enhance logging for notification display and errors - Update build scripts and packaging to include modified plugin DLLs and resources
1 parent 4dfacb3 commit 8046d22

File tree

13 files changed

+1002
-0
lines changed

13 files changed

+1002
-0
lines changed

.github/Dark_Toast.png

20.5 KB
Loading

.github/Light_Toast.png

22.9 KB
Loading

.github/Skin_Preview.png

17.1 KB
Loading
-56.8 KB
Binary file not shown.

Build.ps1

Lines changed: 660 additions & 0 deletions
Large diffs are not rendered by default.

FinalShot/FinalShot.cs

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.Linq;
77
using System.Runtime.InteropServices;
88
using System.Windows.Forms;
9+
using System.Media;
10+
using Microsoft.Win32;
911
using Rainmeter;
1012

1113
namespace PluginScreenshot
@@ -62,6 +64,7 @@ public class Settings
6264
public bool ShowCursor { get; private set; }
6365
public int JpegQuality { get; private set; }
6466
public Rectangle PredefinedRegion { get; private set; }
67+
public bool ShowNotification { get; private set; }
6568

6669
public Settings(API api)
6770
{
@@ -70,6 +73,7 @@ public Settings(API api)
7073
FinishAction = api.ReadString("ScreenshotFinishAction", "");
7174
ShowCursor = api.ReadInt("ShowCursor", 0) > 0;
7275
JpegQuality = api.ReadInt("JpgQuality", 70);
76+
ShowNotification = api.ReadInt("ShowNotification", 0) > 0;
7377
int x = api.ReadInt("PredefX", 0);
7478
int y = api.ReadInt("PredefY", 0);
7579
int w = api.ReadInt("PredefWidth", 0);
@@ -179,6 +183,12 @@ public static void TakeFullScreen(Settings settings)
179183
}
180184
});
181185

186+
if (settings.ShowNotification)
187+
{
188+
Logger.Log("TakeFullScreen: ShowNotification is enabled");
189+
ShowNotificationWithImage(settings.SavePath, "Full Screen");
190+
}
191+
182192
ExecuteFinishAction(settings);
183193
}
184194

@@ -200,6 +210,12 @@ public static void TakePredefined(Settings settings)
200210
}
201211
});
202212

213+
if (settings.ShowNotification)
214+
{
215+
Logger.Log("TakePredefined: ShowNotification is enabled");
216+
ShowNotificationWithImage(settings.SavePath, "Predefined Region");
217+
}
218+
203219
ExecuteFinishAction(settings);
204220
}
205221

@@ -267,6 +283,12 @@ public static void TakeWindowScreenshot(Settings settings, string windowTitle)
267283
}
268284
});
269285

286+
if (settings.ShowNotification)
287+
{
288+
Logger.Log("TakeWindowScreenshot: ShowNotification is enabled");
289+
ShowNotificationWithImage(settings.SavePath, $"Window: {windowTitle}");
290+
}
291+
270292
ExecuteFinishAction(settings);
271293
}
272294

@@ -303,6 +325,12 @@ public static void CompositeCapture(Rectangle rect, Settings settings)
303325

304326
SaveImageSafely(finalBmp, settings);
305327
}
328+
329+
if (settings.ShowNotification)
330+
{
331+
Logger.Log("CompositeCapture: ShowNotification is enabled");
332+
ShowNotificationWithImage(settings.SavePath, "Custom Region");
333+
}
306334
}
307335

308336

@@ -381,6 +409,54 @@ public static void ExecuteFinishAction(Settings settings)
381409
Logger.Log("Error running finish action: " + ex.Message);
382410
}
383411
}
412+
413+
private static void ShowNotificationWithImage(string imagePath, string captureType)
414+
{
415+
try
416+
{
417+
if (!File.Exists(imagePath))
418+
{
419+
Logger.Log($"ShowNotificationWithImage: Image file not found at '{imagePath}'");
420+
return;
421+
}
422+
423+
Logger.Log($"ShowNotificationWithImage: Creating notification for '{captureType}'");
424+
425+
// Play Windows notification sound
426+
try
427+
{
428+
SystemSounds.Asterisk.Play();
429+
Logger.Log("Notification sound played");
430+
}
431+
catch (Exception ex)
432+
{
433+
Logger.Log($"Failed to play notification sound: {ex.Message}");
434+
}
435+
436+
// Create notification form on a separate thread
437+
var notificationThread = new System.Threading.Thread(() =>
438+
{
439+
try
440+
{
441+
Logger.Log("Notification thread started");
442+
Application.Run(new NotificationForm(imagePath, captureType));
443+
}
444+
catch (Exception ex)
445+
{
446+
Logger.Log($"Notification thread error: {ex.Message}");
447+
}
448+
});
449+
notificationThread.SetApartmentState(System.Threading.ApartmentState.STA);
450+
notificationThread.IsBackground = true;
451+
notificationThread.Start();
452+
453+
Logger.Log($"Notification thread started for '{captureType}' capture.");
454+
}
455+
catch (Exception ex)
456+
{
457+
Logger.Log($"Error showing notification: {ex.Message}");
458+
}
459+
}
384460
}
385461

386462
public class CustomScreenshotForm : Form
@@ -470,6 +546,251 @@ private void OnPaint(object s, PaintEventArgs e)
470546
}
471547
}
472548

549+
public class NotificationForm : Form
550+
{
551+
private readonly Timer _autoCloseTimer;
552+
private readonly Timer _fadeOutTimer;
553+
private readonly string _imagePath;
554+
private const int NotificationWidth = 380;
555+
private const int NotificationHeight = 120;
556+
private const int DisplayDuration = 4000; // 4 seconds
557+
private const int FadeOutDuration = 500; // 0.5 seconds
558+
private double _opacity = 1.0;
559+
private readonly bool _isDarkMode;
560+
561+
public NotificationForm(string imagePath, string captureType)
562+
{
563+
_imagePath = imagePath;
564+
_isDarkMode = IsWindowsDarkMode();
565+
566+
// Form settings
567+
FormBorderStyle = FormBorderStyle.None;
568+
StartPosition = FormStartPosition.Manual;
569+
TopMost = true;
570+
ShowInTaskbar = false;
571+
Width = NotificationWidth;
572+
Height = NotificationHeight;
573+
BackColor = _isDarkMode ? Color.FromArgb(30, 30, 30) : Color.FromArgb(240, 240, 240);
574+
Opacity = 0;
575+
576+
// Position at bottom-right of screen
577+
var workingArea = Screen.PrimaryScreen.WorkingArea;
578+
Location = new Point(
579+
workingArea.Right - Width - 20,
580+
workingArea.Bottom - Height - 20
581+
);
582+
583+
// Create UI
584+
CreateNotificationUI(captureType);
585+
586+
// Auto-close timer
587+
_autoCloseTimer = new Timer { Interval = DisplayDuration };
588+
_autoCloseTimer.Tick += (s, e) =>
589+
{
590+
_autoCloseTimer.Stop();
591+
StartFadeOut();
592+
};
593+
594+
// Fade-out timer
595+
_fadeOutTimer = new Timer { Interval = 20 };
596+
_fadeOutTimer.Tick += FadeOutTick;
597+
598+
// Click to close
599+
Click += (s, e) => StartFadeOut();
600+
601+
// Show with fade-in effect
602+
Load += (s, e) =>
603+
{
604+
FadeIn();
605+
_autoCloseTimer.Start();
606+
};
607+
}
608+
609+
private bool IsWindowsDarkMode()
610+
{
611+
try
612+
{
613+
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"))
614+
{
615+
if (key != null)
616+
{
617+
object value = key.GetValue("AppsUseLightTheme");
618+
if (value != null)
619+
{
620+
return (int)value == 0; // 0 = Dark Mode, 1 = Light Mode
621+
}
622+
}
623+
}
624+
}
625+
catch (Exception ex)
626+
{
627+
Logger.Log($"Failed to detect Windows theme: {ex.Message}");
628+
}
629+
return true; // Default to dark mode if detection fails
630+
}
631+
632+
private void CreateNotificationUI(string captureType)
633+
{
634+
// Color scheme based on theme
635+
Color panelBackColor = _isDarkMode ? Color.FromArgb(40, 40, 40) : Color.FromArgb(250, 250, 250);
636+
Color textColor = _isDarkMode ? Color.White : Color.FromArgb(30, 30, 30);
637+
Color subtitleColor = _isDarkMode ? Color.FromArgb(180, 180, 180) : Color.FromArgb(100, 100, 100);
638+
Color closeButtonColor = _isDarkMode ? Color.FromArgb(150, 150, 150) : Color.FromArgb(100, 100, 100);
639+
Color closeButtonHoverColor = _isDarkMode ? Color.White : Color.Black;
640+
Color thumbnailBorderColor = _isDarkMode ? Color.FromArgb(60, 60, 60) : Color.FromArgb(200, 200, 200);
641+
642+
// Main panel
643+
var panel = new Panel
644+
{
645+
Dock = DockStyle.Fill,
646+
Padding = new Padding(10),
647+
BackColor = panelBackColor
648+
};
649+
650+
// Thumbnail image
651+
var thumbnail = new PictureBox
652+
{
653+
Location = new Point(10, 10),
654+
Size = new Size(100, 100),
655+
SizeMode = PictureBoxSizeMode.Zoom,
656+
BorderStyle = BorderStyle.FixedSingle,
657+
BackColor = thumbnailBorderColor
658+
};
659+
660+
try
661+
{
662+
using (var img = Image.FromFile(_imagePath))
663+
{
664+
thumbnail.Image = new Bitmap(img, thumbnail.Size);
665+
}
666+
}
667+
catch
668+
{
669+
thumbnail.BackColor = thumbnailBorderColor;
670+
}
671+
672+
// Success icon/text
673+
var successLabel = new Label
674+
{
675+
Text = "FinalShot",
676+
Font = new Font("Segoe UI", 12, FontStyle.Bold),
677+
ForeColor = Color.FromArgb(0, 200, 100),
678+
Location = new Point(120, 10),
679+
Size = new Size(200, 30),
680+
TextAlign = ContentAlignment.MiddleLeft
681+
};
682+
683+
// Title
684+
var titleLabel = new Label
685+
{
686+
Text = "Screenshot Captured!",
687+
Font = new Font("Segoe UI", 11, FontStyle.Bold),
688+
ForeColor = textColor,
689+
Location = new Point(120, 45),
690+
Size = new Size(250, 25),
691+
TextAlign = ContentAlignment.MiddleLeft
692+
};
693+
694+
// Subtitle
695+
var subtitleLabel = new Label
696+
{
697+
Text = captureType,
698+
Font = new Font("Segoe UI", 9, FontStyle.Regular),
699+
ForeColor = subtitleColor,
700+
Location = new Point(120, 70),
701+
Size = new Size(250, 20),
702+
TextAlign = ContentAlignment.MiddleLeft
703+
};
704+
705+
// Close button
706+
var closeButton = new Label
707+
{
708+
Text = "✕",
709+
Font = new Font("Segoe UI", 12, FontStyle.Bold),
710+
ForeColor = closeButtonColor,
711+
Location = new Point(NotificationWidth - 35, 5),
712+
Size = new Size(25, 25),
713+
TextAlign = ContentAlignment.MiddleCenter,
714+
Cursor = Cursors.Hand
715+
};
716+
closeButton.Click += (s, e) => StartFadeOut();
717+
closeButton.MouseEnter += (s, e) => closeButton.ForeColor = closeButtonHoverColor;
718+
closeButton.MouseLeave += (s, e) => closeButton.ForeColor = closeButtonColor;
719+
720+
// Add all controls
721+
panel.Controls.Add(thumbnail);
722+
panel.Controls.Add(successLabel);
723+
panel.Controls.Add(titleLabel);
724+
panel.Controls.Add(subtitleLabel);
725+
panel.Controls.Add(closeButton);
726+
Controls.Add(panel);
727+
728+
// Make labels click-through to panel
729+
foreach (Control ctrl in panel.Controls)
730+
{
731+
if (ctrl is Label && ctrl != closeButton)
732+
{
733+
ctrl.Click += (s, e) => StartFadeOut();
734+
}
735+
}
736+
}
737+
738+
private void FadeIn()
739+
{
740+
var fadeInTimer = new Timer { Interval = 10 };
741+
double targetOpacity = 0.95;
742+
double step = 0.05;
743+
744+
fadeInTimer.Tick += (s, e) =>
745+
{
746+
_opacity += step;
747+
if (_opacity >= targetOpacity)
748+
{
749+
_opacity = targetOpacity;
750+
Opacity = _opacity;
751+
fadeInTimer.Stop();
752+
fadeInTimer.Dispose();
753+
}
754+
else
755+
{
756+
Opacity = _opacity;
757+
}
758+
};
759+
fadeInTimer.Start();
760+
}
761+
762+
private void StartFadeOut()
763+
{
764+
if (_fadeOutTimer.Enabled) return;
765+
_autoCloseTimer.Stop();
766+
_fadeOutTimer.Start();
767+
}
768+
769+
private void FadeOutTick(object sender, EventArgs e)
770+
{
771+
_opacity -= 0.05;
772+
if (_opacity <= 0)
773+
{
774+
_fadeOutTimer.Stop();
775+
Close();
776+
}
777+
else
778+
{
779+
Opacity = _opacity;
780+
}
781+
}
782+
783+
protected override void Dispose(bool disposing)
784+
{
785+
if (disposing)
786+
{
787+
_autoCloseTimer?.Dispose();
788+
_fadeOutTimer?.Dispose();
789+
}
790+
base.Dispose(disposing);
791+
}
792+
}
793+
473794
public static class Plugin
474795
{
475796
[DllExport]

@Resources/icons/FullScreen.png renamed to Resources/Skins/FinalShot/@Resources/icons/FullScreen.png

File renamed without changes.

@Resources/icons/Predefined.png renamed to Resources/Skins/FinalShot/@Resources/icons/Predefined.png

File renamed without changes.

@Resources/icons/Snap.png renamed to Resources/Skins/FinalShot/@Resources/icons/Snap.png

File renamed without changes.

@Resources/icons/Window.png renamed to Resources/Skins/FinalShot/@Resources/icons/WIndow.png

File renamed without changes.

0 commit comments

Comments
 (0)