Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Move backdrop filter to a canvas based solution #6262

Merged
merged 41 commits into from
Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
27ee7c5
Move backdrop filter to a canvas based solution
germain-gg Jun 24, 2021
652ad36
Backdrop filter compatibility for Firefox and Safari
germain-gg Jun 25, 2021
e628cac
Improve image drawing fill strategy
germain-gg Jun 25, 2021
6d293d1
Merge branch 'develop' into gsouquet/fix-backdrop-filter
germain-gg Jun 25, 2021
31a363d
mock canvas filter polyfill in tests
germain-gg Jun 25, 2021
bf89aa9
Add BackdropPanel copyright header
germain-gg Jun 25, 2021
a17b13f
Add drawable copyright header
germain-gg Jun 25, 2021
533d5ad
Specify return type for drawable promise
germain-gg Jun 25, 2021
5932b93
make backdrop closer to what is happening in production
germain-gg Jun 25, 2021
edae9a4
Merge branch 'develop' into gsouquet/fix-backdrop-filter
germain-gg Jul 6, 2021
7e6d378
Update benchmark action for lockfile diff
germain-gg Jul 6, 2021
dc17bfe
Update benchmark action for lockfile diff
germain-gg Jul 6, 2021
fbac730
Merge branch 'develop' into gsouquet/fix-backdrop-filter
germain-gg Jul 6, 2021
d6c3a22
fix lockfiles
germain-gg Jul 6, 2021
36ba65b
Merge branch 'develop' into gsouquet/fix-backdrop-filter
germain-gg Jul 12, 2021
8f345dc
Rework backdrop to draw one image with two different level of blur
germain-gg Jul 12, 2021
5f9b55e
Merge remote-tracking branch 'origin/develop' into gsouquet/fix-backd…
Palid Aug 13, 2021
7f58a21
Improve BackdropPanel performance by ignoring React
Palid Aug 13, 2021
94a5013
temp
Palid Aug 13, 2021
8bd5441
Merge branch 'develop' into gsouquet/fix-backdrop-filter
Palid Aug 16, 2021
582b5c9
Properly sepearate left column from timeline
Palid Aug 16, 2021
bad37e6
Add missing position:relative;
Palid Aug 16, 2021
a279779
Add proper glass-like look
Palid Aug 16, 2021
bdb5f3b
Refactor GroupFilterPanel to typescript
Palid Aug 17, 2021
a999cad
Properly cache blur effect
Palid Aug 17, 2021
2ee26d0
Make the blur as-close to the css one as possible
Palid Aug 17, 2021
de2eb5b
Satisfy linter
Palid Aug 17, 2021
515fc24
Revert useless change
Palid Aug 17, 2021
78d48b1
Fix weird code style
Palid Aug 17, 2021
eb24204
Fix missing background tonality without avatar
Palid Aug 17, 2021
3fd95ad
Fix missing coma
Palid Aug 17, 2021
a7bda2b
Better blur values for nicer experience
Palid Aug 17, 2021
b1c724a
Fix invalid width for blurred sidebar
Palid Aug 17, 2021
6310949
Fix background blur not being shown on first load
Palid Aug 17, 2021
b5178a3
Fix fonts for inputs being of wrong colors
Palid Aug 17, 2021
e83722e
Fix missing background color in GroupFilterPanel
Aug 18, 2021
455a914
Merge remote-tracking branch 'origin/develop' into gsouquet/fix-backd…
Aug 19, 2021
d65f6dd
Fix bad merge
Aug 19, 2021
468bb5c
Fix mr comments
Aug 19, 2021
fe1d0e6
Remove useless constructor
Aug 19, 2021
fa03b10
Fix missing type for state
Aug 19, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"cheerio": "^1.0.0-rc.9",
"classnames": "^2.2.6",
"commonmark": "^0.29.3",
"context-filter-polyfill": "^0.2.4",
"counterpart": "^0.18.6",
"diff-dom": "^4.2.2",
"diff-match-patch": "^1.0.5",
Expand Down Expand Up @@ -185,7 +186,8 @@
"\\$webapp/i18n/languages.json": "<rootDir>/__mocks__/languages.json",
"decoderWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js"
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
"context-filter-polyfill": "<rootDir>/__mocks__/empty.js"
},
"transformIgnorePatterns": [
"/node_modules/(?!matrix-js-sdk).+$"
Expand Down
2 changes: 1 addition & 1 deletion res/css/structures/_GroupFilterPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ limitations under the License.

.mx_GroupFilterPanel {
flex: 1;
background-color: $groupFilterPanel-bg-color;
cursor: pointer;
position: relative;

display: flex;
flex-direction: column;
Expand Down
2 changes: 1 addition & 1 deletion res/css/structures/_LeftPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ $roomListCollapsedWidth: 68px;
// Create a row-based flexbox for the GroupFilterPanel and the room list
display: flex;
contain: content;
position: relative;

.mx_LeftPanel_GroupFilterPanelContainer {
flex-grow: 0;
Expand All @@ -43,7 +44,6 @@ $roomListCollapsedWidth: 68px;
// Note: The 'room list' in this context is actually everything that isn't the tag
// panel, such as the menu options, breadcrumbs, filtering, etc
.mx_LeftPanel_roomListContainer {
background-color: $roomlist-bg-color;
flex: 1 0 0;
min-width: 0;
// Create another flexbox (this time a column) for the room list components
Expand Down
10 changes: 10 additions & 0 deletions res/css/structures/_MatrixChat.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ limitations under the License.
height: 100%;
}

.mx_BackdropPanel {
position: absolute;
top: 0;
left: 0;
width: 100%;
min-height: 100%;
z-index: 0;
pointer-events: none;
}

.mx_MatrixToolbar {
order: 1;

Expand Down
1 change: 1 addition & 0 deletions res/css/structures/_SpacePanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ $activeBorderColor: $secondary-fg-color;
background-color: $groupFilterPanel-bg-color;
padding: 0;
margin: 0;
position: relative;

// Create another flexbox so the Panel fills the container
display: flex;
Expand Down
21 changes: 0 additions & 21 deletions res/themes/light/css/_mods.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,6 @@
// set the user avatar (if any) as a background so
// it can be blurred by the tag panel and room list

@supports (backdrop-filter: none) {
.mx_LeftPanel {
background-image: var(--avatar-url, unset);
background-repeat: no-repeat;
background-size: cover;
background-position: left top;
}

.mx_GroupFilterPanel {
backdrop-filter: blur($groupFilterPanel-background-blur-amount);
}

.mx_SpacePanel {
backdrop-filter: blur($groupFilterPanel-background-blur-amount);
}

.mx_LeftPanel .mx_LeftPanel_roomListContainer {
backdrop-filter: blur($roomlist-background-blur-amount);
}
}

.mx_RoomSublist_showNButton {
background-color: transparent !important;
}
Expand Down
92 changes: 92 additions & 0 deletions src/components/structures/BackdropPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
Copyright 2021 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { createRef } from "react";
germain-gg marked this conversation as resolved.
Show resolved Hide resolved
import "context-filter-polyfill";

interface IProps {
width?: number;
height?: number;
backgroundImage?: CanvasImageSource;
blur?: string;
opacity?: number;
}


export default class BackdropPanel extends React.PureComponent<IProps> {
germain-gg marked this conversation as resolved.
Show resolved Hide resolved
private canvasRef = createRef<HTMLCanvasElement>();
private ctx: CanvasRenderingContext2D;

static defaultProps = {
blur: "60px",
opacity: .15,
}

public componentDidMount() {
this.ctx = this.canvasRef.current.getContext("2d");
}

public componentDidUpdate() {
if (this.props.backgroundImage) {
requestAnimationFrame(this.refreshBackdropImage);
}
}

private refreshBackdropImage = (): void => {
const { width, height, backgroundImage } = this.props;
this.canvasRef.current.width = width;
this.canvasRef.current.height = height;

const imageWidth = (backgroundImage as ImageBitmap).width
|| (backgroundImage as HTMLImageElement).naturalWidth;
const imageHeight = (backgroundImage as ImageBitmap).height
|| (backgroundImage as HTMLImageElement).naturalHeight;

const contentRatio = imageWidth / imageHeight;
const containerRatio = width / height;
let resultHeight;
let resultWidth;
if (contentRatio > containerRatio) {
resultHeight = height;
resultWidth = height * contentRatio;
} else {
resultWidth = width;
resultHeight = width / contentRatio;
}

const x = (width - resultWidth) / 2;
const y = (height - resultHeight) / 2;

this.ctx.filter = `blur(${this.props.blur})`;
this.ctx.drawImage(
backgroundImage,
x,
y,
resultWidth,
resultHeight,
);
}

public render() {
return <canvas
ref={this.canvasRef}
className="mx_BackdropPanel"
style={{
opacity: this.props.opacity,
}}
/>;
}
}
17 changes: 15 additions & 2 deletions src/components/structures/GroupFilterPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
import SettingsStore from "../../settings/SettingsStore";
import UserTagTile from "../views/elements/UserTagTile";
import {replaceableComponent} from "../../utils/replaceableComponent";
import { replaceableComponent } from "../../utils/replaceableComponent";
import BackdropPanel from "./BackdropPanel";
import UIStore from "../../stores/UIStore";

@replaceableComponent("structures.GroupFilterPanel")
class GroupFilterPanel extends React.Component {
Expand All @@ -40,6 +42,8 @@ class GroupFilterPanel extends React.Component {
selectedTags: [],
};

ref = React.createRef()

componentDidMount() {
this.unmounted = false;
this.context.on("Group.myMembership", this._onGroupMyMembership);
Expand All @@ -56,6 +60,7 @@ class GroupFilterPanel extends React.Component {
});
// This could be done by anything with a matrix client
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
UIStore.instance.trackElementDimensions("GroupPanel", this.ref.current);
}

componentWillUnmount() {
Expand All @@ -65,6 +70,7 @@ class GroupFilterPanel extends React.Component {
if (this._groupFilterOrderStoreToken) {
this._groupFilterOrderStoreToken.remove();
}
UIStore.instance.stopTrackingElementDimensions("GroupPanel");
}

_onGroupMyMembership = () => {
Expand Down Expand Up @@ -147,7 +153,14 @@ class GroupFilterPanel extends React.Component {
);
}

return <div className={classes} onClick={this.onClearFilterClick}>
const panelDimensions = UIStore.instance.getElementDimensions("GroupPanel");

return <div className={classes} onClick={this.onClearFilterClick} ref={this.ref}>
<BackdropPanel
backgroundImage={this.props.backgroundImage}
width={panelDimensions?.width}
height={panelDimensions?.height}
/>
<AutoHideScrollbar
className="mx_GroupFilterPanel_scroller"
onClick={this.onClick}
Expand Down
36 changes: 11 additions & 25 deletions src/components/structures/LeftPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ import SettingsStore from "../../settings/SettingsStore";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore";
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { OwnProfileStore } from "../../stores/OwnProfileStore";
import RoomListNumResults from "../views/rooms/RoomListNumResults";
import LeftPanelWidget from "./LeftPanelWidget";
import {replaceableComponent} from "../../utils/replaceableComponent";
import {mediaFromMxc} from "../../customisations/Media";
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
import UIStore from "../../stores/UIStore";
import BackdropPanel from "./BackdropPanel";

interface IProps {
isMinimized: boolean;
resizeNotifier: ResizeNotifier;
backgroundImage?: CanvasImageSource;
}

interface IState {
Expand Down Expand Up @@ -85,16 +85,14 @@ export default class LeftPanel extends React.Component<IProps, IState> {

BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
OwnProfileStore.instance.on(UPDATE_EVENT, this.onBackgroundImageUpdate);
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
this.bgImageWatcherRef = SettingsStore.watchSetting(
"RoomList.backgroundImage", null, this.onBackgroundImageUpdate);
this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
this.setState({showGroupFilterPanel: SettingsStore.getValue("TagPanel.enableTagPanel")});
});
}

public componentDidMount() {
UIStore.instance.trackElementDimensions("LeftPanel", this.ref.current);
UIStore.instance.trackElementDimensions("ListContainer", this.listContainerRef.current);
UIStore.instance.on("ListContainer", this.refreshStickyHeaders);
// Using the passive option to not block the main thread
Expand All @@ -104,10 +102,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {

public componentWillUnmount() {
SettingsStore.unwatchSetting(this.groupFilterPanelWatcherRef);
SettingsStore.unwatchSetting(this.bgImageWatcherRef);
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
UIStore.instance.stopTrackingElementDimensions("ListContainer");
UIStore.instance.removeListener("ListContainer", this.refreshStickyHeaders);
Expand Down Expand Up @@ -144,23 +140,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
}
};

private onBackgroundImageUpdate = () => {
// Note: we do this in the LeftPanel as it uses this variable most prominently.
const avatarSize = 32; // arbitrary
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
if (settingBgMxc) {
avatarUrl = mediaFromMxc(settingBgMxc).getSquareThumbnailHttp(avatarSize);
}

const avatarUrlProp = `url(${avatarUrl})`;
if (!avatarUrl) {
document.body.style.removeProperty("--avatar-url");
} else if (document.body.style.getPropertyValue("--avatar-url") !== avatarUrlProp) {
document.body.style.setProperty("--avatar-url", avatarUrlProp);
}
};

private handleStickyHeaders(list: HTMLDivElement) {
if (this.isDoingStickyHeaders) return;
this.isDoingStickyHeaders = true;
Expand Down Expand Up @@ -426,7 +405,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
if (this.state.showGroupFilterPanel) {
leftLeftPanel = (
<div className="mx_LeftPanel_GroupFilterPanelContainer">
<GroupFilterPanel />
<GroupFilterPanel backgroundImage={this.props.backgroundImage} />
{SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
</div>
);
Expand All @@ -453,8 +432,15 @@ export default class LeftPanel extends React.Component<IProps, IState> {
"mx_AutoHideScrollbar",
);

const panelDimensions = UIStore.instance.getElementDimensions("LeftPanel");

return (
<div className={containerClasses} ref={this.ref}>
<BackdropPanel
backgroundImage={this.props.backgroundImage}
width={panelDimensions?.width}
height={panelDimensions?.height}
/>
Palid marked this conversation as resolved.
Show resolved Hide resolved
{leftLeftPanel}
<aside className="mx_LeftPanel_roomListContainer">
{this.renderHeader()}
Expand Down
Loading