Skip to content

Commit 92d14b0

Browse files
committed
setting to enable/disable the tray animation
1 parent 506750a commit 92d14b0

9 files changed

Lines changed: 211 additions & 1 deletion

File tree

src/Settings.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace {
1414
constexpr wchar_t kClipboardSyncMaxItemsName[] = L"ClipboardSyncMaxItems";
1515
constexpr wchar_t kHonorExternalPrivacyMarkersName[] = L"HonorExternalPrivacyMarkers";
1616
constexpr wchar_t kMaskShortTextPreviewsName[] = L"MaskShortTextPreviews";
17+
constexpr wchar_t kAnimateFlowFeedbackName[] = L"AnimateFlowFeedback";
1718
constexpr wchar_t kOriginSequenceFloorName[] = L"OriginSequenceFloor";
1819
constexpr wchar_t kRegisterTtlSecondsName[] = L"RegisterTtlSeconds";
1920
constexpr wchar_t kRegisterMaxCountName[] = L"RegisterMaxCount";
@@ -54,6 +55,7 @@ Settings::Settings()
5455
clipboardSyncMaxItems_(DefaultClipboardSyncMaxItems),
5556
honorExternalPrivacyMarkers_(DefaultHonorExternalPrivacyMarkers),
5657
maskShortTextPreviews_(DefaultMaskShortTextPreviews),
58+
animateFlowFeedback_(DefaultAnimateFlowFeedback),
5759
registerTtlSeconds_(DefaultRegisterTtlSeconds),
5860
registerMaxCount_(DefaultRegisterMaxCount) {
5961
LoadCache();
@@ -104,6 +106,11 @@ bool Settings::maskShortTextPreviews() const {
104106
return maskShortTextPreviews_;
105107
}
106108

109+
bool Settings::animateFlowFeedback() const {
110+
std::lock_guard<std::mutex> lock(mutex_);
111+
return animateFlowFeedback_;
112+
}
113+
107114
bool Settings::set_listenerIp(const std::string& value) {
108115
if (!IsValidListenerIp(value)) {
109116
return false;
@@ -191,6 +198,15 @@ bool Settings::set_maskShortTextPreviews(bool value) {
191198
return true;
192199
}
193200

201+
bool Settings::set_animateFlowFeedback(bool value) {
202+
if (!WriteUint32Value(kAnimateFlowFeedbackName, value ? 1 : 0)) {
203+
return false;
204+
}
205+
std::lock_guard<std::mutex> lock(mutex_);
206+
animateFlowFeedback_ = value;
207+
return true;
208+
}
209+
194210
uint64_t Settings::nextOriginSequenceNumber() {
195211
std::lock_guard<std::mutex> lock(mutex_);
196212
const uint64_t next = ++originSequenceCounter_;
@@ -301,6 +317,7 @@ bool Settings::LoadCache() {
301317
uint64_t clipboardSyncMaxItems = DefaultClipboardSyncMaxItems;
302318
int honorExternalPrivacyMarkers = DefaultHonorExternalPrivacyMarkers ? 1 : 0;
303319
int maskShortTextPreviews = DefaultMaskShortTextPreviews ? 1 : 0;
320+
int animateFlowFeedback = DefaultAnimateFlowFeedback ? 1 : 0;
304321
uint64_t originSequenceFloor = 0;
305322

306323
if (ReadStringValue(kListenerIpName, ip) && IsValidListenerIp(ip)) {
@@ -330,6 +347,9 @@ bool Settings::LoadCache() {
330347
if (ReadUint32Value(kMaskShortTextPreviewsName, maskShortTextPreviews)) {
331348
maskShortTextPreviews_ = (maskShortTextPreviews != 0);
332349
}
350+
if (ReadUint32Value(kAnimateFlowFeedbackName, animateFlowFeedback)) {
351+
animateFlowFeedback_ = (animateFlowFeedback != 0);
352+
}
333353

334354
// Origin sequence counter: load the persisted floor (the value the previous
335355
// session reserved through). Start the in-memory counter from there so we

src/Settings.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ class Settings {
3838
// single-token text (the might-be-a-password heuristic). Display-only —
3939
// never affects what syncs.
4040
static constexpr bool DefaultMaskShortTextPreviews = true;
41+
// Default for animateFlowFeedback: nudge the tray / menu bar icon when a
42+
// clipboard item is sent to or received from the group. Display-only; the
43+
// last-event tooltip / menu line stays available either way.
44+
static constexpr bool DefaultAnimateFlowFeedback = true;
4145

4246
Settings();
4347

@@ -53,6 +57,7 @@ class Settings {
5357
uint64_t clipboardSyncMaxItems() const;
5458
bool honorExternalPrivacyMarkers() const;
5559
bool maskShortTextPreviews() const;
60+
bool animateFlowFeedback() const;
5661

5762
bool set_listenerIp(const std::string& value);
5863
bool set_tcpPort(int value);
@@ -63,6 +68,7 @@ class Settings {
6368
bool set_clipboardSyncMaxItems(uint64_t value);
6469
bool set_honorExternalPrivacyMarkers(bool value);
6570
bool set_maskShortTextPreviews(bool value);
71+
bool set_animateFlowFeedback(bool value);
6672

6773
// Atomically increments the per-origin sequence counter and returns the next
6874
// value. Persists every OriginSequenceBatchSize calls. On startup the counter
@@ -107,6 +113,7 @@ class Settings {
107113
uint64_t clipboardSyncMaxItems_;
108114
bool honorExternalPrivacyMarkers_;
109115
bool maskShortTextPreviews_;
116+
bool animateFlowFeedback_;
110117
// In-memory origin sequence counter. Highest value yielded so far.
111118
uint64_t originSequenceCounter_{ 0 };
112119
// The next persisted floor — counter values up to (but not including) this

src/platform/macos/AppMenu.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "Clipboard.h"
66
#include "ClipboardFlowUi.h"
77
#include "Logger.h"
8+
#include "Settings.h"
89
#include "AboutPage.h"
910
#include "ClippPage.h"
1011
#include "NetworkPage.h"
@@ -80,6 +81,7 @@ ClipboardPayload MakeTextClipboardPayload(NSString* text) {
8081
static ClippMainWindowController* g_logReflectorTarget = nil;
8182
extern ClipboardActivityStore g_clipboardActivityStore;
8283
extern PeerDisplay g_peerDisplay;
84+
extern Settings g_settings;
8385

8486
static void LogReflectorCallback(const std::wstring& line);
8587

@@ -1051,6 +1053,11 @@ - (void)noteClipboardFlowReceived:(BOOL)received peer:(NSString*)peer {
10511053
}
10521054

10531055
- (void)runNudgeReceived:(BOOL)received {
1056+
// Setting gates the MOTION only: noteClipboardFlowReceived already stored
1057+
// the event for the menu's last-event header.
1058+
if (!g_settings.animateFlowFeedback()) {
1059+
return;
1060+
}
10541061
NSStatusBarButton* button = [self.statusItem button];
10551062
if (button == nil || self.statusBaseImage == nil) {
10561063
return;

src/platform/macos/SettingsPage.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class MacOSSettingsPage {
2525
void OnResetHostID();
2626
void OnHonorPrivacyMarkersChanged();
2727
void OnMaskShortTextPreviewsChanged();
28+
void OnAnimateFlowFeedbackChanged();
2829
void OnLaunchAtLoginChanged();
2930

3031
private:
@@ -43,6 +44,8 @@ class MacOSSettingsPage {
4344
void ApplyClipboardHistorySettingChange();
4445
void RefreshPrivacyControls();
4546
void ApplyPrivacySettingChange();
47+
void RefreshFeedbackControls();
48+
void ApplyFeedbackSettingChange();
4649
void RefreshLaunchAtLoginControls();
4750
void ApplyLaunchAtLoginChange();
4851

@@ -62,6 +65,7 @@ class MacOSSettingsPage {
6265
NSButton* resetHostIDButton_ = nullptr;
6366
NSButton* maskShortTextPreviewsCheckbox_ = nullptr;
6467
NSButton* honorPrivacyMarkersCheckbox_ = nullptr;
68+
NSButton* animateFlowFeedbackCheckbox_ = nullptr;
6569
NSButton* launchAtLoginCheckbox_ = nullptr;
6670
NSTextField* statusMessage_ = nullptr;
6771
MacOSSettingsPageFieldDelegate* fieldDelegate_ = nullptr;

src/platform/macos/SettingsPage.mm

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ - (void)historySliderChanged:(id)sender;
3131
- (void)resetHostID:(id)sender;
3232
- (void)honorPrivacyMarkersChanged:(id)sender;
3333
- (void)maskShortTextPreviewsChanged:(id)sender;
34+
- (void)animateFlowFeedbackChanged:(id)sender;
3435
- (void)launchAtLoginChanged:(id)sender;
3536
@end
3637

@@ -80,6 +81,13 @@ - (void)maskShortTextPreviewsChanged:(id)sender {
8081
}
8182
}
8283

84+
- (void)animateFlowFeedbackChanged:(id)sender {
85+
(void)sender;
86+
if (owner_ != nullptr) {
87+
owner_->OnAnimateFlowFeedbackChanged();
88+
}
89+
}
90+
8391
- (void)launchAtLoginChanged:(id)sender {
8492
(void)sender;
8593
if (owner_ != nullptr) {
@@ -368,6 +376,10 @@ uint64_t SliderStopValue(NSSlider* slider, const LimitStop(&stops)[N]) {
368376
ApplyPrivacySettingChange();
369377
}
370378

379+
void MacOSSettingsPage::OnAnimateFlowFeedbackChanged() {
380+
ApplyFeedbackSettingChange();
381+
}
382+
371383
void MacOSSettingsPage::OnLaunchAtLoginChanged() {
372384
ApplyLaunchAtLoginChange();
373385
}
@@ -505,6 +517,36 @@ uint64_t SliderStopValue(NSSlider* slider, const LimitStop(&stops)[N]) {
505517
[privacyHelp.bottomAnchor constraintEqualToAnchor:privacySection.bottomAnchor constant:-kSectionInsetY],
506518
]];
507519

520+
NSTextField* feedbackHeader = [NSTextField labelWithString:CLP_NS(CLP_UI_FEEDBACK)];
521+
feedbackHeader.translatesAutoresizingMaskIntoConstraints = NO;
522+
feedbackHeader.font = [NSFont systemFontOfSize:16 weight:NSFontWeightSemibold];
523+
feedbackHeader.textColor = [NSColor labelColor];
524+
525+
NSBox* feedbackSection = MacOSMakeGroupBox();
526+
527+
animateFlowFeedbackCheckbox_ = [NSButton checkboxWithTitle:CLP_NS(CLP_UI_ANIMATE_FLOW_FEEDBACK)
528+
target:fieldDelegate_
529+
action:@selector(animateFlowFeedbackChanged:)];
530+
animateFlowFeedbackCheckbox_.translatesAutoresizingMaskIntoConstraints = NO;
531+
[feedbackSection addSubview:animateFlowFeedbackCheckbox_];
532+
533+
NSTextField* animateHelp = MacOSMakeWrappingLabel(CLP_NS(CLP_UI_ANIMATE_FLOW_FEEDBACK_HELP),
534+
12.0,
535+
[NSColor secondaryLabelColor]);
536+
[feedbackSection addSubview:animateHelp];
537+
538+
NSMutableArray<NSLayoutConstraint*>* feedbackConstraints = [NSMutableArray array];
539+
[feedbackConstraints addObjectsFromArray:@[
540+
[animateFlowFeedbackCheckbox_.leadingAnchor constraintEqualToAnchor:feedbackSection.leadingAnchor constant:kSectionInsetX],
541+
[animateFlowFeedbackCheckbox_.trailingAnchor constraintLessThanOrEqualToAnchor:feedbackSection.trailingAnchor constant:-kSectionInsetX],
542+
[animateFlowFeedbackCheckbox_.topAnchor constraintEqualToAnchor:feedbackSection.topAnchor constant:kSectionInsetY],
543+
544+
[animateHelp.leadingAnchor constraintEqualToAnchor:feedbackSection.leadingAnchor constant:kSectionInsetX],
545+
[animateHelp.trailingAnchor constraintEqualToAnchor:feedbackSection.trailingAnchor constant:-kSectionInsetX],
546+
[animateHelp.topAnchor constraintEqualToAnchor:animateFlowFeedbackCheckbox_.bottomAnchor constant:6.0],
547+
[animateHelp.bottomAnchor constraintEqualToAnchor:feedbackSection.bottomAnchor constant:-kSectionInsetY],
548+
]];
549+
508550
NSTextField* hostIDHeader = [NSTextField labelWithString:CLP_NS(CLP_UI_HOST_ID)];
509551
hostIDHeader.translatesAutoresizingMaskIntoConstraints = NO;
510552
hostIDHeader.font = [NSFont systemFontOfSize:16 weight:NSFontWeightSemibold];
@@ -534,6 +576,8 @@ uint64_t SliderStopValue(NSSlider* slider, const LimitStop(&stops)[N]) {
534576
[contentRoot addSubview:historySection];
535577
[contentRoot addSubview:privacyHeader];
536578
[contentRoot addSubview:privacySection];
579+
[contentRoot addSubview:feedbackHeader];
580+
[contentRoot addSubview:feedbackSection];
537581
[contentRoot addSubview:networkHeader];
538582
[contentRoot addSubview:section];
539583
[contentRoot addSubview:hostIDHeader];
@@ -587,9 +631,17 @@ uint64_t SliderStopValue(NSSlider* slider, const LimitStop(&stops)[N]) {
587631
[privacySection.trailingAnchor constraintEqualToAnchor:heading.trailingAnchor],
588632
[privacySection.topAnchor constraintEqualToAnchor:privacyHeader.bottomAnchor constant:16.0],
589633

634+
[feedbackHeader.leadingAnchor constraintEqualToAnchor:heading.leadingAnchor],
635+
[feedbackHeader.trailingAnchor constraintEqualToAnchor:heading.trailingAnchor],
636+
[feedbackHeader.topAnchor constraintEqualToAnchor:privacySection.bottomAnchor constant:18.0],
637+
638+
[feedbackSection.leadingAnchor constraintEqualToAnchor:heading.leadingAnchor],
639+
[feedbackSection.trailingAnchor constraintEqualToAnchor:heading.trailingAnchor],
640+
[feedbackSection.topAnchor constraintEqualToAnchor:feedbackHeader.bottomAnchor constant:16.0],
641+
590642
[networkHeader.leadingAnchor constraintEqualToAnchor:heading.leadingAnchor],
591643
[networkHeader.trailingAnchor constraintEqualToAnchor:heading.trailingAnchor],
592-
[networkHeader.topAnchor constraintEqualToAnchor:privacySection.bottomAnchor constant:18.0],
644+
[networkHeader.topAnchor constraintEqualToAnchor:feedbackSection.bottomAnchor constant:18.0],
593645

594646
[section.leadingAnchor constraintEqualToAnchor:heading.leadingAnchor],
595647
[section.trailingAnchor constraintEqualToAnchor:heading.trailingAnchor],
@@ -626,6 +678,7 @@ uint64_t SliderStopValue(NSSlider* slider, const LimitStop(&stops)[N]) {
626678
[NSLayoutConstraint activateConstraints:historyConstraints];
627679
[NSLayoutConstraint activateConstraints:rowConstraints];
628680
[NSLayoutConstraint activateConstraints:privacyConstraints];
681+
[NSLayoutConstraint activateConstraints:feedbackConstraints];
629682
[NSLayoutConstraint activateConstraints:hostIDConstraints];
630683
}
631684

@@ -639,6 +692,7 @@ uint64_t SliderStopValue(NSSlider* slider, const LimitStop(&stops)[N]) {
639692
MacOSSetFieldText(listenerIpField_, g_settings.listenerIp());
640693
RefreshClipboardHistoryControls();
641694
RefreshPrivacyControls();
695+
RefreshFeedbackControls();
642696
RefreshLaunchAtLoginControls();
643697
loadingSettings_ = false;
644698

@@ -816,6 +870,33 @@ uint64_t SliderStopValue(NSSlider* slider, const LimitStop(&stops)[N]) {
816870
ShowStatusMessage();
817871
}
818872

873+
void MacOSSettingsPage::RefreshFeedbackControls() {
874+
if (animateFlowFeedbackCheckbox_ == nil) {
875+
return;
876+
}
877+
878+
animateFlowFeedbackCheckbox_.state = g_settings.animateFlowFeedback()
879+
? NSControlStateValueOn
880+
: NSControlStateValueOff;
881+
}
882+
883+
void MacOSSettingsPage::ApplyFeedbackSettingChange() {
884+
if (loadingSettings_ || animateFlowFeedbackCheckbox_ == nil) {
885+
return;
886+
}
887+
888+
const bool desiredAnimate = animateFlowFeedbackCheckbox_.state == NSControlStateValueOn;
889+
if (desiredAnimate == g_settings.animateFlowFeedback()) {
890+
return;
891+
}
892+
if (!g_settings.set_animateFlowFeedback(desiredAnimate)) {
893+
return;
894+
}
895+
896+
MacOSSetFieldText(statusMessage_, CLP_NS(CLP_UI_FEEDBACK_SETTINGS_APPLIED));
897+
ShowStatusMessage();
898+
}
899+
819900
void MacOSSettingsPage::RefreshLaunchAtLoginControls() {
820901
if (launchAtLoginCheckbox_ == nil) {
821902
return;

src/platform/uistrings.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@
9292
#define CLP_UI_HONOR_PRIVACY_MARKERS_HELP "When other apps mark clipboard items as private (passwords from Chrome, password managers, etc.), Clipp will not sync the content to your other devices."
9393
#define CLP_UI_PRIVACY_SETTINGS_APPLIED "Privacy settings applied."
9494

95+
#define CLP_UI_FEEDBACK "Feedback"
96+
#define CLP_UI_ANIMATE_FLOW_FEEDBACK "Animate the Clipp icon on send and receive"
97+
#define CLP_UI_ANIMATE_FLOW_FEEDBACK_HELP "The status icon gives a quick nudge whenever a copied item leaves for your other devices or arrives from one."
98+
#define CLP_UI_FEEDBACK_SETTINGS_APPLIED "Feedback setting applied."
99+
95100
// Mac App Store flavor only (guideline 2.4.5(iii)): login items there require
96101
// explicit consent, so the MAS build swaps autostart-by-default for this toggle.
97102
#define CLP_UI_STARTUP "Startup"

0 commit comments

Comments
 (0)