From 5a71d16d1ff6c56a36906400b496a69ed3e4a3a0 Mon Sep 17 00:00:00 2001 From: samstalhandske Date: Wed, 10 Dec 2025 23:12:19 +0100 Subject: [PATCH] Big initial commit: States, simulation, presentation, menus, some art. --- .gitignore | 1 + Makefile | 60 + src/MultiplayerApi.c | 731 ++ src/MultiplayerApi.h | 88 + src/assets/sprites/spr_floor_grass.png | Bin 0 -> 643 bytes src/assets/sprites/spr_food_apple.png | Bin 0 -> 653 bytes src/assets/sprites/spr_snake_body.png | Bin 0 -> 592 bytes src/assets/sprites/spr_snake_head.png | Bin 0 -> 605 bytes src/jansson/dump.c | 516 ++ src/jansson/error.c | 71 + src/jansson/hashtable.c | 352 + src/jansson/hashtable.h | 176 + src/jansson/hashtable_seed.c | 275 + .../jansson-readthedocs-io-en-latest.pdf | Bin 0 -> 290430 bytes src/jansson/jansson.h | 412 ++ src/jansson/jansson_config.h | 82 + src/jansson/jansson_config.h.in | 51 + src/jansson/jansson_private.h | 109 + src/jansson/jansson_private_config.h | 158 + src/jansson/load.c | 1162 ++++ src/jansson/lookup3.h | 71 + src/jansson/lookup3.h.copy | 383 ++ src/jansson/memory.c | 69 + src/jansson/pack_unpack.c | 952 +++ src/jansson/strbuffer.c | 111 + src/jansson/strbuffer.h | 34 + src/jansson/strconv.c | 143 + src/jansson/utf.c | 187 + src/jansson/utf.h | 25 + src/jansson/value.c | 1076 +++ src/main.c | 53 + src/presentation/states/state_ingame.c | 206 + src/presentation/states/state_ingame.h | 21 + src/presentation/states/state_machine.c | 48 + src/presentation/states/state_machine.h | 40 + src/presentation/states/state_main_menu.c | 127 + src/presentation/states/state_main_menu.h | 27 + src/presentation/states/states.h | 10 + src/raygui.h | 5987 +++++++++++++++++ src/raylib.h | 1727 +++++ src/session/game_session.c | 46 + src/session/game_session.h | 38 + src/shared/entity.h | 17 + src/shared/game_world.c | 66 + src/shared/game_world.h | 20 + src/shared/grid.c | 63 + src/shared/grid.h | 25 + src/simulation/simulation_world.c | 21 + src/simulation/simulation_world.h | 15 + 49 files changed, 15852 insertions(+) create mode 100644 Makefile create mode 100644 src/MultiplayerApi.c create mode 100644 src/MultiplayerApi.h create mode 100644 src/assets/sprites/spr_floor_grass.png create mode 100644 src/assets/sprites/spr_food_apple.png create mode 100644 src/assets/sprites/spr_snake_body.png create mode 100644 src/assets/sprites/spr_snake_head.png create mode 100644 src/jansson/dump.c create mode 100644 src/jansson/error.c create mode 100644 src/jansson/hashtable.c create mode 100644 src/jansson/hashtable.h create mode 100644 src/jansson/hashtable_seed.c create mode 100644 src/jansson/jansson-readthedocs-io-en-latest.pdf create mode 100644 src/jansson/jansson.h create mode 100644 src/jansson/jansson_config.h create mode 100644 src/jansson/jansson_config.h.in create mode 100644 src/jansson/jansson_private.h create mode 100644 src/jansson/jansson_private_config.h create mode 100644 src/jansson/load.c create mode 100644 src/jansson/lookup3.h create mode 100644 src/jansson/lookup3.h.copy create mode 100644 src/jansson/memory.c create mode 100644 src/jansson/pack_unpack.c create mode 100644 src/jansson/strbuffer.c create mode 100644 src/jansson/strbuffer.h create mode 100644 src/jansson/strconv.c create mode 100644 src/jansson/utf.c create mode 100644 src/jansson/utf.h create mode 100644 src/jansson/value.c create mode 100644 src/main.c create mode 100644 src/presentation/states/state_ingame.c create mode 100644 src/presentation/states/state_ingame.h create mode 100644 src/presentation/states/state_machine.c create mode 100644 src/presentation/states/state_machine.h create mode 100644 src/presentation/states/state_main_menu.c create mode 100644 src/presentation/states/state_main_menu.h create mode 100644 src/presentation/states/states.h create mode 100644 src/raygui.h create mode 100644 src/raylib.h create mode 100644 src/session/game_session.c create mode 100644 src/session/game_session.h create mode 100644 src/shared/entity.h create mode 100644 src/shared/game_world.c create mode 100644 src/shared/game_world.h create mode 100644 src/shared/grid.c create mode 100644 src/shared/grid.h create mode 100644 src/simulation/simulation_world.c create mode 100644 src/simulation/simulation_world.h diff --git a/.gitignore b/.gitignore index cd531cf..3819808 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ *.i*86 *.x86_64 *.hex +build # Debug files *.dSYM/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..312b24a --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +CC := gcc +SRC_DIR := src + +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S), Linux) + PLATFORM := linux +else ifeq ($(OS), Windows_NT) + PLATFORM := windows +else + PLATFORM := unknown +endif + +BUILD_DIR := build/$(PLATFORM) +CFLAGS := -g -Isrc -Wextra -MMD -MP -Wall -pedantic +LDFLAGS := -flto -Wl,--gc-sections + +LIB_DIR := $(abspath libs/$(PLATFORM)) +CFLAGS += -I$(LIB_DIR) +LDFLAGS += -L$(LIB_DIR) + +ifeq ($(PLATFORM), linux) + LIBS += -lraylib -lm -lraygui +endif + +SRC := $(shell find -L $(SRC_DIR) -type f -name '*.c') + +OBJ := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRC)) +DEP := $(OBJ:.o=.d) + +ifeq ($(PLATFORM), windows) + BIN := $(BUILD_DIR)/main.exe +endif +ifeq ($(PLATFORM), linux) + BIN := $(BUILD_DIR)/main +endif + + +all: $(BIN) + +run: $(BIN) + @cd $(BUILD_DIR) && ./$(notdir $(BIN)) + +$(BIN): $(OBJ) + @mkdir -p $(dir $@) + @cp -r $(SRC_DIR)/assets $(BUILD_DIR) + @cp -r libs $(BUILD_DIR) + @$(CC) $(LDFLAGS) $(OBJ) -o $@ $(LIBS) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + @echo "Compiling $<..." + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + @rm -rf $(BUILD_DIR) + +-include $(DEP) + +.PHONY: all clean \ No newline at end of file diff --git a/src/MultiplayerApi.c b/src/MultiplayerApi.c new file mode 100644 index 0000000..1510cb9 --- /dev/null +++ b/src/MultiplayerApi.c @@ -0,0 +1,731 @@ +#include "MultiplayerApi.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +typedef struct ListenerNode { + int id; + MultiplayerListener cb; + void *user_data; + struct ListenerNode *next; +} ListenerNode; + +typedef struct ListenerSnapshot { + MultiplayerListener cb; + void *user_data; +} ListenerSnapshot; + +struct MultiplayerApi { + char *server_host; + uint16_t server_port; + int sockfd; + char *session_id; + + pthread_t recv_thread; + int recv_thread_started; + int running; + + pthread_mutex_t lock; + ListenerNode *listeners; + int next_listener_id; +}; + +static int connect_to_server(const char *host, uint16_t port); +static int ensure_connected(MultiplayerApi *api); +static int send_all(int fd, const char *buf, size_t len); +static int send_json_line(MultiplayerApi *api, json_t *obj); /* tar över ägarskap */ +static int read_line(int fd, char **out_line); +static void *recv_thread_main(void *arg); +static void process_line(MultiplayerApi *api, const char *line); +static int start_recv_thread(MultiplayerApi *api); + +MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port) { + MultiplayerApi *api = (MultiplayerApi *)calloc(1, sizeof(MultiplayerApi)); + if (!api) { + return NULL; + } + + if (server_host) { + api->server_host = strdup(server_host); + } else { + api->server_host = strdup("127.0.0.1"); + } + + if (!api->server_host) { + free(api); + return NULL; + } + + api->server_port = server_port; + api->sockfd = -1; + api->session_id = NULL; + api->recv_thread_started = 0; + api->running = 0; + api->listeners = NULL; + api->next_listener_id = 1; + + if (pthread_mutex_init(&api->lock, NULL) != 0) { + free(api->server_host); + free(api); + return NULL; + } + + return api; +} + +void mp_api_destroy(MultiplayerApi *api) { + if (!api) return; + + if (api->recv_thread_started && api->sockfd >= 0) { + shutdown(api->sockfd, SHUT_RDWR); + pthread_join(api->recv_thread, NULL); + } + + if (api->sockfd >= 0) { + close(api->sockfd); + } + + pthread_mutex_lock(&api->lock); + ListenerNode *node = api->listeners; + api->listeners = NULL; + pthread_mutex_unlock(&api->lock); + + while (node) { + ListenerNode *next = node->next; + free(node); + node = next; + } + + if (api->session_id) { + free(api->session_id); + } + if (api->server_host) { + free(api->server_host); + } + + pthread_mutex_destroy(&api->lock); + free(api); +} + +int mp_api_host(MultiplayerApi *api, + char **out_session, + char **out_clientId, + json_t **out_data) { + if (!api) return MP_API_ERR_ARGUMENT; + if (api->session_id) return MP_API_ERR_STATE; + + int rc = ensure_connected(api); + if (rc != MP_API_OK) return rc; + + json_t *root = json_object(); + if (!root) return MP_API_ERR_IO; + + json_object_set_new(root, "session", json_null()); + json_object_set_new(root, "cmd", json_string("host")); + json_object_set_new(root, "data", json_object()); + + rc = send_json_line(api, root); + if (rc != MP_API_OK) { + return rc; + } + + char *line = NULL; + rc = read_line(api->sockfd, &line); + if (rc != MP_API_OK) { + return rc; + } + + json_error_t jerr; + json_t *resp = json_loads(line, 0, &jerr); + free(line); + if (!resp || !json_is_object(resp)) { + if (resp) json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + + json_t *cmd_val = json_object_get(resp, "cmd"); + if (!json_is_string(cmd_val) || strcmp(json_string_value(cmd_val), "host") != 0) { + json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + + json_t *sess_val = json_object_get(resp, "session"); + if (!json_is_string(sess_val)) { + json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + const char *session = json_string_value(sess_val); + + json_t *cid_val = json_object_get(resp, "clientId"); + const char *clientId = json_is_string(cid_val) ? json_string_value(cid_val) : NULL; + + json_t *data_val = json_object_get(resp, "data"); + json_t *data_obj = NULL; + if (json_is_object(data_val)) { + data_obj = data_val; + json_incref(data_obj); + } + + api->session_id = strdup(session); + if (!api->session_id) { + if (data_obj) json_decref(data_obj); + json_decref(resp); + return MP_API_ERR_IO; + } + + if (out_session) { + *out_session = strdup(session); + } + if (out_clientId && clientId) { + *out_clientId = strdup(clientId); + } + if (out_data) { + *out_data = data_obj; + } else if (data_obj) { + json_decref(data_obj); + } + + json_decref(resp); + + rc = start_recv_thread(api); + if (rc != MP_API_OK) { + return rc; + } + + return MP_API_OK; +} + +int mp_api_list(MultiplayerApi *api, json_t **out_list) +{ + if (!api || !out_list) return MP_API_ERR_ARGUMENT; + + int rc = ensure_connected(api); + if (rc != MP_API_OK) return rc; + + json_t *root = json_object(); + if (!root) return MP_API_ERR_IO; + + json_object_set_new(root, "cmd", json_string("list")); + + rc = send_json_line(api, root); + if (rc != MP_API_OK) { + return rc; + } + + char *line = NULL; + rc = read_line(api->sockfd, &line); + if (rc != MP_API_OK) { + return rc; + } + + printf("Received line: %s\n", line); // Debug print + + json_error_t jerr; + json_t *resp = json_loads(line, 0, &jerr); + free(line); + if (!resp || !json_is_object(resp)) { + if (resp) json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + + json_t *cmd_val = json_object_get(resp, "cmd"); + if (!json_is_string(cmd_val) || strcmp(json_string_value(cmd_val), "list") != 0) { + json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + + json_t *list_val = json_object_get(resp, "data"); + if (!json_is_object(list_val)) { + json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + + json_t *list_obj = json_object_get(list_val, "list"); + if (!json_is_array(list_obj)) { + json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + + *out_list = list_obj; + json_incref(*out_list); + + json_decref(resp); + return MP_API_OK; +} + +int mp_api_join(MultiplayerApi *api, + const char *sessionId, + json_t *data, + char **out_session, + char **out_clientId, + json_t **out_data) { + if (!api || !sessionId) return MP_API_ERR_ARGUMENT; + if (api->session_id) return MP_API_ERR_STATE; + + int rc = ensure_connected(api); + if (rc != MP_API_OK) return rc; + + json_t *root = json_object(); + if (!root) return MP_API_ERR_IO; + + json_object_set_new(root, "session", json_string(sessionId)); + json_object_set_new(root, "cmd", json_string("join")); + + json_t *data_copy; + if (data && json_is_object(data)) { + data_copy = json_deep_copy(data); + } else { + data_copy = json_object(); + } + json_object_set_new(root, "data", data_copy); + + rc = send_json_line(api, root); + if (rc != MP_API_OK) { + return rc; + } + + char *line = NULL; + rc = read_line(api->sockfd, &line); + if (rc != MP_API_OK) { + return rc; + } + + json_error_t jerr; + json_t *resp = json_loads(line, 0, &jerr); + free(line); + if (!resp || !json_is_object(resp)) { + if (resp) json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + + json_t *cmd_val = json_object_get(resp, "cmd"); + if (!json_is_string(cmd_val) || strcmp(json_string_value(cmd_val), "join") != 0) { + json_decref(resp); + return MP_API_ERR_PROTOCOL; + } + + json_t *sess_val = json_object_get(resp, "session"); + const char *session = NULL; + if (json_is_string(sess_val)) { + session = json_string_value(sess_val); + } + + json_t *cid_val = json_object_get(resp, "clientId"); + const char *clientId = json_is_string(cid_val) ? json_string_value(cid_val) : NULL; + + json_t *data_val = json_object_get(resp, "data"); + json_t *data_obj = NULL; + if (json_is_object(data_val)) { + data_obj = data_val; + json_incref(data_obj); + } + + int joinAccepted = 1; + if (data_obj) { + json_t *status_val = json_object_get(data_obj, "status"); + if (json_is_string(status_val) && + strcmp(json_string_value(status_val), "error") == 0) { + joinAccepted = 0; + } + } + + if (joinAccepted && session) { + api->session_id = strdup(session); + if (!api->session_id) { + if (data_obj) json_decref(data_obj); + json_decref(resp); + return MP_API_ERR_IO; + } + } + + if (out_session && session) { + *out_session = strdup(session); + } + if (out_clientId && clientId) { + *out_clientId = strdup(clientId); + } + if (out_data) { + *out_data = data_obj; + } else if (data_obj) { + json_decref(data_obj); + } + + json_decref(resp); + + if (joinAccepted && api->session_id) { + rc = start_recv_thread(api); + if (rc != MP_API_OK) { + return rc; + } + } + + return joinAccepted ? MP_API_OK : MP_API_ERR_REJECTED; +} + +int mp_api_game(MultiplayerApi *api, json_t *data) { + if (!api || !data) return MP_API_ERR_ARGUMENT; + if (api->sockfd < 0 || !api->session_id) return MP_API_ERR_STATE; + + json_t *root = json_object(); + if (!root) return MP_API_ERR_IO; + + json_object_set_new(root, "session", json_string(api->session_id)); + json_object_set_new(root, "cmd", json_string("game")); + + json_t *data_copy; + if (json_is_object(data)) { + data_copy = json_deep_copy(data); + } else { + data_copy = json_object(); + } + json_object_set_new(root, "data", data_copy); + + return send_json_line(api, root); +} + +int mp_api_listen(MultiplayerApi *api, + MultiplayerListener cb, + void *user_data) { + if (!api || !cb) return -1; + + ListenerNode *node = (ListenerNode *)malloc(sizeof(ListenerNode)); + if (!node) return -1; + + node->cb = cb; + node->user_data = user_data; + + pthread_mutex_lock(&api->lock); + node->id = api->next_listener_id++; + node->next = api->listeners; + api->listeners = node; + pthread_mutex_unlock(&api->lock); + + return node->id; +} + +void mp_api_unlisten(MultiplayerApi *api, int listener_id) { + if (!api || listener_id <= 0) return; + + pthread_mutex_lock(&api->lock); + ListenerNode *prev = NULL; + ListenerNode *cur = api->listeners; + while (cur) { + if (cur->id == listener_id) { + if (prev) { + prev->next = cur->next; + } else { + api->listeners = cur->next; + } + free(cur); + break; + } + prev = cur; + cur = cur->next; + } + pthread_mutex_unlock(&api->lock); +} + +/* --- Interna hjälpfunktioner --- */ + +static int connect_to_server(const char *host, uint16_t port) { + if (!host) host = "127.0.0.1"; + + char port_str[16]; + snprintf(port_str, sizeof(port_str), "%u", (unsigned int)port); + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* IPv4 eller IPv6 */ + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo *res = NULL; + int err = getaddrinfo(host, port_str, &hints, &res); + if (err != 0) { + return -1; + } + + int fd = -1; + for (struct addrinfo *rp = res; rp != NULL; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) continue; + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) { + break; + } + close(fd); + fd = -1; + } + + freeaddrinfo(res); + return fd; +} + +static int ensure_connected(MultiplayerApi *api) { + if (!api) return MP_API_ERR_ARGUMENT; + if (api->sockfd >= 0) return MP_API_OK; + + int fd = connect_to_server(api->server_host, api->server_port); + if (fd < 0) { + return MP_API_ERR_CONNECT; + } + api->sockfd = fd; + return MP_API_OK; +} + +static int send_all(int fd, const char *buf, size_t len) { + size_t sent = 0; + while (sent < len) { + ssize_t n = send(fd, buf + sent, len - sent, 0); + if (n < 0) { + if (errno == EINTR) continue; + return -1; + } + if (n == 0) { + return -1; + } + sent += (size_t)n; + } + return 0; +} + +static int send_json_line(MultiplayerApi *api, json_t *obj) { + if (!api || api->sockfd < 0 || !obj) return MP_API_ERR_ARGUMENT; + + char *text = json_dumps(obj, JSON_COMPACT); + if (!text) { + json_decref(obj); + return MP_API_ERR_IO; + } + + size_t len = strlen(text); + int fd = api->sockfd; + + int rc = 0; + if (send_all(fd, text, len) != 0 || send_all(fd, "\n", 1) != 0) { + rc = MP_API_ERR_IO; + } + + free(text); + json_decref(obj); + + return rc; +} + +static int read_line(int fd, char **out_line) { + if (!out_line) return MP_API_ERR_ARGUMENT; + + size_t cap = 256; + size_t len = 0; + char *buf = (char *)malloc(cap); + if (!buf) return MP_API_ERR_IO; + + for (;;) { + char c; + ssize_t n = recv(fd, &c, 1, 0); + if (n < 0) { + if (errno == EINTR) continue; + free(buf); + return MP_API_ERR_IO; + } + if (n == 0) { + free(buf); + return MP_API_ERR_IO; + } + + if (c == '\n') { + break; + } + + if (len + 1 >= cap) { + cap *= 2; + char *tmp = (char *)realloc(buf, cap); + if (!tmp) { + free(buf); + return MP_API_ERR_IO; + } + buf = tmp; + } + + buf[len++] = c; + } + + buf[len] = '\0'; + *out_line = buf; + return MP_API_OK; +} + +static void process_line(MultiplayerApi *api, const char *line) { + if (!api || !line || !*line) return; + + json_error_t jerr; + json_t *root = json_loads(line, 0, &jerr); + if (!root || !json_is_object(root)) { + if (root) json_decref(root); + return; + } + + json_t *cmd_val = json_object_get(root, "cmd"); + if (!json_is_string(cmd_val)) { + json_decref(root); + return; + } + + const char *cmd = json_string_value(cmd_val); + if (!cmd) { + json_decref(root); + return; + } + + if (strcmp(cmd, "joined") != 0 && + strcmp(cmd, "leaved") != 0 && + strcmp(cmd, "game") != 0) { + json_decref(root); + return; + } + + json_int_t msgId = 0; + json_t *mid_val = json_object_get(root, "messageId"); + if (json_is_integer(mid_val)) { + msgId = json_integer_value(mid_val); + } + + const char *clientId = NULL; + json_t *cid_val = json_object_get(root, "clientId"); + if (json_is_string(cid_val)) { + clientId = json_string_value(cid_val); + } + + json_t *data_val = json_object_get(root, "data"); + json_t *data_obj; + if (json_is_object(data_val)) { + data_obj = data_val; + json_incref(data_obj); + } else { + data_obj = json_object(); + } + + pthread_mutex_lock(&api->lock); + int count = 0; + ListenerNode *node = api->listeners; + while (node) { + if (node->cb) count++; + node = node->next; + } + + if (count == 0) { + pthread_mutex_unlock(&api->lock); + json_decref(data_obj); + json_decref(root); + return; + } + + ListenerSnapshot *snapshot = (ListenerSnapshot *)malloc(sizeof(ListenerSnapshot) * count); + if (!snapshot) { + pthread_mutex_unlock(&api->lock); + json_decref(data_obj); + json_decref(root); + return; + } + + int idx = 0; + node = api->listeners; + while (node) { + if (node->cb) { + snapshot[idx].cb = node->cb; + snapshot[idx].user_data = node->user_data; + idx++; + } + node = node->next; + } + pthread_mutex_unlock(&api->lock); + + for (int i = 0; i < count; ++i) { + snapshot[i].cb(cmd, (int64_t)msgId, clientId, data_obj, snapshot[i].user_data); + } + + free(snapshot); + json_decref(data_obj); + json_decref(root); +} + +static void *recv_thread_main(void *arg) { + MultiplayerApi *api = (MultiplayerApi *)arg; + char buffer[1024]; + char *acc = NULL; + size_t acc_len = 0; + size_t acc_cap = 0; + + while (1) { + ssize_t n = recv(api->sockfd, buffer, sizeof(buffer), 0); + if (n <= 0) { + break; + } + + for (ssize_t i = 0; i < n; ++i) { + char ch = buffer[i]; + if (ch == '\n') { + if (acc_len > 0) { + char *line = (char *)malloc(acc_len + 1); + if (!line) { + acc_len = 0; + continue; + } + memcpy(line, acc, acc_len); + line[acc_len] = '\0'; + + acc_len = 0; + process_line(api, line); + free(line); + } else { + acc_len = 0; + } + } else { + if (acc_len + 1 >= acc_cap) { + size_t new_cap = acc_cap == 0 ? 256 : acc_cap * 2; + char *tmp = (char *)realloc(acc, new_cap); + if (!tmp) { + free(acc); + acc = NULL; + acc_len = 0; + acc_cap = 0; + break; + } + acc = tmp; + acc_cap = new_cap; + } + acc[acc_len++] = ch; + } + } + } + + if (acc) { + free(acc); + } + + return NULL; +} + +static int start_recv_thread(MultiplayerApi *api) { + if (!api) return MP_API_ERR_ARGUMENT; + if (api->recv_thread_started) { + return MP_API_OK; + } + + api->running = 1; + int rc = pthread_create(&api->recv_thread, NULL, recv_thread_main, api); + if (rc != 0) { + api->running = 0; + return MP_API_ERR_IO; + } + + api->recv_thread_started = 1; + return MP_API_OK; +} diff --git a/src/MultiplayerApi.h b/src/MultiplayerApi.h new file mode 100644 index 0000000..7b20665 --- /dev/null +++ b/src/MultiplayerApi.h @@ -0,0 +1,88 @@ +#ifndef MULTIPLAYER_API_H +#define MULTIPLAYER_API_H + +#include +#include "jansson/jansson.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MultiplayerApi MultiplayerApi; + +/* Callback‑typ för inkommande events från servern. */ +typedef void (*MultiplayerListener)( + const char *event, /* "joined", "leaved", "game" */ + int64_t messageId, /* sekventiellt meddelande‑ID (från host) */ + const char *clientId, /* avsändarens klient‑ID (eller NULL) */ + json_t *data, /* JSON‑objekt med godtycklig speldata */ + void *user_data /* godtycklig pekare som skickas vidare */ +); + +/* Returkoder */ +enum { + MP_API_OK = 0, + MP_API_ERR_ARGUMENT = 1, + MP_API_ERR_STATE = 2, + MP_API_ERR_CONNECT = 3, + MP_API_ERR_PROTOCOL = 4, + MP_API_ERR_IO = 5, + MP_API_ERR_REJECTED = 6 /* t.ex. ogiltigt sessions‑ID vid join */ +}; + +/* Skapar en ny API‑instans. Returnerar NULL vid fel. */ +MultiplayerApi *mp_api_create(const char *server_host, uint16_t server_port); + +/* Stänger ner anslutning, stoppar mottagartråd och frigör minne. */ +void mp_api_destroy(MultiplayerApi *api); + +/* Hostar en ny session. Blockerar tills svar erhållits eller fel uppstår. + out_session / out_clientId pekar på nyallokerade strängar (malloc) som + anroparen ansvarar för att free:a. out_data (om ej NULL) får ett json_t* + med extra data från servern (anroparen ska json_decref när klart). */ +int mp_api_host(MultiplayerApi *api, + char **out_session, + char **out_clientId, + json_t **out_data); + +/* + Hämtar en lista över tillgängliga publika sessioner. + Returnerar MP_API_OK vid framgång, annan felkod vid fel. + Anroparen ansvarar för att json_decref:a out_list när klar. */ +int mp_api_list(MultiplayerApi *api, + json_t **out_list); + +/* Går med i befintlig session. + sessionId: sessionskod (t.ex. "ABC123"). + data: valfri JSON‑payload med spelarinformation (kan vara NULL). + out_* fungerar som i mp_api_host. + + Returnerar: + - MP_API_OK vid lyckad join + - MP_API_ERR_REJECTED om servern svarar med status:error (t.ex. ogiltigt ID) + - annan felkod vid nätverks/protokoll‑fel. +*/ +int mp_api_join(MultiplayerApi *api, + const char *sessionId, + json_t *data, + char **out_session, + char **out_clientId, + json_t **out_data); + +/* Skickar ett "game"‑meddelande med godtycklig JSON‑data till sessionen. */ +int mp_api_game(MultiplayerApi *api, json_t *data); + +/* Registrerar en lyssnare för inkommande events. + Returnerar ett positivt listener‑ID, eller −1 vid fel. */ +int mp_api_listen(MultiplayerApi *api, + MultiplayerListener cb, + void *user_data); + +/* Avregistrerar lyssnare. Listener‑ID är värdet från mp_api_listen. */ +void mp_api_unlisten(MultiplayerApi *api, int listener_id); + +#ifdef __cplusplus +} +#endif + +#endif /* MULTIPLAYER_API_H */ diff --git a/src/assets/sprites/spr_floor_grass.png b/src/assets/sprites/spr_floor_grass.png new file mode 100644 index 0000000000000000000000000000000000000000..acd7858c9b7af985a4bc71148d8524b87617c261 GIT binary patch literal 643 zcmV-}0(||6P)EX>4Tx04R}tkv&MmKpe$iQ>7}E4t5Z6$WWauNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRK2d-P^xs+Wq|ibwYBeGi*M@00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=?n@EFE)VcZE*kq02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{003J_L_t(I%VVtAJn28f00Jg*HD0;%fh;+k4nWpK$bc(% zJ}~I02;h{*Cx_DkIw}H0yBwDVr1%_b6u=B7*8);Jfin$|;|p?wAFBh%jazaZ&P1*3 dM5@nW004cnBg{bVy~O|k002ovPDHLkV1hd)4mJP) literal 0 HcmV?d00001 diff --git a/src/assets/sprites/spr_food_apple.png b/src/assets/sprites/spr_food_apple.png new file mode 100644 index 0000000000000000000000000000000000000000..0f297071000f550d59f89c69c86d2dcf99b0db6c GIT binary patch literal 653 zcmV;80&@L{P)EX>4Tx04R}tkv&MmKpe$iQ>7}E4t5Z6$WWauNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRK2d-P^xs+Wq|ibwYBeGi*M@00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=?n@F4H^_eErkF802y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{003o4L_t&-(_>(Of{M+P{`>Sd{bxV}OfZIf@qPvd1_p*8 zujK#x)dJxP85tND81}0L{x{S7$8hR86N+xfTk9E_7#J8But}m=flCs_4d~#~cNT;K n$6MEX>4Tx04R}tkv&MmKpe$iQ>7}E4t5Z6$WWauNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRK2d-P^xs+Wq|ibwYBeGi*M@00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=?n@F4G{E~#G3#B02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001aSL_t&-(_>&D02pD6|NsC0N0DG;WP~d~mqb>?M92!t e6%ceJK>z?#m<$TGAQ}t+0000EX>4Tx04R}tkv&MmKpe$iQ>7}E4t5Z6$WWauNELCEDi*;)X)CnqU~=gfG-*gu zTpR`0f`cE6RRK2d-P^xs+Wq|ibwYBeGi*M@00006VoOIv0RI600RN!9r;`8x010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=?n@F4Jh1|d<6gi02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{001>fL_t&-(~XZo4geqw!ixX@GhVi22T^;aBp?dgW*|ub rI)zixCB!z1K=-21M4>i*jC|ePY7rU;xXr!A00000NkvXXu0mjf`DXmh literal 0 HcmV?d00001 diff --git a/src/jansson/dump.c b/src/jansson/dump.c new file mode 100644 index 0000000..e1b6bfa --- /dev/null +++ b/src/jansson/dump.c @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "jansson_private.h" + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "jansson.h" +#include "strbuffer.h" +#include "utf.h" + +#define MAX_INTEGER_STR_LENGTH 100 +#define MAX_REAL_STR_LENGTH 100 + +#define FLAGS_TO_INDENT(f) ((f) & 0x1F) +#define FLAGS_TO_PRECISION(f) (((f) >> 11) & 0x1F) + +struct buffer { + const size_t size; + size_t used; + char *data; +}; + +static int dump_to_strbuffer(const char *buffer, size_t size, void *data) +{ + return strbuffer_append_bytes((strbuffer_t *)data, buffer, size); +} + +static int dump_to_buffer(const char *buffer, size_t size, void *data) +{ + struct buffer *buf = (struct buffer *)data; + + if(buf->used + size <= buf->size) + memcpy(&buf->data[buf->used], buffer, size); + + buf->used += size; + return 0; +} + +static int dump_to_file(const char *buffer, size_t size, void *data) +{ + FILE *dest = (FILE *)data; + if(fwrite(buffer, size, 1, dest) != 1) + return -1; + return 0; +} + +static int dump_to_fd(const char *buffer, size_t size, void *data) +{ + (void)buffer; + (void)size; + (void)data; +#ifdef HAVE_UNISTD_H + int *dest = (int *)data; + if(write(*dest, buffer, size) == (ssize_t)size) + return 0; +#endif + return -1; +} + +/* 32 spaces (the maximum indentation size) */ +#ifdef JSON_USE_TAB_INDENT + static const char JSON_ws[] = " "; +#else + static const char JSON_ws[] = " "; +#endif + +static int dump_indent(size_t flags, int depth, int space, json_dump_callback_t dump, void *data) +{ + if(FLAGS_TO_INDENT(flags) > 0) + { + unsigned int ws_count = FLAGS_TO_INDENT(flags), n_spaces = depth * ws_count; + + if(dump("\n", 1, data)) + return -1; + + while(n_spaces > 0) + { + int cur_n = n_spaces < sizeof JSON_ws - 1 ? n_spaces : sizeof JSON_ws - 1; + + if(dump(JSON_ws, cur_n, data)) + return -1; + + n_spaces -= cur_n; + } + } + else if(space && !(flags & JSON_COMPACT)) + { + return dump(" ", 1, data); + } + return 0; +} + +static int dump_string(const char *str, size_t len, json_dump_callback_t dump, void *data, size_t flags) +{ + const char *pos, *end, *lim; + int32_t codepoint = 0; + + if(dump("\"", 1, data)) + return -1; + + end = pos = str; + lim = str + len; + while(1) + { + const char *text; + char seq[13]; + int length; + + while(end < lim) + { + end = utf8_iterate(pos, lim - pos, &codepoint); + if(!end) + return -1; + + /* mandatory escape or control char */ + if(codepoint == '\\' || codepoint == '"' || codepoint < 0x20) + break; + + /* slash */ + if((flags & JSON_ESCAPE_SLASH) && codepoint == '/') + break; + + /* non-ASCII */ + if((flags & JSON_ENSURE_ASCII) && codepoint > 0x7F) + break; + + pos = end; + } + + if(pos != str) { + if(dump(str, pos - str, data)) + return -1; + } + + if(end == pos) + break; + + /* handle \, /, ", and control codes */ + length = 2; + switch(codepoint) + { + case '\\': text = "\\\\"; break; + case '\"': text = "\\\""; break; + case '\b': text = "\\b"; break; + case '\f': text = "\\f"; break; + case '\n': text = "\\n"; break; + case '\r': text = "\\r"; break; + case '\t': text = "\\t"; break; + case '/': text = "\\/"; break; + default: + { + /* codepoint is in BMP */ + if(codepoint < 0x10000) + { + snprintf(seq, sizeof(seq), "\\u%04X", (unsigned int)codepoint); + length = 6; + } + + /* not in BMP -> construct a UTF-16 surrogate pair */ + else + { + int32_t first, last; + + codepoint -= 0x10000; + first = 0xD800 | ((codepoint & 0xffc00) >> 10); + last = 0xDC00 | (codepoint & 0x003ff); + + snprintf(seq, sizeof(seq), "\\u%04X\\u%04X", (unsigned int)first, (unsigned int)last); + length = 12; + } + + text = seq; + break; + } + } + + if(dump(text, length, data)) + return -1; + + str = pos = end; + } + + return dump("\"", 1, data); +} + +static int compare_keys(const void *key1, const void *key2) +{ + return strcmp(*(const char **)key1, *(const char **)key2); +} + +static int loop_check(hashtable_t *parents, const json_t *json, char *key, size_t key_size) +{ + snprintf(key, key_size, "%p", json); + if (hashtable_get(parents, key)) + return -1; + + return hashtable_set(parents, key, json_null()); +} + +static int do_dump(const json_t *json, size_t flags, int depth, + hashtable_t *parents, json_dump_callback_t dump, void *data) +{ + int embed = flags & JSON_EMBED; + + flags &= ~JSON_EMBED; + + if(!json) + return -1; + + switch(json_typeof(json)) { + case JSON_NULL: + return dump("null", 4, data); + + case JSON_TRUE: + return dump("true", 4, data); + + case JSON_FALSE: + return dump("false", 5, data); + + case JSON_INTEGER: + { + char buffer[MAX_INTEGER_STR_LENGTH]; + int size; + + /*size = snprintf(buffer, MAX_INTEGER_STR_LENGTH, + "%" JSON_INTEGER_FORMAT, + json_integer_value(json)); + */ + + size = snprintf(buffer, MAX_INTEGER_STR_LENGTH, "%" JSON_INTEGER_FORMAT, json_integer_value(json)); + + if(size < 0 || size >= MAX_INTEGER_STR_LENGTH) + return -1; + + return dump(buffer, size, data); + } + + case JSON_REAL: + { + char buffer[MAX_REAL_STR_LENGTH]; + int size; + double value = json_real_value(json); + + size = jsonp_dtostr(buffer, MAX_REAL_STR_LENGTH, value, + FLAGS_TO_PRECISION(flags)); + if(size < 0) + return -1; + + return dump(buffer, size, data); + } + + case JSON_STRING: + return dump_string(json_string_value(json), json_string_length(json), dump, data, flags); + + case JSON_ARRAY: + { + size_t n; + size_t i; + /* Space for "0x", double the sizeof a pointer for the hex and a terminator. */ + char key[2 + (sizeof(json) * 2) + 1]; + + /* detect circular references */ + if (loop_check(parents, json, key, sizeof(key))) + return -1; + + n = json_array_size(json); + + if(!embed && dump("[", 1, data)) + return -1; + if(n == 0) { + hashtable_del(parents, key); + return embed ? 0 : dump("]", 1, data); + } + if(dump_indent(flags, depth + 1, 0, dump, data)) + return -1; + + for(i = 0; i < n; ++i) { + if(do_dump(json_array_get(json, i), flags, depth + 1, + parents, dump, data)) + return -1; + + if(i < n - 1) + { + if(dump(",", 1, data) || + dump_indent(flags, depth + 1, 1, dump, data)) + return -1; + } + else + { + if(dump_indent(flags, depth, 0, dump, data)) + return -1; + } + } + + hashtable_del(parents, key); + return embed ? 0 : dump("]", 1, data); + } + + case JSON_OBJECT: + { + void *iter; + const char *separator; + int separator_length; + /* Space for "0x", double the sizeof a pointer for the hex and a terminator. */ + char loop_key[2 + (sizeof(json) * 2) + 1]; + + if(flags & JSON_COMPACT) { + separator = ":"; + separator_length = 1; + } + else { + separator = ": "; + separator_length = 2; + } + + /* detect circular references */ + if (loop_check(parents, json, loop_key, sizeof(loop_key))) + return -1; + + iter = json_object_iter((json_t *)json); + + if(!embed && dump("{", 1, data)) + return -1; + if(!iter) { + hashtable_del(parents, loop_key); + return embed ? 0 : dump("}", 1, data); + } + if(dump_indent(flags, depth + 1, 0, dump, data)) + return -1; + + if(flags & JSON_SORT_KEYS) + { + const char **keys; + size_t size, i; + + size = json_object_size(json); + keys = (const char**)jsonp_malloc(size * sizeof(const char *)); + if(!keys) + return -1; + + i = 0; + while(iter) + { + keys[i] = json_object_iter_key(iter); + iter = json_object_iter_next((json_t *)json, iter); + i++; + } + assert(i == size); + + qsort(keys, size, sizeof(const char *), compare_keys); + + for(i = 0; i < size; i++) + { + const char *key; + json_t *value; + + key = keys[i]; + value = json_object_get(json, key); + assert(value); + + dump_string(key, strlen(key), dump, data, flags); + if(dump(separator, separator_length, data) || + do_dump(value, flags, depth + 1, parents, dump, data)) + { + jsonp_free(keys); + return -1; + } + + if(i < size - 1) + { + if(dump(",", 1, data) || + dump_indent(flags, depth + 1, 1, dump, data)) + { + jsonp_free(keys); + return -1; + } + } + else + { + if(dump_indent(flags, depth, 0, dump, data)) + { + jsonp_free(keys); + return -1; + } + } + } + + jsonp_free(keys); + } + else + { + /* Don't sort keys */ + + while(iter) + { + void *next = json_object_iter_next((json_t *)json, iter); + const char *key = json_object_iter_key(iter); + + dump_string(key, strlen(key), dump, data, flags); + if(dump(separator, separator_length, data) || + do_dump(json_object_iter_value(iter), flags, depth + 1, + parents, dump, data)) + return -1; + + if(next) + { + if(dump(",", 1, data) || + dump_indent(flags, depth + 1, 1, dump, data)) + return -1; + } + else + { + if(dump_indent(flags, depth, 0, dump, data)) + return -1; + } + + iter = next; + } + } + + hashtable_del(parents, loop_key); + return embed ? 0 : dump("}", 1, data); + } + + default: + /* not reached */ + return -1; + } +} + +char *json_dumps(const json_t *json, size_t flags) +{ + strbuffer_t strbuff; + char *result; + + if(strbuffer_init(&strbuff)) + return NULL; + + if(json_dump_callback(json, dump_to_strbuffer, (void *)&strbuff, flags)) + result = NULL; + else + result = jsonp_strdup(strbuffer_value(&strbuff)); + + strbuffer_close(&strbuff); + return result; +} + +size_t json_dumpb(const json_t *json, char *buffer, size_t size, size_t flags) +{ + struct buffer buf = { size, 0, buffer }; + + if(json_dump_callback(json, dump_to_buffer, (void *)&buf, flags)) + return 0; + + return buf.used; +} + +int json_dumpf(const json_t *json, FILE *output, size_t flags) +{ + return json_dump_callback(json, dump_to_file, (void *)output, flags); +} + +int json_dumpfd(const json_t *json, int output, size_t flags) +{ + return json_dump_callback(json, dump_to_fd, (void *)&output, flags); +} + +int json_dump_file(const json_t *json, const char *path, size_t flags) +{ + int result; + + FILE *output = fopen(path, "w"); + if(!output) + return -1; + + result = json_dumpf(json, output, flags); + if(fclose(output) != 0) + return -2; + + return result; +} + +int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags) +{ + int res; + hashtable_t parents_set; + + if(!(flags & JSON_ENCODE_ANY)) { + if(!json_is_array(json) && !json_is_object(json)) + return -1; + } + + if (hashtable_init(&parents_set)) + return -2; + + res = do_dump(json, flags, 0, &parents_set, callback, data); + + hashtable_close(&parents_set); + + return res; +} diff --git a/src/jansson/error.c b/src/jansson/error.c new file mode 100644 index 0000000..27f4e66 --- /dev/null +++ b/src/jansson/error.c @@ -0,0 +1,71 @@ +#include +#include "jansson_private.h" + +void jsonp_error_init(json_error_t *error, const char *source) +{ + if(error) + { + error->text[0] = '\0'; + error->line = -1; + error->column = -1; + error->position = 0; + error->code = 0; + if(source) + jsonp_error_set_source(error, source); + else + error->source[0] = '\0'; + } +} + +void jsonp_error_set_source(json_error_t *error, const char *source) +{ + size_t length; + + if(!error || !source) + return; + + length = strlen(source); + if(length < JSON_ERROR_SOURCE_LENGTH) + { + //strncpy(error->source, source, length + 1); + } + else + { + size_t extra = length - JSON_ERROR_SOURCE_LENGTH + 4; + memcpy(error->source, "...", 3); + strncpy(error->source + 3, source + extra, length - extra + 1); + } +} + +void jsonp_error_set(json_error_t *error, int line, int column, + size_t position, enum json_error_code code, + const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + jsonp_error_vset(error, line, column, position, code, msg, ap); + va_end(ap); +} + +void jsonp_error_vset(json_error_t *error, int line, int column, + size_t position, enum json_error_code code, + const char *msg, va_list ap) +{ + if(!error) + return; + + if(error->text[0] != '\0') { + /* error already set */ + return; + } + + error->line = line; + error->column = column; + error->position = (int)position; + + vsnprintf(error->text, JSON_ERROR_TEXT_LENGTH - 1, msg, ap); + error->text[JSON_ERROR_TEXT_LENGTH - 2] = '\0'; + error->text[JSON_ERROR_TEXT_LENGTH - 1] = code; + error->code = code; +} diff --git a/src/jansson/hashtable.c b/src/jansson/hashtable.c new file mode 100644 index 0000000..58e8421 --- /dev/null +++ b/src/jansson/hashtable.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include "jansson_config.h" + +#include +#include + +#include + +#include "jansson_config.h" /* for JSON_INLINE */ +#include "jansson_private.h" /* for container_of() */ +#include "hashtable.h" + +#ifndef INITIAL_HASHTABLE_ORDER +#define INITIAL_HASHTABLE_ORDER 3 +#endif + +typedef struct hashtable_list list_t; +typedef struct hashtable_pair pair_t; +typedef struct hashtable_bucket bucket_t; + +extern volatile uint32_t hashtable_seed; + +/* Implementation of the hash function */ +#include "lookup3.h" + +#define list_to_pair(list_) container_of(list_, pair_t, list) +#define ordered_list_to_pair(list_) container_of(list_, pair_t, ordered_list) +#define hash_str(key) ((size_t)hashlittle((key), strlen(key), hashtable_seed)) + +JSON_INLINE void list_init(list_t *list) +{ + list->next = list; + list->prev = list; +} + +JSON_INLINE void list_insert(list_t *list, list_t *node) +{ + node->next = list; + node->prev = list->prev; + list->prev->next = node; + list->prev = node; +} + +JSON_INLINE void list_remove(list_t *list) +{ + list->prev->next = list->next; + list->next->prev = list->prev; +} + +JSON_INLINE int bucket_is_empty(hashtable_t *hashtable, bucket_t *bucket) +{ + return bucket->first == &hashtable->list && bucket->first == bucket->last; +} + +static void insert_to_bucket(hashtable_t *hashtable, bucket_t *bucket, + list_t *list) +{ + if(bucket_is_empty(hashtable, bucket)) + { + list_insert(&hashtable->list, list); + bucket->first = bucket->last = list; + } + else + { + list_insert(bucket->first, list); + bucket->first = list; + } +} + +static pair_t *hashtable_find_pair(hashtable_t *hashtable, bucket_t *bucket, + const char *key, size_t hash) +{ + list_t *list; + pair_t *pair; + + if(bucket_is_empty(hashtable, bucket)) + return NULL; + + list = bucket->first; + while(1) + { + pair = list_to_pair(list); + if(pair->hash == hash && strcmp(pair->key, key) == 0) + return pair; + + if(list == bucket->last) + break; + + list = list->next; + } + + return NULL; +} + +/* returns 0 on success, -1 if key was not found */ +static int hashtable_do_del(hashtable_t *hashtable, + const char *key, size_t hash) +{ + pair_t *pair; + bucket_t *bucket; + size_t index; + + index = hash & hashmask(hashtable->order); + bucket = &hashtable->buckets[index]; + + pair = hashtable_find_pair(hashtable, bucket, key, hash); + if(!pair) + return -1; + + if(&pair->list == bucket->first && &pair->list == bucket->last) + bucket->first = bucket->last = &hashtable->list; + + else if(&pair->list == bucket->first) + bucket->first = pair->list.next; + + else if(&pair->list == bucket->last) + bucket->last = pair->list.prev; + + list_remove(&pair->list); + list_remove(&pair->ordered_list); + json_decref(pair->value); + + jsonp_free(pair); + hashtable->size--; + + return 0; +} + +static void hashtable_do_clear(hashtable_t *hashtable) +{ + list_t *list, *next; + pair_t *pair; + + for(list = hashtable->list.next; list != &hashtable->list; list = next) + { + next = list->next; + pair = list_to_pair(list); + json_decref(pair->value); + jsonp_free(pair); + } +} + +static int hashtable_do_rehash(hashtable_t *hashtable) +{ + list_t *list, *next; + pair_t *pair; + size_t i, index, new_size, new_order; + struct hashtable_bucket* new_buckets; + + new_order = hashtable->order + 1; + new_size = hashsize(new_order); + + new_buckets = (struct hashtable_bucket*)jsonp_malloc(new_size * sizeof(bucket_t)); + if(!new_buckets) + return -1; + + jsonp_free(hashtable->buckets); + hashtable->buckets = new_buckets; + hashtable->order = new_order; + + for(i = 0; i < hashsize(hashtable->order); i++) + { + hashtable->buckets[i].first = hashtable->buckets[i].last = + &hashtable->list; + } + + list = hashtable->list.next; + list_init(&hashtable->list); + + for(; list != &hashtable->list; list = next) { + next = list->next; + pair = list_to_pair(list); + index = pair->hash % new_size; + insert_to_bucket(hashtable, &hashtable->buckets[index], &pair->list); + } + + return 0; +} + + +int hashtable_init(hashtable_t *hashtable) +{ + size_t i; + + hashtable->size = 0; + hashtable->order = INITIAL_HASHTABLE_ORDER; + hashtable->buckets = (struct hashtable_bucket*)jsonp_malloc(hashsize(hashtable->order) * sizeof(bucket_t)); + if(!hashtable->buckets) + return -1; + + list_init(&hashtable->list); + list_init(&hashtable->ordered_list); + + for(i = 0; i < hashsize(hashtable->order); i++) + { + hashtable->buckets[i].first = hashtable->buckets[i].last = + &hashtable->list; + } + + return 0; +} + +void hashtable_close(hashtable_t *hashtable) +{ + hashtable_do_clear(hashtable); + jsonp_free(hashtable->buckets); +} + +int hashtable_set(hashtable_t *hashtable, const char *key, json_t *value) +{ + pair_t* pair; + bucket_t* bucket; + size_t hash, index; + + /* rehash if the load ratio exceeds 1 */ + if(hashtable->size >= hashsize(hashtable->order)) + if(hashtable_do_rehash(hashtable)) + return -1; + + hash = hash_str(key); + index = hash & hashmask(hashtable->order); + bucket = &hashtable->buckets[index]; + pair = hashtable_find_pair(hashtable, bucket, key, hash); + + if(pair) + { + json_decref(pair->value); + pair->value = value; + } + else + { + /* offsetof(...) returns the size of pair_t without the last, + flexible member. This way, the correct amount is + allocated. */ + + size_t len = strlen(key); + if(len >= (size_t)-1 - offsetof(pair_t, key)) { + /* Avoid an overflow if the key is very long */ + return -1; + } + + pair = (pair_t*)jsonp_malloc(offsetof(pair_t, key) + len + 1); + if(!pair) + return -1; + + pair->hash = hash; + strncpy(pair->key, key, len + 1); + pair->value = value; + list_init(&pair->list); + list_init(&pair->ordered_list); + + insert_to_bucket(hashtable, bucket, &pair->list); + list_insert(&hashtable->ordered_list, &pair->ordered_list); + + hashtable->size++; + } + return 0; +} + +void *hashtable_get(hashtable_t *hashtable, const char *key) +{ + pair_t *pair; + size_t hash; + bucket_t *bucket; + + hash = hash_str(key); + bucket = &hashtable->buckets[hash & hashmask(hashtable->order)]; + + pair = hashtable_find_pair(hashtable, bucket, key, hash); + if(!pair) + return NULL; + + return pair->value; +} + +int hashtable_del(hashtable_t *hashtable, const char *key) +{ + size_t hash = hash_str(key); + return hashtable_do_del(hashtable, key, hash); +} + +void hashtable_clear(hashtable_t *hashtable) +{ + size_t i; + + hashtable_do_clear(hashtable); + + for(i = 0; i < hashsize(hashtable->order); i++) + { + hashtable->buckets[i].first = hashtable->buckets[i].last = + &hashtable->list; + } + + list_init(&hashtable->list); + list_init(&hashtable->ordered_list); + hashtable->size = 0; +} + +void *hashtable_iter(hashtable_t *hashtable) +{ + return hashtable_iter_next(hashtable, &hashtable->ordered_list); +} + +void *hashtable_iter_at(hashtable_t *hashtable, const char *key) +{ + pair_t *pair; + size_t hash; + bucket_t *bucket; + + hash = hash_str(key); + bucket = &hashtable->buckets[hash & hashmask(hashtable->order)]; + + pair = hashtable_find_pair(hashtable, bucket, key, hash); + if(!pair) + return NULL; + + return &pair->ordered_list; +} + +void *hashtable_iter_next(hashtable_t *hashtable, void *iter) +{ + list_t *list = (list_t *)iter; + if(list->next == &hashtable->ordered_list) + return NULL; + return list->next; +} + +void *hashtable_iter_key(void *iter) +{ + pair_t *pair = ordered_list_to_pair((list_t *)iter); + return pair->key; +} + +void *hashtable_iter_value(void *iter) +{ + pair_t *pair = ordered_list_to_pair((list_t *)iter); + return pair->value; +} + +void hashtable_iter_set(void *iter, json_t *value) +{ + pair_t *pair = ordered_list_to_pair((list_t *)iter); + + json_decref(pair->value); + pair->value = value; +} diff --git a/src/jansson/hashtable.h b/src/jansson/hashtable.h new file mode 100644 index 0000000..5524065 --- /dev/null +++ b/src/jansson/hashtable.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef HASHTABLE_H +#define HASHTABLE_H + +#include +#include "jansson.h" + +struct hashtable_list { + struct hashtable_list *prev; + struct hashtable_list *next; +}; + +/* "pair" may be a bit confusing a name, but think of it as a + key-value pair. In this case, it just encodes some extra data, + too */ +struct hashtable_pair { + struct hashtable_list list; + struct hashtable_list ordered_list; + size_t hash; + json_t *value; + char key[1]; +}; + +struct hashtable_bucket { + struct hashtable_list *first; + struct hashtable_list *last; +}; + +typedef struct hashtable { + size_t size; + struct hashtable_bucket *buckets; + size_t order; /* hashtable has pow(2, order) buckets */ + struct hashtable_list list; + struct hashtable_list ordered_list; +} hashtable_t; + + +#define hashtable_key_to_iter(key_) \ + (&(container_of(key_, struct hashtable_pair, key)->ordered_list)) + + +/** + * hashtable_init - Initialize a hashtable object + * + * @hashtable: The (statically allocated) hashtable object + * + * Initializes a statically allocated hashtable object. The object + * should be cleared with hashtable_close when it's no longer used. + * + * Returns 0 on success, -1 on error (out of memory). + */ +int hashtable_init(hashtable_t *hashtable) JSON_ATTRS(warn_unused_result); + +/** + * hashtable_close - Release all resources used by a hashtable object + * + * @hashtable: The hashtable + * + * Destroys a statically allocated hashtable object. + */ +void hashtable_close(hashtable_t *hashtable); + +/** + * hashtable_set - Add/modify value in hashtable + * + * @hashtable: The hashtable object + * @key: The key + * @serial: For addition order of keys + * @value: The value + * + * If a value with the given key already exists, its value is replaced + * with the new value. Value is "stealed" in the sense that hashtable + * doesn't increment its refcount but decreases the refcount when the + * value is no longer needed. + * + * Returns 0 on success, -1 on failure (out of memory). + */ +int hashtable_set(hashtable_t *hashtable, const char *key, json_t *value); + +/** + * hashtable_get - Get a value associated with a key + * + * @hashtable: The hashtable object + * @key: The key + * + * Returns value if it is found, or NULL otherwise. + */ +void *hashtable_get(hashtable_t *hashtable, const char *key); + +/** + * hashtable_del - Remove a value from the hashtable + * + * @hashtable: The hashtable object + * @key: The key + * + * Returns 0 on success, or -1 if the key was not found. + */ +int hashtable_del(hashtable_t *hashtable, const char *key); + +/** + * hashtable_clear - Clear hashtable + * + * @hashtable: The hashtable object + * + * Removes all items from the hashtable. + */ +void hashtable_clear(hashtable_t *hashtable); + +/** + * hashtable_iter - Iterate over hashtable + * + * @hashtable: The hashtable object + * + * Returns an opaque iterator to the first element in the hashtable. + * The iterator should be passed to hashtable_iter_* functions. + * The hashtable items are not iterated over in any particular order. + * + * There's no need to free the iterator in any way. The iterator is + * valid as long as the item that is referenced by the iterator is not + * deleted. Other values may be added or deleted. In particular, + * hashtable_iter_next() may be called on an iterator, and after that + * the key/value pair pointed by the old iterator may be deleted. + */ +void *hashtable_iter(hashtable_t *hashtable); + +/** + * hashtable_iter_at - Return an iterator at a specific key + * + * @hashtable: The hashtable object + * @key: The key that the iterator should point to + * + * Like hashtable_iter() but returns an iterator pointing to a + * specific key. + */ +void *hashtable_iter_at(hashtable_t *hashtable, const char *key); + +/** + * hashtable_iter_next - Advance an iterator + * + * @hashtable: The hashtable object + * @iter: The iterator + * + * Returns a new iterator pointing to the next element in the + * hashtable or NULL if the whole hastable has been iterated over. + */ +void *hashtable_iter_next(hashtable_t *hashtable, void *iter); + +/** + * hashtable_iter_key - Retrieve the key pointed by an iterator + * + * @iter: The iterator + */ +void *hashtable_iter_key(void *iter); + +/** + * hashtable_iter_value - Retrieve the value pointed by an iterator + * + * @iter: The iterator + */ +void *hashtable_iter_value(void *iter); + +/** + * hashtable_iter_set - Set the value pointed by an iterator + * + * @iter: The iterator + * @value: The value to set + */ +void hashtable_iter_set(void *iter, json_t *value); + +#endif diff --git a/src/jansson/hashtable_seed.c b/src/jansson/hashtable_seed.c new file mode 100644 index 0000000..153dd66 --- /dev/null +++ b/src/jansson/hashtable_seed.c @@ -0,0 +1,275 @@ +/* Generate sizeof(uint32_t) bytes of as random data as possible to seed + the hash function. +*/ + +#include "jansson_config.h" + +#include +#include + +#ifdef HAVE_STDINT_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef HAVE_SCHED_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#if defined(_WIN32) +/* For GetModuleHandle(), GetProcAddress() and GetCurrentProcessId() */ +#include +#endif + +#include "jansson.h" + + +inline uint32_t buf_to_uint32(char *data) { + size_t i; + uint32_t result = 0; + + for (i = 0; i < sizeof(uint32_t); i++) + result = (result << 8) | (unsigned char)data[i]; + + return result; +} + + + +/* /dev/urandom */ +#if !defined(_WIN32) && defined(USE_URANDOM) +int seed_from_urandom(uint32_t *seed) { + /* Use unbuffered I/O if we have open(), close() and read(). Otherwise + fall back to fopen() */ + + char data[sizeof(uint32_t)]; + int ok; + +#if defined(HAVE_OPEN) && defined(HAVE_CLOSE) && defined(HAVE_READ) + int urandom; + urandom = open("/dev/urandom", O_RDONLY); + if (urandom == -1) + return 1; + + ok = read(urandom, data, sizeof(uint32_t)) == sizeof(uint32_t); + close(urandom); +#else + FILE *urandom; + + urandom = fopen("/dev/urandom", "rb"); + if (!urandom) + return 1; + + ok = fread(data, 1, sizeof(uint32_t), urandom) == sizeof(uint32_t); + fclose(urandom); +#endif + + if (!ok) + return 1; + + *seed = buf_to_uint32(data); + return 0; +} +#endif + +/* Windows Crypto API */ +#if defined(_WIN32) && defined(USE_WINDOWS_CRYPTOAPI) +#include + +typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv, LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType, DWORD dwFlags); +typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen, BYTE *pbBuffer); +typedef BOOL (WINAPI *CRYPTRELEASECONTEXT)(HCRYPTPROV hProv, DWORD dwFlags); + +int seed_from_windows_cryptoapi(uint32_t *seed) +{ + HINSTANCE hAdvAPI32 = NULL; + CRYPTACQUIRECONTEXTA pCryptAcquireContext = NULL; + CRYPTGENRANDOM pCryptGenRandom = NULL; + CRYPTRELEASECONTEXT pCryptReleaseContext = NULL; + HCRYPTPROV hCryptProv = 0; + BYTE data[sizeof(uint32_t)]; + int ok; + + hAdvAPI32 = GetModuleHandle(TEXT("advapi32.dll")); + if(hAdvAPI32 == NULL) + return 1; + + pCryptAcquireContext = (CRYPTACQUIRECONTEXTA)GetProcAddress(hAdvAPI32, "CryptAcquireContextA"); + if (!pCryptAcquireContext) + return 1; + + pCryptGenRandom = (CRYPTGENRANDOM)GetProcAddress(hAdvAPI32, "CryptGenRandom"); + if (!pCryptGenRandom) + return 1; + + pCryptReleaseContext = (CRYPTRELEASECONTEXT)GetProcAddress(hAdvAPI32, "CryptReleaseContext"); + if (!pCryptReleaseContext) + return 1; + + if (!pCryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + return 1; + + ok = pCryptGenRandom(hCryptProv, sizeof(uint32_t), data); + pCryptReleaseContext(hCryptProv, 0); + + if (!ok) + return 1; + + *seed = buf_to_uint32((char *)data); + return 0; +} +#endif + +/* gettimeofday() and getpid() */ +int seed_from_timestamp_and_pid(uint32_t *seed) { +#ifdef HAVE_GETTIMEOFDAY + /* XOR of seconds and microseconds */ + struct timeval tv; + gettimeofday(&tv, NULL); + *seed = (uint32_t)tv.tv_sec ^ (uint32_t)tv.tv_usec; +#else + /* Seconds only */ + *seed = (uint32_t)time(NULL); +#endif + + /* XOR with PID for more randomness */ +#if defined(_WIN32) + *seed ^= (uint32_t)GetCurrentProcessId(); +#elif defined(HAVE_GETPID) + *seed ^= (uint32_t)getpid(); +#endif + + return 0; +} + +uint32_t generate_seed() { + uint32_t seed = 0; + int done = 0; + +#if !defined(_WIN32) && defined(USE_URANDOM) + if (seed_from_urandom(&seed) == 0) + done = 1; +#endif + +#if defined(_WIN32) && defined(USE_WINDOWS_CRYPTOAPI) + if (seed_from_windows_cryptoapi(&seed) == 0) + done = 1; +#endif + + if (!done) { + /* Fall back to timestamp and PID if no better randomness is + available */ + seed_from_timestamp_and_pid(&seed); + } + + /* Make sure the seed is never zero */ + if (seed == 0) + seed = 1; + + return seed; +} + + +volatile uint32_t hashtable_seed = 0; + +#if defined(HAVE_ATOMIC_BUILTINS) && (defined(HAVE_SCHED_YIELD) || !defined(_WIN32)) +volatile char seed_initialized = 0; + +void json_object_seed(size_t seed) { + uint32_t new_seed = (uint32_t)seed; + + if (hashtable_seed == 0) { + if (__atomic_test_and_set(&seed_initialized, __ATOMIC_RELAXED) == 0) { + /* Do the seeding ourselves */ + if (new_seed == 0) + new_seed = generate_seed(); + + __atomic_store_n(&hashtable_seed, new_seed, __ATOMIC_RELEASE); + } else { + /* Wait for another thread to do the seeding */ + do { +#ifdef HAVE_SCHED_YIELD + sched_yield(); +#endif + } while(__atomic_load_n(&hashtable_seed, __ATOMIC_ACQUIRE) == 0); + } + } +} +#elif defined(HAVE_SYNC_BUILTINS) && (defined(HAVE_SCHED_YIELD) || !defined(_WIN32)) +void json_object_seed(size_t seed) { + uint32_t new_seed = (uint32_t)seed; + + if (hashtable_seed == 0) { + if (new_seed == 0) { + /* Explicit synchronization fences are not supported by the + __sync builtins, so every thread getting here has to + generate the seed value. + */ + new_seed = generate_seed(); + } + + do { + if (__sync_bool_compare_and_swap(&hashtable_seed, 0, new_seed)) { + /* We were the first to seed */ + break; + } else { + /* Wait for another thread to do the seeding */ +#ifdef HAVE_SCHED_YIELD + sched_yield(); +#endif + } + } while(hashtable_seed == 0); + } +} +#elif defined(_WIN32) +long seed_initialized = 0; +void json_object_seed(size_t seed) { + uint32_t new_seed = (uint32_t)seed; + + if (hashtable_seed == 0) { + if (InterlockedIncrement(&seed_initialized) == 1) { + /* Do the seeding ourselves */ + if (new_seed == 0) + new_seed = generate_seed(); + + hashtable_seed = new_seed; + } else { + /* Wait for another thread to do the seeding */ + do { + SwitchToThread(); + } while (hashtable_seed == 0); + } + } +} +#else +/* Fall back to a thread-unsafe version */ +void json_object_seed(size_t seed) { + uint32_t new_seed = (uint32_t)seed; + + if (hashtable_seed == 0) { + if (new_seed == 0) + new_seed = generate_seed(); + + hashtable_seed = new_seed; + } +} +#endif diff --git a/src/jansson/jansson-readthedocs-io-en-latest.pdf b/src/jansson/jansson-readthedocs-io-en-latest.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9ad9edfb97f047c07e4ae6d88b03595453d474bb GIT binary patch literal 290430 zcma&NV~{9I(=|G_ZO`nnZQHhObB}G?p4nsDwr$(?-QWG*`^JqpC*pbPPgQnBMMr04 zuC+S5np9p`l!k$p1&VZbacC8afq4HYhq#3u|W+ zM*=!gYXfH!VG|=eV-qM|UMMGLM-u}ZDED<66&bsIdW5bMHFx;rb>Jb{QMKT>tRQim z<|-)7t7Nj0NM$6H-C5rrA137W>Sq8?yqq^@Rm8SFO6D~Pjm#L3p7vhVbqW-R{4mqgz zvW@5up0%`aFoOoZo{wlNX^%Ws(Ya$fwybCGm%PcY<#q5B9j#|@ac)p0i^3A4gRO2W z@UvpwIS%@K%am8D+H+TL`rL}M$Q|zNQ22RAbm`oXW!R7FshViL zYPykw4k4@Jz#+jV+mnh{54;xe(h{|=mDl5gQ)O|hfv?PNm(qh~hk<4WnVh+-`*!R$shOpwA83~(x_~x;dPoX=Nf1L2BsgJS1T1}!#^+UV#@K@l;B
*5K(YZh{%CL6MmB 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%0Dlh`vAk?`Z#zJtdH^VD;(zcn4kKP_TORvaDm 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¯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 literal 0 HcmV?d00001 diff --git a/src/jansson/jansson.h b/src/jansson/jansson.h new file mode 100644 index 0000000..0cd0f0d --- /dev/null +++ b/src/jansson/jansson.h @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef JSON_H +#define JSON_H + +#include +#include /* for size_t */ +#include + +#include "jansson_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* version */ + +#define JSON_MAJOR_VERSION 2 +#define JSON_MINOR_VERSION 12 +#define JSON_MICRO_VERSION 0 + +/* Micro version is omitted if it's 0 */ +#define JSON_VERSION "2.12" + +/* Version as a 3-byte hex number, e.g. 0x010201 == 1.2.1. Use this + for numeric comparisons, e.g. #if JSON_VERSION_HEX >= ... */ +#define JSON_VERSION_HEX ((JSON_MAJOR_VERSION << 16) | \ + (JSON_MINOR_VERSION << 8) | \ + (JSON_MICRO_VERSION << 0)) + +/* If __atomic or __sync builtins are available the library is thread + * safe for all read-only functions plus reference counting. */ +#if JSON_HAVE_ATOMIC_BUILTINS || JSON_HAVE_SYNC_BUILTINS +#define JSON_THREAD_SAFE_REFCOUNT 1 +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define JSON_ATTRS(...) __attribute__((__VA_ARGS__)) +#else +#define JSON_ATTRS(...) +#endif + +/* types */ + +typedef enum { + JSON_OBJECT, + JSON_ARRAY, + JSON_STRING, + JSON_INTEGER, + JSON_REAL, + JSON_TRUE, + JSON_FALSE, + JSON_NULL +} json_type; + +typedef struct json_t { + json_type type; + volatile size_t refcount; +} json_t; + +#ifndef JSON_USING_CMAKE /* disabled if using cmake */ + + #if JSON_INTEGER_IS_LONG_LONG + + #ifdef _WIN32 + #define JSON_INTEGER_FORMAT "I64d" + #else + #define JSON_INTEGER_FORMAT "lld" + #endif + + typedef long long json_int_t; + #else + + #define JSON_INTEGER_FORMAT "ld" + typedef long json_int_t; + + #endif /* JSON_INTEGER_IS_LONG_LONG */ + +#endif + +#define json_typeof(json) ((json)->type) +#define json_is_object(json) ((json) && json_typeof(json) == JSON_OBJECT) +#define json_is_array(json) ((json) && json_typeof(json) == JSON_ARRAY) +#define json_is_string(json) ((json) && json_typeof(json) == JSON_STRING) +#define json_is_integer(json) ((json) && json_typeof(json) == JSON_INTEGER) +#define json_is_real(json) ((json) && json_typeof(json) == JSON_REAL) +#define json_is_number(json) (json_is_integer(json) || json_is_real(json)) +#define json_is_true(json) ((json) && json_typeof(json) == JSON_TRUE) +#define json_is_false(json) ((json) && json_typeof(json) == JSON_FALSE) +#define json_boolean_value json_is_true +#define json_is_boolean(json) (json_is_true(json) || json_is_false(json)) +#define json_is_null(json) ((json) && json_typeof(json) == JSON_NULL) + +/* construction, destruction, reference counting */ + +json_t *json_object(void); +json_t *json_array(void); +json_t *json_string(const char *value); +json_t *json_stringn(const char *value, size_t len); +json_t *json_string_nocheck(const char *value); +json_t *json_stringn_nocheck(const char *value, size_t len); +json_t *json_integer(json_int_t value); +json_t *json_real(double value); +json_t *json_true(void); +json_t *json_false(void); +#define json_boolean(val) ((val > 0) ? json_true() : json_false()) +json_t *json_null(void); + +/* do not call JSON_INTERNAL_INCREF or JSON_INTERNAL_DECREF directly */ +#if JSON_HAVE_ATOMIC_BUILTINS +#define JSON_INTERNAL_INCREF(json) __atomic_add_fetch(&json->refcount, 1, __ATOMIC_ACQUIRE) +#define JSON_INTERNAL_DECREF(json) __atomic_sub_fetch(&json->refcount, 1, __ATOMIC_RELEASE) +#elif JSON_HAVE_SYNC_BUILTINS +#define JSON_INTERNAL_INCREF(json) __sync_add_and_fetch(&json->refcount, 1) +#define JSON_INTERNAL_DECREF(json) __sync_sub_and_fetch(&json->refcount, 1) +#else +#define JSON_INTERNAL_INCREF(json) (++json->refcount) +#define JSON_INTERNAL_DECREF(json) (--json->refcount) +#endif + +JSON_INLINE +json_t *json_incref(json_t *json) +{ + if(json && json->refcount != (size_t)-1) + JSON_INTERNAL_INCREF(json); + return json; +} + +/* do not call json_delete directly */ +void json_delete(json_t *json); + +JSON_INLINE +void json_decref(json_t *json) +{ + if(json && json->refcount != (size_t)-1 && JSON_INTERNAL_DECREF(json) == 0) + json_delete(json); +} + +#if defined(__GNUC__) || defined(__clang__) +JSON_INLINE +void json_decrefp(json_t **json) +{ + if(json) { + json_decref(*json); + *json = NULL; + } +} + +JSON_INLINE +void json_decrefp_def(json_t **json) +{ + json_decrefp(json); +} + +#define json_auto_t json_t __attribute__((cleanup(json_decrefp))) +#endif + + +/* error reporting */ + +#define JSON_ERROR_TEXT_LENGTH 160 +#define JSON_ERROR_SOURCE_LENGTH 80 + +typedef struct json_error_t { + int line; + int column; + int position; + char source[JSON_ERROR_SOURCE_LENGTH]; + char text[JSON_ERROR_TEXT_LENGTH]; + int code; +} json_error_t; + +enum json_error_code { + json_error_unknown, + json_error_out_of_memory, + json_error_stack_overflow, + json_error_cannot_open_file, + json_error_invalid_argument, + json_error_invalid_utf8, + json_error_premature_end_of_input, + json_error_end_of_input_expected, + json_error_invalid_syntax, + json_error_invalid_format, + json_error_wrong_type, + json_error_null_character, + json_error_null_value, + json_error_null_byte_in_key, + json_error_duplicate_key, + json_error_numeric_overflow, + json_error_item_not_found, + json_error_index_out_of_range +}; + +JSON_INLINE +enum json_error_code json_error_code(const json_error_t *e) +{ + return (enum json_error_code)e->text[JSON_ERROR_TEXT_LENGTH - 1]; +} + +JSON_INLINE +enum json_error_code json_error_code_def(const json_error_t *e) +{ + return json_error_code(e); +} + +/* getters, setters, manipulation */ + +void json_object_seed(size_t seed); +size_t json_object_size(const json_t *object); +json_t *json_object_get(const json_t *object, const char *key) JSON_ATTRS(warn_unused_result); +int json_object_set_new(json_t *object, const char *key, json_t *value); +int json_object_set_new_nocheck(json_t *object, const char *key, json_t *value); +int json_object_del(json_t *object, const char *key); +int json_object_clear(json_t *object); +int json_object_update(json_t *object, json_t *other); +int json_object_update_existing(json_t *object, json_t *other); +int json_object_update_missing(json_t *object, json_t *other); +void *json_object_iter(json_t *object); +void *json_object_iter_at(json_t *object, const char *key); +void *json_object_key_to_iter(const char *key); +void *json_object_iter_next(json_t *object, void *iter); +const char *json_object_iter_key(void *iter); +json_t *json_object_iter_value(void *iter); +int json_object_iter_set_new(json_t *object, void *iter, json_t *value); + +#define json_object_foreach(object, key, value) \ + for(key = json_object_iter_key(json_object_iter(object)); \ + key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ + key = json_object_iter_key(json_object_iter_next(object, json_object_key_to_iter(key)))) + +#define json_object_foreach_safe(object, n, key, value) \ + for(key = json_object_iter_key(json_object_iter(object)), \ + n = json_object_iter_next(object, json_object_key_to_iter(key)); \ + key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ + key = json_object_iter_key(n), \ + n = json_object_iter_next(object, json_object_key_to_iter(key))) + +#define json_array_foreach(array, index, value) \ + for(index = 0; \ + index < json_array_size(array) && (value = json_array_get(array, index)); \ + index++) + +JSON_INLINE +int json_object_set(json_t *object, const char *key, json_t *value) +{ + return json_object_set_new(object, key, json_incref(value)); +} + +JSON_INLINE +int json_object_set_def(json_t *object, const char *key, json_t *value) +{ + return json_object_set(object, key, value); +} + +JSON_INLINE +int json_object_set_nocheck(json_t *object, const char *key, json_t *value) +{ + return json_object_set_new_nocheck(object, key, json_incref(value)); +} + +JSON_INLINE +int json_object_iter_set(json_t *object, void *iter, json_t *value) +{ + return json_object_iter_set_new(object, iter, json_incref(value)); +} + +JSON_INLINE +int json_object_iter_set_def(json_t *object, void *iter, json_t *value) +{ + return json_object_iter_set(object, iter, value); +} + +size_t json_array_size(const json_t *array); +json_t *json_array_get(const json_t *array, size_t index) JSON_ATTRS(warn_unused_result); +int json_array_set_new(json_t *array, size_t index, json_t *value); +int json_array_append_new(json_t *array, json_t *value); +int json_array_insert_new(json_t *array, size_t index, json_t *value); +int json_array_remove(json_t *array, size_t index); +int json_array_clear(json_t *array); +int json_array_extend(json_t *array, json_t *other); + +JSON_INLINE +int json_array_set(json_t *array, size_t ind, json_t *value) +{ + return json_array_set_new(array, ind, json_incref(value)); +} + +JSON_INLINE +int json_array_set_def(json_t *array, size_t ind, json_t *value) +{ + return json_array_set(array, ind, value); +} + +JSON_INLINE +int json_array_append(json_t *array, json_t *value) +{ + return json_array_append_new(array, json_incref(value)); +} + +JSON_INLINE +int json_array_insert(json_t *array, size_t ind, json_t *value) +{ + return json_array_insert_new(array, ind, json_incref(value)); +} + +JSON_INLINE +int json_array_insert_def(json_t *array, size_t ind, json_t *value) +{ + return json_array_insert(array, ind, value); +} + +const char *json_string_value(const json_t *string); +size_t json_string_length(const json_t *string); +json_int_t json_integer_value(const json_t *integer); +double json_real_value(const json_t *real); +double json_number_value(const json_t *json); + +int json_string_set(json_t *string, const char *value); +int json_string_setn(json_t *string, const char *value, size_t len); +int json_string_set_nocheck(json_t *string, const char *value); +int json_string_setn_nocheck(json_t *string, const char *value, size_t len); +int json_integer_set(json_t *integer, json_int_t value); +int json_real_set(json_t *real, double value); + +/* pack, unpack */ + +json_t *json_pack(const char *fmt, ...) JSON_ATTRS(warn_unused_result); +json_t *json_pack_ex(json_error_t *error, size_t flags, const char *fmt, ...) JSON_ATTRS(warn_unused_result); +json_t *json_vpack_ex(json_error_t *error, size_t flags, const char *fmt, va_list ap) JSON_ATTRS(warn_unused_result); + +#define JSON_VALIDATE_ONLY 0x1 +#define JSON_STRICT 0x2 + +int json_unpack(json_t *root, const char *fmt, ...); +int json_unpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, ...); +int json_vunpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, va_list ap); + +/* sprintf */ + +json_t *json_sprintf(const char *fmt, ...) JSON_ATTRS(warn_unused_result, format(printf, 1, 2)); +json_t *json_vsprintf(const char *fmt, va_list ap) JSON_ATTRS(warn_unused_result, format(printf, 1, 0)); + + +/* equality */ + +int json_equal(const json_t *value1, const json_t *value2); + + +/* copying */ + +json_t *json_copy(json_t *value) JSON_ATTRS(warn_unused_result); +json_t *json_deep_copy(const json_t *value) JSON_ATTRS(warn_unused_result); + + +/* decoding */ + +#define JSON_REJECT_DUPLICATES 0x1 +#define JSON_DISABLE_EOF_CHECK 0x2 +#define JSON_DECODE_ANY 0x4 +#define JSON_DECODE_INT_AS_REAL 0x8 +#define JSON_ALLOW_NUL 0x10 + +typedef size_t (*json_load_callback_t)(void *buffer, size_t buflen, void *data); + +json_t *json_loads(const char *input, size_t flags, json_error_t *error) JSON_ATTRS(warn_unused_result); +json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error) JSON_ATTRS(warn_unused_result); +json_t *json_loadf(FILE *input, size_t flags, json_error_t *error) JSON_ATTRS(warn_unused_result); +json_t *json_loadfd(int input, size_t flags, json_error_t *error) JSON_ATTRS(warn_unused_result); +json_t *json_load_file(const char *path, size_t flags, json_error_t *error) JSON_ATTRS(warn_unused_result); +json_t *json_load_callback(json_load_callback_t callback, void *data, size_t flags, json_error_t *error) JSON_ATTRS(warn_unused_result); + + +/* encoding */ + +#define JSON_MAX_INDENT 0x1F +#define JSON_INDENT(n) ((n) & JSON_MAX_INDENT) +#define JSON_COMPACT 0x20 +#define JSON_ENSURE_ASCII 0x40 +#define JSON_SORT_KEYS 0x80 +#define JSON_PRESERVE_ORDER 0x100 +#define JSON_ENCODE_ANY 0x200 +#define JSON_ESCAPE_SLASH 0x400 +#define JSON_REAL_PRECISION(n) (((n) & 0x1F) << 11) +#define JSON_EMBED 0x10000 + +typedef int (*json_dump_callback_t)(const char *buffer, size_t size, void *data); + +char *json_dumps(const json_t *json, size_t flags) JSON_ATTRS(warn_unused_result); +size_t json_dumpb(const json_t *json, char *buffer, size_t size, size_t flags); +int json_dumpf(const json_t *json, FILE *output, size_t flags); +int json_dumpfd(const json_t *json, int output, size_t flags); +int json_dump_file(const json_t *json, const char *path, size_t flags); +int json_dump_callback(const json_t *json, json_dump_callback_t callback, void *data, size_t flags); + +/* custom memory allocation */ + +typedef void *(*json_malloc_t)(size_t); +typedef void (*json_free_t)(void *); + +void json_set_alloc_funcs(json_malloc_t malloc_fn, json_free_t free_fn); +void json_get_alloc_funcs(json_malloc_t *malloc_fn, json_free_t *free_fn); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/jansson/jansson_config.h b/src/jansson/jansson_config.h new file mode 100644 index 0000000..97e269e --- /dev/null +++ b/src/jansson/jansson_config.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + * + * + * This file specifies a part of the site-specific configuration for + * Jansson, namely those things that affect the public API in + * jansson.h. + * + * The configure script copies this file to jansson_config.h and + * replaces @var@ substitutions by values that fit your system. If you + * cannot run the configure script, you can do the value substitution + * by hand. + */ + +#ifndef JSON_CONFIG_H +#define JSON_CONFIG_H + +#define HAVE_STDINT_H + +#define JSON_USE_TAB_INDENT + +/* If your compiler supports the inline keyword in C, JSON_INLINE is + defined to `inline', otherwise empty. In C++, the inline is always + supported. */ +#ifdef __cplusplus +#define JSON_INLINE inline +#else +#define JSON_INLINE static inline +#endif + +/* If your compiler supports the `long long` type and the strtoll() + library function, JSON_INTEGER_IS_LONG_LONG is defined to 1, + otherwise to 0. */ + +//#if defined WSYSTEM_CORETYPE_ILON + #define JSON_INTEGER_IS_LONG_LONG 1 +//#else + //#define JSON_INTEGER_IS_LONG_LONG 0 +//#endif + +/* If locale.h and localeconv() are available, define to 1, + otherwise to 0. */ +#define JSON_HAVE_LOCALECONV 0 + +/* If __atomic builtins are available they will be used to manage + reference counts of json_t. */ +#define JSON_HAVE_ATOMIC_BUILTINS 0 + +/* If __atomic builtins are not available we try using __sync builtins + to manage reference counts of json_t. */ +#define JSON_HAVE_SYNC_BUILTINS 0 + +/* Maximum recursion depth for parsing JSON input. + This limits the depth of e.g. array-within-array constructions. */ +#define JSON_PARSER_MAX_DEPTH 2048 + +/* +static inline void* Jansson_config_malloc(size_t size) +{ + return (void*)malloc(size); +} + +static inline void Jansson_config_free(void* ptr) +{ + free(ptr); +} + +static inline void* Jansson_config_malloc_def(size_t size) +{ + return Jansson_config_malloc(size); +} + +static inline void Jansson_config_free_def(void* ptr) +{ + Jansson_config_free(ptr); +} +*/ + +#endif diff --git a/src/jansson/jansson_config.h.in b/src/jansson/jansson_config.h.in new file mode 100644 index 0000000..5dbe488 --- /dev/null +++ b/src/jansson/jansson_config.h.in @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2010-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + * + * + * This file specifies a part of the site-specific configuration for + * Jansson, namely those things that affect the public API in + * jansson.h. + * + * The configure script copies this file to jansson_config.h and + * replaces @var@ substitutions by values that fit your system. If you + * cannot run the configure script, you can do the value substitution + * by hand. + */ + +#ifndef JSON_CONFIG_H +#define JSON_CONFIG_H + +/* If your compiler supports the inline keyword in C, JSON_INLINE is + defined to `inline', otherwise empty. In C++, the inline is always + supported. */ +#ifdef __cplusplus +#define JSON_INLINE inline +#else +#define JSON_INLINE @json_inline@ +#endif + +/* If your compiler supports the `long long` type and the strtoll() + library function, JSON_INTEGER_IS_LONG_LONG is defined to 1, + otherwise to 0. */ +#define JSON_INTEGER_IS_LONG_LONG @json_have_long_long@ + +/* If locale.h and localeconv() are available, define to 1, + otherwise to 0. */ +#define JSON_HAVE_LOCALECONV @json_have_localeconv@ + +/* If __atomic builtins are available they will be used to manage + reference counts of json_t. */ +#define JSON_HAVE_ATOMIC_BUILTINS @json_have_atomic_builtins@ + +/* If __atomic builtins are not available we try using __sync builtins + to manage reference counts of json_t. */ +#define JSON_HAVE_SYNC_BUILTINS @json_have_sync_builtins@ + +/* Maximum recursion depth for parsing JSON input. + This limits the depth of e.g. array-within-array constructions. */ +#define JSON_PARSER_MAX_DEPTH 2048 + +#endif diff --git a/src/jansson/jansson_private.h b/src/jansson/jansson_private.h new file mode 100644 index 0000000..d067afb --- /dev/null +++ b/src/jansson/jansson_private.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef JSON_PRIVATE_H +#define JSON_PRIVATE_H + +#include "jansson_config.h" +#include +#include "jansson.h" +#include "hashtable.h" +#include "strbuffer.h" + +#define container_of(ptr_, type_, member_) \ + ((type_ *)((char *)ptr_ - offsetof(type_, member_))) + +/* On some platforms, max() may already be defined */ +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +/* va_copy is a C99 feature. In C89 implementations, it's sometimes + available as __va_copy. If not, memcpy() should do the trick. */ +#ifndef va_copy +#ifdef __va_copy +#define va_copy __va_copy +#else +#define va_copy(a, b) memcpy(&(a), &(b), sizeof(va_list)) +#endif +#endif + +typedef struct { + json_t json; + hashtable_t hashtable; +} json_object_t; + +typedef struct { + json_t json; + size_t size; + size_t entries; + json_t **table; +} json_array_t; + +typedef struct { + json_t json; + char *value; + size_t length; +} json_string_t; + +typedef struct { + json_t json; + double value; +} json_real_t; + +typedef struct { + json_t json; + json_int_t value; +} json_integer_t; + +#define json_to_object(json_) container_of(json_, json_object_t, json) +#define json_to_array(json_) container_of(json_, json_array_t, json) +#define json_to_string(json_) container_of(json_, json_string_t, json) +#define json_to_real(json_) container_of(json_, json_real_t, json) +#define json_to_integer(json_) container_of(json_, json_integer_t, json) + +/* Create a string by taking ownership of an existing buffer */ +json_t *jsonp_stringn_nocheck_own(const char *value, size_t len); + +/* Error message formatting */ +void jsonp_error_init(json_error_t *error, const char *source); +void jsonp_error_set_source(json_error_t *error, const char *source); +void jsonp_error_set(json_error_t *error, int line, int column, + size_t position, enum json_error_code code, + const char *msg, ...); +void jsonp_error_vset(json_error_t *error, int line, int column, + size_t position, enum json_error_code code, + const char *msg, va_list ap); + +/* Locale independent string<->double conversions */ +int jsonp_strtod(strbuffer_t *strbuffer, double *out); +int jsonp_dtostr(char *buffer, size_t size, double value, int prec); + +/* Wrappers for custom memory functions */ +void* jsonp_malloc(size_t size) JSON_ATTRS(warn_unused_result); +void jsonp_free(void *ptr); +char *jsonp_strndup(const char *str, size_t length) JSON_ATTRS(warn_unused_result); +char *jsonp_strdup(const char *str) JSON_ATTRS(warn_unused_result); +char *jsonp_strndup(const char *str, size_t len) JSON_ATTRS(warn_unused_result); + + +/* Windows compatibility */ +#if defined(_WIN32) || defined(WIN32) +# if defined(_MSC_VER) /* MS compiller */ +# if (_MSC_VER < 1900) && !defined(snprintf) /* snprintf not defined yet & not introduced */ +# define snprintf _snprintf +# endif +# if (_MSC_VER < 1500) && !defined(vsnprintf) /* vsnprintf not defined yet & not introduced */ +# define vsnprintf(b,c,f,a) _vsnprintf(b,c,f,a) +# endif +# else /* Other Windows compiller, old definition */ +# define snprintf _snprintf +# define vsnprintf _vsnprintf +# endif +#endif + +#endif diff --git a/src/jansson/jansson_private_config.h b/src/jansson/jansson_private_config.h new file mode 100644 index 0000000..6896e32 --- /dev/null +++ b/src/jansson/jansson_private_config.h @@ -0,0 +1,158 @@ +/* jansson_config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if gcc's __atomic builtins are available */ +#undef HAVE_ATOMIC_BUILTINS + +/* Define to 1 if you have the `close' function. */ +#undef HAVE_CLOSE + +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_ENDIAN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the `getpid' function. */ +#undef HAVE_GETPID + +/* Define to 1 if you have the `gettimeofday' function. */ +#undef HAVE_GETTIMEOFDAY + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `localeconv' function. */ +#undef HAVE_LOCALECONV + +/* Define to 1 if you have the header file. */ +#undef HAVE_LOCALE_H + +/* Define to 1 if the system has the type 'long long int'. */ +#undef HAVE_LONG_LONG_INT + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `open' function. */ +#undef HAVE_OPEN + +/* Define to 1 if you have the `read' function. */ +#undef HAVE_READ + +/* Define to 1 if you have the header file. */ +#undef HAVE_SCHED_H + +/* Define to 1 if you have the `sched_yield' function. */ +#undef HAVE_SCHED_YIELD + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strtoll' function. */ +#undef HAVE_STRTOLL + +/* Define to 1 if gcc's __sync builtins are available */ +#undef HAVE_SYNC_BUILTINS + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if the system has the type 'unsigned long long int'. */ +#undef HAVE_UNSIGNED_LONG_LONG_INT + +/* Number of buckets new object hashtables contain is 2 raised to this power. + E.g. 3 -> 2^3 = 8. */ +#undef INITIAL_HASHTABLE_ORDER + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#undef LT_OBJDIR + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if /dev/urandom should be used for seeding the hash function */ +#undef USE_URANDOM + +/* Define to 1 if CryptGenRandom should be used for seeding the hash function + */ +#undef USE_WINDOWS_CRYPTOAPI + +/* Version number of package */ +#undef VERSION + +/* Define for Solaris 2.5.1 so the uint32_t typedef from , + , or is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +#undef _UINT32_T + +/* Define for Solaris 2.5.1 so the uint8_t typedef from , + , or is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +#undef _UINT8_T + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to the type of a signed integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +#undef int32_t + +/* Define to the type of an unsigned integer type of width exactly 16 bits if + such a type exists and the standard includes do not define it. */ +#undef uint16_t + +/* Define to the type of an unsigned integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +#undef uint32_t + +/* Define to the type of an unsigned integer type of width exactly 8 bits if + such a type exists and the standard includes do not define it. */ +#undef uint8_t diff --git a/src/jansson/load.c b/src/jansson/load.c new file mode 100644 index 0000000..d2d58d8 --- /dev/null +++ b/src/jansson/load.c @@ -0,0 +1,1162 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "jansson_private.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "jansson.h" +#include "strbuffer.h" +#include "utf.h" + +#define STREAM_STATE_OK 0 +#define STREAM_STATE_EOF -1 +#define STREAM_STATE_ERROR -2 + +#define TOKEN_INVALID -1 +#define TOKEN_EOF 0 +#define TOKEN_STRING 256 +#define TOKEN_INTEGER 257 +#define TOKEN_REAL 258 +#define TOKEN_TRUE 259 +#define TOKEN_FALSE 260 +#define TOKEN_NULL 261 + +/* Locale independent versions of isxxx() functions */ +#define l_isupper(c) ('A' <= (c) && (c) <= 'Z') +#define l_islower(c) ('a' <= (c) && (c) <= 'z') +#define l_isalpha(c) (l_isupper(c) || l_islower(c)) +#define l_isdigit(c) ('0' <= (c) && (c) <= '9') +#define l_isxdigit(c) \ + (l_isdigit(c) || ('A' <= (c) && (c) <= 'F') || ('a' <= (c) && (c) <= 'f')) + +/* Read one byte from stream, convert to unsigned char, then int, and + return. return EOF on end of file. This corresponds to the + behaviour of fgetc(). */ +typedef int (*get_func)(void *data); + +typedef struct { + get_func get; + void *data; + char buffer[5]; + size_t buffer_pos; + int state; + int line; + int column, last_column; + size_t position; +} stream_t; + +typedef struct { + stream_t stream; + strbuffer_t saved_text; + size_t flags; + size_t depth; + int token; + union { + struct { + char *val; + size_t len; + } string; + json_int_t integer; + double real; + } value; +} lex_t; + +#define stream_to_lex(stream) container_of(stream, lex_t, stream) + + +/*** error reporting ***/ + +static void error_set(json_error_t *error, const lex_t *lex, + enum json_error_code code, + const char *msg, ...) +{ + va_list ap; + char msg_text[JSON_ERROR_TEXT_LENGTH]; + + + char msg_with_context[JSON_ERROR_TEXT_LENGTH + 32]; + + int line = -1, col = -1; + size_t pos = 0; + const char *result = msg_text; + + if(!error) + return; + + va_start(ap, msg); + vsnprintf(msg_text, JSON_ERROR_TEXT_LENGTH, msg, ap); + msg_text[JSON_ERROR_TEXT_LENGTH - 1] = '\0'; + va_end(ap); + + if(lex) + { + const char *saved_text = strbuffer_value(&lex->saved_text); + + line = lex->stream.line; + col = lex->stream.column; + pos = lex->stream.position; + + if(saved_text && saved_text[0]) + { + if(lex->saved_text.length <= 20) { + + snprintf(msg_with_context, sizeof(msg_with_context), "%s near '%s'", msg_text, saved_text); + + msg_with_context[sizeof(msg_with_context) - 1] = '\0'; + result = msg_with_context; + } + } + else + { + if(code == json_error_invalid_syntax) { + /* More specific error code for premature end of file. */ + code = json_error_premature_end_of_input; + } + if(lex->stream.state == STREAM_STATE_ERROR) { + /* No context for UTF-8 decoding errors */ + result = msg_text; + } + else { + snprintf(msg_with_context, sizeof(msg_with_context), "%s near end of file", msg_text); + msg_with_context[sizeof(msg_with_context) - 1] = '\0'; + result = msg_with_context; + } + } + } + + jsonp_error_set(error, line, col, pos, code, "%s", result); +} + + +/*** lexical analyzer ***/ + +static void +stream_init(stream_t *stream, get_func get, void *data) +{ + stream->get = get; + stream->data = data; + stream->buffer[0] = '\0'; + stream->buffer_pos = 0; + + stream->state = STREAM_STATE_OK; + stream->line = 1; + stream->column = 0; + stream->position = 0; +} + +static int stream_get(stream_t *stream, json_error_t *error) +{ + int c; + + if(stream->state != STREAM_STATE_OK) + return stream->state; + + if(!stream->buffer[stream->buffer_pos]) + { + c = stream->get(stream->data); + if(c == EOF) { + stream->state = STREAM_STATE_EOF; + return STREAM_STATE_EOF; + } + + stream->buffer[0] = c; + stream->buffer_pos = 0; + + if(0x80 <= c && c <= 0xFF) + { + /* multi-byte UTF-8 sequence */ + size_t i, count; + + count = utf8_check_first(c); + if(!count) + goto out; + + assert(count >= 2); + + for(i = 1; i < count; i++) + stream->buffer[i] = stream->get(stream->data); + + if(!utf8_check_full(stream->buffer, count, NULL)) + goto out; + + stream->buffer[count] = '\0'; + } + else + stream->buffer[1] = '\0'; + } + + c = stream->buffer[stream->buffer_pos++]; + + stream->position++; + if(c == '\n') { + stream->line++; + stream->last_column = stream->column; + stream->column = 0; + } + else if(utf8_check_first(c)) { + /* track the Unicode character column, so increment only if + this is the first character of a UTF-8 sequence */ + stream->column++; + } + + return c; + +out: + stream->state = STREAM_STATE_ERROR; + error_set(error, stream_to_lex(stream), json_error_invalid_utf8, "unable to decode byte 0x%x", c); + return STREAM_STATE_ERROR; +} + +static void stream_unget(stream_t *stream, int c) +{ + if(c == STREAM_STATE_EOF || c == STREAM_STATE_ERROR) + return; + + stream->position--; + if(c == '\n') { + stream->line--; + stream->column = stream->last_column; + } + else if(utf8_check_first(c)) + stream->column--; + + assert(stream->buffer_pos > 0); + stream->buffer_pos--; + assert(stream->buffer[stream->buffer_pos] == c); +} + + +static int lex_get(lex_t *lex, json_error_t *error) +{ + return stream_get(&lex->stream, error); +} + +static void lex_save(lex_t *lex, int c) +{ + strbuffer_append_byte(&lex->saved_text, c); +} + +static int lex_get_save(lex_t *lex, json_error_t *error) +{ + int c = stream_get(&lex->stream, error); + if(c != STREAM_STATE_EOF && c != STREAM_STATE_ERROR) + lex_save(lex, c); + return c; +} + +static void lex_unget(lex_t *lex, int c) +{ + stream_unget(&lex->stream, c); +} + +static void lex_unget_unsave(lex_t *lex, int c) +{ + if(c != STREAM_STATE_EOF && c != STREAM_STATE_ERROR) { + /* Since we treat warnings as errors, when assertions are turned + * off the "d" variable would be set but never used. Which is + * treated as an error by GCC. + */ + #ifndef NDEBUG + char d; + #endif + stream_unget(&lex->stream, c); + #ifndef NDEBUG + d = + #endif + strbuffer_pop(&lex->saved_text); + assert(c == d); + } +} + +static void lex_save_cached(lex_t *lex) +{ + while(lex->stream.buffer[lex->stream.buffer_pos] != '\0') + { + lex_save(lex, lex->stream.buffer[lex->stream.buffer_pos]); + lex->stream.buffer_pos++; + lex->stream.position++; + } +} + +static void lex_free_string(lex_t *lex) +{ + jsonp_free(lex->value.string.val); + lex->value.string.val = NULL; + lex->value.string.len = 0; +} + +/* assumes that str points to 'u' plus at least 4 valid hex digits */ +static int32_t decode_unicode_escape(const char *str) +{ + int i; + int32_t value = 0; + + assert(str[0] == 'u'); + + for(i = 1; i <= 4; i++) { + char c = str[i]; + value <<= 4; + if(l_isdigit(c)) + value += c - '0'; + else if(l_islower(c)) + value += c - 'a' + 10; + else if(l_isupper(c)) + value += c - 'A' + 10; + else + return -1; + } + + return value; +} + +static void lex_scan_string(lex_t *lex, json_error_t *error) +{ + int c; + const char *p; + char *t; + int i; + + lex->value.string.val = NULL; + lex->token = TOKEN_INVALID; + + c = lex_get_save(lex, error); + + while(c != '"') { + if(c == STREAM_STATE_ERROR) + goto out; + + else if(c == STREAM_STATE_EOF) { + error_set(error, lex, json_error_premature_end_of_input, "premature end of input"); + goto out; + } + + else if(0 <= c && c <= 0x1F) { + /* control character */ + lex_unget_unsave(lex, c); + if(c == '\n') + error_set(error, lex, json_error_invalid_syntax, "unexpected newline"); + else + error_set(error, lex, json_error_invalid_syntax, "control character 0x%x", c); + goto out; + } + + else if(c == '\\') { + c = lex_get_save(lex, error); + if(c == 'u') { + c = lex_get_save(lex, error); + for(i = 0; i < 4; i++) { + if(!l_isxdigit(c)) { + error_set(error, lex, json_error_invalid_syntax, "invalid escape"); + goto out; + } + c = lex_get_save(lex, error); + } + } + else if(c == '"' || c == '\\' || c == '/' || c == 'b' || + c == 'f' || c == 'n' || c == 'r' || c == 't') + c = lex_get_save(lex, error); + else { + error_set(error, lex, json_error_invalid_syntax, "invalid escape"); + goto out; + } + } + else + c = lex_get_save(lex, error); + } + + /* the actual value is at most of the same length as the source + string, because: + - shortcut escapes (e.g. "\t") (length 2) are converted to 1 byte + - a single \uXXXX escape (length 6) is converted to at most 3 bytes + - two \uXXXX escapes (length 12) forming an UTF-16 surrogate pair + are converted to 4 bytes + */ + t = (char*)jsonp_malloc(lex->saved_text.length + 1); + if(!t) { + /* this is not very nice, since TOKEN_INVALID is returned */ + goto out; + } + lex->value.string.val = t; + + /* + 1 to skip the " */ + p = strbuffer_value(&lex->saved_text) + 1; + + while(*p != '"') { + if(*p == '\\') { + p++; + if(*p == 'u') { + size_t length; + int32_t value; + + value = decode_unicode_escape(p); + if(value < 0) { + error_set(error, lex, json_error_invalid_syntax, "invalid Unicode escape '%.6s'", p - 1); + goto out; + } + p += 5; + + if(0xD800 <= value && value <= 0xDBFF) { + /* surrogate pair */ + if(*p == '\\' && *(p + 1) == 'u') { + int32_t value2 = decode_unicode_escape(++p); + if(value2 < 0) { + error_set(error, lex, json_error_invalid_syntax, "invalid Unicode escape '%.6s'", p - 1); + goto out; + } + p += 5; + + if(0xDC00 <= value2 && value2 <= 0xDFFF) { + /* valid second surrogate */ + value = + ((value - 0xD800) << 10) + + (value2 - 0xDC00) + + 0x10000; + } + else { + /* invalid second surrogate */ + error_set(error, lex, + json_error_invalid_syntax, + "invalid Unicode '\\u%04X\\u%04X'", + value, value2); + goto out; + } + } + else { + /* no second surrogate */ + error_set(error, lex, json_error_invalid_syntax, "invalid Unicode '\\u%04X'", + value); + goto out; + } + } + else if(0xDC00 <= value && value <= 0xDFFF) { + error_set(error, lex, json_error_invalid_syntax, "invalid Unicode '\\u%04X'", value); + goto out; + } + + if(utf8_encode(value, t, &length)) + assert(0); + t += length; + } + else { + switch(*p) { + case '"': case '\\': case '/': + *t = *p; break; + case 'b': *t = '\b'; break; + case 'f': *t = '\f'; break; + case 'n': *t = '\n'; break; + case 'r': *t = '\r'; break; + case 't': *t = '\t'; break; + default: assert(0); + } + t++; + p++; + } + } + else + *(t++) = *(p++); + } + *t = '\0'; + lex->value.string.len = t - lex->value.string.val; + lex->token = TOKEN_STRING; + return; + +out: + lex_free_string(lex); +} + +#ifndef JSON_USING_CMAKE /* disabled if using cmake */ + + #if JSON_INTEGER_IS_LONG_LONG + + #ifdef _MSC_VER /* Microsoft Visual Studio */ + #define json_strtoint _strtoi64 + #else + #define json_strtoint strtoll + #endif + + #else + #define json_strtoint strtol + #endif + +#endif + +static int lex_scan_number(lex_t *lex, int c, json_error_t *error) +{ + const char *saved_text; + char *end; + double doubleval; + + lex->token = TOKEN_INVALID; + + if(c == '-') + c = lex_get_save(lex, error); + + if(c == '0') { + c = lex_get_save(lex, error); + if(l_isdigit(c)) { + lex_unget_unsave(lex, c); + goto out; + } + } + else if(l_isdigit(c)) { + do + c = lex_get_save(lex, error); + while(l_isdigit(c)); + } + else { + lex_unget_unsave(lex, c); + goto out; + } + + if(!(lex->flags & JSON_DECODE_INT_AS_REAL) && + c != '.' && c != 'E' && c != 'e') + { + json_int_t intval; + + lex_unget_unsave(lex, c); + + saved_text = strbuffer_value(&lex->saved_text); + + errno = 0; + intval = json_strtoint(saved_text, &end, 10); + if(errno == ERANGE) { + if(intval < 0) + error_set(error, lex, json_error_numeric_overflow, "too big negative integer"); + else + error_set(error, lex, json_error_numeric_overflow, "too big integer"); + goto out; + } + + assert(end == saved_text + lex->saved_text.length); + + lex->token = TOKEN_INTEGER; + lex->value.integer = intval; + return 0; + } + + if(c == '.') { + c = lex_get(lex, error); + if(!l_isdigit(c)) { + lex_unget(lex, c); + goto out; + } + lex_save(lex, c); + + do + c = lex_get_save(lex, error); + while(l_isdigit(c)); + } + + if(c == 'E' || c == 'e') { + c = lex_get_save(lex, error); + if(c == '+' || c == '-') + c = lex_get_save(lex, error); + + if(!l_isdigit(c)) { + lex_unget_unsave(lex, c); + goto out; + } + + do + c = lex_get_save(lex, error); + while(l_isdigit(c)); + } + + lex_unget_unsave(lex, c); + + if(jsonp_strtod(&lex->saved_text, &doubleval)) { + error_set(error, lex, json_error_numeric_overflow, "real number overflow"); + goto out; + } + + lex->token = TOKEN_REAL; + lex->value.real = doubleval; + return 0; + +out: + return -1; +} + +static int lex_scan(lex_t *lex, json_error_t *error) +{ + int c; + + strbuffer_clear(&lex->saved_text); + + if(lex->token == TOKEN_STRING) + lex_free_string(lex); + + do + c = lex_get(lex, error); + while(c == ' ' || c == '\t' || c == '\n' || c == '\r'); + + if(c == STREAM_STATE_EOF) { + lex->token = TOKEN_EOF; + goto out; + } + + if(c == STREAM_STATE_ERROR) { + lex->token = TOKEN_INVALID; + goto out; + } + + lex_save(lex, c); + + if(c == '{' || c == '}' || c == '[' || c == ']' || c == ':' || c == ',') + lex->token = c; + + else if(c == '"') + lex_scan_string(lex, error); + + else if(l_isdigit(c) || c == '-') { + if(lex_scan_number(lex, c, error)) + goto out; + } + + else if(l_isalpha(c)) { + /* eat up the whole identifier for clearer error messages */ + const char *saved_text; + + do + c = lex_get_save(lex, error); + while(l_isalpha(c)); + lex_unget_unsave(lex, c); + + saved_text = strbuffer_value(&lex->saved_text); + + if(strcmp(saved_text, "true") == 0) + lex->token = TOKEN_TRUE; + else if(strcmp(saved_text, "false") == 0) + lex->token = TOKEN_FALSE; + else if(strcmp(saved_text, "null") == 0) + lex->token = TOKEN_NULL; + else + lex->token = TOKEN_INVALID; + } + + else { + /* save the rest of the input UTF-8 sequence to get an error + message of valid UTF-8 */ + lex_save_cached(lex); + lex->token = TOKEN_INVALID; + } + +out: + return lex->token; +} + +static char *lex_steal_string(lex_t *lex, size_t *out_len) +{ + char *result = NULL; + if(lex->token == TOKEN_STRING) { + result = lex->value.string.val; + *out_len = lex->value.string.len; + lex->value.string.val = NULL; + lex->value.string.len = 0; + } + return result; +} + +static int lex_init(lex_t *lex, get_func get, size_t flags, void *data) +{ + stream_init(&lex->stream, get, data); + if(strbuffer_init(&lex->saved_text)) + return -1; + + lex->flags = flags; + lex->token = TOKEN_INVALID; + return 0; +} + +static void lex_close(lex_t *lex) +{ + if(lex->token == TOKEN_STRING) + lex_free_string(lex); + strbuffer_close(&lex->saved_text); +} + + +/*** parser ***/ + +static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error); + +static json_t *parse_object(lex_t *lex, size_t flags, json_error_t *error) +{ + json_t *object = json_object(); + if(!object) + return NULL; + + lex_scan(lex, error); + if(lex->token == '}') + return object; + + while(1) { + char *key; + size_t len; + json_t *value; + + if(lex->token != TOKEN_STRING) { + error_set(error, lex, json_error_invalid_syntax, "string or '}' expected"); + goto error; + } + + key = lex_steal_string(lex, &len); + if(!key) + return NULL; + if (memchr(key, '\0', len)) { + jsonp_free(key); + error_set(error, lex, json_error_null_byte_in_key, "NUL byte in object key not supported"); + goto error; + } + + if(flags & JSON_REJECT_DUPLICATES) { + if(json_object_get(object, key)) { + jsonp_free(key); + error_set(error, lex, json_error_duplicate_key, "duplicate object key"); + goto error; + } + } + + lex_scan(lex, error); + if(lex->token != ':') { + jsonp_free(key); + error_set(error, lex, json_error_invalid_syntax, "':' expected"); + goto error; + } + + lex_scan(lex, error); + value = parse_value(lex, flags, error); + if(!value) { + jsonp_free(key); + goto error; + } + + if(json_object_set_new_nocheck(object, key, value)) { + jsonp_free(key); + goto error; + } + + jsonp_free(key); + + lex_scan(lex, error); + if(lex->token != ',') + break; + + lex_scan(lex, error); + } + + if(lex->token != '}') { + error_set(error, lex, json_error_invalid_syntax, "'}' expected"); + goto error; + } + + return object; + +error: + json_decref(object); + return NULL; +} + +static json_t *parse_array(lex_t *lex, size_t flags, json_error_t *error) +{ + json_t *array = json_array(); + if(!array) + return NULL; + + lex_scan(lex, error); + if(lex->token == ']') + return array; + + while(lex->token) { + json_t *elem = parse_value(lex, flags, error); + if(!elem) + goto error; + + if(json_array_append_new(array, elem)) { + goto error; + } + + lex_scan(lex, error); + if(lex->token != ',') + break; + + lex_scan(lex, error); + } + + if(lex->token != ']') { + error_set(error, lex, json_error_invalid_syntax, "']' expected"); + goto error; + } + + return array; + +error: + json_decref(array); + return NULL; +} + +static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error) +{ + json_t *json; + + lex->depth++; + if(lex->depth > JSON_PARSER_MAX_DEPTH) { + error_set(error, lex, json_error_stack_overflow, "maximum parsing depth reached"); + return NULL; + } + + switch(lex->token) { + case TOKEN_STRING: { + const char *value = lex->value.string.val; + size_t len = lex->value.string.len; + + if(!(flags & JSON_ALLOW_NUL)) { + if(memchr(value, '\0', len)) { + error_set(error, lex, json_error_null_character, "\\u0000 is not allowed without JSON_ALLOW_NUL"); + return NULL; + } + } + + json = jsonp_stringn_nocheck_own(value, len); + lex->value.string.val = NULL; + lex->value.string.len = 0; + break; + } + + case TOKEN_INTEGER: { + json = json_integer(lex->value.integer); + break; + } + + case TOKEN_REAL: { + json = json_real(lex->value.real); + break; + } + + case TOKEN_TRUE: + json = json_true(); + break; + + case TOKEN_FALSE: + json = json_false(); + break; + + case TOKEN_NULL: + json = json_null(); + break; + + case '{': + json = parse_object(lex, flags, error); + break; + + case '[': + json = parse_array(lex, flags, error); + break; + + case TOKEN_INVALID: + error_set(error, lex, json_error_invalid_syntax, "invalid token"); + return NULL; + + default: + error_set(error, lex, json_error_invalid_syntax, "unexpected token"); + return NULL; + } + + if(!json) + return NULL; + + lex->depth--; + return json; +} + +static json_t *parse_json(lex_t *lex, size_t flags, json_error_t *error) +{ + json_t *result; + + lex->depth = 0; + + lex_scan(lex, error); + if(!(flags & JSON_DECODE_ANY)) { + if(lex->token != '[' && lex->token != '{') { + error_set(error, lex, json_error_invalid_syntax, "'[' or '{' expected"); + return NULL; + } + } + + result = parse_value(lex, flags, error); + if(!result) + return NULL; + + if(!(flags & JSON_DISABLE_EOF_CHECK)) { + lex_scan(lex, error); + if(lex->token != TOKEN_EOF) { + error_set(error, lex, json_error_end_of_input_expected, "end of file expected (%i)", lex->token); + json_decref(result); + return NULL; + } + } + + if(error) { + /* Save the position even though there was no error */ + error->position = (int)lex->stream.position; + } + + return result; +} + +typedef struct +{ + const char *data; + size_t pos; +} string_data_t; + +static int string_get(void *data) +{ + char c; + string_data_t *stream = (string_data_t *)data; + c = stream->data[stream->pos]; + if(c == '\0') + return EOF; + else + { + stream->pos++; + return (unsigned char)c; + } +} + +json_t *json_loads(const char *string, size_t flags, json_error_t *error) +{ + lex_t lex; + json_t *result; + string_data_t stream_data; + + jsonp_error_init(error, ""); + + if (string == NULL) { + error_set(error, NULL, json_error_invalid_argument, "wrong arguments"); + return NULL; + } + + stream_data.data = string; + stream_data.pos = 0; + + if(lex_init(&lex, string_get, flags, (void *)&stream_data)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} + +typedef struct +{ + const char *data; + size_t len; + size_t pos; +} buffer_data_t; + +static int buffer_get(void *data) +{ + char c; + buffer_data_t* stream = (buffer_data_t*)data; + if(stream->pos >= stream->len) + return EOF; + + c = stream->data[stream->pos]; + stream->pos++; + return (unsigned char)c; +} + +json_t *json_loadb(const char *buffer, size_t buflen, size_t flags, json_error_t *error) +{ + lex_t lex; + json_t *result; + buffer_data_t stream_data; + + jsonp_error_init(error, ""); + + if (buffer == NULL) { + error_set(error, NULL, json_error_invalid_argument, "wrong arguments"); + return NULL; + } + + stream_data.data = buffer; + stream_data.pos = 0; + stream_data.len = buflen; + + if(lex_init(&lex, buffer_get, flags, (void *)&stream_data)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} + +json_t *json_loadf(FILE *input, size_t flags, json_error_t *error) +{ + lex_t lex; + const char *source; + json_t *result; + + if(input == stdin) + source = ""; + else + source = ""; + + jsonp_error_init(error, source); + + if (input == NULL) { + error_set(error, NULL, json_error_invalid_argument, "wrong arguments"); + return NULL; + } + + if(lex_init(&lex, (get_func)fgetc, flags, input)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} + +static int fd_get_func(int *fd) +{ + (void)fd; +#ifdef HAVE_UNISTD_H + uint8_t c; + if (read(*fd, &c, 1) == 1) + return c; +#endif + return EOF; +} + +json_t *json_loadfd(int input, size_t flags, json_error_t *error) +{ + lex_t lex; + const char *source; + json_t *result; + +#ifdef HAVE_UNISTD_H + if(input == STDIN_FILENO) + source = ""; + else +#endif + source = ""; + + jsonp_error_init(error, source); + + if (input < 0) { + error_set(error, NULL, json_error_invalid_argument, "wrong arguments"); + return NULL; + } + + if(lex_init(&lex, (get_func)fd_get_func, flags, &input)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} + +json_t *json_load_file(const char *path, size_t flags, json_error_t *error) +{ + json_t *result; + FILE *fp; + + printf("Loading JSON file: %s\n", path); + + jsonp_error_init(error, path); + + if (path == NULL) { + error_set(error, NULL, json_error_invalid_argument, "wrong arguments"); + return NULL; + } + + fp = fopen(path, "rb"); + if(!fp) + { + error_set(error, NULL, json_error_cannot_open_file, "unable to open %s: %s", + path, strerror(errno)); + return NULL; + } + + result = json_loadf(fp, flags, error); + + fclose(fp); + return result; +} + +#define MAX_BUF_LEN 1024 + +typedef struct +{ + char data[MAX_BUF_LEN]; + size_t len; + size_t pos; + json_load_callback_t callback; + void *arg; +} callback_data_t; + +static int callback_get(void *data) +{ + char c; + callback_data_t* stream = (callback_data_t*)data; + + if(stream->pos >= stream->len) { + stream->pos = 0; + stream->len = stream->callback(stream->data, MAX_BUF_LEN, stream->arg); + if(stream->len == 0 || stream->len == (size_t)-1) + return EOF; + } + + c = stream->data[stream->pos]; + stream->pos++; + return (unsigned char)c; +} + +json_t *json_load_callback(json_load_callback_t callback, void *arg, size_t flags, json_error_t *error) +{ + lex_t lex; + json_t *result; + + callback_data_t stream_data; + + memset(&stream_data, 0, sizeof(stream_data)); + stream_data.callback = callback; + stream_data.arg = arg; + + jsonp_error_init(error, ""); + + if (callback == NULL) { + error_set(error, NULL, json_error_invalid_argument, "wrong arguments"); + return NULL; + } + + if(lex_init(&lex, (get_func)callback_get, flags, &stream_data)) + return NULL; + + result = parse_json(&lex, flags, error); + + lex_close(&lex); + return result; +} diff --git a/src/jansson/lookup3.h b/src/jansson/lookup3.h new file mode 100644 index 0000000..98d780a --- /dev/null +++ b/src/jansson/lookup3.h @@ -0,0 +1,71 @@ +#include +#include + +#define hashsize(n) ((size_t)1 << (n)) +#define hashmask(n) (hashsize(n) - 1) + +static inline uint32_t rot(uint32_t x, uint32_t k) { + return (x << k) | (x >> (32 - k)); +} + +#define MIX(a,b,c) do { \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} while (0) + +#define FINAL(a,b,c) do { \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c, 4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} while (0) + +/* ASan-safe Jenkins hashlittle: never reads past 'length' bytes */ +static uint32_t hashlittle(const void *key, size_t length, uint32_t initval) +{ + const uint8_t *p = (const uint8_t *)key; + uint32_t a, b, c; + + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; + + /* Handle most of the key in 12-byte chunks */ + while (length >= 12) { + uint32_t x; + /* use memcpy so the compiler won’t issue over-reads or require alignment */ + memcpy(&x, p + 0, 4); a += x; + memcpy(&x, p + 4, 4); b += x; + memcpy(&x, p + 8, 4); c += x; + + MIX(a, b, c); + p += 12; + length -= 12; + } + + /* Handle the last 0..11 bytes */ + switch (length) { + case 11: c += (uint32_t)p[10] << 16; /* fallthrough */ + case 10: c += (uint32_t)p[9] << 8; /* fallthrough */ + case 9: c += (uint32_t)p[8]; /* fallthrough */ + case 8: b += (uint32_t)p[7] << 24; /* fallthrough */ + case 7: b += (uint32_t)p[6] << 16; /* fallthrough */ + case 6: b += (uint32_t)p[5] << 8; /* fallthrough */ + case 5: b += (uint32_t)p[4]; /* fallthrough */ + case 4: a += (uint32_t)p[3] << 24; /* fallthrough */ + case 3: a += (uint32_t)p[2] << 16; /* fallthrough */ + case 2: a += (uint32_t)p[1] << 8; /* fallthrough */ + case 1: a += (uint32_t)p[0]; /* fallthrough */ + break; + case 0: /* nothing left */ break; + } + + FINAL(a, b, c); + return c; +} diff --git a/src/jansson/lookup3.h.copy b/src/jansson/lookup3.h.copy new file mode 100644 index 0000000..2266d5a --- /dev/null +++ b/src/jansson/lookup3.h.copy @@ -0,0 +1,383 @@ +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +------------------------------------------------------------------------------- +*/ +#ifndef LOOKUUP3_H +#define LOOKUUP3_H + +#include + +#include "jansson_config.h" + +#ifdef HAVE_STDINT_H +#include /* defines uint32_t etc */ +#endif + +#ifdef HAVE_SYS_PARAM_H +#include /* attempt to define endianness */ +#endif + +#ifdef HAVE_ENDIAN_H +# include /* attempt to define endianness */ +#endif + +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +# define HASH_BIG_ENDIAN 0 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 1 +#else +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 0 +#endif + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef NO_MASKING_TRICK + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; /* fall through */ + case 11: c+=((uint32_t)k[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k[9])<<8; /* fall through */ + case 9 : c+=k[8]; /* fall through */ + case 8 : b+=((uint32_t)k[7])<<24; /* fall through */ + case 7 : b+=((uint32_t)k[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k[5])<<8; /* fall through */ + case 5 : b+=k[4]; /* fall through */ + case 4 : a+=((uint32_t)k[3])<<24; /* fall through */ + case 3 : a+=((uint32_t)k[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k[1])<<8; /* fall through */ + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} + +#endif //LOOKUUP3_H diff --git a/src/jansson/memory.c b/src/jansson/memory.c new file mode 100644 index 0000000..c10d765 --- /dev/null +++ b/src/jansson/memory.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * Copyright (c) 2011-2012 Basile Starynkevitch + * + * Jansson is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include + +#include "jansson.h" +#include "jansson_private.h" + +/* C89 allows these to be macros */ +#undef malloc +#undef free + +/* memory function pointers */ +static json_malloc_t do_malloc = malloc; +static json_free_t do_free = free; + +void *jsonp_malloc(size_t size) +{ + if(!size) + return NULL; + + return (*do_malloc)(size); +} + +void jsonp_free(void *ptr) +{ + if(!ptr) + return; + + (*do_free)(ptr); +} + +char *jsonp_strdup(const char *str) +{ + return jsonp_strndup(str, strlen(str)); +} + +char *jsonp_strndup(const char *str, size_t len) +{ + char *new_str; + + new_str = (char*)jsonp_malloc(len + 1); + if(!new_str) + return NULL; + + memcpy(new_str, str, len); + new_str[len] = '\0'; + return new_str; +} + +void json_set_alloc_funcs(json_malloc_t malloc_fn, json_free_t free_fn) +{ + do_malloc = malloc_fn; + do_free = free_fn; +} + +void json_get_alloc_funcs(json_malloc_t *malloc_fn, json_free_t *free_fn) +{ + if (malloc_fn) + *malloc_fn = do_malloc; + if (free_fn) + *free_fn = do_free; +} diff --git a/src/jansson/pack_unpack.c b/src/jansson/pack_unpack.c new file mode 100644 index 0000000..3b99776 --- /dev/null +++ b/src/jansson/pack_unpack.c @@ -0,0 +1,952 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * Copyright (c) 2011-2012 Graeme Smecher + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include "jansson.h" +#include "jansson_private.h" +#include "utf.h" + +typedef struct { + int line; + int column; + size_t pos; + char token; +} token_t; + +typedef struct { + const char *start; + const char *fmt; + token_t prev_token; + token_t token; + token_t next_token; + json_error_t *error; + size_t flags; + int line; + int column; + size_t pos; + int has_error; +} scanner_t; + +#define token(scanner) ((scanner)->token.token) + +static const char * const type_names[] = { + "object", + "array", + "string", + "integer", + "real", + "true", + "false", + "null" +}; + +#define type_name(x) type_names[json_typeof(x)] + +static const char unpack_value_starters[] = "{[siIbfFOon"; + +static void scanner_init(scanner_t *s, json_error_t *error, + size_t flags, const char *fmt) +{ + s->error = error; + s->flags = flags; + s->fmt = s->start = fmt; + memset(&s->prev_token, 0, sizeof(token_t)); + memset(&s->token, 0, sizeof(token_t)); + memset(&s->next_token, 0, sizeof(token_t)); + s->line = 1; + s->column = 0; + s->pos = 0; + s->has_error = 0; +} + +static void next_token(scanner_t *s) +{ + const char *t; + s->prev_token = s->token; + + if(s->next_token.line) { + s->token = s->next_token; + s->next_token.line = 0; + return; + } + + if (!token(s) && !*s->fmt) + return; + + t = s->fmt; + s->column++; + s->pos++; + + /* skip space and ignored chars */ + while(*t == ' ' || *t == '\t' || *t == '\n' || *t == ',' || *t == ':') { + if(*t == '\n') { + s->line++; + s->column = 1; + } + else + s->column++; + + s->pos++; + t++; + } + + s->token.token = *t; + s->token.line = s->line; + s->token.column = s->column; + s->token.pos = s->pos; + + if (*t) t++; + s->fmt = t; +} + +static void prev_token(scanner_t *s) +{ + s->next_token = s->token; + s->token = s->prev_token; +} + +static void set_error(scanner_t *s, const char *source, enum json_error_code code, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + jsonp_error_vset(s->error, s->token.line, s->token.column, s->token.pos, + code, fmt, ap); + + jsonp_error_set_source(s->error, source); + + va_end(ap); +} + +static json_t *pack(scanner_t *s, va_list *ap); + + +/* ours will be set to 1 if jsonp_free() must be called for the result + afterwards */ +static char *read_string(scanner_t *s, va_list *ap, + const char *purpose, size_t *out_len, int *ours, int optional) +{ + char t; + strbuffer_t strbuff; + const char *str; + size_t length; + + next_token(s); + t = token(s); + prev_token(s); + + *ours = 0; + if(t != '#' && t != '%' && t != '+') { + /* Optimize the simple case */ + str = va_arg(*ap, const char *); + + if(!str) { + if (!optional) { + set_error(s, "", json_error_null_value, "NULL %s", purpose); + s->has_error = 1; + } + return NULL; + } + + length = strlen(str); + + if(!utf8_check_string(str, length)) { + set_error(s, "", json_error_invalid_utf8, "Invalid UTF-8 %s", purpose); + s->has_error = 1; + return NULL; + } + + *out_len = length; + return (char *)str; + } else if (optional) { + set_error(s, "", json_error_invalid_format, "Cannot use '%c' on optional strings", t); + s->has_error = 1; + + return NULL; + } + + if(strbuffer_init(&strbuff)) { + set_error(s, "", json_error_out_of_memory, "Out of memory"); + s->has_error = 1; + } + + while(1) { + str = va_arg(*ap, const char *); + if(!str) { + set_error(s, "", json_error_null_value, "NULL %s", purpose); + s->has_error = 1; + } + + next_token(s); + + if(token(s) == '#') { + length = va_arg(*ap, int); + } + else if(token(s) == '%') { + length = va_arg(*ap, size_t); + } + else { + prev_token(s); + length = s->has_error ? 0 : strlen(str); + } + + if(!s->has_error && strbuffer_append_bytes(&strbuff, str, length) == -1) { + set_error(s, "", json_error_out_of_memory, "Out of memory"); + s->has_error = 1; + } + + next_token(s); + if(token(s) != '+') { + prev_token(s); + break; + } + } + + if(s->has_error) { + strbuffer_close(&strbuff); + return NULL; + } + + if(!utf8_check_string(strbuff.value, strbuff.length)) { + set_error(s, "", json_error_invalid_utf8, "Invalid UTF-8 %s", purpose); + strbuffer_close(&strbuff); + s->has_error = 1; + return NULL; + } + + *out_len = strbuff.length; + *ours = 1; + return strbuffer_steal_value(&strbuff); +} + +static json_t *pack_object(scanner_t *s, va_list *ap) +{ + json_t *object = json_object(); + next_token(s); + + while(token(s) != '}') { + char *key; + size_t len; + int ours; + json_t *value; + char valueOptional; + + if(!token(s)) { + set_error(s, "", json_error_invalid_format, "Unexpected end of format string"); + goto error; + } + + if(token(s) != 's') { + set_error(s, "", json_error_invalid_format, "Expected format 's', got '%c'", token(s)); + goto error; + } + + key = read_string(s, ap, "object key", &len, &ours, 0); + + next_token(s); + + next_token(s); + valueOptional = token(s); + prev_token(s); + + value = pack(s, ap); + if(!value) { + if(ours) + jsonp_free(key); + + if(valueOptional != '*') { + set_error(s, "", json_error_null_value, "NULL object value"); + s->has_error = 1; + } + + next_token(s); + continue; + } + + if(s->has_error) + json_decref(value); + + if(!s->has_error && json_object_set_new_nocheck(object, key, value)) { + set_error(s, "", json_error_out_of_memory, "Unable to add key \"%s\"", key); + s->has_error = 1; + } + + if(ours) + jsonp_free(key); + + next_token(s); + } + + if(!s->has_error) + return object; + +error: + json_decref(object); + return NULL; +} + +static json_t *pack_array(scanner_t *s, va_list *ap) +{ + json_t *array = json_array(); + next_token(s); + + while(token(s) != ']') { + json_t *value; + char valueOptional; + + if(!token(s)) { + set_error(s, "", json_error_invalid_format, "Unexpected end of format string"); + /* Format string errors are unrecoverable. */ + goto error; + } + + next_token(s); + valueOptional = token(s); + prev_token(s); + + value = pack(s, ap); + if(!value) { + if(valueOptional != '*') { + s->has_error = 1; + } + + next_token(s); + continue; + } + + if(s->has_error) + json_decref(value); + + if(!s->has_error && json_array_append_new(array, value)) { + set_error(s, "", json_error_out_of_memory, "Unable to append to array"); + s->has_error = 1; + } + + next_token(s); + } + + if(!s->has_error) + return array; + +error: + json_decref(array); + return NULL; +} + +static json_t *pack_string(scanner_t *s, va_list *ap) +{ + char *str; + char t; + size_t len; + int ours; + int optional; + + next_token(s); + t = token(s); + optional = t == '?' || t == '*'; + if (!optional) + prev_token(s); + + str = read_string(s, ap, "string", &len, &ours, optional); + + if (!str) + return t == '?' && !s->has_error ? json_null() : NULL; + + if (s->has_error) { + /* It's impossible to reach this point if ours != 0, do not free str. */ + return NULL; + } + + if (ours) + return jsonp_stringn_nocheck_own(str, len); + + return json_stringn_nocheck(str, len); +} + +static json_t *pack_object_inter(scanner_t *s, va_list *ap, int need_incref) +{ + json_t *json; + char ntoken; + + next_token(s); + ntoken = token(s); + + if (ntoken != '?' && ntoken != '*') + prev_token(s); + + json = va_arg(*ap, json_t *); + + if (json) + return need_incref ? json_incref(json) : json; + + switch (ntoken) { + case '?': + return json_null(); + case '*': + return NULL; + default: + break; + } + + set_error(s, "", json_error_null_value, "NULL object"); + s->has_error = 1; + return NULL; +} + +static json_t *pack_integer(scanner_t *s, json_int_t value) +{ + json_t *json = json_integer(value); + + if (!json) { + set_error(s, "", json_error_out_of_memory, "Out of memory"); + s->has_error = 1; + } + + return json; +} + +static json_t *pack_real(scanner_t *s, double value) +{ + /* Allocate without setting value so we can identify OOM error. */ + json_t *json = json_real(0.0); + + if (!json) { + set_error(s, "", json_error_out_of_memory, "Out of memory"); + s->has_error = 1; + + return NULL; + } + + if (json_real_set(json, value)) { + json_decref(json); + + set_error(s, "", json_error_numeric_overflow, "Invalid floating point value"); + s->has_error = 1; + + return NULL; + } + + return json; +} + +static json_t *pack(scanner_t *s, va_list *ap) +{ + switch(token(s)) { + case '{': + return pack_object(s, ap); + + case '[': + return pack_array(s, ap); + + case 's': /* string */ + return pack_string(s, ap); + + case 'n': /* null */ + return json_null(); + + case 'b': /* boolean */ + return va_arg(*ap, int) ? json_true() : json_false(); + + case 'i': /* integer from int */ + return pack_integer(s, va_arg(*ap, int)); + + case 'I': /* integer from json_int_t */ + return pack_integer(s, va_arg(*ap, json_int_t)); + + case 'f': /* real */ + return pack_real(s, va_arg(*ap, double)); + + case 'O': /* a json_t object; increments refcount */ + return pack_object_inter(s, ap, 1); + + case 'o': /* a json_t object; doesn't increment refcount */ + return pack_object_inter(s, ap, 0); + + default: + set_error(s, "", json_error_invalid_format, "Unexpected format character '%c'", + token(s)); + s->has_error = 1; + return NULL; + } +} + +static int unpack(scanner_t *s, json_t *root, va_list *ap); + +static int unpack_object(scanner_t *s, json_t *root, va_list *ap) +{ + int ret = -1; + int strict = 0; + int gotopt = 0; + + /* Use a set (emulated by a hashtable) to check that all object + keys are accessed. Checking that the correct number of keys + were accessed is not enough, as the same key can be unpacked + multiple times. + */ + hashtable_t key_set; + + if(hashtable_init(&key_set)) { + set_error(s, "", json_error_out_of_memory, "Out of memory"); + return -1; + } + + if(root && !json_is_object(root)) { + set_error(s, "", json_error_wrong_type, "Expected object, got %s", + type_name(root)); + goto out; + } + next_token(s); + + while(token(s) != '}') { + const char *key; + json_t *value; + int opt = 0; + + if(strict != 0) { + set_error(s, "", json_error_invalid_format, "Expected '}' after '%c', got '%c'", + (strict == 1 ? '!' : '*'), token(s)); + goto out; + } + + if(!token(s)) { + set_error(s, "", json_error_invalid_format, "Unexpected end of format string"); + goto out; + } + + if(token(s) == '!' || token(s) == '*') { + strict = (token(s) == '!' ? 1 : -1); + next_token(s); + continue; + } + + if(token(s) != 's') { + set_error(s, "", json_error_invalid_format, "Expected format 's', got '%c'", token(s)); + goto out; + } + + key = va_arg(*ap, const char *); + if(!key) { + set_error(s, "", json_error_null_value, "NULL object key"); + goto out; + } + + next_token(s); + + if(token(s) == '?') { + opt = gotopt = 1; + next_token(s); + } + + if(!root) { + /* skipping */ + value = NULL; + } + else { + value = json_object_get(root, key); + if(!value && !opt) { + set_error(s, "", json_error_item_not_found, "Object item not found: %s", key); + goto out; + } + } + + if(unpack(s, value, ap)) + goto out; + + hashtable_set(&key_set, key, json_null()); + next_token(s); + } + + if(strict == 0 && (s->flags & JSON_STRICT)) + strict = 1; + + if(root && strict == 1) { + /* We need to check that all non optional items have been parsed */ + const char *key; + /* keys_res is 1 for uninitialized, 0 for success, -1 for error. */ + int keys_res = 1; + strbuffer_t unrecognized_keys; + json_t *value; + long unpacked = 0; + + if (gotopt || json_object_size(root) != key_set.size) { + json_object_foreach(root, key, value) { + if(!hashtable_get(&key_set, key)) { + unpacked++; + + /* Save unrecognized keys for the error message */ + if (keys_res == 1) { + keys_res = strbuffer_init(&unrecognized_keys); + } else if (!keys_res) { + keys_res = strbuffer_append_bytes(&unrecognized_keys, ", ", 2); + } + + if (!keys_res) + keys_res = strbuffer_append_bytes(&unrecognized_keys, key, strlen(key)); + } + } + } + if (unpacked) { + set_error(s, "", json_error_end_of_input_expected, + "%li object item(s) left unpacked: %s", + unpacked, + keys_res ? "" : strbuffer_value(&unrecognized_keys)); + strbuffer_close(&unrecognized_keys); + goto out; + } + } + + ret = 0; + +out: + hashtable_close(&key_set); + return ret; +} + +static int unpack_array(scanner_t *s, json_t *root, va_list *ap) +{ + size_t i = 0; + int strict = 0; + + if(root && !json_is_array(root)) { + set_error(s, "", json_error_wrong_type, "Expected array, got %s", type_name(root)); + return -1; + } + next_token(s); + + while(token(s) != ']') { + json_t *value; + + if(strict != 0) { + set_error(s, "", json_error_invalid_format, "Expected ']' after '%c', got '%c'", + (strict == 1 ? '!' : '*'), + token(s)); + return -1; + } + + if(!token(s)) { + set_error(s, "", json_error_invalid_format, "Unexpected end of format string"); + return -1; + } + + if(token(s) == '!' || token(s) == '*') { + strict = (token(s) == '!' ? 1 : -1); + next_token(s); + continue; + } + + if(!strchr(unpack_value_starters, token(s))) { + set_error(s, "", json_error_invalid_format, "Unexpected format character '%c'", + token(s)); + return -1; + } + + if(!root) { + /* skipping */ + value = NULL; + } + else { + value = json_array_get(root, i); + if(!value) { + set_error(s, "", json_error_index_out_of_range, "Array index %lu out of range", + (unsigned long)i); + return -1; + } + } + + if(unpack(s, value, ap)) + return -1; + + next_token(s); + i++; + } + + if(strict == 0 && (s->flags & JSON_STRICT)) + strict = 1; + + if(root && strict == 1 && i != json_array_size(root)) { + long diff = (long)json_array_size(root) - (long)i; + set_error(s, "", json_error_end_of_input_expected, "%li array item(s) left unpacked", diff); + return -1; + } + + return 0; +} + +static int unpack(scanner_t *s, json_t *root, va_list *ap) +{ + switch(token(s)) + { + case '{': + return unpack_object(s, root, ap); + + case '[': + return unpack_array(s, root, ap); + + case 's': + if(root && !json_is_string(root)) { + set_error(s, "", json_error_wrong_type, "Expected string, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + const char **str_target; + size_t *len_target = NULL; + + str_target = va_arg(*ap, const char **); + if(!str_target) { + set_error(s, "", json_error_null_value, "NULL string argument"); + return -1; + } + + next_token(s); + + if(token(s) == '%') { + len_target = va_arg(*ap, size_t *); + if(!len_target) { + set_error(s, "", json_error_null_value, "NULL string length argument"); + return -1; + } + } + else + prev_token(s); + + if(root) { + *str_target = json_string_value(root); + if(len_target) + *len_target = json_string_length(root); + } + } + return 0; + + case 'i': + if(root && !json_is_integer(root)) { + set_error(s, "", json_error_wrong_type, "Expected integer, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + int *target = va_arg(*ap, int*); + if(root) + *target = (int)json_integer_value(root); + } + + return 0; + + case 'I': + if(root && !json_is_integer(root)) { + set_error(s, "", json_error_wrong_type, "Expected integer, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + json_int_t *target = va_arg(*ap, json_int_t*); + if(root) + *target = json_integer_value(root); + } + + return 0; + + case 'b': + if(root && !json_is_boolean(root)) { + set_error(s, "", json_error_wrong_type, "Expected true or false, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + int *target = va_arg(*ap, int*); + if(root) + *target = json_is_true(root); + } + + return 0; + + case 'f': + if(root && !json_is_real(root)) { + set_error(s, "", json_error_wrong_type, "Expected real, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + double *target = va_arg(*ap, double*); + if(root) + *target = json_real_value(root); + } + + return 0; + + case 'F': + if(root && !json_is_number(root)) { + set_error(s, "", json_error_wrong_type, "Expected real or integer, got %s", + type_name(root)); + return -1; + } + + if(!(s->flags & JSON_VALIDATE_ONLY)) { + double *target = va_arg(*ap, double*); + if(root) + *target = json_number_value(root); + } + + return 0; + + case 'O': + if(root && !(s->flags & JSON_VALIDATE_ONLY)) + json_incref(root); + /* Fall through */ + + case 'o': + if(!(s->flags & JSON_VALIDATE_ONLY)) { + json_t **target = va_arg(*ap, json_t**); + if(root) + *target = root; + } + + return 0; + + case 'n': + /* Never assign, just validate */ + if(root && !json_is_null(root)) { + set_error(s, "", json_error_wrong_type, "Expected null, got %s", + type_name(root)); + return -1; + } + return 0; + + default: + set_error(s, "", json_error_invalid_format, "Unexpected format character '%c'", + token(s)); + return -1; + } +} + +json_t *json_vpack_ex(json_error_t *error, size_t flags, + const char *fmt, va_list ap) +{ + scanner_t s; + va_list ap_copy; + json_t *value; + + if(!fmt || !*fmt) { + jsonp_error_init(error, ""); + jsonp_error_set(error, -1, -1, 0, json_error_invalid_argument, "NULL or empty format string"); + return NULL; + } + jsonp_error_init(error, NULL); + + scanner_init(&s, error, flags, fmt); + next_token(&s); + + va_copy(ap_copy, ap); + value = pack(&s, &ap_copy); + va_end(ap_copy); + + /* This will cover all situations where s.has_error is true */ + if(!value) + return NULL; + + next_token(&s); + if(token(&s)) { + json_decref(value); + set_error(&s, "", json_error_invalid_format, "Garbage after format string"); + return NULL; + } + + return value; +} + +json_t *json_pack_ex(json_error_t *error, size_t flags, const char *fmt, ...) +{ + json_t *value; + va_list ap; + + va_start(ap, fmt); + value = json_vpack_ex(error, flags, fmt, ap); + va_end(ap); + + return value; +} + +json_t *json_pack(const char *fmt, ...) +{ + json_t *value; + va_list ap; + + va_start(ap, fmt); + value = json_vpack_ex(NULL, 0, fmt, ap); + va_end(ap); + + return value; +} + +int json_vunpack_ex(json_t *root, json_error_t *error, size_t flags, + const char *fmt, va_list ap) +{ + scanner_t s; + va_list ap_copy; + + if(!root) { + jsonp_error_init(error, ""); + jsonp_error_set(error, -1, -1, 0, json_error_null_value, "NULL root value"); + return -1; + } + + if(!fmt || !*fmt) { + jsonp_error_init(error, ""); + jsonp_error_set(error, -1, -1, 0, json_error_invalid_argument, "NULL or empty format string"); + return -1; + } + jsonp_error_init(error, NULL); + + scanner_init(&s, error, flags, fmt); + next_token(&s); + + va_copy(ap_copy, ap); + if(unpack(&s, root, &ap_copy)) { + va_end(ap_copy); + return -1; + } + va_end(ap_copy); + + next_token(&s); + if(token(&s)) { + set_error(&s, "", json_error_invalid_format, "Garbage after format string"); + return -1; + } + + return 0; +} + +int json_unpack_ex(json_t *root, json_error_t *error, size_t flags, const char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = json_vunpack_ex(root, error, flags, fmt, ap); + va_end(ap); + + return ret; +} + +int json_unpack(json_t *root, const char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = json_vunpack_ex(root, NULL, 0, fmt, ap); + va_end(ap); + + return ret; +} diff --git a/src/jansson/strbuffer.c b/src/jansson/strbuffer.c new file mode 100644 index 0000000..9beaca4 --- /dev/null +++ b/src/jansson/strbuffer.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include "jansson_private.h" +#include "strbuffer.h" + +#define STRBUFFER_MIN_SIZE 16 +#define STRBUFFER_FACTOR 2 +#define STRBUFFER_SIZE_MAX ((size_t)-1) + +int strbuffer_init(strbuffer_t *strbuff) +{ + strbuff->size = STRBUFFER_MIN_SIZE; + strbuff->length = 0; + + strbuff->value = (char*)jsonp_malloc(strbuff->size); + if(!strbuff->value) + return -1; + + /* initialize to empty */ + strbuff->value[0] = '\0'; + return 0; +} + +void strbuffer_close(strbuffer_t *strbuff) +{ + if(strbuff->value) + jsonp_free(strbuff->value); + + strbuff->size = 0; + strbuff->length = 0; + strbuff->value = NULL; +} + +void strbuffer_clear(strbuffer_t *strbuff) +{ + strbuff->length = 0; + strbuff->value[0] = '\0'; +} + +const char *strbuffer_value(const strbuffer_t *strbuff) +{ + return strbuff->value; +} + +char *strbuffer_steal_value(strbuffer_t *strbuff) +{ + char *result = strbuff->value; + strbuff->value = NULL; + return result; +} + +int strbuffer_append_byte(strbuffer_t *strbuff, char byte) +{ + return strbuffer_append_bytes(strbuff, &byte, 1); +} + +int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, size_t size) +{ + if(size >= strbuff->size - strbuff->length) + { + size_t new_size; + char *new_value; + + /* avoid integer overflow */ + if (strbuff->size > STRBUFFER_SIZE_MAX / STRBUFFER_FACTOR + || size > STRBUFFER_SIZE_MAX - 1 + || strbuff->length > STRBUFFER_SIZE_MAX - 1 - size) + return -1; + + new_size = max(strbuff->size * STRBUFFER_FACTOR, + strbuff->length + size + 1); + + new_value = (char*)jsonp_malloc(new_size); + if(!new_value) + return -1; + + memcpy(new_value, strbuff->value, strbuff->length); + + jsonp_free(strbuff->value); + strbuff->value = new_value; + strbuff->size = new_size; + } + + memcpy(strbuff->value + strbuff->length, data, size); + strbuff->length += size; + strbuff->value[strbuff->length] = '\0'; + + return 0; +} + +char strbuffer_pop(strbuffer_t *strbuff) +{ + if(strbuff->length > 0) { + char c = strbuff->value[--strbuff->length]; + strbuff->value[strbuff->length] = '\0'; + return c; + } + else + return '\0'; +} diff --git a/src/jansson/strbuffer.h b/src/jansson/strbuffer.h new file mode 100644 index 0000000..ce1be59 --- /dev/null +++ b/src/jansson/strbuffer.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef STRBUFFER_H +#define STRBUFFER_H + +#include + +typedef struct { + char *value; + size_t length; /* bytes used */ + size_t size; /* bytes allocated */ +} strbuffer_t; + +int strbuffer_init(strbuffer_t *strbuff) JSON_ATTRS(warn_unused_result); +void strbuffer_close(strbuffer_t *strbuff); + +void strbuffer_clear(strbuffer_t *strbuff); + +const char *strbuffer_value(const strbuffer_t *strbuff); + +/* Steal the value and close the strbuffer */ +char *strbuffer_steal_value(strbuffer_t *strbuff); + +int strbuffer_append_byte(strbuffer_t *strbuff, char byte); +int strbuffer_append_bytes(strbuffer_t *strbuff, const char *data, size_t size); + +char strbuffer_pop(strbuffer_t *strbuff); + +#endif diff --git a/src/jansson/strconv.c b/src/jansson/strconv.c new file mode 100644 index 0000000..2ef5f75 --- /dev/null +++ b/src/jansson/strconv.c @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include +#ifdef __MINGW32__ +#undef __NO_ISOCEXT /* ensure stdlib.h will declare prototypes for mingw own 'strtod' replacement, called '__strtod' */ +#endif +#include "jansson_private.h" +#include "strbuffer.h" + +/* need jansson_config.h to get the correct snprintf */ +#include "jansson_config.h" + +#ifdef __MINGW32__ +#define strtod __strtod +#endif + +#if JSON_HAVE_LOCALECONV +#include + +/* + - This code assumes that the decimal separator is exactly one + character. + + - If setlocale() is called by another thread between the call to + localeconv() and the call to sprintf() or strtod(), the result may + be wrong. setlocale() is not thread-safe and should not be used + this way. Multi-threaded programs should use uselocale() instead. +*/ + +static void to_locale(strbuffer_t *strbuffer) +{ + const char *point; + char *pos; + + point = localeconv()->decimal_point; + if(*point == '.') { + /* No conversion needed */ + return; + } + + pos = strchr(strbuffer->value, '.'); + if(pos) + *pos = *point; +} + +static void from_locale(char *buffer) +{ + const char *point; + char *pos; + + point = localeconv()->decimal_point; + if(*point == '.') { + /* No conversion needed */ + return; + } + + pos = strchr(buffer, *point); + if(pos) + *pos = '.'; +} +#endif + +int jsonp_strtod(strbuffer_t *strbuffer, double *out) +{ + double value; + char *end; + +#if JSON_HAVE_LOCALECONV + to_locale(strbuffer); +#endif + + errno = 0; + value = strtod(strbuffer->value, &end); + assert(end == strbuffer->value + strbuffer->length); + + if((value == HUGE_VAL || value == -HUGE_VAL) && errno == ERANGE) { + /* Overflow */ + return -1; + } + + *out = value; + return 0; +} + +int jsonp_dtostr(char *buffer, size_t size, double value, int precision) +{ + int ret; + char *start, *end; + size_t length; + + if (precision == 0) + precision = 17; + + ret = snprintf(buffer, size, "%.*g", precision, value); + if(ret < 0) + return -1; + + length = (size_t)ret; + if(length >= size) + return -1; + +#if JSON_HAVE_LOCALECONV + from_locale(buffer); +#endif + + /* Make sure there's a dot or 'e' in the output. Otherwise + a real is converted to an integer when decoding */ + if(strchr(buffer, '.') == NULL && + strchr(buffer, 'e') == NULL) + { + if(length + 3 >= size) { + /* No space to append ".0" */ + return -1; + } + buffer[length] = '.'; + buffer[length + 1] = '0'; + buffer[length + 2] = '\0'; + length += 2; + } + + /* Remove leading '+' from positive exponent. Also remove leading + zeros from exponents (added by some printf() implementations) */ + start = strchr(buffer, 'e'); + if(start) { + start++; + end = start + 1; + + if(*start == '-') + start++; + + while(*end == '0') + end++; + + if(end != start) { + memmove(start, end, length - (size_t)(end - buffer)); + length -= (size_t)(end - start); + } + } + + return (int)length; +} diff --git a/src/jansson/utf.c b/src/jansson/utf.c new file mode 100644 index 0000000..be966cb --- /dev/null +++ b/src/jansson/utf.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include "utf.h" + +int utf8_encode(int32_t codepoint, char *buffer, size_t *size) +{ + if(codepoint < 0) + return -1; + else if(codepoint < 0x80) + { + buffer[0] = (char)codepoint; + *size = 1; + } + else if(codepoint < 0x800) + { + buffer[0] = 0xC0 + ((codepoint & 0x7C0) >> 6); + buffer[1] = 0x80 + ((codepoint & 0x03F)); + *size = 2; + } + else if(codepoint < 0x10000) + { + buffer[0] = 0xE0 + ((codepoint & 0xF000) >> 12); + buffer[1] = 0x80 + ((codepoint & 0x0FC0) >> 6); + buffer[2] = 0x80 + ((codepoint & 0x003F)); + *size = 3; + } + else if(codepoint <= 0x10FFFF) + { + buffer[0] = 0xF0 + ((codepoint & 0x1C0000) >> 18); + buffer[1] = 0x80 + ((codepoint & 0x03F000) >> 12); + buffer[2] = 0x80 + ((codepoint & 0x000FC0) >> 6); + buffer[3] = 0x80 + ((codepoint & 0x00003F)); + *size = 4; + } + else + return -1; + + return 0; +} + +size_t utf8_check_first(char byte) +{ + unsigned char u = (unsigned char)byte; + + if(u < 0x80) + return 1; + + if(0x80 <= u && u <= 0xBF) { + /* second, third or fourth byte of a multi-byte + sequence, i.e. a "continuation byte" */ + return 0; + } + else if(u == 0xC0 || u == 0xC1) { + /* overlong encoding of an ASCII byte */ + return 0; + } + else if(0xC2 <= u && u <= 0xDF) { + /* 2-byte sequence */ + return 2; + } + + else if(0xE0 <= u && u <= 0xEF) { + /* 3-byte sequence */ + return 3; + } + else if(0xF0 <= u && u <= 0xF4) { + /* 4-byte sequence */ + return 4; + } + else { /* u >= 0xF5 */ + /* Restricted (start of 4-, 5- or 6-byte sequence) or invalid + UTF-8 */ + return 0; + } +} + +size_t utf8_check_full(const char *buffer, size_t size, int32_t *codepoint) +{ + size_t i; + int32_t value = 0; + unsigned char u = (unsigned char)buffer[0]; + + if(size == 2) + { + value = u & 0x1F; + } + else if(size == 3) + { + value = u & 0xF; + } + else if(size == 4) + { + value = u & 0x7; + } + else + return 0; + + for(i = 1; i < size; i++) + { + u = (unsigned char)buffer[i]; + + if(u < 0x80 || u > 0xBF) { + /* not a continuation byte */ + return 0; + } + + value = (value << 6) + (u & 0x3F); + } + + if(value > 0x10FFFF) { + /* not in Unicode range */ + return 0; + } + + else if(0xD800 <= value && value <= 0xDFFF) { + /* invalid code point (UTF-16 surrogate halves) */ + return 0; + } + + else if((size == 2 && value < 0x80) || + (size == 3 && value < 0x800) || + (size == 4 && value < 0x10000)) { + /* overlong encoding */ + return 0; + } + + if(codepoint) + *codepoint = value; + + return 1; +} + +const char *utf8_iterate(const char *buffer, size_t bufsize, int32_t *codepoint) +{ + size_t count; + int32_t value; + + if(!bufsize) + return buffer; + + count = utf8_check_first(buffer[0]); + if(count <= 0) + return NULL; + + if(count == 1) + value = (unsigned char)buffer[0]; + else + { + if(count > bufsize || !utf8_check_full(buffer, count, &value)) + return NULL; + } + + if(codepoint) + *codepoint = value; + + return buffer + count; +} + +int utf8_check_string(const char *string, size_t length) +{ + size_t i; + + for(i = 0; i < length; i++) + { + size_t count = utf8_check_first(string[i]); + if(count == 0) + return 0; + else if(count > 1) + { + if(count > length - i) + return 0; + + if(!utf8_check_full(&string[i], count, NULL)) + return 0; + + i += count - 1; + } + } + + return 1; +} diff --git a/src/jansson/utf.h b/src/jansson/utf.h new file mode 100644 index 0000000..ad74357 --- /dev/null +++ b/src/jansson/utf.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef UTF_H +#define UTF_H + +#include "jansson_config.h" + +#ifdef HAVE_STDINT_H +#include +#endif + +int utf8_encode(int32_t codepoint, char *buffer, size_t *size); + +size_t utf8_check_first(char byte); +size_t utf8_check_full(const char *buffer, size_t size, int32_t *codepoint); +const char *utf8_iterate(const char *buffer, size_t size, int32_t *codepoint); + +int utf8_check_string(const char *string, size_t length); + +#endif diff --git a/src/jansson/value.c b/src/jansson/value.c new file mode 100644 index 0000000..f65844d --- /dev/null +++ b/src/jansson/value.c @@ -0,0 +1,1076 @@ +/* + * Copyright (c) 2009-2016 Petri Lehtinen + * + * Jansson is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "jansson_config.h" + + +#include +#include +#include +#include + +#ifdef HAVE_STDINT_H +#include +#endif + +#include "jansson.h" +#include "hashtable.h" +#include "jansson_private.h" +#include "utf.h" + +/* Work around nonstandard isnan() and isinf() implementations */ + +/* +#ifndef isnan +#ifndef __sun +JSON_INLINE int isnan(double x) { return x != x; } +#endif +#endif +#ifndef isinf +JSON_INLINE int isinf(double x) { return !isnan(x) && isnan(x - x); } +#endif +*/ + +JSON_INLINE void json_init(json_t *json, json_type type) +{ + json->type = type; + json->refcount = 1; +} + + +/*** object ***/ + +extern volatile uint32_t hashtable_seed; + +json_t *json_object(void) +{ + json_object_t* object = (json_object_t*)jsonp_malloc(sizeof(json_object_t)); + if(!object) + return NULL; + + if (!hashtable_seed) { + /* Autoseed */ + json_object_seed(0); + } + + json_init(&object->json, JSON_OBJECT); + + if(hashtable_init(&object->hashtable)) + { + jsonp_free(object); + return NULL; + } + + return &object->json; +} + +static void json_delete_object(json_object_t *object) +{ + hashtable_close(&object->hashtable); + jsonp_free(object); +} + +size_t json_object_size(const json_t *json) +{ + json_object_t *object; + + if(!json_is_object(json)) + return 0; + + object = json_to_object(json); + return object->hashtable.size; +} + +json_t* json_object_get(const json_t *json, const char *key) +{ + json_object_t* object; + + if(!key || !json_is_object(json)) + return NULL; + + object = json_to_object(json); + return (json_t*)hashtable_get(&object->hashtable, key); +} + +int json_object_set_new_nocheck(json_t *json, const char *key, json_t *value) +{ + json_object_t *object; + + if(!value) + return -1; + + if(!key || !json_is_object(json) || json == value) + { + json_decref(value); + return -1; + } + object = json_to_object(json); + + if(hashtable_set(&object->hashtable, key, value)) + { + json_decref(value); + return -1; + } + + return 0; +} + +int json_object_set_new(json_t *json, const char *key, json_t *value) +{ + if(!key || !utf8_check_string(key, strlen(key))) + { + json_decref(value); + return -1; + } + + return json_object_set_new_nocheck(json, key, value); +} + +int json_object_del(json_t *json, const char *key) +{ + json_object_t *object; + + if(!key || !json_is_object(json)) + return -1; + + object = json_to_object(json); + return hashtable_del(&object->hashtable, key); +} + +int json_object_clear(json_t *json) +{ + json_object_t *object; + + if(!json_is_object(json)) + return -1; + + object = json_to_object(json); + hashtable_clear(&object->hashtable); + + return 0; +} + +int json_object_update(json_t *object, json_t *other) +{ + const char *key; + json_t *value; + + if(!json_is_object(object) || !json_is_object(other)) + return -1; + + json_object_foreach(other, key, value) { + if(json_object_set_nocheck(object, key, value)) + return -1; + } + + return 0; +} + +int json_object_update_existing(json_t *object, json_t *other) +{ + const char *key; + json_t *value; + + if(!json_is_object(object) || !json_is_object(other)) + return -1; + + json_object_foreach(other, key, value) { + if(json_object_get(object, key)) + json_object_set_nocheck(object, key, value); + } + + return 0; +} + +int json_object_update_missing(json_t *object, json_t *other) +{ + const char *key; + json_t *value; + + if(!json_is_object(object) || !json_is_object(other)) + return -1; + + json_object_foreach(other, key, value) { + if(!json_object_get(object, key)) + json_object_set_nocheck(object, key, value); + } + + return 0; +} + +void *json_object_iter(json_t *json) +{ + json_object_t *object; + + if(!json_is_object(json)) + return NULL; + + object = json_to_object(json); + return hashtable_iter(&object->hashtable); +} + +void *json_object_iter_at(json_t *json, const char *key) +{ + json_object_t *object; + + if(!key || !json_is_object(json)) + return NULL; + + object = json_to_object(json); + return hashtable_iter_at(&object->hashtable, key); +} + +void *json_object_iter_next(json_t *json, void *iter) +{ + json_object_t *object; + + if(!json_is_object(json) || iter == NULL) + return NULL; + + object = json_to_object(json); + return hashtable_iter_next(&object->hashtable, iter); +} + +const char *json_object_iter_key(void *iter) +{ + if(!iter) + return NULL; + + return (char*)hashtable_iter_key(iter); +} + +json_t *json_object_iter_value(void *iter) +{ + if(!iter) + return NULL; + + return (json_t *)hashtable_iter_value(iter); +} + +int json_object_iter_set_new(json_t *json, void *iter, json_t *value) +{ + if(!json_is_object(json) || !iter || !value) + { + json_decref(value); + return -1; + } + + hashtable_iter_set(iter, value); + return 0; +} + +void *json_object_key_to_iter(const char *key) +{ + if(!key) + return NULL; + + return hashtable_key_to_iter(key); +} + +static int json_object_equal(const json_t *object1, const json_t *object2) +{ + const char *key; + const json_t *value1, *value2; + + if(json_object_size(object1) != json_object_size(object2)) + return 0; + + json_object_foreach((json_t *)object1, key, value1) { + value2 = json_object_get(object2, key); + + if(!json_equal(value1, value2)) + return 0; + } + + return 1; +} + +static json_t *json_object_copy(json_t *object) +{ + json_t *result; + + const char *key; + json_t *value; + + result = json_object(); + if(!result) + return NULL; + + json_object_foreach(object, key, value) + json_object_set_nocheck(result, key, value); + + return result; +} + +static json_t *json_object_deep_copy(const json_t *object) +{ + json_t *result; + void *iter; + + result = json_object(); + if(!result) + return NULL; + + /* Cannot use json_object_foreach because object has to be cast + non-const */ + iter = json_object_iter((json_t *)object); + while(iter) { + const char *key; + const json_t *value; + key = json_object_iter_key(iter); + value = json_object_iter_value(iter); + + json_object_set_new_nocheck(result, key, json_deep_copy(value)); + iter = json_object_iter_next((json_t *)object, iter); + } + + return result; +} + + +/*** array ***/ + +json_t* json_array(void) +{ + json_array_t* array = (json_array_t*)jsonp_malloc(sizeof(json_array_t)); + if(!array) + return NULL; + json_init(&array->json, JSON_ARRAY); + + array->entries = 0; + array->size = 8; + + array->table = (json_t**)jsonp_malloc(array->size * sizeof(json_t *)); + if(!array->table) { + jsonp_free(array); + return NULL; + } + + return &array->json; +} + +static void json_delete_array(json_array_t *array) +{ + size_t i; + + for(i = 0; i < array->entries; i++) + json_decref(array->table[i]); + + jsonp_free(array->table); + jsonp_free(array); +} + +size_t json_array_size(const json_t *json) +{ + if(!json_is_array(json)) + return 0; + + return json_to_array(json)->entries; +} + +json_t *json_array_get(const json_t *json, size_t index) +{ + json_array_t *array; + if(!json_is_array(json)) + return NULL; + array = json_to_array(json); + + if(index >= array->entries) + return NULL; + + return array->table[index]; +} + +int json_array_set_new(json_t *json, size_t index, json_t *value) +{ + json_array_t *array; + + if(!value) + return -1; + + if(!json_is_array(json) || json == value) + { + json_decref(value); + return -1; + } + array = json_to_array(json); + + if(index >= array->entries) + { + json_decref(value); + return -1; + } + + json_decref(array->table[index]); + array->table[index] = value; + + return 0; +} + +static void array_move(json_array_t *array, size_t dest, + size_t src, size_t count) +{ + memmove(&array->table[dest], &array->table[src], count * sizeof(json_t *)); +} + +static void array_copy(json_t **dest, size_t dpos, + json_t **src, size_t spos, + size_t count) +{ + memcpy(&dest[dpos], &src[spos], count * sizeof(json_t *)); +} + +static json_t **json_array_grow(json_array_t *array, + size_t amount, + int copy) +{ + size_t new_size; + json_t **old_table, **new_table; + + if(array->entries + amount <= array->size) + return array->table; + + old_table = array->table; + + new_size = max(array->size + amount, array->size * 2); + new_table = (json_t**)jsonp_malloc(new_size * sizeof(json_t *)); + if(!new_table) + return NULL; + + array->size = new_size; + array->table = new_table; + + if(copy) { + array_copy(array->table, 0, old_table, 0, array->entries); + jsonp_free(old_table); + return array->table; + } + + return old_table; +} + +int json_array_append_new(json_t *json, json_t *value) +{ + json_array_t *array; + + if(!value) + return -1; + + if(!json_is_array(json) || json == value) + { + json_decref(value); + return -1; + } + array = json_to_array(json); + + if(!json_array_grow(array, 1, 1)) { + json_decref(value); + return -1; + } + + array->table[array->entries] = value; + array->entries++; + + return 0; +} + +int json_array_insert_new(json_t *json, size_t index, json_t *value) +{ + json_array_t *array; + json_t **old_table; + + if(!value) + return -1; + + if(!json_is_array(json) || json == value) { + json_decref(value); + return -1; + } + array = json_to_array(json); + + if(index > array->entries) { + json_decref(value); + return -1; + } + + old_table = json_array_grow(array, 1, 0); + if(!old_table) { + json_decref(value); + return -1; + } + + if(old_table != array->table) { + array_copy(array->table, 0, old_table, 0, index); + array_copy(array->table, index + 1, old_table, index, + array->entries - index); + jsonp_free(old_table); + } + else + array_move(array, index + 1, index, array->entries - index); + + array->table[index] = value; + array->entries++; + + return 0; +} + +int json_array_remove(json_t *json, size_t index) +{ + json_array_t *array; + + if(!json_is_array(json)) + return -1; + array = json_to_array(json); + + if(index >= array->entries) + return -1; + + json_decref(array->table[index]); + + /* If we're removing the last element, nothing has to be moved */ + if(index < array->entries - 1) + array_move(array, index, index + 1, array->entries - index - 1); + + array->entries--; + + return 0; +} + +int json_array_clear(json_t *json) +{ + json_array_t *array; + size_t i; + + if(!json_is_array(json)) + return -1; + array = json_to_array(json); + + for(i = 0; i < array->entries; i++) + json_decref(array->table[i]); + + array->entries = 0; + return 0; +} + +int json_array_extend(json_t *json, json_t *other_json) +{ + json_array_t *array, *other; + size_t i; + + if(!json_is_array(json) || !json_is_array(other_json)) + return -1; + array = json_to_array(json); + other = json_to_array(other_json); + + if(!json_array_grow(array, other->entries, 1)) + return -1; + + for(i = 0; i < other->entries; i++) + json_incref(other->table[i]); + + array_copy(array->table, array->entries, other->table, 0, other->entries); + + array->entries += other->entries; + return 0; +} + +static int json_array_equal(const json_t *array1, const json_t *array2) +{ + size_t i, size; + + size = json_array_size(array1); + if(size != json_array_size(array2)) + return 0; + + for(i = 0; i < size; i++) + { + json_t *value1, *value2; + + value1 = json_array_get(array1, i); + value2 = json_array_get(array2, i); + + if(!json_equal(value1, value2)) + return 0; + } + + return 1; +} + +static json_t *json_array_copy(json_t *array) +{ + json_t *result; + size_t i; + + result = json_array(); + if(!result) + return NULL; + + for(i = 0; i < json_array_size(array); i++) + json_array_append(result, json_array_get(array, i)); + + return result; +} + +static json_t *json_array_deep_copy(const json_t *array) +{ + json_t *result; + size_t i; + + result = json_array(); + if(!result) + return NULL; + + for(i = 0; i < json_array_size(array); i++) + json_array_append_new(result, json_deep_copy(json_array_get(array, i))); + + return result; +} + +/*** string ***/ + +static json_t *string_create(const char *value, size_t len, int own) +{ + char *v; + json_string_t* string; + + if(!value) + return NULL; + + if(own) + v = (char *)value; + else { + v = jsonp_strndup(value, len); + if(!v) + return NULL; + } + + string = (json_string_t*)jsonp_malloc(sizeof(json_string_t)); + if(!string) { + jsonp_free(v); + return NULL; + } + json_init(&string->json, JSON_STRING); + string->value = v; + string->length = len; + + return &string->json; +} + +json_t *json_string_nocheck(const char *value) +{ + if(!value) + return NULL; + + return string_create(value, strlen(value), 0); +} + +json_t *json_stringn_nocheck(const char *value, size_t len) +{ + return string_create(value, len, 0); +} + +/* this is private; "steal" is not a public API concept */ +json_t *jsonp_stringn_nocheck_own(const char *value, size_t len) +{ + return string_create(value, len, 1); +} + +json_t *json_string(const char *value) +{ + if(!value) + return NULL; + + return json_stringn(value, strlen(value)); +} + +json_t *json_stringn(const char *value, size_t len) +{ + if(!value || !utf8_check_string(value, len)) + return NULL; + + return json_stringn_nocheck(value, len); +} + +const char *json_string_value(const json_t *json) +{ + if(!json_is_string(json)) + return NULL; + + return json_to_string(json)->value; +} + +size_t json_string_length(const json_t *json) +{ + if(!json_is_string(json)) + return 0; + + return json_to_string(json)->length; +} + +int json_string_set_nocheck(json_t *json, const char *value) +{ + if(!value) + return -1; + + return json_string_setn_nocheck(json, value, strlen(value)); +} + +int json_string_setn_nocheck(json_t *json, const char *value, size_t len) +{ + char *dup; + json_string_t *string; + + if(!json_is_string(json) || !value) + return -1; + + dup = jsonp_strndup(value, len); + if(!dup) + return -1; + + string = json_to_string(json); + jsonp_free(string->value); + string->value = dup; + string->length = len; + + return 0; +} + +int json_string_set(json_t *json, const char *value) +{ + if(!value) + return -1; + + return json_string_setn(json, value, strlen(value)); +} + +int json_string_setn(json_t *json, const char *value, size_t len) +{ + if(!value || !utf8_check_string(value, len)) + return -1; + + return json_string_setn_nocheck(json, value, len); +} + +static void json_delete_string(json_string_t *string) +{ + jsonp_free(string->value); + jsonp_free(string); +} + +static int json_string_equal(const json_t *string1, const json_t *string2) +{ + json_string_t *s1, *s2; + + s1 = json_to_string(string1); + s2 = json_to_string(string2); + return s1->length == s2->length && !memcmp(s1->value, s2->value, s1->length); +} + +static json_t *json_string_copy(const json_t *string) +{ + json_string_t *s; + + s = json_to_string(string); + return json_stringn_nocheck(s->value, s->length); +} + +json_t *json_vsprintf(const char *fmt, va_list ap) { + json_t *json = NULL; + int length; + char *buf; + va_list aq; + va_copy(aq, ap); + + length = vsnprintf(NULL, 0, fmt, ap); + if (length == 0) { + json = json_string(""); + goto out; + } + + buf = (char*)jsonp_malloc(length + 1); + if (!buf) + goto out; + + vsnprintf(buf, length + 1, fmt, aq); + if (!utf8_check_string(buf, length)) { + jsonp_free(buf); + goto out; + } + + json = jsonp_stringn_nocheck_own(buf, length); + +out: + va_end(aq); + return json; +} + +json_t *json_sprintf(const char *fmt, ...) { + json_t *result; + va_list ap; + + va_start(ap, fmt); + result = json_vsprintf(fmt, ap); + va_end(ap); + + return result; +} + + +/*** integer ***/ + +json_t *json_integer(json_int_t value) +{ + json_integer_t* integer = (json_integer_t*)jsonp_malloc(sizeof(json_integer_t)); + if(!integer) + return NULL; + json_init(&integer->json, JSON_INTEGER); + + integer->value = value; + return &integer->json; +} + +json_int_t json_integer_value(const json_t *json) +{ + if(!json_is_integer(json)) + return 0; + + return json_to_integer(json)->value; +} + +int json_integer_set(json_t *json, json_int_t value) +{ + if(!json_is_integer(json)) + return -1; + + json_to_integer(json)->value = value; + + return 0; +} + +static void json_delete_integer(json_integer_t *integer) +{ + jsonp_free(integer); +} + +static int json_integer_equal(const json_t *integer1, const json_t *integer2) +{ + return json_integer_value(integer1) == json_integer_value(integer2); +} + +static json_t *json_integer_copy(const json_t *integer) +{ + return json_integer(json_integer_value(integer)); +} + + +/*** real ***/ + +json_t *json_real(double value) +{ + json_real_t* real; + + if(isnan(value) || isinf(value)) + return NULL; + + real = (json_real_t*)jsonp_malloc(sizeof(json_real_t)); + if(!real) + return NULL; + json_init(&real->json, JSON_REAL); + + real->value = value; + return &real->json; +} + +double json_real_value(const json_t *json) +{ + if(!json_is_real(json)) + return 0; + + return json_to_real(json)->value; +} + +int json_real_set(json_t *json, double value) +{ + if(!json_is_real(json) || isnan(value) || isinf(value)) + return -1; + + json_to_real(json)->value = value; + + return 0; +} + +static void json_delete_real(json_real_t *real) +{ + jsonp_free(real); +} + +static int json_real_equal(const json_t *real1, const json_t *real2) +{ + return json_real_value(real1) == json_real_value(real2); +} + +static json_t *json_real_copy(const json_t *real) +{ + return json_real(json_real_value(real)); +} + + +/*** number ***/ + +double json_number_value(const json_t *json) +{ + if(json_is_integer(json)) + return (double)json_integer_value(json); + else if(json_is_real(json)) + return json_real_value(json); + else + return 0.0; +} + + +/*** simple values ***/ + +json_t *json_true(void) +{ + static json_t the_true = {JSON_TRUE, (size_t)-1}; + return &the_true; +} + + +json_t *json_false(void) +{ + static json_t the_false = {JSON_FALSE, (size_t)-1}; + return &the_false; +} + + +json_t *json_null(void) +{ + static json_t the_null = {JSON_NULL, (size_t)-1}; + return &the_null; +} + + +/*** deletion ***/ + +void json_delete(json_t *json) +{ + if (!json) + return; + + switch(json_typeof(json)) { + case JSON_OBJECT: + json_delete_object(json_to_object(json)); + break; + case JSON_ARRAY: + json_delete_array(json_to_array(json)); + break; + case JSON_STRING: + json_delete_string(json_to_string(json)); + break; + case JSON_INTEGER: + json_delete_integer(json_to_integer(json)); + break; + case JSON_REAL: + json_delete_real(json_to_real(json)); + break; + default: + return; + } + + /* json_delete is not called for true, false or null */ +} + + +/*** equality ***/ + +int json_equal(const json_t *json1, const json_t *json2) +{ + if(!json1 || !json2) + return 0; + + if(json_typeof(json1) != json_typeof(json2)) + return 0; + + /* this covers true, false and null as they are singletons */ + if(json1 == json2) + return 1; + + switch(json_typeof(json1)) { + case JSON_OBJECT: + return json_object_equal(json1, json2); + case JSON_ARRAY: + return json_array_equal(json1, json2); + case JSON_STRING: + return json_string_equal(json1, json2); + case JSON_INTEGER: + return json_integer_equal(json1, json2); + case JSON_REAL: + return json_real_equal(json1, json2); + default: + return 0; + } +} + + +/*** copying ***/ + +json_t *json_copy(json_t *json) +{ + if(!json) + return NULL; + + switch(json_typeof(json)) { + case JSON_OBJECT: + return json_object_copy(json); + case JSON_ARRAY: + return json_array_copy(json); + case JSON_STRING: + return json_string_copy(json); + case JSON_INTEGER: + return json_integer_copy(json); + case JSON_REAL: + return json_real_copy(json); + case JSON_TRUE: + case JSON_FALSE: + case JSON_NULL: + return json; + default: + return NULL; + } +} + +json_t *json_deep_copy(const json_t *json) +{ + if(!json) + return NULL; + + switch(json_typeof(json)) { + case JSON_OBJECT: + return json_object_deep_copy(json); + case JSON_ARRAY: + return json_array_deep_copy(json); + /* for the rest of the types, deep copying doesn't differ from + shallow copying */ + case JSON_STRING: + return json_string_copy(json); + case JSON_INTEGER: + return json_integer_copy(json); + case JSON_REAL: + return json_real_copy(json); + case JSON_TRUE: + case JSON_FALSE: + case JSON_NULL: + return (json_t *)json; + default: + return NULL; + } +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e4ea836 --- /dev/null +++ b/src/main.c @@ -0,0 +1,53 @@ +#include +#include + +#include "presentation/states/state_ingame.h" +#include "presentation/states/state_main_menu.h" + +#include "raylib.h" + +int main() { + InitWindow(1280, 720, "snejk"); + SetTargetFPS(60); + SetExitKey(KEY_NULL); + + bool should_quit_game = false; + + Presentation_State_Ingame_Context presentation_state_ingame_ctx = (Presentation_State_Ingame_Context) { + }; + Presentation_State_Main_Menu_Context presentation_state_main_menu_ctx = { + .should_quit_game = &should_quit_game, + }; + + presentation_state_ingame_init(&presentation_state_ingame_ctx); + presentation_state_main_menu_init(&presentation_state_main_menu_ctx); + + presentation_state_machine_go_to(&presentation_state_main_menu); + + while(true) { + if(WindowShouldClose() || should_quit_game) { + break; + } + + Presentation_State *state = presentation_state_machine.current; + + // Tick. + if(state != NULL && state->tick != NULL) { + state->tick(state); + } + + // Render. + BeginDrawing(); + { + if(state != NULL && state->render != NULL) { + state->render(state); + } + } + EndDrawing(); + } + + assert(presentation_state_machine_go_to(NULL)); + CloseWindow(); + + return 0; +} \ No newline at end of file diff --git a/src/presentation/states/state_ingame.c b/src/presentation/states/state_ingame.c new file mode 100644 index 0000000..1644454 --- /dev/null +++ b/src/presentation/states/state_ingame.c @@ -0,0 +1,206 @@ +#include "state_ingame.h" + +#include +#include +#include +#include + +#include "raylib.h" +#include "raygui.h" + +#include "session/game_session.h" + +#define GRID_CELL_SIZE 8 +#define ENTITY_PRESENTATION_Y_OFFSET -2 + +static void state_enter(Presentation_State *state) { + Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; + (void)ctx; + + printf("Entered ingame.\n"); + + printf("Setting up session ...\n"); + + assert(g_current_session != NULL); + Game_Session_Settings *settings = &g_current_session->settings; + printf( + "Singleplayer? %i.\n" + "Settings:\n" + "- Seed: %u\n" + "- Level width: %u, height: %u\n" + "- Max players: %u\n" + , + g_current_session->is_singleplayer, + settings->seed, + settings->level_width, settings->level_height, + settings->max_players + ); + + + printf("Setting up context ...\n"); + ctx->main_camera = (Camera2D) { + .offset = (Vector2) { 0, 0 }, + .target = (Vector2) { 0, 0 }, + .rotation = 0.0f, + .zoom = 3.0f + }; + + // TODO: SS - Maybe put the textures in an array and index them using an enum? + // These should probably be loaded at start-up instead of here. + ctx->texture_grass = LoadTexture("assets/sprites/spr_floor_grass.png"); + assert(IsTextureValid(ctx->texture_grass)); + ctx->texture_apple = LoadTexture("assets/sprites/spr_food_apple.png"); + assert(IsTextureValid(ctx->texture_apple)); + ctx->texture_snake_head = LoadTexture("assets/sprites/spr_snake_head.png"); + assert(IsTextureValid(ctx->texture_snake_head)); + ctx->texture_snake_body = LoadTexture("assets/sprites/spr_snake_body.png"); + assert(IsTextureValid(ctx->texture_snake_body)); +} + +static void state_tick(Presentation_State *state) { + Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; + (void)ctx; + + { // TEMP: SS + if(IsKeyPressed(KEY_ESCAPE)) { + presentation_state_machine_go_to(&presentation_state_main_menu); + } + } +} + +static void state_render(Presentation_State *state) { + Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; + (void)ctx; + + ClearBackground((Color) { 240, 236, 226, 255 }); + + Game_World *world = g_current_session->simulation_world.game_world; + Grid *grid = &world->grid; + + uint32_t grid_total_size = grid->width * grid->height; + + BeginMode2D(ctx->main_camera); { + // Render the level. + for(uint32_t i = 0; i < grid_total_size; i++) { + uint32_t x = i % grid->width; + uint32_t y = i / grid->width; + + uint32_t pres_x = x * GRID_CELL_SIZE; + uint32_t pres_y = y * GRID_CELL_SIZE; + + uint32_t random_floor_index = i; // TODO: SS - Get a deterministic random value based on seed. + + DrawTextureRec( + ctx->texture_grass, + (Rectangle) { GRID_CELL_SIZE * random_floor_index, 0, GRID_CELL_SIZE, GRID_CELL_SIZE }, + (Vector2) { pres_x, pres_y }, + WHITE + ); + DrawRectangleLines(pres_x, pres_y, GRID_CELL_SIZE, GRID_CELL_SIZE, (Color) { 0, 0, 0, 8 }); + } + + for(uint32_t i = 0; i < grid_total_size; i++) { + uint32_t x = i % grid->width; + uint32_t y = i / grid->width; + + uint32_t pres_x = x * GRID_CELL_SIZE; + uint32_t pres_y = y * GRID_CELL_SIZE; + + Grid_Cell *cell = &grid->cells[i]; + Entity *entity = &cell->entity; + switch(entity->type) { + case Entity_Type_None: { + break; + } + case Entity_Type_Snake_Head: + case Entity_Type_Snake_Body: { + Color tint = (Color) { 255, 135, 102, 255 }; // TODO: SS - Get tint based on player ID. + + Vector2 origin = (Vector2) { GRID_CELL_SIZE/2, GRID_CELL_SIZE/2 }; + + float sin_frequency = 0.0f; + float sin_amplitude = 0.0f; + + Texture2D *texture = NULL; + if(entity->type == Entity_Type_Snake_Head) { + texture = &ctx->texture_snake_head; + } + else { + texture = &ctx->texture_snake_body; + // TODO: SS - If it's a body, check what index it is and use that as an y-offset to make it look cool, like a wave. + sin_frequency = 4.0f; + sin_amplitude = 1.0f; + } + + DrawTexturePro( + *texture, + (Rectangle) { // Source. + 0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE + }, + (Rectangle) { // Destination. + pres_x + origin.x, + pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - (sin(GetTime() * sin_frequency)) * sin_amplitude, + GRID_CELL_SIZE, + GRID_CELL_SIZE + }, + origin, // Origin. + 0, // Rotation. + tint // Tint. + ); + + break; + } + case Entity_Type_Food: { + uint32_t flash = ((sin(GetTime() * 8) + 1)/2) * 32; + Color tint = (Color) { 255-flash, 255-flash, 255-flash, 255 }; + + Vector2 origin = (Vector2) { GRID_CELL_SIZE/2, GRID_CELL_SIZE/2 }; + + DrawTexturePro( + ctx->texture_apple, + (Rectangle) { // Source. + 0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE + }, + (Rectangle) { // Destination. + pres_x + origin.x, + pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - ((sin(GetTime() * 12) + 1)/2) * 1, + GRID_CELL_SIZE, + GRID_CELL_SIZE + }, + origin, // Origin. + 0, // Rotation. + tint // Tint. + ); + break; + } + } + } + } + EndMode2D(); +} + +static void state_exit(Presentation_State *state) { + Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; + (void)ctx; + printf("Exiting ingame\n"); + + UnloadTexture(ctx->texture_grass); + UnloadTexture(ctx->texture_apple); + UnloadTexture(ctx->texture_snake_head); + UnloadTexture(ctx->texture_snake_body); + + game_session_destroy(); +} + +Presentation_State presentation_state_ingame; + +void presentation_state_ingame_init(Presentation_State_Ingame_Context *ctx) { + presentation_state_ingame = (Presentation_State) { + .name = "Ingame", + .context = (void *)ctx, + .enter = state_enter, + .tick = state_tick, + .render = state_render, + .exit = state_exit + }; +} \ No newline at end of file diff --git a/src/presentation/states/state_ingame.h b/src/presentation/states/state_ingame.h new file mode 100644 index 0000000..c002898 --- /dev/null +++ b/src/presentation/states/state_ingame.h @@ -0,0 +1,21 @@ +#ifndef PRES_STATE_INGAME_H +#define PRES_STATE_INGAME_H + +#include "states.h" +#include "simulation/simulation_world.h" + +#include "raylib.h" + +typedef struct { + Camera2D main_camera; + + // Textures. + Texture2D texture_grass; + Texture2D texture_apple; + Texture2D texture_snake_head; + Texture2D texture_snake_body; +} Presentation_State_Ingame_Context; + +void presentation_state_ingame_init(Presentation_State_Ingame_Context *ctx); + +#endif \ No newline at end of file diff --git a/src/presentation/states/state_machine.c b/src/presentation/states/state_machine.c new file mode 100644 index 0000000..a3c7148 --- /dev/null +++ b/src/presentation/states/state_machine.c @@ -0,0 +1,48 @@ +#include "states.h" + +#include +#include + +Presentation_State_Machine presentation_state_machine = (Presentation_State_Machine) { + .current = NULL +}; + +Presentation_State presentation_state_create( + const char *name, + void *context, + Presentation_State_Enter_Callback enter, + Presentation_State_Tick_Callback tick, + Presentation_State_Render_Callback render, + Presentation_State_Exit_Callback exit +) { + return (Presentation_State) { + .name = name, + .context = context, + .enter = enter, + .tick = tick, + .render = render, + .exit = exit, + }; +} + +bool presentation_state_machine_go_to(Presentation_State *target) { + if(presentation_state_machine.current == target) { + return false; + } + + if(presentation_state_machine.current != NULL) { + assert(presentation_state_machine.current->exit != NULL); + presentation_state_machine.current->exit(presentation_state_machine.current); + } + + presentation_state_machine.current = target; + + if(presentation_state_machine.current == NULL) { + return true; // Return true here even though it's NULL. This is nice for when we want to exit the program and the current state. + } + + assert(presentation_state_machine.current->enter != NULL); + presentation_state_machine.current->enter(presentation_state_machine.current); + + return true; +} \ No newline at end of file diff --git a/src/presentation/states/state_machine.h b/src/presentation/states/state_machine.h new file mode 100644 index 0000000..c02e4cc --- /dev/null +++ b/src/presentation/states/state_machine.h @@ -0,0 +1,40 @@ +#ifndef PRES_STATE_MACHINE_H +#define PRES_STATE_MACHINE_H + +#include + +typedef struct Presentation_State Presentation_State; + +typedef void (*Presentation_State_Enter_Callback)(Presentation_State *state); +typedef void (*Presentation_State_Tick_Callback)(Presentation_State *state); +typedef void (*Presentation_State_Render_Callback)(Presentation_State *state); +typedef void (*Presentation_State_Exit_Callback)(Presentation_State *state); + +struct Presentation_State { + const char *name; + void *context; + + Presentation_State_Enter_Callback enter; + Presentation_State_Tick_Callback tick; + Presentation_State_Render_Callback render; + Presentation_State_Exit_Callback exit; +}; + +Presentation_State presentation_state_create( + const char *name, + void *context, + Presentation_State_Enter_Callback enter, + Presentation_State_Tick_Callback tick, + Presentation_State_Render_Callback render, + Presentation_State_Exit_Callback exit +); + +typedef struct { + Presentation_State *current; +} Presentation_State_Machine; + +extern Presentation_State_Machine presentation_state_machine; + +bool presentation_state_machine_go_to(Presentation_State *target); + +#endif \ No newline at end of file diff --git a/src/presentation/states/state_main_menu.c b/src/presentation/states/state_main_menu.c new file mode 100644 index 0000000..45c012c --- /dev/null +++ b/src/presentation/states/state_main_menu.c @@ -0,0 +1,127 @@ +#include "state_main_menu.h" + +#include +#include + +#include "raylib.h" +#include "raygui.h" + +static void state_enter(Presentation_State *state) { + Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context; + (void)ctx; + + printf("Entered main menu\n"); +} + +static void state_tick(Presentation_State *state) { + (void)state; +} + +static void state_render(Presentation_State *state) { + Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context; + + ClearBackground((Color) { + 230, 204, 138, 255 + }); + + #define BUTTON_HEIGHT 32 + + switch(ctx->mode) { + case Main_Menu_Mode_Home: { + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 0), 128, BUTTON_HEIGHT }, "#191#Singleplayer")) { + ctx->mode = Main_Menu_Mode_Singleplayer; + } + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "#191#Multiplayer")) { + ctx->mode = Main_Menu_Mode_Multiplayer; + } + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 2), 128, BUTTON_HEIGHT }, "#191#Quit")) { + *(ctx->should_quit_game) = true; + } + + break; + } + case Main_Menu_Mode_Singleplayer: { + ctx->is_singleplayer = true; + ctx->mode = Main_Menu_Mode_Game_Setup; + game_session_init_default_settings(ctx->is_singleplayer, &ctx->session_settings); + + break; + } + case Main_Menu_Mode_Multiplayer: { + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 0), 128, BUTTON_HEIGHT }, "#191#Host")) { + ctx->is_singleplayer = false; + ctx->mode = Main_Menu_Mode_Game_Setup; + game_session_init_default_settings(ctx->is_singleplayer, &ctx->session_settings); + } + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "#191#Join")) { + ctx->mode = Main_Menu_Mode_Multiplayer_Join; + } + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 2), 128, BUTTON_HEIGHT }, "#191#Back to Main Menu")) { + ctx->mode = Main_Menu_Mode_Home; + } + + break; + } + case Main_Menu_Mode_Game_Setup: { + // TODO: SS - Add options for the game here so players can customize.. + // - public or locked session (unlock with password?) + // - max-players + // - game-speed (tickrate) + // - match-time + // - map + // - seed + // - obstacles + // - power-ups + // etc.. + // Modify 'ctx->session_settings'. + + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 0), 128, BUTTON_HEIGHT }, "#191#Play")) { + // Set up the ingame-context and transition to the ingame-state. + // NOTE: SS - We might need to wait before transitioning when playing multiplayer. + game_session_create( + ctx->is_singleplayer, + !ctx->is_singleplayer, + ctx->session_settings + ); + + presentation_state_machine_go_to(&presentation_state_ingame); + } + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "#191#Cancel")) { + ctx->mode = ctx->is_singleplayer ? Main_Menu_Mode_Home : Main_Menu_Mode_Multiplayer; + } + + break; + } + case Main_Menu_Mode_Multiplayer_Join: { + // TODO: SS - Add text-input here so the player can specify what session to try connecting to. + + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "#191#Connect")) { + printf("TODO: SS - Connect to session id.\n"); + } + + if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 2), 128, BUTTON_HEIGHT }, "#191#Cancel")) { + ctx->mode = Main_Menu_Mode_Multiplayer; + } + + break; + } + } +} + +static void state_exit(Presentation_State *state) { + (void)state; + printf("Exited main menu\n"); +} + +Presentation_State presentation_state_main_menu; + +void presentation_state_main_menu_init(Presentation_State_Main_Menu_Context *ctx) { + presentation_state_main_menu = (Presentation_State) { + .name = "Main Menu", + .context = (void *)ctx, + .enter = state_enter, + .tick = state_tick, + .render = state_render, + .exit = state_exit + }; +} \ No newline at end of file diff --git a/src/presentation/states/state_main_menu.h b/src/presentation/states/state_main_menu.h new file mode 100644 index 0000000..9ecf7ac --- /dev/null +++ b/src/presentation/states/state_main_menu.h @@ -0,0 +1,27 @@ +#ifndef PRES_STATE_MAIN_MENU_H +#define PRES_STATE_MAIN_MENU_H + +#include "states.h" +#include "session/game_session.h" + +typedef enum { + Main_Menu_Mode_Home, + Main_Menu_Mode_Singleplayer, + Main_Menu_Mode_Multiplayer, + + Main_Menu_Mode_Game_Setup, + Main_Menu_Mode_Multiplayer_Join, +} Main_Menu_Mode; + +typedef struct { + Main_Menu_Mode mode; + + bool is_singleplayer; + Game_Session_Settings session_settings; + + bool *should_quit_game; +} Presentation_State_Main_Menu_Context; + +void presentation_state_main_menu_init(Presentation_State_Main_Menu_Context *ctx); + +#endif \ No newline at end of file diff --git a/src/presentation/states/states.h b/src/presentation/states/states.h new file mode 100644 index 0000000..c390bb5 --- /dev/null +++ b/src/presentation/states/states.h @@ -0,0 +1,10 @@ +#ifndef PRES_STATES_H +#define PRES_STATES_H + +#include "state_machine.h" + +extern Presentation_State presentation_state_main_menu; +extern Presentation_State presentation_state_ingame; + + +#endif \ No newline at end of file diff --git a/src/raygui.h b/src/raygui.h new file mode 100644 index 0000000..f86ff79 --- /dev/null +++ b/src/raygui.h @@ -0,0 +1,5987 @@ +/******************************************************************************************* +* +* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library +* +* DESCRIPTION: +* raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also +* available as a standalone library, as long as input and drawing functions are provided +* +* FEATURES: +* - Immediate-mode gui, minimal retained data +* - +25 controls provided (basic and advanced) +* - Styling system for colors, font and metrics +* - Icons supported, embedded as a 1-bit icons pack +* - Standalone mode option (custom input/graphics backend) +* - Multiple support tools provided for raygui development +* +* POSSIBLE IMPROVEMENTS: +* - Better standalone mode API for easy plug of custom backends +* - Externalize required inputs, allow user easier customization +* +* LIMITATIONS: +* - No editable multi-line word-wraped text box supported +* - No auto-layout mechanism, up to the user to define controls position and size +* - Standalone mode requires library modification and some user work to plug another backend +* +* NOTES: +* - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for +* font atlas recs and glyphs, freeing that memory is (usually) up to the user, +* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads +* by default any previously loaded font (texture, recs, glyphs) +* - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions +* +* CONTROLS PROVIDED: +* # Container/separators Controls +* - WindowBox --> StatusBar, Panel +* - GroupBox --> Line +* - Line +* - Panel --> StatusBar +* - ScrollPanel --> StatusBar +* - TabBar --> Button +* +* # Basic Controls +* - Label +* - LabelButton --> Label +* - Button +* - Toggle +* - ToggleGroup --> Toggle +* - ToggleSlider +* - CheckBox +* - ComboBox +* - DropdownBox +* - TextBox +* - ValueBox --> TextBox +* - Spinner --> Button, ValueBox +* - Slider +* - SliderBar --> Slider +* - ProgressBar +* - StatusBar +* - DummyRec +* - Grid +* +* # Advance Controls +* - ListView +* - ColorPicker --> ColorPanel, ColorBarHue +* - MessageBox --> Window, Label, Button +* - TextInputBox --> Window, Label, TextBox, Button +* +* It also provides a set of functions for styling the controls based on its properties (size, color) +* +* +* RAYGUI STYLE (guiStyle): +* raygui uses a global data array for all gui style properties (allocated on data segment by default), +* when a new style is loaded, it is loaded over the global style... but a default gui style could always be +* recovered with GuiLoadStyleDefault() function, that overwrites the current style to the default one +* +* The global style array size is fixed and depends on the number of controls and properties: +* +* static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; +* +* guiStyle size is by default: 16*(16 + 8) = 384 int = 384*4 bytes = 1536 bytes = 1.5 KB +* +* Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style +* used for all controls, when any of those base values is set, it is automatically populated to all +* controls, so, specific control values overwriting generic style should be set after base values +* +* After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those +* properties are actually common to all controls and can not be overwritten individually (like BASE ones) +* Some of those properties are: TEXT_SIZE, TEXT_SPACING, LINE_COLOR, BACKGROUND_COLOR +* +* Custom control properties can be defined using the EXTENDED properties for each independent control. +* +* TOOL: rGuiStyler is a visual tool to customize raygui style: github.com/raysan5/rguistyler +* +* +* RAYGUI ICONS (guiIcons): +* raygui could use a global array containing icons data (allocated on data segment by default), +* a custom icons set could be loaded over this array using GuiLoadIcons(), but loaded icons set +* must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS will be loaded +* +* Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon +* requires 8 integers (16*16/32) to be stored in memory. +* +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set +* +* The global icons array size is fixed and depends on the number of icons and size: +* +* static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS]; +* +* guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +* +* TOOL: rGuiIcons is a visual tool to customize/create raygui icons: github.com/raysan5/rguiicons +* +* RAYGUI LAYOUT: +* raygui currently does not provide an auto-layout mechanism like other libraries, +* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it +* +* TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout +* +* CONFIGURATION: +* #define RAYGUI_IMPLEMENTATION +* Generates the implementation of the library into the included file +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation +* +* #define RAYGUI_STANDALONE +* Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined +* internally in the library and input management and drawing functions must be provided by +* the user (check library implementation for further details) +* +* #define RAYGUI_NO_ICONS +* Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) +* +* #define RAYGUI_CUSTOM_ICONS +* Includes custom ricons.h header defining a set of custom icons, +* this file can be generated using rGuiIcons tool +* +* #define RAYGUI_DEBUG_RECS_BOUNDS +* Draw control bounds rectangles for debug +* +* #define RAYGUI_DEBUG_TEXT_BOUNDS +* Draw text bounds rectangles for debug +* +* VERSIONS HISTORY: +* 5.0 (xx-Nov-2025) ADDED: Support up to 32 controls (v500) +* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes +* ADDED: GuiValueBoxFloat() +* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP +* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: GuiLoadIconsFromMemory() +* ADDED: Multiple new icons +* REMOVED: GuiSpinner() from controls list, using BUTTON + VALUEBOX properties +* REMOVED: GuiSliderPro(), functionality was redundant +* REVIEWED: Controls using text labels to use LABEL properties +* REVIEWED: Replaced sprintf() by snprintf() for more safety +* REVIEWED: GuiTabBar(), close tab with mouse middle button +* REVIEWED: GuiScrollPanel(), scroll speed proportional to content +* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow +* REVIEWED: GuiTextBox(), cursor position initialization +* REVIEWED: GuiSliderPro(), control value change check +* REVIEWED: GuiGrid(), simplified implementation +* REVIEWED: GuiIconText(), increase buffer size and reviewed padding +* REVIEWED: GuiDrawText(), improved wrap mode drawing +* REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: GuiProgressBar(), improved borders computing +* REVIEWED: GuiTextBox(), multiple improvements: autocursor and more +* REVIEWED: Functions descriptions, removed wrong return value reference +* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion +* +* 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() +* ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() +* ADDED: Multiple new icons, mostly compiler related +* ADDED: New DEFAULT properties: TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE +* ADDED: New enum values: GuiTextAlignment, GuiTextAlignmentVertical, GuiTextWrapMode +* ADDED: Support loading styles with custom font charset from external file +* REDESIGNED: GuiTextBox(), support mouse cursor positioning +* REDESIGNED: GuiDrawText(), support multiline and word-wrap modes (read only) +* REDESIGNED: GuiProgressBar() to be more visual, progress affects border color +* REDESIGNED: Global alpha consideration moved to GuiDrawRectangle() and GuiDrawText() +* REDESIGNED: GuiScrollPanel(), get parameters by reference and return result value +* REDESIGNED: GuiToggleGroup(), get parameters by reference and return result value +* REDESIGNED: GuiComboBox(), get parameters by reference and return result value +* REDESIGNED: GuiCheckBox(), get parameters by reference and return result value +* REDESIGNED: GuiSlider(), get parameters by reference and return result value +* REDESIGNED: GuiSliderBar(), get parameters by reference and return result value +* REDESIGNED: GuiProgressBar(), get parameters by reference and return result value +* REDESIGNED: GuiListView(), get parameters by reference and return result value +* REDESIGNED: GuiColorPicker(), get parameters by reference and return result value +* REDESIGNED: GuiColorPanel(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarAlpha(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarHue(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), added extra parameter +* REDESIGNED: GuiListViewEx(), change parameters order +* REDESIGNED: All controls return result as int value +* REVIEWED: GuiScrollPanel() to avoid smallish scroll-bars +* REVIEWED: All examples and specially controls_test_suite +* RENAMED: gui_file_dialog module to gui_window_file_dialog +* UPDATED: All styles to include ISO-8859-15 charset (as much as possible) +* +* 3.6 (10-May-2023) ADDED: New icon: SAND_TIMER +* ADDED: GuiLoadStyleFromMemory() (binary only) +* REVIEWED: GuiScrollBar() horizontal movement key +* REVIEWED: GuiTextBox() crash on cursor movement +* REVIEWED: GuiTextBox(), additional inputs support +* REVIEWED: GuiLabelButton(), avoid text cut +* REVIEWED: GuiTextInputBox(), password input +* REVIEWED: Local GetCodepointNext(), aligned with raylib +* REDESIGNED: GuiSlider*()/GuiScrollBar() to support out-of-bounds +* +* 3.5 (20-Apr-2023) ADDED: GuiTabBar(), based on GuiToggle() +* ADDED: Helper functions to split text in separate lines +* ADDED: Multiple new icons, useful for code editing tools +* REMOVED: Unneeded icon editing functions +* REMOVED: GuiTextBoxMulti(), very limited and broken +* REMOVED: MeasureTextEx() dependency, logic directly implemented +* REMOVED: DrawTextEx() dependency, logic directly implemented +* REVIEWED: GuiScrollBar(), improve mouse-click behaviour +* REVIEWED: Library header info, more info, better organized +* REDESIGNED: GuiTextBox() to support cursor movement +* REDESIGNED: GuiDrawText() to divide drawing by lines +* +* 3.2 (22-May-2022) RENAMED: Some enum values, for unification, avoiding prefixes +* REMOVED: GuiScrollBar(), only internal +* REDESIGNED: GuiPanel() to support text parameter +* REDESIGNED: GuiScrollPanel() to support text parameter +* REDESIGNED: GuiColorPicker() to support text parameter +* REDESIGNED: GuiColorPanel() to support text parameter +* REDESIGNED: GuiColorBarAlpha() to support text parameter +* REDESIGNED: GuiColorBarHue() to support text parameter +* REDESIGNED: GuiTextInputBox() to support password +* +* 3.1 (12-Jan-2022) REVIEWED: Default style for consistency (aligned with rGuiLayout v2.5 tool) +* REVIEWED: GuiLoadStyle() to support compressed font atlas image data and unload previous textures +* REVIEWED: External icons usage logic +* REVIEWED: GuiLine() for centered alignment when including text +* RENAMED: Multiple controls properties definitions to prepend RAYGUI_ +* RENAMED: RICON_ references to RAYGUI_ICON_ for library consistency +* Projects updated and multiple tweaks +* +* 3.0 (04-Nov-2021) Integrated ricons data to avoid external file +* REDESIGNED: GuiTextBoxMulti() +* REMOVED: GuiImageButton*() +* Multiple minor tweaks and bugs corrected +* +* 2.9 (17-Mar-2021) REMOVED: Tooltip API +* 2.8 (03-May-2020) Centralized rectangles drawing to GuiDrawRectangle() +* 2.7 (20-Feb-2020) ADDED: Possible tooltips API +* 2.6 (09-Sep-2019) ADDED: GuiTextInputBox() +* REDESIGNED: GuiListView*(), GuiDropdownBox(), GuiSlider*(), GuiProgressBar(), GuiMessageBox() +* REVIEWED: GuiTextBox(), GuiSpinner(), GuiValueBox(), GuiLoadStyle() +* Replaced property INNER_PADDING by TEXT_PADDING, renamed some properties +* ADDED: 8 new custom styles ready to use +* Multiple minor tweaks and bugs corrected +* +* 2.5 (28-May-2019) Implemented extended GuiTextBox(), GuiValueBox(), GuiSpinner() +* 2.3 (29-Apr-2019) ADDED: rIcons auxiliar library and support for it, multiple controls reviewed +* Refactor all controls drawing mechanism to use control state +* 2.2 (05-Feb-2019) ADDED: GuiScrollBar(), GuiScrollPanel(), reviewed GuiListView(), removed Gui*Ex() controls +* 2.1 (26-Dec-2018) REDESIGNED: GuiCheckBox(), GuiComboBox(), GuiDropdownBox(), GuiToggleGroup() > Use combined text string +* REDESIGNED: Style system (breaking change) +* 2.0 (08-Nov-2018) ADDED: Support controls guiLock and custom fonts +* REVIEWED: GuiComboBox(), GuiListView()... +* 1.9 (09-Oct-2018) REVIEWED: GuiGrid(), GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()... +* 1.8 (01-May-2018) Lot of rework and redesign to align with rGuiStyler and rGuiLayout +* 1.5 (21-Jun-2017) Working in an improved styles system +* 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) +* 1.3 (12-Jun-2017) Complete redesign of style system +* 1.1 (01-Jun-2017) Complete review of the library +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria +* +* DEPENDENCIES: +* raylib 5.6-dev - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* +* STANDALONE MODE: +* By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled +* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs +* +* The following functions should be redefined for a custom backend: +* +* - Vector2 GetMousePosition(void); +* - float GetMouseWheelMove(void); +* - bool IsMouseButtonDown(int button); +* - bool IsMouseButtonPressed(int button); +* - bool IsMouseButtonReleased(int button); +* - bool IsKeyDown(int key); +* - bool IsKeyPressed(int key); +* - int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +* +* - void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +* - void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +* +* - Font GetFontDefault(void); // -- GuiLoadStyleDefault() +* - Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle() +* - Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +* - void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) +* - char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +* - void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data +* - const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs +* - int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +* - void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list +* - unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +* +* CONTRIBUTORS: +* Ramon Santamaria: Supervision, review, redesign, update and maintenance +* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) +* Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) +* Adria Arranz: Testing and implementation of additional controls (2018) +* Jordi Jorba: Testing and implementation of additional controls (2018) +* Albert Martos: Review and testing of the library (2015) +* Ian Eito: Review and testing of the library (2015) +* Kevin Gato: Initial implementation of basic components (2014) +* Daniel Nicolas: Initial implementation of basic components (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYGUI_H +#define RAYGUI_H + +#define RAYGUI_VERSION_MAJOR 4 +#define RAYGUI_VERSION_MINOR 5 +#define RAYGUI_VERSION_PATCH 0 +#define RAYGUI_VERSION "5.0-dev" + +#if !defined(RAYGUI_STANDALONE) + #include "raylib.h" +#endif + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#endif + +// Function specifiers definition +#ifndef RAYGUIAPI + #define RAYGUIAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Simple log system to avoid printf() calls if required +// NOTE: Avoiding those calls, also avoids const strings memory usage +#define RAYGUI_SUPPORT_LOG_INFO +#if defined(RAYGUI_SUPPORT_LOG_INFO) + #define RAYGUI_LOG(...) printf(__VA_ARGS__) +#else + #define RAYGUI_LOG(...) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Some types are required for RAYGUI_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + #ifndef __cplusplus + // Boolean type + #ifndef true + typedef enum { false, true } bool; + #endif + #endif + + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type // -- ConvertHSVtoRGB(), ConvertRGBtoHSV() + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Color type, RGBA (32bit) + typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } Color; + + // Rectangle type + typedef struct Rectangle { + float x; + float y; + float width; + float height; + } Rectangle; + + // TODO: Texture2D type is very coupled to raylib, required by Font type + // It should be redesigned to be provided by user + typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Texture; + + // Texture2D, same as Texture + typedef Texture Texture2D; + + // Image, pixel data stored in CPU memory (RAM) + typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Image; + + // GlyphInfo, font characters glyphs info + typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data + } GlyphInfo; + + // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() + // It should be redesigned to be provided by user + typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data + } Font; +#endif + +// Style property +// NOTE: Used when exporting style as code for convenience +typedef struct GuiStyleProp { + unsigned short controlId; // Control identifier + unsigned short propertyId; // Property identifier + int propertyValue; // Property value +} GuiStyleProp; + +/* +// Controls text style -NOT USED- +// NOTE: Text style is defined by control +typedef struct GuiTextStyle { + unsigned int size; + int charSpacing; + int lineSpacing; + int alignmentH; + int alignmentV; + int padding; +} GuiTextStyle; +*/ + +// Gui control state +typedef enum { + STATE_NORMAL = 0, + STATE_FOCUSED, + STATE_PRESSED, + STATE_DISABLED +} GuiState; + +// Gui control text alignment +typedef enum { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT +} GuiTextAlignment; + +// Gui control text alignment vertical +// NOTE: Text vertical position inside the text bounds +typedef enum { + TEXT_ALIGN_TOP = 0, + TEXT_ALIGN_MIDDLE, + TEXT_ALIGN_BOTTOM +} GuiTextAlignmentVertical; + +// Gui control text wrap mode +// NOTE: Useful for multiline text +typedef enum { + TEXT_WRAP_NONE = 0, + TEXT_WRAP_CHAR, + TEXT_WRAP_WORD +} GuiTextWrapMode; + +// Gui controls +typedef enum { + // Default -> populates to all controls when set + DEFAULT = 0, + + // Basic controls + LABEL, // Used also for: LABELBUTTON + BUTTON, + TOGGLE, // Used also for: TOGGLEGROUP + SLIDER, // Used also for: SLIDERBAR, TOGGLESLIDER + PROGRESSBAR, + CHECKBOX, + COMBOBOX, + DROPDOWNBOX, + TEXTBOX, // Used also for: TEXTBOXMULTI + VALUEBOX, + CONTROL11, + LISTVIEW, + COLORPICKER, + SCROLLBAR, + STATUSBAR +} GuiControl; + +// Gui base properties for every control +// NOTE: RAYGUI_MAX_PROPS_BASE properties (by default 16 properties) +typedef enum { + BORDER_COLOR_NORMAL = 0, // Control border color in STATE_NORMAL + BASE_COLOR_NORMAL, // Control base color in STATE_NORMAL + TEXT_COLOR_NORMAL, // Control text color in STATE_NORMAL + BORDER_COLOR_FOCUSED, // Control border color in STATE_FOCUSED + BASE_COLOR_FOCUSED, // Control base color in STATE_FOCUSED + TEXT_COLOR_FOCUSED, // Control text color in STATE_FOCUSED + BORDER_COLOR_PRESSED, // Control border color in STATE_PRESSED + BASE_COLOR_PRESSED, // Control base color in STATE_PRESSED + TEXT_COLOR_PRESSED, // Control text color in STATE_PRESSED + BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED + BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED + TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED + BORDER_WIDTH = 12, // Control border size, 0 for no border + //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls + //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls + //TEXT_LINE_SPACING, // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING = 13, // Control text padding, not considering border + TEXT_ALIGNMENT = 14, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls +} GuiControlProperty; + +// TODO: Which text styling properties should be global or per-control? +// At this moment TEXT_PADDING and TEXT_ALIGNMENT is configured and saved per control while +// TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and +// should be configured by user as needed while defining the UI layout + +// Gui extended properties depend on control +// NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) +//---------------------------------------------------------------------------------- +// DEFAULT extended properties +// NOTE: Those properties are common to all controls or global +// WARNING: We only have 8 slots for those properties by default!!! -> New global control: TEXT? +typedef enum { + TEXT_SIZE = 16, // Text size (glyphs max height) + TEXT_SPACING, // Text spacing between glyphs + LINE_COLOR, // Line control color + BACKGROUND_COLOR, // Background color + TEXT_LINE_SPACING, // Text spacing between lines + TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) + TEXT_WRAP_MODE // Text wrap-mode inside text bounds + //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline + //TEXT_DECORATION_THICK // Text decoration line thickness +} GuiDefaultProperty; + +// Other possible text properties: +// TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change +// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... + +// Label +//typedef enum { } GuiLabelProperty; + +// Button/Spinner +//typedef enum { } GuiButtonProperty; + +// Toggle/ToggleGroup +typedef enum { + GROUP_PADDING = 16, // ToggleGroup separation between toggles +} GuiToggleProperty; + +// Slider/SliderBar +typedef enum { + SLIDER_WIDTH = 16, // Slider size of internal bar + SLIDER_PADDING // Slider/SliderBar internal bar padding +} GuiSliderProperty; + +// ProgressBar +typedef enum { + PROGRESS_PADDING = 16, // ProgressBar internal padding +} GuiProgressBarProperty; + +// ScrollBar +typedef enum { + ARROWS_SIZE = 16, // ScrollBar arrows size + ARROWS_VISIBLE, // ScrollBar arrows visible + SCROLL_SLIDER_PADDING, // ScrollBar slider internal padding + SCROLL_SLIDER_SIZE, // ScrollBar slider size + SCROLL_PADDING, // ScrollBar scroll padding from arrows + SCROLL_SPEED, // ScrollBar scrolling speed +} GuiScrollBarProperty; + +// CheckBox +typedef enum { + CHECK_PADDING = 16 // CheckBox internal check padding +} GuiCheckBoxProperty; + +// ComboBox +typedef enum { + COMBO_BUTTON_WIDTH = 16, // ComboBox right button width + COMBO_BUTTON_SPACING // ComboBox button separation +} GuiComboBoxProperty; + +// DropdownBox +typedef enum { + ARROW_PADDING = 16, // DropdownBox arrow separation from border and items + DROPDOWN_ITEMS_SPACING, // DropdownBox items separation + DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden + DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) +} GuiDropdownBoxProperty; + +// TextBox/TextBoxMulti/ValueBox/Spinner +typedef enum { + TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable +} GuiTextBoxProperty; + +// ValueBox/Spinner +typedef enum { + SPINNER_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPINNER_BUTTON_SPACING, // Spinner buttons separation +} GuiValueBoxProperty; + +// Control11 +//typedef enum { } GuiControl11Property; + +// ListView +typedef enum { + LIST_ITEMS_HEIGHT = 16, // ListView items height + LIST_ITEMS_SPACING, // ListView items separation + SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) + SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_NORMAL, // ListView items border enabled in normal state + LIST_ITEMS_BORDER_WIDTH // ListView items border width +} GuiListViewProperty; + +// ColorPicker +typedef enum { + COLOR_SELECTOR_SIZE = 16, + HUEBAR_WIDTH, // ColorPicker right hue bar width + HUEBAR_PADDING, // ColorPicker right hue bar separation from panel + HUEBAR_SELECTOR_HEIGHT, // ColorPicker right hue bar selector height + HUEBAR_SELECTOR_OVERFLOW // ColorPicker right hue bar selector overflow +} GuiColorPickerProperty; + +#define SCROLLBAR_LEFT_SIDE 0 +#define SCROLLBAR_RIGHT_SIDE 1 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Global gui state control functions +RAYGUIAPI void GuiEnable(void); // Enable gui controls (global state) +RAYGUIAPI void GuiDisable(void); // Disable gui controls (global state) +RAYGUIAPI void GuiLock(void); // Lock gui controls (global state) +RAYGUIAPI void GuiUnlock(void); // Unlock gui controls (global state) +RAYGUIAPI bool GuiIsLocked(void); // Check if gui is locked (global state) +RAYGUIAPI void GuiSetAlpha(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f +RAYGUIAPI void GuiSetState(int state); // Set gui state (global state) +RAYGUIAPI int GuiGetState(void); // Get gui state (global state) + +// Font set/get functions +RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state) +RAYGUIAPI Font GuiGetFont(void); // Get gui custom font (global state) + +// Style set/get functions +RAYGUIAPI void GuiSetStyle(int control, int property, int value); // Set one style property +RAYGUIAPI int GuiGetStyle(int control, int property); // Get one style property + +// Styles loading functions +RAYGUIAPI void GuiLoadStyle(const char *fileName); // Load style file over global style variable (.rgs) +RAYGUIAPI void GuiLoadStyleDefault(void); // Load style default over global style + +// Tooltips management functions +RAYGUIAPI void GuiEnableTooltip(void); // Enable gui tooltips (global state) +RAYGUIAPI void GuiDisableTooltip(void); // Disable gui tooltips (global state) +RAYGUIAPI void GuiSetTooltip(const char *tooltip); // Set tooltip string + +// Icons functionality +RAYGUIAPI const char *GuiIconText(int iconId, const char *text); // Get text with icon id prepended (if supported) +#if !defined(RAYGUI_NO_ICONS) +RAYGUIAPI void GuiSetIconScale(int scale); // Set default icon drawing size +RAYGUIAPI unsigned int *GuiGetIcons(void); // Get raygui icons data pointer +RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load raygui icons file (.rgi) into internal icons data +RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position +#endif + +// Utility functions +RAYGUIAPI int GuiGetTextWidth(const char *text); // Get text width considering gui style and icon size (if required) + +// Controls +//---------------------------------------------------------------------------------------------------------- +// Container/separator controls, useful for controls organization +RAYGUIAPI int GuiWindowBox(Rectangle bounds, const char *title); // Window Box control, shows a window that can be closed +RAYGUIAPI int GuiGroupBox(Rectangle bounds, const char *text); // Group Box control with text name +RAYGUIAPI int GuiLine(Rectangle bounds, const char *text); // Line separator control, could contain text +RAYGUIAPI int GuiPanel(Rectangle bounds, const char *text); // Panel control, useful to group controls +RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *active); // Tab Bar control, returns TAB to be closed or -1 +RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control + +// Basic controls set +RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control +RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked +RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked +RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control +RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control +RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control + +RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control +RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control +RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values +RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text + +RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control +RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control +RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control +RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text +RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders +RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control + +// Advance controls set +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control +RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters +RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message +RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret +RAYGUIAPI int GuiColorPicker(Rectangle bounds, const char *text, Color *color); // Color Picker control (multiple color controls) +RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); // Color Panel control +RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control +RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control +RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) +RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() +//---------------------------------------------------------------------------------------------------------- + +#if !defined(RAYGUI_NO_ICONS) + +#if !defined(RAYGUI_CUSTOM_ICONS) +//---------------------------------------------------------------------------------- +// Icons enumeration +//---------------------------------------------------------------------------------- +typedef enum { + ICON_NONE = 0, + ICON_FOLDER_FILE_OPEN = 1, + ICON_FILE_SAVE_CLASSIC = 2, + ICON_FOLDER_OPEN = 3, + ICON_FOLDER_SAVE = 4, + ICON_FILE_OPEN = 5, + ICON_FILE_SAVE = 6, + ICON_FILE_EXPORT = 7, + ICON_FILE_ADD = 8, + ICON_FILE_DELETE = 9, + ICON_FILETYPE_TEXT = 10, + ICON_FILETYPE_AUDIO = 11, + ICON_FILETYPE_IMAGE = 12, + ICON_FILETYPE_PLAY = 13, + ICON_FILETYPE_VIDEO = 14, + ICON_FILETYPE_INFO = 15, + ICON_FILE_COPY = 16, + ICON_FILE_CUT = 17, + ICON_FILE_PASTE = 18, + ICON_CURSOR_HAND = 19, + ICON_CURSOR_POINTER = 20, + ICON_CURSOR_CLASSIC = 21, + ICON_PENCIL = 22, + ICON_PENCIL_BIG = 23, + ICON_BRUSH_CLASSIC = 24, + ICON_BRUSH_PAINTER = 25, + ICON_WATER_DROP = 26, + ICON_COLOR_PICKER = 27, + ICON_RUBBER = 28, + ICON_COLOR_BUCKET = 29, + ICON_TEXT_T = 30, + ICON_TEXT_A = 31, + ICON_SCALE = 32, + ICON_RESIZE = 33, + ICON_FILTER_POINT = 34, + ICON_FILTER_BILINEAR = 35, + ICON_CROP = 36, + ICON_CROP_ALPHA = 37, + ICON_SQUARE_TOGGLE = 38, + ICON_SYMMETRY = 39, + ICON_SYMMETRY_HORIZONTAL = 40, + ICON_SYMMETRY_VERTICAL = 41, + ICON_LENS = 42, + ICON_LENS_BIG = 43, + ICON_EYE_ON = 44, + ICON_EYE_OFF = 45, + ICON_FILTER_TOP = 46, + ICON_FILTER = 47, + ICON_TARGET_POINT = 48, + ICON_TARGET_SMALL = 49, + ICON_TARGET_BIG = 50, + ICON_TARGET_MOVE = 51, + ICON_CURSOR_MOVE = 52, + ICON_CURSOR_SCALE = 53, + ICON_CURSOR_SCALE_RIGHT = 54, + ICON_CURSOR_SCALE_LEFT = 55, + ICON_UNDO = 56, + ICON_REDO = 57, + ICON_REREDO = 58, + ICON_MUTATE = 59, + ICON_ROTATE = 60, + ICON_REPEAT = 61, + ICON_SHUFFLE = 62, + ICON_EMPTYBOX = 63, + ICON_TARGET = 64, + ICON_TARGET_SMALL_FILL = 65, + ICON_TARGET_BIG_FILL = 66, + ICON_TARGET_MOVE_FILL = 67, + ICON_CURSOR_MOVE_FILL = 68, + ICON_CURSOR_SCALE_FILL = 69, + ICON_CURSOR_SCALE_RIGHT_FILL = 70, + ICON_CURSOR_SCALE_LEFT_FILL = 71, + ICON_UNDO_FILL = 72, + ICON_REDO_FILL = 73, + ICON_REREDO_FILL = 74, + ICON_MUTATE_FILL = 75, + ICON_ROTATE_FILL = 76, + ICON_REPEAT_FILL = 77, + ICON_SHUFFLE_FILL = 78, + ICON_EMPTYBOX_SMALL = 79, + ICON_BOX = 80, + ICON_BOX_TOP = 81, + ICON_BOX_TOP_RIGHT = 82, + ICON_BOX_RIGHT = 83, + ICON_BOX_BOTTOM_RIGHT = 84, + ICON_BOX_BOTTOM = 85, + ICON_BOX_BOTTOM_LEFT = 86, + ICON_BOX_LEFT = 87, + ICON_BOX_TOP_LEFT = 88, + ICON_BOX_CENTER = 89, + ICON_BOX_CIRCLE_MASK = 90, + ICON_POT = 91, + ICON_ALPHA_MULTIPLY = 92, + ICON_ALPHA_CLEAR = 93, + ICON_DITHERING = 94, + ICON_MIPMAPS = 95, + ICON_BOX_GRID = 96, + ICON_GRID = 97, + ICON_BOX_CORNERS_SMALL = 98, + ICON_BOX_CORNERS_BIG = 99, + ICON_FOUR_BOXES = 100, + ICON_GRID_FILL = 101, + ICON_BOX_MULTISIZE = 102, + ICON_ZOOM_SMALL = 103, + ICON_ZOOM_MEDIUM = 104, + ICON_ZOOM_BIG = 105, + ICON_ZOOM_ALL = 106, + ICON_ZOOM_CENTER = 107, + ICON_BOX_DOTS_SMALL = 108, + ICON_BOX_DOTS_BIG = 109, + ICON_BOX_CONCENTRIC = 110, + ICON_BOX_GRID_BIG = 111, + ICON_OK_TICK = 112, + ICON_CROSS = 113, + ICON_ARROW_LEFT = 114, + ICON_ARROW_RIGHT = 115, + ICON_ARROW_DOWN = 116, + ICON_ARROW_UP = 117, + ICON_ARROW_LEFT_FILL = 118, + ICON_ARROW_RIGHT_FILL = 119, + ICON_ARROW_DOWN_FILL = 120, + ICON_ARROW_UP_FILL = 121, + ICON_AUDIO = 122, + ICON_FX = 123, + ICON_WAVE = 124, + ICON_WAVE_SINUS = 125, + ICON_WAVE_SQUARE = 126, + ICON_WAVE_TRIANGULAR = 127, + ICON_CROSS_SMALL = 128, + ICON_PLAYER_PREVIOUS = 129, + ICON_PLAYER_PLAY_BACK = 130, + ICON_PLAYER_PLAY = 131, + ICON_PLAYER_PAUSE = 132, + ICON_PLAYER_STOP = 133, + ICON_PLAYER_NEXT = 134, + ICON_PLAYER_RECORD = 135, + ICON_MAGNET = 136, + ICON_LOCK_CLOSE = 137, + ICON_LOCK_OPEN = 138, + ICON_CLOCK = 139, + ICON_TOOLS = 140, + ICON_GEAR = 141, + ICON_GEAR_BIG = 142, + ICON_BIN = 143, + ICON_HAND_POINTER = 144, + ICON_LASER = 145, + ICON_COIN = 146, + ICON_EXPLOSION = 147, + ICON_1UP = 148, + ICON_PLAYER = 149, + ICON_PLAYER_JUMP = 150, + ICON_KEY = 151, + ICON_DEMON = 152, + ICON_TEXT_POPUP = 153, + ICON_GEAR_EX = 154, + ICON_CRACK = 155, + ICON_CRACK_POINTS = 156, + ICON_STAR = 157, + ICON_DOOR = 158, + ICON_EXIT = 159, + ICON_MODE_2D = 160, + ICON_MODE_3D = 161, + ICON_CUBE = 162, + ICON_CUBE_FACE_TOP = 163, + ICON_CUBE_FACE_LEFT = 164, + ICON_CUBE_FACE_FRONT = 165, + ICON_CUBE_FACE_BOTTOM = 166, + ICON_CUBE_FACE_RIGHT = 167, + ICON_CUBE_FACE_BACK = 168, + ICON_CAMERA = 169, + ICON_SPECIAL = 170, + ICON_LINK_NET = 171, + ICON_LINK_BOXES = 172, + ICON_LINK_MULTI = 173, + ICON_LINK = 174, + ICON_LINK_BROKE = 175, + ICON_TEXT_NOTES = 176, + ICON_NOTEBOOK = 177, + ICON_SUITCASE = 178, + ICON_SUITCASE_ZIP = 179, + ICON_MAILBOX = 180, + ICON_MONITOR = 181, + ICON_PRINTER = 182, + ICON_PHOTO_CAMERA = 183, + ICON_PHOTO_CAMERA_FLASH = 184, + ICON_HOUSE = 185, + ICON_HEART = 186, + ICON_CORNER = 187, + ICON_VERTICAL_BARS = 188, + ICON_VERTICAL_BARS_FILL = 189, + ICON_LIFE_BARS = 190, + ICON_INFO = 191, + ICON_CROSSLINE = 192, + ICON_HELP = 193, + ICON_FILETYPE_ALPHA = 194, + ICON_FILETYPE_HOME = 195, + ICON_LAYERS_VISIBLE = 196, + ICON_LAYERS = 197, + ICON_WINDOW = 198, + ICON_HIDPI = 199, + ICON_FILETYPE_BINARY = 200, + ICON_HEX = 201, + ICON_SHIELD = 202, + ICON_FILE_NEW = 203, + ICON_FOLDER_ADD = 204, + ICON_ALARM = 205, + ICON_CPU = 206, + ICON_ROM = 207, + ICON_STEP_OVER = 208, + ICON_STEP_INTO = 209, + ICON_STEP_OUT = 210, + ICON_RESTART = 211, + ICON_BREAKPOINT_ON = 212, + ICON_BREAKPOINT_OFF = 213, + ICON_BURGER_MENU = 214, + ICON_CASE_SENSITIVE = 215, + ICON_REG_EXP = 216, + ICON_FOLDER = 217, + ICON_FILE = 218, + ICON_SAND_TIMER = 219, + ICON_WARNING = 220, + ICON_HELP_BOX = 221, + ICON_INFO_BOX = 222, + ICON_PRIORITY = 223, + ICON_LAYERS_ISO = 224, + ICON_LAYERS2 = 225, + ICON_MLAYERS = 226, + ICON_MAPS = 227, + ICON_HOT = 228, + ICON_LABEL = 229, + ICON_NAME_ID = 230, + ICON_SLICING = 231, + ICON_MANUAL_CONTROL = 232, + ICON_COLLISION = 233, + ICON_CIRCLE_ADD = 234, + ICON_CIRCLE_ADD_FILL = 235, + ICON_CIRCLE_WARNING = 236, + ICON_CIRCLE_WARNING_FILL = 237, + ICON_BOX_MORE = 238, + ICON_BOX_MORE_FILL = 239, + ICON_BOX_MINUS = 240, + ICON_BOX_MINUS_FILL = 241, + ICON_UNION = 242, + ICON_INTERSECTION = 243, + ICON_DIFFERENCE = 244, + ICON_SPHERE = 245, + ICON_CYLINDER = 246, + ICON_CONE = 247, + ICON_ELLIPSOID = 248, + ICON_CAPSULE = 249, + ICON_250 = 250, + ICON_251 = 251, + ICON_252 = 252, + ICON_253 = 253, + ICON_254 = 254, + ICON_255 = 255 +} GuiIconName; +#endif + +#endif + +#if defined(__cplusplus) +} // Prevents name mangling of functions +#endif + +#endif // RAYGUI_H + +/*********************************************************************************** +* +* RAYGUI IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RAYGUI_IMPLEMENTATION) + +#include // required for: isspace() [GuiTextBox()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), snprintf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() +#include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] +#include // Required for: roundf() [GuiColorPicker()] + +// Allow custom memory allocators +#if defined(RAYGUI_MALLOC) || defined(RAYGUI_CALLOC) || defined(RAYGUI_FREE) + #if !defined(RAYGUI_MALLOC) || !defined(RAYGUI_CALLOC) || !defined(RAYGUI_FREE) + #error "RAYGUI: if RAYGUI_MALLOC, RAYGUI_CALLOC, or RAYGUI_FREE is customized, all three must be customized" + #endif +#else + #include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] + + #define RAYGUI_MALLOC(sz) malloc(sz) + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) + #define RAYGUI_FREE(p) free(p) +#endif + +#ifdef __cplusplus + #define RAYGUI_CLITERAL(name) name +#else + #define RAYGUI_CLITERAL(name) (name) +#endif + +// Check if two rectangles are equal, used to validate a slider bounds as an id +#ifndef CHECK_BOUNDS_ID + #define CHECK_BOUNDS_ID(src, dst) (((int)src.x == (int)dst.x) && ((int)src.y == (int)dst.y) && ((int)src.width == (int)dst.width) && ((int)src.height == (int)dst.height)) +#endif + +#if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) + +// Embedded icons, no external file provided +#define RAYGUI_ICON_SIZE 16 // Size of icons in pixels (squared) +#define RAYGUI_ICON_MAX_ICONS 256 // Maximum number of icons +#define RAYGUI_ICON_MAX_NAME_LENGTH 32 // Maximum length of icon name id + +// Icons data is defined by bit array (every bit represents one pixel) +// Those arrays are stored as unsigned int data arrays, so, +// every array element defines 32 pixels (bits) of information +// One icon is defined by 8 int, (8 int*32 bit = 256 bit = 16*16 pixels) +// NOTE: Number of elemens depend on RAYGUI_ICON_SIZE (by default 16x16 pixels) +#define RAYGUI_ICON_DATA_ELEMENTS (RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32) + +//---------------------------------------------------------------------------------- +// Icons data for all gui possible icons (allocated on data segment by default) +// +// NOTE 1: Every icon is codified in binary form, using 1 bit per pixel, so, +// every 16x16 icon requires 8 integers (16*16/32) to be stored +// +// NOTE 2: A different icon set could be loaded over this array using GuiLoadIcons(), +// but loaded icons set must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS +// +// guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +//---------------------------------------------------------------------------------- +static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_NONE + 0x3ff80000, 0x2f082008, 0x2042207e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x00007ffe, // ICON_FOLDER_FILE_OPEN + 0x3ffe0000, 0x44226422, 0x400247e2, 0x5ffa4002, 0x57ea500a, 0x500a500a, 0x40025ffa, 0x00007ffe, // ICON_FILE_SAVE_CLASSIC + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024002, 0x44424282, 0x793e4102, 0x00000100, // ICON_FOLDER_OPEN + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024102, 0x44424102, 0x793e4282, 0x00000000, // ICON_FOLDER_SAVE + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x24442284, 0x21042104, 0x20042104, 0x00003ffc, // ICON_FILE_OPEN + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x21042104, 0x22842444, 0x20042104, 0x00003ffc, // ICON_FILE_SAVE + 0x3ff00000, 0x201c2010, 0x00042004, 0x20041004, 0x20844784, 0x00841384, 0x20042784, 0x00003ffc, // ICON_FILE_EXPORT + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x22042204, 0x22042f84, 0x20042204, 0x00003ffc, // ICON_FILE_ADD + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x25042884, 0x25042204, 0x20042884, 0x00003ffc, // ICON_FILE_DELETE + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_FILETYPE_TEXT + 0x3ff00000, 0x201c2010, 0x27042004, 0x244424c4, 0x26442444, 0x20642664, 0x20042004, 0x00003ffc, // ICON_FILETYPE_AUDIO + 0x3ff00000, 0x201c2010, 0x26042604, 0x20042004, 0x35442884, 0x2414222c, 0x20042004, 0x00003ffc, // ICON_FILETYPE_IMAGE + 0x3ff00000, 0x201c2010, 0x20c42004, 0x22442144, 0x22442444, 0x20c42144, 0x20042004, 0x00003ffc, // ICON_FILETYPE_PLAY + 0x3ff00000, 0x3ffc2ff0, 0x3f3c2ff4, 0x3dbc2eb4, 0x3dbc2bb4, 0x3f3c2eb4, 0x3ffc2ff4, 0x00002ff4, // ICON_FILETYPE_VIDEO + 0x3ff00000, 0x201c2010, 0x21842184, 0x21842004, 0x21842184, 0x21842184, 0x20042184, 0x00003ffc, // ICON_FILETYPE_INFO + 0x0ff00000, 0x381c0810, 0x28042804, 0x28042804, 0x28042804, 0x28042804, 0x20102ffc, 0x00003ff0, // ICON_FILE_COPY + 0x00000000, 0x701c0000, 0x079c1e14, 0x55a000f0, 0x079c00f0, 0x701c1e14, 0x00000000, 0x00000000, // ICON_FILE_CUT + 0x01c00000, 0x13e41bec, 0x3f841004, 0x204420c4, 0x20442044, 0x20442044, 0x207c2044, 0x00003fc0, // ICON_FILE_PASTE + 0x00000000, 0x3aa00fe0, 0x2abc2aa0, 0x2aa42aa4, 0x20042aa4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_CURSOR_HAND + 0x00000000, 0x003c000c, 0x030800c8, 0x30100c10, 0x10202020, 0x04400840, 0x01800280, 0x00000000, // ICON_CURSOR_POINTER + 0x00000000, 0x00180000, 0x01f00078, 0x03e007f0, 0x07c003e0, 0x04000e40, 0x00000000, 0x00000000, // ICON_CURSOR_CLASSIC + 0x00000000, 0x04000000, 0x11000a00, 0x04400a80, 0x01100220, 0x00580088, 0x00000038, 0x00000000, // ICON_PENCIL + 0x04000000, 0x15000a00, 0x50402880, 0x14102820, 0x05040a08, 0x015c028c, 0x007c00bc, 0x00000000, // ICON_PENCIL_BIG + 0x01c00000, 0x01400140, 0x01400140, 0x0ff80140, 0x0ff80808, 0x0aa80808, 0x0aa80aa8, 0x00000ff8, // ICON_BRUSH_CLASSIC + 0x1ffc0000, 0x5ffc7ffe, 0x40004000, 0x00807f80, 0x01c001c0, 0x01c001c0, 0x01c001c0, 0x00000080, // ICON_BRUSH_PAINTER + 0x00000000, 0x00800000, 0x01c00080, 0x03e001c0, 0x07f003e0, 0x036006f0, 0x000001c0, 0x00000000, // ICON_WATER_DROP + 0x00000000, 0x3e003800, 0x1f803f80, 0x0c201e40, 0x02080c10, 0x00840104, 0x00380044, 0x00000000, // ICON_COLOR_PICKER + 0x00000000, 0x07800300, 0x1fe00fc0, 0x3f883fd0, 0x0e021f04, 0x02040402, 0x00f00108, 0x00000000, // ICON_RUBBER + 0x00c00000, 0x02800140, 0x08200440, 0x20081010, 0x2ffe3004, 0x03f807fc, 0x00e001f0, 0x00000040, // ICON_COLOR_BUCKET + 0x00000000, 0x21843ffc, 0x01800180, 0x01800180, 0x01800180, 0x01800180, 0x03c00180, 0x00000000, // ICON_TEXT_T + 0x00800000, 0x01400180, 0x06200340, 0x0c100620, 0x1ff80c10, 0x380c1808, 0x70067004, 0x0000f80f, // ICON_TEXT_A + 0x78000000, 0x50004000, 0x00004800, 0x03c003c0, 0x03c003c0, 0x00100000, 0x0002000a, 0x0000000e, // ICON_SCALE + 0x75560000, 0x5e004002, 0x54001002, 0x41001202, 0x408200fe, 0x40820082, 0x40820082, 0x00006afe, // ICON_RESIZE + 0x00000000, 0x3f003f00, 0x3f003f00, 0x3f003f00, 0x00400080, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_POINT + 0x6d800000, 0x00004080, 0x40804080, 0x40800000, 0x00406d80, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_BILINEAR + 0x40080000, 0x1ffe2008, 0x14081008, 0x11081208, 0x10481088, 0x10081028, 0x10047ff8, 0x00001002, // ICON_CROP + 0x00100000, 0x3ffc0010, 0x2ab03550, 0x22b02550, 0x20b02150, 0x20302050, 0x2000fff0, 0x00002000, // ICON_CROP_ALPHA + 0x40000000, 0x1ff82000, 0x04082808, 0x01082208, 0x00482088, 0x00182028, 0x35542008, 0x00000002, // ICON_SQUARE_TOGGLE + 0x00000000, 0x02800280, 0x06c006c0, 0x0ea00ee0, 0x1e901eb0, 0x3e883e98, 0x7efc7e8c, 0x00000000, // ICON_SYMMETRY + 0x01000000, 0x05600100, 0x1d480d50, 0x7d423d44, 0x3d447d42, 0x0d501d48, 0x01000560, 0x00000100, // ICON_SYMMETRY_HORIZONTAL + 0x01800000, 0x04200240, 0x10080810, 0x00001ff8, 0x00007ffe, 0x0ff01ff8, 0x03c007e0, 0x00000180, // ICON_SYMMETRY_VERTICAL + 0x00000000, 0x010800f0, 0x02040204, 0x02040204, 0x07f00308, 0x1c000e00, 0x30003800, 0x00000000, // ICON_LENS + 0x00000000, 0x061803f0, 0x08240c0c, 0x08040814, 0x0c0c0804, 0x23f01618, 0x18002400, 0x00000000, // ICON_LENS_BIG + 0x00000000, 0x00000000, 0x1c7007c0, 0x638e3398, 0x1c703398, 0x000007c0, 0x00000000, 0x00000000, // ICON_EYE_ON + 0x00000000, 0x10002000, 0x04700fc0, 0x610e3218, 0x1c703098, 0x001007a0, 0x00000008, 0x00000000, // ICON_EYE_OFF + 0x00000000, 0x00007ffc, 0x40047ffc, 0x10102008, 0x04400820, 0x02800280, 0x02800280, 0x00000100, // ICON_FILTER_TOP + 0x00000000, 0x40027ffe, 0x10082004, 0x04200810, 0x02400240, 0x02400240, 0x01400240, 0x000000c0, // ICON_FILTER + 0x00800000, 0x00800080, 0x00000080, 0x3c9e0000, 0x00000000, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_POINT + 0x00800000, 0x00800080, 0x00800080, 0x3f7e01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL + 0x00800000, 0x00800080, 0x03e00080, 0x3e3e0220, 0x03e00220, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG + 0x01000000, 0x04400280, 0x01000100, 0x43842008, 0x43849ab2, 0x01002008, 0x04400100, 0x01000280, // ICON_TARGET_MOVE + 0x01000000, 0x04400280, 0x01000100, 0x41042108, 0x41049ff2, 0x01002108, 0x04400100, 0x01000280, // ICON_CURSOR_MOVE + 0x781e0000, 0x500a4002, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x4002500a, 0x0000781e, // ICON_CURSOR_SCALE + 0x00000000, 0x20003c00, 0x24002800, 0x01000200, 0x00400080, 0x00140024, 0x003c0004, 0x00000000, // ICON_CURSOR_SCALE_RIGHT + 0x00000000, 0x0004003c, 0x00240014, 0x00800040, 0x02000100, 0x28002400, 0x3c002000, 0x00000000, // ICON_CURSOR_SCALE_LEFT + 0x00000000, 0x00100020, 0x10101fc8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO + 0x00000000, 0x08000400, 0x080813f8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3f902020, 0x00400020, 0x00000000, // ICON_REREDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3fc82010, 0x00200010, 0x00000000, // ICON_MUTATE + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18101020, 0x00100fc8, 0x00000020, // ICON_ROTATE + 0x00000000, 0x04000200, 0x240429fc, 0x20042204, 0x20442004, 0x3f942024, 0x00400020, 0x00000000, // ICON_REPEAT + 0x00000000, 0x20001000, 0x22104c0e, 0x00801120, 0x11200040, 0x4c0e2210, 0x10002000, 0x00000000, // ICON_SHUFFLE + 0x7ffe0000, 0x50024002, 0x44024802, 0x41024202, 0x40424082, 0x40124022, 0x4002400a, 0x00007ffe, // ICON_EMPTYBOX + 0x00800000, 0x03e00080, 0x08080490, 0x3c9e0808, 0x08080808, 0x03e00490, 0x00800080, 0x00000000, // ICON_TARGET + 0x00800000, 0x00800080, 0x00800080, 0x3ffe01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL_FILL + 0x00800000, 0x00800080, 0x03e00080, 0x3ffe03e0, 0x03e003e0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x638c2008, 0x638cfbbe, 0x01002008, 0x07c00100, 0x01000380, // ICON_TARGET_MOVE_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x610c2108, 0x610cfffe, 0x01002108, 0x07c00100, 0x01000380, // ICON_CURSOR_MOVE_FILL + 0x781e0000, 0x6006700e, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x700e6006, 0x0000781e, // ICON_CURSOR_SCALE_FILL + 0x00000000, 0x38003c00, 0x24003000, 0x01000200, 0x00400080, 0x000c0024, 0x003c001c, 0x00000000, // ICON_CURSOR_SCALE_RIGHT_FILL + 0x00000000, 0x001c003c, 0x0024000c, 0x00800040, 0x02000100, 0x30002400, 0x3c003800, 0x00000000, // ICON_CURSOR_SCALE_LEFT_FILL + 0x00000000, 0x00300020, 0x10301ff8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO_FILL + 0x00000000, 0x0c000400, 0x0c081ff8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3ff02060, 0x00400060, 0x00000000, // ICON_REREDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3ff82030, 0x00200030, 0x00000000, // ICON_MUTATE_FILL + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18301020, 0x00300ff8, 0x00000020, // ICON_ROTATE_FILL + 0x00000000, 0x06000200, 0x26042ffc, 0x20042204, 0x20442004, 0x3ff42064, 0x00400060, 0x00000000, // ICON_REPEAT_FILL + 0x00000000, 0x30001000, 0x32107c0e, 0x00801120, 0x11200040, 0x7c0e3210, 0x10003000, 0x00000000, // ICON_SHUFFLE_FILL + 0x00000000, 0x30043ffc, 0x24042804, 0x21042204, 0x20442084, 0x20142024, 0x3ffc200c, 0x00000000, // ICON_EMPTYBOX_SMALL + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX + 0x00000000, 0x23c43ffc, 0x23c423c4, 0x200423c4, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP + 0x00000000, 0x3e043ffc, 0x3e043e04, 0x20043e04, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x3e043e04, 0x3e043e04, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x3e042004, 0x3e043e04, 0x3ffc3e04, 0x00000000, // ICON_BOX_BOTTOM_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x23c42004, 0x23c423c4, 0x3ffc23c4, 0x00000000, // ICON_BOX_BOTTOM + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x207c2004, 0x207c207c, 0x3ffc207c, 0x00000000, // ICON_BOX_BOTTOM_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x207c207c, 0x207c207c, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_LEFT + 0x00000000, 0x207c3ffc, 0x207c207c, 0x2004207c, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x23c423c4, 0x23c423c4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_CENTER + 0x7ffe0000, 0x40024002, 0x47e24182, 0x4ff247e2, 0x47e24ff2, 0x418247e2, 0x40024002, 0x00007ffe, // ICON_BOX_CIRCLE_MASK + 0x7fff0000, 0x40014001, 0x40014001, 0x49555ddd, 0x4945495d, 0x400149c5, 0x40014001, 0x00007fff, // ICON_POT + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x404e40ce, 0x48125432, 0x4006540e, 0x00007ffe, // ICON_ALPHA_MULTIPLY + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x5c4e40ce, 0x44124432, 0x40065c0e, 0x00007ffe, // ICON_ALPHA_CLEAR + 0x7ffe0000, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x00007ffe, // ICON_DITHERING + 0x07fe0000, 0x1ffa0002, 0x7fea000a, 0x402a402a, 0x5b2a512a, 0x5128552a, 0x40205128, 0x00007fe0, // ICON_MIPMAPS + 0x00000000, 0x1ff80000, 0x12481248, 0x12481ff8, 0x1ff81248, 0x12481248, 0x00001ff8, 0x00000000, // ICON_BOX_GRID + 0x12480000, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x00001248, // ICON_GRID + 0x00000000, 0x1c380000, 0x1c3817e8, 0x08100810, 0x08100810, 0x17e81c38, 0x00001c38, 0x00000000, // ICON_BOX_CORNERS_SMALL + 0x700e0000, 0x700e5ffa, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x5ffa700e, 0x0000700e, // ICON_BOX_CORNERS_BIG + 0x3f7e0000, 0x21422142, 0x21422142, 0x00003f7e, 0x21423f7e, 0x21422142, 0x3f7e2142, 0x00000000, // ICON_FOUR_BOXES + 0x00000000, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x00000000, // ICON_GRID_FILL + 0x7ffe0000, 0x7ffe7ffe, 0x77fe7000, 0x77fe77fe, 0x777e7700, 0x777e777e, 0x777e777e, 0x0000777e, // ICON_BOX_MULTISIZE + 0x781e0000, 0x40024002, 0x00004002, 0x01800000, 0x00000180, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_SMALL + 0x781e0000, 0x40024002, 0x00004002, 0x03c003c0, 0x03c003c0, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_MEDIUM + 0x781e0000, 0x40024002, 0x07e04002, 0x07e007e0, 0x07e007e0, 0x400207e0, 0x40024002, 0x0000781e, // ICON_ZOOM_BIG + 0x781e0000, 0x5ffa4002, 0x1ff85ffa, 0x1ff81ff8, 0x1ff81ff8, 0x5ffa1ff8, 0x40025ffa, 0x0000781e, // ICON_ZOOM_ALL + 0x00000000, 0x2004381c, 0x00002004, 0x00000000, 0x00000000, 0x20040000, 0x381c2004, 0x00000000, // ICON_ZOOM_CENTER + 0x00000000, 0x1db80000, 0x10081008, 0x10080000, 0x00001008, 0x10081008, 0x00001db8, 0x00000000, // ICON_BOX_DOTS_SMALL + 0x35560000, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x35562002, 0x00000000, // ICON_BOX_DOTS_BIG + 0x7ffe0000, 0x40024002, 0x48124ff2, 0x49924812, 0x48124992, 0x4ff24812, 0x40024002, 0x00007ffe, // ICON_BOX_CONCENTRIC + 0x00000000, 0x10841ffc, 0x10841084, 0x1ffc1084, 0x10841084, 0x10841084, 0x00001ffc, 0x00000000, // ICON_BOX_GRID_BIG + 0x00000000, 0x00000000, 0x10000000, 0x04000800, 0x01040200, 0x00500088, 0x00000020, 0x00000000, // ICON_OK_TICK + 0x00000000, 0x10080000, 0x04200810, 0x01800240, 0x02400180, 0x08100420, 0x00001008, 0x00000000, // ICON_CROSS + 0x00000000, 0x02000000, 0x00800100, 0x00200040, 0x00200010, 0x00800040, 0x02000100, 0x00000000, // ICON_ARROW_LEFT + 0x00000000, 0x00400000, 0x01000080, 0x04000200, 0x04000800, 0x01000200, 0x00400080, 0x00000000, // ICON_ARROW_RIGHT + 0x00000000, 0x00000000, 0x00000000, 0x08081004, 0x02200410, 0x00800140, 0x00000000, 0x00000000, // ICON_ARROW_DOWN + 0x00000000, 0x00000000, 0x01400080, 0x04100220, 0x10040808, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP + 0x00000000, 0x02000000, 0x03800300, 0x03e003c0, 0x03e003f0, 0x038003c0, 0x02000300, 0x00000000, // ICON_ARROW_LEFT_FILL + 0x00000000, 0x00400000, 0x01c000c0, 0x07c003c0, 0x07c00fc0, 0x01c003c0, 0x004000c0, 0x00000000, // ICON_ARROW_RIGHT_FILL + 0x00000000, 0x00000000, 0x00000000, 0x0ff81ffc, 0x03e007f0, 0x008001c0, 0x00000000, 0x00000000, // ICON_ARROW_DOWN_FILL + 0x00000000, 0x00000000, 0x01c00080, 0x07f003e0, 0x1ffc0ff8, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP_FILL + 0x00000000, 0x18a008c0, 0x32881290, 0x24822686, 0x26862482, 0x12903288, 0x08c018a0, 0x00000000, // ICON_AUDIO + 0x00000000, 0x04800780, 0x004000c0, 0x662000f0, 0x08103c30, 0x130a0e18, 0x0000318e, 0x00000000, // ICON_FX + 0x00000000, 0x00800000, 0x08880888, 0x2aaa0a8a, 0x0a8a2aaa, 0x08880888, 0x00000080, 0x00000000, // ICON_WAVE + 0x00000000, 0x00600000, 0x01080090, 0x02040108, 0x42044204, 0x24022402, 0x00001800, 0x00000000, // ICON_WAVE_SINUS + 0x00000000, 0x07f80000, 0x04080408, 0x04080408, 0x04080408, 0x7c0e0408, 0x00000000, 0x00000000, // ICON_WAVE_SQUARE + 0x00000000, 0x00000000, 0x00a00040, 0x22084110, 0x08021404, 0x00000000, 0x00000000, 0x00000000, // ICON_WAVE_TRIANGULAR + 0x00000000, 0x00000000, 0x04200000, 0x01800240, 0x02400180, 0x00000420, 0x00000000, 0x00000000, // ICON_CROSS_SMALL + 0x00000000, 0x18380000, 0x12281428, 0x10a81128, 0x112810a8, 0x14281228, 0x00001838, 0x00000000, // ICON_PLAYER_PREVIOUS + 0x00000000, 0x18000000, 0x11801600, 0x10181060, 0x10601018, 0x16001180, 0x00001800, 0x00000000, // ICON_PLAYER_PLAY_BACK + 0x00000000, 0x00180000, 0x01880068, 0x18080608, 0x06081808, 0x00680188, 0x00000018, 0x00000000, // ICON_PLAYER_PLAY + 0x00000000, 0x1e780000, 0x12481248, 0x12481248, 0x12481248, 0x12481248, 0x00001e78, 0x00000000, // ICON_PLAYER_PAUSE + 0x00000000, 0x1ff80000, 0x10081008, 0x10081008, 0x10081008, 0x10081008, 0x00001ff8, 0x00000000, // ICON_PLAYER_STOP + 0x00000000, 0x1c180000, 0x14481428, 0x15081488, 0x14881508, 0x14281448, 0x00001c18, 0x00000000, // ICON_PLAYER_NEXT + 0x00000000, 0x03c00000, 0x08100420, 0x10081008, 0x10081008, 0x04200810, 0x000003c0, 0x00000000, // ICON_PLAYER_RECORD + 0x00000000, 0x0c3007e0, 0x13c81818, 0x14281668, 0x14281428, 0x1c381c38, 0x08102244, 0x00000000, // ICON_MAGNET + 0x07c00000, 0x08200820, 0x3ff80820, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_CLOSE + 0x07c00000, 0x08000800, 0x3ff80800, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_OPEN + 0x01c00000, 0x0c180770, 0x3086188c, 0x60832082, 0x60034781, 0x30062002, 0x0c18180c, 0x01c00770, // ICON_CLOCK + 0x0a200000, 0x1b201b20, 0x04200e20, 0x04200420, 0x04700420, 0x0e700e70, 0x0e700e70, 0x04200e70, // ICON_TOOLS + 0x01800000, 0x3bdc318c, 0x0ff01ff8, 0x7c3e1e78, 0x1e787c3e, 0x1ff80ff0, 0x318c3bdc, 0x00000180, // ICON_GEAR + 0x01800000, 0x3ffc318c, 0x1c381ff8, 0x781e1818, 0x1818781e, 0x1ff81c38, 0x318c3ffc, 0x00000180, // ICON_GEAR_BIG + 0x00000000, 0x08080ff8, 0x08081ffc, 0x0aa80aa8, 0x0aa80aa8, 0x0aa80aa8, 0x08080aa8, 0x00000ff8, // ICON_BIN + 0x00000000, 0x00000000, 0x20043ffc, 0x08043f84, 0x04040f84, 0x04040784, 0x000007fc, 0x00000000, // ICON_HAND_POINTER + 0x00000000, 0x24400400, 0x00001480, 0x6efe0e00, 0x00000e00, 0x24401480, 0x00000400, 0x00000000, // ICON_LASER + 0x00000000, 0x03c00000, 0x08300460, 0x11181118, 0x11181118, 0x04600830, 0x000003c0, 0x00000000, // ICON_COIN + 0x00000000, 0x10880080, 0x06c00810, 0x366c07e0, 0x07e00240, 0x00001768, 0x04200240, 0x00000000, // ICON_EXPLOSION + 0x00000000, 0x3d280000, 0x2528252c, 0x3d282528, 0x05280528, 0x05e80528, 0x00000000, 0x00000000, // ICON_1UP + 0x01800000, 0x03c003c0, 0x018003c0, 0x0ff007e0, 0x0bd00bd0, 0x0a500bd0, 0x02400240, 0x02400240, // ICON_PLAYER + 0x01800000, 0x03c003c0, 0x118013c0, 0x03c81ff8, 0x07c003c8, 0x04400440, 0x0c080478, 0x00000000, // ICON_PLAYER_JUMP + 0x3ff80000, 0x30183ff8, 0x30183018, 0x3ff83ff8, 0x03000300, 0x03c003c0, 0x03e00300, 0x000003e0, // ICON_KEY + 0x3ff80000, 0x3ff83ff8, 0x33983ff8, 0x3ff83398, 0x3ff83ff8, 0x00000540, 0x0fe00aa0, 0x00000fe0, // ICON_DEMON + 0x00000000, 0x0ff00000, 0x20041008, 0x25442004, 0x10082004, 0x06000bf0, 0x00000300, 0x00000000, // ICON_TEXT_POPUP + 0x00000000, 0x11440000, 0x07f00be8, 0x1c1c0e38, 0x1c1c0c18, 0x07f00e38, 0x11440be8, 0x00000000, // ICON_GEAR_EX + 0x00000000, 0x20080000, 0x0c601010, 0x07c00fe0, 0x07c007c0, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK + 0x00000000, 0x20080000, 0x0c601010, 0x04400fe0, 0x04405554, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK_POINTS + 0x00000000, 0x00800080, 0x01c001c0, 0x1ffc3ffe, 0x03e007f0, 0x07f003e0, 0x0c180770, 0x00000808, // ICON_STAR + 0x0ff00000, 0x08180810, 0x08100818, 0x0a100810, 0x08180810, 0x08100818, 0x08100810, 0x00001ff8, // ICON_DOOR + 0x0ff00000, 0x08100810, 0x08100810, 0x10100010, 0x4f902010, 0x10102010, 0x08100010, 0x00000ff0, // ICON_EXIT + 0x00040000, 0x001f000e, 0x0ef40004, 0x12f41284, 0x0ef41214, 0x10040004, 0x7ffc3004, 0x10003000, // ICON_MODE_2D + 0x78040000, 0x501f600e, 0x0ef44004, 0x12f41284, 0x0ef41284, 0x10140004, 0x7ffc300c, 0x10003000, // ICON_MODE_3D + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE + 0x7fe00000, 0x5ff87ff0, 0x47fe4ffc, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_TOP + 0x7fe00000, 0x50386030, 0x47c2483c, 0x443e443e, 0x443e443e, 0x241e75fe, 0x0c06140e, 0x000007fe, // ICON_CUBE_FACE_LEFT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x47fe47fe, 0x47fe47fe, 0x27fe77fe, 0x0ffe17fe, 0x000007fe, // ICON_CUBE_FACE_FRONT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x3bf27be2, 0x0bfe1bfa, 0x000007fe, // ICON_CUBE_FACE_BOTTOM + 0x7fe00000, 0x70286030, 0x7ffe7804, 0x7c227c02, 0x7c227c22, 0x3c127de2, 0x0c061c0a, 0x000007fe, // ICON_CUBE_FACE_RIGHT + 0x7fe00000, 0x6fe85ff0, 0x781e77e4, 0x7be27be2, 0x7be27be2, 0x24127be2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_BACK + 0x00000000, 0x2a0233fe, 0x22022602, 0x22022202, 0x2a022602, 0x00a033fe, 0x02080110, 0x00000000, // ICON_CAMERA + 0x00000000, 0x200c3ffc, 0x000c000c, 0x3ffc000c, 0x30003000, 0x30003000, 0x3ffc3004, 0x00000000, // ICON_SPECIAL + 0x00000000, 0x0022003e, 0x012201e2, 0x0100013e, 0x01000100, 0x79000100, 0x4f004900, 0x00007800, // ICON_LINK_NET + 0x00000000, 0x44007c00, 0x45004600, 0x00627cbe, 0x00620022, 0x45007cbe, 0x44004600, 0x00007c00, // ICON_LINK_BOXES + 0x00000000, 0x0044007c, 0x0010007c, 0x3f100010, 0x3f1021f0, 0x3f100010, 0x3f0021f0, 0x00000000, // ICON_LINK_MULTI + 0x00000000, 0x0044007c, 0x00440044, 0x0010007c, 0x00100010, 0x44107c10, 0x440047f0, 0x00007c00, // ICON_LINK + 0x00000000, 0x0044007c, 0x00440044, 0x0000007c, 0x00000010, 0x44007c10, 0x44004550, 0x00007c00, // ICON_LINK_BROKE + 0x02a00000, 0x22a43ffc, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_TEXT_NOTES + 0x3ffc0000, 0x20042004, 0x245e27c4, 0x27c42444, 0x2004201e, 0x201e2004, 0x20042004, 0x00003ffc, // ICON_NOTEBOOK + 0x00000000, 0x07e00000, 0x04200420, 0x24243ffc, 0x24242424, 0x24242424, 0x3ffc2424, 0x00000000, // ICON_SUITCASE + 0x00000000, 0x0fe00000, 0x08200820, 0x40047ffc, 0x7ffc5554, 0x40045554, 0x7ffc4004, 0x00000000, // ICON_SUITCASE_ZIP + 0x00000000, 0x20043ffc, 0x3ffc2004, 0x13c81008, 0x100813c8, 0x10081008, 0x1ff81008, 0x00000000, // ICON_MAILBOX + 0x00000000, 0x40027ffe, 0x5ffa5ffa, 0x5ffa5ffa, 0x40025ffa, 0x03c07ffe, 0x1ff81ff8, 0x00000000, // ICON_MONITOR + 0x0ff00000, 0x6bfe7ffe, 0x7ffe7ffe, 0x68167ffe, 0x08106816, 0x08100810, 0x0ff00810, 0x00000000, // ICON_PRINTER + 0x3ff80000, 0xfffe2008, 0x870a8002, 0x904a888a, 0x904a904a, 0x870a888a, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA + 0x0fc00000, 0xfcfe0cd8, 0x8002fffe, 0x84428382, 0x84428442, 0x80028382, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA_FLASH + 0x00000000, 0x02400180, 0x08100420, 0x20041008, 0x23c42004, 0x22442244, 0x3ffc2244, 0x00000000, // ICON_HOUSE + 0x00000000, 0x1c700000, 0x3ff83ef8, 0x3ff83ff8, 0x0fe01ff0, 0x038007c0, 0x00000100, 0x00000000, // ICON_HEART + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0xe000c000, // ICON_CORNER + 0x00000000, 0x14001c00, 0x15c01400, 0x15401540, 0x155c1540, 0x15541554, 0x1ddc1554, 0x00000000, // ICON_VERTICAL_BARS + 0x00000000, 0x03000300, 0x1b001b00, 0x1b601b60, 0x1b6c1b60, 0x1b6c1b6c, 0x1b6c1b6c, 0x00000000, // ICON_VERTICAL_BARS_FILL + 0x00000000, 0x00000000, 0x403e7ffe, 0x7ffe403e, 0x7ffe0000, 0x43fe43fe, 0x00007ffe, 0x00000000, // ICON_LIFE_BARS + 0x7ffc0000, 0x43844004, 0x43844284, 0x43844004, 0x42844284, 0x42844284, 0x40044384, 0x00007ffc, // ICON_INFO + 0x40008000, 0x10002000, 0x04000800, 0x01000200, 0x00400080, 0x00100020, 0x00040008, 0x00010002, // ICON_CROSSLINE + 0x00000000, 0x1ff01ff0, 0x18301830, 0x1f001830, 0x03001f00, 0x00000300, 0x03000300, 0x00000000, // ICON_HELP + 0x3ff00000, 0x2abc3550, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x00003ffc, // ICON_FILETYPE_ALPHA + 0x3ff00000, 0x201c2010, 0x22442184, 0x28142424, 0x29942814, 0x2ff42994, 0x20042004, 0x00003ffc, // ICON_FILETYPE_HOME + 0x07fe0000, 0x04020402, 0x7fe20402, 0x44224422, 0x44224422, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS_VISIBLE + 0x07fe0000, 0x04020402, 0x7c020402, 0x44024402, 0x44024402, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS + 0x00000000, 0x40027ffe, 0x7ffe4002, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_WINDOW + 0x09100000, 0x09f00910, 0x09100910, 0x00000910, 0x24a2779e, 0x27a224a2, 0x709e20a2, 0x00000000, // ICON_HIDPI + 0x3ff00000, 0x201c2010, 0x2a842e84, 0x2e842a84, 0x2ba42004, 0x2aa42aa4, 0x20042ba4, 0x00003ffc, // ICON_FILETYPE_BINARY + 0x00000000, 0x00000000, 0x00120012, 0x4a5e4bd2, 0x485233d2, 0x00004bd2, 0x00000000, 0x00000000, // ICON_HEX + 0x01800000, 0x381c0660, 0x23c42004, 0x23c42044, 0x13c82204, 0x08101008, 0x02400420, 0x00000180, // ICON_SHIELD + 0x007e0000, 0x20023fc2, 0x40227fe2, 0x400a403a, 0x400a400a, 0x400a400a, 0x4008400e, 0x00007ff8, // ICON_FILE_NEW + 0x00000000, 0x0042007e, 0x40027fc2, 0x44024002, 0x5f024402, 0x44024402, 0x7ffe4002, 0x00000000, // ICON_FOLDER_ADD + 0x44220000, 0x12482244, 0xf3cf0000, 0x14280420, 0x48122424, 0x08100810, 0x1ff81008, 0x03c00420, // ICON_ALARM + 0x0aa00000, 0x1ff80aa0, 0x1068700e, 0x1008706e, 0x1008700e, 0x1008700e, 0x0aa01ff8, 0x00000aa0, // ICON_CPU + 0x07e00000, 0x04201db8, 0x04a01c38, 0x04a01d38, 0x04a01d38, 0x04a01d38, 0x04201d38, 0x000007e0, // ICON_ROM + 0x00000000, 0x03c00000, 0x3c382ff0, 0x3c04380c, 0x01800000, 0x03c003c0, 0x00000180, 0x00000000, // ICON_STEP_OVER + 0x01800000, 0x01800180, 0x01800180, 0x03c007e0, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_INTO + 0x01800000, 0x07e003c0, 0x01800180, 0x01800180, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_OUT + 0x00000000, 0x0ff003c0, 0x181c1c34, 0x303c301c, 0x30003000, 0x1c301800, 0x03c00ff0, 0x00000000, // ICON_RESTART + 0x00000000, 0x00000000, 0x07e003c0, 0x0ff00ff0, 0x0ff00ff0, 0x03c007e0, 0x00000000, 0x00000000, // ICON_BREAKPOINT_ON + 0x00000000, 0x00000000, 0x042003c0, 0x08100810, 0x08100810, 0x03c00420, 0x00000000, 0x00000000, // ICON_BREAKPOINT_OFF + 0x00000000, 0x00000000, 0x1ff81ff8, 0x1ff80000, 0x00001ff8, 0x1ff81ff8, 0x00000000, 0x00000000, // ICON_BURGER_MENU + 0x00000000, 0x00000000, 0x00880070, 0x0c880088, 0x1e8810f8, 0x3e881288, 0x00000000, 0x00000000, // ICON_CASE_SENSITIVE + 0x00000000, 0x02000000, 0x07000a80, 0x07001fc0, 0x02000a80, 0x00300030, 0x00000000, 0x00000000, // ICON_REG_EXP + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE + 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER + 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING + 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX + 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX + 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY + 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO + 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS + 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT + 0x00000000, 0x20803f00, 0x2a202e40, 0x20082e10, 0x08021004, 0x02040402, 0x00900108, 0x00000060, // ICON_LABEL + 0x00000000, 0x042007e0, 0x47e27c3e, 0x4ffa4002, 0x47fa4002, 0x4ffa4002, 0x7ffe4002, 0x00000000, // ICON_NAME_ID + 0x7fe00000, 0x402e4020, 0x43ce5e0a, 0x40504078, 0x438e4078, 0x402e5e0a, 0x7fe04020, 0x00000000, // ICON_SLICING + 0x00000000, 0x40027ffe, 0x47c24002, 0x55425d42, 0x55725542, 0x50125552, 0x10105016, 0x00001ff0, // ICON_MANUAL_CONTROL + 0x7ffe0000, 0x43c24002, 0x48124422, 0x500a500a, 0x500a500a, 0x44224812, 0x400243c2, 0x00007ffe, // ICON_COLLISION + 0x03c00000, 0x10080c30, 0x21842184, 0x4ff24182, 0x41824ff2, 0x21842184, 0x0c301008, 0x000003c0, // ICON_CIRCLE_ADD + 0x03c00000, 0x1ff80ff0, 0x3e7c3e7c, 0x700e7e7e, 0x7e7e700e, 0x3e7c3e7c, 0x0ff01ff8, 0x000003c0, // ICON_CIRCLE_ADD_FILL + 0x03c00000, 0x10080c30, 0x21842184, 0x41824182, 0x40024182, 0x21842184, 0x0c301008, 0x000003c0, // ICON_CIRCLE_WARNING + 0x03c00000, 0x1ff80ff0, 0x3e7c3e7c, 0x7e7e7e7e, 0x7ffe7e7e, 0x3e7c3e7c, 0x0ff01ff8, 0x000003c0, // ICON_CIRCLE_WARNING_FILL + 0x00000000, 0x10041ffc, 0x10841004, 0x13e41084, 0x10841084, 0x10041004, 0x00001ffc, 0x00000000, // ICON_BOX_MORE + 0x00000000, 0x1ffc1ffc, 0x1f7c1ffc, 0x1c1c1f7c, 0x1f7c1f7c, 0x1ffc1ffc, 0x00001ffc, 0x00000000, // ICON_BOX_MORE_FILL + 0x00000000, 0x1ffc1ffc, 0x1ffc1ffc, 0x1c1c1ffc, 0x1ffc1ffc, 0x1ffc1ffc, 0x00001ffc, 0x00000000, // ICON_BOX_MINUS + 0x00000000, 0x10041ffc, 0x10041004, 0x13e41004, 0x10041004, 0x10041004, 0x00001ffc, 0x00000000, // ICON_BOX_MINUS_FILL + 0x07fe0000, 0x055606aa, 0x7ff606aa, 0x55766eba, 0x55766eaa, 0x55606ffe, 0x55606aa0, 0x00007fe0, // ICON_UNION + 0x07fe0000, 0x04020402, 0x7fe20402, 0x456246a2, 0x456246a2, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_INTERSECTION + 0x07fe0000, 0x055606aa, 0x7ff606aa, 0x4436442a, 0x4436442a, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_DIFFERENCE + 0x03c00000, 0x10080c30, 0x20042004, 0x60064002, 0x47e2581a, 0x20042004, 0x0c301008, 0x000003c0, // ICON_SPHERE + 0x03e00000, 0x08080410, 0x0c180808, 0x08080be8, 0x08080808, 0x08080808, 0x04100808, 0x000003e0, // ICON_CYLINDER + 0x00800000, 0x01400140, 0x02200220, 0x04100410, 0x08080808, 0x1c1c13e4, 0x08081004, 0x000007f0, // ICON_CONE + 0x00000000, 0x07e00000, 0x20841918, 0x40824082, 0x40824082, 0x19182084, 0x000007e0, 0x00000000, // ICON_ELLIPSOID + 0x00000000, 0x00000000, 0x20041ff8, 0x40024002, 0x40024002, 0x1ff82004, 0x00000000, 0x00000000, // ICON_CAPSULE + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_253 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_254 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 +}; + +// NOTE: A pointer to current icons array should be defined +static unsigned int *guiIconsPtr = guiIcons; + +#endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS + +#ifndef RAYGUI_ICON_SIZE + #define RAYGUI_ICON_SIZE 0 +#endif + +// WARNING: Those values define the total size of the style data array, +// if changed, previous saved styles could become incompatible +#define RAYGUI_MAX_CONTROLS 16 // Maximum number of controls +#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of base properties +#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties + +//---------------------------------------------------------------------------------- +// Module Types and Structures Definition +//---------------------------------------------------------------------------------- +// Gui control property style color element +typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GuiState guiState = STATE_NORMAL; // Gui global state, if !STATE_NORMAL, forces defined state + +static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) +static bool guiLocked = false; // Gui lock state (no inputs processed) +static float guiAlpha = 1.0f; // Gui controls transparency + +static unsigned int guiIconScale = 1; // Gui icon default scale (if icons enabled) + +static bool guiTooltip = false; // Tooltip enabled/disabled +static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) + +static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) +static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier + +static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() +//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking +static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay) + +//---------------------------------------------------------------------------------- +// Style data array for all gui style properties (allocated on data segment by default) +// +// NOTE 1: First set of BASE properties are generic to all controls but could be individually +// overwritten per control, first set of EXTENDED properties are generic to all controls and +// can not be overwritten individually but custom EXTENDED properties can be used by control +// +// NOTE 2: A new style set could be loaded over this array using GuiLoadStyle(), +// but default gui style could always be recovered with GuiLoadStyleDefault() +// +// guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +//---------------------------------------------------------------------------------- +static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; + +static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization + +//---------------------------------------------------------------------------------- +// Standalone Mode Functions Declaration +// +// NOTE: raygui depend on some raylib input and drawing functions +// To use raygui as standalone library, below functions must be defined by the user +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + +#define KEY_RIGHT 262 +#define KEY_LEFT 263 +#define KEY_DOWN 264 +#define KEY_UP 265 +#define KEY_BACKSPACE 259 +#define KEY_ENTER 257 + +#define MOUSE_LEFT_BUTTON 0 + +// Input required functions +//------------------------------------------------------------------------------- +static Vector2 GetMousePosition(void); +static float GetMouseWheelMove(void); +static bool IsMouseButtonDown(int button); +static bool IsMouseButtonPressed(int button); +static bool IsMouseButtonReleased(int button); + +static bool IsKeyDown(int key); +static bool IsKeyPressed(int key); +static int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +//------------------------------------------------------------------------------- + +// Drawing required functions +//------------------------------------------------------------------------------- +static void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +static void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +//------------------------------------------------------------------------------- + +// Text required functions +//------------------------------------------------------------------------------- +static Font GetFontDefault(void); // -- GuiLoadStyleDefault() +static Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle(), load font + +static Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +static void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) + +static char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +static void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data + +static const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs + +static int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +static void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list + +static unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +//------------------------------------------------------------------------------- + +// raylib functions already implemented in raygui +//------------------------------------------------------------------------------- +static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value +static int ColorToInt(Color color); // Returns hexadecimal value for a Color +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' +static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +static int TextToInteger(const char *text); // Get integer value from text +static float TextToFloat(const char *text); // Get float value from text + +static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text +static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) + +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); // Draw rectangle vertical gradient +//------------------------------------------------------------------------------- + +#endif // RAYGUI_STANDALONE + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) + +static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds +static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor + +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint); // Gui draw text using default font +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color); // Gui draw rectangle using default raygui style + +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow); // Split controls text into multiple strings +static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color data from HSV to RGB +static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV + +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() +static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position + +static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor + +//---------------------------------------------------------------------------------- +// Gui Setup Functions Definition +//---------------------------------------------------------------------------------- +// Enable gui global state +// NOTE: We check for STATE_DISABLED to avoid messing custom global state setups +void GuiEnable(void) { if (guiState == STATE_DISABLED) guiState = STATE_NORMAL; } + +// Disable gui global state +// NOTE: We check for STATE_NORMAL to avoid messing custom global state setups +void GuiDisable(void) { if (guiState == STATE_NORMAL) guiState = STATE_DISABLED; } + +// Lock gui global state +void GuiLock(void) { guiLocked = true; } + +// Unlock gui global state +void GuiUnlock(void) { guiLocked = false; } + +// Check if gui is locked (global state) +bool GuiIsLocked(void) { return guiLocked; } + +// Set gui controls alpha global state +void GuiSetAlpha(float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + guiAlpha = alpha; +} + +// Set gui state (global state) +void GuiSetState(int state) { guiState = (GuiState)state; } + +// Get gui state (global state) +int GuiGetState(void) { return guiState; } + +// Set custom gui font +// NOTE: Font loading/unloading is external to raygui +void GuiSetFont(Font font) +{ + if (font.texture.id > 0) + { + // NOTE: If we try to setup a font but default style has not been + // lazily loaded before, it will be overwritten, so we need to force + // default style loading first + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + guiFont = font; + } +} + +// Get custom gui font +Font GuiGetFont(void) +{ + return guiFont; +} + +// Set control style property value +void GuiSetStyle(int control, int property, int value) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + + // Default properties are propagated to all controls + if ((control == 0) && (property < RAYGUI_MAX_PROPS_BASE)) + { + for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) guiStyle[i*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + } +} + +// Get control style property value +int GuiGetStyle(int control, int property) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + return guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property]; +} + +//---------------------------------------------------------------------------------- +// Gui Controls Functions Definition +//---------------------------------------------------------------------------------- + +// Window Box control +int GuiWindowBox(Rectangle bounds, const char *title) +{ + // Window title bar height (including borders) + // NOTE: This define is also used by GuiMessageBox() and GuiTextInputBox() + #if !defined(RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT) + #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 + #endif + + #if !defined(RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT) + #define RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT 18 + #endif + + int result = 0; + //GuiState state = guiState; + + int statusBarHeight = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT; + + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; + if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + + const float vPadding = statusBarHeight/2.0f - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT/2.0f; + Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT - vPadding, + statusBar.y + vPadding, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT }; + + // Update control + //-------------------------------------------------------------------- + // NOTE: Logic is directly managed by button + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiStatusBar(statusBar, title); // Draw window header as status bar + GuiPanel(windowPanel, NULL); // Draw window base + + // Draw window close button + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + result = GuiButton(closeButtonRec, "x"); +#else + result = GuiButton(closeButtonRec, GuiIconText(ICON_CROSS_SMALL, NULL)); +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + //-------------------------------------------------------------------- + + return result; // Window close button clicked: result = 1 +} + +// Group Box control with text name +int GuiGroupBox(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_GROUPBOX_LINE_THICK) + #define RAYGUI_GROUPBOX_LINE_THICK 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + + GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y - GuiGetStyle(DEFAULT, TEXT_SIZE)/2, bounds.width, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) }, text); + //-------------------------------------------------------------------- + + return result; +} + +// Line control +int GuiLine(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_LINE_MARGIN_TEXT) + #define RAYGUI_LINE_MARGIN_TEXT 12 + #endif + #if !defined(RAYGUI_LINE_TEXT_PADDING) + #define RAYGUI_LINE_TEXT_PADDING 4 + #endif + + int result = 0; + GuiState state = guiState; + + Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)); + + // Draw control + //-------------------------------------------------------------------- + if (text == NULL) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, bounds.width, 1 }, 0, BLANK, color); + else + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = bounds.height; + textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; + textBounds.y = bounds.y; + + // Draw line with embedded text label: "--- text --------------" + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + GuiDrawText(text, textBounds, TEXT_ALIGN_LEFT, color); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + 12 + textBounds.width + 4, bounds.y + bounds.height/2, bounds.width - textBounds.width - RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + } + //-------------------------------------------------------------------- + + return result; +} + +// Panel control +int GuiPanel(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_PANEL_BORDER_WIDTH) + #define RAYGUI_PANEL_BORDER_WIDTH 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if ((text != NULL) && (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f)) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + } + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BASE_COLOR_DISABLED : (int)BACKGROUND_COLOR))); + //-------------------------------------------------------------------- + + return result; +} + +// Tab Bar control +// NOTE: Using GuiToggle() for the TABS +int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) +{ + #define RAYGUI_TABBAR_ITEM_WIDTH 148 + + int result = -1; + //GuiState state = guiState; + + Rectangle tabBounds = { bounds.x, bounds.y, RAYGUI_TABBAR_ITEM_WIDTH, bounds.height }; + + if (*active < 0) *active = 0; + else if (*active > count - 1) *active = count - 1; + + int offsetX = 0; // Required in case tabs go out of screen + offsetX = (*active + 2)*RAYGUI_TABBAR_ITEM_WIDTH - GetScreenWidth(); + if (offsetX < 0) offsetX = 0; + + bool toggle = false; // Required for individual toggles + + // Draw control + //-------------------------------------------------------------------- + for (int i = 0; i < count; i++) + { + tabBounds.x = bounds.x + (RAYGUI_TABBAR_ITEM_WIDTH + 4)*i - offsetX; + + if (tabBounds.x < GetScreenWidth()) + { + // Draw tabs as toggle controls + int textAlignment = GuiGetStyle(TOGGLE, TEXT_ALIGNMENT); + int textPadding = GuiGetStyle(TOGGLE, TEXT_PADDING); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(TOGGLE, TEXT_PADDING, 8); + + if (i == (*active)) + { + toggle = true; + GuiToggle(tabBounds, text[i], &toggle); + } + else + { + toggle = false; + GuiToggle(tabBounds, text[i], &toggle); + if (toggle) *active = i; + } + + // Close tab with middle mouse button pressed + if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; + + GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); + + // Draw tab close button + // NOTE: Only draw close button for current tab: if (CheckCollisionPointRec(mousePosition, tabBounds)) + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, "x")) result = i; +#else + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, GuiIconText(ICON_CROSS_SMALL, NULL))) result = i; +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + } + } + + // Draw tab-bar bottom line + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, 1 }, 0, BLANK, GetColor(GuiGetStyle(TOGGLE, BORDER_COLOR_NORMAL))); + //-------------------------------------------------------------------- + + return result; // Return as result the current TAB closing requested +} + +// Scroll Panel control +int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view) +{ + #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 + #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 + #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 + + int result = 0; + GuiState state = guiState; + + Rectangle temp = { 0 }; + if (view == NULL) view = &temp; + + Vector2 scrollPos = { 0.0f, 0.0f }; + if (scroll != NULL) scrollPos = *scroll; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + 1; + } + + bool hasHorizontalScrollBar = (content.width > bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + bool hasVerticalScrollBar = (content.height > bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + + // Recheck to account for the other scrollbar being visible + if (!hasHorizontalScrollBar) hasHorizontalScrollBar = (hasVerticalScrollBar && (content.width > (bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + if (!hasVerticalScrollBar) hasVerticalScrollBar = (hasHorizontalScrollBar && (content.height > (bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + + int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + Rectangle horizontalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)horizontalScrollBarWidth + }; + Rectangle verticalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), + (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)verticalScrollBarWidth, + (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Make sure scroll bars have a minimum width/height + if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; + if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; + + // Calculate view area (area without the scrollbars) + *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? + RAYGUI_CLITERAL(Rectangle){ bounds.x + verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth } : + RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth }; + + // Clip view area to the actual content size + if (view->width > content.width) view->width = content.width; + if (view->height > content.height) view->height = content.height; + + float horizontalMin = hasHorizontalScrollBar? ((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH); + float horizontalMax = hasHorizontalScrollBar? content.width - bounds.width + (float)verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH) - (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)verticalScrollBarWidth : 0) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + float verticalMin = hasVerticalScrollBar? 0.0f : -1.0f; + float verticalMax = hasVerticalScrollBar? content.height - bounds.height + (float)horizontalScrollBarWidth + (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + +#if defined(SUPPORT_SCROLLBAR_KEY_INPUT) + if (hasHorizontalScrollBar) + { + if (IsKeyDown(KEY_RIGHT)) scrollPos.x -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_LEFT)) scrollPos.x += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } + + if (hasVerticalScrollBar) + { + if (IsKeyDown(KEY_DOWN)) scrollPos.y -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_UP)) scrollPos.y += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } +#endif + float wheelMove = GetMouseWheelMove(); + + // Set scrolling speed with mouse wheel based on ratio between bounds and content + Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; + if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + + // Horizontal and vertical scrolling with mouse wheel + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; + else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll + } + } + + // Normalize scroll values + if (scrollPos.x > -horizontalMin) scrollPos.x = -horizontalMin; + if (scrollPos.x < -horizontalMax) scrollPos.x = -horizontalMax; + if (scrollPos.y > -verticalMin) scrollPos.y = -verticalMin; + if (scrollPos.y < -verticalMax) scrollPos.y = -verticalMax; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Save size of the scrollbar slider + const int slider = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + + // Draw horizontal scrollbar if visible + if (hasHorizontalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content width and the widget width + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth)/(int)content.width)*((int)bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth))); + scrollPos.x = (float)-GuiScrollBar(horizontalScrollBar, (int)-scrollPos.x, (int)horizontalMin, (int)horizontalMax); + } + else scrollPos.x = 0.0f; + + // Draw vertical scrollbar if visible + if (hasVerticalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content height and the widget height + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth)/(int)content.height)*((int)bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth))); + scrollPos.y = (float)-GuiScrollBar(verticalScrollBar, (int)-scrollPos.y, (int)verticalMin, (int)verticalMax); + } + else scrollPos.y = 0.0f; + + // Draw detail corner rectangle if both scroll bars are visible + if (hasHorizontalScrollBar && hasVerticalScrollBar) + { + Rectangle corner = { (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) + 2) : (horizontalScrollBar.x + horizontalScrollBar.width + 2), verticalScrollBar.y + verticalScrollBar.height + 2, (float)horizontalScrollBarWidth - 4, (float)verticalScrollBarWidth - 4 }; + GuiDrawRectangle(corner, 0, BLANK, GetColor(GuiGetStyle(LISTVIEW, TEXT + (state*3)))); + } + + // Draw scrollbar lines depending on current state + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); + + // Set scrollbar slider size back to the way it was before + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); + //-------------------------------------------------------------------- + + if (scroll != NULL) *scroll = scrollPos; + + return result; +} + +// Label control +int GuiLabel(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + //... + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Button control, returns true when clicked +int GuiButton(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), GetColor(GuiGetStyle(BUTTON, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3)))); + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //------------------------------------------------------------------ + + return result; // Button pressed: result = 1 +} + +// Label button control +int GuiLabelButton(Rectangle bounds, const char *text) +{ + GuiState state = guiState; + bool pressed = false; + + // NOTE: We force bounds.width to be all text + float textWidth = (float)GuiGetTextWidth(text); + if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return pressed; +} + +// Toggle Button control +int GuiToggle(Rectangle bounds, const char *text, bool *active) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (active == NULL) active = &temp; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check toggle button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_NORMAL; + *active = !(*active); + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_NORMAL) + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, ((*active)? BORDER_COLOR_PRESSED : (BORDER + state*3)))), GetColor(GuiGetStyle(TOGGLE, ((*active)? BASE_COLOR_PRESSED : (BASE + state*3))))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, ((*active)? TEXT_COLOR_PRESSED : (TEXT + state*3))))); + } + else + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + state*3)), GetColor(GuiGetStyle(TOGGLE, BASE + state*3))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, TEXT + state*3))); + } + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; +} + +// Toggle Group control +int GuiToggleGroup(Rectangle bounds, const char *text, int *active) +{ + #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) + #define RAYGUI_TOGGLEGROUP_MAX_ITEMS 32 + #endif + + int result = 0; + float initBoundsX = bounds.x; + + int temp = 0; + if (active == NULL) active = &temp; + + bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int rows[RAYGUI_TOGGLEGROUP_MAX_ITEMS] = { 0 }; + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, rows); + + int prevRow = rows[0]; + + for (int i = 0; i < itemCount; i++) + { + if (prevRow != rows[i]) + { + bounds.x = initBoundsX; + bounds.y += (bounds.height + GuiGetStyle(TOGGLE, GROUP_PADDING)); + prevRow = rows[i]; + } + + if (i == (*active)) + { + toggle = true; + GuiToggle(bounds, items[i], &toggle); + } + else + { + toggle = false; + GuiToggle(bounds, items[i], &toggle); + if (toggle) *active = i; + } + + bounds.x += (bounds.width + GuiGetStyle(TOGGLE, GROUP_PADDING)); + } + + return result; +} + +// Toggle Slider control extended +int GuiToggleSlider(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + //bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle slider = { + 0, // Calculated later depending on the active toggle + bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + (bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - (itemCount + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING))/itemCount, + bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + (*active)++; + result = 1; + } + else state = STATE_FOCUSED; + } + + if ((*active) && (state != STATE_FOCUSED)) state = STATE_PRESSED; + } + + if (*active >= itemCount) *active = 0; + slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + (*active + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING) + (*active)*slider.width; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + (state*3))), + GetColor(GuiGetStyle(TOGGLE, BASE_COLOR_NORMAL))); + + // Draw internal slider + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + + // Draw text in slider + if (text != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = slider.x + slider.width/2 - textBounds.width/2; + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(items[*active], textBounds, GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, TEXT + (state*3))), guiAlpha)); + } + //-------------------------------------------------------------------- + + return result; +} + +// Check Box control, returns 1 when state changed +int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (checked == NULL) checked = &temp; + + Rectangle textBounds = { 0 }; + + if (text != NULL) + { + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(CHECKBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + Rectangle totalBounds = { + (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT)? textBounds.x : bounds.x, + bounds.y, + bounds.width + textBounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING), + bounds.height, + }; + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, totalBounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + *checked = !(*checked); + result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(CHECKBOX, BORDER_WIDTH), GetColor(GuiGetStyle(CHECKBOX, BORDER + (state*3))), BLANK); + + if (*checked) + { + Rectangle check = { bounds.x + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.y + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.width - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)), + bounds.height - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)) }; + GuiDrawRectangle(check, 0, BLANK, GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3))); + } + + GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Combo Box control +int GuiComboBox(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + bounds.width -= (GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH) + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING)); + + Rectangle selector = { (float)bounds.x + bounds.width + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING), + (float)bounds.y, (float)GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH), (float)bounds.height }; + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + if (*active < 0) *active = 0; + else if (*active > (itemCount - 1)) *active = itemCount - 1; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds) || + CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + *active += 1; + if (*active >= itemCount) *active = 0; // Cyclic combobox + } + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // Draw combo box main + GuiDrawRectangle(bounds, GuiGetStyle(COMBOBOX, BORDER_WIDTH), GetColor(GuiGetStyle(COMBOBOX, BORDER + (state*3))), GetColor(GuiGetStyle(COMBOBOX, BASE + (state*3)))); + GuiDrawText(items[*active], GetTextBounds(COMBOBOX, bounds), GuiGetStyle(COMBOBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(COMBOBOX, TEXT + (state*3)))); + + // Draw selector using a custom button + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiButton(selector, TextFormat("%i/%i", *active + 1, itemCount)); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + //-------------------------------------------------------------------- + + return result; +} + +// Dropdown Box control +// NOTE: Returns mouse click +int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + int itemSelected = *active; + int itemFocused = -1; + + int direction = 0; // Dropdown box open direction: down (default) + if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle boundsOpen = bounds; + boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); + + Rectangle itemBounds = bounds; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + // Check if mouse has been pressed or released outside limits + if (!CheckCollisionPointRec(mousePoint, boundsOpen)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + + // Check if already selected item has been pressed again + if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + + // Check focused and selected item + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = i; + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + itemSelected = i; + result = 1; // Item selected + } + break; + } + } + + itemBounds = bounds; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + result = 1; + state = STATE_PRESSED; + } + else state = STATE_FOCUSED; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (editMode) GuiPanel(boundsOpen, NULL); + + GuiDrawRectangle(bounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3))); + GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3))); + + if (editMode) + { + // Draw visible items + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (i == itemSelected) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED))); + } + else if (i == itemFocused) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED))); + } + else GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL))); + } + } + + if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) + { + // Draw arrows (using icon if available) +#if defined(RAYGUI_NO_ICONS) + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL +#endif + } + //-------------------------------------------------------------------- + + *active = itemSelected; + + // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item... + return result; // Mouse click: result = 1 +} + +// Text Box control +// NOTE: Returns true on ENTER pressed (useful for data validation) +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) +{ + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 20 // Frames to wait for autocursor movement + #endif + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement + #endif + + int result = 0; + GuiState state = guiState; + + bool multiline = false; // TODO: Consider multiline text input + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); + + Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); + int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GuiGetTextWidth(text) - GuiGetTextWidth(text + thisCursorIndex); + int textIndexOffset = 0; // Text index offset to start drawing in the box + + // Cursor rectangle + // NOTE: Position X value should be updated + Rectangle cursor = { + textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), + textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), + 2, + (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 + }; + + if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); + + // Mouse cursor rectangle + // NOTE: Initialized outside of screen + Rectangle mouseCursor = cursor; + mouseCursor.x = -1; + mouseCursor.width = 1; + + // Blink-cursor frame counter + //if (!autoCursorMode) blinkCursorFrameCounter++; + //else blinkCursorFrameCounter = 0; + + // Update control + //-------------------------------------------------------------------- + // WARNING: Text editing is only supported under certain conditions: + if ((state != STATE_DISABLED) && // Control not disabled + !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode + !guiLocked && // Gui not locked + !guiControlExclusiveMode && // No gui slider on dragging + (wrapMode == TEXT_WRAP_NONE)) // No wrap mode + { + Vector2 mousePosition = GetMousePosition(); + + if (editMode) + { + // GLOBAL: Auto-cursor movement logic + // NOTE: Keystrokes are handled repeatedly when button is held down for some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++; + else autoCursorCounter = 0; + + bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0); + + state = STATE_PRESSED; + + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // If text does not fit in the textbox and current cursor position is out of bounds, + // we add an index offset to text for drawing only what requires depending on cursor + while (textWidth >= textBounds.width) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textIndexOffset, &nextCodepointSize); + + textIndexOffset += nextCodepointSize; + + textWidth = GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex); + } + + int codepoint = GetCharPressed(); // Get Unicode codepoint + if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; + + // Encode codepoint as UTF-8 + int codepointSize = 0; + const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); + + // Handle text paste action + if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + const char *pasteText = GetClipboardText(); + if (pasteText != NULL) + { + int pasteLength = 0; + int pasteCodepoint; + int pasteCodepointSize; + + // Count how many codepoints to copy, stopping at the first unwanted control character + while (true) + { + pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize); + if (textLength + pasteLength + pasteCodepointSize >= textSize) break; + if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; + pasteLength += pasteCodepointSize; + } + + if (pasteLength > 0) + { + // Move forward data from cursor position + for (int i = textLength + pasteLength; i > textBoxCursorIndex; i--) text[i] = text[i - pasteLength]; + + // Paste data in at cursor + for (int i = 0; i < pasteLength; i++) text[textBoxCursorIndex + i] = pasteText[i]; + + textBoxCursorIndex += pasteLength; + textLength += pasteLength; + text[textLength] = '\0'; + } + } + } + else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Adding codepoint to text, at current cursor position + + // Move forward data from cursor position + for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; + + // Add new codepoint in current cursor position + for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; + + textBoxCursorIndex += codepointSize; + textLength += codepointSize; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + + // Move cursor to start + if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; + + // Move cursor to end + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; + + // Delete related codepoints from text, after current cursor position + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) + { + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) + break; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Check whitespace to delete (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Move text after cursor forward (including final null terminator) + for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + } + + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) + { + // Delete single codepoint from text, after current cursor position + + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i]; + + textLength -= nextCodepointSize; + } + + // Delete related codepoints from text, before current cursor position + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int prevCodepointSize = 0; + int prevCodepoint = 0; + + // Check whitespace to delete (ASCII only) + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + textBoxCursorIndex -= accCodepointSize; + } + + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) + { + // Delete single codepoint from text, before current cursor position + + int prevCodepointSize = 0; + + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i]; + + textLength -= prevCodepointSize; + textBoxCursorIndex -= prevCodepointSize; + } + + // Move cursor position with keys + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + //int accCodepointSize = 0; + int prevCodepointSize = 0; + int prevCodepoint = 0; + + // Check whitespace to skip (ASCII only) + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; + + offset -= prevCodepointSize; + //accCodepointSize += prevCodepointSize; + } + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + //accCodepointSize += prevCodepointSize; + } + + textBoxCursorIndex = offset; + } + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger))) + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + textBoxCursorIndex -= prevCodepointSize; + } + else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + //int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) + { + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break; + + offset += nextCodepointSize; + //accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Check whitespace to skip (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + //accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + textBoxCursorIndex = offset; + } + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger))) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + textBoxCursorIndex += nextCodepointSize; + } + + // Move cursor position with mouse + if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text + { + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; + int codepointIndex = 0; + float glyphWidth = 0.0f; + float widthToMouseX = 0; + int mouseCursorIndex = 0; + + for (int i = textIndexOffset; i < textLength; i += codepointSize) + { + codepoint = GetCodepointNext(&text[i], &codepointSize); + codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) + { + mouseCursor.x = textBounds.x + widthToMouseX; + mouseCursorIndex = i; + break; + } + + widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + + // Check if mouse cursor is at the last position + int textEndWidth = GuiGetTextWidth(text + textIndexOffset); + if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) + { + mouseCursor.x = textBounds.x + textEndWidth; + mouseCursorIndex = textLength; + } + + // Place cursor at required index on mouse click + if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + cursor.x = mouseCursor.x; + textBoxCursorIndex = mouseCursorIndex; + } + } + else mouseCursor.x = -1; + + // Recalculate cursor position.y depending on textBoxCursorIndex + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + //if (multiline) cursor.y = GetTextLines() + + // Finish text editing on ENTER or mouse click outside bounds + if ((!multiline && IsKeyPressed(KEY_ENTER)) || + (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes + result = 1; + } + } + else + { + if (CheckCollisionPointRec(mousePosition, bounds)) + { + state = STATE_FOCUSED; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes + result = 1; + } + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_PRESSED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + } + else if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); + } + else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); + + // Draw text considering index offset if required + // NOTE: Text index offset depends on cursor position + GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) + { + //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + + // Draw mouse position cursor (if required) + if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + } + else if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; // Mouse button pressed: result = 1 +} + +/* +// Text Box control with multiple lines and word-wrap +// NOTE: This text-box is readonly, no editing supported by default +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) +{ + bool pressed = false; + + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); + + // TODO: Implement methods to calculate cursor position properly + pressed = GuiTextBox(bounds, text, textSize, editMode); + + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); + + return pressed; +} +*/ + +// Spinner control, returns selected value +int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + int result = 1; + GuiState state = guiState; + + int tempValue = *value; + + Rectangle valueBoxBounds = { + bounds.x + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING), + bounds.y, + bounds.width - 2*(GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.y, + (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check spinner state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(leftButtonBound, "<")) tempValue--; + if (GuiButton(rightButtonBound, ">")) tempValue++; +#else + if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; + if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; +#endif + + if (!editMode) + { + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + result = GuiValueBox(valueBoxBounds, NULL, &tempValue, minValue, maxValue, editMode); + + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(VALUEBOX, BORDER_WIDTH)); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + *value = tempValue; + return result; +} + +// Value Box control, updates input text with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = { 0 }; + snprintf(textValue, RAYGUI_VALUEBOX_MAX_CHARS + 1, "%i", *value); + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0 ; i < keyCount; i++) textValue[i] = textValue[i + 1]; + + keyCount--; + valueHasChanged = true; + } + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (keyCount == 0) + { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + + for (int i = keyCount ; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + + // Add new digit to text value + if ((keyCount >= 0) && (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) && (GuiGetTextWidth(textValue) < bounds.width)) + { + int key = GetCharPressed(); + + // Only allow keys in range [48..57] + if ((key >= 48) && (key <= 57)) + { + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; + } + } + + // Delete text + if ((keyCount > 0) && IsKeyPressed(KEY_BACKSPACE)) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + + if (valueHasChanged) *value = TextToInteger(textValue); + + // NOTE: We are not clamp values until user input finishes + //if (*value > maxValue) *value = maxValue; + //else if (*value < minValue) *value = minValue; + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + result = 1; + } + } + else + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor rectangle + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = { bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + 2, + 2, bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2 - 4 }; + if (cursor.height > bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Floating point Value Box control, updates input val_str with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //snprintf(textValue, sizeof(textValue), "%2.2f", *value); + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0; i < keyCount; i++) textValue[i] = textValue[i + 1]; + + keyCount--; + valueHasChanged = true; + } + else if (keyCount < (RAYGUI_VALUEBOX_MAX_CHARS - 1)) + { + if (keyCount == 0) + { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + + for (int i = keyCount; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GuiGetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) + { + textValue[keyCount] = (char)key; + keyCount++; + + valueHasChanged = true; + } + } + } + + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) + { + if (keyCount > 0) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToFloat(textValue); + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = {bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, + (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, + GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Slider control with pro parameters +// NOTE: Other GuiSlider*() controls use this one +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + float oldValue = *value; + + int sliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + if (!CheckCollisionPointRec(mousePoint, slider)) + { + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; + } + } + else state = STATE_FOCUSED; + } + + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + } + + // Control value change check + if (oldValue == *value) result = 0; + else result = 1; + + // Slider bar limits check + float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + if (sliderWidth > 0) // Slider + { + slider.x += sliderValue; + slider.width = (float)sliderWidth; + if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); + else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + else if (sliderWidth == 0) // SliderBar + { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = sliderValue; + if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + + // Draw slider internal bar (depends on state) + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + else if (state == STATE_DISABLED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_DISABLED))); + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Slider Bar control extended, returns selected value +int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + int preSliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 0); + result = GuiSlider(bounds, textLeft, textRight, value, minValue, maxValue); + GuiSetStyle(SLIDER, SLIDER_WIDTH, preSliderWidth); + + return result; +} + +// Progress Bar control extended, shows current progress value +int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + + // Progress bar + Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), + bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, + bounds.height - GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) -1 }; + + // Update control + //-------------------------------------------------------------------- + if (*value > maxValue) *value = maxValue; + + // WARNING: Working with floats could lead to rounding issues + if ((state != STATE_DISABLED)) progress.width = ((float)*value/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)); + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(PROGRESSBAR, BORDER + (state*3))), BLANK); + } + else + { + if (*value > minValue) + { + // Draw progress bar with colored border, more visual + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + } + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1}, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + else + { + // Draw borders not yet reached by value + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + bounds.height - 1, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + } + + // Draw slider internal progress bar (depends on state) + GuiDrawRectangle(progress, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BASE_COLOR_PRESSED))); + } + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Status Bar control +int GuiStatusBar(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(STATUSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(STATUSBAR, BORDER + (state*3))), GetColor(GuiGetStyle(STATUSBAR, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(STATUSBAR, bounds), GuiGetStyle(STATUSBAR, TEXT_ALIGNMENT), GetColor(GuiGetStyle(STATUSBAR, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Dummy rectangle control, intended for placeholding +int GuiDummyRec(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + GuiDrawText(text, GetTextBounds(DEFAULT, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(BUTTON, (state != STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED))); + //------------------------------------------------------------------ + + return result; +} + +// List View control +int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active) +{ + int result = 0; + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + result = GuiListViewEx(bounds, items, itemCount, scrollIndex, active, NULL); + + return result; +} + +// List View control with extended parameters +int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus) +{ + int result = 0; + GuiState state = guiState; + + int itemFocused = (focus == NULL)? -1 : *focus; + int itemSelected = (active == NULL)? -1 : *active; + + // Check if we need a scroll bar + bool useScrollBar = false; + if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING))*count > bounds.height) useScrollBar = true; + + // Define base item rectangle [0] + Rectangle itemBounds = { 0 }; + itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING); + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.height = (float)GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); + + // Get items on the list + int visibleItems = (int)bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + if (visibleItems > count) visibleItems = count; + + int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; + if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; + int endIndex = startIndex + visibleItems; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check mouse inside list view + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Check focused and selected item + for (int i = 0; i < visibleItems; i++) + { + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = startIndex + i; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (itemSelected == (startIndex + i)) itemSelected = -1; + else itemSelected = startIndex + i; + } + break; + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + int wheelMove = (int)GetMouseWheelMove(); + startIndex -= wheelMove; + + if (startIndex < 0) startIndex = 0; + else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; + + endIndex = startIndex + visibleItems; + if (endIndex > count) endIndex = count; + } + } + else itemFocused = -1; + + // Reset item rectangle y to [0] + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Draw visible items + for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) + { + if (GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_NORMAL)) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + + if (state == STATE_DISABLED) + { + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); + } + else + { + if (((startIndex + i) == itemSelected) && (active != NULL)) + { + // Draw item selected + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); + } + else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! + { + // Draw item focused + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); + } + else + { + // Draw item normal (no rectangle) + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); + } + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + Rectangle scrollBarBounds = { + bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Calculate percentage of visible items and apply same percentage to scrollbar + float percentVisible = (float)(endIndex - startIndex)/count; + float sliderSize = bounds.height*percentVisible; + + int prevSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); // Save default slider size + int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)sliderSize); // Change slider size + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed + + startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); + + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, prevSliderSize); // Reset slider size to default + } + //-------------------------------------------------------------------- + + if (active != NULL) *active = itemSelected; + if (focus != NULL) *focus = itemFocused; + if (scrollIndex != NULL) *scrollIndex = startIndex; + + return result; +} + +// Color Panel control - Color (RGBA) variant +int GuiColorPanel(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; + Vector3 hsv = ConvertRGBtoHSV(vcolor); + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv + + GuiColorPanelHSV(bounds, text, &hsv); + + // Check if the hsv was changed, only then change the color + // This is required, because the Color->HSV->Color conversion has precision errors + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value + // Otherwise GuiColorPanel would often modify it's color without user input + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check + if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) + { + Vector3 rgb = ConvertHSVtoRGB(hsv); + + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + color->a }; + } + return result; +} + +// Color Bar Alpha control +// NOTE: Returns alpha value normalized [0..1] +int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +{ + #if !defined(RAYGUI_COLORBARALPHA_CHECKED_SIZE) + #define RAYGUI_COLORBARALPHA_CHECKED_SIZE 10 + #endif + + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, + (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), + (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), + (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + //selector.x = bounds.x + (int)(((alpha - 0)/(100 - 0))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))) - selector.width/2; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // Draw alpha bar: checked background + if (state != STATE_DISABLED) + { + int checksX = (int)bounds.width/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + int checksY = (int)bounds.height/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + + for (int x = 0; x < checksX; x++) + { + for (int y = 0; y < checksY; y++) + { + Rectangle check = { bounds.x + x*RAYGUI_COLORBARALPHA_CHECKED_SIZE, bounds.y + y*RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE }; + GuiDrawRectangle(check, 0, BLANK, ((x + y)%2)? Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.4f) : Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.4f)); + } + } + + DrawRectangleGradientEx(bounds, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientEx(bounds, Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw alpha bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Bar Hue control +// Returns hue value normalized [0..1] +// NOTE: Other similar bars (for reference): +// Color GuiColorBarSat() [WHITE->color] +// Color GuiColorBarValue() [BLACK->color], HSV/HSL +// float GuiColorBarLuminance() [BLACK->WHITE] +int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) +{ + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + (*hue)/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + + } + else state = STATE_FOCUSED; + + /*if (IsKeyDown(KEY_UP)) + { + hue -= 2.0f; + if (hue <= 0.0f) hue = 0.0f; + } + else if (IsKeyDown(KEY_DOWN)) + { + hue += 2.0f; + if (hue >= 360.0f) hue = 360.0f; + }*/ + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + // Draw hue bar:color bars + // TODO: Use directly DrawRectangleGradientEx(bounds, color1, color2, color2, color1); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + bounds.height/6), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 2*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 3*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 4*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 5*(bounds.height/6)), (int)bounds.width, (int)(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientV((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw hue bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Picker control +// NOTE: It's divided in multiple controls: +// Color GuiColorPanel(Rectangle bounds, Color color) +// float GuiColorBarAlpha(Rectangle bounds, float alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanel() size +// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead +int GuiColorPicker(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Color temp = { 200, 0, 0, 255 }; + if (color == NULL) color = &temp; + + GuiColorPanel(bounds, NULL, color); + + Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used + Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); + + GuiColorBarHue(boundsHue, NULL, &hsv.x); + + //color.a = (unsigned char)(GuiColorBarAlpha(boundsAlpha, (float)color.a/255.0f)*255.0f); + Vector3 rgb = ConvertHSVtoRGB(hsv); + + *color = RAYGUI_CLITERAL(Color){ (unsigned char)roundf(rgb.x*255.0f), (unsigned char)roundf(rgb.y*255.0f), (unsigned char)roundf(rgb.z*255.0f), (*color).a }; + + return result; +} + +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB +// NOTE: It's divided in multiple controls: +// int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +// int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanelHSV() size +int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + + Vector3 tempHsv = { 0 }; + + if (colorHsv == NULL) + { + const Vector3 tempColor = { 200.0f/255.0f, 0.0f, 0.0f }; + tempHsv = ConvertRGBtoHSV(tempColor); + colorHsv = &tempHsv; + } + + GuiColorPanelHSV(bounds, NULL, colorHsv); + + const Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + + GuiColorBarHue(boundsHue, NULL, &colorHsv->x); + + return result; +} + +// Color Panel control - HSV variant +int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + GuiState state = guiState; + Vector2 pickerSelector = { 0 }; + + const Color colWhite = { 255, 255, 255, 255 }; + const Color colBlack = { 0, 0, 0, 255 }; + + pickerSelector.x = bounds.x + (float)colorHsv->y*bounds.width; // HSV: Saturation + pickerSelector.y = bounds.y + (1.0f - (float)colorHsv->z)*bounds.height; // HSV: Value + + Vector3 maxHue = { colorHsv->x, 1.0f, 1.0f }; + Vector3 rgbHue = ConvertHSVtoRGB(maxHue); + Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), + (unsigned char)(255.0f*rgbHue.y), + (unsigned char)(255.0f*rgbHue.z), 255 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + pickerSelector = mousePoint; + + if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; + if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; + if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; + if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; + pickerSelector = mousePoint; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); + DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); + + // Draw color picker: selector + Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; + GuiDrawRectangle(selector, 0, BLANK, colWhite); + } + else + { + DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); + } + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + //-------------------------------------------------------------------- + + return result; +} + +// Message Box control +int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons) +{ + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_HEIGHT) + #define RAYGUI_MESSAGEBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_PADDING) + #define RAYGUI_MESSAGEBOX_BUTTON_PADDING 12 + #endif + + int result = -1; // Returns clicked button from buttons list, 0 refers to closed window button + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT - RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + //int textWidth = GuiGetTextWidth(message) + 2; + + Rectangle textBounds = { 0 }; + textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; + textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + + prevTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevTextAlignment); + //-------------------------------------------------------------------- + + return result; +} + +// Text Input Box control, ask for text +int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive) +{ + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_PADDING) + #define RAYGUI_TEXTINPUTBOX_BUTTON_PADDING 12 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_HEIGHT 26 + #endif + + // Used to enable text edit mode + // WARNING: No more than one GuiTextInputBox() should be open at the same time + static bool textEditMode = false; + + int result = -1; + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT; + + int messageInputHeight = (int)bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - 2*RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + + Rectangle textBounds = { 0 }; + if (message != NULL) + { + int textSize = GuiGetTextWidth(message) + 2; + + textBounds.x = bounds.x + bounds.width/2 - textSize/2; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + textBounds.width = (float)textSize; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + } + + Rectangle textBoxBounds = { 0 }; + textBoxBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + textBoxBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - RAYGUI_TEXTINPUTBOX_HEIGHT/2; + if (message == NULL) textBoxBounds.y = bounds.y + 24 + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + else textBoxBounds.y += (messageInputHeight/2 + messageInputHeight/4); + textBoxBounds.width = bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*2; + textBoxBounds.height = RAYGUI_TEXTINPUTBOX_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + // Draw message if available + if (message != NULL) + { + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + } + + if (secretViewActive != NULL) + { + static char stars[] = "****************"; + if (GuiTextBox(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x, textBoxBounds.y, textBoxBounds.width - 4 - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.height }, + ((*secretViewActive == 1) || textEditMode)? text : stars, textMaxSize, textEditMode)) textEditMode = !textEditMode; + + GuiToggle(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x + textBoxBounds.width - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.y, RAYGUI_TEXTINPUTBOX_HEIGHT, RAYGUI_TEXTINPUTBOX_HEIGHT }, (*secretViewActive == 1)? "#44#" : "#45#", secretViewActive); + } + else + { + if (GuiTextBox(textBoxBounds, text, textMaxSize, textEditMode)) textEditMode = !textEditMode; + } + + int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + if (result >= 0) textEditMode = false; + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment); + //-------------------------------------------------------------------- + + return result; // Result is the pressed button index +} + +// Grid control +// NOTE: Returns grid mouse-hover selected cell +// About drawing lines at subpixel spacing, simple put, not easy solution: +// Ref: https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster +int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell) +{ + // Grid lines alpha amount + #if !defined(RAYGUI_GRID_ALPHA) + #define RAYGUI_GRID_ALPHA 0.15f + #endif + + int result = 0; + GuiState state = guiState; + + Vector2 mousePoint = GetMousePosition(); + Vector2 currentMouseCell = { -1, -1 }; + + float spaceWidth = spacing/(float)subdivs; + int linesV = (int)(bounds.width/spaceWidth) + 1; + int linesH = (int)(bounds.height/spaceWidth) + 1; + + int color = GuiGetStyle(DEFAULT, LINE_COLOR); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + // NOTE: Cell values must be the upper left of the cell the mouse is in + currentMouseCell.x = floorf((mousePoint.x - bounds.x)/spacing); + currentMouseCell.y = floorf((mousePoint.y - bounds.y)/spacing); + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) color = GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED); + + if (subdivs > 0) + { + // Draw vertical grid lines + for (int i = 0; i < linesV; i++) + { + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; + GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + + // Draw horizontal grid lines + for (int i = 0; i < linesH; i++) + { + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; + GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + } + + if (mouseCell != NULL) *mouseCell = currentMouseCell; + return result; +} + +//---------------------------------------------------------------------------------- +// Tooltip management functions +// NOTE: Tooltips requires some global variables: tooltipPtr +//---------------------------------------------------------------------------------- +// Enable gui tooltips (global state) +void GuiEnableTooltip(void) { guiTooltip = true; } + +// Disable gui tooltips (global state) +void GuiDisableTooltip(void) { guiTooltip = false; } + +// Set tooltip string +void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } + +//---------------------------------------------------------------------------------- +// Styles loading functions +//---------------------------------------------------------------------------------- + +// Load raygui style file (.rgs) +// NOTE: By default a binary file is expected, that file could contain a custom font, +// in that case, custom font image atlas is GRAY+ALPHA and pixel data can be compressed (DEFLATE) +void GuiLoadStyle(const char *fileName) +{ + #define MAX_LINE_BUFFER_SIZE 256 + + bool tryBinary = false; + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + // Try reading the files as text file first + FILE *rgsFile = fopen(fileName, "rt"); + + if (rgsFile != NULL) + { + char buffer[MAX_LINE_BUFFER_SIZE] = { 0 }; + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + + if (buffer[0] == '#') + { + int controlId = 0; + int propertyId = 0; + unsigned int propertyValue = 0; + + while (!feof(rgsFile)) + { + switch (buffer[0]) + { + case 'p': + { + // Style property: p + + sscanf(buffer, "p %d %d 0x%x", &controlId, &propertyId, &propertyValue); + GuiSetStyle(controlId, propertyId, (int)propertyValue); + + } break; + case 'f': + { + // Style font: f + + int fontSize = 0; + char charmapFileName[256] = { 0 }; + char fontFileName[256] = { 0 }; + sscanf(buffer, "f %d %s %[^\r\n]s", &fontSize, charmapFileName, fontFileName); + + Font font = { 0 }; + int *codepoints = NULL; + int codepointCount = 0; + + if (charmapFileName[0] != '0') + { + // Load text data from file + // NOTE: Expected an UTF-8 array of codepoints, no separation + char *textData = LoadFileText(TextFormat("%s/%s", GetDirectoryPath(fileName), charmapFileName)); + codepoints = LoadCodepoints(textData, &codepointCount); + UnloadFileText(textData); + } + + if (fontFileName[0] != '\0') + { + // In case a font is already loaded and it is not default internal font, unload it + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + + if (codepointCount > 0) font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, codepoints, codepointCount); + else font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, NULL, 0); // Default to 95 standard codepoints + } + + // If font texture not properly loaded, revert to default font and size/spacing + if (font.texture.id == 0) + { + font = GetFontDefault(); + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); + } + + UnloadCodepoints(codepoints); + + if ((font.texture.id > 0) && (font.glyphCount > 0)) GuiSetFont(font); + + } break; + default: break; + } + + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + } + } + else tryBinary = true; + + fclose(rgsFile); + } + + if (tryBinary) + { + rgsFile = fopen(fileName, "rb"); + + if (rgsFile != NULL) + { + fseek(rgsFile, 0, SEEK_END); + int fileDataSize = ftell(rgsFile); + fseek(rgsFile, 0, SEEK_SET); + + if (fileDataSize > 0) + { + unsigned char *fileData = (unsigned char *)RAYGUI_CALLOC(fileDataSize, sizeof(unsigned char)); + fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); + + GuiLoadStyleFromMemory(fileData, fileDataSize); + + RAYGUI_FREE(fileData); + } + + fclose(rgsFile); + } + } +} + +// Load style default over global style +void GuiLoadStyleDefault(void) +{ + // We set this variable first to avoid cyclic function calls + // when calling GuiSetStyle() and GuiGetStyle() + guiStyleLoaded = true; + + // Initialize default LIGHT style property values + // WARNING: Default value are applied to all controls on set but + // they can be overwritten later on for every custom control + GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, 0x838383ff); + GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, 0xc9c9c9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, 0x686868ff); + GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, 0x5bb2d9ff); + GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, 0xc9effeff); + GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, 0x6c9bbcff); + GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, 0x0492c7ff); + GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, 0x97e8ffff); + GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, 0x368bafff); + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, 0xb5c1c2ff); + GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, 0xe6e9e9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, 0xaeb7b8ff); + GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); + GuiSetStyle(DEFAULT, TEXT_PADDING, 0); + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + // Initialize default extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, LINE_COLOR, 0x90abb5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0xf5f5f5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, 15); // DEFAULT, 15 pixels between lines + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); // DEFAULT, text aligned vertically to middle of text-bounds + + // Initialize control-specific property values + // NOTE: Those properties are in default list but require specific values by control type + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 2); + GuiSetStyle(SLIDER, TEXT_PADDING, 4); + GuiSetStyle(PROGRESSBAR, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_RIGHT); + GuiSetStyle(DROPDOWNBOX, TEXT_PADDING, 0); + GuiSetStyle(DROPDOWNBOX, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiSetStyle(TEXTBOX, TEXT_PADDING, 4); + GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); + GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); + GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + + // Initialize extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(TOGGLE, GROUP_PADDING, 2); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 16); + GuiSetStyle(SLIDER, SLIDER_PADDING, 1); + GuiSetStyle(PROGRESSBAR, PROGRESS_PADDING, 1); + GuiSetStyle(CHECKBOX, CHECK_PADDING, 1); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_WIDTH, 32); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); + GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); + GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH, 24); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_SPACING, 2); + GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); + GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); + GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 16); + GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); + GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH, 1); + GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); + GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); + GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); + GuiSetStyle(COLORPICKER, HUEBAR_WIDTH, 16); + GuiSetStyle(COLORPICKER, HUEBAR_PADDING, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW, 2); + + if (guiFont.texture.id != GetFontDefault().texture.id) + { + // Unload previous font texture + UnloadTexture(guiFont.texture); + RAYGUI_FREE(guiFont.recs); + RAYGUI_FREE(guiFont.glyphs); + guiFont.recs = NULL; + guiFont.glyphs = NULL; + + // Setup default raylib font + guiFont = GetFontDefault(); + + // NOTE: Default raylib font character 95 is a white square + Rectangle whiteChar = guiFont.recs[95]; + + // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(guiFont.texture, RAYGUI_CLITERAL(Rectangle){ whiteChar.x + 1, whiteChar.y + 1, whiteChar.width - 2, whiteChar.height - 2 }); + } +} + +// Get text with icon id prepended +// NOTE: Useful to add icons by name id (enum) instead of +// a number that can change between ricon versions +const char *GuiIconText(int iconId, const char *text) +{ +#if defined(RAYGUI_NO_ICONS) + return NULL; +#else + static char buffer[1024] = { 0 }; + static char iconBuffer[16] = { 0 }; + + if (text != NULL) + { + memset(buffer, 0, 1024); + snprintf(buffer, 1024, "#%03i#", iconId); + + for (int i = 5; i < 1024; i++) + { + buffer[i] = text[i - 5]; + if (text[i - 5] == '\0') break; + } + + return buffer; + } + else + { + snprintf(iconBuffer, 16, "#%03i#", iconId); + + return iconBuffer; + } +#endif +} + +#if !defined(RAYGUI_NO_ICONS) +// Get full icons data pointer +unsigned int *GuiGetIcons(void) { return guiIconsPtr; } + +// Load raygui icons file (.rgi) +// NOTE: In case nameIds are required, they can be requested with loadIconsName, +// they are returned as a guiIconsName[iconCount][RAYGUI_ICON_MAX_NAME_LENGTH], +// WARNING: guiIconsName[]][] memory should be manually freed! +char **GuiLoadIcons(const char *fileName, bool loadIconsName) +{ + // Style File Structure (.rgi) + // ------------------------------------------------------ + // Offset | Size | Type | Description + // ------------------------------------------------------ + // 0 | 4 | char | Signature: "rGI " + // 4 | 2 | short | Version: 100 + // 6 | 2 | short | reserved + + // 8 | 2 | short | Num icons (N) + // 10 | 2 | short | Icons size (Options: 16, 32, 64) (S) + + // Icons name id (32 bytes per name id) + // foreach (icon) + // { + // 12+32*i | 32 | char | Icon NameId + // } + + // Icons data: One bit per pixel, stored as unsigned int array (depends on icon size) + // S*S pixels/32bit per unsigned int = K unsigned int per icon + // foreach (icon) + // { + // ... | K | unsigned int | Icon Data + // } + + FILE *rgiFile = fopen(fileName, "rb"); + + char **guiIconsName = NULL; + + if (rgiFile != NULL) + { + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + fread(signature, 1, 4, rgiFile); + fread(&version, sizeof(short), 1, rgiFile); + fread(&reserved, sizeof(short), 1, rgiFile); + fread(&iconCount, sizeof(short), 1, rgiFile); + fread(&iconSize, sizeof(short), 1, rgiFile); + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_CALLOC(iconCount, sizeof(char *)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_CALLOC(RAYGUI_ICON_MAX_NAME_LENGTH, sizeof(char)); + fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); + } + } + else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); + + // Read icons data directly over internal icons array + fread(guiIconsPtr, sizeof(unsigned int), (int)iconCount*((int)iconSize*(int)iconSize/32), rgiFile); + } + + fclose(rgiFile); + } + + return guiIconsName; +} + +// Load icons from memory +// WARNING: Binary files only +char **GuiLoadIconsFromMemory(const unsigned char *fileData, int dataSize, bool loadIconsName) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + char **guiIconsName = NULL; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&iconCount, fileDataPtr + 4 + 2 + 2, sizeof(short)); + memcpy(&iconSize, fileDataPtr + 4 + 2 + 2 + 2, sizeof(short)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_CALLOC(iconCount, sizeof(char *)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_CALLOC(RAYGUI_ICON_MAX_NAME_LENGTH, sizeof(char)); + memcpy(guiIconsName[i], fileDataPtr, RAYGUI_ICON_MAX_NAME_LENGTH); + fileDataPtr += RAYGUI_ICON_MAX_NAME_LENGTH; + } + } + else + { + // Skip icon name data if not required + fileDataPtr += iconCount*RAYGUI_ICON_MAX_NAME_LENGTH; + } + + int iconDataSize = iconCount*((int)iconSize*(int)iconSize/32)*(int)sizeof(unsigned int); + guiIconsPtr = (unsigned int *)RAYGUI_CALLOC(iconDataSize, 1); + + memcpy(guiIconsPtr, fileDataPtr, iconDataSize); + } + + return guiIconsName; +} + +// Draw selected icon using rectangles pixel-by-pixel +void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) +{ + #define BIT_CHECK(a,b) ((a) & (1u<<(b))) + + for (int i = 0, y = 0; i < RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32; i++) + { + for (int k = 0; k < 32; k++) + { + if (BIT_CHECK(guiIconsPtr[iconId*RAYGUI_ICON_DATA_ELEMENTS + i], k)) + { + #if !defined(RAYGUI_STANDALONE) + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ (float)posX + (k%RAYGUI_ICON_SIZE)*pixelSize, (float)posY + y*pixelSize, (float)pixelSize, (float)pixelSize }, 0, BLANK, color); + #endif + } + + if ((k == 15) || (k == 31)) y++; + } + } +} + +// Set icon drawing size +void GuiSetIconScale(int scale) +{ + if (scale >= 1) guiIconScale = scale; +} + +// Get text width considering gui style and icon size (if required) +int GuiGetTextWidth(const char *text) +{ + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + Vector2 textSize = { 0 }; + int textIconOffset = 0; + + if ((text != NULL) && (text[0] != '\0')) + { + if (text[0] == '#') + { + for (int i = 1; (i < 5) && (text[i] != '\0'); i++) + { + if (text[i] == '#') + { + textIconOffset = i; + break; + } + } + } + + text += textIconOffset; + + // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly + float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + + // Custom MeasureText() implementation + if ((guiFont.texture.id > 0) && (text != NULL)) + { + // Get size in bytes of text, considering end of line and line break + int size = 0; + for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) + { + if ((text[i] != '\0') && (text[i] != '\n')) size++; + else break; + } + + float scaleFactor = fontSize/(float)guiFont.baseSize; + textSize.y = (float)guiFont.baseSize*scaleFactor; + float glyphWidth = 0.0f; + + for (int i = 0, codepointSize = 0; i < size; i += codepointSize) + { + int codepoint = GetCodepointNext(&text[i], &codepointSize); + int codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); + } + + return (int)textSize.x; +} + +#endif // !RAYGUI_NO_ICONS + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Load style from memory +// WARNING: Binary files only +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + int propertyCount = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&propertyCount, fileDataPtr + 4 + 2 + 2, sizeof(int)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'S') && + (signature[3] == ' ')) + { + short controlId = 0; + short propertyId = 0; + unsigned int propertyValue = 0; + + for (int i = 0; i < propertyCount; i++) + { + memcpy(&controlId, fileDataPtr, sizeof(short)); + memcpy(&propertyId, fileDataPtr + 2, sizeof(short)); + memcpy(&propertyValue, fileDataPtr + 2 + 2, sizeof(unsigned int)); + fileDataPtr += 8; + + if (controlId == 0) // DEFAULT control + { + // If a DEFAULT property is loaded, it is propagated to all controls + // NOTE: All DEFAULT properties should be defined first in the file + GuiSetStyle(0, (int)propertyId, propertyValue); + + if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int j = 1; j < RAYGUI_MAX_CONTROLS; j++) GuiSetStyle(j, (int)propertyId, propertyValue); + } + else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); + } + + // Font loading is highly dependant on raylib API to load font data and image + +#if !defined(RAYGUI_STANDALONE) + // Load custom font if available + int fontDataSize = 0; + memcpy(&fontDataSize, fileDataPtr, sizeof(int)); + fileDataPtr += 4; + + if (fontDataSize > 0) + { + Font font = { 0 }; + int fontType = 0; // 0-Normal, 1-SDF + + memcpy(&font.baseSize, fileDataPtr, sizeof(int)); + memcpy(&font.glyphCount, fileDataPtr + 4, sizeof(int)); + memcpy(&fontType, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + // Load font white rectangle + Rectangle fontWhiteRec = { 0 }; + memcpy(&fontWhiteRec, fileDataPtr, sizeof(Rectangle)); + fileDataPtr += 16; + + // Load font image parameters + int fontImageUncompSize = 0; + int fontImageCompSize = 0; + memcpy(&fontImageUncompSize, fileDataPtr, sizeof(int)); + memcpy(&fontImageCompSize, fileDataPtr + 4, sizeof(int)); + fileDataPtr += 8; + + Image imFont = { 0 }; + imFont.mipmaps = 1; + memcpy(&imFont.width, fileDataPtr, sizeof(int)); + memcpy(&imFont.height, fileDataPtr + 4, sizeof(int)); + memcpy(&imFont.format, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + if ((fontImageCompSize > 0) && (fontImageCompSize != fontImageUncompSize)) + { + // Compressed font atlas image data (DEFLATE), it requires DecompressData() + int dataUncompSize = 0; + unsigned char *compData = (unsigned char *)RAYGUI_CALLOC(fontImageCompSize, sizeof(unsigned char)); + memcpy(compData, fileDataPtr, fontImageCompSize); + fileDataPtr += fontImageCompSize; + + imFont.data = DecompressData(compData, fontImageCompSize, &dataUncompSize); + + // Security check, dataUncompSize must match the provided fontImageUncompSize + if (dataUncompSize != fontImageUncompSize) RAYGUI_LOG("WARNING: Uncompressed font atlas image data could be corrupted"); + + RAYGUI_FREE(compData); + } + else + { + // Font atlas image data is not compressed + imFont.data = (unsigned char *)RAYGUI_CALLOC(fontImageUncompSize, sizeof(unsigned char)); + memcpy(imFont.data, fileDataPtr, fontImageUncompSize); + fileDataPtr += fontImageUncompSize; + } + + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + font.texture = LoadTextureFromImage(imFont); + + RAYGUI_FREE(imFont.data); + + // Validate font atlas texture was loaded correctly + if (font.texture.id != 0) + { + // Load font recs data + int recsDataSize = font.glyphCount*sizeof(Rectangle); + int recsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed recs data + memcpy(&recsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) + { + // Recs data is compressed, uncompress it + unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_CALLOC(recsDataCompressedSize, sizeof(unsigned char)); + + memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); + fileDataPtr += recsDataCompressedSize; + + int recsDataUncompSize = 0; + font.recs = (Rectangle *)DecompressData(recsDataCompressed, recsDataCompressedSize, &recsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (recsDataUncompSize != recsDataSize) RAYGUI_LOG("WARNING: Uncompressed font recs data could be corrupted"); + + RAYGUI_FREE(recsDataCompressed); + } + else + { + // Recs data is uncompressed + font.recs = (Rectangle *)RAYGUI_CALLOC(font.glyphCount, sizeof(Rectangle)); + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.recs[i], fileDataPtr, sizeof(Rectangle)); + fileDataPtr += sizeof(Rectangle); + } + } + + // Load font glyphs info data + int glyphsDataSize = font.glyphCount*16; // 16 bytes data per glyph + int glyphsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed glyphs data + memcpy(&glyphsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + // Allocate required glyphs space to fill with data + font.glyphs = (GlyphInfo *)RAYGUI_CALLOC(font.glyphCount, sizeof(GlyphInfo)); + + if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) + { + // Glyphs data is compressed, uncompress it + unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_CALLOC(glyphsDataCompressedSize, sizeof(unsigned char)); + + memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); + fileDataPtr += glyphsDataCompressedSize; + + int glyphsDataUncompSize = 0; + unsigned char *glyphsDataUncomp = DecompressData(glypsDataCompressed, glyphsDataCompressedSize, &glyphsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (glyphsDataUncompSize != glyphsDataSize) RAYGUI_LOG("WARNING: Uncompressed font glyphs data could be corrupted"); + + unsigned char *glyphsDataUncompPtr = glyphsDataUncomp; + + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, glyphsDataUncompPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, glyphsDataUncompPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, glyphsDataUncompPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, glyphsDataUncompPtr + 12, sizeof(int)); + glyphsDataUncompPtr += 16; + } + + RAYGUI_FREE(glypsDataCompressed); + RAYGUI_FREE(glyphsDataUncomp); + } + else + { + // Glyphs data is uncompressed + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, fileDataPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, fileDataPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, fileDataPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, fileDataPtr + 12, sizeof(int)); + fileDataPtr += 16; + } + } + } + else font = GetFontDefault(); // Fallback in case of errors loading font atlas texture + + GuiSetFont(font); + + // Set font texture source rectangle to be used as white texture to draw shapes + // NOTE: It makes possible to draw shapes and text (full UI) in a single draw call + if ((fontWhiteRec.x > 0) && + (fontWhiteRec.y > 0) && + (fontWhiteRec.width > 0) && + (fontWhiteRec.height > 0)) SetShapesTexture(font.texture, fontWhiteRec); + } +#endif + } +} + +// Get text bounds considering control bounds +static Rectangle GetTextBounds(int control, Rectangle bounds) +{ + Rectangle textBounds = bounds; + + textBounds.x = bounds.x + GuiGetStyle(control, BORDER_WIDTH); + textBounds.y = bounds.y + GuiGetStyle(control, BORDER_WIDTH) + GuiGetStyle(control, TEXT_PADDING); + textBounds.width = bounds.width - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); + textBounds.height = bounds.height - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); // NOTE: Text is processed line per line! + + // Depending on control, TEXT_PADDING and TEXT_ALIGNMENT properties could affect the text-bounds + switch (control) + { + case COMBOBOX: + case DROPDOWNBOX: + case LISTVIEW: + // TODO: Special cases (no label): COMBOBOX, DROPDOWNBOX, LISTVIEW + case SLIDER: + case CHECKBOX: + case VALUEBOX: + case CONTROL11: + // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER + default: + { + // TODO: WARNING: TEXT_ALIGNMENT is already considered in GuiDrawText() + if (GuiGetStyle(control, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT) textBounds.x -= GuiGetStyle(control, TEXT_PADDING); + else textBounds.x += GuiGetStyle(control, TEXT_PADDING); + } + break; + } + + return textBounds; +} + +// Get text icon if provided and move text cursor +// NOTE: We support up to 999 values for iconId +static const char *GetTextIcon(const char *text, int *iconId) +{ +#if !defined(RAYGUI_NO_ICONS) + *iconId = -1; + if (text[0] == '#') // Maybe we have an icon! + { + char iconValue[4] = { 0 }; // Maximum length for icon value: 3 digits + '\0' + + int pos = 1; + while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) + { + iconValue[pos - 1] = text[pos]; + pos++; + } + + if (text[pos] == '#') + { + *iconId = TextToInteger(iconValue); + + // Move text pointer after icon + // WARNING: If only icon provided, it could point to EOL character: '\0' + if (*iconId >= 0) text += (pos + 1); + } + } +#endif + + return text; +} + +// Get text divided into lines (by line-breaks '\n') +// WARNING: It returns pointers to new lines but it does not add NULL ('\0') terminator! +static const char **GetTextLines(const char *text, int *count) +{ + #define RAYGUI_MAX_TEXT_LINES 128 + + static const char *lines[RAYGUI_MAX_TEXT_LINES] = { 0 }; + for (int i = 0; i < RAYGUI_MAX_TEXT_LINES; i++) lines[i] = NULL; // Init NULL pointers to substrings + + int textSize = (int)strlen(text); + + lines[0] = text; + *count = 1; + + for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) + { + if (text[i] == '\n') + { + k++; + lines[k] = &text[i + 1]; // WARNING: next value is valid? + *count += 1; + } + } + + return lines; +} + +// Get text width to next space for provided string +static float GetNextSpaceWidth(const char *text, int *nextSpaceIndex) +{ + float width = 0; + int codepointByteCount = 0; + int codepoint = 0; + int index = 0; + float glyphWidth = 0; + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + for (int i = 0; text[i] != '\0'; i++) + { + if (text[i] != ' ') + { + codepoint = GetCodepoint(&text[i], &codepointByteCount); + index = GetGlyphIndex(guiFont, codepoint); + glyphWidth = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; + width += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + else + { + *nextSpaceIndex = i; + break; + } + } + + return width; +} + +// Gui draw text using default font +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint) +{ + #define TEXT_VALIGN_PIXEL_OFFSET(h) ((int)h%2) // Vertical alignment for pixel perfect + + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + if ((text == NULL) || (text[0] == '\0')) return; // Security check + + // PROCEDURE: + // - Text is processed line per line + // - For every line, horizontal alignment is defined + // - For all text, vertical alignment is defined (multiline text only) + // - For every line, wordwrap mode is checked (useful for GuitextBox(), read-only) + + // Get text lines (using '\n' as delimiter) to be processed individually + // WARNING: We can't use GuiTextSplit() function because it can be already used + // before the GuiDrawText() call and its buffer is static, it would be overriden :( + int lineCount = 0; + const char **lines = GetTextLines(text, &lineCount); + + // Text style variables + //int alignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT); + int alignmentVertical = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); // Wrap-mode only available in read-only mode, no for text editing + + // TODO: WARNING: This totalHeight is not valid for vertical alignment in case of word-wrap + float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); + float posOffsetY = 0.0f; + + for (int i = 0; i < lineCount; i++) + { + int iconId = 0; + lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor + + // Get text position depending on alignment and iconId + //--------------------------------------------------------------------------------- + Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; + float textBoundsWidthOffset = 0.0f; + + // NOTE: We get text size after icon has been processed + // WARNING: GuiGetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GuiGetTextWidth(lines[i]); + + // If text requires an icon, add size to measure + if (iconId >= 0) + { + textSizeX += RAYGUI_ICON_SIZE*guiIconScale; + + // WARNING: If only icon provided, text could be pointing to EOF character: '\0' +#if !defined(RAYGUI_NO_ICONS) + if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; +#endif + } + + // Check guiTextAlign global variables + switch (alignment) + { + case TEXT_ALIGN_LEFT: textBoundsPosition.x = textBounds.x; break; + case TEXT_ALIGN_CENTER: textBoundsPosition.x = textBounds.x + textBounds.width/2 - textSizeX/2; break; + case TEXT_ALIGN_RIGHT: textBoundsPosition.x = textBounds.x + textBounds.width - textSizeX; break; + default: break; + } + + if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; + + switch (alignmentVertical) + { + // Only valid in case of wordWrap = 0; + case TEXT_ALIGN_TOP: textBoundsPosition.y = textBounds.y + posOffsetY; break; + case TEXT_ALIGN_MIDDLE: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + case TEXT_ALIGN_BOTTOM: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + default: break; + } + + // NOTE: Make sure we get pixel-perfect coordinates, + // In case of decimals we got weird text positioning + textBoundsPosition.x = (float)((int)textBoundsPosition.x); + textBoundsPosition.y = (float)((int)textBoundsPosition.y); + //--------------------------------------------------------------------------------- + + // Draw text (with icon if available) + //--------------------------------------------------------------------------------- +#if !defined(RAYGUI_NO_ICONS) + if (iconId >= 0) + { + // NOTE: We consider icon height, probably different than text size + GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); + textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + } +#endif + // Get size in bytes of text, + // considering end of line and line break + int lineSize = 0; + for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + int lastSpaceIndex = 0; + bool tempWrapCharMode = false; + + int textOffsetY = 0; + float textOffsetX = 0.0f; + float glyphWidth = 0; + + int ellipsisWidth = GuiGetTextWidth("..."); + bool textOverflow = false; + for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) + { + int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); + int index = GetGlyphIndex(guiFont, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + + // Get glyph width to check if it goes out of bounds + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; + + // Wrap mode text measuring, to validate if + // it can be drawn or a new line is required + if (wrapMode == TEXT_WRAP_CHAR) + { + // Jump to next line if current character reach end of the box limits + if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + + if (tempWrapCharMode) // Wrap at char level when too long words + { + wrapMode = TEXT_WRAP_WORD; + tempWrapCharMode = false; + } + } + } + else if (wrapMode == TEXT_WRAP_WORD) + { + if (codepoint == 32) lastSpaceIndex = c; + + // Get width to next space in line + int nextSpaceIndex = 0; + float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); + + int nextSpaceIndex2 = 0; + float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); + + if (nextWordSize > textBounds.width - textBoundsWidthOffset) + { + // Considering the case the next word is longer than bounds + tempWrapCharMode = true; + wrapMode = TEXT_WRAP_CHAR; + } + else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + } + } + + if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint + else + { + // TODO: There are multiple types of spaces in Unicode, + // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html + if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph + { + if (wrapMode == TEXT_WRAP_NONE) + { + // Draw only required text glyphs fitting the textBounds.width + if (textSizeX > textBounds.width) + { + if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + else if (!textOverflow) + { + textOverflow = true; + + for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) + { + DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + else + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) + { + // Draw only glyphs inside the bounds + if ((textBoundsPosition.y + textOffsetY) <= (textBounds.y + textBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + + if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (wrapMode == TEXT_WRAP_NONE) posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) posOffsetY += (textOffsetY + (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING)); + //--------------------------------------------------------------------------------- + } + +#if defined(RAYGUI_DEBUG_TEXT_BOUNDS) + GuiDrawRectangle(textBounds, 0, WHITE, Fade(BLUE, 0.4f)); +#endif +} + +// Gui draw rectangle using default raygui plain style with borders +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color) +{ + if (color.a > 0) + { + // Draw rectangle filled with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, GuiFade(color, guiAlpha)); + } + + if (borderWidth > 0) + { + // Draw rectangle border lines with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + } + +#if defined(RAYGUI_DEBUG_RECS_BOUNDS) + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, Fade(RED, 0.4f)); +#endif +} + +// Draw tooltip using control bounds +static void GuiTooltip(Rectangle controlRec) +{ + if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) + { + Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + + if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); + + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, NULL); + + int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); + int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_PADDING, 0); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, guiTooltipPtr); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); + GuiSetStyle(LABEL, TEXT_PADDING, textPadding); + } +} + +// Split controls text into multiple strings +// Also check for multiple columns (required by GuiToggleGroup()) +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + // NOTE: Those definitions could be externally provided if required + + // TODO: HACK: GuiTextSplit() - Review how textRows are returned to user + // textRow is an externally provided array of integers that stores row number for every splitted string + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; // String pointers array (points to buffer data) + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; // Buffer data (text input copy with '\0' added) + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 1; + + if (textRow != NULL) textRow[0] = 0; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if ((buffer[i] == delimiter) || (buffer[i] == '\n')) + { + result[counter] = buffer + i + 1; + + if (textRow != NULL) + { + if (buffer[i] == '\n') textRow[counter] = textRow[counter - 1] + 1; + else textRow[counter] = textRow[counter - 1]; + } + + buffer[i] = '\0'; // Set an end of string at this point + + counter++; + if (counter >= RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + + *count = counter; + + return result; +} + +// Convert color data from RGB to HSV +// NOTE: Color data should be passed normalized +static Vector3 ConvertRGBtoHSV(Vector3 rgb) +{ + Vector3 hsv = { 0 }; + float min = 0.0f; + float max = 0.0f; + float delta = 0.0f; + + min = (rgb.x < rgb.y)? rgb.x : rgb.y; + min = (min < rgb.z)? min : rgb.z; + + max = (rgb.x > rgb.y)? rgb.x : rgb.y; + max = (max > rgb.z)? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Convert color data from HSV to RGB +// NOTE: Color data should be passed normalized +static Vector3 ConvertHSVtoRGB(Vector3 hsv) +{ + Vector3 rgb = { 0 }; + float hh = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f, ff = 0.0f; + long i = 0; + + // NOTE: Comparing float values could not work properly + if (hsv.y <= 0.0f) + { + rgb.x = hsv.z; + rgb.y = hsv.z; + rgb.z = hsv.z; + return rgb; + } + + hh = hsv.x; + if (hh >= 360.0f) hh = 0.0f; + hh /= 60.0f; + + i = (long)hh; + ff = hh - i; + p = hsv.z*(1.0f - hsv.y); + q = hsv.z*(1.0f - (hsv.y*ff)); + t = hsv.z*(1.0f - (hsv.y*(1.0f - ff))); + + switch (i) + { + case 0: + { + rgb.x = hsv.z; + rgb.y = t; + rgb.z = p; + } break; + case 1: + { + rgb.x = q; + rgb.y = hsv.z; + rgb.z = p; + } break; + case 2: + { + rgb.x = p; + rgb.y = hsv.z; + rgb.z = t; + } break; + case 3: + { + rgb.x = p; + rgb.y = q; + rgb.z = hsv.z; + } break; + case 4: + { + rgb.x = t; + rgb.y = p; + rgb.z = hsv.z; + } break; + case 5: + default: + { + rgb.x = hsv.z; + rgb.y = p; + rgb.z = q; + } break; + } + + return rgb; +} + +// Scroll bar control (used by GuiScrollPanel()) +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) +{ + GuiState state = guiState; + + // Is the scrollbar horizontal or vertical? + bool isVertical = (bounds.width > bounds.height)? false : true; + + // The size (width or height depending on scrollbar type) of the spinner buttons + const int spinnerSize = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)? + (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : + (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; + + // Arrow buttons [<] [>] [∧] [∨] + Rectangle arrowUpLeft = { 0 }; + Rectangle arrowDownRight = { 0 }; + + // Actual area of the scrollbar excluding the arrow buttons + Rectangle scrollbar = { 0 }; + + // Slider bar that moves --[///]----- + Rectangle slider = { 0 }; + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size + + // Calculate rectangles for all of the components + arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ + (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)spinnerSize, (float)spinnerSize }; + + if (isVertical) + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), + bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), + (float)sliderSize }; + } + else // horizontal + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), + bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + (float)sliderSize, + bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && + !CheckCollisionPointRec(mousePoint, arrowUpLeft) && + !CheckCollisionPointRec(mousePoint, arrowDownRight)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Handle mouse wheel + int wheel = (int)GetMouseWheelMove(); + if (wheel != 0) value += wheel; + + // Handle mouse button down + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + // Check arrows click + if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (!CheckCollisionPointRec(mousePoint, slider)) + { + // If click on scrollbar position but not on slider, place slider directly on that position + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + + state = STATE_PRESSED; + } + + // Keyboard control on mouse hover scrollbar + /* + if (isVertical) + { + if (IsKeyDown(KEY_DOWN)) value += 5; + else if (IsKeyDown(KEY_UP)) value -= 5; + } + else + { + if (IsKeyDown(KEY_RIGHT)) value += 5; + else if (IsKeyDown(KEY_LEFT)) value -= 5; + } + */ + } + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background + + GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background + GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar + + // Draw arrows (using icon if available) + if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) + { +#if defined(RAYGUI_NO_ICONS) + GuiDrawText(isVertical? "^" : "<", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText(isVertical? "v" : ">", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(isVertical? "#121#" : "#118#", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL + GuiDrawText(isVertical? "#120#" : "#119#", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL +#endif + } + //-------------------------------------------------------------------- + + return value; +} + +// Color fade-in or fade-out, alpha goes from 0.0f to 1.0f +// WARNING: It multiplies current alpha by alpha scale factor +static Color GuiFade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + Color result = { color.r, color.g, color.b, (unsigned char)(color.a*alpha) }; + + return result; +} + +#if defined(RAYGUI_STANDALONE) +// Returns a Color struct from hexadecimal value +static Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xff; + color.g = (unsigned char)(hexValue >> 16) & 0xff; + color.b = (unsigned char)(hexValue >> 8) & 0xff; + color.a = (unsigned char)hexValue & 0xff; + + return color; +} + +// Returns hexadecimal value for a Color +static int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Check if point is inside rectangle +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && + (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Formatting of text with variables to 'embed' +static const char *TextFormat(const char *text, ...) +{ + #if !defined(RAYGUI_TEXTFORMAT_MAX_SIZE) + #define RAYGUI_TEXTFORMAT_MAX_SIZE 256 + #endif + + static char buffer[RAYGUI_TEXTFORMAT_MAX_SIZE]; + + va_list args; + va_start(args, text); + vsnprintf(buffer, RAYGUI_TEXTFORMAT_MAX_SIZE, text, args); + va_end(args); + + return buffer; +} + +// Draw rectangle with vertical gradient fill color +// NOTE: This function is only used by GuiColorPicker() +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +{ + Rectangle bounds = { (float)posX, (float)posY, (float)width, (float)height }; + DrawRectangleGradientEx(bounds, color1, color2, color2, color1); +} + +// Split string into multiple strings +const char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + } + + *count = counter; + return result; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +static int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10 + (int)(text[i] - '0'); + + return value*sign; +} + +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +static float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] != '.') value *= sign; + else + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + + return value; +} + +// Encode codepoint into UTF-8 text (char array size returned as parameter) +static const char *CodepointToUTF8(int codepoint, int *byteSize) +{ + static char utf8[6] = { 0 }; + int size = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + size = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + size = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + size = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + size = 4; + } + + *byteSize = size; + + return utf8; +} + +// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found +// When a invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +static int GetCodepointNext(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + + // Get current codepoint and bytes processed + if (0xf0 == (0xf8 & ptr[0])) + { + // 4 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); + *codepointSize = 4; + } + else if (0xe0 == (0xf0 & ptr[0])) + { + // 3 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); + *codepointSize = 3; + } + else if (0xc0 == (0xe0 & ptr[0])) + { + // 2 byte UTF-8 codepoint + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks + codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); + *codepointSize = 2; + } + else if (0x00 == (0x80 & ptr[0])) + { + // 1 byte UTF-8 codepoint + codepoint = ptr[0]; + *codepointSize = 1; + } + + return codepoint; +} +#endif // RAYGUI_STANDALONE + +#endif // RAYGUI_IMPLEMENTATION \ No newline at end of file diff --git a/src/raylib.h b/src/raylib.h new file mode 100644 index 0000000..8c638b7 --- /dev/null +++ b/src/raylib.h @@ -0,0 +1,1727 @@ +/********************************************************************************************** +* +* raylib v5.6-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* +* FEATURES: +* - NO external dependencies, all required libraries included with raylib +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, +* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5 +* - Written in plain C code (C99) in PascalCase/camelCase notation +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) +* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) +* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! +* - Flexible Materials system, supporting classic maps and PBR maps +* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) +* - Shaders support, including Model shaders and Postprocessing shaders +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) +* - VR stereo rendering with configurable HMD device parameters +* - Bindings to multiple programming languages available! +* +* NOTES: +* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) +* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) +* +* DEPENDENCIES (included): +* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input +* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input +* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading +* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management +* +* OPTIONAL DEPENDENCIES (included): +* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm +* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm +* [rcore] rprand (Ramon Santamaria) for pseudo-random numbers generation +* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage +* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) +* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) +* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms +* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation +* [rtext] stb_truetype (Sean Barret) for ttf fonts loading +* [rtext] stb_rect_pack (Sean Barret) for rectangles packing +* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation +* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) +* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) +* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) +* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) +* [raudio] dr_wav (David Reid) for WAV audio file loading +* [raudio] dr_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barret) for OGG audio loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage +* +* +* LICENSE: zlib/libpng +* +* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYLIB_H +#define RAYLIB_H + +#include // Required for: va_list - Only used by TraceLogCallback + +#define RAYLIB_VERSION_MAJOR 5 +#define RAYLIB_VERSION_MINOR 6 +#define RAYLIB_VERSION_PATCH 0 +#define RAYLIB_VERSION "5.6-dev" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) + #if defined(__TINYC__) + #define __declspec(x) __attribute__((x)) + #endif + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // Building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // Using the library as a Win32 shared library (.dll) + #endif +#else + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // Building as a Unix shared library (.so/.dylib) + #endif +#endif + +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Allow custom memory allocators +// NOTE: Require recompiling raylib sources +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized with { } +// This is called aggregate initialization (C++11 feature) +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +// Some compilers (mostly macos clang) default to C++98, +// where aggregate initialization can't be used +// So, give a more clear error stating how to fix this +#if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) + #error "C++11 or later is required. Add -std=c++11" +#endif + +// NOTE: Set some defines with some data types declared by raylib +// Other modules (raymath, rlgl) also require some of those types, so, +// to be able to use those other modules as standalone (not depending on raylib) +// this defines are very useful for internal check and avoid type (re)definitions +#define RL_COLOR_TYPE +#define RL_RECTANGLE_TYPE +#define RL_VECTOR2_TYPE +#define RL_VECTOR3_TYPE +#define RL_VECTOR4_TYPE +#define RL_QUATERNION_TYPE +#define RL_MATRIX_TYPE + +// Some Basic Colors +// NOTE: Custom raylib color palette for amazing visuals on WHITE background +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown + +#define WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Boolean type +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include +#elif !defined(__cplusplus) && !defined(bool) + typedef enum bool { false = 0, true = !false } bool; + #define RL_BOOL_TYPE +#endif + +// Vector2, 2 components +typedef struct Vector2 { + float x; // Vector x component + float y; // Vector y component +} Vector2; + +// Vector3, 3 components +typedef struct Vector3 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component +} Vector3; + +// Vector4, 4 components +typedef struct Vector4 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component + float w; // Vector w component +} Vector4; + +// Quaternion, 4 components (Vector4 alias) +typedef Vector4 Quaternion; + +// Matrix, 4x4 components, column major, OpenGL style, right-handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; + +// Color, 4 components, R8G8B8A8 (32bit) +typedef struct Color { + unsigned char r; // Color red value + unsigned char g; // Color green value + unsigned char b; // Color blue value + unsigned char a; // Color alpha value +} Color; + +// Rectangle, 4 components +typedef struct Rectangle { + float x; // Rectangle top-left corner position x + float y; // Rectangle top-left corner position y + float width; // Rectangle width + float height; // Rectangle height +} Rectangle; + +// Image, pixel data stored in CPU memory (RAM) +typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Image; + +// Texture, tex data stored in GPU memory (VRAM) +typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Texture; + +// Texture2D, same as Texture +typedef Texture Texture2D; + +// TextureCubemap, same as Texture +typedef Texture TextureCubemap; + +// RenderTexture, fbo for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; + +// RenderTexture2D, same as RenderTexture +typedef RenderTexture RenderTexture2D; + +// NPatchInfo, n-patch layout info +typedef struct NPatchInfo { + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 +} NPatchInfo; + +// GlyphInfo, font characters glyphs info +typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data +} GlyphInfo; + +// Font, font texture and GlyphInfo array data +typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data +} Font; + +// Camera, defines position/orientation in 3d space +typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane height in world units in orthographic + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC +} Camera3D; + +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + +// Camera2D, defines position/orientation in 2d space +typedef struct Camera2D { + Vector2 offset; // Camera offset (displacement from target) + Vector2 target; // Camera target (rotation and zoom origin) + float rotation; // Camera rotation in degrees + float zoom; // Camera zoom (scaling), should be 1.0f by default +} Camera2D; + +// Mesh, vertex data and vao/vbo +typedef struct Mesh { + int vertexCount; // Number of vertices stored in arrays + int triangleCount; // Number of triangles stored (indexed or not) + + // Vertex attributes data + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) + float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) + + // Animation vertex data + float *animVertices; // Animated vertex positions (after bones transformations) + float *animNormals; // Animated normals (after bones transformations) + unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) + Matrix *boneMatrices; // Bones animated transformation matrices + int boneCount; // Number of bones + + // OpenGL identifiers + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) +} Mesh; + +// Shader +typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) +} Shader; + +// MaterialMap +typedef struct MaterialMap { + Texture2D texture; // Material map texture + Color color; // Material map color + float value; // Material map value +} MaterialMap; + +// Material, includes shader and maps +typedef struct Material { + Shader shader; // Material shader + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) + float params[4]; // Material generic parameters (if required) +} Material; + +// Transform, vertex transformation data +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Bone, skeletal animation bone +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + +// Model, meshes, materials and animation data +typedef struct Model { + Matrix transform; // Local transform matrix + + int meshCount; // Number of meshes + int materialCount; // Number of materials + Mesh *meshes; // Meshes array + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + Transform *bindPose; // Bones base transformation (pose) +} Model; + +// ModelAnimation +typedef struct ModelAnimation { + int boneCount; // Number of bones + int frameCount; // Number of animation frames + BoneInfo *bones; // Bones information (skeleton) + Transform **framePoses; // Poses array by frame + char name[32]; // Animation name +} ModelAnimation; + +// Ray, ray for raycasting +typedef struct Ray { + Vector3 position; // Ray position (origin) + Vector3 direction; // Ray direction (normalized) +} Ray; + +// RayCollision, ray hit information +typedef struct RayCollision { + bool hit; // Did the ray hit something? + float distance; // Distance to the nearest hit + Vector3 point; // Point of the nearest hit + Vector3 normal; // Surface normal of hit +} RayCollision; + +// BoundingBox +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + +// Wave, audio wave data +typedef struct Wave { + unsigned int frameCount; // Total number of frames (considering channels) + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) + void *data; // Buffer data pointer +} Wave; + +// Opaque structs declaration +// NOTE: Actual structs are defined internally in raudio module +typedef struct rAudioBuffer rAudioBuffer; +typedef struct rAudioProcessor rAudioProcessor; + +// AudioStream, custom audio stream +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) +} AudioStream; + +// Sound +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) +} Sound; + +// Music, audio stream, anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +// VrDeviceInfo, Head-Mounted-Display device parameters +typedef struct VrDeviceInfo { + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters +} VrDeviceInfo; + +// VrStereoConfig, VR stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + +// File path list +typedef struct FilePathList { + unsigned int capacity; // Filepaths max entries + unsigned int count; // Filepaths entries count + char **paths; // Filepaths entries +} FilePathList; + +// Automation event +typedef struct AutomationEvent { + unsigned int frame; // Event frame + unsigned int type; // Event type (AutomationEventType) + int params[4]; // Event parameters (if required) +} AutomationEvent; + +// Automation event list +typedef struct AutomationEventList { + unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) + unsigned int count; // Events entries count + AutomationEvent *events; // Events entries +} AutomationEventList; + +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 +typedef enum { + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED + FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, // Trace logging, intended for internal use only + LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + LOG_INFO, // Info logging, used for program execution info + LOG_WARNING, // Warning logging, used on recoverable failures + LOG_ERROR, // Error logging, used on unrecoverable failures + LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + LOG_NONE // Disable logging +} TraceLogLevel; + +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining +// required keys for alternative layouts +typedef enum { + KEY_NULL = 0, // Key: NULL, used for no key pressed + // Alphanumeric keys + KEY_APOSTROPHE = 39, // Key: ' + KEY_COMMA = 44, // Key: , + KEY_MINUS = 45, // Key: - + KEY_PERIOD = 46, // Key: . + KEY_SLASH = 47, // Key: / + KEY_ZERO = 48, // Key: 0 + KEY_ONE = 49, // Key: 1 + KEY_TWO = 50, // Key: 2 + KEY_THREE = 51, // Key: 3 + KEY_FOUR = 52, // Key: 4 + KEY_FIVE = 53, // Key: 5 + KEY_SIX = 54, // Key: 6 + KEY_SEVEN = 55, // Key: 7 + KEY_EIGHT = 56, // Key: 8 + KEY_NINE = 57, // Key: 9 + KEY_SEMICOLON = 59, // Key: ; + KEY_EQUAL = 61, // Key: = + KEY_A = 65, // Key: A | a + KEY_B = 66, // Key: B | b + KEY_C = 67, // Key: C | c + KEY_D = 68, // Key: D | d + KEY_E = 69, // Key: E | e + KEY_F = 70, // Key: F | f + KEY_G = 71, // Key: G | g + KEY_H = 72, // Key: H | h + KEY_I = 73, // Key: I | i + KEY_J = 74, // Key: J | j + KEY_K = 75, // Key: K | k + KEY_L = 76, // Key: L | l + KEY_M = 77, // Key: M | m + KEY_N = 78, // Key: N | n + KEY_O = 79, // Key: O | o + KEY_P = 80, // Key: P | p + KEY_Q = 81, // Key: Q | q + KEY_R = 82, // Key: R | r + KEY_S = 83, // Key: S | s + KEY_T = 84, // Key: T | t + KEY_U = 85, // Key: U | u + KEY_V = 86, // Key: V | v + KEY_W = 87, // Key: W | w + KEY_X = 88, // Key: X | x + KEY_Y = 89, // Key: Y | y + KEY_Z = 90, // Key: Z | z + KEY_LEFT_BRACKET = 91, // Key: [ + KEY_BACKSLASH = 92, // Key: '\' + KEY_RIGHT_BRACKET = 93, // Key: ] + KEY_GRAVE = 96, // Key: ` + // Function keys + KEY_SPACE = 32, // Key: Space + KEY_ESCAPE = 256, // Key: Esc + KEY_ENTER = 257, // Key: Enter + KEY_TAB = 258, // Key: Tab + KEY_BACKSPACE = 259, // Key: Backspace + KEY_INSERT = 260, // Key: Ins + KEY_DELETE = 261, // Key: Del + KEY_RIGHT = 262, // Key: Cursor right + KEY_LEFT = 263, // Key: Cursor left + KEY_DOWN = 264, // Key: Cursor down + KEY_UP = 265, // Key: Cursor up + KEY_PAGE_UP = 266, // Key: Page up + KEY_PAGE_DOWN = 267, // Key: Page down + KEY_HOME = 268, // Key: Home + KEY_END = 269, // Key: End + KEY_CAPS_LOCK = 280, // Key: Caps lock + KEY_SCROLL_LOCK = 281, // Key: Scroll down + KEY_NUM_LOCK = 282, // Key: Num lock + KEY_PRINT_SCREEN = 283, // Key: Print screen + KEY_PAUSE = 284, // Key: Pause + KEY_F1 = 290, // Key: F1 + KEY_F2 = 291, // Key: F2 + KEY_F3 = 292, // Key: F3 + KEY_F4 = 293, // Key: F4 + KEY_F5 = 294, // Key: F5 + KEY_F6 = 295, // Key: F6 + KEY_F7 = 296, // Key: F7 + KEY_F8 = 297, // Key: F8 + KEY_F9 = 298, // Key: F9 + KEY_F10 = 299, // Key: F10 + KEY_F11 = 300, // Key: F11 + KEY_F12 = 301, // Key: F12 + KEY_LEFT_SHIFT = 340, // Key: Shift left + KEY_LEFT_CONTROL = 341, // Key: Control left + KEY_LEFT_ALT = 342, // Key: Alt left + KEY_LEFT_SUPER = 343, // Key: Super left + KEY_RIGHT_SHIFT = 344, // Key: Shift right + KEY_RIGHT_CONTROL = 345, // Key: Control right + KEY_RIGHT_ALT = 346, // Key: Alt right + KEY_RIGHT_SUPER = 347, // Key: Super right + KEY_KB_MENU = 348, // Key: KB menu + // Keypad keys + KEY_KP_0 = 320, // Key: Keypad 0 + KEY_KP_1 = 321, // Key: Keypad 1 + KEY_KP_2 = 322, // Key: Keypad 2 + KEY_KP_3 = 323, // Key: Keypad 3 + KEY_KP_4 = 324, // Key: Keypad 4 + KEY_KP_5 = 325, // Key: Keypad 5 + KEY_KP_6 = 326, // Key: Keypad 6 + KEY_KP_7 = 327, // Key: Keypad 7 + KEY_KP_8 = 328, // Key: Keypad 8 + KEY_KP_9 = 329, // Key: Keypad 9 + KEY_KP_DECIMAL = 330, // Key: Keypad . + KEY_KP_DIVIDE = 331, // Key: Keypad / + KEY_KP_MULTIPLY = 332, // Key: Keypad * + KEY_KP_SUBTRACT = 333, // Key: Keypad - + KEY_KP_ADD = 334, // Key: Keypad + + KEY_KP_ENTER = 335, // Key: Keypad Enter + KEY_KP_EQUAL = 336, // Key: Keypad = + // Android key buttons + KEY_BACK = 4, // Key: Android back button + KEY_MENU = 5, // Key: Android menu button + KEY_VOLUME_UP = 24, // Key: Android volume up button + KEY_VOLUME_DOWN = 25 // Key: Android volume down button +} KeyboardKey; + +// Add backwards compatibility support for deprecated names +#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT +#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT +#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE + +// Mouse buttons +typedef enum { + MOUSE_BUTTON_LEFT = 0, // Mouse button left + MOUSE_BUTTON_RIGHT = 1, // Mouse button right + MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) + MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) + MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) + MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) + MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) +} MouseButton; + +// Mouse cursor +typedef enum { + MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape + MOUSE_CURSOR_ARROW = 1, // Arrow shape + MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape + MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape + MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor + MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; + +// Gamepad buttons +typedef enum { + GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking + GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button + GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button + GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button + GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) + GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button + GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button + GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) + GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) + GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) + GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left + GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right +} GamepadButton; + +// Gamepad axes +typedef enum { + GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis + GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis + GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis + GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis + GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] + GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] +} GamepadAxis; + +// Material map index +typedef enum { + MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) + MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) + MATERIAL_MAP_NORMAL, // Normal material + MATERIAL_MAP_ROUGHNESS, // Roughness material + MATERIAL_MAP_OCCLUSION, // Ambient occlusion material + MATERIAL_MAP_EMISSION, // Emission material + MATERIAL_MAP_HEIGHT, // Heightmap material + MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_BRDF // Brdf material +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) + SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) + SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf + SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds + SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights + SHADER_LOC_BONE_MATRICES, // Shader location: array of matrices uniform: boneMatrices + SHADER_LOC_VERTEX_INSTANCE_TX // Shader location: vertex attribute: instanceTransform +} ShaderLocationIndex; + +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + SHADER_UNIFORM_INT, // Shader uniform type: int + SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) + SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} ShaderUniformDataType; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} ShaderAttributeDataType; + +// Pixel formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} PixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; + +// Texture parameters: wrap mode +typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces +} CubemapLayout; + +// Font type, defines generation method +typedef enum { + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader +} FontType; + +// Color blending modes (pre-defined) +typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) +} BlendMode; + +// Gesture +// NOTE: Provided as bit-wise flags to enable only desired gestures +typedef enum { + GESTURE_NONE = 0, // No gesture + GESTURE_TAP = 1, // Tap gesture + GESTURE_DOUBLETAP = 2, // Double tap gesture + GESTURE_HOLD = 4, // Hold gesture + GESTURE_DRAG = 8, // Drag gesture + GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture + GESTURE_SWIPE_LEFT = 32, // Swipe left gesture + GESTURE_SWIPE_UP = 64, // Swipe up gesture + GESTURE_SWIPE_DOWN = 128, // Swipe down gesture + GESTURE_PINCH_IN = 256, // Pinch in gesture + GESTURE_PINCH_OUT = 512 // Pinch out gesture +} Gesture; + +// Camera system modes +typedef enum { + CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) + CAMERA_FREE, // Camera free mode + CAMERA_ORBITAL, // Camera orbital, around target, zoom supported + CAMERA_FIRST_PERSON, // Camera first person + CAMERA_THIRD_PERSON // Camera third person +} CameraMode; + +// Camera projection +typedef enum { + CAMERA_PERSPECTIVE = 0, // Perspective projection + CAMERA_ORTHOGRAPHIC // Orthographic projection +} CameraProjection; + +// N-patch layout +typedef enum { + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: These callbacks are intended for advanced users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, const char *text); // FileIO: Save text data + +//------------------------------------------------------------------------------------ +// Global Variables Definition +//------------------------------------------------------------------------------------ +// It's lonely here... + +//------------------------------------------------------------------------------------ +// Window and Graphics Device Functions (Module: core) +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Window-related functions +RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully +RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized +RLAPI bool IsWindowFocused(void); // Check if window is currently focused +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable +RLAPI void RestoreWindow(void); // Restore window from being minimized/maximized +RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) +RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) +RLAPI void SetWindowTitle(const char *title); // Set title for window +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen +RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window +RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] +RLAPI void SetWindowFocused(void); // Set window focused +RLAPI void *GetWindowHandle(void); // Get native window handle +RLAPI int GetScreenWidth(void); // Get current screen width +RLAPI int GetScreenHeight(void); // Get current screen height +RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) +RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) +RLAPI int GetMonitorCount(void); // Get number of connected monitors +RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate +RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor +RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor +RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor +RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content +RLAPI Image GetClipboardImage(void); // Get clipboard image content +RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling +RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling + +// Cursor-related functions +RLAPI void ShowCursor(void); // Shows cursor +RLAPI void HideCursor(void); // Hides cursor +RLAPI bool IsCursorHidden(void); // Check if cursor is not visible +RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) +RLAPI void DisableCursor(void); // Disables cursor (lock cursor) +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen + +// Drawing-related functions +RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) +RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing +RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) +RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) +RLAPI void EndMode2D(void); // Ends 2D mode with custom camera +RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) +RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode +RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture +RLAPI void EndTextureMode(void); // Ends drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value and bind the texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) + +// Screen-space-related functions +#define GetMouseRay GetScreenToWorldRay // Compatibility hack for previous raylib versions +RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) +RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position +RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix + +// Timing-related functions +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() +RLAPI int GetFPS(void); // Get current FPS + +// Custom frame control functions +// NOTE: Those functions are intended for advanced users that want full control over the frame processing +// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() +// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL +RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) +RLAPI void PollInputEvents(void); // Register all input events +RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) + +// Random values generation functions +RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator +RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) +RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated +RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence + +// Misc. functions +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + +// NOTE: Following functions implemented in module [utils] +//------------------------------------------------------------------ +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// Set custom callbacks +// WARNING: Callbacks setup is intended for advanced users +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + +// Files management functions +RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success +RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success +RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string +RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, const char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success +//------------------------------------------------------------------ + +// File system functions +RLAPI int FileRename(const char *fileName, const char *fileRename); // Rename file (if exists) +RLAPI int FileRemove(const char *fileName); // Remove file (if exists) +RLAPI int FileCopy(const char *srcPath, const char *dstPath); // Copy file from one path to another, dstPath created if it doesn't exist +RLAPI int FileMove(const char *srcPath, const char *dstPath); // Move file from one directory to another, dstPath created if it doesn't exist +RLAPI int FileTextReplace(const char *fileName, const char *search, const char *replacement); // Replace text in an existing file +RLAPI int FileTextFindIndex(const char *fileName, const char *search); // Find text in existing file +RLAPI bool FileExists(const char *fileName); // Check if file exists +RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists +RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (recommended include point: .png, .wav) +RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) +RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) +RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) +RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success +RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success +RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory +RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS +RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths +RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result +RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths +RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window +RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths +RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths + +// Compression/Encoding functionality +RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() +RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() +RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string (includes NULL terminator), memory must be MemFree() +RLAPI unsigned char *DecodeDataBase64(const char *text, int *outputSize); // Decode Base64 string (expected NULL terminated), memory must be MemFree() +RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code +RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) +RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) +RLAPI unsigned int *ComputeSHA256(unsigned char *data, int dataSize); // Compute SHA256 hash code, returns static int[8] (32 bytes) + +// Automation events functionality +RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS +RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file +RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file +RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to +RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording +RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) +RLAPI void StopAutomationEventRecording(void); // Stop recording automation events +RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event + +//------------------------------------------------------------------------------------ +// Input Handling Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Input-related functions: keyboard +RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once +RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again +RLAPI bool IsKeyDown(int key); // Check if a key is being pressed +RLAPI bool IsKeyReleased(int key); // Check if a key has been released once +RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty +RLAPI const char *GetKeyName(int key); // Get name of a QWERTY key on the current keyboard layout (eg returns string 'q' for KEY_A on an AZERTY keyboard) +RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) + +// Input-related functions: gamepads +RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available +RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Get axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) +RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) + +// Input-related functions: mouse +RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once +RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed +RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once +RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed +RLAPI int GetMouseX(void); // Get mouse position X +RLAPI int GetMouseY(void); // Get mouse position Y +RLAPI Vector2 GetMousePosition(void); // Get mouse position XY +RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames +RLAPI void SetMousePosition(int x, int y); // Set mouse position XY +RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset +RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling +RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger +RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor + +// Input-related functions: touch +RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) +RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) +RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) +RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index +RLAPI int GetTouchPointCount(void); // Get number of touch points + +//------------------------------------------------------------------------------------ +// Gestures and Touch Handling Functions (Module: rgestures) +//------------------------------------------------------------------------------------ +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle + +//------------------------------------------------------------------------------------ +// Camera System Functions (Module: rcamera) +//------------------------------------------------------------------------------------ +RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode +RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation + +//------------------------------------------------------------------------------------ +// Basic Shapes Drawing Functions (Module: shapes) +//------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing +RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing +RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing + +// Basic shapes drawing functions +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] +RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) +RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation +RLAPI void DrawLineDashed(Vector2 startPos, Vector2 endPos, int dashSize, int spaceSize, Color color); // Draw a dashed line +RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle +RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) +RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline +RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) +RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse (Vector version) +RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawEllipseLinesV(Vector2 center, float radiusH, float radiusV, Color color); // Draw ellipse outline (Vector version) +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) +RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color bottomRight, Color topRight); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters +RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges +RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline +RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) +RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) +RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides +RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters + +// Splines drawing functions +RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points +RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points +RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points +RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] +RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] +RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points +RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points +RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points +RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point +RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points + +// Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] +RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear +RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline +RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom +RLAPI Vector2 GetSplinePointBezierQuad(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier +RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier + +// Basic shapes collision detection functions +RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles +RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles +RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] +RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle +RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision + +//------------------------------------------------------------------------------------ +// Texture Loading and Drawing Functions (Module: textures) +//------------------------------------------------------------------------------------ + +// Image loading functions +// NOTE: These functions do not require GPU access +RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) +RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data +RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' +RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data +RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) +RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) +RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success + +// Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color +RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient +RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient +RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient +RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked +RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise +RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise +RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells +RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data + +// Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece +RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) +RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) +RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) +RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format +RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation +RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth, int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color +RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI void ImageFlipVertical(Image *image); // Flip image vertically +RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally +RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) +RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg +RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg +RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint +RLAPI void ImageColorInvert(Image *image); // Modify image color: invert +RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale +RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) +RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) +RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle +RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position + +// Image drawing functions +// NOTE: Image software-rendering functions (CPU) +RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color +RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image +RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) +RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image +RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) +RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image +RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image +RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) +RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image +RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) +RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image +RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image +RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image +RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image +RLAPI void ImageDrawTriangleFan(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) +RLAPI void ImageDrawTriangleStrip(Image *dst, const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image +RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) +RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) +RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) + +// Texture loading functions +// NOTE: These functions require GPU access +RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) +RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) +RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) +RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data (pixels should be able to fill texture) +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data (pixels and rec should fit in texture) + +// Texture configuration functions +RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode + +// Texture drawing functions +RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D +RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 +RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters +RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely + +// Color/pixel related functions +RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal +RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) +RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] +RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] +RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] +RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color +RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f +RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f +RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint +RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] +RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value +RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format +RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format + +//------------------------------------------------------------------------------------ +// Font Loading and Text Drawing Functions (Module: text) +//------------------------------------------------------------------------------------ + +// Font loading/unloading functions +RLAPI Font GetFontDefault(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI Font LoadFontEx(const char *fileName, int fontSize, const int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height +RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, const int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' +RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) +RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, const int *codepoints, int codepointCount, int type, int *glyphCount); // Load font data for further use +RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) +RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) +RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success + +// Text drawing functions +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) +RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) + +// Text font info functions +RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found +RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found +RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found + +// Text codepoints management functions (unicode characters) +RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array +RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array +RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter +RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory +RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string +RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) + +// Text strings management functions (no UTF-8 strings, only byte chars) +// WARNING 1: Most of these functions use internal static buffers[], it's recommended to store returned data on user-side for re-use +// WARNING 2: Some strings allocate memory internally for the returned strings, those strings must be free by user using MemFree() +RLAPI char **LoadTextLines(const char *text, int *count); // Load text as separate lines ('\n') +RLAPI void UnloadTextLines(char **text, int lineCount); // Unload text lines +RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI const char *TextRemoveSpaces(const char *text); // Remove text spaces, concat words +RLAPI char *GetTextBetween(const char *text, const char *begin, const char *end); // Get text between two strings +RLAPI char *TextReplace(const char *text, const char *search, const char *replacement); // Replace text string (WARNING: memory must be freed!) +RLAPI char *TextReplaceBetween(const char *text, const char *begin, const char *end, const char *replacement); // Replace text between two specific strings (WARNING: memory must be freed!) +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) +RLAPI char *TextJoin(char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings, using MAX_TEXTSPLIT_COUNT static strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor +RLAPI int TextFindIndex(const char *text, const char *search); // Find first text occurrence within a string, -1 if not found +RLAPI char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI char *TextToSnake(const char *text); // Get Snake case notation version of provided string +RLAPI char *TextToCamel(const char *text); // Get Camel case notation version of provided string +RLAPI int TextToInteger(const char *text); // Get integer value from text +RLAPI float TextToFloat(const char *text); // Get float value from text + +//------------------------------------------------------------------------------------ +// Basic 3d Shapes Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Basic geometric 3D shapes drawing functions +RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space +RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line +RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space +RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube +RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) +RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) +RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere +RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters +RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires +RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone +RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos +RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires +RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos +RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos +RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos +RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line +RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) + +//------------------------------------------------------------------------------------ +// Model 3d Loading and Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Model management functions +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) + +// Model drawing functions +RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) +RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters +RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) +RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points +RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters +RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source +RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation + +// Mesh management functions +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success +RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes + +// Mesh generation functions +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data + +// Material loading/unloading functions +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh + +// Model animations loading/unloading functions +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) +RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) +RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data +RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match + +// Collision detection functions +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere +RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere +RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box +RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad + +//------------------------------------------------------------------------------------ +// Audio Loading and Playing Functions (Module: audio) +//------------------------------------------------------------------------------------ +typedef void (*AudioCallback)(void *bufferData, unsigned int frames); + +// Audio device management functions +RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI void CloseAudioDevice(void); // Close the audio device and context +RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +RLAPI void SetMasterVolume(float volume); // Set master volume (listener) +RLAPI float GetMasterVolume(void); // Get master volume (listener) + +// Wave/Sound loading/unloading functions +RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' +RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) +RLAPI Sound LoadSound(const char *fileName); // Load sound from file +RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data +RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) +RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data (default data format: 32 bit float, stereo) +RLAPI void UnloadWave(Wave wave); // Unload wave data +RLAPI void UnloadSound(Sound sound); // Unload sound +RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PauseSound(Sound sound); // Pause a sound +RLAPI void ResumeSound(Sound sound); // Resume a paused sound +RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (-1.0 left, 0.0 center, 1.0 right) +RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave +RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range +RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data +RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) +RLAPI void UnloadMusicStream(Music music); // Unload music stream +RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing +RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming +RLAPI void StopMusicStream(Music music); // Stop music playing +RLAPI void PauseMusicStream(Music music); // Pause music playing +RLAPI void ResumeMusicStream(Music music); // Resume playing paused music +RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) +RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (-1.0 left, 0.0 center, 1.0 right) +RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) +RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) +RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) +RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory +RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream +RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream +RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (0.5 is centered) +RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams +RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data + +RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives frames x 2 samples as 'float' (stereo) +RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream + +RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives frames x 2 samples as 'float' (stereo) +RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline + +#if defined(__cplusplus) +} +#endif + +#endif // RAYLIB_H \ No newline at end of file diff --git a/src/session/game_session.c b/src/session/game_session.c new file mode 100644 index 0000000..44c360b --- /dev/null +++ b/src/session/game_session.c @@ -0,0 +1,46 @@ +#include "game_session.h" + +#include +#include +#include +#include + +Game_Session *g_current_session = NULL; + +void game_session_create(bool is_singleplayer, bool is_host, Game_Session_Settings settings) { + assert(g_current_session == NULL); + + Game_Session *session = (Game_Session *)calloc(1, sizeof(Game_Session)); + session->is_singleplayer = is_singleplayer; + session->is_host = is_host; + session->settings = settings; + + session->simulation_world = simulation_create_world( + session->settings.seed, + session->settings.max_players, + session->settings.level_width, + session->settings.level_height + ); + + g_current_session = session; + + printf("New Game_Session created.\n"); +} + +void game_session_destroy() { + if(g_current_session == NULL) { + return; + } + + simulation_destroy_world(&g_current_session->simulation_world); + + free(g_current_session); + g_current_session = NULL; +} + +void game_session_init_default_settings(bool is_singleplayer, Game_Session_Settings *out_settings) { + out_settings->seed = 1337; // TODO: SS - Randomize. + out_settings->level_width = 48; + out_settings->level_height = 48; + out_settings->max_players = is_singleplayer ? 1 : 8; +} \ No newline at end of file diff --git a/src/session/game_session.h b/src/session/game_session.h new file mode 100644 index 0000000..d7a0a74 --- /dev/null +++ b/src/session/game_session.h @@ -0,0 +1,38 @@ +#ifndef GAME_SESSION_H +#define GAME_SESSION_H + +#include +#include + +#include "simulation/simulation_world.h" + +typedef struct { + uint32_t seed; + + uint16_t level_width; + uint16_t level_height; + + uint8_t max_players; + + // .. +} Game_Session_Settings; + +typedef struct { + bool is_singleplayer; + bool is_host; + + Game_Session_Settings settings; + + // TODO: SS - Game-state (counting down, active, over) + // TODO: SS - Local(?) input-queue. + Simulation_World simulation_world; +} Game_Session; + +extern Game_Session *g_current_session; + +void game_session_create(bool is_singleplayer, bool is_host, Game_Session_Settings settings); +void game_session_destroy(); + +void game_session_init_default_settings(bool is_singleplayer, Game_Session_Settings *out_settings); + +#endif \ No newline at end of file diff --git a/src/shared/entity.h b/src/shared/entity.h new file mode 100644 index 0000000..9c5bf3a --- /dev/null +++ b/src/shared/entity.h @@ -0,0 +1,17 @@ +#ifndef ENTITY_H +#define ENTITY_H + +#include + +typedef enum { + Entity_Type_None, + Entity_Type_Snake_Head, + Entity_Type_Snake_Body, + Entity_Type_Food, +} Entity_Type; + +typedef struct { + Entity_Type type; +} Entity; + +#endif \ No newline at end of file diff --git a/src/shared/game_world.c b/src/shared/game_world.c new file mode 100644 index 0000000..0fd558d --- /dev/null +++ b/src/shared/game_world.c @@ -0,0 +1,66 @@ +#include "game_world.h" + +#include +#include + +#define MIN_LEVEL_WIDTH 8 // TODO: SS - Move these out of here. +#define MIN_LEVEL_HEIGHT 8 + +Game_World *game_world_create(uint32_t seed, uint16_t level_width, uint16_t level_height) { + assert(level_width >= MIN_LEVEL_WIDTH); // We should probably have failed earlier. + assert(level_height >= MIN_LEVEL_HEIGHT); + + Game_World *w = (Game_World *)calloc(1, sizeof(Game_World)); + assert(w != NULL); + w->seed = seed; + + w->max_entities = level_width * level_height; + w->entities = (Entity *)calloc(w->max_entities, sizeof(Entity)); + + grid_initialize(&w->grid, level_width, level_height); + + + { // TODO: SS - Create the level. + { + Grid_Cell *cell = grid_get_cell(&w->grid, 5, 3); + assert(cell != NULL); + cell->entity.type = Entity_Type_Food; + } + + { + Grid_Cell *cell = grid_get_cell(&w->grid, 11, 4); + assert(cell != NULL); + cell->entity.type = Entity_Type_Food; + } + + { + Grid_Cell *cell = grid_get_cell(&w->grid, 15, 9); + assert(cell != NULL); + cell->entity.type = Entity_Type_Food; + } + + { + Grid_Cell *cell = grid_get_cell(&w->grid, 24, 15); + assert(cell != NULL); + cell->entity.type = Entity_Type_Snake_Head; + } + { + Grid_Cell *cell = grid_get_cell(&w->grid, 23, 15); + assert(cell != NULL); + cell->entity.type = Entity_Type_Snake_Body; + } + } + + + return w; +} + +void game_world_destroy(Game_World *world) { + assert(world != NULL); + + world->seed = 0; + world->max_entities = 0; + free(world->entities); + grid_dispose(&world->grid); + free(world); +} \ No newline at end of file diff --git a/src/shared/game_world.h b/src/shared/game_world.h new file mode 100644 index 0000000..4338e4e --- /dev/null +++ b/src/shared/game_world.h @@ -0,0 +1,20 @@ +#ifndef WORLD_H +#define WORLD_H + +#include +#include "entity.h" +#include "grid.h" + +typedef struct { + uint32_t seed; + + Entity *entities; + uint16_t max_entities; + + Grid grid; +} Game_World; + +Game_World *game_world_create(uint32_t seed, uint16_t level_width, uint16_t level_height); +void game_world_destroy(Game_World *world); + +#endif \ No newline at end of file diff --git a/src/shared/grid.c b/src/shared/grid.c new file mode 100644 index 0000000..b8431ae --- /dev/null +++ b/src/shared/grid.c @@ -0,0 +1,63 @@ +#include "grid.h" +#include +#include +#include + +#define GRID_MIN_WIDTH 8 +#define GRID_MIN_HEIGHT 8 + +bool grid_initialize(Grid *grid, uint16_t width, uint16_t height) { + assert(grid != NULL); + + if(width < GRID_MIN_WIDTH || height < GRID_MIN_HEIGHT) { + return false; + } + + grid->width = width; + grid->height = height; + grid->cells = calloc(width * height, sizeof(Grid_Cell)); + return true; +} + +void grid_dispose(Grid *grid) { + assert(grid != NULL); + + free(grid->cells); + memset(grid, 0, sizeof(Grid)); +} + +static bool to_grid_index(Grid *grid, uint16_t x, uint16_t y, uint16_t *out_grid_index) { + assert(grid != NULL); + assert(out_grid_index != NULL); + + if (!grid || !out_grid_index) return false; + if (x >= grid->width || y >= grid->height) return false; + + *out_grid_index = y * grid->width + x; + return true; +} + +static bool from_grid_index(Grid *grid, uint16_t grid_index, uint16_t *out_x, uint16_t *out_y) { + assert(grid != NULL); + assert(out_x != NULL); + assert(out_y != NULL); + + if (!grid || !out_x || !out_y) return false; + if (grid_index >= grid->width * grid->height) return false; + + *out_x = grid_index % grid->width; + *out_y = grid_index / grid->width; + return true; +} + +Grid_Cell *grid_get_cell(Grid *grid, uint16_t x, uint16_t y) { + assert(grid != NULL); + if(x >= grid->width || y >= grid->height) return NULL; + + uint16_t grid_index = 0; + if(!to_grid_index(grid, x, y, &grid_index)) { + return NULL; + } + + return &grid->cells[grid_index]; +} \ No newline at end of file diff --git a/src/shared/grid.h b/src/shared/grid.h new file mode 100644 index 0000000..ba2adb7 --- /dev/null +++ b/src/shared/grid.h @@ -0,0 +1,25 @@ +#ifndef GRID_H +#define GRID_H + +#include +#include + +#include "entity.h" + +typedef struct { + Entity entity; +} Grid_Cell; + +typedef struct { + uint16_t width; + uint16_t height; + + Grid_Cell *cells; +} Grid; + +bool grid_initialize(Grid *grid, uint16_t width, uint16_t height); +void grid_dispose(Grid *grid); + +Grid_Cell *grid_get_cell(Grid *grid, uint16_t x, uint16_t y); + +#endif \ No newline at end of file diff --git a/src/simulation/simulation_world.c b/src/simulation/simulation_world.c new file mode 100644 index 0000000..d55321b --- /dev/null +++ b/src/simulation/simulation_world.c @@ -0,0 +1,21 @@ +#include "simulation_world.h" + +#include +#include + +Simulation_World simulation_create_world(uint32_t seed, uint8_t max_players, uint16_t width, uint16_t height) { + Simulation_World w; + memset(&w, 0, sizeof(Simulation_World)); + + w.game_world = game_world_create(seed, width, height); + assert(w.game_world != NULL); + + return w; +} + +void simulation_destroy_world(Simulation_World *simulation_world) { + assert(simulation_world != NULL); + + game_world_destroy(simulation_world->game_world); + simulation_world->game_world = NULL; +} \ No newline at end of file diff --git a/src/simulation/simulation_world.h b/src/simulation/simulation_world.h new file mode 100644 index 0000000..8c9202e --- /dev/null +++ b/src/simulation/simulation_world.h @@ -0,0 +1,15 @@ +#ifndef SIMULATION_WORLD_H +#define SIMULATION_WORLD_H + +#include + +#include "shared/game_world.h" + +typedef struct { + Game_World *game_world; +} Simulation_World; + +Simulation_World simulation_create_world(uint32_t seed, uint8_t max_players, uint16_t width, uint16_t height); +void simulation_destroy_world(Simulation_World *simulation_world); + +#endif \ No newline at end of file