GUI API
This page documents the AngelScript GUI API used to build and interact with the Perception.cx UI.
❗ All GUI types are owned and cleaned up by the engine. You never
deleteor free them from AngelScript.
Overview
Typical usage flow:
Create a subtab under one of the fixed top-level tabs.
Create a panel inside that subtab.
Add UI elements (checkboxes, keybinds, sliders, inputs, color pickers, lists, multi/single-select, buttons) to that panel.
Read/update values in your
main()oron_frame()loop.Optionally, attach a callback to a button.
Example:
subtab_t st = create_subtab(0, "My Script");
if (!st.is_valid())
{
log("[GUI] failed to create subtab");
return -1;
}
panel_t p = st.add_panel("Main Panel", false); // false = large panel
checkbox_t cb = p.add_checkbox("Enable Feature", false);
slider_double_t dist = p.add_slider_double("Distance", "m", 100.0, 0.0, 500.0, 1.0);
// Each frame:
void on_frame(int cb_index, int data_index)
{
bool enabled = cb.get();
double d = dist.get();
// ... use enabled + d ...
}Subtabs & Panels
subtab_t
subtab_tA subtab_t represents a subtab under one of the fixed top-level tabs.
Create
subtab_t create_subtab(int parent_tab, const string &in name);parent_tab: 0–(FIXED_TAB_COUNT-1) – which main tab to attach to.name: label shown in the UI subtab bar.
Methods
panel_t add_panel(const string &in name, bool is_small);
bool is_valid() const;
void subtab_t::set_active(bool active);
void panel_t::set_active(bool active);add_panel– creates a new panel inside this subtab.is_small = true→ small panel layout;false→ normal/large.
is_valid()–trueif the handle is non-null. Only needed for error-checking inmain().
panel_t
panel_tRepresents a panel inside a subtab. All other UI elements are added to a panel.
You don’t manually free panels; they’re destroyed when the owning subtab is destroyed (usually when the script unloads).
Checkbox
Create
checkbox_t panel_t::add_checkbox(
const string &in name,
bool initial,
bool draw_title = true,
bool find_protect = false
);name: label.initial: initial on/off state.draw_title: whether to render the title text.find_protect: iftrue, protects the element from naive find/replace patterns (internal anti-scan behavior).
❗ Design rule:
A checkbox can act as a parent for either:
up to two color pickers, or
one keybind
Color pickers / keybind must be added immediately after the checkbox on the panel.
Methods
bool checkbox_t::get() const;
void checkbox_t::set(bool v);
void checkbox_t::set_active(bool active);Example:
checkbox_t cb = p.add_checkbox("Enable ESP", false);
cb.set(true);
bool enabled = cb.get();Keybind
Keybinds let you bind a virtual-key and mode to a checkbox-controlled feature.
Create
keybind_t panel_t::add_keybind(
const string &in name,
int key,
const string &in mode,
bool draw_title = true,
bool find_protect = false
);Must appear right after its parent checkbox.
key: a Win32 virtual-key code (e.g.0x2Efor Delete,0x2Cfor PrintScreen).mode:"off","on","single","toggle", or"always_on".
Methods
void keybind_t::get(int &out key, string &out mode) const;
void keybind_t::set(int key, const string &in mode);
void keybind_t::set_active(bool active);
bool keybind_t::is_pressed() constExample:
checkbox_t cb = p.add_checkbox("Aimbot", false);
keybind_t kb = p.add_keybind("Aimbot Hotkey", 0x2E, "toggle");
int k; string m;
kb.get(k, m); // -> 0x2E, "toggle"
kb.set(0x2C, "single");
kb.get(k, m); // -> 0x2C, "single"Color Picker
Color pickers store RGBA as 0–255 floats.
Create
color_picker_t panel_t::add_color(
const string &in name,
const array<float> &in rgba,
bool find_protect = false
);Must follow a checkbox or keybind within that chain (according to your UI layout rules).
rgba–array<float>of size 4:{R, G, B, A}, each in [0–255].
Methods
void color_picker_t::get(array<float> &out rgba) const;
void color_picker_t::set(const array<float> &in rgba);
void color_picker_t::set_active(bool active);Example:
checkbox_t cb = p.add_checkbox("Chams", true);
array<float> rgba = {255.0f, 0.0f, 0.0f, 255.0f};
color_picker_t col = p.add_color("Enemy Color", rgba);
// read back
array<float> out = array<float>(4);
col.get(out); // out[0..3] now contain 0–255
// change
array<float> new_rgba = {77.0f, 204.0f, 26.0f, 255.0f};
col.set(new_rgba);Sliders
Double slider
slider_double_t panel_t::add_slider_double(
const string &in name,
const string &in postfix,
double value,
double minv,
double maxv,
double step,
bool draw_title = true,
bool find_protect = false
);Methods
double slider_double_t::get() const;
void slider_double_t::set(double v);
void slider_double_t::set_active(bool active);Example:
slider_double_t dist = p.add_slider_double("Distance", "m", 150.0, 0.0, 500.0, 1.0);
double d0 = dist.get();
dist.set(275.0);
double d1 = dist.get();Int slider
slider_int_t panel_t::add_slider_int(
const string &in name,
const string &in postfix,
int value,
int minv,
int maxv,
int step,
bool draw_title = true,
bool find_protect = false
);Methods
int slider_int_t::get() const;
void slider_int_t::set(int v);
void slider_int_t::set_active(bool active);Input Text Box
input_t panel_t::add_input(
const string &in name,
const string &in initial,
bool draw_title = true,
bool find_protect = false
);
string input_t::get() const;
void input_t::set(const string &in v);Example:
input_t user = p.add_input("Username", "DefaultUser");
string s0 = user.get();
user.set("AS_User");
string s1 = user.get();List
Lists are 2-column rows (info1, info2).
Create
list_t panel_t::add_list(
const string &in name,
const array<dictionary@> &in members,
bool draw_title = true,
bool find_protect = false
);Each dictionary is expected to have:
"info1"– string"info2"– string
Methods
int list_t::get_count() const;
void list_t::clear();
void list_t::append(const string &in info1, const string &in info2);
void list_t::set_active(bool active);Example:
array<dictionary@> members;
dictionary@ d0 = dictionary();
d0.set("info1", "Row0-Col0");
d0.set("info2", "Row0-Col1");
members.insertLast(d0);
dictionary@ d1 = dictionary();
d1.set("info1", "Row1-Col0");
d1.set("info2", "Row1-Col1");
members.insertLast(d1);
list_t lst = p.add_list("Test List", members);
int c0 = lst.get_count();
lst.append("Extra", "Info");
int c1 = lst.get_count();Multi-Select
A multi-select lets you toggle multiple options.
Create
multi_select_t panel_t::add_multi_select(
const string &in name,
const array<dictionary@> &in options,
bool is_expandable,
bool draw_title = true,
bool find_protect = false
);Each dictionary in options should contain:
"label"– string"selected"– bool (optional; defaults to false)
Methods
void multi_select_t::get(array<bool> &out states) const;
void multi_select_t::set(int index, bool state);
void multi_select_t::set_active(bool active);Example:
array<dictionary@> mopts;
dictionary@ m0 = dictionary();
m0.set("label", "A");
m0.set("selected", true);
mopts.insertLast(m0);
dictionary@ m1 = dictionary();
m1.set("label", "B");
m1.set("selected", false);
mopts.insertLast(m1);
multi_select_t ms = p.add_multi_select("Modes", mopts, true);
array<bool> states;
ms.get(states); // states.length=2, [true,false]
ms.set(1, true);
ms.get(states); // [true,true]Single-Select
Single-select is a dropdown/radio list with a single active index.
Create
single_select_t panel_t::add_single_select(
const string &in name,
const array<string> &in options,
int initial_index,
bool is_expandable,
bool draw_title = true,
bool find_protect = false
);Methods
int single_select_t::get() const;
void single_select_t::set(int index);
void single_select_t::set_active(bool active);Example:
array<string> sopts = { "One", "Two", "Three" };
single_select_t ss = p.add_single_select("Select One", sopts, 1, true);
int idx0 = ss.get(); // 1
ss.set(2);
int idx1 = ss.get(); // 2Buttons & Callbacks
Buttons are clickable UI elements with an AngelScript callback.
Funcdef
funcdef void button_callback_t();Create
button_t panel_t::add_button(
const string &in name,
button_callback_t@ cb,
bool find_protect = false
);cbis a function with signaturevoid f().
Methods
void button_t::set_active(bool active);Example
int g_hits = 0;
void OnTestButton()
{
g_hits++;
Print("[GUI] Test button clicked " + g_hits + " times");
}
int main()
{
subtab_t st = create_subtab(0, "Button Demo");
if (!st.is_valid()) return -1;
panel_t p = st.add_panel("Main", false);
button_t btn = p.add_button("Click Me", @OnTestButton);
return 1;
}When you click the button in the UI, OnTestButton will be executed.
bool gui_active()
bool gui_active()Checks if the gui is currently visible or not
Finding Existing Elements (find_*)
find_*)If your script needs to attach to UI created elsewhere (or from config), use the find_* helpers.
Each find_* searches by:
tab– top-level tab index (0..FIXED_TAB_COUNT-1)subtab– subtab name (string)panel– panel name(string)name– element label (string)element type (encoded in the function name)
Signatures
checkbox_t find_checkbox(int tab, const string &in subtab, const string &in panel, const string &in name);
slider_double_t find_slider_double(int tab, const string &in subtab, const string &in panel, const string &in name);
slider_int_t find_slider_int(int tab, const string &in subtab, const string &in panel, const string &in name);
input_t find_input(int tab, const string &in subtab, const string &in panel, const string &in name);
multi_select_t find_multi_select(int tab, const string &in subtab, const string &in panel, const string &in name);
single_select_t find_single_select(int tab, const string &in subtab, const string &in panel, const string &in name);
keybind_t find_keybind(int tab, const string &in subtab, const string &in panel, const string &in name);
button_t find_button(int tab, const string &in subtab, const string &in panel, const string &in name);
color_picker_t find_color(int tab, const string &in subtab, const string &in panel, const string &in name);
list_t find_list(int tab, const string &in subtab, const string &in panel, const string &in name);These return handle 0 if no element is found.
Example:
void on_frame()
{
checkbox_t cb = find_checkbox(0, "AS GUI Test", "Panel Name", "Color Feature");
if (cb != 0)
{
bool v = cb.get();
// use v ...
}
slider_double_t sld = find_slider_double(0, "AS GUI Test", "Panel Name", "Distance Slider");
if (sld != 0)
{
double d = sld.get();
// ...
}
}❗ Internally,
find_*marks elements as "referenced" for cleanup when the script unloads. You don’t need (and must not try) to free them manually.
Config Helpers
These are global functions for saving/loading the UI configuration (all tabs, subtabs, elements).
string construct_config();
void apply_config(const string &in cfg);construct_config()– builds a serialized config string of the current UI state.apply_config(cfg)– applies a serialized config string.
Example:
void save_ui()
{
string cfg = construct_config();
// write cfg to file or elsewhere
}
void load_ui(const string &in cfg)
{
apply_config(cfg);
}Name Prefixing for Elements (##prefix_)
##prefix_)Multiple UI elements with the same visible name are supported, but the configuration system cannot uniquely identify them unless you explicitly assign a prefix.
The ##prefix_ syntax allows you to attach an internal unique ID to an element while keeping the visible label unchanged.
✔ What Prefixing Does
The part after
##prefox_is the visible label shown in the UI and the rest is Unique ID.Prefixing is optional, but required if you want your elements to save/load uniquely in configs.
find_ functions completely ignore the prefix* and search only by the visible label.
✔ Example
// Both show "Player Name" on screen
p.add_input("##elem1_Player Name", "Alex");
p.add_input("##elem2_Player Name", "Alex");Both inputs look identical to the user, but internally:
Internal IDs:
elem1_Player Name,elem2_Player NameVisible name:
Player NameConfig system will save/load each one correctly.
Behavior Summary
UI Rendering
❌ No — prefix hidden
Only the text after ## is shown.
Internal ID / Config
✅ Yes — full prefixed name used
Required for unique config entries.
find_ API*
❌ No — prefix ignored
Searches only by visible name.
Duplicate visible names
✔ Allowed, even without prefix
But: config will not differentiate them unless prefixed.
⚠ Important Notes
1. Duplicate names without prefix
These work in the UI, but the configuration system will treat them as the same element, causing:
overwritten values
incorrect load order
mismatched element states
2. Duplicate names with prefix
These behave perfectly:
visible name is the same
internal ID is unique
config system saves & loads correctly
find_* still searches by visible name only
3. find_* and duplicates
Because find_* searches by visible name, if you have two labels both named "Player Name", then:
auto inp = find_input(0, "Tab", "Panel", "Player Name");…it will always return the first one created, regardless of prefixes.
When Should You Use Prefixing?
Use ##prefix_ whenever:
You create multiple repeated UI blocks (e.g. per-player, per-item, per-weapon).
You have identical element names inside the same panel or subtab.
You want your configuration to properly remember each element’s value.
Quick Example
panel_t p = st.add_panel("Players", false);
// These two will look identical to the user
p.add_slider_double("##p1_Speed", 0.0, 100.0, 50.0);
p.add_slider_double("##p2_Speed", 0.0, 100.0, 60.0);
// Without prefixes, the config system would conflict them
// With prefixes, each one saves/loads separatelyLast updated