Finish basic ui binding to ruby

This commit is contained in:
2026-05-05 11:53:27 +01:00
parent a51eb903c4
commit 1b15f51e1e
10 changed files with 536 additions and 23 deletions
+418 -5
View File
@@ -1,19 +1,432 @@
#include "app/app.h"
using namespace app;
app::App *app_ = nullptr;
struct SceneWrapper {
int id;
};
static void scene_free(mrb_state *mrb, void *ptr) {
int id = ((SceneWrapper *)ptr)->id;
app::Scene *scene = app::scene_pool[id];
if (scene != nullptr)
scene->unused = true;
delete (SceneWrapper *)ptr;
}
static const struct mrb_data_type scene_type = {
"Scene",
scene_free
};
static mrb_value scene_init(mrb_state *mrb, mrb_value self) {
SceneWrapper *s = new SceneWrapper();
app::Scene *loading_scene = new app::Scene{};
s->id = app::scene_pool.acquire(loading_scene);
DATA_PTR(self) = s;
DATA_TYPE(self) = &scene_type;
return self;
}
SceneWrapper *unwrap_scene(mrb_value self) {
return (SceneWrapper *)DATA_PTR(self);
}
struct FontWrapper {
int id;
};
static void font_free(mrb_state *mrb, void *ptr) {
int id = ((FontWrapper *)ptr)->id;
window.unload_font(id);
delete (FontWrapper *)ptr;
}
static const struct mrb_data_type font_type = {
"Font",
font_free
};
static mrb_value font_init(mrb_state *mrb, mrb_value self) {
char *path;
mrb_int size;
mrb_value kwargs;
mrb_get_args(mrb, "zi|H", &path, &size, &kwargs);
bool pixel_art = false, bold = false, italic = false, underline = false;
if (!mrb_nil_p(kwargs)) {
mrb_value p_val = mrb_hash_get(mrb, kwargs, mrb_symbol_value(mrb_intern_cstr(mrb, "pixel_art")));
if (!mrb_nil_p(p_val))
pixel_art = mrb_bool(p_val);
mrb_value bold_val = mrb_hash_get(mrb, kwargs, mrb_symbol_value(mrb_intern_cstr(mrb, "bold")));
if (!mrb_nil_p(bold_val))
bold = mrb_bool(bold_val);
mrb_value italic_val = mrb_hash_get(mrb, kwargs, mrb_symbol_value(mrb_intern_cstr(mrb, "italic")));
if (!mrb_nil_p(italic_val))
italic = mrb_bool(italic_val);
mrb_value underline_val = mrb_hash_get(mrb, kwargs, mrb_symbol_value(mrb_intern_cstr(mrb, "underline")));
if (!mrb_nil_p(underline_val))
underline = mrb_bool(underline_val);
}
FontWrapper *f = new FontWrapper();
f->id = window.load_font(path, size, bold, italic, underline, pixel_art);
DATA_PTR(self) = f;
DATA_TYPE(self) = &font_type;
return self;
}
struct ElementTextWrapper {
int id;
};
static void element_text_free(mrb_state *mrb, void *ptr) {
int id = ((ElementTextWrapper *)ptr)->id;
app::Element *element = app::element_pool[id];
if (element != nullptr)
element->reference_count--;
if (element != nullptr && element->reference_count <= 0) {
delete element;
app::element_pool.release(id);
}
delete (ElementTextWrapper *)ptr;
}
static const struct mrb_data_type element_text_type = {
"ElementText",
element_text_free
};
static mrb_value element_text_init(mrb_state *mrb, mrb_value self) {
mrb_value font_val;
mrb_sym key;
mrb_value kwargs;
mrb_get_args(mrb, "no|H", &key, &font_val, &kwargs);
auto key_id = Localization::key_from_sym(mrb, key);
if (!mrb_obj_is_kind_of(mrb, font_val, mrb_class_get(mrb, "Font"))) {
mrb_raise(mrb, mrb_class_get(Ruby::mrb, "Exception"), "Expected a Font object");
return mrb_nil_value();
}
float x = 0, y = 0;
float z = 0;
bool click_through = false;
if (!mrb_nil_p(kwargs)) {
mrb_value pos_hash = mrb_hash_get(mrb, kwargs, mrb_symbol_value(mrb_intern_cstr(mrb, "position")));
if (!mrb_nil_p(pos_hash)) {
mrb_value x_val = mrb_hash_get(mrb, pos_hash, mrb_symbol_value(mrb_intern_cstr(mrb, "x")));
mrb_value y_val = mrb_hash_get(mrb, pos_hash, mrb_symbol_value(mrb_intern_cstr(mrb, "y")));
if (mrb_fixnum_p(x_val))
x = (float)mrb_fixnum(x_val);
if (mrb_fixnum_p(y_val))
y = (float)mrb_fixnum(y_val);
}
mrb_value z_val = mrb_hash_get(mrb, kwargs, mrb_symbol_value(mrb_intern_cstr(mrb, "z")));
if (mrb_fixnum_p(z_val))
z = (float)mrb_fixnum(z_val);
mrb_value click_through_val = mrb_hash_get(mrb, kwargs, mrb_symbol_value(mrb_intern_cstr(mrb, "click_through")));
if (!mrb_nil_p(click_through_val))
click_through = mrb_bool(click_through_val);
}
Vec2<float> position = {x, y};
FontWrapper *font = (FontWrapper *)DATA_PTR(font_val);
app::EText *element = new app::EText(key_id, font->id, {255, 255, 255, 255});
element->reference_count++;
element->position = position;
element->z = z;
element->click_through = click_through;
ElementTextWrapper *wrapper = new ElementTextWrapper();
wrapper->id = app::element_pool.acquire(element);
DATA_PTR(self) = wrapper;
DATA_TYPE(self) = &element_text_type;
return self;
}
struct ElementRectWrapper {
int id;
};
static void element_rect_free(mrb_state *mrb, void *ptr) {
int id = ((ElementRectWrapper *)ptr)->id;
app::Element *element = app::element_pool[id];
if (element != nullptr)
element->reference_count--;
if (element != nullptr && element->reference_count <= 0) {
delete element;
app::element_pool.release(id);
}
delete (ElementRectWrapper *)ptr;
}
static const struct mrb_data_type element_rect_type = {
"ElementRect",
element_rect_free
};
static mrb_value element_rect_init(mrb_state *mrb, mrb_value self) {
mrb_value shape_val;
mrb_int color;
mrb_value kwargs;
mrb_get_args(mrb, "Hi|H", &shape_val, &color, &kwargs);
float x = 0, y = 0, w = 0, h = 0;
if (!mrb_nil_p(shape_val)) {
mrb_value x_val = mrb_hash_get(mrb, shape_val, mrb_symbol_value(mrb_intern_cstr(mrb, "x")));
mrb_value y_val = mrb_hash_get(mrb, shape_val, mrb_symbol_value(mrb_intern_cstr(mrb, "y")));
mrb_value w_val = mrb_hash_get(mrb, shape_val, mrb_symbol_value(mrb_intern_cstr(mrb, "w")));
mrb_value h_val = mrb_hash_get(mrb, shape_val, mrb_symbol_value(mrb_intern_cstr(mrb, "h")));
if (mrb_fixnum_p(x_val))
x = (float)mrb_fixnum(x_val);
if (mrb_fixnum_p(y_val))
y = (float)mrb_fixnum(y_val);
if (mrb_fixnum_p(w_val))
w = (float)mrb_fixnum(w_val);
if (mrb_fixnum_p(h_val))
h = (float)mrb_fixnum(h_val);
}
float z = 0;
if (!mrb_nil_p(kwargs)) {
mrb_value z_val = mrb_hash_get(mrb, kwargs, mrb_symbol_value(mrb_intern_cstr(mrb, "z")));
if (mrb_fixnum_p(z_val))
z = (float)mrb_fixnum(z_val);
}
Vec2<float> position = {x, y};
Vec2<float> shape = {w, h};
app::ERect *element = new app::ERect(
shape,
{(uint8_t)((color >> 24) & 0xFF),
(uint8_t)((color >> 16) & 0xFF),
(uint8_t)((color >> 8) & 0xFF),
(uint8_t)(color & 0xFF)}
);
element->reference_count++;
element->position = position;
element->z = z;
ElementRectWrapper *wrapper = new ElementRectWrapper();
wrapper->id = app::element_pool.acquire(element);
DATA_PTR(self) = wrapper;
DATA_TYPE(self) = &element_rect_type;
return self;
}
static mrb_value scene_add_element(mrb_state *mrb, mrb_value self) {
mrb_value element_val;
mrb_get_args(mrb, "o", &element_val);
if (!mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementText")) && !mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementRect"))) {
mrb_raise(mrb, mrb_class_get(Ruby::mrb, "Exception"), "Expected an ElementText or ElementRect object");
return mrb_nil_value();
}
SceneWrapper *s = unwrap_scene(self);
app::Scene *scene = app::scene_pool[s->id];
int element_id;
if (mrb_obj_is_kind_of(mrb, element_val, mrb_class_get(mrb, "ElementText"))) {
ElementTextWrapper *wrapper = (ElementTextWrapper *)DATA_PTR(element_val);
element_id = wrapper->id;
} else {
ElementRectWrapper *wrapper = (ElementRectWrapper *)DATA_PTR(element_val);
element_id = wrapper->id;
}
scene->add_element(element_id);
return self;
}
mrb_value app_run(mrb_state *mrb, mrb_value self) {
mrb_int width, height;
char *title = nullptr;
mrb_get_args(mrb, "iiz", &width, &height, &title);
int main() {
int loading_image = window.load_image("assets/images/menu_bg.png", 1.0f, true);
Animation loading_animation = {
.frames = {loading_image},
.frame_rate = 1.0f, // Not animated, so frame rate doesn't matter
.frame_rate = 1.0f,
.loop = false
};
App app({720, 480}, "Mysth Engine", loading_animation);
app_ = new app::App({(float)width, (float)height}, title, loading_animation);
app.loop();
return mrb_nil_value();
}
mrb_value app_start(mrb_state *mrb, mrb_value self) {
mrb_value scene_val;
mrb_get_args(mrb, "o", &scene_val);
if (!mrb_obj_is_kind_of(mrb, scene_val, mrb_class_get(mrb, "Scene"))) {
mrb_raise(mrb, mrb_class_get(Ruby::mrb, "Exception"), "Expected a Scene object");
return mrb_nil_value();
}
SceneWrapper *s = unwrap_scene(scene_val);
app_->switch_to_scene(s->id);
app_->loop();
return mrb_nil_value();
}
mrb_value app_switch_to(mrb_state *mrb, mrb_value self) {
mrb_value scene_val;
mrb_get_args(mrb, "o", &scene_val);
if (!mrb_obj_is_kind_of(mrb, scene_val, mrb_class_get(mrb, "Scene"))) {
mrb_raise(mrb, mrb_class_get(Ruby::mrb, "Exception"), "Expected a Scene object");
return mrb_nil_value();
}
SceneWrapper *s = unwrap_scene(scene_val);
app_->switch_to_scene(s->id);
return mrb_nil_value();
}
static mrb_value app_localize(mrb_state *mrb, mrb_value self) {
mrb_sym lang_sym, key_sym;
char *text;
mrb_get_args(mrb, "nnz", &lang_sym, &key_sym, &text);
auto lang = Localization::lang_from_sym(mrb, lang_sym);
const char *key_name = mrb_sym2name(mrb, key_sym);
auto it = Localization::localization_key_map.find(key_name);
Localization::Key key_id;
if (it == Localization::localization_key_map.end()) {
key_id = (Localization::Key)Localization::localization_key_map.size();
Localization::localization_key_map[key_name] = key_id;
} else {
key_id = it->second;
}
Localization::localized_strings[Localization::join_keys(lang, key_id)] = text;
return mrb_nil_value();
}
int main(int argc, char *argv[]) {
RClass *font_class = mrb_define_class(Ruby::mrb, "Font", Ruby::mrb->object_class);
mrb_define_method(Ruby::mrb, font_class, "initialize", font_init, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1));
RClass *element_text_class = mrb_define_class(Ruby::mrb, "ElementText", Ruby::mrb->object_class);
mrb_define_method(Ruby::mrb, element_text_class, "initialize", element_text_init, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1));
mrb_define_method(Ruby::mrb, element_text_class, "on_click", [](mrb_state *mrb, mrb_value self) {
mrb_value block;
mrb_get_args(mrb, "&", &block);
ElementTextWrapper *wrapper = (ElementTextWrapper *)DATA_PTR(self);
app::Element *element = app::element_pool[wrapper->id];
element->on_click.set_proc(block);
return self; }, MRB_ARGS_BLOCK());
RClass *element_rect_class = mrb_define_class(Ruby::mrb, "ElementRect", Ruby::mrb->object_class);
mrb_define_method(Ruby::mrb, element_rect_class, "initialize", element_rect_init, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1));
mrb_define_method(Ruby::mrb, element_rect_class, "on_click", [](mrb_state *mrb, mrb_value self) {
mrb_value block;
mrb_get_args(mrb, "&", &block);
ElementRectWrapper *wrapper = (ElementRectWrapper *)DATA_PTR(self);
app::Element *element = app::element_pool[wrapper->id];
element->on_click.set_proc(block);
return self; }, MRB_ARGS_BLOCK());
mrb_define_method(Ruby::mrb, element_rect_class, "color=", [](mrb_state *mrb, mrb_value self) {
mrb_value color_val;
mrb_get_args(mrb, "o", &color_val);
ElementRectWrapper *wrapper = (ElementRectWrapper *)DATA_PTR(self);
app::ERect *element = (app::ERect *)app::element_pool[wrapper->id];
element->color = {
(uint8_t)((mrb_fixnum(color_val) >> 24) & 0xFF),
(uint8_t)((mrb_fixnum(color_val) >> 16) & 0xFF),
(uint8_t)((mrb_fixnum(color_val) >> 8) & 0xFF),
(uint8_t)(mrb_fixnum(color_val) & 0xFF)
};
return self; }, MRB_ARGS_REQ(1));
mrb_define_method(Ruby::mrb, element_rect_class, "color", [](mrb_state *mrb, mrb_value self) {
ElementRectWrapper *wrapper = (ElementRectWrapper *)DATA_PTR(self);
app::ERect *element = (app::ERect *)app::element_pool[wrapper->id];
uint32_t color = ((uint32_t)element->color.r << 24) |
((uint32_t)element->color.g << 16) |
((uint32_t)element->color.b << 8) |
(uint32_t)element->color.a;
return mrb_fixnum_value(color); }, MRB_ARGS_NONE());
RClass *scene_class = mrb_define_class(Ruby::mrb, "Scene", Ruby::mrb->object_class);
mrb_define_method(Ruby::mrb, scene_class, "initialize", scene_init, MRB_ARGS_NONE());
mrb_define_method(Ruby::mrb, scene_class, "<<", scene_add_element, MRB_ARGS_REQ(1));
RClass *app_module = mrb_define_module(Ruby::mrb, "App");
mrb_define_class_method(Ruby::mrb, app_module, "run", app_run, MRB_ARGS_REQ(3));
mrb_define_class_method(Ruby::mrb, app_module, "start", app_start, MRB_ARGS_REQ(1));
mrb_define_class_method(Ruby::mrb, app_module, "switch_to", app_switch_to, MRB_ARGS_REQ(1));
mrb_define_class_method(Ruby::mrb, app_module, "localize", app_localize, MRB_ARGS_REQ(3));
mrb_define_class_method(Ruby::mrb, app_module, "language=", [](mrb_state *mrb, mrb_value self) {
mrb_sym lang_sym;
mrb_get_args(mrb, "n", &lang_sym);
Localization::current_language = Localization::lang_from_sym(mrb, lang_sym);
return mrb_nil_value(); }, MRB_ARGS_REQ(1));
char *startup_script = nullptr;
if (argc > 1)
startup_script = argv[1];
if (startup_script) {
if (FILE *file = fopen(startup_script, "r")) {
fseek(file, 0, SEEK_END);
size_t size = ftell(file);
fseek(file, 0, SEEK_SET);
char *script_content = (char *)malloc(size + 1);
fread(script_content, 1, size, file);
script_content[size] = '\0';
fclose(file);
Ruby::run_string(script_content, size);
free(script_content);
if (Ruby::mrb->exc) {
mrb_print_error(Ruby::mrb);
Ruby::mrb->exc = 0;
}
printf("Ran: %s\n", startup_script);
} else {
fprintf(stderr, "Failed to open startup script: %s\n", startup_script);
return 1;
}
}
return 0;
}