# Popups

PinballY uses popup boxes to display messages and detailed information. Popup boxes are functionally similar to Windows dialog boxes, but they're visually styled to fit the arcade graphics environment, rather than appearing as separate windows. They're also operable with the basic pin cab buttons rather than requiring a mouse. Popups are mostly limited to information displays, as they don't have a full complement of input controls like Windows dialogs do. PinballY resorts to regular Windows dialogs for anything involving keyboard input or other complex input gestures.

## Popup events

When a popup opens or closes, the system fires popupopen and popupclose events (respectively). See PopupEvent for details.

## System popups

Here's a list of the system popup display types, with the ID strings reported in the .popupID property of the object returned from mainWindow.getUIMode() while the popup is being displayed.

- "about box": the program about box
- "batch capture preview": the batch capture preview dialog
- "capture delay": the capture delay selection dialog box
- "media list": the media list for the current game
- "message": a message box, with an error, warning, or informational alert
- "flyer": a game flyer
- "game info": the detailed game information box
- "high scores": the high scores box
- "instructions": a game instruction card
- "rate game": the game rating dialog (for entering a star rating)

## Custom popups

The mainWindow.showPopup() method lets you create your own completely custom popup dialog, using the same basic display style as the system popups, but with your own custom contents.

The argument to mainWindow.showPopup() is a descriptor object specifying the layout and contents of the popup. The descriptor has the following properties:

- backgroundColor: The background color for the whole popup area, as an HTML-style RGB (red/green/blue) value. Specify this as a hex number with two hex digits each for the red, green, and blue components, each from 0x00 to 0xFF. For example, solid blue is 0x0000FF, and a 50% gray is 0x808080. See any HTML color chart for example hues, and replace the "#" in the HTML notation with the Javascript "0x" prefix for a hex number. The background color is ignored if a background image is specified.
- backgroundImage: The name of a file (JPEG or PNG) to display as the background image. If specified, this supersedes the background color and opacity, and it can help determine the sizing if you don't explicitly specify the width and height.
- borderColor: The RGB color for the popup border, as an HTML-style 0xRRGGBB value, just like the backgroundColor property.
- borderWidth: The width in pixels of the border to draw around the popup. If this is omitted, no border is shown.
- draw: A Javascript function that draws the contents of the popup. See "drawing function" below. If you only want to display a simple image, you can specify the image via backgroundImage and omit the drawing function.
- height: The height of the popup, as a percentage of the layout width of the window. You can omit this if you want the system to use a suitable default or if you want to calculate the height dynamically based on the popup's contents. See "default sizing" and "calculated layout height" below.
- id: A string identifying the popup. This is an arbitrary identifier for your use in keeping track of the UI state; it's reported back to you in PopupEvent events and in the UI state returned from mainWindow.getUIMode().
- opacity: The opacity of the background color, as a value from 0 to 1, with 0 being fully transparent and 1 being fully opaque. Most of the system popups use a dark background and an opacity of around 0.8 to 0.9, so that the playfield graphics remain faintly visible under the popup. You can use the opacity to achieve a similar effect. The opacity is ignored if a background image is supplied, since the image's own alpha channel information is used instead to determine its opacity. If you want to use partial transparency with a background image, use the PNG format (since it supports full alpha channel information), and apply the desired transparency to the image itself with a photo editor program.
- textColor: The initial text color for text drawn within the popup, as an HTML style 0xRRGGBB value, just like backgroundColor. Defaults to white (0xFFFFFF) if not specified.
- width: The width of the popup, as a percentage of the layout height of the window. Note that the window height is used as the basis here even though we're talking about the width of the popup, because this preserves the aspect ratio of the object as the window scales. Internally, the system uses a normalized window aspect ratio of 16:9 (height:width), which is the standard aspect ratio of an HD or 4K monitor in portrait mode. So 100% of the standard layout width is 9/16 = .5625.
- x: The horizontal offset of the popup, as a percentage of the main window's layout width. If this is omitted, the popup is centered horizontally.
- y: The vertical offset of the popup, as a percentage of the main window's layout height. If this is omitted, PinballY automatically positions the popup according to the standard rules it uses for its own system popups, which are designed to make the popup look visually centered in the top portion of the screen above the wheel icons and status area at the bottom.

### Default sizing

If you omit the width and/or height settings in the descriptor, the system figures a default size as follows:

