Browse Source

Merge branch 'bg-fixes'

master
Eduard Kuzmenko 3 years ago
parent
commit
82488dda32
  1. 45
      src/components/chat/chat.ts
  2. 146
      src/components/chat/patternRenderer.ts
  3. 2
      src/helpers/dom/renderImageFromUrl.ts
  4. 15
      src/lib/appManagers/appImManager.ts
  5. 9
      src/scss/partials/_chat.scss

45
src/components/chat/chat.ts

@ -85,6 +85,7 @@ export default class Chat extends EventListenerBase<{
public patternCanvas: HTMLCanvasElement; public patternCanvas: HTMLCanvasElement;
public backgroundTempId: number; public backgroundTempId: number;
public setBackgroundPromise: Promise<void>; public setBackgroundPromise: Promise<void>;
// public renderDarkPattern: () => Promise<void>;
constructor( constructor(
public appImManager: AppImManager, public appImManager: AppImManager,
@ -159,6 +160,7 @@ export default class Chat extends EventListenerBase<{
this.patternRenderer = this.patternRenderer =
this.gradientCanvas = this.gradientCanvas =
this.patternCanvas = this.patternCanvas =
// this.renderDarkPattern =
undefined; undefined;
const intensity = theme.background.intensity && theme.background.intensity / 100; const intensity = theme.background.intensity && theme.background.intensity / 100;
@ -179,11 +181,28 @@ export default class Chat extends EventListenerBase<{
patternRenderer = this.patternRenderer = ChatBackgroundPatternRenderer.getInstance({ patternRenderer = this.patternRenderer = ChatBackgroundPatternRenderer.getInstance({
url, url,
width: rect.width, width: rect.width,
height: rect.height height: rect.height,
mask: isDarkPattern
}); });
patternCanvas = this.patternCanvas = patternRenderer.createCanvas(); patternCanvas = this.patternCanvas = patternRenderer.createCanvas();
patternCanvas.classList.add('chat-background-item-canvas', 'chat-background-item-pattern-canvas'); patternCanvas.classList.add('chat-background-item-canvas', 'chat-background-item-pattern-canvas');
if(isDarkPattern) {
item.classList.add('is-dark');
}
// if(isDarkPattern) {
// this.renderDarkPattern = () => {
// return patternRenderer.exportCanvasPatternToImage(patternCanvas).then(url => {
// if(this.backgroundTempId !== tempId) {
// return;
// }
// gradientCanvas.style.webkitMaskImage = `url(${url})`;
// });
// };
// }
} else if(theme.background.slug) { } else if(theme.background.slug) {
item.classList.add('is-image'); item.classList.add('is-image');
} }
@ -236,7 +255,11 @@ export default class Chat extends EventListenerBase<{
return; return;
} }
const append = [gradientCanvas, isDarkPattern ? undefined : patternCanvas].filter(Boolean); const append = [
gradientCanvas,
// isDarkPattern && this.renderDarkPattern ? undefined : patternCanvas
patternCanvas
].filter(Boolean);
if(append.length) { if(append.length) {
item.append(...append); item.append(...append);
} }
@ -261,18 +284,16 @@ export default class Chat extends EventListenerBase<{
if(patternRenderer) { if(patternRenderer) {
const renderPatternPromise = patternRenderer.renderToCanvas(patternCanvas); const renderPatternPromise = patternRenderer.renderToCanvas(patternCanvas);
renderPatternPromise.then(() => { renderPatternPromise.then(() => {
if(this.backgroundTempId !== tempId) {
return;
}
let promise: Promise<any>; let promise: Promise<any>;
if(isDarkPattern) { // if(isDarkPattern && this.renderDarkPattern) {
promise = patternRenderer.exportCanvasPatternToImage(patternCanvas).then(url => { // promise = this.renderDarkPattern();
if(this.backgroundTempId !== tempId) { // } else {
return;
}
gradientCanvas.style.webkitMaskImage = `url(${url})`;
});
} else {
promise = Promise.resolve(); promise = Promise.resolve();
} // }
promise.then(cb); promise.then(cb);
}); });

146
src/components/chat/patternRenderer.ts

@ -4,27 +4,29 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import { IS_SAFARI } from "../../environment/userAgent";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice"; import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
import deepEqual from "../../helpers/object/deepEqual"; import deepEqual from "../../helpers/object/deepEqual";
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes";
type ChatBackgroundPatternRendererInitOptions = { type ChatBackgroundPatternRendererInitOptions = {
url: string, url: string,
width: number, width: number,
height: number height: number,
mask?: boolean
}; };
export default class ChatBackgroundPatternRenderer { export default class ChatBackgroundPatternRenderer {
private static INSTANCES: ChatBackgroundPatternRenderer[] = []; private static INSTANCES: ChatBackgroundPatternRenderer[] = [];
private pattern: CanvasPattern; // private pattern: CanvasPattern;
private objectUrl: string; private objectUrl: string;
private options: ChatBackgroundPatternRendererInitOptions; private options: ChatBackgroundPatternRendererInitOptions;
private canvases: Set<HTMLCanvasElement>; private canvases: Set<HTMLCanvasElement>;
private createCanvasPatternPromise: Promise<void>; // private createCanvasPatternPromise: Promise<CanvasPattern>;
private exportCanvasPatternToImagePromise: Promise<string>; // private exportCanvasPatternToImagePromise: Promise<string>;
// private img: HTMLImageElement; private renderImageFromUrlPromise: Promise<HTMLImageElement>;
private img: HTMLImageElement;
constructor() { constructor() {
this.canvases = new Set(); this.canvases = new Set();
@ -45,36 +47,54 @@ export default class ChatBackgroundPatternRenderer {
} }
public init(options: ChatBackgroundPatternRendererInitOptions) { public init(options: ChatBackgroundPatternRendererInitOptions) {
// if(this.options) {
// if(this.options.width !== options.width || this.options.height !== options.height) {
// this.createCanvasPatternPromise =
// this.pattern =
// this.exportCanvasPatternToImagePromise =
// undefined;
// }
// }
this.options = options; this.options = options;
} }
public renderToCanvas(canvas: HTMLCanvasElement) { public renderToCanvas(canvas: HTMLCanvasElement) {
return this.createCanvasPattern(canvas).then(() => { // return this.createCanvasPattern(canvas).then(() => {
// return this.fillCanvas(canvas);
// });
return this.renderImageFromUrl(this.options.url).then(() => {
return this.fillCanvas(canvas); return this.fillCanvas(canvas);
}); });
} }
private createCanvasPattern(canvas: HTMLCanvasElement) { private renderImageFromUrl(url: string) {
if(this.renderImageFromUrlPromise) return this.renderImageFromUrlPromise;
const img = this.img = document.createElement('img');
img.crossOrigin = 'anonymous';
return this.renderImageFromUrlPromise = renderImageFromUrlPromise(img, url, false).then(() => img);
}
/* private createCanvasPattern(canvas: HTMLCanvasElement) {
if(this.createCanvasPatternPromise) return this.createCanvasPatternPromise; if(this.createCanvasPatternPromise) return this.createCanvasPatternPromise;
return this.createCanvasPatternPromise = new Promise((resolve) => { return this.createCanvasPatternPromise = this.renderImageFromUrl(this.options.url).then((img) => {
const img = document.createElement('img'); let createPatternFrom: HTMLImageElement | HTMLCanvasElement;
img.crossOrigin = 'anonymous'; if(IS_SAFARI) {
renderImageFromUrlPromise(img, this.options.url, false).then(() => { const canvas = createPatternFrom = document.createElement('canvas');
let createPatternFrom: HTMLImageElement | HTMLCanvasElement; canvas.width = img.naturalWidth;
if(IS_SAFARI) { canvas.height = img.naturalHeight;
const canvas = createPatternFrom = document.createElement('canvas'); const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth; ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.height = img.naturalHeight; } else {
const ctx = canvas.getContext('2d'); createPatternFrom = img;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); }
} else {
createPatternFrom = img; const perf = performance.now();
} this.pattern = canvas.getContext('2d').createPattern(createPatternFrom, 'repeat-x');
console.warn('creating pattern time:', performance.now() - perf);
// this.img = img;
this.pattern = canvas.getContext('2d').createPattern(createPatternFrom, 'repeat-x'); return this.pattern;
resolve();
});
}); });
} }
@ -86,7 +106,7 @@ export default class ChatBackgroundPatternRenderer {
resolve(newUrl); resolve(newUrl);
}, 'image/png'); }, 'image/png');
}); });
} } */
public cleanup(canvas: HTMLCanvasElement) { public cleanup(canvas: HTMLCanvasElement) {
this.canvases.delete(canvas); this.canvases.delete(canvas);
@ -102,14 +122,42 @@ export default class ChatBackgroundPatternRenderer {
public fillCanvas(canvas: HTMLCanvasElement) { public fillCanvas(canvas: HTMLCanvasElement) {
const context = canvas.getContext('2d'); const context = canvas.getContext('2d');
context.fillStyle = this.pattern; if(context.fillStyle instanceof CanvasPattern) {
context.fillRect(0, 0, canvas.width, canvas.height); context.clearRect(0, 0, canvas.width, canvas.height);
// context.drawImage(this.img, 0, 0, canvas.width, canvas.height); }
// const perf = performance.now();
const img = this.img;
let imageWidth = img.width, imageHeight = img.height;
// if(imageHeight < canvas.height) {
const ratio = canvas.height / imageHeight;
imageWidth *= ratio;
imageHeight = canvas.height;
// }
if(this.options.mask) {
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = 'destination-out';
} else {
context.globalCompositeOperation = 'source-over';
}
for(let x = 0; x < canvas.width; x += imageWidth) {
for(let y = 0; y < canvas.height; y += imageHeight) {
context.drawImage(img, x, y, imageWidth, imageHeight);
}
}
// context.fillStyle = this.pattern;
// context.fillRect(0, 0, canvas.width, canvas.height);
// console.warn('fill canvas time', performance.now() - perf);
} }
public setCanvasDimensions(canvas: HTMLCanvasElement) { public setCanvasDimensions(canvas: HTMLCanvasElement) {
canvas.width = this.options.width * window.devicePixelRatio; const devicePixelRatio = Math.min(2, window.devicePixelRatio);
canvas.height = this.options.height * window.devicePixelRatio * 1.5; canvas.width = this.options.width * devicePixelRatio;
canvas.height = this.options.height * devicePixelRatio * (mediaSizes.activeScreen === ScreenSize.large ? 1.5 : 1);
} }
public createCanvas() { public createCanvas() {
@ -118,4 +166,34 @@ export default class ChatBackgroundPatternRenderer {
this.setCanvasDimensions(canvas); this.setCanvasDimensions(canvas);
return canvas; return canvas;
} }
public resize(width: number, height: number) {
this.init({
...this.options,
width,
height
});
const promises: Promise<any>[] = [];
for(const canvas of this.canvases) {
this.setCanvasDimensions(canvas);
promises.push(this.renderToCanvas(canvas));
}
return Promise.all(promises);
}
public static resizeInstances(width: number, height: number) {
return Promise.all(this.INSTANCES.map(instance => instance.resize(width, height)));
}
/* public setResizeMode(resizing: boolean) {
const canvases = Array.from(this.canvases);
const canvas = canvases[canvases.length - 1];
canvas.style.display = resizing ? 'none' : '';
const img = this.img;
img.style.display = resizing ? '' : 'none';
return {img, canvas};
} */
} }

2
src/helpers/dom/renderImageFromUrl.ts

@ -61,7 +61,7 @@ export default function renderImageFromUrl(
} }
export function renderImageFromUrlPromise(elem: Parameters<typeof renderImageFromUrl>[0], url: string, useCache?: boolean) { export function renderImageFromUrlPromise(elem: Parameters<typeof renderImageFromUrl>[0], url: string, useCache?: boolean) {
return new Promise((resolve) => { return new Promise<Event>((resolve) => {
renderImageFromUrl(elem, url, resolve, useCache); renderImageFromUrl(elem, url, resolve, useCache);
}); });
} }

15
src/lib/appManagers/appImManager.ts

@ -89,6 +89,7 @@ import getObjectKeysAndSort from '../../helpers/object/getObjectKeysAndSort';
import type GroupCallInstance from '../calls/groupCallInstance'; import type GroupCallInstance from '../calls/groupCallInstance';
import type CallInstance from '../calls/callInstance'; import type CallInstance from '../calls/callInstance';
import numberThousandSplitter from '../../helpers/number/numberThousandSplitter'; import numberThousandSplitter from '../../helpers/number/numberThousandSplitter';
import ChatBackgroundPatternRenderer from '../../components/chat/patternRenderer';
//console.log('appImManager included33!'); //console.log('appImManager included33!');
@ -234,11 +235,17 @@ export class AppImManager {
this.appendEmojiAnimationContainer(to); this.appendEmojiAnimationContainer(to);
}); });
const resizeBackgroundDebounced = debounce(() => {
this.setBackground(this.lastBackgroundUrl, false);
}, 200, false, true);
mediaSizes.addEventListener('resize', () => { mediaSizes.addEventListener('resize', () => {
resizeBackgroundDebounced(); // const perf = performance.now();
const rect = this.chatsContainer.getBoundingClientRect();
ChatBackgroundPatternRenderer.resizeInstances(rect.width, rect.height).then(() => {
// this.log.warn('resize bg time:', performance.now() - perf);
// for(const chat of this.chats) {
// if(chat.renderDarkPattern) {
// chat.renderDarkPattern();
// }
// }
});
}); });
rootScope.addEventListener('history_focus', (e) => { rootScope.addEventListener('history_focus', (e) => {

9
src/scss/partials/_chat.scss

@ -691,8 +691,11 @@ $background-transition-total-time: #{$input-transition-time - $background-transi
align-items: center; align-items: center;
justify-content: center; justify-content: center;
// mix-blend-mode: overlay; // mix-blend-mode: overlay;
height: 150%;
top: -25%; @include respond-to(medium-screens) {
height: 150%;
top: -25%;
}
} }
@include animation-level(2) { @include animation-level(2) {
@ -727,7 +730,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi
width: 100%; width: 100%;
} }
&-pattern-canvas { &:not(.is-dark) &-pattern-canvas {
mix-blend-mode: overlay; mix-blend-mode: overlay;
// height: 100%; // height: 100%;
} }

Loading…
Cancel
Save