FMF:Custom Menu 1
The first OHRRPGCE only allowed users four maps, so let's honor that tradition by restricting our users to four colored boxes, arranged like so:
Although there are many different kinds of Menu Slices, this kind (blank boxes) keep all their code in MenuSlice.java. Recall that MetaMenu.java contains all our initialization and layout code. MenuEngine.java doesn't need to be touched.
Background and borders[edit]
First, let's define the visuals. Each Menu Slice must be initialized with a MenuFormatArgs.java object, which contains all sorts of good stuff. Backgrounds are defined as an ARGB color, and a "Fill Type". Borders are defined as a list of 1 pixel RGB colors, outside first. We'll use a 3-pixel border; two of the target color and one black, just to look spiffy. Here's the code for our blue box:
//Blue box MenuFormatArgs mf1 = new MenuFormatArgs(); mf1.bgColor = 0x99D9EA; mf1.borderColors = new int[]{0x4D6DF3, 0x4D6DF3, 0x000000}; mf1.fillType = MenuSlice.FILL_SOLID; MenuSlice blueBox = new MenuSlice(mf1);
The colors are presented as 0xAARRGGBB, where RR GG and BB are hexadecimal digits for red/green/blue components, and AA is the alpha component (00 is clear, FF is solid). For performance reasons, you should set the "fillType" properly whenever possible. Its values include:
- MenuSlice.FILL_SOLID
- MenuSlice.FILL_NONE (don't color it)
- MenuSlice.FILL_TRANSLUCENT (for semi-transparent values)
- MenuSlice.FILL_GUESS (set 0x00****** to FILL_NONE, 0xFF****** to FILL_SOLID, and anything else to FILL_TRANSLUCENT)
The remaining three boxes are declared as follows:
//Green box MenuFormatArgs mf2 = new MenuFormatArgs(); mf2.bgColor = 0xA8E61D; mf2.borderColors = new int[]{0x22B14C, 0x22B14C, 0x000000}; mf2.fillType = MenuSlice.FILL_SOLID; MenuSlice greenBox = new MenuSlice(mf2); //Orange box MenuFormatArgs mf3 = new MenuFormatArgs(); mf3.bgColor = 0xFFC20E; mf3.borderColors = new int[]{0xFF7E00, 0xFF7E00, 0x000000}; mf3.fillType = MenuSlice.FILL_SOLID; MenuSlice orangeBox = new MenuSlice(mf3); //Purple box MenuFormatArgs mf4 = new MenuFormatArgs(); mf4.bgColor = 0xB5A5D5; mf4.borderColors = new int[]{0x6F3198, 0x6F3198, 0x000000}; mf4.fillType = MenuSlice.FILL_SOLID; MenuSlice purpleBox = new MenuSlice(mf4);
You can actually use the same MenuFormatArgs object each time; calling MenuSlice's constructor will (deep) copy everything to its own local storage.
Anchors[edit]
Now that we have our boxes, how do we connect them? I've found it best to say the connections out loud, and then convert them to code. For example: the green box is connected "from the middle-right of the blue box to the lower-left of itself". In other words:
mf2.fromAnchor = GraphicsAdapter.VCENTER|GraphicsAdapter.RIGHT; mf2.toAnchor = GraphicsAdapter.BOTTOM|GraphicsAdapter.LEFT;
The pipe (also called "or") is used to concatenate bitflags. You should always pick one of "GraphicsAdapter.TOP/BOTTOM/VCENTER" and or it with one "GraphicsAdapter.LEFT/RIGHT/HCENTER" --any further mixing has unspecified behavior. Make sure you do this BEFORE you call MenuSlice's constructor! Here's our other two formats:
mf3.fromAnchor = GraphicsAdapter.BOTTOM|GraphicsAdapter.HCENTER; mf3.toAnchor = GraphicsAdapter.TOP|GraphicsAdapter.HCENTER; mf4.fromAnchor = GraphicsAdapter.BOTTOM|GraphicsAdapter.RIGHT; mf4.toAnchor = GraphicsAdapter.BOTTOM|GraphicsAdapter.RIGHT;
That was easy. Note that mf1 doesn't really need any anchors, since it's the top-left component, and not directly connected to anything. It gets the default connection of TOP|LEFT; if you change this, you might see some "glitchy" behavior that will actually make more sense later.
Position and Size[edit]
Size and location are somewhat different, due to their nature to change without warning. We can only set a hint for the x/y/width/height values of these fields. In our case, width and height are just the actual size (e.g., "200"), and the x and y "hints" are the number of pixels to the right and then down from where the anchors connect the components. After setting all these values, our Slices are still floating in the void; we call "connect()" to append one menu item to another. There are four possible directions to connect on, and two flags:
- CF_PAINT - connect these Slices visibly (direction doesn't really matter)
- CF_CONTROL - auto-process input for these Slices (direction matters, see next section)
Look at the code below for the green box, then compare it with our initial sketch. It should make sense:
mf2.xHint = 10; mf2.yHint = 0; mf2.widthHint = 43; mf2.heightHint = 50; blueBox.connect(greenBox, MenuSlice.CONNECT_RIGHT, MenuSlice.CFLAG_CONTROL|MenuSlice.CFLAG_PAINT); greenBox.connect(purpleBox, MenuSlice.CONNECT_BOTTOM, MenuSlice.CFLAG_CONTROL|MenuSlice.CFLAG_PAINT);
Sample Code[edit]
Here is the entire source listing for our custom menu:
public static void buildMenu(int width, int height) { //Blue box MenuFormatArgs mf1 = new MenuFormatArgs(); mf1.bgColor = 0x99D9EA; mf1.borderColors = new int[]{0x4D6DF3, 0x4D6DF3, 0x000000}; mf1.fillType = MenuSlice.FILL_SOLID; mf1.xHint = 22; mf1.yHint = 22; mf1.widthHint = 100; mf1.heightHint = 100; MenuSlice blueBox = new MenuSlice(mf1); //Green box MenuFormatArgs mf2 = new MenuFormatArgs(); mf2.bgColor = 0xA8E61D; mf2.borderColors = new int[]{0x22B14C, 0x22B14C, 0x000000}; mf2.fillType = MenuSlice.FILL_SOLID; mf2.fromAnchor = GraphicsAdapter.VCENTER|GraphicsAdapter.RIGHT; mf2.toAnchor = GraphicsAdapter.BOTTOM|GraphicsAdapter.LEFT; mf2.xHint = 10; mf2.yHint = 0; mf2.widthHint = 50; mf2.heightHint = 80; MenuSlice greenBox = new MenuSlice(mf2); //Orange box MenuFormatArgs mf3 = new MenuFormatArgs(); mf3.bgColor = 0xFFC20E; mf3.borderColors = new int[]{0xFF7E00, 0xFF7E00, 0x000000}; mf3.fillType = MenuSlice.FILL_SOLID; mf3.fromAnchor = GraphicsAdapter.BOTTOM|GraphicsAdapter.HCENTER; mf3.toAnchor = GraphicsAdapter.TOP|GraphicsAdapter.HCENTER; mf3.xHint = 0; mf3.yHint = 10; mf3.widthHint = 76; mf3.heightHint = 30; MenuSlice orangeBox = new MenuSlice(mf3); //Purple box MenuFormatArgs mf4 = new MenuFormatArgs(); mf4.bgColor = 0xB5A5D5; mf4.borderColors = new int[]{0x6F3198, 0x6F3198, 0x000000}; mf4.fillType = MenuSlice.FILL_SOLID; mf4.fromAnchor = GraphicsAdapter.BOTTOM|GraphicsAdapter.RIGHT; mf4.toAnchor = GraphicsAdapter.BOTTOM|GraphicsAdapter.RIGHT; mf4.xHint = 0; mf4.yHint = 150; mf4.widthHint = 100; mf4.heightHint = 100; MenuSlice purpleBox = new MenuSlice(mf4); //Connect blueBox.connect(greenBox, MenuSlice.CONNECT_RIGHT, MenuSlice.CFLAG_CONTROL|MenuSlice.CFLAG_PAINT); blueBox.connect(orangeBox, MenuSlice.CONNECT_BOTTOM, MenuSlice.CFLAG_CONTROL|MenuSlice.CFLAG_PAINT); greenBox.connect(purpleBox, MenuSlice.CONNECT_BOTTOM, MenuSlice.CFLAG_CONTROL|MenuSlice.CFLAG_PAINT); //Some bookkeeping MetaMenu.topLeftMI = blueBox; }
Here's a screenshot of our system in action:
Although this might seem very tedious -if not wasteful- to you, the method is general enough to use for anything. For example, it's difficult to determine the height of a text box that automatically word-wraps. In such a case, using from/to Anchors becomes very powerful. And, as we all know, tasks which are "long, boring and explicit" can usually be incorporated into, say, a visual editor. By someone else with time on his hands. :P