- If a background image is specified, and both the width and height properties are omitted from the popup descriptor, the natural size of the background image is used, normalized to a notional 1920x1080 layout. (The window layout doesn't actually have to be 1920x1080; whatever the actual size is, the image is scaled as though it were being displayed in a 1920x1080 window.)
- If a background image is specified, and either the width or height is specified, but not both, the dimension that's specified is applied, and the other dimension is chosen so that the natural aspect ratio of the image is preserved.
- If there's no background image, and you omit the width, the system uses a default width of 80% of the layout width.
- If there's no background image, and you omit the height, the system requires you to calculate a pixel height for the content area and return it from the draw function you provide in the popup descriptor. An error is thrown if you don't supply a draw function or it doesn't return a height value. See "calculated heights" below.

### Calculated layout height

In some cases, you might want to adjust your popup's overall display height according to its contents, so that it's just tall enough to display the contents. You can do this as follows:

- In the popup descriptor, specify the desired width via the width property, but omit the height property.
- Also, don't use a background image, as the background image will determine the display height. If you need to display a background image with a flexible-height popup, you'll have to draw it yourself within your draw function using the drawing context's drawImage() method.
- In your draw function, keep track of the layout sizes of the items you're displaying. You can use the drawing context's measureText() function to figure the size of a text item.
- Return the final computed height as the return value from your draw function.

When the system has to use this "computed" method to determine the height, it calls your drawing function twice. The first time, the call is only to figure out the height, and doesn't do any actual on-screen drawing. There are two important implications of this two-pass process. First, the actual layout height isn't yet known on the first call, since the whole point of the first call is to determine that information. So if you call getSize() on the drawing context, the height returned will be invalid. Second, any drawing you do on this first pass is ignored and won't actually appear on-screen, so it's okay if the exact drawing positions of objects are wrong on this first pass. For example, if you want to align something with the bottom of the popup, you won't be able to get that position right on the first pass because the overall popup height won't be known at that point. The actual drawing occurs on the second pass, at which point the final height is known, so your vertical position calculations will be valid.

### Drawing function

The draw property of the popup descriptor object passed to mainWindow.showPopup() lets you specify a Javascript function where you create your own custom internal graphics in the popup area.

The system calls this function after setting up the graphics area for the drawing. The argument to the function is a "drawing context" object, which has methods that let you draw text and graphics into the popup area.

The drawing context has the following methods:

- drawImage(file, x, y, width, height): Draw an image. file is the name of the image file (JPEG and PNG formats are supported); this can be an absolute path, or can be a path relative to the PinballY program folder. x and y give the top left coordinates in pixels within the popup drawing area.
- drawText(string): Draws the text in string, starting at the current text origin, within the current text area boundaries, using the current text font and color. This automatically moves the text origin so that the next drawText() call will place its text after this text. If string ends in a newline character ("\n"), the text origin is advanced to the start of the next "line", vertically just below the drawn text and at the left edge of the text area. If string doesn't end in a newline, the text origin is positioned immediately to the right of the drawn text.

  This function automatically word-wraps the text if it exceeds the width of the current text area. However, this accesses a low-level Windows drawing function with a primitive line-wrapping algorithm that wraps each line to the horizontal position of the current origin, not the current text area bounds. This means that you can't use drawText() to draw a mixture of styles with word wrapping, since each segment will wrap from its own origin. If you need to use word-wrapping with styled text, you'll have to implement a higher level layout algorithm in Javascript.

- getTextOrigin(): Returns an object with properties {x: integer, y: integer} giving the current text origin, in pixel coordinates within the popup drawing area. This reflects the updates to the origin made by any drawText() calls, so it's helpful when calculating the content height dynamically (see "calculated layout height" above).
- measureText(string): Figures the pixel area required to display the given text. Returns an object with properties {left: integer, top: integer, right: integer, bottom: integer, width: integer, height: integer}, giving the pixel positions and dimensions of the bounding box the text would occupy if drawn at the current text origin. Note that this doesn't take into account line wrapping; it measures the text as though it were going to be laid out in an infinitely widely boundary area.
- fillRect(x, y, width, height, color): Fills a rectangle at the given position and dimensions with the given color. The color is given as an HTML-style 0xRRGGBB value, just like the backgroundColor in the popup descriptor. The coordinates are in pixels relative to the popup drawing area, and the dimensions are in pixels.
- frameRect(x, y, width, height, frameWidth, color): Draws an outline of the given width in pixels around the specified rectangle. The color is an HTML-style 0xRRGGBB value. The frame is drawn such that each frame line is centered on the edge of the specified rectangle. For example, if you draw a 4-pixel frame, two of the pixels of each edge will be drawn outside the given rectangle area and two of the pixels will be inside the rectangle area.
- getImageSize(filename): Returns an object with properties {width: integer, height: integer} giving the dimensions in pixels of the given image file.
- getSize(): Returns an object with properties {width: integer, height: integer} giving the dimensions in pixels of the popup drawing area. Note that the height will be invalid on the first call to your draw function if the two-pass process is used to compute the final layout height, as described in "calculated layout height" above.
- setFont(name, pointSize, weight): Sets the text font to use for subsequent drawText() and measureText() calls. Any of the arguments can be passed as undefined to keep the values for the current font; for example, you can change just the point size, keeping the current weight, by calling `dc.setFont(undefined, 24)`. The weight value is from 100 to 900; for most fonts, 400 is the normal weight and 700 is bold. (Some fonts have extra-light and extra-bold variations, which is why the range is wider than 400-700, and some have semi-bold variations between normal and bold.)
- setTextAlign(horz, vert): Sets the text alignment for drawText(). Either parameter can be set to undefined to keep the current alignment for that dimension. For horz, -1 means left alignment, 0 is center alignment, and 1 is right alignment. For vert, -1 is top alignment, 0 is vertically centered, and 1 is bottom alignment. When drawing, the alignments are relative to the current text area and the current text origin.
- setTextArea(x, y, width, height): Sets the boundaries of the current text area. The coordinates and dimensions are specified in pixels relative to the popup drawing area. The text boundaries are used for word-wrapping and alignment in drawText().
- setTextColor(color): Sets the color for text drawn with drawText(), as an HTML-style 0xRRGGBB value.
- setTextOrigin(x, y: Sets the text origin for the next call to drawText(). The coordinates are in pixels relative to the popup drawing area.

Return value: If necessary, the function must return the desired final height in pixels of the popup. This is needed if you don't specify a height for the popup in the popup descriptor and you don't supply a background image, since in this case the system has no way to figure the required height on its own. To figure the required height, you should keep track of the positions and heights of the objects you draw, and use that to calculate the overall height needed for the contents. Return the result as the return value from the function. The system ignores the returned height if you explicitly specify a height or a background image in the popup descriptor.