Implement zoom in extension popup, in-player UI
This commit is contained in:
parent
a5f35248bd
commit
ec0896e17a
@ -64,7 +64,7 @@
|
|||||||
</GhettoContextMenuOption>
|
</GhettoContextMenuOption>
|
||||||
</slot>
|
</slot>
|
||||||
</GhettoContextMenu>
|
</GhettoContextMenu>
|
||||||
<!-- <GhettoContextMenu alignment="right">
|
<GhettoContextMenu alignment="right">
|
||||||
<template v-slot:activator>
|
<template v-slot:activator>
|
||||||
Zoom
|
Zoom
|
||||||
</template>
|
</template>
|
||||||
@ -86,7 +86,7 @@
|
|||||||
/>
|
/>
|
||||||
</GhettoContextMenuItem>
|
</GhettoContextMenuItem>
|
||||||
</slot>
|
</slot>
|
||||||
</GhettoContextMenu> -->
|
</GhettoContextMenu>
|
||||||
<GhettoContextMenu alignment="right">
|
<GhettoContextMenu alignment="right">
|
||||||
<template v-slot:activator>
|
<template v-slot:activator>
|
||||||
<div class="context-item">
|
<div class="context-item">
|
||||||
|
@ -108,26 +108,6 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area">
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Default for this site</div>
|
|
||||||
<div class="select">
|
|
||||||
<select
|
|
||||||
:value="siteDefaultZoom"
|
|
||||||
@change="setDefaultZoom($event, 'site')"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="(command, index) of settings?.active.commands.zoom"
|
|
||||||
:key="index"
|
|
||||||
:value="JSON.stringify(command.arguments)"
|
|
||||||
>
|
|
||||||
{{command.label}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!--
|
<!--
|
||||||
@ -228,6 +208,13 @@ export default {
|
|||||||
KeyboardShortcutParserMixin,
|
KeyboardShortcutParserMixin,
|
||||||
CommsMixin
|
CommsMixin
|
||||||
],
|
],
|
||||||
|
props: [
|
||||||
|
'settings', // required for buttons and actions, which are global
|
||||||
|
'siteSettings',
|
||||||
|
'eventBus',
|
||||||
|
'isEditing',
|
||||||
|
'compact',
|
||||||
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
AspectRatioType,
|
AspectRatioType,
|
||||||
@ -247,13 +234,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: [
|
|
||||||
'settings', // required for buttons and actions, which are global
|
|
||||||
'siteSettings',
|
|
||||||
'eventBus',
|
|
||||||
'isEditing',
|
|
||||||
'compact',
|
|
||||||
],
|
|
||||||
created() {
|
created() {
|
||||||
if (this.isEditing) {
|
if (this.isEditing) {
|
||||||
this.enableEditMode();
|
this.enableEditMode();
|
||||||
@ -272,7 +252,6 @@ export default {
|
|||||||
getZoomForDisplay(axis) {
|
getZoomForDisplay(axis) {
|
||||||
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
|
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
|
||||||
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
|
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
|
||||||
|
|
||||||
return `${(Math.pow(2, this.zoom[axis]) * 100).toFixed()}%`
|
return `${(Math.pow(2, this.zoom[axis]) * 100).toFixed()}%`
|
||||||
},
|
},
|
||||||
toggleZoomAr() {
|
toggleZoomAr() {
|
||||||
|
@ -6,11 +6,16 @@
|
|||||||
current settings do not allow the extension to only be disabled while in full screen
|
current settings do not allow the extension to only be disabled while in full screen
|
||||||
-->
|
-->
|
||||||
<template v-if="siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Disabled">
|
<template v-if="siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Disabled">
|
||||||
|
<div class="h-full flex flex-col items-center justify-center">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
Extension is not enabled for this site.
|
Extension is not enabled for this site.
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
Please enable extension for this site.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<mdicon name="crop" :size="16" />
|
<mdicon name="crop" :size="16" />
|
||||||
<span>CROP</span>
|
<span>CROP</span>
|
||||||
@ -73,7 +78,7 @@
|
|||||||
:large="true"
|
:large="true"
|
||||||
> </AlignmentOptionsControlComponent>
|
> </AlignmentOptionsControlComponent>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,42 +1,109 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="flex flex-row" style="width: 250px;">
|
||||||
|
<div class="flex-grow">
|
||||||
Custom zoom
|
Custom zoom
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<Button
|
||||||
|
v-if="zoomAspectRatioLocked"
|
||||||
|
icon="lock"
|
||||||
|
:iconSize="16"
|
||||||
|
:fixedWidth="true"
|
||||||
|
:noPad="true"
|
||||||
|
@click="toggleZoomAr()"
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-else
|
||||||
|
icon="lock-open-variant"
|
||||||
|
:iconSize="16"
|
||||||
|
:fixedWidth="true"
|
||||||
|
:noPad="true"
|
||||||
|
@click="toggleZoomAr()"
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
icon="restore"
|
||||||
|
:iconSize="16"
|
||||||
|
:noPad="true"
|
||||||
|
@click="resetZoom()"
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="top-label">Zoom:</div>
|
<template v-if="zoomAspectRatioLocked">
|
||||||
<div class="input range-input">
|
<div class="input range-input no-bg">
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
class="slider"
|
class="slider"
|
||||||
min="0"
|
min="-1"
|
||||||
max="3"
|
max="3"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
|
:value="zoom.x"
|
||||||
|
@input="changeZoom($event.target.value)"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
class="disabled"
|
||||||
|
style="width: 2rem;"
|
||||||
|
:value="getZoomForDisplay('x')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="true">
|
</template>
|
||||||
<div class="top-label">Vertical zoom:</div>
|
<template v-else>
|
||||||
<div class="input range-input">
|
<div class="top-label">Horizontal zoom:</div>
|
||||||
|
<div class="input range-input no-bg">
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
class="slider"
|
class="slider"
|
||||||
min="0"
|
min="-1"
|
||||||
max="3"
|
max="3"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
|
:value="zoom.x"
|
||||||
|
@input="changeZoom($event.target.value, 'x')"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
class="disabled"
|
||||||
|
style="width: 2rem;"
|
||||||
|
:value="getZoomForDisplay('x')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="top-label">Vertical zoom:</div>
|
||||||
|
<div class="input range-input no-bg">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
class="slider"
|
||||||
|
min="-1"
|
||||||
|
max="3"
|
||||||
|
step="0.01"
|
||||||
|
:value="zoom.y"
|
||||||
|
@input="changeZoom($event.target.value, 'y')"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="disabled"
|
||||||
|
style="width: 2rem;"
|
||||||
|
:value="getZoomForDisplay('y')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div><input type="checkbox"/> Control vertical and horizontal zoom independently.</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Button from '@csui/src/components/Button.vue';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Button,
|
||||||
|
|
||||||
|
},
|
||||||
|
mixins: [
|
||||||
|
|
||||||
|
],
|
||||||
|
props: [
|
||||||
|
'settings', // required for buttons and actions, which are global
|
||||||
|
'eventBus'
|
||||||
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
zoomAspectRatioLocked: true,
|
zoomAspectRatioLocked: true,
|
||||||
@ -51,17 +118,42 @@ export default {
|
|||||||
stretch: null,
|
stretch: null,
|
||||||
zoom: null,
|
zoom: null,
|
||||||
pan: null
|
pan: null
|
||||||
|
},
|
||||||
|
pollingInterval: undefined,
|
||||||
|
debouncedGetEffectiveZoom: undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.eventBus?.subscribeMulti(
|
||||||
|
{
|
||||||
|
'announce-zoom': {
|
||||||
|
function: (data) => {
|
||||||
|
this.zoom = {
|
||||||
|
x: Math.log2(data.x),
|
||||||
|
y: Math.log2(data.y)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mixins: [
|
this
|
||||||
|
);
|
||||||
],
|
this.debouncedGetEffectiveZoom = _.debounce(
|
||||||
props: [
|
() => {
|
||||||
'settings', // required for buttons and actions, which are global
|
this.getEffectiveZoom();
|
||||||
'eventBus'
|
},
|
||||||
],
|
250
|
||||||
|
),
|
||||||
|
this.getEffectiveZoom();
|
||||||
|
this.pollingInterval = setInterval(this.debouncedGetEffectiveZoom, 2000);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.eventBus.unsubscribe(this);
|
||||||
|
clearInterval(this.pollingInterval);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getEffectiveZoom() {
|
||||||
|
this.eventBus?.sendToTunnel('get-effective-zoom', {});
|
||||||
|
},
|
||||||
getZoomForDisplay(axis) {
|
getZoomForDisplay(axis) {
|
||||||
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
|
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
|
||||||
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
|
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
|
||||||
@ -81,25 +173,36 @@ export default {
|
|||||||
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'});
|
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'});
|
||||||
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'});
|
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'});
|
||||||
|
|
||||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1, axis: 'y'});
|
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1});
|
||||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1, axis: 'x'});
|
|
||||||
},
|
},
|
||||||
changeZoom(newZoom, axis) {
|
changeZoom(newZoom, axis, isLinear) {
|
||||||
// we store zoom logarithmically on this compnent
|
if (isNaN(+newZoom)) {
|
||||||
if (!axis) {
|
return;
|
||||||
this.zoom.x = newZoom;
|
}
|
||||||
|
|
||||||
|
let logZoom, linZoom;
|
||||||
|
if (isLinear) {
|
||||||
|
newZoom /= 100;
|
||||||
|
logZoom = Math.log2(newZoom);
|
||||||
|
linZoom = newZoom;
|
||||||
} else {
|
} else {
|
||||||
this.zoom[axis] = newZoom;
|
logZoom = newZoom;
|
||||||
|
linZoom = Math.pow(2, newZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we store zoom logarithmically on this component
|
||||||
|
if (!axis) {
|
||||||
|
this.zoom.x = logZoom;
|
||||||
|
} else {
|
||||||
|
this.zoom[axis] = logZoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we do not use logarithmic zoom elsewhere, therefore we need to convert
|
// we do not use logarithmic zoom elsewhere, therefore we need to convert
|
||||||
newZoom = Math.pow(2, newZoom);
|
|
||||||
|
|
||||||
if (this.zoomAspectRatioLocked) {
|
if (this.zoomAspectRatioLocked) {
|
||||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: 'y'});
|
this.eventBus?.sendToTunnel('set-zoom', {zoom: linZoom});
|
||||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: 'x'});
|
|
||||||
} else {
|
} else {
|
||||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: axis ?? 'x'});
|
this.eventBus?.sendToTunnel('set-zoom', {zoom: {[axis ?? 'x']: linZoom}});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,12 @@ button, .button {
|
|||||||
border-bottom: 1px solid rgba($primary, 0.5);
|
border-bottom: 1px solid rgba($primary, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.no-bg {
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -63,6 +63,8 @@ class Resizer {
|
|||||||
currentCssValidFor: any;
|
currentCssValidFor: any;
|
||||||
currentVideoSettings: any;
|
currentVideoSettings: any;
|
||||||
|
|
||||||
|
private effectiveZoom: {x: number, y: number} = {x: 1, y: 1};
|
||||||
|
|
||||||
_lastAr: Ar = {type: AspectRatioType.Initial};
|
_lastAr: Ar = {type: AspectRatioType.Initial};
|
||||||
set lastAr(x: Ar) {
|
set lastAr(x: Ar) {
|
||||||
// emit updates for UI when setting lastAr, but only if AR really changed
|
// emit updates for UI when setting lastAr, but only if AR really changed
|
||||||
@ -88,6 +90,11 @@ class Resizer {
|
|||||||
|
|
||||||
//#region event bus configuration
|
//#region event bus configuration
|
||||||
private eventBusCommands = {
|
private eventBusCommands = {
|
||||||
|
'get-effective-zoom': [{
|
||||||
|
function: () => {
|
||||||
|
this.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom);
|
||||||
|
}
|
||||||
|
}],
|
||||||
'set-ar': [{
|
'set-ar': [{
|
||||||
function: (config: any) => {
|
function: (config: any) => {
|
||||||
this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe.
|
this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe.
|
||||||
@ -136,7 +143,9 @@ class Resizer {
|
|||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
'set-zoom': [{
|
'set-zoom': [{
|
||||||
function: (config: any) => this.setZoom(config.zoom)
|
function: (config: any) => {
|
||||||
|
this.setZoom(config?.zoom ?? {zoom: 1});
|
||||||
|
}
|
||||||
}],
|
}],
|
||||||
'change-zoom': [{
|
'change-zoom': [{
|
||||||
function: (config: any) => this.zoomStep(config.zoom)
|
function: (config: any) => this.zoomStep(config.zoom)
|
||||||
@ -449,12 +458,12 @@ class Resizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyScaling(stretchFactors: VideoDimensions, options?: {noAnnounce?: boolean, ar?: Ar}) {
|
applyScaling(stretchFactors: VideoDimensions, options?: {noAnnounce?: boolean, ar?: Ar}) {
|
||||||
// this.stretcher.chromeBugMitigation(stretchFactors);
|
this.zoom.effectiveZoom = {x: stretchFactors.xFactor, y: stretchFactors.yFactor};
|
||||||
|
|
||||||
// let the UI know
|
// announcing zoom somehow keeps incorrectly resetting zoom sliders in UI — UI is now polling for effective zoom while visible
|
||||||
if(!options?.noAnnounce) {
|
// if(!options?.noAnnounce) {
|
||||||
this.videoData.eventBus.send('announce-zoom', {x: stretchFactors.xFactor, y: stretchFactors.yFactor});
|
// this.videoData.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom);
|
||||||
}
|
// }
|
||||||
|
|
||||||
let translate = this.computeOffsets(stretchFactors, options?.ar);
|
let translate = this.computeOffsets(stretchFactors, options?.ar);
|
||||||
this.applyCss(stretchFactors, translate);
|
this.applyCss(stretchFactors, translate);
|
||||||
|
@ -30,6 +30,7 @@ class Zoom {
|
|||||||
maxScale = 8;
|
maxScale = 8;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
effectiveZoom: {x: number, y: number}; // we're setting this in Resizer based on Resizer data!
|
||||||
|
|
||||||
constructor(videoData) {
|
constructor(videoData) {
|
||||||
this.conf = videoData;
|
this.conf = videoData;
|
||||||
@ -50,7 +51,12 @@ class Zoom {
|
|||||||
* @param axis — leave undefined to apply zoom to both axes
|
* @param axis — leave undefined to apply zoom to both axes
|
||||||
*/
|
*/
|
||||||
zoomStep(amount: number, axis?: 'x' | 'y') {
|
zoomStep(amount: number, axis?: 'x' | 'y') {
|
||||||
let newLog = axis === 'y' ? this.logScaleY : this.logScale;
|
const effectiveLog = {
|
||||||
|
x: Math.log2(this.effectiveZoom.x),
|
||||||
|
y: Math.log2(this.effectiveZoom.y)
|
||||||
|
};
|
||||||
|
|
||||||
|
let newLog = axis === 'y' ? effectiveLog.y : effectiveLog.x;
|
||||||
newLog += amount;
|
newLog += amount;
|
||||||
newLog = Math.min(Math.max(newLog, LOG_MIN_SCALE), LOG_MAX_SCALE);
|
newLog = Math.min(Math.max(newLog, LOG_MIN_SCALE), LOG_MAX_SCALE);
|
||||||
|
|
||||||
@ -65,7 +71,6 @@ class Zoom {
|
|||||||
this.scale = Math.pow(2, this.logScale);
|
this.scale = Math.pow(2, this.logScale);
|
||||||
this.scaleY = Math.pow(2, this.logScaleY);
|
this.scaleY = Math.pow(2, this.logScaleY);
|
||||||
|
|
||||||
this.logger.info('zoomStep', "changing zoom by", amount, ". New zoom level:", this.scale);
|
|
||||||
this.processZoom();
|
this.processZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,18 +99,6 @@ class Zoom {
|
|||||||
this.conf.resizer.toFixedAr();
|
this.conf.resizer.toFixedAr();
|
||||||
this.conf.resizer.applyScaling({xFactor: this.scale, yFactor: this.scaleY}, {noAnnounce: true});
|
this.conf.resizer.applyScaling({xFactor: this.scale, yFactor: this.scaleY}, {noAnnounce: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
applyZoom(stretchFactors){
|
|
||||||
if (!stretchFactors) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.logger.info('setZoom', "Applying zoom. Stretch factors pre:", stretchFactors, " —> scale:", this.scale);
|
|
||||||
|
|
||||||
stretchFactors.xFactor *= this.scale;
|
|
||||||
stretchFactors.yFactor *= this.scale;
|
|
||||||
|
|
||||||
this.logger.info('setZoom', "Applying zoom. Stretch factors post:", stretchFactors);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Zoom;
|
export default Zoom;
|
||||||
|
Loading…
Reference in New Issue
Block a user