Rework backdrop to draw one image with two different level of blur

This commit is contained in:
Germain Souquet 2021-07-12 13:51:46 +02:00
parent 36ba65b534
commit 8f345dc1ba
8 changed files with 89 additions and 61 deletions

View file

@ -18,7 +18,6 @@ $groupFilterPanelWidth: 56px; // only applies in this file, used for calculation
$roomListCollapsedWidth: 68px; $roomListCollapsedWidth: 68px;
.mx_LeftPanel { .mx_LeftPanel {
background-color: $roomlist-bg-color;
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel // TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
min-width: 206px; min-width: 206px;
max-width: 50%; max-width: 50%;

View file

@ -39,10 +39,10 @@ limitations under the License.
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%;
min-height: 100%; min-height: 100%;
z-index: 0; z-index: 0;
pointer-events: none; pointer-events: none;
overflow: hidden;
} }
.mx_MatrixToolbar { .mx_MatrixToolbar {
@ -76,7 +76,7 @@ limitations under the License.
} }
/* not the left panel, and not the resize handle, so the roomview/groupview/... */ /* not the left panel, and not the resize handle, so the roomview/groupview/... */
.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle) { .mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle):not(canvas) {
background-color: $primary-bg-color; background-color: $primary-bg-color;
flex: 1 1 0; flex: 1 1 0;

View file

@ -24,7 +24,6 @@ $activeBorderColor: $secondary-fg-color;
.mx_SpacePanel { .mx_SpacePanel {
flex: 0 0 auto; flex: 0 0 auto;
background-color: $groupFilterPanel-bg-color;
padding: 0; padding: 0;
margin: 0; margin: 0;
position: relative; position: relative;

View file

@ -17,25 +17,44 @@ limitations under the License.
import React, { createRef } from "react"; import React, { createRef } from "react";
import "context-filter-polyfill"; import "context-filter-polyfill";
import UIStore from "../../stores/UIStore";
interface IProps { interface IProps {
width?: number;
height?: number;
backgroundImage?: CanvasImageSource; backgroundImage?: CanvasImageSource;
blur?: string;
opacity?: number;
} }
export default class BackdropPanel extends React.PureComponent<IProps> { interface IState {
private canvasRef = createRef<HTMLCanvasElement>(); spacePanelWidth: number;
private ctx: CanvasRenderingContext2D; roomListWidth: number;
viewportHeight: number;
}
static defaultProps = { export default class BackdropPanel extends React.PureComponent<IProps, IState> {
blur: "60px", private spacesCanvasRef = createRef<HTMLCanvasElement>();
opacity: .15, private roomListCanvasRef = createRef<HTMLCanvasElement>();
};
private spacesCtx: CanvasRenderingContext2D;
private roomListCtx: CanvasRenderingContext2D;
constructor(props: IProps) {
super(props);
this.state = {
spacePanelWidth: 0,
roomListWidth: 0,
viewportHeight: UIStore.instance.windowHeight,
};
}
public componentDidMount() { public componentDidMount() {
this.ctx = this.canvasRef.current.getContext("2d"); this.spacesCtx = this.spacesCanvasRef.current.getContext("2d");
this.roomListCtx = this.roomListCanvasRef.current.getContext("2d");
UIStore.instance.on("SpacePanel", this.onResize);
UIStore.instance.on("LeftPanel", this.onResize);
}
public componentWillUnmount() {
UIStore.instance.off("SpacePanel", this.onResize);
UIStore.instance.off("LeftPanel", this.onResize);
} }
public componentDidUpdate() { public componentDidUpdate() {
@ -44,15 +63,25 @@ export default class BackdropPanel extends React.PureComponent<IProps> {
} }
} }
private onResize = () => {
const spacePanelDimensions = UIStore.instance.getElementDimensions("SpacePanel");
const roomListDimensions = UIStore.instance.getElementDimensions("LeftPanel");
this.setState({
spacePanelWidth: spacePanelDimensions ? spacePanelDimensions.width : 0,
roomListWidth: roomListDimensions ? roomListDimensions.width : 0,
viewportHeight: UIStore.instance.windowHeight,
});
};
private refreshBackdropImage = (): void => { private refreshBackdropImage = (): void => {
const { width, height, backgroundImage } = this.props; const width = this.state.spacePanelWidth + this.state.roomListWidth;
this.canvasRef.current.width = width; const height = this.state.viewportHeight;
this.canvasRef.current.height = height; const { backgroundImage } = this.props;
const imageWidth = (backgroundImage as ImageBitmap).width const imageWidth = (backgroundImage as ImageBitmap).width
|| (backgroundImage as HTMLImageElement).naturalWidth; || (backgroundImage as HTMLImageElement).naturalWidth;
const imageHeight = (backgroundImage as ImageBitmap).height const imageHeight = (backgroundImage as ImageBitmap).height
|| (backgroundImage as HTMLImageElement).naturalHeight; || (backgroundImage as HTMLImageElement).naturalHeight;
const contentRatio = imageWidth / imageHeight; const contentRatio = imageWidth / imageHeight;
const containerRatio = width / height; const containerRatio = width / height;
@ -69,23 +98,48 @@ export default class BackdropPanel extends React.PureComponent<IProps> {
const x = (width - resultWidth) / 2; const x = (width - resultWidth) / 2;
const y = (height - resultHeight) / 2; const y = (height - resultHeight) / 2;
this.ctx.filter = `blur(${this.props.blur})`; this.spacesCanvasRef.current.width = this.state.spacePanelWidth;
this.ctx.drawImage( this.spacesCanvasRef.current.height = this.state.viewportHeight;
this.roomListCanvasRef.current.width = this.state.roomListWidth;
this.roomListCanvasRef.current.height = this.state.viewportHeight;
this.spacesCtx.filter = `blur(30px)`;
this.roomListCtx.filter = `blur(60px)`;
this.spacesCtx.drawImage(
backgroundImage, backgroundImage,
x, x,
y, y,
resultWidth, resultWidth,
resultHeight, resultHeight,
); );
this.roomListCtx.drawImage(
backgroundImage,
0, 0,
imageWidth, imageHeight,
x - this.state.spacePanelWidth,
y,
resultWidth,
resultHeight,
);
}; };
public render() { public render() {
return <canvas return <>
ref={this.canvasRef} <canvas
className="mx_BackdropPanel" ref={this.spacesCanvasRef}
style={{ className="mx_BackdropPanel"
opacity: this.props.opacity, style={{
}} opacity: .15,
/>; }}
/>
<canvas
style={{
transform: `translateX(${this.state.spacePanelWidth}px)`,
opacity: .1,
}}
ref={this.roomListCanvasRef}
className="mx_BackdropPanel"
/>
</>;
} }
} }

View file

@ -30,7 +30,6 @@ import AutoHideScrollbar from "./AutoHideScrollbar";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import UserTagTile from "../views/elements/UserTagTile"; 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"; import UIStore from "../../stores/UIStore";
@replaceableComponent("structures.GroupFilterPanel") @replaceableComponent("structures.GroupFilterPanel")
@ -153,14 +152,7 @@ class GroupFilterPanel extends React.Component {
); );
} }
const panelDimensions = UIStore.instance.getElementDimensions("GroupPanel");
return <div className={classes} onClick={this.onClearFilterClick} ref={this.ref}> return <div className={classes} onClick={this.onClearFilterClick} ref={this.ref}>
<BackdropPanel
backgroundImage={this.props.backgroundImage}
width={panelDimensions?.width}
height={panelDimensions?.height}
/>
<AutoHideScrollbar <AutoHideScrollbar
className="mx_GroupFilterPanel_scroller" className="mx_GroupFilterPanel_scroller"
onClick={this.onClick} onClick={this.onClick}

