diff --git a/PageSize/Help.txt b/PageSize/Help.txt new file mode 100644 index 0000000000..ddc94dd8d8 --- /dev/null +++ b/PageSize/Help.txt @@ -0,0 +1,104 @@ +PageSize + +PageSize allows the user to resize a page, choosing where to anchor the page's +contents and whether to scale distances and/or sizes based on the ratio of the +page's new size to its old size. + +It is recommended that this script be used in conjunction with the CommandShell +module, which will improve output formatting and command discovery. + + +Commands: + +The "pagesize" command operates in three modes: + + !pagesize [options] X Y + Adjust both width and height of the page. + + !pagesize [options] x X + Adjust only the width of the page. + + !pagesize [options] y Y + Adjust only the height of the page. + +X and Y can either be absolute (N), which will set the relevant dimension to the +given size, or relative (+N, -N), which will adjust the relevant dimension by +the specified amount. Numbers are given in squares. + +By default, if a token is selected, its page will be used. If no token is +selected but the player executing the command is assigned to a page, that page +will be used. Otherwise, the page with the player ribbon will be used. It is +possible to specify other pages to be resized (see options below). + +The "pagesize" command accepts the following options: + + -h, --help Displays a help message and exits. + + -p ID, --page ID Adjusts the size of the specified page. + + -P, --playerpage Adjusts the size of the page with the player + ribbon. + + -a POS, --anchor POS Anchors existing contents to a particular area + (default is top-left, which leaves contents in + place on the screen and adjusts the bottom and + right edges). Available values (and aliases): + top-left (tl, upper-left, ul) + top (t, center-top, ct) + top-right (tr, upper-right, ur) + left (l, center-left, cl) + center (c, center-center, cc) + right (r, center-right, cr) + bottom-left (bl, lower-left, ll) + bottom (b, center-bottom, cb) + bottom-right (br, lower-right, lr) + + -e MODE, --edge MODE Adjusts existing contents in the specified mode: + push Moves contents which falls + outside the new edges to the + nearest positions inside the + new edges (default). + crop Removes contents which falls + outside the new edges. + scale Scales distances based on the + ratio of the new size to the old + size (e.g. a token which was + 1/3 of the way across the page + will be moved to 1/3 of the way + across the new page size). + stretch As scale, but resizes contents + as well as moving them. + + +Examples: + +!pagesize 10 12 + Resizes the default page (see above) to 10 squares wide by 12 squares high + +!pagesize -P x +2 + Adds two columns on the right side of the page with the player ribbon + +!pagesize -p -JpzPx2j_9piP09ceyOv y -1 + Removes one row from the bottom of the specified page. Anything whose + center is below the new bottom of the page will be moved up until it is + fully inside the page. + +!pagesize +1 +2 -a rc + Adds one column on the left and one row each on the top and bottom of the + default page. + +!pagesize x -3 -e crop + Removes the rightmost three columns from the default page, deleting anything + which is no longer visible (items with even one pixel's worth of size within + the visible area are left in place). + +!pagesize +25 +25 -e scale + Adds 25 rows and columns to the default page. Assuming the page was 25x25 + before this command was executed, everything on the page will have its + coordinates doubled (note that, as each square of the old size represents + a 2x2 grid of the new size, single-square tokens will end up at the center + of 2x2 grids, rather than fully in any one square). + +!pagesize +25 +25 -e stretch + As the previous example, but everything on the page will have its size + doubled as well (effectively zooming in and doubling the grid density). diff --git a/PageSize/package.json b/PageSize/package.json new file mode 100644 index 0000000000..3235bf5f15 --- /dev/null +++ b/PageSize/package.json @@ -0,0 +1,10 @@ +{ + "name": "PageSize", + "version": "0.1", + "description": "Resize page, optionally moving or scaling its contents in the process.", + "authors": "manveti", + "roll20userid": "503018", + "dependencies": {}, + "modifies": {}, + "conflicts": [] +} diff --git a/PageSize/pagesize.js b/PageSize/pagesize.js new file mode 100644 index 0000000000..0fac3bbf9e --- /dev/null +++ b/PageSize/pagesize.js @@ -0,0 +1,258 @@ +var PageSize = PageSize || { + GRID_SIZE: 70, + + ANCHORS: {'top-left': "tl", 'tl': "tl", 'left-top': "tl", 'lt': "tl", 'upper-left': "tl", 'ul': "tl", 'left-upper': "tl", 'lu': "tl", + 'top': "t", 't': "t", 'center-top': "t", 'ct': "t", 'top-center': "t", 'tc': "t", + 'top-right': "tr", 'tr': "tr", 'right-top': "tr", 'rt': "tr", 'upper-right': "tr", 'ur': "tr", 'right-upper': "tr", 'ru': "tr", + 'left': "l", 'l': "l", 'center-left': "l", 'cl': "l", 'left-center': "l", 'lc': "l", + 'center': "c", 'c': "c", 'center-center': "c", 'cc': "c", + 'right': "r", 'r': "r", 'center-right': "r", 'cr': "r", 'right-center': "r", 'rc': "r", + 'bottom-left': "bl", 'bl': "bl", 'left-bottom': "bl", 'lb': "bl", 'lower-left': "bl", 'll': "bl", 'left-lower': "bl", + 'bottom': "b", 'b': "b", 'center-bottom': "b", 'cb': "b", 'bottom-center': "b", 'bc': "b", + 'bottom-right': "br", 'br': "br", 'right-bottom': "br", 'rb': "br", 'lower-right': "br", 'lr': "br", 'right-lower': "br", 'rl': "br"}, + + write: function(s, who, style, from){ + if (who){ + who = "/w " + who.split(" ", 1)[0] + " "; + } + sendChat(from, who + s.replace(//g, ">").replace(/\n/g, "
")); + }, + + showHelp: function(who, cmd){ + var helpMsg = ""; + helpMsg += "Usage: " + cmd + " [options] X Y\n"; + helpMsg += " or: " + cmd + " [options] x X\n"; + helpMsg += " or: " + cmd + " [options] y Y\n"; + helpMsg += "In the first form, resizes page to X,Y.\n"; + helpMsg += "In the second and third forms, resizes only the width or height.\n"; + helpMsg += "X and/or Y can use +N or -N to adjust relative to current size."; + PageSize.write(helpMsg, who, "", "PageSize"); + helpMsg = "Options:\n"; + helpMsg += " -h, --help: display this help message\n"; + helpMsg += " -p ID, --page ID: resize specified page\n"; + helpMsg += " -P, --playerpage: resize page with player ribbon\n"; + helpMsg += " -a POS, --anchor POS: where to anchor existing contents\n"; + helpMsg += " default is top-left, adjusting bottom and right edges\n"; + helpMsg += " must be one of: top-left, top, top-right,\n"; + helpMsg += " left, center, right,\n"; + helpMsg += " bottom-left, bottom, bottom-right\n"; + helpMsg += " -e MODE, --edge MODE: mode for adjusting existing contents when page edge moves:\n" + helpMsg += " push: move all contents inside new edges (default)\n"; + helpMsg += " crop: remove contents outside new edges\n"; + helpMsg += " scale: move contents based on ratio of new size to old\n"; + helpMsg += " stretch: as scale, but resize contents as well\n"; + PageSize.write(helpMsg, who, "font-size: small; font-family: monospace", "PageSize"); + }, + + handlePageSizeMessage: function(tokens, msg){ + if (tokens.length < 3){ + return PageSize.showHelp(msg.who, tokens[0]); + } + var args = {'anchor': "tl", 'edge': "push"}; + var getArg = null; + for (var i = 1; i < tokens.length; i++){ + if (getArg){ + args[getArg] = tokens[i]; + getArg = null; + continue; + } + switch (tokens[i]){ + case "-p": + case "--page": + getArg = 'page'; + break; + case "-P": + case "--playerpage": + args['page'] = Campaign().get('playerpageid'); + break; + case "-a": + case "--anchor": + getArg = 'anchor'; + break; + case "-e": + case "--edge": + getArg = 'edge'; + break; + case "x": + case "y": + getArg = tokens[i]; + break; + case "-h": + case "--help": + return PageSize.showHelp(msg.who, tokens[0]); + default: + if (!args.hasOwnProperty('x')){ args['x'] = tokens[i]; } + else if (!args.hasOwnProperty('y')){ args['y'] = tokens[i]; } + else{ + PageSize.write("Unrecognized argument: " + tokens[i], msg.who, "", "PageSize"); + return PageSize.showHelp(msg.who, tokens[0]); + } + } + } + if (getArg){ + PageSize.write("Expected argument for " + getArg, msg.who, "", "PageSize"); + return PageSize.showHelp(msg.who, tokens[0]); + } + + // verify anchor is valid + var anchor = PageSize.ANCHORS[args['anchor']]; + if (!anchor){ + PageSize.write("Invalid anchor: " + args['anchor'], msg.who, "", "PageSize"); + return PageSize.showHelp(msg.who, tokens[0]); + } + + // if no page specified, choose a default page + if (!args['page']){ + // if player has a token selected, use its page + if (msg.selected){ + for (var i = 0; i < msg.selected.length; i++){ + var tok = getObj(msg.selected[i]._type, msg.selected[i]._id); + if (tok){ + args['page'] = tok._pageid; + break; + } + } + } + // otherwise, if player is on their own page, use that + if (!args['page']){ + var playerPages = Campaign().get('playerspecificpages'); + if (playerPages){ args['page'] = playerPages[msg.playerid]; } + } + // otherwise, use the page with the players ribbon + if (!args['page']){ args['page'] = Campaign().get('playerpageid'); } + } + + // grab page object, and verify specified page ID is valid + var page = getObj("page", args['page']); + if (!page){ + PageSize.write("Unable to locate page " + args['page'], msg.who, "", "PageSize"); + return; + } + + // get new X and Y size, verifying specified X and Y are valid + var oldX = page.get('width'), oldY = page.get('height'), x = 0, y = 0; + if ((!args.x) || (args.x.charAt(0) == '+') || (args.x.charAt(0) == '-')){ + x += oldX; + if (args.x){ args.x = args.x.substring(1); } + } + if (args.x){ x += parseInt(args.x); } + if ((!x) || (x <= 0)){ + PageSize.write("Invalid new X size: " + (args.x || x), msg.who, "", "PageSize"); + return PageSize.showHelp(msg.who, tokens[0]); + } + if ((!args.y) || (args.y.charAt(0) == '+') || (args.y.charAt(0) == '-')){ + y += oldY; + if (args.y){ args.y = args.y.substring(1); } + } + if (args.y){ y += parseInt(args.y); } + if ((!y) || (y <= 0)){ + PageSize.write("Invalid new Y size: " + (args.y || y), msg.who, "", "PageSize"); + return PageSize.showHelp(msg.who, tokens[0]); + } + + // move page contents as necessary based on new size, anchor, and edge + var tokens = findObjs({_type: "graphic", _pageid: args['page']}); + switch (args['edge']){ + case "push": + case "crop": + var xMax = x * PageSize.GRID_SIZE, yMax = y * PageSize.GRID_SIZE; + var dX, dY; + if ((anchor == "tl") || (anchor == "l") || (anchor == "bl")){ dX = 0; } + else if ((anchor == "t") || (anchor == "c") || (anchor == "b")){ dX = (x - oldX) * PageSize.GRID_SIZE / 2; } + else { dX = (x - oldX) * PageSize.GRID_SIZE; } + if ((anchor == "tl") || (anchor == "t") || (anchor == "tr")){ dY = 0; } + else if ((anchor == "l") || (anchor == "c") || (anchor == "r")){ dY = (y - oldY) * PageSize.GRID_SIZE / 2; } + else{ dY = (y - oldY) * PageSize.GRID_SIZE; } + for (var i = 0; i < tokens.length; i++){ + var tokX = tokens[i].get('left') + dX, tokY = tokens[i].get('top') + dY; + if ((tokX < 0) || (tokX >= xMax)){ + // token outside new viewable area; push inside or crop based on args['edge'] + var tokRad = tokens[i].get('width') / 2; + if (args['edge'] == "push"){ + if ((tokX < 0) ^ (tokens[i].get('width') >= xMax)){ + // either token is left of page and fits in page, or is right of page and doesn't fit in page: + // align token's left edge with page's left edge + tokX = tokRad; + } + else{ + // either token is right of page and fits in page, or is left of page and doesn't fit in page: + // align token's right edge with page's right edge + tokX = xMax - tokRad; + } + } + else{ + // crop token if it's far enough outside of new bounds to no longer be visible + if ((tokX + tokRad < 0) || (tokX - tokRad >= xMax)){ + tokens[i].remove(); + continue; + } + } + } + if ((tokY < 0) || (tokY >= yMax)){ + // token outside new viewable area; push inside or crop based on args['edge'] + var tokRad = tokens[i].get('height') / 2; + if (args['edge'] == "push"){ + if ((tokY < 0) ^ (tokens[i].get('height') >= yMax)){ + // either token is left of page and fits in page, or is right of page and doesn't fit in page: + // align token's left edge with page's left edge + tokY = tokRad; + } + else{ + // either token is right of page and fits in page, or is left of page and doesn't fit in page: + // align token's right edge with page's right edge + tokY = yMax - tokRad; + } + } + else{ + // crop token if it's far enough outside of new bounds to no longer be visible + if ((tokY + tokRad < 0) || (tokY - tokRad >= yMax)){ + tokens[i].remove(); + continue; + } + } + } + tokens[i].set({'left': tokX, 'top': tokY}); + } + break; + case "scale": + case "stretch": + var xRatio = (oldX ? x / oldX : 1), yRatio = (oldY ? y / oldY : 1); + for (var i = 0; i < tokens.length; i++){ + var newProps = {'left': tokens[i].get('left') * xRatio, 'top': tokens[i].get('top') * yRatio}; + if (args['edge'] == "stretch"){ + newProps['width'] = tokens[i].get('width') * xRatio; + newProps['height'] = tokens[i].get('height') * yRatio; + } + tokens[i].set(newProps); + } + break; + default: + PageSize.write("Invalid edge mode: " + args['edge'], msg.who, "", "PageSize"); + return PageSize.showHelp(msg.who, tokens[0]); + } + + // resize page + page.set({'width': x, 'height': y}); + }, + + handleChatMessage: function(msg){ + if ((msg.type != "api") || (msg.content.indexOf("!pagesize") != 0)){ return; } + + return PageSize.handlePageSizeMessage(msg.content.split(" "), msg); + }, + + registerPageSize: function(){ + if ((typeof(Shell) != "undefined") && (Shell) && (Shell.registerCommand)){ + Shell.registerCommand("!pagesize", "!pagesize [options] X Y", + "Resize a page, optionally moving or scaling its contents", PageSize.handlePageSizeMessage); + if (Shell.write){ + PageSize.write = Shell.write; + } + } + else{ + on("chat:message", PageSize.handleChatMessage); + } + } +}; + +on("ready", function(){ PageSize.registerPageSize(); });