From 1b15f51e1e44afe590370cfe9d8417aa8e339c80 Mon Sep 17 00:00:00 2001 From: Daanish Date: Tue, 5 May 2026 11:53:27 +0100 Subject: [PATCH] Finish basic ui binding to ruby --- include/app/app.h | 48 +++- include/binding/ruby.h | 16 ++ include/localization/localization.h | 43 ++- include/pch.h | 3 + include/utils.h | 2 +- samples/button.rb | 20 +- samples/main.rb | 2 +- src/main.cc | 423 +++++++++++++++++++++++++++- src/window/window.cc | 1 + tst.rb | 1 + 10 files changed, 536 insertions(+), 23 deletions(-) create mode 100644 tst.rb diff --git a/include/app/app.h b/include/app/app.h index 6cc24b1..be7c768 100644 --- a/include/app/app.h +++ b/include/app/app.h @@ -8,6 +8,7 @@ namespace app { struct Element { + int reference_count = 0; Vec2 position = {0, 0}; float rotation = 0; Vec2 scale = {1, 1}; @@ -51,11 +52,10 @@ struct EImage : Element { struct EText : Element { Localization::Key key; int font_id; - int font_size = 16; Color color = {255, 255, 255, 255}; - EText(Localization::Key key, int font_id, int font_size, Color color) - : key(key), font_id(font_id), font_size(font_size), color(color) {} + EText(Localization::Key key, int font_id, Color color) + : key(key), font_id(font_id), color(color) {} Vec2 size(Window *window) override { return window->get_text_size(text_id); @@ -96,9 +96,20 @@ struct ERect : Element { inline Pool element_pool; struct Scene { + bool unused = false; std::vector element_ids; Ruby::Block update; + ~Scene() { + for (const int &id : element_ids) { + element_pool[id]->reference_count--; + if (element_pool[id]->reference_count <= 0) { + delete element_pool[id]; + element_pool.release(id); + } + } + } + void add_element(int element_id) { auto it = std::lower_bound( element_ids.begin(), @@ -108,9 +119,22 @@ struct Scene { return element_pool[a]->z < element_pool[b]->z || (element_pool[a]->z == element_pool[b]->z && a < b); } ); + element_pool[element_id]->reference_count++; element_ids.insert(it, element_id); } + void delete_element(int element_id) { + auto it = std::find(element_ids.begin(), element_ids.end(), element_id); + if (it != element_ids.end()) { + element_pool[element_id]->reference_count--; + if (element_pool[element_id]->reference_count <= 0) { + delete element_pool[element_id]; + element_pool.release(element_id); + } + element_ids.erase(it); + } + } + void update_scene(uint64_t delta_time) { if (!mrb_nil_p(update.proc)) { int x = mrb_gc_arena_save(Ruby::mrb); @@ -147,6 +171,21 @@ struct App { int current_scene_id = 0; bool running = true; + void switch_to_scene(int scene_id) { + int scene_pool_size = (int)scene_pool.all().size(); + for (int i = 0; i < scene_pool_size; ++i) { + auto &val = scene_pool[i]; + if (i == scene_id && val != nullptr && val->unused) + val->unused = false; + if (val != nullptr && val->unused) { + delete val; + scene_pool.release(i); + } + } + + current_scene_id = scene_id; + } + App(Vec2 size, const char *title, Animation loading_animation) { window.update(size, title); #warning "Should try to center the loading image or scale it to fit the screen" @@ -154,7 +193,7 @@ struct App { EImage *image = new EImage(loading_animation); int image_id = element_pool.acquire(image); loading_scene->add_element(image_id); - scene_pool.acquire(loading_scene); + current_scene_id = scene_pool.acquire(loading_scene); } void loop() { @@ -183,7 +222,6 @@ struct App { break; case Event::MOUSE_BUTTON_DOWN: scene->click(&window, e.mouse_button.position); - printf("DT: %lu ms\n", dt); break; default: break; diff --git a/include/binding/ruby.h b/include/binding/ruby.h index a3c8793..44f0a9e 100644 --- a/include/binding/ruby.h +++ b/include/binding/ruby.h @@ -12,10 +12,20 @@ struct Block { Block() : proc(mrb_nil_value()) {} + void set_proc(mrb_value new_proc) { + if (!mrb_nil_p(proc)) + mrb_gc_unregister(mrb, proc); + proc = new_proc; + if (!mrb_nil_p(proc)) + mrb_gc_register(mrb, proc); + } + Block(const Block &) = delete; Block &operator=(const Block &) = delete; mrb_value call(int argc = 0, mrb_value *argv = nullptr) const { + if (mrb_nil_p(proc)) + return mrb_nil_value(); return mrb_funcall(mrb, proc, "call", argc, argv); } @@ -23,4 +33,10 @@ struct Block { mrb_gc_unregister(mrb, proc); } }; + +inline void run_string(const char *code, size_t length) { + int ai = mrb_gc_arena_save(mrb); + mrb_load_nstring(mrb, code, length); + mrb_gc_arena_restore(mrb, ai); +} }; // namespace Ruby \ No newline at end of file diff --git a/include/localization/localization.h b/include/localization/localization.h index bc39898..9d015ff 100644 --- a/include/localization/localization.h +++ b/include/localization/localization.h @@ -5,11 +5,11 @@ namespace Localization { using Key = uint16_t; // Maps localization keys to their string identifiers. This is used for text elements to look up the correct localized string. -extern std::unordered_map localization_key_map; +inline std::unordered_map localization_key_map; using LanguageCode = uint32_t; // 32-bit integer to store the language code (e.g., "en", "fr", "es") -extern LanguageCode current_language; // The currently active language code +inline LanguageCode current_language = 'e' << 24 | 'n' << 16; using JoinedKey = uint64_t; // 64-bit integer to store the joined language code and key @@ -17,12 +17,47 @@ inline JoinedKey join_keys(LanguageCode lang_code, Key loc_key) { return (static_cast(lang_code) << 16) | loc_key; } -extern std::unordered_map localized_strings; +inline std::unordered_map localized_strings; inline std::string get(Key key) { auto it = localized_strings.find(join_keys(current_language, key)); if (it != localized_strings.end()) return it->second; - return ""; + + for (const auto &[joined, value] : localized_strings) { + Key k = (Key)(joined & 0xFFFF); + if (k == key) + return value; + } + + return "Localization not set!"; +} + +inline LanguageCode lang_from_sym(mrb_state *mrb, mrb_sym sym) { + const char *s = mrb_sym2name(mrb, sym); + size_t len = strlen(s); + + if (len == 0 || len > 4) + return 0; + + LanguageCode code = 0; + + for (size_t i = 0; i < len; ++i) { + code |= (static_cast(s[i]) << (8 * (3 - i))); + } + + return code; +} + +inline Key key_from_sym(mrb_state *mrb, mrb_sym sym) { + const char *key_name = mrb_sym2name(mrb, sym); + + auto it = localization_key_map.find(key_name); + if (it != localization_key_map.end()) + return it->second; + + Key new_id = (Key)localization_key_map.size(); + localization_key_map[key_name] = new_id; + return new_id; } } // namespace Localization \ No newline at end of file diff --git a/include/pch.h b/include/pch.h index 84842b8..956330b 100644 --- a/include/pch.h +++ b/include/pch.h @@ -4,9 +4,12 @@ #include "mruby.h" #include "mruby/array.h" #include "mruby/compile.h" +#include "mruby/data.h" #include "mruby/hash.h" #include "mruby/irep.h" #include "mruby/string.h" +#include "mruby/value.h" +#include "mruby/variable.h" #include #include diff --git a/include/utils.h b/include/utils.h index 269d71b..7489229 100644 --- a/include/utils.h +++ b/include/utils.h @@ -7,7 +7,7 @@ #define MAX(x, y) ((x) > (y) ? (x) : (y)) struct Color { - uint8_t r, g, b, a; + uint8_t r, g, b = 0, a = 255; }; template diff --git a/samples/button.rb b/samples/button.rb index 14fe9f9..40c8774 100644 --- a/samples/button.rb +++ b/samples/button.rb @@ -1,25 +1,31 @@ -App.run 400, 400, title: "Button Test" +App.run 400, 400, "Button Test" main_scene = Scene.new +default_font = Font.new "assets/fonts/charybdis.ttf", 24, pixel_art: true -default_font = Font.new "assets/DejaVuSans.ttf", 24, italic: true +App.language = :en +App.localize(:en, :btn_text, "Click me!") -text = ElementText.new "Click me!", font: default_font, position: {x: 50, y: 50}, z: 1, click_through: true +text = ElementText.new :btn_text, default_font, position: {x: 50, y: 50}, z: 1, click_through: true -rect = ElementRect.new {x: 50, y: 50, w: 100, h: 25}, color: 0xFF0000, z: 0 +rect = ElementRect.new({x: 50, y: 50, w: 100, h: 25}, 0xFF0000FF, z: 0) rect.on_click do - rect.color = rect.color == 0xFF0000 ? - 0x00FF00 + rect.color = rect.color == 0xFF0000FF ? + 0x00FF00FF : - 0xFF0000 + 0xFF0000FF end +=begin main_scene.update do |dt| puts "Delta time: #{dt}ms" end +=end main_scene << text main_scene << rect +# main_scene.delete(text) # Uncomment to test element deletion + App.start main_scene \ No newline at end of file diff --git a/samples/main.rb b/samples/main.rb index 6f36a55..93f5f4f 100644 --- a/samples/main.rb +++ b/samples/main.rb @@ -24,7 +24,7 @@ start_button = ElementText.new "Start Game", font: default_font, position: {x: 1 game_scene = Scene.new start_button.on_click do - App.start game_scene + App.switch_to game_scene end menu_scene << start_button diff --git a/src/main.cc b/src/main.cc index 144ab98..b0ff34b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -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 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 position = {x, y}; + Vec2 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; } \ No newline at end of file diff --git a/src/window/window.cc b/src/window/window.cc index 720dfd8..0e08a51 100644 --- a/src/window/window.cc +++ b/src/window/window.cc @@ -27,6 +27,7 @@ Window::Window(Vec2 size, const char *title) { uint32_t pixel = 0xFFFFFFFF; SDL_UpdateTexture(white_tex, nullptr, &pixel, sizeof(pixel)); + SDL_SetTextureBlendMode(white_tex, SDL_BLENDMODE_BLEND); }; void Window::update(Vec2 new_size, const char *new_title) { diff --git a/tst.rb b/tst.rb new file mode 100644 index 0000000..bbad01b --- /dev/null +++ b/tst.rb @@ -0,0 +1 @@ +puts "hello"