From e9504c63593e427f5409fc31e9999259d22c31d8 Mon Sep 17 00:00:00 2001 From: Elijah R Date: Wed, 10 Jul 2024 22:07:58 -0400 Subject: [PATCH] add username balloons under agents --- msagent.js/src/agent.ts | 40 +++++++++++++++++++++++++++++------ msagent.js/src/wordballoon.ts | 9 ++++---- webapp/src/ts/client.ts | 2 ++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/msagent.js/src/agent.ts b/msagent.js/src/agent.ts index 5177288..635a81d 100644 --- a/msagent.js/src/agent.ts +++ b/msagent.js/src/agent.ts @@ -51,16 +51,25 @@ class AgentAnimationState { } } +enum AgentWordBalloonPosition { + AboveCentered, + BelowCentered +} + class AgentWordBalloonState { char: Agent; text: string; + hasTip: boolean; + position: AgentWordBalloonPosition; balloonCanvas: HTMLCanvasElement; balloonCanvasCtx: CanvasRenderingContext2D; - constructor(char: Agent, text: string) { + constructor(char: Agent, text: string, hasTip: boolean, position: AgentWordBalloonPosition) { this.char = char; this.text = text; + this.hasTip = hasTip; + this.position = position; this.balloonCanvas = document.createElement('canvas'); this.balloonCanvasCtx = this.balloonCanvas.getContext('2d')!; @@ -74,13 +83,13 @@ class AgentWordBalloonState { // hack fix for above this.balloonCanvas.style.pointerEvents = 'none'; - let rect = wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20); + let rect = wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20, hasTip); // Second pass, actually set the element to the right width and stuffs this.balloonCanvas.width = rect.w; this.balloonCanvas.height = rect.h; - wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20); + wordballoonDrawText(this.balloonCanvasCtx, { x: 0, y: 0 }, this.text, 20, hasTip); this.char.getElement().appendChild(this.balloonCanvas); @@ -101,9 +110,17 @@ class AgentWordBalloonState { positionUpdated() { let size = this.char.getSize(); - - this.balloonCanvas.style.top = -(this.balloonCanvas.height) + 'px'; this.balloonCanvas.style.left = -((this.balloonCanvas.width / 2) - (size.w / 2)) + 'px'; + switch (this.position) { + case AgentWordBalloonPosition.AboveCentered: { + this.balloonCanvas.style.top = -(this.balloonCanvas.height) + 'px'; + break; + } + case AgentWordBalloonPosition.BelowCentered: { + this.balloonCanvas.style.bottom = -(this.balloonCanvas.height) + 'px'; + break; + } + } } } @@ -119,6 +136,7 @@ export class Agent { private animState: AgentAnimationState | null = null; private wordballoonState: AgentWordBalloonState | null = null; + private usernameBalloonState: AgentWordBalloonState | null = null; constructor(data: AcsData) { this.data = data; @@ -262,12 +280,22 @@ export class Agent { if (index !== -1) this.playAnimation(index, finishCallback); } + setUsername(username: string) { + if (this.usernameBalloonState !== null) { + this.usernameBalloonState.finish(); + this.usernameBalloonState = null; + } + + this.usernameBalloonState = new AgentWordBalloonState(this, username, false, AgentWordBalloonPosition.BelowCentered); + this.usernameBalloonState.show(); + } + speak(text: string) { if (this.wordballoonState != null) { this.stopSpeaking(); } - this.wordballoonState = new AgentWordBalloonState(this, text); + this.wordballoonState = new AgentWordBalloonState(this, text, true, AgentWordBalloonPosition.AboveCentered); this.wordballoonState.positionUpdated(); this.wordballoonState.show(); } diff --git a/msagent.js/src/wordballoon.ts b/msagent.js/src/wordballoon.ts index 45a732a..1e25c97 100644 --- a/msagent.js/src/wordballoon.ts +++ b/msagent.js/src/wordballoon.ts @@ -37,7 +37,7 @@ export async function wordballoonInit() { } // This function returns a rect which is the usable inner contents of the box. -export function wordballoonDraw(ctx: CanvasRenderingContext2D, at: Point, size: Size): Rect { +export function wordballoonDraw(ctx: CanvasRenderingContext2D, at: Point, size: Size, hasTip: boolean = true): Rect { // Snap the size to a clean 12x12 system, // so we stay (as close to) pixel perfect as possible. // This is "lazy" but oh well. It works! @@ -93,7 +93,8 @@ export function wordballoonDraw(ctx: CanvasRenderingContext2D, at: Point, size: // For now, we always simply use the center of the bottom.. // Draw the tip. - spriteDraw(ctx, tip_sprite, at.x + size.w / 2, at.y + 12 * (j + 1) - 1); + if (hasTip) + spriteDraw(ctx, tip_sprite, at.x + size.w / 2, at.y + 12 * (j + 1) - 1); ctx.restore(); @@ -128,7 +129,7 @@ function wordWrapToStringList(text: string, maxLength: number) { } // This draws a wordballoon with text. This function respects the current context's font settings and does *not* modify them. -export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, text: string, maxLen: number = 20): Rect { +export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, text: string, maxLen: number = 20, hasTip: boolean = true): Rect { let lines = wordWrapToStringList(text, maxLen); // Create metrics for each line @@ -158,7 +159,7 @@ export function wordballoonDrawText(ctx: CanvasRenderingContext2D, at: Point, te size.h = Math.floor(size.h); // Draw the word balloon and get the inner rect - let rectInner = wordballoonDraw(ctx, at, size); + let rectInner = wordballoonDraw(ctx, at, size, hasTip); // Draw all the lines of text let y = 0; diff --git a/webapp/src/ts/client.ts b/webapp/src/ts/client.ts index ce40e66..f56f134 100644 --- a/webapp/src/ts/client.ts +++ b/webapp/src/ts/client.ts @@ -142,6 +142,7 @@ export class MSAgentClient { this.charlimit = initMsg.data.charlimit; for (let _user of initMsg.data.users) { let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + _user.agent); + agent.setUsername(_user.username); agent.addToDom(this.agentContainer); agent.show(); let user = new User(_user.username, agent); @@ -153,6 +154,7 @@ export class MSAgentClient { case MSAgentProtocolMessageType.AddUser: { let addUserMsg = msg as MSAgentAddUserMessage; let agent = await agentCreateCharacterFromUrl(this.url + '/api/agents/' + addUserMsg.data.agent); + agent.setUsername(addUserMsg.data.username); agent.addToDom(this.agentContainer); agent.show(); let user = new User(addUserMsg.data.username, agent);