From a1e2faefc4f98fc6c46d208f8e997b191852e1b2 Mon Sep 17 00:00:00 2001 From: Chris Lord Date: Sat, 10 Oct 2015 12:40:00 +0100 Subject: [PATCH] Bug 1212905 - Make scroll snapping optional. r=gasolin,gmarty --- apps/homescreen/js/app.js | 20 ++++- apps/homescreen/js/settings.js | 21 ++++- apps/homescreen/style/apps-panel.css | 5 +- apps/homescreen/style/grid.css | 3 + apps/homescreen/test/unit/app_test.js | 2 + apps/homescreen/test/unit/settings_test.js | 83 ++++++++++++++++++- apps/settings/elements/developer.html | 5 ++ .../settings/js/panels/developer/developer.js | 28 +++++++ apps/settings/js/panels/developer/panel.js | 3 +- .../locales/settings.en-US.properties | 1 + apps/settings/test/unit/_developer.html | 5 ++ .../unit/panels/developer/developer_test.js | 9 +- 12 files changed, 174 insertions(+), 11 deletions(-) diff --git a/apps/homescreen/js/app.js b/apps/homescreen/js/app.js index 3652f5ca6205..8e2d3c8574fe 100644 --- a/apps/homescreen/js/app.js +++ b/apps/homescreen/js/app.js @@ -344,6 +344,15 @@ const BLACKLIST = []; this.snapScrollPosition(); }, + toggleScrollSnapping: function(scrollSnapping) { + if (this.scrollable.classList.contains('snapping') === scrollSnapping) { + return; + } + + this.scrollable.classList.toggle('snapping', this.scrollSnapping); + this.snapScrollPosition(); + }, + onVisualLoad: function() { this.visualLoadComplete = true; this.icons.thaw(); @@ -592,8 +601,14 @@ const BLACKLIST = []; var currentScroll = this.scrollable.scrollTop; var scrollHeight = this.scrollable.clientHeight; - var destination = Math.min(gridHeight - scrollHeight, - Math.round(currentScroll / this.pageHeight + bias) * this.pageHeight); + var destination; + if (this.scrollSnapping) { + destination = Math.min(gridHeight - scrollHeight, + Math.round(currentScroll / this.pageHeight + bias) * this.pageHeight); + } else { + destination = Math.min(gridHeight - scrollHeight, + currentScroll + (this.pageHeight * bias)); + } if (Math.abs(destination - currentScroll) > 1) { this.scrollable.style.overflow = ''; @@ -954,6 +969,7 @@ const BLACKLIST = []; case 'settings-changed': this.toggleSmall(this.settings.small); + this.toggleScrollSnapping(this.settings.scrollSnapping); break; } } diff --git a/apps/homescreen/js/settings.js b/apps/homescreen/js/settings.js index d7a98e3d52f6..0f529ca2c448 100644 --- a/apps/homescreen/js/settings.js +++ b/apps/homescreen/js/settings.js @@ -18,9 +18,15 @@ */ const COLUMNS_SETTING = 'grid.cols'; + /** + * Name of the global home screen paging setting. + */ + const PAGING_SETTING = 'grid.paging'; + function Settings() { // Initialise default settings this.small = false; + this.scrollSnapping = false; this.firstRun = false; // Listen to external settings changes @@ -42,15 +48,26 @@ }); }; + var syncPagingSetting = () => { + stores[0].get(PAGING_SETTING).then(paging => { + this.scrollSnapping = paging; + signalChange(); + }); + }; + stores[0].addEventListener('change', e => { switch (e.id) { case COLUMNS_SETTING: syncSmallSetting(); break; + case PAGING_SETTING: + syncPagingSetting(); + break; } }); syncSmallSetting(); + syncPagingSetting(); }, e => { console.error('Error retrieving home screen settings datastore:', e); }); @@ -71,6 +88,7 @@ } this.small = settings.small || false; + this.scrollSnapping = settings.scrollSnapping || false; // Monitor global homescreen settings if (!navigator.getDataStores) { @@ -83,7 +101,8 @@ save: function() { localStorage.setItem('settings', JSON.stringify({ version: SETTINGS_VERSION, - small: this.small + small: this.small, + scrollSnapping: this.scrollSnapping })); } }; diff --git a/apps/homescreen/style/apps-panel.css b/apps/homescreen/style/apps-panel.css index fae8aff7a086..5ee1c1f8d529 100644 --- a/apps/homescreen/style/apps-panel.css +++ b/apps/homescreen/style/apps-panel.css @@ -1,11 +1,14 @@ #apps-panel > .scrollable { overflow-y: auto; - scroll-snap-type-y: mandatory; height: 100%; transition: transform 0.2s; padding-top: 1.5vw; } +#apps-panel > .scrollable.snapping { + scroll-snap-type-y: mandatory; +} + /* This weird arrangement of constant sizes, transforms and containers is so * the gaia-container can animate fully between small and not-small states. * Without this animation, the container would be unnecessary and only the diff --git a/apps/homescreen/style/grid.css b/apps/homescreen/style/grid.css index 76da2a0c8aba..ba4b41af53c3 100644 --- a/apps/homescreen/style/grid.css +++ b/apps/homescreen/style/grid.css @@ -8,6 +8,9 @@ gaia-container { box-sizing: padding-box; padding: 0 1.5vw; padding-bottom: 6rem; +} + +.snapping gaia-container { background: linear-gradient(to bottom, transparent, transparent 50%, rgba(255, 255, 255, 0.15) 50%, diff --git a/apps/homescreen/test/unit/app_test.js b/apps/homescreen/test/unit/app_test.js index f8e0e3caa584..2121848f699b 100644 --- a/apps/homescreen/test/unit/app_test.js +++ b/apps/homescreen/test/unit/app_test.js @@ -542,9 +542,11 @@ suite('Homescreen app', () => { scrollTo: () => {} }; scrollToSpy = sinon.spy(app.scrollable, 'scrollTo'); + app.scrollSnapping = true; }); teardown(() => { + app.scrollSnapping = false; app.scrollable = realScrollable; }); diff --git a/apps/homescreen/test/unit/settings_test.js b/apps/homescreen/test/unit/settings_test.js index 0acfbbe8c2a8..b0e18f76d13e 100644 --- a/apps/homescreen/test/unit/settings_test.js +++ b/apps/homescreen/test/unit/settings_test.js @@ -7,7 +7,7 @@ require('/shared/test/unit/mocks/mock_navigator_datastore.js'); require('/js/settings.js'); suite('Settings', () => { - const DEFAULT_SETTINGS = '{"version":0,"small":false}'; + const DEFAULT_SETTINGS = '{"version":0,"small":false,"scrollSnapping":false}'; var realLocalStorage; var realNavigatorDatastores; @@ -55,10 +55,13 @@ suite('Settings', () => { test('restores settings', () => { var settings = new Settings(); assert.isFalse(settings.small); + assert.isFalse(settings.scrollSnapping); - mockLocalStorage.setItem('settings', '{"version":0,"small":true}'); + mockLocalStorage.setItem('settings', + '{"version":0,"small":true,"scrollSnapping":true}'); settings = new Settings(); assert.isTrue(settings.small); + assert.isTrue(settings.scrollSnapping); }); test('should not restore data if version number differs', () => { @@ -76,17 +79,37 @@ suite('Settings', () => { }); suite('datastore settings', () => { + var target; var cols; + var page; var afterGetCall; var datastoreGetStub; setup(() => { cols = 3; + page = false; + datastoreGetStub = sinon.stub(MockDatastore, 'get', id => { + var returnData; + var returnObject = { + then: callback => { + callback(returnData); + if (id === target) { + afterGetCall(); + } + } + }; + switch (id) { case 'grid.cols': - return { then: callback => { callback(cols); afterGetCall(); } }; + returnData = cols; + break; + + case 'grid.paging': + returnData = page; + break; } + return returnObject; }); }); @@ -97,6 +120,7 @@ suite('Settings', () => { test('restores columns setting', done => { var settings; + target = 'grid.cols'; afterGetCall = () => { try { assert.isFalse(settings.small); @@ -120,6 +144,7 @@ suite('Settings', () => { test('responds to columns setting change', done => { var settings; + target = 'grid.cols'; afterGetCall = () => { try { assert.isFalse(settings.small); @@ -143,6 +168,58 @@ suite('Settings', () => { settings = new Settings(); }); + + test('restores paging setting', done => { + var settings; + + target = 'grid.paging'; + afterGetCall = () => { + try { + assert.isFalse(settings.scrollSnapping); + } catch(e) { + done(e); + } + + afterGetCall = () => { + done(() => { + assert.isTrue(settings.scrollSnapping); + }); + }; + + page = true; + settings = new Settings(); + }; + + settings = new Settings(); + }); + + test('responds to paging setting change', done => { + var settings; + + target = 'grid.paging'; + afterGetCall = () => { + try { + assert.isFalse(settings.scrollSnapping); + } catch(e) { + done(e); + } + + var dispatchEventStub = sinon.stub(window, 'dispatchEvent'); + + afterGetCall = () => { + done(() => { + assert.isTrue(dispatchEventStub.called); + assert.isTrue(settings.scrollSnapping); + dispatchEventStub.restore(); + }); + }; + + page = true; + MockDatastore._cb({ id: 'grid.paging' }); + }; + + settings = new Settings(); + }); }); }); }); diff --git a/apps/settings/elements/developer.html b/apps/settings/elements/developer.html index c6b43dccfc50..10b281b908cd 100644 --- a/apps/settings/elements/developer.html +++ b/apps/settings/elements/developer.html @@ -252,6 +252,11 @@

+
  • + + + +
  • diff --git a/apps/settings/js/panels/developer/developer.js b/apps/settings/js/panels/developer/developer.js index 991eeec28183..68bf7e115622 100644 --- a/apps/settings/js/panels/developer/developer.js +++ b/apps/settings/js/panels/developer/developer.js @@ -10,6 +10,7 @@ define(function(require) { var AppsCache = require('modules/apps_cache'); var ScreenLayout = require('shared/screen_layout'); var SettingsCache = require('modules/settings_cache'); + var HomescreenSettings = require('shared/homescreens/homescreen_settings'); const DEVTOOLS_UNRESTRICTED_KEY = 'devtools.unrestricted'; @@ -56,6 +57,26 @@ define(function(require) { // disable button if mozPower is undefined or can't be used this._elements.resetSwitch.disabled = true; } + + // Listen to home screen paging setting + HomescreenSettings.addEventListener('updated', e => { + var prop = e.target; + if (prop.name === 'grid.paging') { + this._homescreenPagingEnabled = prop.value; + this._elements.homescreenPaging.checked = prop.value; + } + }); + + // Listen to checked/unchecked + this._elements.homescreenPaging.addEventListener('change', () => { + this._setHomescreenPaging(this._elements.homescreenPaging.checked); + }); + + // Set the default value. + HomescreenSettings.get('grid.paging').then(value => { + this._homescreenPagingEnabled = value; + this._elements.homescreenPaging.checked = value; + }); }, /** @@ -144,6 +165,13 @@ define(function(require) { return; } power.factoryReset(reason); + }, + + _setHomescreenPaging: function d__setHomescrenPaging(paging) { + if (this._homescreenPagingEnabled === paging) { + return; + } + HomescreenSettings.put('grid.paging', paging); } }; diff --git a/apps/settings/js/panels/developer/panel.js b/apps/settings/js/panels/developer/panel.js index 25c1a086748c..c2ac3bfecbf4 100644 --- a/apps/settings/js/panels/developer/panel.js +++ b/apps/settings/js/panels/developer/panel.js @@ -16,7 +16,8 @@ define(function(require) { resetSwitch: panel.querySelector('.reset-devtools'), ftuLauncher: panel.querySelector('.ftuLauncher'), softwareHomeButton: panel.querySelector('.software-home-button'), - homegesture: panel.querySelector('.homegesture') + homegesture: panel.querySelector('.homegesture'), + homescreenPaging: panel.querySelector('.homescreen-paging') }; developer.init(elements); } diff --git a/apps/settings/locales/settings.en-US.properties b/apps/settings/locales/settings.en-US.properties index 1508b56417d4..575260827f40 100644 --- a/apps/settings/locales/settings.en-US.properties +++ b/apps/settings/locales/settings.en-US.properties @@ -1299,6 +1299,7 @@ multi-screen=Multi-Screen verbose-app-permissions=Verbose App Permissions pinning-the-web=Enable Pinning the Web view-source=Enable View Source Gesture +homescreen-paging=Enable Home Screen Paging window-management=Window Management # LOCALIZATION NOTE: this string is in developer settings and reset also enables # full DevTools access. diff --git a/apps/settings/test/unit/_developer.html b/apps/settings/test/unit/_developer.html index 1f855df4c946..9520d2cead91 100644 --- a/apps/settings/test/unit/_developer.html +++ b/apps/settings/test/unit/_developer.html @@ -10,6 +10,11 @@ +
  • + + + +