When i was starting my journey with starsector modding i saw, that there were no guides about making UI. Same goes with knowledge, which was scattered. This is my attempt of trying to collect knowledge i gained so when someone starts their journey with UI
Here is a link to guide
https://docs.google.com/document/d/1L0oP8hm8DEHkGudso7a4oonTnZ_-DQdWDtGjy3bLDrk/edit?usp=sharingFeel free to contribiute in any way as there might be things that I got wrong
The knowledge here is based only on my experience, if you think I told sth wrong, or there is a better way, than what I presented, feel free to contribute. This is meant for all.
This one's for UI that uses CustomVisualDialogDelegate, forum link
hereThis guide is to show the basic of how to do your full custom UI from scratch telling process one by one
Designing:Before you begin coding UI you first need to design it. As obvious as it might sound this is most important step, you plan your UI to be scalable with resolution, to avoid issues
Basically you test on two resolutions which i find most notorious
1280x960
1366x768If the UI looks good on those both you can just be sure to test on 1920x1080 and if all is fine and dandy, the UI is meeting criteria of being readable.
As an example of custom UI we will use tech tree UI (at least part of it) to just summarize how you can do such UI.
Here basically we have a checkbox with text that shows hovering info about sth, we will basically now dive into code how to make such one from scratch first to imitate its look then functionality so for example if i press it, something will appear.
Basics:First what we need is to describe hierarchy of UI:
Main panel(
CustomPanelAPI class) - > we can call it as foundations, all is bound to the main panel, we will use it to build our UI layer by layer. Of course you can directly create Tooltip (this will be discussed later what is tooltip) but in a much more complex UI this will only create a mess, that will be very hard to clean up later and for you as codder even harder to make adjustments in future.
To create such custom panel of our precious checkbox we use sth like this :
CustomPanelAPi customPanel = mainPanel.createCustomPanel(float width, float height, CustomPanelPlugin plugin);CustomPanels are mainly used I say as “containers” of true UI.
TooltipMakerAPI is basically a class that is all about creating buttons, text and all that fancy stuff we always want in the UI.
TooltipMakerAPI customTooltip = customPanel.createUIElement(float width, float height, boolean withScroller) Tooltips and custom panels are a little tricky as you can insert panels into tooltips and tooltips into panels.
Let’s start by going for the small component. First you insert panels into the tooltip (let’s call it
tooltipA), so that you can have a more complex UI component which can use the scrollbar fairly easily. Then you can insert that
tooltipA into the “container” panels so that not only can you place the container wherever you want, you can move it relatively easily.
Side note: If you were to place panel A directly inside panel B, the scroll bar no longer effects on panel A
In summary, tooltip is for creating whole UI stuff like buttons while panel is more of container where tooltip is placed , tooltip must be placed in container to work , but it can also hold other panels as UI components.
Important: Panels you can only create from
CustomPanelAPI, not the tooltip. So panel 2 and panel 3 must be initialized from panel 1
createCustomPanel method.
Tooltips MUST be initialized from PANELS you plan to place them in, so TOOLTIP 3 must be initialized from PANEL 3.
By making such a design you are able to create a much more advanced UI with ease.Example of it you have right here:Examples: Button:Okay so first to create such a button we will lay out layers. First custom panel and Tooltip. Because I want this button to basically cover the entire panel I’ll write on paint.
buttonPanel=panel.createCustomPanel(AoTDUiComp.WIDTH_OF_TECH_PANEL - 1, AoTDUiComp.HEIGHT_OF_TECH_PANEL - 1, null);
TooltipMakerAPI vTT = buttonPanel.createUIElement(AoTDUiComp.WIDTH_OF_TECH_PANEL, AoTDUiComp.HEIGHT_OF_TECH_PANEL, false);
To insert button to tooltip we use method of
addAreaChecboxvTT.addAreaCheckbox("", TechToResearch.Id, Misc.getNegativeHighlightColor(), Misc.getDarkHighlightColor(), Misc.getTooltipTitleAndLightHighlightColor(), AoTDUiComp.WIDTH_OF_TECH_PANEL, AoTDUiComp.HEIGHT_OF_TECH_PANEL, 0);
Pad here represents the amount of y pixels away from the last thing you placed on UI.
The colors here mean what color will highlight when hovered over this checkbox.
You can make if statement that changes this color depending on what you need (but its need to be done as it adds checkbox but with different parameters)
If you really wanna plan your UI, like for example i want text to be on top of tooltip you will use getPosition.intl(float x, float y) method. (Intl means from Top left corner)
vTT.addPara(TechToResearch.Name, Color.ORANGE, 10f).getPosition().inTL(10, 5);
Also remember if a method returns for example an object of
LabelAPI or
TooltipMaker or
ButtonAPI is always best to store those variables somewhere as you get more freedom over UI.
Now to make the button fully appear we do sth like this. x and y are coordinates there.
buttonPanel.addUIElement(vTT).inTL(0, -1);
This code takes the vTT variable which is a tooltip from the code above, adding it into a button panel which creates the small component. Now we need to add it into the bigger container.
panel.addUIElement(buttonPanel).inTL(x, y);
Or if you want to add to the tooltip if the container panel had one already created.
tooltip.addComponent(buttonPanel).inTL(x, y);
The results?About the blue lines around the box, it requires knowledge of OpenGL to pull this off. For simplicity the script you need is in the render method (
This must be done when the panel is initialized !)
void drawPanelBorder(CustomPanelAPI p) {
GL11.glBegin(GL11.GL_LINE_LOOP);
float x = p.getPosition().getX() - 5;
float y = p.getPosition().getY();
float w = p.getPosition().getWidth() + 10;
float h = p.getPosition().getHeight();
GL11.glVertex2f(x, y);
GL11.glVertex2f(x + w, y);
GL11.glVertex2f(x + w, y + h);
GL11.glVertex2f(x, y + h);
GL11.glEnd();
}
Note!: If you happen to use OpenGL on Panel that is inserted in tooltip with scrollbar, it will cause at some point render problems, like: Why those lines still render when i scrolled down and content of UI is not being shown. Read Stencil Mask Section!So for now we have a simple area checkbox that highlights when you hover over it. But we can do more.
To make such tooltip appear on hovering we need to use method
NOTE: It must be used after you place the button. If you use it when you place Text, it will only appear if you hover over text ! This is the entire script for hovering UI.
Spoiler
vTT.addTooltipToPrevious(new TooltipMakerAPI.TooltipCreator() {
@Override
public boolean isTooltipExpandable(Object tooltipParam) {
return true;
}
@Override
public float getTooltipWidth(Object tooltipParam) {
return 300;
}
@Override
public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
tooltip.addSectionHeading("Technology Name", Alignment.MID, 10f);
tooltip.addPara(TechToResearch.Name, 10f);
tooltip.addSectionHeading("Time need to research", Alignment.MID, 10f);
float days = TechToResearch.TimeToResearch-TechToResearch.daysSpentOnResearching;
String d = " days";
if(days<=1){
d=" day";
}
AoTDFactionResearchManager manager = AoTDMainResearchManager.getInstance().getManagerForPlayer();
if(!TechToResearch.isResearched()){
tooltip.addPara((int)days+d+" to finish research", Misc.getTooltipTitleAndLightHighlightColor(), 10f);
}
else{
tooltip.addPara("Researched!", Misc.getPositiveHighlightColor(), 10f);
}
tooltip.addSectionHeading("Unlocks", Alignment.MID, 10f);
HashMap<ResearchRewardType, Boolean> haveThat = new HashMap<>();
for (Map.Entry<String, ResearchRewardType> entry : TechToResearch.Rewards.entrySet()) {
haveThat.put(entry.getValue(), true);
}
for (ResearchRewardType researchRewardType : haveThat.keySet()) {
pickHeaderByReward(researchRewardType,tooltip);
for (Map.Entry<String, ResearchRewardType> entry : TechToResearch.Rewards.entrySet()) {
if (entry.getValue() == researchRewardType) {
createInfoFromType(entry.getValue(), entry.getKey(),tooltip);
}
}
}
if (!TechToResearch.ReqTechsToResearchFirst.isEmpty() || (TechToResearch.ReqItemsToResearchFirst != null && !TechToResearch.ReqItemsToResearchFirst.isEmpty())) {
tooltip.addSectionHeading("Requirements", Alignment.MID, 10f);
}
if (!TechToResearch.ReqTechsToResearchFirst.isEmpty()) {
tooltip.setParaInsigniaLarge();
tooltip.addPara("Research", Color.ORANGE, 10f);
tooltip.setParaFontDefault();
}
for (String s : TechToResearch.ReqTechsToResearchFirst) {
if (s.equals("none")) continue;
if(manager.haveResearched(s)){
tooltip.addPara(manager.findNameOfTech(s).Name, Misc.getPositiveHighlightColor(), 10f);
}
else{
tooltip.addPara(manager.findNameOfTech(s).Name, Misc.getNegativeHighlightColor(), 10f);
}
}
if (TechToResearch.ReqItemsToResearchFirst != null && !TechToResearch.ReqItemsToResearchFirst.isEmpty()) {
tooltip.setParaInsigniaLarge();
LabelAPI title = tooltip.addPara("Items", Color.ORANGE, 10f);
tooltip.setParaFontDefault();
for (Map.Entry<String, Integer> entry : TechToResearch.ReqItemsToResearchFirst.entrySet()) {
CustomPanelAPI panel = mainPanel.createCustomPanel(300,60,null);
TooltipMakerAPI tooltipMakerAPI = panel.createUIElement(60,60,false);
TooltipMakerAPI labelTooltip = panel.createUIElement(220,60,false);
LabelAPI labelAPI1 = null;
if(Global.getSettings().getCommoditySpec(entry.getKey())!=null){
tooltipMakerAPI.addImage(Global.getSettings().getCommoditySpec(entry.getKey()).getIconName(),60,60,10f);
labelAPI1 = labelTooltip.addPara(Global.getSettings().getCommoditySpec(entry.getKey()).getName()+" : "+entry.getValue(),10f);
}
if(Global.getSettings().getSpecialItemSpec(entry.getKey())!=null){
tooltipMakerAPI.addImage(Global.getSettings().getSpecialItemSpec(entry.getKey()).getIconName(),60,60,10f);
labelAPI1 = labelTooltip.addPara(Global.getSettings().getSpecialItemSpec(entry.getKey()).getName()+" : "+entry.getValue(),10f);
}
if(manager.haveMetReqForItem(entry.getKey(),entry.getValue())||manager.getResearchOptionFromRepo(TechToResearch.Id).havePaidForResearch){
labelAPI1.setColor(Misc.getPositiveHighlightColor());
}
else{
labelAPI1.setColor(Misc.getNegativeHighlightColor());
}
if(TechToResearch.isResearched){
labelAPI1.setColor(Misc.getPositiveHighlightColor());
}
panel.addUIElement(tooltipMakerAPI).inTL(-10,-20);
panel.addUIElement(labelTooltip).inTL(60,5);
tooltip.addCustom(panel,10f);
}
}
if(TechToResearch.otherReq!=null){
tooltip.setParaInsigniaLarge();
tooltip.addPara("Other", Color.ORANGE, 10f);
tooltip.setParaFontDefault();
if(!TechToResearch.metOtherReq){
tooltip.addPara(TechToResearch.otherReq.two+"\n",Misc.getNegativeHighlightColor(),10f);
}
else{
tooltip.addPara(TechToResearch.otherReq.two+"\n",Misc.getPositiveHighlightColor(),10f);
}
}
}
}, TooltipMakerAPI.TooltipLocation.RIGHT);
Image:Okey i need to explain one trick here with images.
addImage method is void method, so you get nothing. So you can't manipulate placement of image, unless….
unless you make special panel with tooltip which sole purpose is just to be anchor for image.
You place that image in tooltip, which you then place on that special panel and you got yourself image, that can easily be relocated with location of that special panel.
(Or you can use getPrev method in TooltipMakerAPI directly after you place image with addImage - added because someone was feeling insulted that guide does not include this method)
Other MethodsaddSectionHeading is responsible for adding this part
setParaInsigniaLarge makes string that were placed with addPara method much bigger
Using Stencil MaskTo avoid blue lines rendered with
drawPanelBorder like on that image below:
You need to use
Stencil Mask.
First before you even start using stencil you need to include this in
advance method.
glClearStencil(0);
glStencilMask(0xff);
Here is function for :
drawmask as we will be using that below:
void drawmask(CustomPanelAPI p) {
GL11.glBegin(GL_QUADS);
float x = p.getPosition().getX() - 6;
float y = p.getPosition().getY();
float w = p.getPosition().getWidth() + 11;
float h = p.getPosition().getHeight();
GL11.glVertex2f(x, y);
GL11.glVertex2f(x + w, y);
GL11.glVertex2f(x + w, y + h);
GL11.glVertex2f(x, y + h);
GL11.glEnd();
}
Then moving on to
render method section.
glClear(GL_STENCIL_BUFFER_BIT);
glColorMask(false, false, false, false); //disable colour
glEnable(GL_STENCIL_TEST); //enable stencil
openGlUtilis.drawmask(panel1);
glStencilFunc(GL_ALWAYS, 1, 0xff); // Do not test the current value in the stencil buffer, always accept any value on there for drawing
glStencilMask(0xff);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); // Make every test succeed
openGlUtilis.drawmask(panel1);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Make sure you will no longer (over)write stencil values, even if any test succeeds
glColorMask(true, true, true, true); // Make sure we draw on the backbuffer again.
glStencilFunc(GL_EQUAL, 1, 0xFF); // Now we will only draw pixels where the corresponding stencil buffer value equals 1
//here you have code for drawing panels for example with drawPanelBorder we mentioned , that will be located in stencil mask
glDisable(GL_STENCIL_TEST);
TipsTry to divide your UI into smaller components, therefore it will be easier for you, to navigate in your code.
Example of such is for example my own UIPanel:
package data.kaysaar.aotd.vok.ui.components;
import com.fs.starfarer.api.ui.CustomPanelAPI;
import com.fs.starfarer.api.ui.TooltipMakerAPI;
import java.awt.*;
public class UiPanel implements AoTDUiComp {
public CustomPanelAPI mainPanel;
public CustomPanelAPI panel;
public TooltipMakerAPI tooltip;
public void init(CustomPanelAPI mainPanel, CustomPanelAPI panelAPI, TooltipMakerAPI tooltipMakerAPI) {
this.mainPanel = mainPanel;
panel = panelAPI;
tooltip = tooltipMakerAPI;
}
public void createUI() {
}
@Override
public void createUI(float x, float y) {
}
public void placeTooltip(float x, float y) {
panel.addUIElement(tooltip).inTL(x, y);
}
public void placeSubPanel(float x, float y) {
mainPanel.addComponent(panel).inTL(x, y);
}
@Override
public void render(Color colorOfRender, float alphamult) {
}
}
Summary:For the rest of the UI what you need is imagination of how you wanna place components and how it's gonna look like. This guide only shows very basics of UI -> basically how to treat
CustomPanelAPI and
TooltipMakerAPI.