diff --git a/web/css/ui.css b/web/css/ui.css index 1a2d05fdc..ce3cfd27a 100644 --- a/web/css/ui.css +++ b/web/css/ui.css @@ -74,10 +74,13 @@ .settings__option-value select, .settings__option-value input { font-family: '6809', monospace; - width: 6em; font-size: 90%; } +.settings__option-value input:not([type='checkbox']) { + width: 6em; +} + .settings__option-value option { font-size: 150%; } @@ -88,10 +91,15 @@ .keyboard-bindings .settings__option-value { display: grid; - grid-template-columns: 25% 25% auto auto; + grid-template-columns: 20% 20% 20% 20% auto; row-gap: 5px; } +.settings__option-checkbox label { + display: inline-flex; + align-items: center; +} + .binding-element { display: flex; flex-direction: column; diff --git a/web/index.html b/web/index.html index e710272f9..068279c6a 100644 --- a/web/index.html +++ b/web/index.html @@ -16,7 +16,7 @@ - + Cloud Retro @@ -102,18 +102,18 @@ - + - - - + + + - + @@ -122,7 +122,7 @@ - + diff --git a/web/js/controller.js b/web/js/controller.js index cd57844a3..e85733a07 100644 --- a/web/js/controller.js +++ b/web/js/controller.js @@ -138,6 +138,7 @@ input.poll.disable(); gui.hide(menuScreen); stream.toggle(true); + stream.forceFullscreenMaybe(); gui.show(keyButtons[KEY.SAVE]); gui.show(keyButtons[KEY.LOAD]); // end clear diff --git a/web/js/env.js b/web/js/env.js index 0a6ddb80b..a8f79ebd6 100644 --- a/web/js/env.js +++ b/web/js/env.js @@ -108,8 +108,7 @@ const env = (() => { return { getOs: getOS, getBrowser: getBrowser, - // Check mobile type because different mobile can accept different video encoder. - isMobileDevice: () => (typeof window.orientation !== 'undefined') || (navigator.userAgent.indexOf('IEMobile') !== -1), + isMobileDevice: () => /Mobi|Android|iPhone/i.test(navigator.userAgent), display: () => ({ isPortrait: isPortrait, toggleFullscreen: toggleFullscreen, diff --git a/web/js/gui/gui.js b/web/js/gui/gui.js index 239affde7..b58ca0a3b 100644 --- a/web/js/gui/gui.js +++ b/web/js/gui/gui.js @@ -42,6 +42,36 @@ const gui = (() => { return el; } + const checkbox = (id, cb = () => ({}), checked = false, label = '', cc = '') => { + const el = _create(); + cc !== '' && el.classList.add(cc); + + let parent = el; + + if (label) { + const _label = _create('label', (el) => { + el.setAttribute('htmlFor', id); + }) + _label.innerText = label; + el.append(_label) + parent = _label; + } + + const input = _create('input', (el) => { + el.setAttribute('id', id); + el.setAttribute('name', id); + el.setAttribute('type', 'checkbox'); + el.onclick = ((e) => { + checked = e.target.checked + cb(id, checked) + }) + checked && el.setAttribute('checked', ''); + }); + parent.prepend(input); + + return el; + } + const panel = (root, title = '', cc = '', content, buttons = [], onToggle) => { const state = { br: null, @@ -221,6 +251,7 @@ const gui = (() => { fadeInOut, }, binding, + checkbox, create: _create, fragment, hide, diff --git a/web/js/settings/opts.js b/web/js/settings/opts.js index b0b2b9662..5e9fac033 100644 --- a/web/js/settings/opts.js +++ b/web/js/settings/opts.js @@ -11,5 +11,6 @@ const opts = Object.freeze({ LOG_LEVEL: 'log.level', INPUT_KEYBOARD_MAP: 'input.keyboard.map', MIRROR_SCREEN: 'mirror.screen', - VOLUME: 'volume' + VOLUME: 'volume', + FORCE_FULLSCREEN: 'force.fullscreen' }); diff --git a/web/js/settings/settings.js b/web/js/settings/settings.js index 01da6f1c4..09e1cb7f0 100644 --- a/web/js/settings/settings.js +++ b/web/js/settings/settings.js @@ -15,7 +15,7 @@ */ const settings = (() => { // internal structure version - const revision = 1.5; + const revision = 1.51; // default settings // keep them for revert to defaults option @@ -480,7 +480,7 @@ settings.renderer = (() => { case opts.MIRROR_SCREEN: _option(data).withName('Video mirroring') .add(gui.select(k, onChange, {values: ['mirror'], labels: []}, value)) - .withDescription('Disables video image smoothing by rendering the video on a canvas (much more demanding on the CPU/GPU)') + .withDescription('Disables video image smoothing by rendering the video on a canvas (much more demanding for browser)') .build(); break; case opts.VOLUME: @@ -489,6 +489,14 @@ settings.renderer = (() => { .restartNeeded() .build() break; + case opts.FORCE_FULLSCREEN: + _option(data).withName('Force fullscreen') + .withDescription( + 'Whether games should open in full-screen mode after starting up (excluding mobile devices)' + ) + .add(gui.checkbox(k, onChange, value, 'Enbabled', 'settings__option-checkbox')) + .build() + break; default: _option(data).withName(k).add(value).build(); } diff --git a/web/js/stream/stream.js b/web/js/stream/stream.js index c5dcf4782..b0fb730d8 100644 --- a/web/js/stream/stream.js +++ b/web/js/stream/stream.js @@ -12,6 +12,7 @@ const stream = (() => { poster: '/img/screen_loading.gif', mirrorMode: null, mirrorUpdateRate: 1 / 60, + forceFullscreen: true, }, state = { screen: screen, @@ -112,6 +113,12 @@ const stream = (() => { screen.classList.toggle('no-media-controls', make) } + const forceFullscreenMaybe = () => { + const touchMode = env.isMobileDevice(); + log.debug('touch check', touchMode) + !touchMode && options.forceFullscreen && toggleFullscreen(); + } + const useCustomScreen = (use) => { if (use) { if (screen.paused || screen.ended) return; @@ -158,14 +165,20 @@ const stream = (() => { const init = () => { options.mirrorMode = settings.loadOr(opts.MIRROR_SCREEN, 'none'); options.volume = settings.loadOr(opts.VOLUME, 50) / 100; + options.forceFullscreen = settings.loadOr(opts.FORCE_FULLSCREEN, false); } event.sub(SETTINGS_CHANGED, () => { - const newValue = settings.get()[opts.MIRROR_SCREEN]; + const s = settings.get(); + const newValue = s[opts.MIRROR_SCREEN]; if (newValue !== options.mirrorMode) { useCustomScreen(newValue === 'mirror'); options.mirrorMode = newValue; } + const newValue2 = s[opts.FORCE_FULLSCREEN]; + if (newValue2 !== options.forceFullscreen) { + options.forceFullscreen = newValue2; + } }); @@ -196,6 +209,7 @@ const stream = (() => { play: stream, toggle, useCustomScreen, + forceFullscreenMaybe, init } }