mirror of https://github.com/inolen/redream.git
483 lines
14 KiB
C++
483 lines
14 KiB
C++
#define IMGUI_IMPLEMENTATION
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#define IMGUI_DEFINE_PLACEMENT_NEW
|
|
#include <imgui/imgui.h>
|
|
#include <imgui/imgui_internal.h>
|
|
|
|
extern "C" {
|
|
#include <zlib.h>
|
|
#include "core/core.h"
|
|
#include "core/time.h"
|
|
#include "host/keycode.h"
|
|
#include "imgui.h"
|
|
#include "render/render_backend.h"
|
|
}
|
|
|
|
#define IMFONT_MAX_HEIGHT 128
|
|
|
|
struct imgui {
|
|
struct render_backend *r;
|
|
struct ImFont *fonts[IMFONT_NUM_FONTS][IMFONT_MAX_HEIGHT];
|
|
int64_t time;
|
|
int alt[2];
|
|
int ctrl[2];
|
|
int shift[2];
|
|
uint16_t keys[K_NUM_KEYS];
|
|
};
|
|
|
|
/* maintain global pointer for ig* functions */
|
|
static struct imgui *g_imgui;
|
|
#include "assets/fontawesome-webfont.inc"
|
|
#include "assets/opensans-regular.inc"
|
|
#include "assets/oswald-medium.inc"
|
|
|
|
static ImFont *imgui_get_font(struct imgui *imgui, int id, int font_height);
|
|
|
|
/*
|
|
* imgui extensions
|
|
*/
|
|
using namespace ImGui;
|
|
|
|
namespace ImGui {
|
|
|
|
static ImRect ButtonBox(const ImVec2 &label_size, const ImVec2 &req_size) {
|
|
ImGuiContext &g = *GImGui;
|
|
const ImGuiStyle &style = g.Style;
|
|
ImGuiWindow *window = GetCurrentWindow();
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
ImVec2 size =
|
|
CalcItemSize(req_size, label_size.x + style.FramePadding.x * 2.0f,
|
|
label_size.y + style.FramePadding.y * 2.0f);
|
|
return ImRect(pos, pos + size);
|
|
}
|
|
|
|
static ImU32 SelectableColor(bool selected, bool hovered, bool held) {
|
|
if (selected || (hovered && held)) {
|
|
return ImGui::GetColorU32(ImGuiCol_ButtonActive);
|
|
}
|
|
|
|
if (hovered) {
|
|
return ImGui::GetColorU32(ImGuiCol_ButtonHovered);
|
|
}
|
|
|
|
return ImGui::GetColorU32(ImGuiCol_Button);
|
|
}
|
|
|
|
static void RenderCircularNavHighlight(const ImRect &bb, ImGuiID id) {
|
|
ImGuiContext &g = *GImGui;
|
|
|
|
if (id != g.NavId || g.NavDisableHighlight) {
|
|
return;
|
|
}
|
|
|
|
ImGuiWindow *window = GetCurrentWindow();
|
|
const float THICKNESS = 2.0f;
|
|
const float DISTANCE = 3.0f + THICKNESS * 0.5f;
|
|
ImRect display_rect(bb.Min - ImVec2(DISTANCE, DISTANCE),
|
|
bb.Max + ImVec2(DISTANCE, DISTANCE));
|
|
|
|
if (!window->ClipRect.Contains(display_rect)) {
|
|
window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
|
|
}
|
|
|
|
const ImRect draw_rect(
|
|
display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f),
|
|
display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f));
|
|
const ImVec2 center = draw_rect.GetCenter();
|
|
float radius = draw_rect.GetWidth() / 2.0f;
|
|
window->DrawList->AddCircle(
|
|
center, radius, GetColorU32(ImGuiCol_NavHighlight), 48, THICKNESS);
|
|
|
|
if (!window->ClipRect.Contains(display_rect)) {
|
|
window->DrawList->PopClipRect();
|
|
}
|
|
}
|
|
}
|
|
|
|
int igDiscButton(ImTextureID user_texture_id, float item_diameter,
|
|
float draw_diameter, const struct ImVec2 uv0,
|
|
const struct ImVec2 uv1) {
|
|
ImGuiWindow *window = GetCurrentWindow();
|
|
|
|
if (window->SkipItems) {
|
|
return 0;
|
|
}
|
|
|
|
ImGuiContext &g = *GImGui;
|
|
const ImGuiStyle &style = g.Style;
|
|
|
|
ImGui::PushID((void *)user_texture_id);
|
|
const ImGuiID id = window->GetID("#image");
|
|
ImGui::PopID();
|
|
|
|
const ImVec2 item_size = {item_diameter, item_diameter};
|
|
const ImVec2 item_pos = window->DC.CursorPos;
|
|
const ImRect item_bb(item_pos, item_pos + item_size);
|
|
|
|
const ImVec2 draw_size = {draw_diameter, draw_diameter};
|
|
const ImVec2 draw_pos = {item_pos.x - (draw_diameter - item_diameter) / 2.0f,
|
|
item_pos.y - (draw_diameter - item_diameter) / 2.0f};
|
|
const ImRect draw_bb(draw_pos, draw_pos + draw_size);
|
|
|
|
ImGui::ItemSize(item_bb);
|
|
|
|
if (!ImGui::ItemAdd(item_bb, id)) {
|
|
return 0;
|
|
}
|
|
|
|
bool hovered, held;
|
|
bool pressed = ButtonBehavior(item_bb, id, &hovered, &held);
|
|
ImGui::RenderCircularNavHighlight(draw_bb, id);
|
|
window->DrawList->AddImage(user_texture_id, draw_bb.Min, draw_bb.Max, uv0,
|
|
uv1, 0xffffffff);
|
|
|
|
return (int)pressed;
|
|
}
|
|
|
|
int igOptionString(const char *label, const char *value, struct ImVec2 size) {
|
|
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
|
|
|
if (window->SkipItems) {
|
|
return 0;
|
|
}
|
|
|
|
ImGuiContext &g = *GImGui;
|
|
const ImGuiStyle &style = g.Style;
|
|
const ImGuiID id = window->GetID(label);
|
|
const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
|
|
const ImVec2 value_size = ImGui::CalcTextSize(value, NULL, true);
|
|
const ImVec2 total_size(label_size.x + value_size.x,
|
|
MAX(label_size.y, value_size.y));
|
|
const ImRect bb = ImGui::ButtonBox(total_size, size);
|
|
|
|
ImGui::ItemSize(bb, style.FramePadding.y);
|
|
|
|
if (!ImGui::ItemAdd(bb, id)) {
|
|
return 0;
|
|
}
|
|
|
|
ImGuiButtonFlags flags = 0;
|
|
if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) {
|
|
flags |= ImGuiButtonFlags_Repeat;
|
|
}
|
|
bool hovered, held;
|
|
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags);
|
|
|
|
const ImU32 col = SelectableColor(false, hovered, held);
|
|
ImGui::RenderNavHighlight(bb, id);
|
|
ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
|
|
ImGui::RenderTextClipped(bb.Min + style.FramePadding,
|
|
bb.Max - style.FramePadding, label, NULL,
|
|
&label_size, ImVec2(0.0f, 0.5f), &bb);
|
|
ImGui::RenderTextClipped(bb.Min + style.FramePadding,
|
|
bb.Max - style.FramePadding, value, NULL,
|
|
&value_size, ImVec2(1.0f, 0.5f), &bb);
|
|
|
|
return (int)pressed;
|
|
}
|
|
|
|
int igOptionInt(const char *label, int value, struct ImVec2 size) {
|
|
char value_str[128];
|
|
snprintf(value_str, sizeof(value_str), "%d", value);
|
|
return igOptionString(label, value_str, size);
|
|
}
|
|
|
|
int igTab(const char *label, struct ImVec2 size, int selected) {
|
|
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
|
|
|
if (window->SkipItems) {
|
|
return 0;
|
|
}
|
|
|
|
ImGuiContext &g = *GImGui;
|
|
const ImGuiStyle &style = g.Style;
|
|
const ImGuiID id = window->GetID(label);
|
|
const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
|
|
const ImRect bb = ImGui::ButtonBox(label_size, size);
|
|
|
|
ImGui::ItemSize(bb, style.FramePadding.y);
|
|
|
|
if (!ImGui::ItemAdd(bb, id)) {
|
|
return 0;
|
|
}
|
|
|
|
ImGuiButtonFlags flags = 0;
|
|
if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) {
|
|
flags |= ImGuiButtonFlags_Repeat;
|
|
}
|
|
bool hovered, held;
|
|
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags);
|
|
|
|
const ImU32 col = SelectableColor(selected, hovered, held);
|
|
ImGui::RenderNavHighlight(bb, id);
|
|
ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
|
|
ImGui::RenderTextClipped(bb.Min + style.FramePadding,
|
|
bb.Max - style.FramePadding, label, NULL,
|
|
&label_size, style.ButtonTextAlign, &bb);
|
|
|
|
return (int)pressed;
|
|
}
|
|
|
|
void igPushFontEx(int id, int font_height) {
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
ImFont *font = imgui_get_font(g_imgui, id, font_height);
|
|
ImGui::PushFont(font);
|
|
}
|
|
|
|
/*
|
|
* imgui implementation
|
|
*/
|
|
static void imgui_update_font_tex(struct imgui *imgui) {
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
/* destroy old texture first */
|
|
texture_handle_t font_tex = (texture_handle_t)(intptr_t)io.Fonts->TexID;
|
|
if (font_tex) {
|
|
r_destroy_texture(imgui->r, font_tex);
|
|
}
|
|
|
|
/* create new texture if fonts have been added */
|
|
uint8_t *pixels;
|
|
int width;
|
|
int height;
|
|
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
|
|
|
if (!width || !height) {
|
|
return;
|
|
}
|
|
|
|
font_tex = r_create_texture(imgui->r, PXL_RGBA, FILTER_BILINEAR, WRAP_REPEAT,
|
|
WRAP_REPEAT, 0, width, height, pixels);
|
|
io.Fonts->TexID = (void *)(intptr_t)font_tex;
|
|
}
|
|
|
|
static ImFont *imgui_get_font(struct imgui *imgui, int id, int font_height) {
|
|
CHECK(id >= 0 && id < IMFONT_NUM_FONTS);
|
|
CHECK(font_height >= 0 && font_height < IMFONT_MAX_HEIGHT);
|
|
|
|
ImFont **font = &g_imgui->fonts[id][font_height];
|
|
|
|
if (*font) {
|
|
return *font;
|
|
}
|
|
|
|
int font_len = 0;
|
|
int font_gz_len = 0;
|
|
const uint8_t *font_gz = NULL;
|
|
|
|
switch (id) {
|
|
case IMFONT_OSWALD_MEDIUM:
|
|
font_len = oswald_medium_len;
|
|
font_gz_len = oswald_medium_gz_len;
|
|
font_gz = oswald_medium_gz;
|
|
break;
|
|
case IMFONT_OPENSANS_REGULAR:
|
|
font_len = opensans_regular_len;
|
|
font_gz_len = opensans_regular_len;
|
|
font_gz = opensans_regular_gz;
|
|
break;
|
|
default:
|
|
LOG_FATAL("igPushFontEx unsupported font %d", id);
|
|
break;
|
|
}
|
|
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
/* load base font. note, AddFontFromMemoryTTF takes ownership of font_data, so
|
|
it doesn't need to be freed */
|
|
{
|
|
unsigned long tmp_len = font_len;
|
|
uint8_t *font_data = (uint8_t *)malloc(tmp_len);
|
|
int res = uncompress(font_data, &tmp_len, font_gz, font_gz_len);
|
|
CHECK_EQ(res, Z_OK);
|
|
*font = io.Fonts->AddFontFromMemoryTTF(font_data, tmp_len,
|
|
(float)font_height, NULL, NULL);
|
|
CHECK_NOTNULL(*font);
|
|
}
|
|
|
|
/* merge fontawesome icons */
|
|
{
|
|
static const ImWchar fa_ranges[] = {IMICON_RANGES, 0};
|
|
ImFontConfig config;
|
|
config.MergeMode = true;
|
|
|
|
unsigned long tmp_len = fontawesome_webfont_len;
|
|
uint8_t *font_data = (uint8_t *)malloc(tmp_len);
|
|
int res = uncompress(font_data, &tmp_len, fontawesome_webfont_gz,
|
|
fontawesome_webfont_gz_len);
|
|
CHECK_EQ(res, Z_OK);
|
|
*font = io.Fonts->AddFontFromMemoryTTF(
|
|
font_data, tmp_len, (float)font_height, &config, fa_ranges);
|
|
CHECK_NOTNULL(*font);
|
|
}
|
|
|
|
imgui_update_font_tex(imgui);
|
|
|
|
return *font;
|
|
}
|
|
|
|
void imgui_end_frame(struct imgui *imgui) {
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
int width = (int)io.DisplaySize.x;
|
|
int height = (int)io.DisplaySize.y;
|
|
|
|
/* update draw batches. note, this doesn't _actually_ render anything because
|
|
io.RenderDrawListsFn is null */
|
|
ImGui::Render();
|
|
|
|
/* get the latest draw batches, and pass them off out the render backend */
|
|
ImDrawData *draw_data = ImGui::GetDrawData();
|
|
|
|
r_viewport(imgui->r, 0, 0, width, height);
|
|
|
|
for (int i = 0; i < draw_data->CmdListsCount; ++i) {
|
|
const auto cmd_list = draw_data->CmdLists[i];
|
|
|
|
struct ui_vertex *verts = (struct ui_vertex *)cmd_list->VtxBuffer.Data;
|
|
int num_verts = cmd_list->VtxBuffer.size();
|
|
|
|
uint16_t *indices = cmd_list->IdxBuffer.Data;
|
|
int num_indices = cmd_list->IdxBuffer.size();
|
|
|
|
r_begin_ui_surfaces(imgui->r, verts, num_verts, indices, num_indices);
|
|
|
|
int index_offset = 0;
|
|
|
|
for (int j = 0; j < cmd_list->CmdBuffer.size(); ++j) {
|
|
const auto &cmd = cmd_list->CmdBuffer[j];
|
|
|
|
struct ui_surface surf;
|
|
surf.prim_type = PRIM_TRIANGLES;
|
|
surf.texture = (texture_handle_t)(intptr_t)cmd.TextureId;
|
|
surf.src_blend = BLEND_SRC_ALPHA;
|
|
surf.dst_blend = BLEND_ONE_MINUS_SRC_ALPHA;
|
|
surf.scissor = 1;
|
|
surf.scissor_rect[0] = cmd.ClipRect.x;
|
|
surf.scissor_rect[1] = io.DisplaySize.y - cmd.ClipRect.w;
|
|
surf.scissor_rect[2] = cmd.ClipRect.z - cmd.ClipRect.x;
|
|
surf.scissor_rect[3] = cmd.ClipRect.w - cmd.ClipRect.y;
|
|
surf.first_vert = index_offset;
|
|
surf.num_verts = cmd.ElemCount;
|
|
|
|
r_draw_ui_surface(imgui->r, &surf);
|
|
|
|
index_offset += cmd.ElemCount;
|
|
}
|
|
|
|
r_end_ui_surfaces(imgui->r);
|
|
}
|
|
}
|
|
|
|
void imgui_begin_frame(struct imgui *imgui) {
|
|
int64_t now = time_nanoseconds();
|
|
int64_t delta_time = now - imgui->time;
|
|
imgui->time = now;
|
|
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
int width = r_width(imgui->r);
|
|
int height = r_height(imgui->r);
|
|
|
|
io.DeltaTime = delta_time / (float)NS_PER_SEC;
|
|
io.MouseWheel = 0.0;
|
|
io.DisplaySize = ImVec2((float)width, (float)height);
|
|
|
|
/* update navigation inputs */
|
|
io.NavInputs[ImGuiNavInput_PadActivate] = imgui->keys[K_CONT_A];
|
|
io.NavInputs[ImGuiNavInput_PadCancel] = imgui->keys[K_CONT_B];
|
|
io.NavInputs[ImGuiNavInput_PadUp] =
|
|
imgui->keys[K_CONT_DPAD_UP] || imgui->keys[K_CONT_JOYY_NEG];
|
|
io.NavInputs[ImGuiNavInput_PadDown] =
|
|
imgui->keys[K_CONT_DPAD_DOWN] || imgui->keys[K_CONT_JOYY_POS];
|
|
io.NavInputs[ImGuiNavInput_PadLeft] =
|
|
imgui->keys[K_CONT_DPAD_LEFT] || imgui->keys[K_CONT_JOYX_NEG];
|
|
io.NavInputs[ImGuiNavInput_PadRight] =
|
|
imgui->keys[K_CONT_DPAD_RIGHT] || imgui->keys[K_CONT_JOYX_POS];
|
|
|
|
ImGui::NewFrame();
|
|
}
|
|
|
|
int imgui_keydown(struct imgui *imgui, int key, uint16_t value) {
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
if (key == K_MWHEELUP) {
|
|
io.MouseWheel = 1.0f;
|
|
} else if (key == K_MWHEELDOWN) {
|
|
io.MouseWheel = -1.0f;
|
|
} else if (key == K_MOUSE1) {
|
|
io.MouseDown[0] = (bool)value;
|
|
} else if (key == K_MOUSE2) {
|
|
io.MouseDown[1] = (bool)value;
|
|
} else if (key == K_MOUSE3) {
|
|
io.MouseDown[2] = (bool)value;
|
|
} else if (key == K_LALT || key == K_RALT) {
|
|
imgui->alt[key == K_LALT ? 0 : 1] = value;
|
|
io.KeyAlt = imgui->alt[0] || imgui->alt[1];
|
|
} else if (key == K_LCTRL || key == K_RCTRL) {
|
|
imgui->ctrl[key == K_LCTRL ? 0 : 1] = value;
|
|
io.KeyCtrl = imgui->ctrl[0] || imgui->ctrl[1];
|
|
} else if (key == K_LSHIFT || key == K_RSHIFT) {
|
|
imgui->shift[key == K_LSHIFT ? 0 : 1] = value;
|
|
io.KeyShift = imgui->shift[0] || imgui->shift[1];
|
|
} else {
|
|
imgui->keys[key] = value;
|
|
io.KeysDown[key] = (bool)value;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void imgui_mousemove(struct imgui *imgui, int x, int y) {
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
io.MousePos = ImVec2((float)x, (float)y);
|
|
}
|
|
|
|
void imgui_destroy(struct imgui *imgui) {
|
|
ImGui::Shutdown();
|
|
|
|
free(imgui);
|
|
}
|
|
|
|
void imgui_vid_destroyed(struct imgui *imgui) {
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
/* free up cached font data */
|
|
io.Fonts->Clear();
|
|
memset(imgui->fonts, 0, sizeof(imgui->fonts));
|
|
imgui_update_font_tex(imgui);
|
|
|
|
imgui->r = NULL;
|
|
}
|
|
|
|
void imgui_vid_created(struct imgui *imgui, struct render_backend *r) {
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
imgui->r = r;
|
|
|
|
/* register default font */
|
|
io.Fonts->AddFontDefault();
|
|
imgui_update_font_tex(imgui);
|
|
}
|
|
|
|
struct imgui *imgui_create() {
|
|
struct imgui *imgui =
|
|
reinterpret_cast<struct imgui *>(calloc(1, sizeof(struct imgui)));
|
|
|
|
/* initialize imgui */
|
|
ImGuiIO &io = ImGui::GetIO();
|
|
|
|
/* don't save settings */
|
|
io.IniFilename = NULL;
|
|
|
|
/* setup misc callbacks ImGui relies on */
|
|
io.RenderDrawListsFn = nullptr;
|
|
io.SetClipboardTextFn = nullptr;
|
|
io.GetClipboardTextFn = nullptr;
|
|
|
|
g_imgui = imgui;
|
|
|
|
return imgui;
|
|
}
|