EduNetworkBuilder/Web/textwindow.js
Tim Young 858bfef1bb Change how the text menu works. It is somewhat behind in that it does
not have all the same old functionality, but it uses menuitems now.
This will allow us to much-better utilize the textwindows.
2024-06-14 16:01:41 -05:00

409 lines
14 KiB
JavaScript

//This file pertains to the textwindow.
//We use this for displaying a puzzle's starting instructions
//and for choosing new puzzles.
var tmOutsideYMargin=20; //How far from the top and bottom left blank
var tmOutsideXMargin=20; //How far from right and left side left blank
var tmBorderWidth=4;
var tmMenuBarHight=20;
var tmScrollBarWidth=20;
var tmWindowBackground="grey";
var tmTextHeight; //Calculated when we build the page
var tmLastHighlightedIndex=-1;
var tmTextYGap=5; //The gap between lines
var tmPuzzleSelectMenuLevel=0;
var cachedTextMenuCanvas = null;
//Print the textmenu
function textMenuPrint(TextToPrint, selectedindex = -1, highlightedindex = -1)
{
//It would be nice to print it on top of whatever is currently there.
//But we will do that later.
PrintScreen(0); //Print the network, then we can lay over it
if(cachedTextMenuCanvas == null)
{
cachedTextMenuCanvas = document.createElement('canvas');
cachedTextMenuCanvas.width = MainCanvas.width - (tmOutsideXMargin * 2);
cachedTextMenuCanvas.height = MainCanvas.height - (tmOutsideYMargin * 2);
}
//If we get here, it is already created. Get the context
var cTMCctx = cachedTextMenuCanvas.getContext('2d');
var rect;
//Fill in the background
cTMCctx.fillStyle = tmWindowBackground;
cTMCctx.fillRect(0,0, cachedTextMenuCanvas.width, cachedTextMenuCanvas.height);
//Put the X there so we can click on it
rect = makeRectangle(cachedTextMenuCanvas.width - tmScrollBarWidth, 0, tmScrollBarWidth, tmMenuBarHight, tmOutsideXMargin, tmOutsideYMargin);
cTMCctx.drawImage(imageFromName("x"), rect.sx, rect.sy, rect.deltax, rect.deltay);
registerActionStruct("square", rect, null, textwindow_XClick);
//Put the DownArrow there so we can click on it
cTMCctx.drawImage(imageFromName("ArrowUp"),cachedTextMenuCanvas.width - tmScrollBarWidth,tmMenuBarHight,tmScrollBarWidth,tmMenuBarHight);
//Put the X there so we can click on it
cTMCctx.drawImage(imageFromName("ArrowDown"),cachedTextMenuCanvas.width - tmScrollBarWidth,cachedTextMenuCanvas.height - tmMenuBarHight,tmScrollBarWidth,tmMenuBarHight);
//Create and Draw the menu bar
cTMCctx.beginPath();
cTMCctx.moveTo(cachedTextMenuCanvas.width - tmScrollBarWidth,0);
cTMCctx.lineTo(cachedTextMenuCanvas.width - tmScrollBarWidth,cachedTextMenuCanvas.height);
cTMCctx.strokeStyle="white";
cTMCctx.stroke();
//horizontal line under the X
cTMCctx.beginPath();
cTMCctx.moveTo(cachedTextMenuCanvas.width - tmScrollBarWidth,tmMenuBarHight);
cTMCctx.lineTo(cachedTextMenuCanvas.width,tmMenuBarHight);
cTMCctx.strokeStyle="white";
cTMCctx.stroke();
var cachedTextMenuTextCanvas = document.createElement('canvas');
//Figure out how much space we need. - start with a simple canvas (non expandable)
cachedTextMenuTextCanvas.width = cachedTextMenuCanvas.width - tmScrollBarWidth;
cachedTextMenuTextCanvas.height = cachedTextMenuCanvas.height;
var cTMTCctx = cachedTextMenuTextCanvas.getContext('2d');
cTMTCctx.strokeStyle="black";
cTMTCctx.font = "20px serif";
var lines = [];
if(typeof(TextToPrint) === "string")
lines = fragmentTextIntoLines(cTMTCctx, TextToPrint, cachedTextMenuTextCanvas.width - 10);
else
lines = TextToPrint; //If we passed in a list of strings
//Now we have the number of lines.
var metrics = cTMTCctx.measureText("test");
var yHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent + tmTextYGap; //the hight of the default font and gap
tmTextHeight = yHeight; //store it for use in highlighting
//console.log("Height = "+yHeight);
var totalHeight = (lines.length * yHeight) + tmTextYGap;
if(cachedTextMenuTextCanvas.height < totalHeight) cachedTextMenuTextCanvas.height = totalHeight;// resize if needed
//Highlight text
if (highlightedindex >= 0)
{
//console.log("Showing hilighted index " + highlightedindex);
//Fill in the area highlighted
cTMTCctx.fillStyle = "white";
cTMTCctx.globalAlpha = 0.3; //mostly transparent
cTMTCctx.fillRect(0,highlightedindex * yHeight + (yHeight/3), cachedTextMenuCanvas.width, yHeight);
cTMTCctx.globalAlpha = 1.0; //reset
}
//Chosen text
if (selectedindex >= 0)
{
//console.log("Showing selected index " + selectedindex + " " + yHeight);
//Fill in the area highlighted
cTMTCctx.fillStyle = "green";
cTMTCctx.globalAlpha = 0.4; //mostly transparent
cTMTCctx.fillRect(0,selectedindex * yHeight + (yHeight/3), cachedTextMenuCanvas.width, yHeight);
cTMTCctx.globalAlpha = 1.0; //reset
}
cTMTCctx.fillStyle = "black";
cTMTCctx.strokeStyle="black";
//Now, print text on the canvas.
for (var i = 0; i < lines.length; i++)
{
cTMTCctx.fillText(lines[i], 5, ((i+1) * yHeight));
//console.log("printing text part: " + lines[i]);
}
//Write the text canvas on the main canvas. If we are scrolled up or down, do that.
cTMCctx.drawImage(cachedTextMenuTextCanvas,0,0);
//create and Draw the scroll-bar on the side
//Then make the text if we have not done so
//Finally print on top of the main canvas
MainCanvas_ctx.globalAlpha = 0.9; //some transparancy
MainCanvas_ctx.drawImage(cachedTextMenuCanvas,tmOutsideXMargin, tmOutsideYMargin)
MainCanvas_ctx.globalAlpha = 1; //reset transparancy
}
function fragmentTextIntoLines(ctx, text, maxWidth) {
var words = text.split(" ");
var lines = [];
var currentLine = words[0];
for (var i = 1; i < words.length; i++) {
var word = words[i];
var width = ctx.measureText(currentLine + " " + word).width;
if (width < maxWidth) {
currentLine += " " + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine);
return lines;
}
function TextWindow_handleMouseUp(evt)
{
console.log("TextWindow Mouse Up");
//If we get here, it is a mouse-click event. See if we are on the X
if(mouseDownLocation.pageX + tmScrollBarWidth >= cachedTextMenuCanvas.width - tmScrollBarWidth)
{
console.log("TextWindow Mouse Up - X fits");
//The X fits. Now, see which button, or scroll-bar we clicked on.
if(mouseDownLocation.pageY - tmOutsideYMargin <= tmMenuBarHight
&& mouseDownLocation.pageY - tmOutsideYMargin >= 0)
{
console.log("TextWindow Mouse Up - Y fits");
//We clicked the X
textwindow_XClick();
}
}
else
{
if(uiMode == 2)
{
//We are in puzzle-select mode and clicked somewhere.
var levellist=networkNamesMatchingText("Level"+tmPuzzleSelectMenuLevel);
if(tmLastHighlightedIndex>=0 && tmLastHighlightedIndex< levellist.length)
{
//We found a puzzle
console.log("Clicked on puzzle: " + levellist[tmLastHighlightedIndex]);
uiMode=1;
switchPuzzle(levellist[tmLastHighlightedIndex]);
}
}
}
mouseDidMovement=false; //reset it after we raise the button
}
function textwindow_XClick(point, object) {
//When the x is clicked, we do not care about the position or object. There is no object
//Dispose of the text window
uiMode = 0;
//dispose of temp canvas; will recreate later if needed
cachedTextMenuCanvas = null;
cachedTextMenuTextCanvas = null;
//Redraw the screen
PrintScreen();
}
function PrintPuzzleSelectMenu(level=0)
{
var levellist=networkNamesMatchingText("Level"+level);
//console.log("list is this long: " + levellist.length);
//textMenuPrint(levellist, -1, tmLastHighlightedIndex);
//tmPuzzleSelectMenuLevel=level;
//
var local_menuitems = [];
for (var i = 1; i < levellist.length; i++) {
var item = new menuitem(null, levellist[i], levellist[i], null, null, textMenu_PuzzleClick)
local_menuitems.push(item);
}
var local_menu = new menuclass(null);
local_menu.items = local_menuitems;
menuPrint(local_menu);
}
function textMenu_PuzzleClick(text, name, source, dest) {
//If we get here, someone clicked on a puzzle name. Select it and move on
//We are in puzzle-select mode and clicked somewhere.
//if (text == null) { } //!== does not work properly
//else {
// console.log("Clicked on puzzle: " + text);
// uiMode = 1;
// switchPuzzle(text);
//}
}
function textMenu_HandleMouseMove(evt)
{
//var highlighted_index = Math.floor(((evt.pageY - tmOutsideYMargin) - (tmTextHeight/3)) / tmTextHeight);
//if(tmLastHighlightedIndex != highlighted_index)
//{
// //the index has changed
// console.log("index = " + highlighted_index);
// tmLastHighlightedIndex = highlighted_index;
// PrintPuzzleSelectMenu(tmPuzzleSelectMenuLevel);
//}
}
class menuitem {
constructor(rectangle, shownText, payloadName = null, payloadSource = null, payloadDest = null, clickFunc=null) {
this.rectangle = rectangle
this.shownText = shownText
this.payloadName = payloadName //The command, such as 'delete', 'power-on', 'ping', etc
this.payloadSource = payloadSource //The link, device, or original item.
this.payloadDest = payloadDest //The destination device, etc. Often null unless we are pinging or tracert
this.clickFunc = clickFunc
}
}
//Our menuclass will have a menu. We need to be able to have cascading menus (one on top of the other)
//so we can return to a past menu if we have moved on to a child menu.
class menuclass {
constructor(sourceItem) {
this.SourceItem = sourceItem
this.hmargin = 10 //horizontal margin on both left and right
this.vmargin = 10 //virtical margin on both top and bottom
this.backcolor = "grey" //The background color
this.font = "20px serif" //font and size
this.textcolor = "black" //Font color
this.items = [] //An array of menuitems
this.TextYGap = 3; //The gap to add to the size
}
}
function menuPrint(menu, menutop = 0, highlightedindex = -1) {
//the menu is the menuclass that holds everything
//menutop is the top=most item. If we scroll up and down, this changes.
//highlighted index is the item that has been moused-over. When this changes, re-draw the menu
PrintScreen(0); //Print the network, then we can lay over it
//This menu covers the entire place.
cachedTextMenuCanvas = document.createElement('canvas');
cachedTextMenuCanvas.width = MainCanvas.width - (tmOutsideXMargin * 2);
cachedTextMenuCanvas.height = MainCanvas.height - (tmOutsideYMargin * 2);
//Get the context
var cTMCctx = cachedTextMenuCanvas.getContext('2d');
var tw_rect;
var tw_width=0; //the width of the menu alltogether
var tw_height=0; //the height of the menu alltogether
var tw_itemHeight=0; //the height of both the font and margin spacing
var tw_rect; //the rectangle we end up using
//We need to figure out the size of the menu.
//Do we have an item to print? If so, add that at top
if (menu.SourceItem == null) {
//!== null does not work right
}
else {
tw_height = imageSize + 10;
}
var tw_startY = tw_height;
//Calculate the size of each text line
//Add spacing. If we are larger than the alloted space, add arrows at top and bottom
//Then we can fill in the area based on all of this.
cTMCctx.strokeStyle = menu.textcolor;
cTMCctx.font = menu.font;
var metrics = cTMCctx.measureText("test");
var yHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent + menu.TextYGap; //the hight of the default font and gap
var tw_TextHeight = yHeight; //store it for use
var maxWidth = 0;
//determine max width (width of longest text line)
for (var index = 0; index < menu.items.length; index++) {
var tmetrics = cTMCctx.measureText(menu.items[index].shownText);
var xWidth = tmetrics.width;
if (xWidth > maxWidth) maxWidth = xWidth;
}
maxWidth = maxWidth + 20 + menu.hmargin;
if (maxWidth > cachedTextMenuCanvas.width) maxWidth = cachedTextMenuCanvas.width;
tw_width = maxWidth;
//determine height (if we have more items than fit, figure out what will fit)
tw_height += menu.items.length * yHeight;
if(tw_height > cachedTextMenuCanvas.height) tw_height = cachedTextMenuCanvas.height;
//Now that we have the width and height, center it
var x = (cachedTextMenuCanvas.width - tw_width) / 2;
var y = (cachedTextMenuCanvas.height - tw_height) / 2;
//make a rectangle of the correct size
//Use that rectangle to place the closing X at the top
tw_rect = makeRectangle(x, y, tw_width, tw_height);
//fill in the whole thing with white and opaque
drawshape("square", makeRectangle(0, 0, cachedTextMenuCanvas.width, cachedTextMenuCanvas.height), "white", .5, cTMCctx);
//Fill in the background, nearly fully but showing a very little behind
drawshape("square", tw_rect, menu.backcolor, .9, cTMCctx);
//Put the X there so we can click on it
rect = makeRectangle(cachedTextMenuCanvas.width - tmScrollBarWidth, y, tmScrollBarWidth, tmMenuBarHight, tmOutsideXMargin, tmOutsideYMargin);
cTMCctx.drawImage(imageFromName("x"), rect.sx, rect.sy, rect.deltax, rect.deltay);
registerActionStruct("square", rect, null, textwindow_XClick);
//Put the UpArrow there so we can click on it
cTMCctx.drawImage(imageFromName("ArrowUp"), cachedTextMenuCanvas.width - tmScrollBarWidth, tmMenuBarHight, tmScrollBarWidth, tmMenuBarHight);
//Put the DownArrow there so we can click on it
cTMCctx.drawImage(imageFromName("ArrowDown"), cachedTextMenuCanvas.width - tmScrollBarWidth, cachedTextMenuCanvas.height - tmMenuBarHight, tmScrollBarWidth, tmMenuBarHight);
//Create and Draw the menu bar
cTMCctx.beginPath();
cTMCctx.moveTo(cachedTextMenuCanvas.width - tmScrollBarWidth, 0);
cTMCctx.lineTo(cachedTextMenuCanvas.width - tmScrollBarWidth, cachedTextMenuCanvas.height);
cTMCctx.strokeStyle = "white";
cTMCctx.stroke();
//horizontal line under the X
cTMCctx.beginPath();
cTMCctx.moveTo(cachedTextMenuCanvas.width - tmScrollBarWidth, tmMenuBarHight);
cTMCctx.lineTo(cachedTextMenuCanvas.width, tmMenuBarHight);
cTMCctx.strokeStyle = "white";
cTMCctx.stroke();
var oldfill = cTMCctx.fillStyle;
var oldstroke = cTMCctx.strokeStyle;
cTMCctx.font = menu.font;
cTMCctx.fillStyle = menu.textcolor;
cTMCctx.strokeStyle = menu.textcolor;
for (var index = menutop; index < menu.items.length; index++) {
var ty = tw_startY + (tw_TextHeight * (index - menutop));
//x is already defined
cTMCctx.fillText(menu.items[index].shownText, x, ty);
metrics = cTMCctx.measureText(menu.items[index].shownText);
var trect = makeRectangle(x + 20, ty, metrics.width, tw_TextHeight);
menu.items[index].rectangle = trect;
registerActionStruct("square", trect, menu.items[index], puzzle_clickOn, null, generic_mouseoverHighlight);
}
cTMCctx.fillStyle = oldfill;
cTMCctx.strokeStyle = oldstroke;
MainCanvas_ctx.globalAlpha = 0.9; //some transparancy
MainCanvas_ctx.drawImage(cachedTextMenuCanvas, tmOutsideXMargin, tmOutsideYMargin)
MainCanvas_ctx.globalAlpha = 1; //reset transparancy
}
function puzzle_clickOn(point, actionrec) {
console.log("clicking on puzzle:" + actionrec.theObject.shownText);
uiMode = 1;
switchPuzzle(actionrec.theObject.shownText);
}