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)
This commit is contained in:
parent
b7c2f8be7b
commit
044495cf05
6
front/dist/resources/style/style.css
vendored
6
front/dist/resources/style/style.css
vendored
@ -317,13 +317,13 @@ body {
|
|||||||
flex: 0 0 75%;
|
flex: 0 0 75%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
/*align-items: flex-start;*/
|
align-items: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-section > div {
|
.main-section > div {
|
||||||
margin: 5%;
|
margin: 2%;
|
||||||
flex-basis: 90%;
|
flex-basis: 96%;
|
||||||
/*flex-shrink: 2;*/
|
/*flex-shrink: 2;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
PositionInterface
|
PositionInterface
|
||||||
} from "../../Connection";
|
} from "../../Connection";
|
||||||
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
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 {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledTileSet} from "../Map/ITiledMap";
|
||||||
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
|
import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character";
|
||||||
import {AddPlayerInterface} from "./AddPlayerInterface";
|
import {AddPlayerInterface} from "./AddPlayerInterface";
|
||||||
@ -22,7 +22,7 @@ import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
|||||||
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
||||||
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
|
import {FourOFourSceneName} from "../Reconnecting/FourOFourScene";
|
||||||
import {loadAllLayers} from "../Entity/body_character";
|
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 Texture = Phaser.Textures.Texture;
|
||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||||
@ -69,7 +69,7 @@ interface DeleteGroupEventInterface {
|
|||||||
groupId: string
|
groupId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GameScene extends Phaser.Scene {
|
export class GameScene extends Phaser.Scene implements CenterListener {
|
||||||
GameManager : GameManager;
|
GameManager : GameManager;
|
||||||
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
||||||
CurrentPlayer!: CurrentGamerInterface;
|
CurrentPlayer!: CurrentGamerInterface;
|
||||||
@ -408,6 +408,9 @@ export class GameScene extends Phaser.Scene {
|
|||||||
this.repositionCallback = this.reposition.bind(this);
|
this.repositionCallback = this.reposition.bind(this);
|
||||||
window.addEventListener('resize', this.repositionCallback);
|
window.addEventListener('resize', this.repositionCallback);
|
||||||
this.reposition();
|
this.reposition();
|
||||||
|
|
||||||
|
// From now, this game scene will be notified of reposition events
|
||||||
|
layoutManager.setListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private switchLayoutMode(): void {
|
private switchLayoutMode(): void {
|
||||||
@ -527,7 +530,7 @@ export class GameScene extends Phaser.Scene {
|
|||||||
//todo: in a dedicated class/function?
|
//todo: in a dedicated class/function?
|
||||||
initCamera() {
|
initCamera() {
|
||||||
this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels);
|
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);
|
this.cameras.main.setZoom(ZOOM_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -874,5 +877,30 @@ export class GameScene extends Phaser.Scene {
|
|||||||
private reposition(): void {
|
private reposition(): void {
|
||||||
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
||||||
this.chatModeSprite.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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,14 @@ export enum DivImportance {
|
|||||||
Normal = "Normal",
|
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.
|
* 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.
|
* 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<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
private importantDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
||||||
private normalDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
private normalDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
||||||
|
private listener: CenterListener|null = null;
|
||||||
|
|
||||||
|
public setListener(centerListener: CenterListener|null) {
|
||||||
|
this.listener = centerListener;
|
||||||
|
}
|
||||||
|
|
||||||
public add(importance: DivImportance, userId: string, html: string): void {
|
public add(importance: DivImportance, userId: string, html: string): void {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
@ -45,6 +58,7 @@ class LayoutManager {
|
|||||||
|
|
||||||
this.positionDiv(div, importance);
|
this.positionDiv(div, importance);
|
||||||
this.adjustVideoChatClass();
|
this.adjustVideoChatClass();
|
||||||
|
this.listener?.onCenterChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private positionDiv(elem: HTMLDivElement, importance: DivImportance): void {
|
private positionDiv(elem: HTMLDivElement, importance: DivImportance): void {
|
||||||
@ -72,6 +86,7 @@ class LayoutManager {
|
|||||||
div.remove();
|
div.remove();
|
||||||
this.importantDivs.delete(userId);
|
this.importantDivs.delete(userId);
|
||||||
this.adjustVideoChatClass();
|
this.adjustVideoChatClass();
|
||||||
|
this.listener?.onCenterChange();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +95,7 @@ class LayoutManager {
|
|||||||
div.remove();
|
div.remove();
|
||||||
this.normalDivs.delete(userId);
|
this.normalDivs.delete(userId);
|
||||||
this.adjustVideoChatClass();
|
this.adjustVideoChatClass();
|
||||||
|
this.listener?.onCenterChange();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,11 +139,133 @@ class LayoutManager {
|
|||||||
for (const div of this.normalDivs.values()) {
|
for (const div of this.normalDivs.values()) {
|
||||||
this.positionDiv(div, DivImportance.Normal);
|
this.positionDiv(div, DivImportance.Normal);
|
||||||
}
|
}
|
||||||
|
this.listener?.onCenterChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLayoutMode(): LayoutMode {
|
public getLayoutMode(): LayoutMode {
|
||||||
return this.mode;
|
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<HTMLDivElement>('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<HTMLDivElement>('div.main-section > div').values());
|
||||||
|
const sidebarChildren = Array.from(document.querySelectorAll<HTMLDivElement>('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<HTMLDivElement>('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();
|
const layoutManager = new LayoutManager();
|
||||||
|
Loading…
Reference in New Issue
Block a user