commit 5232019b84a29c6417c5271be43be1684e30dc78 Author: Atridad Lahiji Date: Tue Jan 21 15:25:47 2025 -0600 Init diff --git a/background.js b/background.js new file mode 100644 index 0000000..abbc448 --- /dev/null +++ b/background.js @@ -0,0 +1,57 @@ +// Dynamic Icon generator +function createIcon(text, size = 16) { + const canvas = new OffscreenCanvas(size, size); + const ctx = canvas.getContext("2d"); + + // Wipe canvas + ctx.clearRect(0, 0, size, size); + + // Background + ctx.fillStyle = "#4285F4"; // Google Blue + ctx.fillRect(0, 0, size, size); + + // Text + ctx.fillStyle = "white"; + ctx.font = `bold ${Math.floor(size * 0.5625)}px Arial`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(text, size / 2, size / 2); + + return ctx.getImageData(0, 0, size, size); +} + +// Update the extension icon +function updateExtensionIcon(zoomLevel, accelerationMultiplier) { + const zoomText = zoomLevel.toFixed(2); + const accelText = accelerationMultiplier.toFixed(2); + + // Create icons of different sizes + const icon16 = createIcon(zoomText, 16); + const icon32 = createIcon(zoomText, 32); + const icon48 = createIcon(zoomText, 48); + const icon128 = createIcon(zoomText, 128); + + // Set the icon + chrome.action.setIcon({ + imageData: { + 16: icon16, + 32: icon32, + 48: icon48, + 128: icon128, + }, + }); + + // Update badge with multiplier + chrome.action.setBadgeText({ text: accelText + "x" }); + chrome.action.setBadgeBackgroundColor({ color: "#34A853" }); // Google Green +} + +// Listen for messages +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "updateZoomInfo") { + updateExtensionIcon(request.zoomLevel, request.accelerationMultiplier); + } +}); + +// Set icon +updateExtensionIcon(1, 1); diff --git a/content.js b/content.js new file mode 100644 index 0000000..9ddf677 --- /dev/null +++ b/content.js @@ -0,0 +1,93 @@ +let zoomLevel = 1; +let lastGestureTime = 0; +let lastGestureScale = 1; +const minZoom = 0.1; +const maxZoom = 5; +let accelerationCurve = [1, 1, 1, 1, 1]; + +function interpolateAcceleration(speed) { + const index = Math.min( + Math.floor((speed * (accelerationCurve.length - 1)) / 2), + accelerationCurve.length - 2, + ); + const t = ((speed * (accelerationCurve.length - 1)) / 2) % 1; + return accelerationCurve[index] * (1 - t) + accelerationCurve[index + 1] * t; +} + +function applyZoom(delta, speed) { + const accelerationMultiplier = interpolateAcceleration(speed); + const zoomChange = delta * accelerationMultiplier; + zoomLevel *= 1 + zoomChange; + zoomLevel = Math.min(Math.max(zoomLevel, minZoom), maxZoom); + + document.body.style.transform = `scale(${zoomLevel})`; + document.body.style.transformOrigin = "center top"; + + // Update Icon via Messages + chrome.runtime.sendMessage({ + action: "updateZoomInfo", + zoomLevel: zoomLevel, + accelerationMultiplier: accelerationMultiplier, + }); +} + +window.addEventListener( + "wheel", + (event) => { + if (event.ctrlKey || event.metaKey) { + event.preventDefault(); + const currentTime = performance.now(); + const timeDiff = (currentTime - lastGestureTime) / 1000; + const delta = event.deltaY * -0.001; + const speed = Math.abs(delta / timeDiff); + applyZoom(delta, speed); + lastGestureTime = currentTime; + } + }, + { passive: false }, +); + +window.addEventListener( + "gesturestart", + (event) => { + event.preventDefault(); + lastGestureScale = 1; + lastGestureTime = performance.now(); + }, + { passive: false }, +); + +window.addEventListener( + "gesturechange", + (event) => { + event.preventDefault(); + const currentTime = performance.now(); + const timeDiff = (currentTime - lastGestureTime) / 1000; + const delta = event.scale - lastGestureScale; + const speed = Math.abs(delta / timeDiff); + applyZoom(delta, speed); + lastGestureScale = event.scale; + lastGestureTime = currentTime; + }, + { passive: false }, +); + +window.addEventListener( + "gestureend", + (event) => { + event.preventDefault(); + }, + { passive: false }, +); + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "updateSettings") { + accelerationCurve = request.settings; + } +}); + +chrome.storage.sync.get("accelerationCurve", (data) => { + if (data.accelerationCurve) { + accelerationCurve = data.accelerationCurve; + } +}); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..41ea2e0 --- /dev/null +++ b/manifest.json @@ -0,0 +1,20 @@ +{ + "manifest_version": 3, + "name": "Zoom Accelerator", + "version": "1.0", + "description": "Adds customizable smooth zoom acceleration to Chrome", + "permissions": ["activeTab", "storage"], + "host_permissions": [""], + "action": { + "default_popup": "popup.html" + }, + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ] +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..4c77a18 --- /dev/null +++ b/popup.html @@ -0,0 +1,31 @@ + + + + + + +

Zoom Acceleration Curve

+
+ + + + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..3ec3d43 --- /dev/null +++ b/popup.js @@ -0,0 +1,75 @@ +const numSliders = 5; +const defaultMultiplier = 1; + +function createSliders() { + const slidersContainer = document.getElementById("sliders"); + for (let i = 0; i < numSliders; i++) { + const sliderContainer = document.createElement("div"); + sliderContainer.className = "slider-container"; + + const label = document.createElement("label"); + label.textContent = `${(i / (numSliders - 1) * 2).toFixed(2)} zoom units/s`; + + const slider = document.createElement("input"); + slider.type = "range"; + slider.min = "1"; + slider.max = "10"; + slider.step = "0.1"; + slider.value = defaultMultiplier; + slider.className = "slider"; + + const value = document.createElement("span"); + value.className = "value"; + value.textContent = `${defaultMultiplier}x`; + + sliderContainer.appendChild(label); + sliderContainer.appendChild(slider); + sliderContainer.appendChild(value); + slidersContainer.appendChild(sliderContainer); + + slider.addEventListener("input", () => { + value.textContent = `${parseFloat(slider.value).toFixed(1)}x`; + saveSettings(); + }); + } +} + +function saveSettings() { + const sliders = document.querySelectorAll(".slider"); + const settings = Array.from(sliders).map((slider) => parseFloat(slider.value)); + chrome.storage.sync.set({ accelerationCurve: settings }, () => { + sendSettingsToActiveTab(settings); + }); +} + +function sendSettingsToActiveTab(settings) { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs[0]) { + chrome.tabs.sendMessage( + tabs[0].id, + { action: "updateSettings", settings }, + (response) => { + if (chrome.runtime.lastError) { + console.log("Could not send settings to content script."); + } + } + ); + } + }); +} + +function loadSettings() { + chrome.storage.sync.get("accelerationCurve", (data) => { + if (data.accelerationCurve) { + const sliders = document.querySelectorAll(".slider"); + data.accelerationCurve.forEach((value, index) => { + sliders[index].value = value; + sliders[index].nextElementSibling.textContent = `${value.toFixed(1)}x`; + }); + } + }); +} + +createSliders(); +loadSettings(); +