feat: Finished haptic feedback support, b=(no-bug), c=common, tabs, workspaces

This commit is contained in:
mr. m
2025-05-13 13:31:03 +02:00
parent 33fff9e19b
commit f9b0f8c436
11 changed files with 50 additions and 36 deletions

View File

@@ -19,6 +19,9 @@ pref('zen.view.show-newtab-button-top', true);
pref('zen.mediacontrols.enabled', true);
// Exposure:
pref('zen.haptic-feedback.enabled', true);
#ifdef MOZILLA_OFFICIAL
pref('zen.rice.api.url', 'https://share.zen-browser.app', locked);
pref('zen.injections.match-urls', 'https://zen-browser.app/*,https://share.zen-browser.app/*', locked);

View File

@@ -11,3 +11,8 @@
value: 1
mirror: always
#endif
- name: zen.haptic-feedback.enabled
type: bool
value: true
mirror: always

View File

@@ -62,5 +62,9 @@ panel[type='arrow'] {
--panel-border-color: transparent;
/* This should be kept in sync with GetMenuMaskImage() */
--panel-border-radius: 6px;
&::part(content) {
background-color: light-dark(rgba(255, 255, 255, 0.2), rgba(0, 0, 0, 0.2)) !important;
}
}
}

View File

@@ -942,6 +942,9 @@
}
removeTabContainersDragoverClass() {
if (this._dragIndicator) {
Services.zen.playHapticFeedback();
}
this.dragIndicator.remove();
this._dragIndicator = null;
ZenWorkspaces.activeWorkspaceIndicator?.removeAttribute('open');
@@ -1043,16 +1046,19 @@
// Calculate middle to decide 'before' or 'after'
const rect = targetTab.getBoundingClientRect();
let shouldPlayHapticFeedback = false;
if (isVertical) {
const separation = 8;
const middleY = targetTab.screenY + rect.height / 2;
const indicator = this.dragIndicator;
let top = 0;
if (event.screenY > middleY) {
top = rect.top + rect.height + 'px';
top = Math.round(rect.top + rect.height) + 'px';
} else {
top = rect.top + 'px';
top = Math.round(rect.top) + 'px';
}
if (indicator.style.top !== top) {
shouldPlayHapticFeedback = true;
}
indicator.setAttribute('orientation', 'horizontal');
indicator.style.setProperty('--indicator-left', rect.left + separation / 2 + 'px');
@@ -1065,9 +1071,12 @@
const indicator = this.dragIndicator;
let left = 0;
if (event.screenX > middleX) {
left = rect.left + rect.width + 1 + 'px';
left = Math.round(rect.left + rect.width + 1) + 'px';
} else {
left = rect.left - 2 + 'px';
left = Math.round(rect.left - 2) + 'px';
}
if (indicator.style.left !== left) {
shouldPlayHapticFeedback = true;
}
indicator.setAttribute('orientation', 'vertical');
indicator.style.setProperty('--indicator-top', rect.top + separation / 2 + 'px');
@@ -1075,7 +1084,9 @@
indicator.style.left = left;
indicator.style.removeProperty('top');
}
Services.zen.playHapticFeedback(/*soft*/3);
if (shouldPlayHapticFeedback) {
Services.zen.playHapticFeedback();
}
}
async onTabLabelChanged(tab) {

View File

@@ -11,6 +11,8 @@
#include "nsServiceManagerUtils.h"
#include "nsISharePicker.h"
#include "mozilla/StaticPrefs_zen.h"
#if defined(XP_WIN)
# include "mozilla/WindowsVersion.h"
#endif
@@ -58,10 +60,13 @@ using mozilla::dom::WindowGlobalChild;
return NS_OK;
NS_IMETHODIMP
ZenCommonUtils::PlayHapticFeedback(uint32_t type) {
ZenCommonUtils::PlayHapticFeedback() {
// We don't have any haptic feedback on non-macOS platforms
// so we can just return.
return PlayHapticFeedbackInternal(type);
if (!mozilla::StaticPrefs::zen_haptic_feedback_enabled()) {
return NS_OK;
}
return PlayHapticFeedbackInternal();
}
NS_IMETHODIMP

View File

@@ -45,15 +45,14 @@ class ZenCommonUtils final : public nsIZenCommonUtils {
-> nsresult;
/**
* @brief Helper function to play haptic feedback.
* @param type The type of haptic feedback to play.
*/
#if !defined(XP_MACOSX)
static auto PlayHapticFeedbackInternal(uint32_t type) -> nsresult {
static auto PlayHapticFeedbackInternal() -> nsresult {
// No-op on non-macOS platforms
return NS_ERROR_NOT_IMPLEMENTED;
}
#else
static auto PlayHapticFeedbackInternal(uint32_t type) -> nsresult;
static auto PlayHapticFeedbackInternal() -> nsresult;
#endif
};

View File

@@ -17,32 +17,19 @@ extern mozilla::LazyLogModule gCocoaUtilsLog;
#import <AppKit/AppKit.h>
namespace zen {
using ::mozilla::widget::WidgetUtils;
namespace {
/**
* Get the native haptic feedback type from the uint32_t.
*
* @param type The uint32_t to convert.
* @return The native haptic feedback type.
*/
inline UIImpactFeedbackStyle GetNativeHapticFeedbackType(uint32_t type) {
return static_cast<UIImpactFeedbackStyle>(type);
}
}
nsresult ZenCommonUtils::PlayHapticFeedbackInternal(uint32_t type) {
nsresult ZenCommonUtils::PlayHapticFeedbackInternal() {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
auto style = GetNativeHapticFeedbackType(type);
if (@available(macOS 10.14, *)) {
UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:style];
[generator prepare];
[generator impactOccurred];
generator = nil;
id<NSHapticFeedbackPerformer> performer = [NSHapticFeedbackManager defaultPerformer];
[performer performFeedbackPattern:NSHapticFeedbackPatternAlignment
performanceTime:NSHapticFeedbackPerformanceTimeDefault];
} else {
// Fallback on earlier versions
// Note: This is a no-op on older versions of iOS/macOS
}
NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}
return NS_OK;
NS_OBJC_END_TRY_BLOCK_RETURN(NS_OK);
}
}

View File

@@ -10,4 +10,4 @@ LOCAL_INCLUDES += [
"/widget",
"/widget/cocoa",
"/xpcom/base",
]
]

View File

@@ -30,8 +30,7 @@ interface nsIZenCommonUtils : nsISupports {
boolean canShare();
/*
* @brief Play a single haptic feedback note if supported.
* @param type The type of haptic feedback to play.
*/
void playHapticFeedback(in uint32_t type);
void playHapticFeedback();
};

View File

@@ -267,6 +267,7 @@
}
if (previousTexture !== this.currentTexture) {
this.updateCurrentWorkspace();
Services.zen.playHapticFeedback();
}
}

View File

@@ -356,13 +356,13 @@
height: 4px;
width: 4px;
border-radius: 50%;
background: light-dark(rgba(0, 0, 0, 0.3), rgba(255, 255, 255, 0.2));
background: light-dark(rgba(0, 0, 0, 0.3), rgba(255, 255, 255, 0.3));
position: absolute;
transition: opacity 0.2s;
transform: translate(-50%, -50%);
pointer-events: none;
&:not(.active) {
opacity: 0.2;
opacity: 0.4;
}
}