diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 7c2ac709..914ea7d4 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -85,6 +85,7 @@ export default class Chat extends EventListenerBase<{ public patternCanvas: HTMLCanvasElement; public backgroundTempId: number; public setBackgroundPromise: Promise; + // public renderDarkPattern: () => Promise; constructor( public appImManager: AppImManager, @@ -159,6 +160,7 @@ export default class Chat extends EventListenerBase<{ this.patternRenderer = this.gradientCanvas = this.patternCanvas = + // this.renderDarkPattern = undefined; const intensity = theme.background.intensity && theme.background.intensity / 100; @@ -179,11 +181,28 @@ export default class Chat extends EventListenerBase<{ patternRenderer = this.patternRenderer = ChatBackgroundPatternRenderer.getInstance({ url, width: rect.width, - height: rect.height + height: rect.height, + mask: isDarkPattern }); patternCanvas = this.patternCanvas = patternRenderer.createCanvas(); 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) { item.classList.add('is-image'); } @@ -236,7 +255,11 @@ export default class Chat extends EventListenerBase<{ return; } - const append = [gradientCanvas, isDarkPattern ? undefined : patternCanvas].filter(Boolean); + const append = [ + gradientCanvas, + // isDarkPattern && this.renderDarkPattern ? undefined : patternCanvas + patternCanvas + ].filter(Boolean); if(append.length) { item.append(...append); } @@ -261,18 +284,16 @@ export default class Chat extends EventListenerBase<{ if(patternRenderer) { const renderPatternPromise = patternRenderer.renderToCanvas(patternCanvas); renderPatternPromise.then(() => { + if(this.backgroundTempId !== tempId) { + return; + } + let promise: Promise; - if(isDarkPattern) { - promise = patternRenderer.exportCanvasPatternToImage(patternCanvas).then(url => { - if(this.backgroundTempId !== tempId) { - return; - } - - gradientCanvas.style.webkitMaskImage = `url(${url})`; - }); - } else { + // if(isDarkPattern && this.renderDarkPattern) { + // promise = this.renderDarkPattern(); + // } else { promise = Promise.resolve(); - } + // } promise.then(cb); }); diff --git a/src/components/chat/patternRenderer.ts b/src/components/chat/patternRenderer.ts index 8a81e896..44e64960 100644 --- a/src/components/chat/patternRenderer.ts +++ b/src/components/chat/patternRenderer.ts @@ -4,27 +4,29 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ -import { IS_SAFARI } from "../../environment/userAgent"; import indexOfAndSplice from "../../helpers/array/indexOfAndSplice"; -import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import deepEqual from "../../helpers/object/deepEqual"; +import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; +import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes"; type ChatBackgroundPatternRendererInitOptions = { url: string, width: number, - height: number + height: number, + mask?: boolean }; export default class ChatBackgroundPatternRenderer { private static INSTANCES: ChatBackgroundPatternRenderer[] = []; - private pattern: CanvasPattern; + // private pattern: CanvasPattern; private objectUrl: string; private options: ChatBackgroundPatternRendererInitOptions; private canvases: Set; - private createCanvasPatternPromise: Promise; - private exportCanvasPatternToImagePromise: Promise; - // private img: HTMLImageElement; + // private createCanvasPatternPromise: Promise; + // private exportCanvasPatternToImagePromise: Promise; + private renderImageFromUrlPromise: Promise; + private img: HTMLImageElement; constructor() { this.canvases = new Set(); @@ -45,36 +47,54 @@ export default class ChatBackgroundPatternRenderer { } 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; } 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); }); } - 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; - return this.createCanvasPatternPromise = new Promise((resolve) => { - const img = document.createElement('img'); - img.crossOrigin = 'anonymous'; - renderImageFromUrlPromise(img, this.options.url, false).then(() => { - let createPatternFrom: HTMLImageElement | HTMLCanvasElement; - if(IS_SAFARI) { - const canvas = createPatternFrom = document.createElement('canvas'); - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0, canvas.width, canvas.height); - } else { - createPatternFrom = img; - } - - // this.img = img; - this.pattern = canvas.getContext('2d').createPattern(createPatternFrom, 'repeat-x'); - resolve(); - }); + return this.createCanvasPatternPromise = this.renderImageFromUrl(this.options.url).then((img) => { + let createPatternFrom: HTMLImageElement | HTMLCanvasElement; + if(IS_SAFARI) { + const canvas = createPatternFrom = document.createElement('canvas'); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + const ctx = canvas.getContext('2d'); + 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); + + return this.pattern; }); } @@ -86,7 +106,7 @@ export default class ChatBackgroundPatternRenderer { resolve(newUrl); }, 'image/png'); }); - } + } */ public cleanup(canvas: HTMLCanvasElement) { this.canvases.delete(canvas); @@ -102,14 +122,42 @@ export default class ChatBackgroundPatternRenderer { public fillCanvas(canvas: HTMLCanvasElement) { const context = canvas.getContext('2d'); - context.fillStyle = this.pattern; - context.fillRect(0, 0, canvas.width, canvas.height); - // context.drawImage(this.img, 0, 0, canvas.width, canvas.height); + if(context.fillStyle instanceof CanvasPattern) { + context.clearRect(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) { - canvas.width = this.options.width * window.devicePixelRatio; - canvas.height = this.options.height * window.devicePixelRatio * 1.5; + const devicePixelRatio = Math.min(2, window.devicePixelRatio); + canvas.width = this.options.width * devicePixelRatio; + canvas.height = this.options.height * devicePixelRatio * (mediaSizes.activeScreen === ScreenSize.large ? 1.5 : 1); } public createCanvas() { @@ -118,4 +166,34 @@ export default class ChatBackgroundPatternRenderer { this.setCanvasDimensions(canvas); return canvas; } + + public resize(width: number, height: number) { + this.init({ + ...this.options, + width, + height + }); + + const promises: Promise[] = []; + 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}; + } */ } diff --git a/src/helpers/dom/renderImageFromUrl.ts b/src/helpers/dom/renderImageFromUrl.ts index 5078273c..3c3db569 100644 --- a/src/helpers/dom/renderImageFromUrl.ts +++ b/src/helpers/dom/renderImageFromUrl.ts @@ -61,7 +61,7 @@ export default function renderImageFromUrl( } export function renderImageFromUrlPromise(elem: Parameters[0], url: string, useCache?: boolean) { - return new Promise((resolve) => { + return new Promise((resolve) => { renderImageFromUrl(elem, url, resolve, useCache); }); } diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index e1d3e452..e2949f44 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -89,6 +89,7 @@ import getObjectKeysAndSort from '../../helpers/object/getObjectKeysAndSort'; import type GroupCallInstance from '../calls/groupCallInstance'; import type CallInstance from '../calls/callInstance'; import numberThousandSplitter from '../../helpers/number/numberThousandSplitter'; +import ChatBackgroundPatternRenderer from '../../components/chat/patternRenderer'; //console.log('appImManager included33!'); @@ -234,11 +235,17 @@ export class AppImManager { this.appendEmojiAnimationContainer(to); }); - const resizeBackgroundDebounced = debounce(() => { - this.setBackground(this.lastBackgroundUrl, false); - }, 200, false, true); 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) => { diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 93a19f92..5dba219f 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -691,8 +691,11 @@ $background-transition-total-time: #{$input-transition-time - $background-transi align-items: center; justify-content: center; // mix-blend-mode: overlay; - height: 150%; - top: -25%; + + @include respond-to(medium-screens) { + height: 150%; + top: -25%; + } } @include animation-level(2) { @@ -727,7 +730,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi width: 100%; } - &-pattern-canvas { + &:not(.is-dark) &-pattern-canvas { mix-blend-mode: overlay; // height: 100%; }