Don't reload images on start (they are already loaded)

Someone should check if the start param in this function is even needed now, looks like an artifact from the old renderer
This commit is contained in:
Daniel 2020-03-05 20:35:25 +00:00 committed by Tim Bentley
parent 79950b6a85
commit d5892b6258
13 changed files with 543 additions and 270 deletions

View File

@ -333,6 +333,7 @@ class Settings(QtCore.QSettings):
'themes/last directory import': None, 'themes/last directory import': None,
'themes/theme level': ThemeLevel.Global, 'themes/theme level': ThemeLevel.Global,
'themes/wrap footer': False, 'themes/wrap footer': False,
'themes/item transitions': False,
'user interface/live panel': True, 'user interface/live panel': True,
'user interface/live splitter geometry': QtCore.QByteArray(), 'user interface/live splitter geometry': QtCore.QByteArray(),
'user interface/lock panel': True, 'user interface/lock panel': True,

View File

@ -22,14 +22,53 @@ sup {
font-size: smaller !important; font-size: smaller !important;
} }
body,
.reveal .slides,
.reveal .footer {
transition: none;
z-index: 2;
}
body.transition,
body.transition .reveal .slides,
body.transition .reveal .footer {
/* Transition speed for content visibility */
transition: opacity 800ms ease-in-out !important;
}
.reveal .slide-background {
opacity: 1;
visibility: visible;
transition: none !important;
z-index: 2;
/* important required to override inline attributes */
background-repeat: no-repeat !important;
background-size: cover !important;
background-position: 50% 50% !important;
}
.reveal .slide-background:nth-child(n+3) {
/* Hide backgrounds beyond the first 2 (prevents ugly transitions in corner case) */
visibility: hidden;
}
.reveal .slide-background.future,
.reveal .slide-background.past {
opacity: 0;
}
body.transition .reveal .slide-background {
transition: all 800ms ease-in-out !important;
}
.reveal .slides > section, .reveal .slides > section,
.reveal .slides > section > section { .reveal .slides > section > section {
padding: 0; padding: 0;
max-height: 100%; max-height: 100%;
} }
.reveal .slides > section.text-slides { .reveal .slides > section {
/* Need to override reveal styles to use our text aligment */ /* Need to override reveal styles to use our text alignment */
display: flex !important; display: flex !important;
flex-direction: column; flex-direction: column;
word-wrap: break-word; word-wrap: break-word;
@ -318,21 +357,24 @@ sup {
.reveal[class*=fade] .slides section.present:not([data-transition]) { .reveal[class*=fade] .slides section.present:not([data-transition]) {
transition-delay: 400ms; } transition-delay: 400ms; }
.reveal .slides section.present[data-transition*=fade], .reveal .slides section.present[data-transition-speed="fast"][data-transition*=fade],
.reveal[data-transition-speed="fast"][class*=fade] .slides section.present:not([data-transition]) { .reveal[data-transition-speed="fast"][class*=fade] .slides section.present:not([data-transition]) {
transition-delay: 200ms; } transition-delay: 200ms; }
.reveal .slides section.present[data-transition*=fade], .reveal .slides section.present[data-transition-speed="slow"][data-transition*=fade],
.reveal[data-transition-speed="slow"][class*=fade] .slides section.present:not([data-transition]) { .reveal[data-transition-speed="slow"][class*=fade] .slides section.present:not([data-transition]) {
transition-delay: 800ms; } transition-delay: 800ms; }
.reveal[class*=fade] .slides section { .reveal[class*=fade] .slides section,
.reveal .slides section[class*=fade] {
transition-duration: 400ms !important; } transition-duration: 400ms !important; }
.reveal[data-transition-speed="fast"][class*=fade] .slides section { .reveal[data-transition-speed="fast"][class*=fade] .slides section,
.reveal .slides section[data-transition-speed="fast"][class*=fade] {
transition-duration: 200ms !important; } transition-duration: 200ms !important; }
.reveal[data-transition-speed="slow"][class*=fade] .slides section { .reveal[data-transition-speed="slow"][class*=fade] .slides section,
.reveal .slides section[data-transition-speed="slow"][class*=fade] {
transition-duration: 800ms !important; } transition-duration: 800ms !important; }
.reveal[class*=fade].overview .slides section { .reveal[class*=fade].overview .slides section {

View File

@ -352,6 +352,9 @@ AudioPlayer.prototype.stop = function () {
* The Display object is what we use from OpenLP * The Display object is what we use from OpenLP
*/ */
var Display = { var Display = {
_slidesContainer: null,
_footerContainer: null,
_backgroundsContainer: null,
_alerts: [], _alerts: [],
_slides: {}, _slides: {},
_alertSettings: {}, _alertSettings: {},
@ -359,6 +362,8 @@ var Display = {
_transitionState: TransitionState.NoTransition, _transitionState: TransitionState.NoTransition,
_animationState: AnimationState.NoAnimation, _animationState: AnimationState.NoAnimation,
_doTransitions: false, _doTransitions: false,
_doItemTransitions: false,
_themeApplied: true,
_revealConfig: { _revealConfig: {
margin: 0.0, margin: 0.0,
minScale: 1.0, minScale: 1.0,
@ -380,9 +385,16 @@ var Display = {
/** /**
* Start up reveal and do any other initialisation * Start up reveal and do any other initialisation
*/ */
init: function (doTransitions=false) { init: function (doTransitions=false, doItemtransitions=false) {
var globalBackground = $("#global-background")[0];
globalBackground.style.cssText = "";
globalBackground.style.setProperty("background", "black");
Display._slidesContainer = $(".slides")[0];
Display._footerContainer = $(".footer")[0];
Display._backgroundsContainer = $(".backgrounds")[0];
Display._doTransitions = doTransitions; Display._doTransitions = doTransitions;
Reveal.initialize(Display._revealConfig); Reveal.initialize(Display._revealConfig);
Display.setItemTransition(doItemtransitions && doTransitions);
displayWatcher.setInitialised(true); displayWatcher.setInitialised(true);
}, },
/** /**
@ -394,30 +406,79 @@ var Display = {
Reveal.slide(0); Reveal.slide(0);
}, },
/** /**
* Set the transition type * Enable/Disable item transitions
* @param {string} transitionType - Can be one of "none", "fade", "slide", "convex", "concave", "zoom"
* @param {string} transitionSpeed - Can be one of "default", "fast", "slow"
*/ */
setTransition: function (transitionType, transitionSpeed) { setItemTransition: function (enable) {
Reveal.configure({"transition": transitionType, "transitionSpeed": transitionSpeed}); Display._doItemTransitions = enable;
var body = $("body")[0];
if (enable) {
body.classList.add("transition");
Reveal.configure({"backgroundTransition": "fade", "transitionSpeed": "default"});
} else {
body.classList.remove("transition");
Reveal.configure({"backgroundTransition": "none"});
}
}, },
/** /**
* Clear the current list of slides * Clear the current list of slides
*/ */
clearSlides: function () { clearSlides: function () {
$(".slides")[0].innerHTML = ""; Display._slidesContainer.innerHTML = "";
Display._clearSlidesList();
},
/**
* Clear the current list of slides
*/
_clearSlidesList: function () {
Display._footerContainer.innerHTML = "";
Display._slides = {}; Display._slides = {};
}, },
/**
* Add new item/slides, replacing the old one
* Clears current list of slides but allows time for a transition animation
* Items are ordered newest to oldest in the slides container
* @param {element} new_slides - New slides to display
* @param {element} is_text - Used to decide if the theme main area constraints should apply
*/
replaceSlides: function (new_slides, is_text=false) {
if (Display._doItemTransitions) {
new_slides.setAttribute('data-transition', "fade");
new_slides.setAttribute('data-transition-speed', "default");
}
new_slides.classList.add("future");
Display.applyTheme(new_slides, is_text);
Display._slidesContainer.prepend(new_slides);
var currentSlide = Reveal.getIndices();
if (Display._doItemTransitions && Display._slidesContainer.children.length >= 2) {
// Set the slide one section ahead so we'll stay on the old slide after reinit
Reveal.slide(1, currentSlide.v);
Display.reinit();
// Timeout to allow time to transition before deleting the old slides
setTimeout (Display._removeLastSection, 5000);
} else {
Reveal.slide(0, currentSlide.v);
Reveal.sync();
Display._removeLastSection();
}
},
/**
* Removes the last slides item if there are more than one
*/
_removeLastSection: function () {
if (Display._slidesContainer.children.length > 1) {
Display._slidesContainer.lastChild.remove();
}
},
/** /**
* Checks if the present slide content fits within the slide * Checks if the present slide content fits within the slide
*/ */
doesContentFit: function () { doesContentFit: function () {
var currSlide = $("section.text-slides"); var currSlide = $("section.text-slides");
if (currSlide.length === 0) { if (currSlide.length === 0) {
currSlide = $(".slides"); currSlide = Display._footerContainer;
} else {
currSlide = currSlide[0];
} }
currSlide = currSlide[0];
console.debug("scrollHeight: " + currSlide.scrollHeight + ", clientHeight: " + currSlide.clientHeight);
return currSlide.clientHeight >= currSlide.scrollHeight; return currSlide.clientHeight >= currSlide.scrollHeight;
}, },
/** /**
@ -426,22 +487,17 @@ var Display = {
* @param {string} image - Path to the splash image * @param {string} image - Path to the splash image
*/ */
setStartupSplashScreen: function(bg_color, image) { setStartupSplashScreen: function(bg_color, image) {
Display.clearSlides(); Display._clearSlidesList();
var globalBackground = $("#global-background")[0];
globalBackground.style.cssText = "";
globalBackground.style.setProperty("background", bg_color);
var slidesDiv = $(".slides")[0];
var section = document.createElement("section"); var section = document.createElement("section");
section.setAttribute("id", 0); section.setAttribute("id", 0);
section.setAttribute("data-background", bg_color); section.setAttribute("data-background", bg_color);
section.setAttribute("style", "height: 100%; width: 100%; position: relative;"); section.setAttribute("style", "height: 100%; width: 100%;");
var img = document.createElement('img'); var img = document.createElement('img');
img.src = image; img.src = image;
img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-height: 100%; max-width: 100%"); img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-height: 100%; max-width: 100%");
section.appendChild(img); section.appendChild(img);
slidesDiv.appendChild(section);
Display._slides['0'] = 0; Display._slides['0'] = 0;
Display.reinit(); Display.replaceSlides(section);
}, },
/** /**
* Set fullscreen image from path * Set fullscreen image from path
@ -450,10 +506,6 @@ var Display = {
*/ */
setFullscreenImage: function(bg_color, image) { setFullscreenImage: function(bg_color, image) {
Display.clearSlides(); Display.clearSlides();
var globalBackground = $("#global-background")[0];
globalBackground.style.cssText = "";
globalBackground.style.setProperty("background", bg_color);
var slidesDiv = $(".slides")[0];
var section = document.createElement("section"); var section = document.createElement("section");
section.setAttribute("id", 0); section.setAttribute("id", 0);
section.setAttribute("data-background", bg_color); section.setAttribute("data-background", bg_color);
@ -462,9 +514,8 @@ var Display = {
img.src = image; img.src = image;
img.setAttribute("style", "height: 100%; width: 100%"); img.setAttribute("style", "height: 100%; width: 100%");
section.appendChild(img); section.appendChild(img);
slidesDiv.appendChild(section);
Display._slides['0'] = 0; Display._slides['0'] = 0;
Display.reinit(); Display.replaceSlides(parentSection);
}, },
/** /**
* Set fullscreen image from base64 data * Set fullscreen image from base64 data
@ -473,10 +524,6 @@ var Display = {
*/ */
setFullscreenImageFromData: function(bg_color, image_data) { setFullscreenImageFromData: function(bg_color, image_data) {
Display.clearSlides(); Display.clearSlides();
var globalBackground = $("#global-background")[0];
globalBackground.style.cssText = "";
globalBackground.style.setProperty("background", bg_color);
var slidesDiv = $(".slides")[0];
var section = document.createElement("section"); var section = document.createElement("section");
section.setAttribute("id", 0); section.setAttribute("id", 0);
section.setAttribute("data-background", bg_color); section.setAttribute("data-background", bg_color);
@ -485,7 +532,7 @@ var Display = {
img.src = 'data:image/png;base64,' + image_data; img.src = 'data:image/png;base64,' + image_data;
img.setAttribute("style", "height: 100%; width: 100%"); img.setAttribute("style", "height: 100%; width: 100%");
section.appendChild(img); section.appendChild(img);
slidesDiv.appendChild(section); Display._slidesContainer.appendChild(section);
Display._slides['0'] = 0; Display._slides['0'] = 0;
Display.reinit(); Display.reinit();
}, },
@ -632,62 +679,64 @@ var Display = {
} }
}, },
/** /**
* Add a slide. If the slide exists but the HTML is different, update the slide. * Create a text slide.
* @param {string} verse - The verse number, e.g. "v1" * @param {string} verse - The verse number, e.g. "v1"
* @param {string} text - The HTML for the verse, e.g. "line1<br>line2" * @param {string} text - The HTML for the verse, e.g. "line1<br>line2"
* @param {string} footer_text - The HTML for the footer
* @param {bool} reinit - True if React should reinit when creating a new slide
*/ */
addTextSlide: function (verse, text, footerText, reinit=true) { _createTextSlide: function (verse, text) {
var slide; var slide;
var html = _prepareText(text); var html = _prepareText(text);
if (Display._slides.hasOwnProperty(verse)) { slide = document.createElement("section");
slide = $("#" + verse)[0]; slide.setAttribute("id", verse);
if (slide.innerHTML != html) { slide.innerHTML = html;
slide.innerHTML = html; return slide;
}
} else {
var parent = $("section.text-slides");
if (parent.length === 0) {
Display._createTextContainer();
parent = $("section.text-slides");
}
parent = parent[0];
slide = document.createElement("section");
slide.setAttribute("id", verse);
slide.innerHTML = html;
parent.appendChild(slide);
Display._slides[verse] = parent.children.length - 1;
if (footerText) {
$(".footer")[0].innerHTML = footerText;
}
if (reinit) {
Display.reinit();
}
}
}, },
/** /**
* Set text slides. * Set text slides.
* @param {Object[]} slides - A list of slides to add as JS objects: {"verse": "v1", "text": "line 1\nline2"} * @param {Object[]} slides - A list of slides to add as JS objects: {"verse": "v1", "text": "line 1\nline2"}
*/ */
setTextSlides: function (slides) { setTextSlides: function (slides) {
Display.clearSlides(); Display._clearSlidesList();
var slide_html;
var parentSection = document.createElement("section");
parentSection.classList = "text-slides";
slides.forEach(function (slide) { slides.forEach(function (slide) {
Display.addTextSlide(slide.verse, slide.text, slide.footer, false); slide_html = Display._createTextSlide(slide.verse, slide.text);
parentSection.appendChild(slide_html);
Display._slides[slide.verse] = parentSection.children.length - 1;
if (slide.footer) {
Display._footerContainer.innerHTML = slide.footer;
}
}); });
Display.reinit(); Display.replaceSlides(parentSection, true);
}, },
/** /**
* Create the <section> that will contain text slides (vertical slides in react) * Set a single text slide. This changes the slide with no transition.
* Prevents the need to reapply the theme if only changing content.
* @param String slide - Text to put on the slide
*/ */
_createTextContainer: function () { setTextSlide: function (text) {
var slideContainer = document.createElement("section"); if (Display._slides.hasOwnProperty("test-slide") && Object.keys(Display._slides).length === 1) {
slideContainer.classList = "text-slides"; var slide = $("#" + "test-slide")[0];
var slidesDiv = $(".slides")[0]; var html = _prepareText(text);
slidesDiv.appendChild(slideContainer); if (slide.innerHTML != html) {
// Apply the current theme to the new container slide.innerHTML = html;
if (!!Display._theme) { }
Display.setTheme(Display._theme); if (!Display._themeApplied) {
Display.applyTheme(slide.parent);
}
} else {
Display._clearSlidesList();
var slide_html;
var parentSection = document.createElement("section");
parentSection.classList = "text-slides";
slide_html = Display._createTextSlide("test-slide", text);
parentSection.appendChild(slide_html);
Display._slides["test-slide"] = 0;
Display.applyTheme(parentSection);
Display._slidesContainer.innerHTML = "";
Display._slidesContainer.prepend(parentSection);
Display.reinit();
} }
}, },
/** /**
@ -695,8 +744,7 @@ var Display = {
* @param {Object[]} slides - A list of images to add as JS objects [{"path": "url/to/file"}] * @param {Object[]} slides - A list of images to add as JS objects [{"path": "url/to/file"}]
*/ */
setImageSlides: function (slides) { setImageSlides: function (slides) {
Display.clearSlides(); Display._clearSlidesList();
var slidesDiv = $(".slides")[0];
var parentSection = document.createElement("section"); var parentSection = document.createElement("section");
slides.forEach(function (slide, index) { slides.forEach(function (slide, index) {
var section = document.createElement("section"); var section = document.createElement("section");
@ -710,15 +758,14 @@ var Display = {
parentSection.appendChild(section); parentSection.appendChild(section);
Display._slides[index.toString()] = index; Display._slides[index.toString()] = index;
}); });
slidesDiv.appendChild(parentSection); Display.replaceSlides(parentSection);
Display.reinit();
}, },
/** /**
* Set a video * Set a video
* @param {Object} video - The video to show as a JS object: {"path": "url/to/file"} * @param {Object} video - The video to show as a JS object: {"path": "url/to/file"}
*/ */
setVideo: function (video) { setVideo: function (video) {
Display.clearSlides(); Display._clearSlidesList();
var section = document.createElement("section"); var section = document.createElement("section");
section.setAttribute("data-background", "#000"); section.setAttribute("data-background", "#000");
var videoElement = document.createElement("video"); var videoElement = document.createElement("video");
@ -747,8 +794,7 @@ var Display = {
mediaWatcher.has_muted(event.target.muted); mediaWatcher.has_muted(event.target.muted);
}); });
section.appendChild(videoElement); section.appendChild(videoElement);
$(".slides")[0].appendChild(section); Display.replaceSlides(section);
Display.reinit();
}, },
/** /**
* Play a video * Play a video
@ -856,32 +902,45 @@ var Display = {
/** /**
* Blank the screen * Blank the screen
*/ */
blankToBlack: function () { toBlack: function () {
var documentBody = $("body")[0];
documentBody.style.opacity = 1;
if (!Reveal.isPaused()) { if (!Reveal.isPaused()) {
Reveal.togglePause(); Reveal.togglePause();
} }
// var slidesDiv = $(".slides")[0];
}, },
/** /**
* Blank to theme * Hide all but theme background
*/ */
blankToTheme: function () { toTheme: function () {
var slidesDiv = $(".slides")[0]; var documentBody = $("body")[0];
slidesDiv.style.visibility = "hidden"; documentBody.style.opacity = 1;
var footerDiv = $(".footer")[0]; Display._slidesContainer.style.opacity = 0;
footerDiv.style.visibility = "hidden"; Display._footerContainer.style.opacity = 0;
if (Reveal.isPaused()) { if (Reveal.isPaused()) {
Reveal.togglePause(); Reveal.togglePause();
} }
}, },
/**
* Hide everything (CAUTION: Causes a invisible mouse barrier)
*/
toTransparent: function () {
Display._slidesContainer.style.opacity = 0;
Display._footerContainer.style.opacity = 0;
var documentBody = $("body")[0];
documentBody.style.opacity = 0;
if (!Reveal.isPaused()) {
Reveal.togglePause();
}
},
/** /**
* Show the screen * Show the screen
*/ */
show: function () { show: function () {
var slidesDiv = $(".slides")[0]; var documentBody = $("body")[0];
slidesDiv.style.visibility = "visible"; documentBody.style.opacity = 1;
var footerDiv = $(".footer")[0]; Display._slidesContainer.style.opacity = 1;
footerDiv.style.visibility = "visible"; Display._footerContainer.style.opacity = 1;
if (Reveal.isPaused()) { if (Reveal.isPaused()) {
Reveal.togglePause(); Reveal.togglePause();
} }
@ -903,13 +962,31 @@ var Display = {
var dh = parseFloat(_getStyle(d, "height")); var dh = parseFloat(_getStyle(d, "height"));
return Math.floor(dh / lh); return Math.floor(dh / lh);
}, },
/**
* Prepare the theme for the next item to be added
* @param theme The theme to be used
*/
setTheme: function (theme) { setTheme: function (theme) {
Display._theme = theme; if (Display._theme != theme) {
Display._themeApplied = false;
Display._theme = theme;
}
},
/**
* Apply the theme to the provided element
* @param targetElement The target element to apply the theme (expected to be a <section> in the slides container)
* @param is_text Used to decide if the main area constraints should be applied
*/
applyTheme: function (targetElement, is_text=true) {
Display._themeApplied = true;
if (!Display._theme) {
return;
}
// Set slide transitions // Set slide transitions
var new_transition_type = "none", var new_transition_type = "none",
new_transition_speed = "default"; new_transition_speed = "default";
if (!!theme.display_slide_transition && Display._doTransitions) { if (!!Display._theme.display_slide_transition && Display._doTransitions) {
switch (theme.display_slide_transition_type) { switch (Display._theme.display_slide_transition_type) {
case TransitionType.Fade: case TransitionType.Fade:
new_transition_type = "fade"; new_transition_type = "fade";
break; break;
@ -928,7 +1005,7 @@ var Display = {
default: default:
new_transition_type = "fade"; new_transition_type = "fade";
} }
switch (theme.display_slide_transition_speed) { switch (Display._theme.display_slide_transition_speed) {
case TransitionSpeed.Normal: case TransitionSpeed.Normal:
new_transition_speed = "default"; new_transition_speed = "default";
break; break;
@ -941,7 +1018,7 @@ var Display = {
default: default:
new_transition_speed = "default"; new_transition_speed = "default";
} }
switch (theme.display_slide_transition_direction) { switch (Display._theme.display_slide_transition_direction) {
case TransitionDirection.Vertical: case TransitionDirection.Vertical:
new_transition_type += "-vertical"; new_transition_type += "-vertical";
break; break;
@ -949,95 +1026,102 @@ var Display = {
default: default:
new_transition_type += "-horizontal"; new_transition_type += "-horizontal";
} }
if (theme.display_slide_transition_reverse) { if (Display._theme.display_slide_transition_reverse) {
new_transition_type += "-reverse"; new_transition_type += "-reverse";
} }
} }
var slides = targetElement.children;
Display.setTransition(new_transition_type, new_transition_speed); for (var i = 0; i < slides.length; i++) {
slides[i].setAttribute("data-transition", new_transition_type);
slides[i].setAttribute("data-transition-speed", new_transition_speed);
}
// Set the background // Set the background
var globalBackground = $("#global-background")[0]; var backgroundContent = "";
var backgroundStyle = {};
var backgroundHtml = ""; var backgroundHtml = "";
switch (theme.background_type) { var globalBackground = $("#global-background")[0];
globalBackground.style.setProperty("background", "black");
switch (Display._theme.background_type) {
case BackgroundType.Transparent: case BackgroundType.Transparent:
backgroundStyle.background = "transparent"; backgroundContent = "transparent";
globalBackground.style.setProperty("background", "transparent");
break; break;
case BackgroundType.Solid: case BackgroundType.Solid:
backgroundStyle.background = theme.background_color; backgroundContent = Display._theme.background_color;
break; break;
case BackgroundType.Gradient: case BackgroundType.Gradient:
switch (theme.background_direction) { switch (Display._theme.background_direction) {
case GradientType.Horizontal: case GradientType.Horizontal:
backgroundStyle.background = _buildLinearGradient("left top", "left bottom", backgroundContent = _buildLinearGradient("left top", "left bottom",
theme.background_start_color, Display._theme.background_start_color,
theme.background_end_color); Display._theme.background_end_color);
break; break;
case GradientType.Vertical: case GradientType.Vertical:
backgroundStyle.background = _buildLinearGradient("left top", "right top", backgroundContent = _buildLinearGradient("left top", "right top",
theme.background_start_color, Display._theme.background_start_color,
theme.background_end_color); Display._theme.background_end_color);
break; break;
case GradientType.LeftTop: case GradientType.LeftTop:
backgroundStyle.background = _buildLinearGradient("left top", "right bottom", backgroundContent = _buildLinearGradient("left top", "right bottom",
theme.background_start_color, Display._theme.background_start_color,
theme.background_end_color); Display._theme.background_end_color);
break; break;
case GradientType.LeftBottom: case GradientType.LeftBottom:
backgroundStyle.background = _buildLinearGradient("left bottom", "right top", backgroundContent = _buildLinearGradient("left bottom", "right top",
theme.background_start_color, Display._theme.background_start_color,
theme.background_end_color); Display._theme.background_end_color);
break; break;
case GradientType.Circular: case GradientType.Circular:
backgroundStyle.background = _buildRadialGradient(window.innerWidth / 2, theme.background_start_color, backgroundContent = _buildRadialGradient(window.innerWidth / 2, Display._theme.background_start_color,
theme.background_end_color); Display._theme.background_end_color);
break; break;
default: default:
backgroundStyle.background = "#000"; backgroundContent = "#000";
} }
break; break;
case BackgroundType.Image: case BackgroundType.Image:
backgroundStyle["background-image"] = "url('" + theme.background_filename + "')"; backgroundContent = "url('" + Display._theme.background_filename + "')";
console.warn(backgroundStyle["background-image"]); console.warn(backgroundContent);
break; break;
case BackgroundType.Video: case BackgroundType.Video:
// never actually used since background type is overridden from video to transparent in window.py // never actually used since background type is overridden from video to transparent in window.py
backgroundStyle["background-color"] = theme.background_border_color; backgroundContent = Display._theme.background_border_color;
backgroundHtml = "<video loop autoplay muted><source src='" + theme.background_filename + "'></video>"; backgroundHtml = "<video loop autoplay muted><source src='" + Display._theme.background_filename + "'></video>";
console.warn(backgroundHtml); console.warn(backgroundHtml);
break; break;
default: default:
backgroundStyle.background = "#000"; backgroundContent = "#000";
}
globalBackground.style.cssText = "";
for (var bgKey in backgroundStyle) {
if (backgroundStyle.hasOwnProperty(bgKey)) {
globalBackground.style.setProperty(bgKey, backgroundStyle[bgKey]);
}
} }
targetElement.style.cssText = "";
targetElement.setAttribute("data-background", backgroundContent);
targetElement.setAttribute("data-background-size", "cover");
if (!!backgroundHtml) { if (!!backgroundHtml) {
globalBackground.innerHTML = backgroundHtml; background.innerHTML = backgroundHtml;
} }
// set up the main area // set up the main area
if (!is_text) {
// only transition and background for non text slides
return;
}
mainStyle = {}; mainStyle = {};
if (!!theme.font_main_outline) { if (!!Display._theme.font_main_outline) {
mainStyle["-webkit-text-stroke"] = "" + theme.font_main_outline_size + "pt " + mainStyle["-webkit-text-stroke"] = "" + Display._theme.font_main_outline_size + "pt " +
theme.font_main_outline_color; Display._theme.font_main_outline_color;
mainStyle["-webkit-text-fill-color"] = theme.font_main_color; mainStyle["-webkit-text-fill-color"] = Display._theme.font_main_color;
} }
// These need to be fixed, in the Python they use a width passed in as a parameter // These need to be fixed, in the Python they use a width passed in as a parameter
mainStyle.width = theme.font_main_width + "px"; mainStyle.width = Display._theme.font_main_width + "px";
mainStyle.height = theme.font_main_height + "px"; mainStyle.height = Display._theme.font_main_height + "px";
mainStyle["margin-top"] = "" + theme.font_main_y + "px"; mainStyle["margin-top"] = "" + Display._theme.font_main_y + "px";
mainStyle.left = "" + theme.font_main_x + "px"; mainStyle.left = "" + Display._theme.font_main_x + "px";
mainStyle.color = theme.font_main_color; mainStyle.color = Display._theme.font_main_color;
mainStyle["font-family"] = theme.font_main_name; mainStyle["font-family"] = Display._theme.font_main_name;
mainStyle["font-size"] = "" + theme.font_main_size + "pt"; mainStyle["font-size"] = "" + Display._theme.font_main_size + "pt";
mainStyle["font-style"] = !!theme.font_main_italics ? "italic" : ""; mainStyle["font-style"] = !!Display._theme.font_main_italics ? "italic" : "";
mainStyle["font-weight"] = !!theme.font_main_bold ? "bold" : ""; mainStyle["font-weight"] = !!Display._theme.font_main_bold ? "bold" : "";
mainStyle["line-height"] = "" + (100 + theme.font_main_line_adjustment) + "%"; mainStyle["line-height"] = "" + (100 + Display._theme.font_main_line_adjustment) + "%";
// Using text-align-last because there is a <br> seperating each line // Using text-align-last because there is a <br> seperating each line
switch (theme.display_horizontal_align) { switch (Display._theme.display_horizontal_align) {
case HorizontalAlign.Justify: case HorizontalAlign.Justify:
mainStyle["text-align"] = "justify"; mainStyle["text-align"] = "justify";
mainStyle["text-align-last"] = "justify"; mainStyle["text-align-last"] = "justify";
@ -1058,7 +1142,7 @@ var Display = {
mainStyle["text-align"] = "center"; mainStyle["text-align"] = "center";
mainStyle["text-align-last"] = "center"; mainStyle["text-align-last"] = "center";
} }
switch (theme.display_vertical_align) { switch (Display._theme.display_vertical_align) {
case VerticalAlign.Middle: case VerticalAlign.Middle:
mainStyle["justify-content"] = "center"; mainStyle["justify-content"] = "center";
break; break;
@ -1068,23 +1152,19 @@ var Display = {
case VerticalAlign.Bottom: case VerticalAlign.Bottom:
mainStyle["justify-content"] = "flex-end"; mainStyle["justify-content"] = "flex-end";
// This gets around the webkit scroll height bug // This gets around the webkit scroll height bug
mainStyle["padding-bottom"] = "" + (theme.font_main_size / 8) + "px"; mainStyle["padding-bottom"] = "" + (Display._theme.font_main_size / 8) + "px";
break; break;
default: default:
mainStyle["justify-content"] = "center"; mainStyle["justify-content"] = "center";
} }
if (theme.hasOwnProperty('font_main_shadow_size') && !!theme.font_main_shadow) { if (Display._theme.hasOwnProperty('font_main_shadow_size') && !!Display._theme.font_main_shadow) {
mainStyle["text-shadow"] = theme.font_main_shadow_color + " " + theme.font_main_shadow_size + "pt " + mainStyle["text-shadow"] = Display._theme.font_main_shadow_color + " " + Display._theme.font_main_shadow_size + "pt " +
theme.font_main_shadow_size + "pt"; Display._theme.font_main_shadow_size + "pt";
} }
var slidesDiv = $("section.text-slides"); targetElement.style.cssText = "";
if (slidesDiv.length > 0) { for (var mainKey in mainStyle) {
slidesDiv = slidesDiv[0]; if (mainStyle.hasOwnProperty(mainKey)) {
slidesDiv.style.cssText = ""; targetElement.style.setProperty(mainKey, mainStyle[mainKey]);
for (var mainKey in mainStyle) {
if (mainStyle.hasOwnProperty(mainKey)) {
slidesDiv.style.setProperty(mainKey, mainStyle[mainKey]);
}
} }
} }
// Set up the footer // Set up the footer
@ -1092,19 +1172,18 @@ var Display = {
"text-align": "left" "text-align": "left"
}; };
footerStyle.position = "absolute"; footerStyle.position = "absolute";
footerStyle.left = "" + theme.font_footer_x + "px"; footerStyle.left = "" + Display._theme.font_footer_x + "px";
footerStyle.top = "" + theme.font_footer_y + "px"; footerStyle.top = "" + Display._theme.font_footer_y + "px";
footerStyle.width = "" + theme.font_footer_width + "px"; footerStyle.width = "" + Display._theme.font_footer_width + "px";
footerStyle.height = "" + theme.font_footer_height + "px"; footerStyle.height = "" + Display._theme.font_footer_height + "px";
footerStyle.color = theme.font_footer_color; footerStyle.color = Display._theme.font_footer_color;
footerStyle["font-family"] = theme.font_footer_name; footerStyle["font-family"] = Display._theme.font_footer_name;
footerStyle["font-size"] = "" + theme.font_footer_size + "pt"; footerStyle["font-size"] = "" + Display._theme.font_footer_size + "pt";
footerStyle["white-space"] = theme.font_footer_wrap ? "normal" : "nowrap"; footerStyle["white-space"] = Display._theme.font_footer_wrap ? "normal" : "nowrap";
var footer = $(".footer")[0]; Display._footerContainer.style.cssText = "";
footer.style.cssText = "";
for (var footerKey in footerStyle) { for (var footerKey in footerStyle) {
if (footerStyle.hasOwnProperty(footerKey)) { if (footerStyle.hasOwnProperty(footerKey)) {
footer.style.setProperty(footerKey, footerStyle[footerKey]); Display._footerContainer.style.setProperty(footerKey, footerStyle[footerKey]);
} }
} }
}, },

View File

@ -812,7 +812,7 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow):
""" """
self.clear_slides() self.clear_slides()
self.log_debug('_text_fits_on_slide: 1\n{text}'.format(text=text)) self.log_debug('_text_fits_on_slide: 1\n{text}'.format(text=text))
self.run_javascript('Display.addTextSlide("v1", "{text}", "Dummy Footer");' self.run_javascript('Display.setTextSlide("{text}");'
.format(text=text.replace('"', '\\"')), is_sync=True) .format(text=text.replace('"', '\\"')), is_sync=True)
self.log_debug('_text_fits_on_slide: 2') self.log_debug('_text_fits_on_slide: 2')
does_text_fits = self.run_javascript('Display.doesContentFit();', is_sync=True) does_text_fits = self.run_javascript('Display.doesContentFit();', is_sync=True)

View File

@ -244,7 +244,9 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties):
Add stuff after page initialisation Add stuff after page initialisation
""" """
js_is_display = str(self.is_display).lower() js_is_display = str(self.is_display).lower()
self.run_javascript('Display.init({do_transitions});'.format(do_transitions=js_is_display)) item_transitions = str(self.settings.value('themes/item transitions')).lower()
self.run_javascript('Display.init({do_transitions}, {do_item_transitions});'
.format(do_transitions=js_is_display, do_item_transitions=item_transitions))
wait_for(lambda: self._is_initialised) wait_for(lambda: self._is_initialised)
if self.scale != 1: if self.scale != 1:
self.set_scale(self.scale) self.set_scale(self.scale)
@ -430,12 +432,6 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties):
if self.is_display: if self.is_display:
Registry().execute('live_display_active') Registry().execute('live_display_active')
def blank_to_theme(self):
"""
Blank to theme
"""
self.run_javascript('Display.blankToTheme();')
def hide_display(self, mode=HideMode.Screen): def hide_display(self, mode=HideMode.Screen):
""" """
Hide the display by making all layers transparent Store the images so they can be replaced when required Hide the display by making all layers transparent Store the images so they can be replaced when required
@ -450,9 +446,11 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties):
if mode == HideMode.Screen: if mode == HideMode.Screen:
self.setVisible(False) self.setVisible(False)
elif mode == HideMode.Blank: elif mode == HideMode.Blank:
self.run_javascript('Display.blankToBlack();') self.run_javascript('Display.toBlack();')
elif mode == HideMode.Theme:
self.run_javascript('Display.toTheme();')
else: else:
self.run_javascript('Display.blankToTheme();') self.run_javascript('Display.toTransparent();')
if mode != HideMode.Screen: if mode != HideMode.Screen:
if self.isHidden(): if self.isHidden():
self.setVisible(True) self.setVisible(True)

View File

@ -41,6 +41,7 @@ class HideMode(object):
Blank = 1 Blank = 1
Theme = 2 Theme = 2
Screen = 3 Screen = 3
Transparent = 4
class DisplayControllerType(object): class DisplayControllerType(object):

View File

@ -40,7 +40,7 @@ from openlp.core.common.path import path_to_str
from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.lib.serviceitem import ItemCapabilities from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import DisplayControllerType from openlp.core.ui import DisplayControllerType, HideMode
from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_devicestream_path, \ from openlp.core.ui.media import MediaState, ItemMediaInfo, MediaType, parse_optical_path, parse_devicestream_path, \
VIDEO_EXT, AUDIO_EXT VIDEO_EXT, AUDIO_EXT
from openlp.core.ui.media.remote import register_views from openlp.core.ui.media.remote import register_views
@ -454,7 +454,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.is_playing = True controller.media_info.is_playing = True
if not controller.media_info.is_background: if not controller.media_info.is_background:
display = self._define_display(controller) display = self._define_display(controller)
display.setVisible(False) display.hide_display(HideMode.Transparent)
controller._set_theme(controller.service_item)
display.load_verses([{"verse": "v1", "text": "", "footer": " "}])
return True return True
def tick(self, controller): def tick(self, controller):
@ -466,9 +468,14 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
start_again = False start_again = False
if controller.media_info.is_playing and controller.media_info.length > 0: if controller.media_info.is_playing and controller.media_info.length > 0:
if controller.media_info.timer > controller.media_info.length: if controller.media_info.timer > controller.media_info.length:
self.media_stop(controller)
if controller.media_info.is_looping_playback: if controller.media_info.is_looping_playback:
start_again = True start_again = True
else:
self.media_stop(controller)
elif controller.media_info.timer > controller.media_info.length - TICK_TIME * 4:
if not controller.media_info.is_looping_playback:
display = self._define_display(controller)
display.show_display()
controller.media_info.timer += TICK_TIME controller.media_info.timer += TICK_TIME
seconds = controller.media_info.timer // 1000 seconds = controller.media_info.timer // 1000
minutes = seconds // 60 minutes = seconds // 60
@ -479,7 +486,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.position_label.setText(' %02d:%02d / %02d:%02d' % controller.position_label.setText(' %02d:%02d / %02d:%02d' %
(minutes, seconds, total_minutes, total_seconds)) (minutes, seconds, total_minutes, total_seconds))
if start_again: if start_again:
self.media_play(controller, True) controller.seek_slider.setSliderPosition(0)
self.media_play(controller, False)
def media_pause_msg(self, msg): def media_pause_msg(self, msg):
""" """
@ -560,6 +568,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.is_playing = False controller.media_info.is_playing = False
controller.media_info.timer = 1000 controller.media_info.timer = 1000
controller.media_timer = 0 controller.media_timer = 0
display = self._define_display(controller)
display.show_display()
def media_volume_msg(self, msg): def media_volume_msg(self, msg):
""" """
@ -615,6 +625,8 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
""" """
self.set_controls_visible(controller, False) self.set_controls_visible(controller, False)
if controller.controller_type in self.current_media_players: if controller.controller_type in self.current_media_players:
display = self._define_display(controller)
display.show_display()
self.current_media_players[controller.controller_type].reset(controller) self.current_media_players[controller.controller_type].reset(controller)
self.current_media_players[controller.controller_type].set_visible(controller, False) self.current_media_players[controller.controller_type].set_visible(controller, False)
del self.current_media_players[controller.controller_type] del self.current_media_players[controller.controller_type]

View File

@ -855,7 +855,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.preview_display.set_theme(theme_data, service_item_type=service_item.service_item_type) self.preview_display.set_theme(theme_data, service_item_type=service_item.service_item_type)
# Set theme for displays # Set theme for displays
for display in self.displays: for display in self.displays:
display.set_theme(service_item.get_theme_data(), service_item_type=service_item.service_item_type) display.set_theme(theme_data, service_item_type=service_item.service_item_type)
def _process_item(self, service_item, slide_no): def _process_item(self, service_item, slide_no):
""" """
@ -1168,20 +1168,9 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
Registry().execute('{text}_slide'.format(text=self.service_item.name.lower()), Registry().execute('{text}_slide'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live, row]) [self.service_item, self.is_live, row])
else: else:
# to_display = self.service_item.get_rendered_frame(row) for display in self.displays:
if self.service_item.is_text(): display.go_to_slide(row)
for display in self.displays: if not self.service_item.is_text():
display.go_to_slide(row)
# self.display.text(to_display, row != old_selected_row)
else:
if start:
for display in self.displays:
display.load_images(self.service_item.slides)
# self.display.build_html(self.service_item, to_display)
else:
for display in self.displays:
display.go_to_slide(row)
# self.display.image(to_display)
# reset the store used to display first image # reset the store used to display first image
self.service_item.bg_image_bytes = None self.service_item.bg_image_bytes = None
self.selected_row = row self.selected_row = row

View File

@ -69,6 +69,9 @@ class ThemesTab(SettingsTab):
self.wrap_footer_check_box = QtWidgets.QCheckBox(self.universal_group_box) self.wrap_footer_check_box = QtWidgets.QCheckBox(self.universal_group_box)
self.wrap_footer_check_box.setObjectName('wrap_footer_check_box') self.wrap_footer_check_box.setObjectName('wrap_footer_check_box')
self.universal_group_box_layout.addWidget(self.wrap_footer_check_box) self.universal_group_box_layout.addWidget(self.wrap_footer_check_box)
self.item_transitions_check_box = QtWidgets.QCheckBox(self.universal_group_box)
self.item_transitions_check_box.setObjectName('item_transitions_check_box')
self.universal_group_box_layout.addWidget(self.item_transitions_check_box)
self.left_layout.addWidget(self.universal_group_box) self.left_layout.addWidget(self.universal_group_box)
self.left_layout.addStretch() self.left_layout.addStretch()
self.level_group_box = QtWidgets.QGroupBox(self.right_column) self.level_group_box = QtWidgets.QGroupBox(self.right_column)
@ -115,6 +118,7 @@ class ThemesTab(SettingsTab):
self.global_group_box.setTitle(translate('OpenLP.ThemesTab', 'Global Theme')) self.global_group_box.setTitle(translate('OpenLP.ThemesTab', 'Global Theme'))
self.universal_group_box.setTitle(translate('OpenLP.ThemesTab', 'Universal Settings')) self.universal_group_box.setTitle(translate('OpenLP.ThemesTab', 'Universal Settings'))
self.wrap_footer_check_box.setText(translate('OpenLP.ThemesTab', '&Wrap footer text')) self.wrap_footer_check_box.setText(translate('OpenLP.ThemesTab', '&Wrap footer text'))
self.item_transitions_check_box.setText(translate('OpenLP.ThemesTab', '&Transition between service items'))
self.level_group_box.setTitle(translate('OpenLP.ThemesTab', 'Theme Level')) self.level_group_box.setTitle(translate('OpenLP.ThemesTab', 'Theme Level'))
self.song_level_radio_button.setText(translate('OpenLP.ThemesTab', 'S&ong Level')) self.song_level_radio_button.setText(translate('OpenLP.ThemesTab', 'S&ong Level'))
self.song_level_label.setText( self.song_level_label.setText(
@ -139,6 +143,7 @@ class ThemesTab(SettingsTab):
self.theme_level = self.settings.value('theme level') self.theme_level = self.settings.value('theme level')
self.global_theme = self.settings.value('global theme') self.global_theme = self.settings.value('global theme')
self.wrap_footer_check_box.setChecked(self.settings.value('wrap footer')) self.wrap_footer_check_box.setChecked(self.settings.value('wrap footer'))
self.item_transitions_check_box.setChecked(self.settings.value('item transitions'))
self.settings.endGroup() self.settings.endGroup()
if self.theme_level == ThemeLevel.Global: if self.theme_level == ThemeLevel.Global:
self.global_level_radio_button.setChecked(True) self.global_level_radio_button.setChecked(True)
@ -155,6 +160,7 @@ class ThemesTab(SettingsTab):
self.settings.setValue('theme level', self.theme_level) self.settings.setValue('theme level', self.theme_level)
self.settings.setValue('global theme', self.global_theme) self.settings.setValue('global theme', self.global_theme)
self.settings.setValue('wrap footer', self.wrap_footer_check_box.isChecked()) self.settings.setValue('wrap footer', self.wrap_footer_check_box.isChecked())
self.settings.setValue('item transitions', self.item_transitions_check_box.isChecked())
self.settings.endGroup() self.settings.endGroup()
self.renderer.set_theme_level(self.theme_level) self.renderer.set_theme_level(self.theme_level)
if self.tab_visited: if self.tab_visited:

View File

@ -32,6 +32,7 @@ from PyQt5 import QtCore
sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock() sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock()
from openlp.core.display.window import DisplayWindow from openlp.core.display.window import DisplayWindow
from openlp.core.ui import HideMode
@patch('PyQt5.QtWidgets.QVBoxLayout') @patch('PyQt5.QtWidgets.QVBoxLayout')
@ -103,6 +104,31 @@ def test_set_scale_initialised(mocked_webengine, mocked_addWidget, mock_settings
display_window.run_javascript.assert_called_once_with('Display.setScale(50.0);') display_window.run_javascript.assert_called_once_with('Display.setScale(50.0);')
@patch('PyQt5.QtWidgets.QVBoxLayout')
@patch('openlp.core.display.webengine.WebEngineView')
def test_after_loaded(mocked_webengine, mocked_addWidget, mock_settings):
"""
Test the correct steps are taken when the webview is loaded
"""
# GIVEN: An initialised display window and settings for item transitions returns true
display_window = DisplayWindow()
display_window.is_display = True
mock_settings.value.return_value = True
display_window.scale = 2
display_window._is_initialised = True
display_window.run_javascript = MagicMock()
display_window.set_scale = MagicMock()
display_window.set_startup_screen = MagicMock()
# WHEN: after_loaded is run
display_window.after_loaded()
# THEN: The following functions should have been called
display_window.run_javascript.assert_called_once_with('Display.init(true, true);')
display_window.set_scale.assert_called_once_with(2)
display_window.set_startup_screen.assert_called_once()
@patch('PyQt5.QtWidgets.QVBoxLayout') @patch('PyQt5.QtWidgets.QVBoxLayout')
@patch('openlp.core.display.webengine.WebEngineView') @patch('openlp.core.display.webengine.WebEngineView')
@patch.object(time, 'time') @patch.object(time, 'time')
@ -146,3 +172,75 @@ def test_run_javascript_sync_no_wait(mock_time, mocked_webengine, mocked_addWidg
assert result == 1234 assert result == 1234
webengine_page.runJavaScript.assert_called_once() webengine_page.runJavaScript.assert_called_once()
mock_time.sleep.assert_not_called() mock_time.sleep.assert_not_called()
@patch('PyQt5.QtWidgets.QVBoxLayout')
@patch('openlp.core.display.webengine.WebEngineView')
def test_hide_display_to_screen(mocked_webengine, mocked_addWidget, mock_settings):
"""
Test hide to screen in the hide_display function
"""
# GIVEN:
display_window = DisplayWindow()
display_window.setVisible = MagicMock()
# WHEN: Hide display is run with no mode (should default to Screen)
display_window.hide_display()
# THEN: Should hide the display and set the hide mode
display_window.setVisible.assert_called_once_with(False)
assert display_window.hide_mode == HideMode.Screen
@patch('PyQt5.QtWidgets.QVBoxLayout')
@patch('openlp.core.display.webengine.WebEngineView')
def test_hide_display_to_blank(mocked_webengine, mocked_addWidget, mock_settings):
"""
Test hide to screen in the hide_display function
"""
# GIVEN:
display_window = DisplayWindow()
display_window.run_javascript = MagicMock()
# WHEN: Hide display is run with HideMode.Blank
display_window.hide_display(HideMode.Blank)
# THEN: Should run the correct javascript on the display and set the hide mode
display_window.run_javascript.assert_called_once_with('Display.toBlack();')
assert display_window.hide_mode == HideMode.Blank
@patch('PyQt5.QtWidgets.QVBoxLayout')
@patch('openlp.core.display.webengine.WebEngineView')
def test_hide_display_to_theme(mocked_webengine, mocked_addWidget, mock_settings):
"""
Test hide to screen in the hide_display function
"""
# GIVEN:
display_window = DisplayWindow()
display_window.run_javascript = MagicMock()
# WHEN: Hide display is run with HideMode.Theme
display_window.hide_display(HideMode.Theme)
# THEN: Should run the correct javascript on the display and set the hide mode
display_window.run_javascript.assert_called_once_with('Display.toTheme();')
assert display_window.hide_mode == HideMode.Theme
@patch('PyQt5.QtWidgets.QVBoxLayout')
@patch('openlp.core.display.webengine.WebEngineView')
def test_hide_display_to_transparent(mocked_webengine, mocked_addWidget, mock_settings):
"""
Test hide to screen in the hide_display function
"""
# GIVEN:
display_window = DisplayWindow()
display_window.run_javascript = MagicMock()
# WHEN: Hide display is run with HideMode.Transparent
display_window.hide_display(HideMode.Transparent)
# THEN: Should run the correct javascript on the display and set the hide mode
display_window.run_javascript.assert_called_once_with('Display.toTransparent();')
assert display_window.hide_mode == HideMode.Transparent

View File

@ -324,3 +324,23 @@ def test_setup_display(MockItemMediaInfo, media_env):
assert controller.has_audio is False assert controller.has_audio is False
media_env.media_controller._define_display.assert_called_once_with(controller) media_env.media_controller._define_display.assert_called_once_with(controller)
media_env.media_controller.vlc_player.setup(controller, mocked_display, False) media_env.media_controller.vlc_player.setup(controller, mocked_display, False)
def test_media_play(media_env):
"""
Test that the display/controllers are set up correctly
"""
# GIVEN: A mocked controller where is_background is false
media_env.current_media_players = MagicMock()
Registry().register('settings', MagicMock())
media_env.live_timer = MagicMock()
mocked_controller = MagicMock()
mocked_controller.media_info.is_background = False
# WHEN: media_play is called
result = media_env.media_play(mocked_controller)
# THEN: The web display should become transparent (only tests that the theme is reset here)
# And the function should return true to indicate success
assert result is True
mocked_controller._set_theme.assert_called_once()

View File

@ -43,6 +43,29 @@ def test_initial_slide_controller(registry):
assert slide_controller.is_live is False, 'The base slide controller should not be a live controller' assert slide_controller.is_live is False, 'The base slide controller should not be a live controller'
def test_slide_selected(settings):
"""
Test the slide selected method
"""
# GIVEN: A new SlideController instance on slide 4 of 10, and is not a command
slide_controller = SlideController(None)
slide_controller.update_preview = MagicMock()
slide_controller.slide_selected_lock = MagicMock()
slide_controller.service_item = MagicMock()
slide_controller.service_item.is_command.return_value = False
slide_controller.preview_widget = MagicMock()
slide_controller.preview_widget.slide_count.return_value = 10
slide_controller.preview_widget.current_slide_number.return_value = 4
mocked_display = MagicMock()
slide_controller.displays = [mocked_display]
# WHEN: The slide_selected method is run
slide_controller.slide_selected(True)
# THEN: The display is updated with the slide number
mocked_display.go_to_slide.assert_called_once_with(4)
def test_text_service_item_blank(settings): def test_text_service_item_blank(settings):
""" """
Test that loading a text-based service item into the slide controller sets the correct blank menu Test that loading a text-based service item into the slide controller sets the correct blank menu

View File

@ -76,6 +76,7 @@ describe("The function", function () {
}); });
describe("The Display object", function () { describe("The Display object", function () {
it("should start with a blank _slides object", function () { it("should start with a blank _slides object", function () {
expect(Display._slides).toEqual({}); expect(Display._slides).toEqual({});
}); });
@ -107,6 +108,8 @@ describe("The Display object", function () {
it("should initialise Reveal when init is called", function () { it("should initialise Reveal when init is called", function () {
spyOn(Reveal, "initialize"); spyOn(Reveal, "initialize");
document.body.innerHTML = "";
_createDiv({"id": "global-background"});
Display.init(); Display.init();
expect(Reveal.initialize).toHaveBeenCalled(); expect(Reveal.initialize).toHaveBeenCalled();
}); });
@ -123,16 +126,18 @@ describe("The Display object", function () {
expect(Reveal.slide).toHaveBeenCalledWith(0); expect(Reveal.slide).toHaveBeenCalledWith(0);
}); });
it("should have a setTransition() method", function () { it("should have a setItemTransition() method", function () {
expect(Display.setTransition).toBeDefined(); expect(Display.setItemTransition).toBeDefined();
}); });
it("should have a correctly functioning clearSlides() method", function () { it("should have a correctly functioning clearSlides() method", function () {
expect(Display.clearSlides).toBeDefined(); expect(Display.clearSlides).toBeDefined();
document.body.innerHTML = "";
var slidesDiv = _createDiv({"class": "slides"}); var slidesDiv = _createDiv({"class": "slides"});
slidesDiv.innerHTML = "<section><p></p></section>"; slidesDiv.innerHTML = "<section><p></p></section>";
Display._slidesContainer = slidesDiv;
var footerDiv = _createDiv({"class": "footer"});
Display._footerContainer = footerDiv;
Display.clearSlides(); Display.clearSlides();
expect($(".slides")[0].innerHTML).toEqual(""); expect($(".slides")[0].innerHTML).toEqual("");
@ -167,42 +172,48 @@ describe("Transitions", function () {
Display._theme = null; Display._theme = null;
}); });
it("should have a correctly functioning setTransition() method", function () { it("should have a correctly functioning setItemTransition() method", function () {
spyOn(Reveal, "configure"); spyOn(Reveal, "configure");
Display.setTransition("fade", "slow"); Display.setItemTransition(true);
expect(Reveal.configure).toHaveBeenCalledWith({"transition": "fade", "transitionSpeed": "slow"}); expect(Reveal.configure).toHaveBeenCalledWith({"backgroundTransition": "fade", "transitionSpeed": "default"});
}); });
it("should have enabled transitions when _doTransitions is true and setTheme is run", function () { it("should have enabled transitions when _doTransitions is true and applyTheme is run", function () {
spyOn(Display, "setTransition");
Display._doTransitions = true; Display._doTransitions = true;
var theme = { var theme = {
"display_slide_transition": true, "display_slide_transition": true,
"display_slide_transition_type": TransitionType.Slide, "display_slide_transition_type": TransitionType.Slide,
"display_slide_transition_speed": TransitionSpeed.Fast "display_slide_transition_speed": TransitionSpeed.Fast
} }
Display.setTheme(theme); Display.setTheme(theme);
var slidesDiv = _createDiv({"class": "slides"});
slidesDiv.innerHTML = "<section><section><p></p></section></section>";
Display._slidesContainer = slidesDiv;
expect(Display.setTransition).toHaveBeenCalledWith("slide-horizontal", "fast"); Display.applyTheme(Display._slidesContainer.children[0])
expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition")).toEqual("slide-horizontal");
expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition-speed")).toEqual("fast");
}); });
it("should have not enabled transitions when init() with no transitions and setTheme is run", function () { it("should have not enabled transitions when init() with no transitions and setTheme is run", function () {
spyOn(Display, "setTransition");
Display._doTransitions = false; Display._doTransitions = false;
var theme = { var theme = {
"display_slide_transition": true, "display_slide_transition": true,
"display_slide_transition_type": TransitionType.Slide, "display_slide_transition_type": TransitionType.Slide,
"display_slide_transition_speed": TransitionSpeed.Fast, "display_slide_transition_speed": TransitionSpeed.Fast,
} }
Display.setTheme(theme); Display.setTheme(theme);
var slidesDiv = _createDiv({"class": "slides"});
slidesDiv.innerHTML = "<section><section><p></p></section></section>";
Display._slidesContainer = slidesDiv;
expect(Display.setTransition).toHaveBeenCalledWith("none", "default"); Display.applyTheme(Display._slidesContainer.children[0])
expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition")).toEqual("none");
}); });
it("should have enabled transitions in the correct direction", function () { it("should have enabled transitions in the correct direction", function () {
spyOn(Display, "setTransition");
Display._doTransitions = true; Display._doTransitions = true;
var theme = { var theme = {
"display_slide_transition": true, "display_slide_transition": true,
@ -211,10 +222,15 @@ describe("Transitions", function () {
"display_slide_transition_direction": TransitionDirection.Vertical, "display_slide_transition_direction": TransitionDirection.Vertical,
"display_slide_transition_reverse": true, "display_slide_transition_reverse": true,
} }
Display.setTheme(theme); Display.setTheme(theme);
var slidesDiv = _createDiv({"class": "slides"});
slidesDiv.innerHTML = "<section><section><p></p></section></section>";
Display._slidesContainer = slidesDiv;
expect(Display.setTransition).toHaveBeenCalledWith("convex-vertical-reverse", "slow"); Display.applyTheme(Display._slidesContainer.children[0])
expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition")).toEqual("convex-vertical-reverse");
expect(Display._slidesContainer.children[0].children[0].getAttribute("data-transition-speed")).toEqual("slow");
}); });
}); });
@ -504,63 +520,49 @@ describe("Display.alertAnimationEndEvent", function () {
}); });
}); });
describe("Display.addTextSlide", function () { describe("Display.setTextSlide", function () {
beforeEach(function() { beforeEach(function() {
document.body.innerHTML = ""; document.body.innerHTML = "";
_createDiv({"class": "slides"}); var slides_container = _createDiv({"class": "slides"});
_createDiv({"class": "footer"}); var footer_container = _createDiv({"class": "footer"});
Display._slidesContainer = slides_container;
Display._footerContainer = footer_container;
Display._slides = {}; Display._slides = {};
}); });
it("should add a new slide", function () { it("should add a new slide", function () {
var verse = "v1", var text = "Amazing grace,\nhow sweet the sound";
text = "Amazing grace,\nhow sweet the sound",
footer = "Public Domain";
spyOn(Display, "reinit"); spyOn(Display, "reinit");
Display.addTextSlide(verse, text, footer); Display.setTextSlide(text);
expect(Display._slides[verse]).toEqual(0); expect(Display._slides["test-slide"]).toEqual(0);
expect($(".slides > section > section").length).toEqual(1); expect($(".slides > section > section").length).toEqual(1);
expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text)); expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text));
expect(Display.reinit).toHaveBeenCalled(); expect(Display.reinit).toHaveBeenCalled();
}); });
it("should add a new slide without calling reinit()", function () {
var verse = "v1",
text = "Amazing grace,\nhow sweet the sound",
footer = "Public Domain";
spyOn(Display, "reinit");
Display.addTextSlide(verse, text, footer, false);
expect(Display._slides[verse]).toEqual(0);
expect($(".slides > section > section").length).toEqual(1);
expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text));
expect(Display.reinit).not.toHaveBeenCalled();
});
it("should update an existing slide", function () { it("should update an existing slide", function () {
var verse = "v1", var text = "That saved a wretch\nlike me";
text = "Amazing grace, how sweet the sound\nThat saved a wretch like me",
footer = "Public Domain";
Display.addTextSlide(verse, "Amazing grace,\nhow sweet the sound", footer, false);
spyOn(Display, "reinit"); spyOn(Display, "reinit");
Display.setTextSlide("Amazing grace,\nhow sweet the sound");
Display.addTextSlide(verse, text, footer, true); Display.setTextSlide(text);
expect(Display._slides[verse]).toEqual(0); expect(Display._slides["test-slide"]).toEqual(0);
expect($(".slides > section > section").length).toEqual(1); expect($(".slides > section > section").length).toEqual(1);
expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text)); expect($(".slides > section > section")[0].innerHTML).toEqual(_prepareText(text));
expect(Display.reinit).toHaveBeenCalledTimes(0); expect(Display.reinit).toHaveBeenCalledTimes(1); // only called once for the first setTextSlide
}); });
}); });
describe("Display.setTextSlides", function () { describe("Display.setTextSlides", function () {
beforeEach(function() { beforeEach(function() {
document.body.innerHTML = ""; document.body.innerHTML = "";
_createDiv({"class": "slides"}); var slides_container = _createDiv({"class": "slides"});
_createDiv({"class": "footer"}); var footer_container = _createDiv({"class": "footer"});
Display._slidesContainer = slides_container;
Display._footerContainer = footer_container;
_createDiv({"id": "global-background"}); _createDiv({"id": "global-background"});
Display._slides = {}; Display._slides = {};
}); });
@ -581,15 +583,16 @@ describe("Display.setTextSlides", function () {
} }
]; ];
spyOn(Display, "clearSlides"); spyOn(Display, "clearSlides");
spyOn(Display, "reinit"); spyOn(Reveal, "sync");
spyOn(Reveal, "slide");
Display.setTextSlides(slides); Display.setTextSlides(slides);
expect(Display.clearSlides).toHaveBeenCalledTimes(1); expect(Display.clearSlides).toHaveBeenCalledTimes(0);
expect(Display._slides["v1"]).toEqual(0); expect(Display._slides["v1"]).toEqual(0);
expect(Display._slides["v2"]).toEqual(1); expect(Display._slides["v2"]).toEqual(1);
expect($(".slides > section > section").length).toEqual(2); expect($(".slides > section > section").length).toEqual(2);
expect(Display.reinit).toHaveBeenCalledTimes(1); expect(Reveal.sync).toHaveBeenCalledTimes(1);
}); });
it("should correctly set outline width", function () { it("should correctly set outline width", function () {
@ -607,7 +610,7 @@ describe("Display.setTextSlides", function () {
'font_main_outline_size': 42, 'font_main_outline_size': 42,
'font_main_outline_color': 'red' 'font_main_outline_color': 'red'
}; };
spyOn(Display, "reinit"); spyOn(Reveal, "sync");
spyOn(Reveal, "slide"); spyOn(Reveal, "slide");
Display.setTheme(theme); Display.setTheme(theme);
@ -633,7 +636,7 @@ describe("Display.setTextSlides", function () {
'display_horizontal_align': 3, 'display_horizontal_align': 3,
'display_vertical_align': 1 'display_vertical_align': 1
}; };
spyOn(Display, "reinit"); spyOn(Reveal, "sync");
spyOn(Reveal, "slide"); spyOn(Reveal, "slide");
Display.setTheme(theme); Display.setTheme(theme);
@ -659,7 +662,7 @@ describe("Display.setTextSlides", function () {
'font_main_shadow_color': "#000", 'font_main_shadow_color': "#000",
'font_main_shadow_size': 5 'font_main_shadow_size': 5
}; };
spyOn(Display, "reinit"); spyOn(Reveal, "sync");
spyOn(Reveal, "slide"); spyOn(Reveal, "slide");
Display.setTheme(theme); Display.setTheme(theme);
@ -684,7 +687,7 @@ describe("Display.setTextSlides", function () {
'font_main_shadow_color': "#000", 'font_main_shadow_color': "#000",
'font_main_shadow_size': 5 'font_main_shadow_size': 5
}; };
spyOn(Display, "reinit"); spyOn(Reveal, "sync");
spyOn(Reveal, "slide"); spyOn(Reveal, "slide");
Display.setTheme(theme); Display.setTheme(theme);
@ -710,7 +713,7 @@ describe("Display.setTextSlides", function () {
'font_main_width': 1230, 'font_main_width': 1230,
'font_main_height': 4560 'font_main_height': 4560
}; };
spyOn(Display, "reinit"); spyOn(Reveal, "sync");
spyOn(Reveal, "slide"); spyOn(Reveal, "slide");
Display.setTheme(theme); Display.setTheme(theme);
@ -727,20 +730,21 @@ describe("Display.setTextSlides", function () {
describe("Display.setImageSlides", function () { describe("Display.setImageSlides", function () {
beforeEach(function() { beforeEach(function() {
document.body.innerHTML = ""; document.body.innerHTML = "";
_createDiv({"class": "slides"}); var slides_container = _createDiv({"class": "slides"});
_createDiv({"class": "footer"}); var footer_container = _createDiv({"class": "footer"});
Display._slidesContainer = slides_container;
Display._footerContainer = footer_container;
_createDiv({"id": "global-background"}); _createDiv({"id": "global-background"});
Display._slides = {}; Display._slides = {};
}); });
it("should add a list of images", function () { it("should add a list of images", function () {
var slides = [{"path": "file:///openlp1.jpg"}, {"path": "file:///openlp2.jpg"}]; var slides = [{"path": "file:///openlp1.jpg"}, {"path": "file:///openlp2.jpg"}];
spyOn(Display, "clearSlides"); spyOn(Reveal, "sync");
spyOn(Display, "reinit"); spyOn(Reveal, "slide");
Display.setImageSlides(slides); Display.setImageSlides(slides);
expect(Display.clearSlides).toHaveBeenCalledTimes(1);
expect(Display._slides["0"]).toEqual(0); expect(Display._slides["0"]).toEqual(0);
expect(Display._slides["1"]).toEqual(1); expect(Display._slides["1"]).toEqual(1);
expect($(".slides > section > section").length).toEqual(2); expect($(".slides > section > section").length).toEqual(2);
@ -749,30 +753,30 @@ describe("Display.setImageSlides", function () {
expect($(".slides > section > section > img")[0].getAttribute("style")).toEqual("max-width: 100%; max-height: 100%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);") expect($(".slides > section > section > img")[0].getAttribute("style")).toEqual("max-width: 100%; max-height: 100%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);")
expect($(".slides > section > section > img")[1].getAttribute("src")).toEqual("file:///openlp2.jpg") expect($(".slides > section > section > img")[1].getAttribute("src")).toEqual("file:///openlp2.jpg")
expect($(".slides > section > section > img")[1].getAttribute("style")).toEqual("max-width: 100%; max-height: 100%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);") expect($(".slides > section > section > img")[1].getAttribute("style")).toEqual("max-width: 100%; max-height: 100%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);")
expect(Display.reinit).toHaveBeenCalledTimes(1); expect(Reveal.sync).toHaveBeenCalledTimes(1);
}); });
}); });
describe("Display.setVideo", function () { describe("Display.setVideo", function () {
beforeEach(function() { beforeEach(function() {
document.body.innerHTML = ""; document.body.innerHTML = "";
_createDiv({"class": "slides"}); var slides_container = _createDiv({"class": "slides"});
Display._slidesContainer = slides_container;
_createDiv({"id": "global-background"}); _createDiv({"id": "global-background"});
Display._slides = {}; Display._slides = {};
}); });
it("should add a video to the page", function () { it("should add a video to the page", function () {
var video = {"path": "file:///video.mp4"}; var video = {"path": "file:///video.mp4"};
spyOn(Display, "clearSlides"); spyOn(Reveal, "sync");
spyOn(Display, "reinit"); spyOn(Reveal, "slide");
Display.setVideo(video); Display.setVideo(video);
expect(Display.clearSlides).toHaveBeenCalledTimes(1);
expect($(".slides > section").length).toEqual(1); expect($(".slides > section").length).toEqual(1);
expect($(".slides > section > video").length).toEqual(1); expect($(".slides > section > video").length).toEqual(1);
expect($(".slides > section > video")[0].src).toEqual("file:///video.mp4") expect($(".slides > section > video")[0].src).toEqual("file:///video.mp4")
expect(Display.reinit).toHaveBeenCalledTimes(1); expect(Reveal.sync).toHaveBeenCalledTimes(1);
}); });
}); });