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.