From 044495cf05bbacb3e403597e6608129d96c01393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 24 Aug 2020 14:19:36 +0200 Subject: [PATCH] Centering character in free space This commit adds the ability to put the character where there is free space when a discussion is hapening (either in presentation or chat mode) --- front/dist/resources/style/style.css | 6 +- front/src/Phaser/Game/GameScene.ts | 36 ++++++- front/src/WebRtc/LayoutManager.ts | 138 +++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 7 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index c5a3cc67..8d232fb5 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -317,13 +317,13 @@ body { flex: 0 0 75%; display: flex; justify-content: start; - /*align-items: flex-start;*/ + align-items: flex-start; flex-wrap: wrap; } .main-section > div { - margin: 5%; - flex-basis: 90%; + margin: 2%; + flex-basis: 96%; /*flex-shrink: 2;*/ } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 3c3a6536..431bd3db 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -9,7 +9,7 @@ import { PositionInterface } from "../../Connection"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; -import {DEBUG_MODE, POSITION_DELAY, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; +import {DEBUG_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledTileSet} from "../Map/ITiledMap"; import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; import {AddPlayerInterface} from "./AddPlayerInterface"; @@ -22,7 +22,7 @@ import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import {FourOFourSceneName} from "../Reconnecting/FourOFourScene"; import {loadAllLayers} from "../Entity/body_character"; -import {layoutManager, LayoutMode} from "../../WebRtc/LayoutManager"; +import {CenterListener, layoutManager, LayoutMode} from "../../WebRtc/LayoutManager"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; @@ -69,7 +69,7 @@ interface DeleteGroupEventInterface { groupId: string } -export class GameScene extends Phaser.Scene { +export class GameScene extends Phaser.Scene implements CenterListener { GameManager : GameManager; Terrains : Array; CurrentPlayer!: CurrentGamerInterface; @@ -408,6 +408,9 @@ export class GameScene extends Phaser.Scene { this.repositionCallback = this.reposition.bind(this); window.addEventListener('resize', this.repositionCallback); this.reposition(); + + // From now, this game scene will be notified of reposition events + layoutManager.setListener(this); } private switchLayoutMode(): void { @@ -527,7 +530,7 @@ export class GameScene extends Phaser.Scene { //todo: in a dedicated class/function? initCamera() { this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); - this.cameras.main.startFollow(this.CurrentPlayer); + this.updateCameraOffset(); this.cameras.main.setZoom(ZOOM_LEVEL); } @@ -874,5 +877,30 @@ export class GameScene extends Phaser.Scene { private reposition(): void { this.presentationModeSprite.setY(this.game.renderer.height - 2); this.chatModeSprite.setY(this.game.renderer.height - 2); + + // Recompute camera offset if needed + this.updateCameraOffset(); + } + + /** + * Updates the offset of the character compared to the center of the screen according to the layout mananger + * (tries to put the character in the center of the reamining space if there is a discussion going on. + */ + private updateCameraOffset(): void { + const array = layoutManager.findBiggestAvailableArray(); + let xCenter = (array.xEnd - array.xStart) / 2 + array.xStart; + let yCenter = (array.yEnd - array.yStart) / 2 + array.yStart; + + // Let's put this in Game coordinates by applying the zoom level: + xCenter /= ZOOM_LEVEL * RESOLUTION; + yCenter /= ZOOM_LEVEL * RESOLUTION; + + //console.log("updateCameraOffset", array, xCenter, yCenter, this.game.renderer.width, this.game.renderer.height); + + this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2); + } + + public onCenterChange(): void { + this.updateCameraOffset(); } } diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index 6695fe7f..c2bb683e 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -14,6 +14,14 @@ export enum DivImportance { Normal = "Normal", } +/** + * Classes implementing this interface can be notified when the center of the screen (the player position) should be + * changed. + */ +export interface CenterListener { + onCenterChange(): void; +} + /** * This class is in charge of the video-conference layout. * It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode. @@ -23,6 +31,11 @@ class LayoutManager { private importantDivs: Map = new Map(); private normalDivs: Map = new Map(); + private listener: CenterListener|null = null; + + public setListener(centerListener: CenterListener|null) { + this.listener = centerListener; + } public add(importance: DivImportance, userId: string, html: string): void { const div = document.createElement('div'); @@ -45,6 +58,7 @@ class LayoutManager { this.positionDiv(div, importance); this.adjustVideoChatClass(); + this.listener?.onCenterChange(); } private positionDiv(elem: HTMLDivElement, importance: DivImportance): void { @@ -72,6 +86,7 @@ class LayoutManager { div.remove(); this.importantDivs.delete(userId); this.adjustVideoChatClass(); + this.listener?.onCenterChange(); return; } @@ -80,6 +95,7 @@ class LayoutManager { div.remove(); this.normalDivs.delete(userId); this.adjustVideoChatClass(); + this.listener?.onCenterChange(); return; } @@ -123,11 +139,133 @@ class LayoutManager { for (const div of this.normalDivs.values()) { this.positionDiv(div, DivImportance.Normal); } + this.listener?.onCenterChange(); } public getLayoutMode(): LayoutMode { return this.mode; } + + /*public getGameCenter(): {x: number, y: number} { + + }*/ + + /** + * Tries to find the biggest available box of remaining space (this is a space where we can center the character) + */ + public findBiggestAvailableArray(): {xStart: number, yStart: number, xEnd: number, yEnd: number} { + if (this.mode === LayoutMode.VideoChat) { + const children = document.querySelectorAll('div.chat-mode > div'); + const htmlChildren = Array.from(children.values()); + + // No chat? Let's go full center + if (htmlChildren.length === 0) { + return { + xStart: 0, + yStart: 0, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } + + const lastDiv = htmlChildren[htmlChildren.length - 1]; + // Compute area between top right of the last div and bottom right of window + const area1 = (window.innerWidth - (lastDiv.offsetLeft + lastDiv.offsetWidth)) + * (window.innerHeight - lastDiv.offsetTop); + + // Compute area between bottom of last div and bottom of the screen on whole width + const area2 = window.innerWidth + * (window.innerHeight - (lastDiv.offsetTop + lastDiv.offsetHeight)); + + if (area1 < 0 && area2 < 0) { + // If screen is full, let's not attempt something foolish and simply center character in the middle. + return { + xStart: 0, + yStart: 0, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } + if (area1 <= area2) { + console.log('lastDiv', lastDiv.offsetTop, lastDiv.offsetHeight); + return { + xStart: 0, + yStart: lastDiv.offsetTop + lastDiv.offsetHeight, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } else { + console.log('lastDiv', lastDiv.offsetTop); + return { + xStart: lastDiv.offsetLeft + lastDiv.offsetWidth, + yStart: lastDiv.offsetTop, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } + } else { + // Possible destinations: at the center bottom or at the right bottom. + const mainSectionChildren = Array.from(document.querySelectorAll('div.main-section > div').values()); + const sidebarChildren = Array.from(document.querySelectorAll('aside.sidebar > div').values()); + + // Nothing? Let's center + if (mainSectionChildren.length === 0 && sidebarChildren.length === 0) { + return { + xStart: 0, + yStart: 0, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } + + if (mainSectionChildren.length === 0) { + const lastSidebarDiv = sidebarChildren[sidebarChildren.length-1]; + + // No presentation? Let's center on the main-section space + return { + xStart: 0, + yStart: 0, + xEnd: lastSidebarDiv.offsetLeft, + yEnd: window.innerHeight + } + } + + // At this point, we know we have at least one element in the main section. + const lastPresentationDiv = mainSectionChildren[mainSectionChildren.length-1]; + + const presentationArea = (window.innerHeight - (lastPresentationDiv.offsetTop + lastPresentationDiv.offsetHeight)) + * (lastPresentationDiv.offsetLeft + lastPresentationDiv.offsetWidth); + + let leftSideBar: number; + let bottomSideBar: number; + if (sidebarChildren.length === 0) { + leftSideBar = HtmlUtils.getElementByIdOrFail('sidebar').offsetLeft; + bottomSideBar = 0; + } else { + const lastSideBarChildren = sidebarChildren[sidebarChildren.length - 1]; + leftSideBar = lastSideBarChildren.offsetLeft; + bottomSideBar = lastSideBarChildren.offsetTop + lastSideBarChildren.offsetHeight; + } + const sideBarArea = (window.innerWidth - leftSideBar) + * (window.innerHeight - bottomSideBar); + + if (presentationArea <= sideBarArea) { + return { + xStart: leftSideBar, + yStart: bottomSideBar, + xEnd: window.innerWidth, + yEnd: window.innerHeight + } + } else { + return { + xStart: 0, + yStart: lastPresentationDiv.offsetTop + lastPresentationDiv.offsetHeight, + xEnd: /*lastPresentationDiv.offsetLeft + lastPresentationDiv.offsetWidth*/ window.innerWidth , // To avoid flickering when a chat start, we center on the center of the screen, not the center of the main content area + yEnd: window.innerHeight + } + } + } + } } const layoutManager = new LayoutManager();