Merge pull request #8266 from zen-browser/haptic-feedback

This commit is contained in:
mr. m
2025-05-13 13:39:54 +02:00
committed by GitHub
12 changed files with 110 additions and 26 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,6 +1084,9 @@
indicator.style.left = left;
indicator.style.removeProperty('top');
}
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
@@ -57,11 +59,16 @@ using mozilla::dom::WindowGlobalChild;
*canShare = false; \
return NS_OK;
/*
* @brief Check if the current context can share data.
* @param data The data to share.
* @returns True if the current context can share data, false otherwise.
*/
NS_IMETHODIMP
ZenCommonUtils::PlayHapticFeedback() {
// We don't have any haptic feedback on non-macOS platforms
// so we can just return.
if (!mozilla::StaticPrefs::zen_haptic_feedback_enabled()) {
return NS_OK;
}
return PlayHapticFeedbackInternal();
}
NS_IMETHODIMP
ZenCommonUtils::CanShare(bool* canShare) {
auto aWindow = GetMostRecentWindow();
@@ -103,7 +110,7 @@ nsresult ZenCommonUtils::ShareInternal(nsCOMPtr<mozIDOMWindowProxy>& aWindow, ns
auto ZenCommonUtils::IsSharingSupported() -> bool {
#if defined(XP_WIN) && !defined(__MINGW32__)
// The first public build that supports ShareCanceled API
return IsWindows10BuildOrLater(18956);
return mozilla::IsWindows10BuildOrLater(18956);
#elif defined(NS_ZEN_CAN_SHARE_NATIVE)
return NS_ZEN_CAN_SHARE_NATIVE;
#else

View File

@@ -32,17 +32,28 @@ class ZenCommonUtils final : public nsIZenCommonUtils {
*/
static auto IsSharingSupported() -> bool;
/**
* @brief Helper function to share data via the native dialogs.
* @param aWindow The window to use for the share dialog.
* @param url The URL to share.
* @param title The title of the share.
* @param text The text to share.
* @returns A promise that resolves when the share is complete.
*/
* @brief Helper function to share data via the native dialogs.
* @param aWindow The window to use for the share dialog.
* @param url The URL to share.
* @param title The title of the share.
* @param text The text to share.
* @returns A promise that resolves when the share is complete.
*/
static auto ShareInternal(nsCOMPtr<mozIDOMWindowProxy>& aWindow, nsIURI* url,
const nsACString& title, const nsACString& text, uint32_t aX, uint32_t aY,
uint32_t aWidth, uint32_t aHeight)
-> nsresult;
/**
* @brief Helper function to play haptic feedback.
*/
#if !defined(XP_MACOSX)
static auto PlayHapticFeedbackInternal() -> nsresult {
// No-op on non-macOS platforms
return NS_ERROR_NOT_IMPLEMENTED;
}
#else
static auto PlayHapticFeedbackInternal() -> nsresult;
#endif
};
} // namespace zen

View File

@@ -0,0 +1,35 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenCommonUtils.h"
#include "nsCocoaUtils.h"
#include "nsPIDOMWindow.h"
#include "WidgetUtils.h"
#include "nsIWidget.h"
extern mozilla::LazyLogModule gCocoaUtilsLog;
#undef LOG
#define LOG(...) MOZ_LOG(gCocoaUtilsLog, mozilla::LogLevel::Info, (__VA_ARGS__))
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
namespace zen {
nsresult ZenCommonUtils::PlayHapticFeedbackInternal() {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
if (@available(macOS 10.14, *)) {
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
}
return NS_OK;
NS_OBJC_END_TRY_BLOCK_RETURN(NS_OK);
}
}

View File

@@ -74,5 +74,6 @@ auto nsZenNativeShareInternal::ShowNativeDialog(nsCOMPtr<mozIDOMWindowProxy>& aW
[sharingPicker showRelativeToRect:rect
ofView:cocoaMru.contentView
preferredEdge:NSMaxYEdge];
[sharingPicker release];
return NS_OK;
}

View File

@@ -1,6 +1,7 @@
FINAL_LIBRARY = "xul"
SOURCES += [
"ZenHapticFeedback.mm",
"ZenShareInternal.mm",
]
@@ -9,4 +10,4 @@ LOCAL_INCLUDES += [
"/widget",
"/widget/cocoa",
"/xpcom/base",
]
]

View File

@@ -28,5 +28,9 @@ interface nsIZenCommonUtils : nsISupports {
* @returns True if the current context can share data, false otherwise.
*/
boolean canShare();
/*
* @brief Play a single haptic feedback note if supported.
*/
void playHapticFeedback();
};

View File

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

View File

@@ -180,7 +180,7 @@
}
&::-moz-range-track {
background: var(--zen-colors-border);
background: light-dark(rgba(0, 0, 0, 0.3), rgba(255, 255, 255, 0.3));
border-radius: 999px;
height: 6px;
}
@@ -225,9 +225,9 @@
transform: translate(-50%, -50%);
&:first-of-type {
width: 30px;
height: 30px;
border-width: 3px;
width: 36px;
height: 36px;
border-width: 4px;
z-index: 2;
transition: transform 0.1s;
&:hover {
@@ -264,7 +264,7 @@
transform: translateX(-50%);
& .separator,
& #PanelUI-zen-gradient-generator-color-toggle-algo {
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.5));
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.15));
}
& button {
@@ -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;
}
}