View file

@ -43,12 +43,10 @@ import { replaceableComponent } from "../../utils/replaceableComponent";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
import UIStore from "../../stores/UIStore"; import UIStore from "../../stores/UIStore";
import BackdropPanel from "./BackdropPanel";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
backgroundImage?: CanvasImageSource;
} }
interface IState { interface IState {
@ -426,7 +424,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
if (this.state.showGroupFilterPanel) { if (this.state.showGroupFilterPanel) {
leftLeftPanel = ( leftLeftPanel = (
<div className="mx_LeftPanel_GroupFilterPanelContainer"> <div className="mx_LeftPanel_GroupFilterPanelContainer">
<GroupFilterPanel backgroundImage={this.props.backgroundImage} /> <GroupFilterPanel />
{SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null} {SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
</div> </div>
); );
@ -453,15 +451,8 @@ export default class LeftPanel extends React.Component<IProps, IState> {
"mx_AutoHideScrollbar", "mx_AutoHideScrollbar",
); );
const panelDimensions = UIStore.instance.getElementDimensions("LeftPanel");
return ( return (
<div className={containerClasses} ref={this.ref}> <div className={containerClasses} ref={this.ref}>
<BackdropPanel
backgroundImage={this.props.backgroundImage}
width={panelDimensions?.width}
height={panelDimensions?.height}
/>
{leftLeftPanel} {leftLeftPanel}
<aside className="mx_LeftPanel_roomListContainer"> <aside className="mx_LeftPanel_roomListContainer">
{this.renderHeader()} {this.renderHeader()}

View file

@ -65,6 +65,7 @@ import ToastContainer from './ToastContainer';
import MyGroups from "./MyGroups"; import MyGroups from "./MyGroups";
import UserView from "./UserView"; import UserView from "./UserView";
import GroupView from "./GroupView"; import GroupView from "./GroupView";
import BackdropPanel from "./BackdropPanel";
// We need to fetch each pinned message individually (if we don't already have it) // We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity. // so each pinned message may trigger a request. Limit the number per room for sanity.
@ -643,13 +644,15 @@ class LoggedInView extends React.Component<IProps, IState> {
> >
<ToastContainer /> <ToastContainer />
<div ref={this._resizeContainer} className={bodyClasses}> <div ref={this._resizeContainer} className={bodyClasses}>
<BackdropPanel
backgroundImage={this.state.backgroundImage}
/>
{ SettingsStore.getValue("feature_spaces") { SettingsStore.getValue("feature_spaces")
? <SpacePanel backgroundImage={this.state.backgroundImage} /> ? <SpacePanel />
: null } : null }
<LeftPanel <LeftPanel
isMinimized={this.props.collapseLhs || false} isMinimized={this.props.collapseLhs || false}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
backgroundImage={this.state.backgroundImage}
/> />
<ResizeHandle /> <ResizeHandle />
{ pageElement } { pageElement }

View file

@ -43,7 +43,6 @@ import { Key } from "../../../Keyboard";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationState } from "../../../stores/notifications/NotificationState"; import { NotificationState } from "../../../stores/notifications/NotificationState";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import BackdropPanel from "../../structures/BackdropPanel";
import UIStore from "../../../stores/UIStore"; import UIStore from "../../../stores/UIStore";
interface IButtonProps { interface IButtonProps {
@ -180,9 +179,7 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCo
</div>; </div>;
}); });
interface IProps { interface IProps {}
backgroundImage?: CanvasImageSource;
}
const SpacePanel = (props: IProps) => { const SpacePanel = (props: IProps) => {
// We don't need the handle as we position the menu in a constant location // We don't need the handle as we position the menu in a constant location
@ -274,7 +271,6 @@ const SpacePanel = (props: IProps) => {
UIStore.instance.stopTrackingElementDimensions("SpacePanel"); UIStore.instance.stopTrackingElementDimensions("SpacePanel");
}; };
}, []); }, []);
const panelDimensions = UIStore.instance.getElementDimensions("SpacePanel");
return ( return (
<DragDropContext onDragEnd={result => { <DragDropContext onDragEnd={result => {
@ -288,12 +284,6 @@ const SpacePanel = (props: IProps) => {
onKeyDown={onKeyDownHandler} onKeyDown={onKeyDownHandler}
ref={ref} ref={ref}
> >
<BackdropPanel
backgroundImage={props.backgroundImage}
width={panelDimensions?.width}
height={panelDimensions?.height}
opacity={.3}
/>
<Droppable droppableId="top-level-spaces"> <Droppable droppableId="top-level-spaces">
{(provided, snapshot) => ( {(provided, snapshot) => (
<AutoHideScrollbar <AutoHideScrollbar