The purpose of this is to illustrate how to create buttons and checkboxes with images on them, and point out some of the game's various style quirks. It's the result of a lot of guessing and experimenting. Credit should also go to Macros, who really figured a lot of this out, and anybody who wrote a mod with buttons with graphics, because I probably looked at your code at least twice.
There are some things here that might be unnecessary or could be improved. If you know of anything please share. Right now this represents my best guesses. Also if anything below is confusing, know that it's not you. It's just really confusing. There isn't really any documentation for element-specific styles and behaviors.
This is an intermediate-level guide that assumes you know how to work with styles, create GUI elements, and handle GUI events. I discuss styling only (for the most part).
This was written against Factorio 0.12.29.
Before going into code, here is a summary of a few quirks. File these away so that the stuff below makes more sense. They'll be explained in more detail later.
- Buttons:
- Always automatically scale images.
- Can have left click sounds (not covered here).
- Checkboxes:
- Never automatically scale images.
- Can't have left click sounds.
- Inexplicably offset images vertically depending on checkbox size (shift must be used to correct).
- Can't center background images automatically.
- Both:
- Button / checkbox size must be 2 pixels larger than image in both directions to prevent cropping or scaling.
- Backgrounds and borders are not separate from the image (although check marks are their own image). They must be part of the image. However, you can hack a checkbox to overlay an arbitrary image on top of an existing background/border image (covered below).
The consequence of this is that it becomes very difficult to center transparent images on GUI elements: Even if you center the image in your actual image file, the game will crop it and you'll still have to adjust the position manually with 'shift'.
Because of these oddities, there are times when you might have to hack checkboxes into push buttons. This will be covered.
Resources
In the examples below we'll be using the following graphics (images came from Factorio and https://www.iconfinder.com, I composited them). The built in explosive shell, 32x32, with a transparent background:
A basic duck, 32x32, with no transparency:
An... express duck. A full set of tiles (default, hover, click, disabled), 128x32 total, with no transparency:
A "no" sign, 32x32, with a transparent background:
And also all the graphics and example code here can be found in a functioning mod at https://github.com/JC3/ImageButtonExample.
Buttons
So getting right into things, here is an example of a basic button style with an image on it:
Code: Select all
data.raw["gui-style"].default["trans-image-button-style"] = {
type = "button_style",
parent = "button_style",
width = 34,
height = 34,
default_graphical_set = {
type = "monolith",
monolith_image = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32
}
},
hovered_graphical_set = {
type = "monolith",
monolith_image = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32
}
},
clicked_graphical_set = {
type = "monolith",
monolith_image = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32
}
},
disabled_graphical_set = {
type = "monolith",
monolith_image = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32
}
}
}
- The size of the button should be 2 pixels larger in both directions than the sizes of your images, because there is a 1-pixel border around the outside of buttons. In this example since we're using 32x32 images, we make 34x34 buttons.
- Monoliths have 4 undocumented border properties: top_monolith_border, bottom_*, right_*, left_*. These have no apparent effect on internal borders.
- GUI elements have 4 documented padding properties. These affect the text on GUI elements, but have no apparent effect on internal borders and do not seem to affect the images.
- Note that buttons use *_graphical_set for their images. This is different than checkboxes (see below).
- Here we are using a "monolith", which is basically just a plain old image. There is also a "composition", this can be used for defining frames and borders for empty / text buttons (I will add more info when I have time, for now see the forum post below), although you cannot use them in checkboxes and cannot overlay your own images on top -- these "monolith", "composition", etc. image types only apply to *_graphical_sets, which checkboxes do not use.
- The image will always be scaled to fill the button (after the auto-cropping is done).
- The 'scalable' property has no effect.
- Setting "scale=1" has no effect.
- There are undocumented *-no-scale priority values. They have no effect.
- I have left the priority values off because I don't actually know what they do. Also the wiki implies that they apply to animations, although the game's built-in style code uses them liberally on still images.
As you can see you have no control over the stretching. Because of this, you really can't use images with transparent backgrounds on buttons, unless the button is the same size as the auto-cropped image, of course. That said, if you use the above example with an image that does not have transparent edges, you can get a pixel-accurate button. For example, using the basic duck image produces:
You may also notice that the above shows the same image for default, hover, click, and disabled states. If you want to provide visual feedback on those states you'll have to use a different image for each. This is where our express duck tiled image comes in handy. The example below uses that image. It is the same as above except I've added the "x" parameter to each image. In image styles, the "x" and "y" parameter specify where in the image the graphic should be taken from; this can be used to provide multiple images in a single file. They both default to 0 if not specified:
Code: Select all
data.raw["gui-style"].default["tiled-image-button-style"] = {
type = "button_style",
parent = "button_style",
width = 34,
height = 34,
default_graphical_set = {
type = "monolith",
monolith_image = {
filename = "__image-button-example__/graphics/tiled-full.png",
width = 32,
height = 32,
x = 0 -- x offset of default image in our tiled graphic
}
},
hovered_graphical_set = {
type = "monolith",
monolith_image = {
filename = "__image-button-example__/graphics/tiled-full.png",
width = 32,
height = 32,
x = 32 -- x offset of hover image in our tiled graphic
}
},
clicked_graphical_set = {
type = "monolith",
monolith_image = {
filename = "__image-button-example__/graphics/tiled-full.png",
width = 32,
height = 32,
x = 64 -- x offset of click image in our tiled graphic
}
},
disabled_graphical_set = {
type = "monolith",
monolith_image = {
filename = "__image-button-example__/graphics/tiled-full.png",
width = 32,
height = 32,
x = 96 -- x offset of disabled image in our tiled graphic
}
}
}
That pretty much covers it for buttons. They're very straightforward, but you will be stuck with scaled images. If you don't mind, or if you just make all your images have non-transparent edges (and make them the same size as the button to eliminate scaling, if you want), then you'll be satisfied with them.
Checkboxes
Checkboxes basically have two uses, as far as I'm concerned: 1) An actual checkbox, 2) A push button to workaround the scaling limitations of the built-in buttons.
So first, an example of a basic checkbox with a custom checkmark graphic:
Code: Select all
data.raw["gui-style"].default["trans-image-checkbox-style"] = {
type = "checkbox_style",
parent = "checkbox_style",
width = 34,
height = 34,
default_background = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32,
shift = { 0, -8 }
},
hovered_background = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32,
shift = { 0, -8 }
},
clicked_background = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32,
shift = { 0, -8 }
},
disabled_background = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32,
shift = { 0, -8 }
},
checked = {
filename = "__image-button-example__/graphics/no.png",
width = 32,
height = 32,
shift = { 0, -8 }
}
}
- As with buttons, the size of your checkbox should be 2 pixels larger in each direction.
- Note that checkboxes use *_background for their images. This is different than buttons. Pay attention. You don't really have the "monolith" or anything here either. The backgrounds are just basic sprite definitions.
- The image will never be scaled to fit the checkbox.
- The 'scalable' property has no effect.
- However, 'scale' can still be used to scale the images manually (e.g. "scale=2" in the sprite definition).
- The image won't be automatically centered. It'll always be positioned at the top left after auto-cropping.
- There is an "align" property that has no effect on images (also I think it's only for buttons, not checkboxes).
- The images are always offset vertically.
- The exact amount appears to depend on the size of the checkbox and you will have to experiment.
- You will have to use the 'shift' property in each sprite to account for this. The above uses "shift = {0, -8}" everywhere.
- If you leave out "checked" you'll just get the base style's little "x", which may be appropriate for you.
=>
Immediately you'll notice that the "check" graphic doesn't line up with the button graphic. That's because, as just mentioned, the graphics are auto-cropped and checkboxes can't center the background. So if you're using a background graphic with transparent edges you'll have to adjust the x component of its "shift" property to move it into the correct place. The up-side is that, unlike buttons, the image is no longer stretched.
On the other hand, if your image doesn't have transparent edges, it will line up just fine:
=>
Like buttons, checkboxes also have separate default, hover, click, and disabled graphics. Usage is the same: Use different graphics for each, or pull from a tiled image if you want. I'll leave that example out since it's pretty redundant at this point.
By the way, about the vertical shifting. Here's what happens with the above example if you leave "shift" out:
=> (I don't have the checked screenshot but same thing happens to the check image)
As you can see, the graphic was shifted below the checkbox's boundaries. Weird, isn't it.
Checkboxes as Buttons
So you can use checkboxes as normal buttons. The most basic use is if you want a non-scaled graphic, as above. This is pretty straightforward. All you need to do is:
- Create a image file that is just a transparent block, nothing else in it (there might be one in the game already, not sure, I didn't look).
- Use that image as the "check" image.
Code: Select all
parent.add({
type = "checkbox",
name = "trans-image-checkbox",
style = "trans-image-checkbox-style",
state = false -- <-- this is required for checkboxes (true or false, whatever)
})
To that end you can use a checkbox as follows:
- Create a checkbox with your "empty" border/background graphics.
- Use the "check" graphic for your specific icon (which should have a transparent background).
- In your control code, ensure that the checkbox is always checked, i.e. force it to checked if the user unchecks it.
So here's an example, using the explosive cannon shell graphic on top of the game's default slot button graphics. What we've done below is used the graphics the game usually uses for slot_button_styles, and specified our graphic as the check image. Also since the slot graphics are 36x36, we're making a 38x38 button, and the "shift" value has changed to {0,-10} (found experimentally). The tile positions for the backgrounds were copied from the game's default styles.lua.
Code: Select all
data.raw["gui-style"].default["hacky-checkbox-style"] = {
type = "checkbox_style",
parent = "checkbox_style",
width = 38,
height = 38,
default_background = {
filename = "__core__/graphics/gui.png",
width = 36,
height = 36,
shift = { 0, -10 },
x = 111
},
hovered_background = {
filename = "__core__/graphics/gui.png",
width = 36,
height = 36,
shift = { 0, -10 },
x = 148
},
clicked_background = {
filename = "__core__/graphics/gui.png",
width = 36,
height = 36,
shift = { 0, -10 },
x = 184
},
disabled_background = {
filename = "__core__/graphics/gui.png",
width = 36,
height = 36,
shift = { 0, -10 },
x = 111
},
checked = {
filename = "__image-button-example__/graphics/explosive-cannon-shell.png",
width = 32,
height = 32,
shift = { 0, -10 }
}
}
Code: Select all
frame.add({
type = "checkbox",
name = "hacky-checkbox",
style = "hacky-checkbox-style",
state = true -- this is important, it makes our graphic, which is the "check mark", display
})
Code: Select all
function handle_checkbox_click (element)
-- force checkbox state back to true
element.state = true
-- blah blah the rest of our code...
end
Note that our image is centered (almost, like I mentioned above the shell graphic has a stray pixel on the left but whatever, you can adjust with "shift", but I don't care here), not scaled, and that it's overlaid on top of the game's nice default slot button borders, with hover effects and all. So it's completely saved us the trouble of having to make separate graphics for hover states and such. This also gives you a lot of power to easily change the look-and-feel of your mod (by having only a single image for all button borders and backgrounds) or to make it always match Factorio's theme (by using Factorio's standard GUI graphics). It's also a useful technique for when you're programmatically generating styles from existing entity icons, since you don't need to modify the graphics to provide a border and background.
Mod Update Caveat
This is important. Be aware that, at least right now, if you change button styles to checkbox styles in a mod update, you must change the style names as well, or else you will run into this issue.
-------
So here's the final results you should expect from above (the funky non-centered borders are just from default flow on the frame, outside the scope of this write-up):
Anyways, I hope that that was clear and helpful, and saves some of you guys from all the headaches we went through discovering that. And for those of you who already knew everything here, well, you have my deepest sympathies for the pain you went through to figure it out.
I wouldn't be surprised if future versions of Factorio change some of this around so I'll try to keep it updated.
If anybody has any corrections, extra info, interesting related hacks, etc. please post below.