diff --git a/ReadMe.mdx b/ReadMe.md similarity index 68% rename from ReadMe.mdx rename to ReadMe.md index e3d885049..6d27dfb77 100644 --- a/ReadMe.mdx +++ b/ReadMe.md @@ -1,14 +1,15 @@ -# @alloyscript/runtime +# AlloyScript Engine -The AlloyScript runtime is a high-performance, secure JavaScript environment built for WebView applications. It uses **Bun** for development and bundling, and a **C host program** for native capabilities. +The AlloyScript runtime is a high-performance, secure JavaScript environment built using WebView as streamlined cross-platform JS runtime for desktop applications. It uses **Bun** for development and bundling, and a **C host program** for native capabilities. ## Architecture 1. **TypeScript Library**: Provides typed APIs for SQLite, Spawn, and SecureEval. -2. **C Host Program**: A native wrapper that initializes a WebView window and exposes a bridge to the JS context. +2. **C Host Program**: A native wrapper that initialises a [WebView](docs/webview.md) window and exposes a bridge to the JS context. 3. **Bridge**: Communication between JS and C via `window.Alloy`. 4. **Secure Evaluation**: `window.eval` is replaced with `secureEval` which runs [MicroQuickJS](https://github.com/bellard/mquickjs) within an OCI-compatible, chainguarded containerized Linux kernel for ultimate isolation. 5. **SQLite Driver**: A high-performance driver with transactions, prepared statement caching, and `bigint` support. +6. **Native GUI Framework (`alloy:gui`)**: A declarative component framework (ASX) that wraps native OS controls (Win32/Cocoa/GTK) using the Yoga layout engine. ## Security diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 5c2083c73..fbbeced24 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -20,7 +20,58 @@ endif() if(WEBVIEW_BUILD_SHARED_LIBRARY) add_library(webview_core_shared SHARED) add_library(webview::core_shared ALIAS webview_core_shared) - target_sources(webview_core_shared PRIVATE src/webview.cc) + target_sources(webview_core_shared PRIVATE + src/webview.cc + src/alloy_gui.cpp + src/signals.cpp + src/compiler.cpp + src/components/accordion.cpp + src/components/badge.cpp + src/components/button.cpp + src/components/card.cpp + src/components/checkbox.cpp + src/components/chip.cpp + src/components/codeeditor.cpp + src/components/colorpicker.cpp + src/components/combobox.cpp + src/components/contextmenu.cpp + src/components/datepicker.cpp + src/components/dialog.cpp + src/components/divider.cpp + src/components/filedialog.cpp + src/components/groupbox.cpp + src/components/hstack.cpp + src/components/icon.cpp + src/components/image.cpp + src/components/label.cpp + src/components/link.cpp + src/components/listview.cpp + src/components/menu.cpp + src/components/menubar.cpp + src/components/popover.cpp + src/components/progressbar.cpp + src/components/radiobutton.cpp + src/components/rating.cpp + src/components/richtext.cpp + src/components/scrollview.cpp + src/components/separator.cpp + src/components/slider.cpp + src/components/spinner.cpp + src/components/spinner_loading.cpp + src/components/splitter.cpp + src/components/statusbar.cpp + src/components/switch.cpp + src/components/tabview.cpp + src/components/textarea.cpp + src/components/textfield.cpp + src/components/timepicker.cpp + src/components/toolbar.cpp + src/components/tooltip.cpp + src/components/treeview.cpp + src/components/vstack.cpp + src/components/webview.cpp + src/components/window.cpp + ) target_link_libraries(webview_core_shared PUBLIC webview_core_headers) set_target_properties(webview_core_shared PROPERTIES OUTPUT_NAME webview @@ -43,7 +94,58 @@ if(WEBVIEW_BUILD_STATIC_LIBRARY) add_library(webview_core_static STATIC) add_library(webview::core_static ALIAS webview_core_static) - target_sources(webview_core_static PRIVATE src/webview.cc) + target_sources(webview_core_static PRIVATE + src/webview.cc + src/alloy_gui.cpp + src/signals.cpp + src/compiler.cpp + src/components/accordion.cpp + src/components/badge.cpp + src/components/button.cpp + src/components/card.cpp + src/components/checkbox.cpp + src/components/chip.cpp + src/components/codeeditor.cpp + src/components/colorpicker.cpp + src/components/combobox.cpp + src/components/contextmenu.cpp + src/components/datepicker.cpp + src/components/dialog.cpp + src/components/divider.cpp + src/components/filedialog.cpp + src/components/groupbox.cpp + src/components/hstack.cpp + src/components/icon.cpp + src/components/image.cpp + src/components/label.cpp + src/components/link.cpp + src/components/listview.cpp + src/components/menu.cpp + src/components/menubar.cpp + src/components/popover.cpp + src/components/progressbar.cpp + src/components/radiobutton.cpp + src/components/rating.cpp + src/components/richtext.cpp + src/components/scrollview.cpp + src/components/separator.cpp + src/components/slider.cpp + src/components/spinner.cpp + src/components/spinner_loading.cpp + src/components/splitter.cpp + src/components/statusbar.cpp + src/components/switch.cpp + src/components/tabview.cpp + src/components/textarea.cpp + src/components/textfield.cpp + src/components/timepicker.cpp + src/components/toolbar.cpp + src/components/tooltip.cpp + src/components/treeview.cpp + src/components/vstack.cpp + src/components/webview.cpp + src/components/window.cpp + ) target_link_libraries(webview_core_static PUBLIC webview_core_headers) set_target_properties(webview_core_static PROPERTIES OUTPUT_NAME "${STATIC_LIBRARY_OUTPUT_NAME}" diff --git a/core/include/alloy/api.h b/core/include/alloy/api.h new file mode 100644 index 000000000..db1c65316 --- /dev/null +++ b/core/include/alloy/api.h @@ -0,0 +1,186 @@ +#ifndef ALLOY_API_H +#define ALLOY_API_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#define ALLOY_API __declspec(dllexport) +#else +#define ALLOY_API __attribute__((visibility("default"))) +#endif + +typedef void *alloy_component_t; +typedef void *alloy_signal_t; +typedef void *alloy_computed_t; +typedef void *alloy_effect_t; + +typedef enum { + ALLOY_OK = 0, + ALLOY_ERROR_INVALID_ARGUMENT, + ALLOY_ERROR_INVALID_STATE, + ALLOY_ERROR_PLATFORM, + ALLOY_ERROR_BUFFER_TOO_SMALL, + ALLOY_ERROR_NOT_SUPPORTED, +} alloy_error_t; + +typedef enum { + ALLOY_EVENT_CLICK = 0, + ALLOY_EVENT_CHANGE, + ALLOY_EVENT_CLOSE, + ALLOY_EVENT_FOCUS, + ALLOY_EVENT_BLUR, +} alloy_event_type_t; + +typedef enum { + ALLOY_PROP_TEXT = 0, + ALLOY_PROP_CHECKED, + ALLOY_PROP_VALUE, + ALLOY_PROP_ENABLED, + ALLOY_PROP_VISIBLE, + ALLOY_PROP_LABEL, +} alloy_prop_id_t; + +typedef void (*alloy_event_cb_t)(alloy_component_t handle, + alloy_event_type_t event, + void *userdata); + +typedef struct { + unsigned int background; + unsigned int foreground; + float font_size; + const char *font_family; + float border_radius; + float opacity; +} alloy_style_t; + +ALLOY_API const char *alloy_error_message(alloy_error_t err); + +ALLOY_API alloy_signal_t alloy_signal_create_str(const char *initial); +ALLOY_API alloy_signal_t alloy_signal_create_double(double initial); +ALLOY_API alloy_signal_t alloy_signal_create_int(int initial); +ALLOY_API alloy_signal_t alloy_signal_create_bool(int initial); + +ALLOY_API alloy_error_t alloy_signal_set_str(alloy_signal_t s, const char *v); +ALLOY_API alloy_error_t alloy_signal_set_double(alloy_signal_t s, double v); +ALLOY_API alloy_error_t alloy_signal_set_int(alloy_signal_t s, int v); +ALLOY_API alloy_error_t alloy_signal_set_bool(alloy_signal_t s, int v); + +ALLOY_API const char *alloy_signal_get_str(alloy_signal_t s); +ALLOY_API double alloy_signal_get_double(alloy_signal_t s); +ALLOY_API int alloy_signal_get_int(alloy_signal_t s); +ALLOY_API int alloy_signal_get_bool(alloy_signal_t s); + +ALLOY_API alloy_computed_t alloy_computed_create( + alloy_signal_t *deps, size_t dep_count, + void (*compute)(alloy_signal_t *deps, size_t dep_count, void *out, void *userdata), + void *userdata); + +ALLOY_API alloy_effect_t alloy_effect_create( + alloy_signal_t *deps, size_t dep_count, + void (*run)(void *userdata), void *userdata); + +ALLOY_API alloy_error_t alloy_signal_destroy(alloy_signal_t s); +ALLOY_API alloy_error_t alloy_computed_destroy(alloy_computed_t c); +ALLOY_API alloy_error_t alloy_effect_destroy(alloy_effect_t e); + +ALLOY_API alloy_error_t alloy_bind_property(alloy_component_t component, + alloy_prop_id_t property, + alloy_signal_t signal); +ALLOY_API alloy_error_t alloy_unbind_property(alloy_component_t component, + alloy_prop_id_t property); + +ALLOY_API alloy_component_t alloy_create_window(const char *title, + int width, int height); +ALLOY_API alloy_component_t alloy_create_button(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_textfield(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_textarea(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_label(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_checkbox(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_radiobutton(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_combobox(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_slider(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_spinner(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_switch(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_progressbar(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_tabview(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_listview(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_treeview(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_webview(alloy_component_t parent); +ALLOY_API alloy_error_t alloy_webview_bind_global(alloy_component_t webview, + const char *name, + void (*callback)(const char *json_args, void *userdata), + void *userdata); +ALLOY_API alloy_error_t alloy_webview_secure_post(alloy_component_t webview, + const char *encrypted_msg); + +ALLOY_API alloy_error_t alloy_build_bytecode(const char *source, + unsigned char **out_bytecode, + size_t *out_len); + +ALLOY_API alloy_error_t alloy_decompile_bytecode(const unsigned char *bytecode, + size_t len, + char **out_js); + +typedef void *alloy_transpiler_t; + +ALLOY_API alloy_transpiler_t alloy_transpiler_create(const char *options_json); +ALLOY_API alloy_error_t alloy_transpiler_transform(alloy_transpiler_t t, + const char *code, + const char *loader, + char **out_result); +ALLOY_API alloy_error_t alloy_transpiler_scan(alloy_transpiler_t t, + const char *code, + char **out_json_result); +ALLOY_API void alloy_transpiler_destroy(alloy_transpiler_t t); +ALLOY_API alloy_component_t alloy_create_vstack(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_hstack(alloy_component_t parent); +ALLOY_API alloy_component_t alloy_create_scrollview(alloy_component_t parent); + +ALLOY_API alloy_error_t alloy_destroy(alloy_component_t handle); + +ALLOY_API alloy_error_t alloy_set_text(alloy_component_t h, const char *text); +ALLOY_API alloy_error_t alloy_get_text(alloy_component_t h, + char *buf, size_t buf_len); +ALLOY_API alloy_error_t alloy_set_checked(alloy_component_t h, int checked); +ALLOY_API int alloy_get_checked(alloy_component_t h); +ALLOY_API alloy_error_t alloy_set_value(alloy_component_t h, double value); +ALLOY_API double alloy_get_value(alloy_component_t h); +ALLOY_API alloy_error_t alloy_set_enabled(alloy_component_t h, int enabled); +ALLOY_API int alloy_get_enabled(alloy_component_t h); +ALLOY_API alloy_error_t alloy_set_visible(alloy_component_t h, int visible); +ALLOY_API int alloy_get_visible(alloy_component_t h); +ALLOY_API alloy_error_t alloy_set_style(alloy_component_t h, + const alloy_style_t *style); + +ALLOY_API alloy_error_t alloy_add_child(alloy_component_t container, + alloy_component_t child); +ALLOY_API alloy_error_t alloy_set_flex(alloy_component_t h, float flex); +ALLOY_API alloy_error_t alloy_set_padding(alloy_component_t h, + float top, float right, + float bottom, float left); +ALLOY_API alloy_error_t alloy_set_margin(alloy_component_t h, + float top, float right, + float bottom, float left); +ALLOY_API alloy_error_t alloy_set_width(alloy_component_t h, float width); +ALLOY_API alloy_error_t alloy_set_height(alloy_component_t h, float height); +ALLOY_API alloy_error_t alloy_layout(alloy_component_t window); + +ALLOY_API alloy_error_t alloy_set_event_callback(alloy_component_t handle, + alloy_event_type_t event, + alloy_event_cb_t callback, + void *userdata); + +ALLOY_API alloy_error_t alloy_run(alloy_component_t window); +ALLOY_API alloy_error_t alloy_terminate(alloy_component_t window); +ALLOY_API alloy_error_t alloy_dispatch(alloy_component_t window, + void (*fn)(void *arg), void *arg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/include/alloy/detail/backends.hh b/core/include/alloy/detail/backends.hh new file mode 100644 index 000000000..c4d09ddf2 --- /dev/null +++ b/core/include/alloy/detail/backends.hh @@ -0,0 +1,12 @@ +#ifndef ALLOY_BACKENDS_HH +#define ALLOY_BACKENDS_HH + +#if defined(ALLOY_PLATFORM_WINDOWS) +#include "backends/win32_gui.hh" +#elif defined(ALLOY_PLATFORM_DARWIN) +#include "backends/cocoa_gui.hh" +#elif defined(ALLOY_PLATFORM_LINUX) +#include "backends/gtk_gui.hh" +#endif + +#endif diff --git a/core/include/alloy/detail/backends/cocoa_gui.hh b/core/include/alloy/detail/backends/cocoa_gui.hh new file mode 100644 index 000000000..ceacc9e8c --- /dev/null +++ b/core/include/alloy/detail/backends/cocoa_gui.hh @@ -0,0 +1,80 @@ +#ifndef ALLOY_COCOA_GUI_HH +#define ALLOY_COCOA_GUI_HH + +#include "../component_base.hh" +#include +#include + +namespace alloy::detail { + +class cocoa_component : public component_base { +public: + cocoa_component(id view, bool is_container = false) + : component_base(is_container), m_view(view) {} + + virtual ~cocoa_component() { + if (m_view) { + ((void (*)(id, SEL))objc_msgSend)(m_view, sel_registerName("removeFromSuperview")); + ((void (*)(id, SEL))objc_msgSend)(m_view, sel_registerName("release")); + } + } + + alloy_error_t set_text(std::string_view text) override { + id str = ((id (*)(id, SEL, const char*))objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text.data()); + ((void (*)(id, SEL, id))objc_msgSend)(m_view, sel_registerName("setTitle:"), str); + return ALLOY_OK; + } + + alloy_error_t get_text(char *buf, size_t len) override { + return ALLOY_ERROR_NOT_SUPPORTED; + } + + alloy_error_t set_checked(bool v) override { + ((void (*)(id, SEL, long))objc_msgSend)(m_view, sel_registerName("setState:"), v ? 1 : 0); + return ALLOY_OK; + } + + bool get_checked() override { + return ((long (*)(id, SEL))objc_msgSend)(m_view, sel_registerName("state")) == 1; + } + + alloy_error_t set_value(double v) override { + ((void (*)(id, SEL, double))objc_msgSend)(m_view, sel_registerName("setDoubleValue:"), v); + return ALLOY_OK; + } + + double get_value() override { + return ((double (*)(id, SEL))objc_msgSend)(m_view, sel_registerName("doubleValue")); + } + + alloy_error_t set_enabled(bool v) override { + ((void (*)(id, SEL, BOOL))objc_msgSend)(m_view, sel_registerName("setEnabled:"), v ? YES : NO); + return ALLOY_OK; + } + + bool get_enabled() override { + return ((BOOL (*)(id, SEL))objc_msgSend)(m_view, sel_registerName("isEnabled")); + } + + alloy_error_t set_visible(bool v) override { + ((void (*)(id, SEL, BOOL))objc_msgSend)(m_view, sel_registerName("setHidden:"), v ? NO : YES); + return ALLOY_OK; + } + + bool get_visible() override { + return !((BOOL (*)(id, SEL))objc_msgSend)(m_view, sel_registerName("isHidden")); + } + + alloy_error_t set_style(const alloy_style_t &s) override { + return ALLOY_OK; + } + + void *native_handle() override { return m_view; } + +protected: + id m_view; +}; + +} + +#endif diff --git a/core/include/alloy/detail/backends/gtk_gui.hh b/core/include/alloy/detail/backends/gtk_gui.hh new file mode 100644 index 000000000..b6b5633c7 --- /dev/null +++ b/core/include/alloy/detail/backends/gtk_gui.hh @@ -0,0 +1,85 @@ +#ifndef ALLOY_GTK_GUI_HH +#define ALLOY_GTK_GUI_HH + +#include "../component_base.hh" +#include + +namespace alloy::detail { + +class gtk_component : public component_base { +public: + gtk_component(GtkWidget* widget, bool is_container = false) + : component_base(is_container), m_widget(widget) {} + + virtual ~gtk_component() { + if (m_widget) gtk_widget_destroy(m_widget); + } + + alloy_error_t set_text(std::string_view text) override { + if (GTK_IS_BUTTON(m_widget)) { + gtk_button_set_label(GTK_BUTTON(m_widget), text.data()); + } else if (GTK_IS_ENTRY(m_widget)) { + gtk_entry_set_text(GTK_ENTRY(m_widget), text.data()); + } else if (GTK_IS_LABEL(m_widget)) { + gtk_label_set_text(GTK_LABEL(m_widget), text.data()); + } + return ALLOY_OK; + } + + alloy_error_t get_text(char *buf, size_t len) override { + return ALLOY_ERROR_NOT_SUPPORTED; + } + + alloy_error_t set_checked(bool v) override { + if (GTK_IS_TOGGLE_BUTTON(m_widget)) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_widget), v); + } + return ALLOY_OK; + } + + bool get_checked() override { + return GTK_IS_TOGGLE_BUTTON(m_widget) && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_widget)); + } + + alloy_error_t set_value(double v) override { + if (GTK_IS_RANGE(m_widget)) { + gtk_range_set_value(GTK_RANGE(m_widget), v); + } + return ALLOY_OK; + } + + double get_value() override { + return GTK_IS_RANGE(m_widget) ? gtk_range_get_value(GTK_RANGE(m_widget)) : 0.0; + } + + alloy_error_t set_enabled(bool v) override { + gtk_widget_set_sensitive(m_widget, v); + return ALLOY_OK; + } + + bool get_enabled() override { + return gtk_widget_is_sensitive(m_widget); + } + + alloy_error_t set_visible(bool v) override { + if (v) gtk_widget_show(m_widget); else gtk_widget_hide(m_widget); + return ALLOY_OK; + } + + bool get_visible() override { + return gtk_widget_get_visible(m_widget); + } + + alloy_error_t set_style(const alloy_style_t &s) override { + return ALLOY_OK; + } + + void *native_handle() override { return m_widget; } + +protected: + GtkWidget* m_widget; +}; + +} + +#endif diff --git a/core/include/alloy/detail/backends/win32_gui.hh b/core/include/alloy/detail/backends/win32_gui.hh new file mode 100644 index 000000000..e1b579fcc --- /dev/null +++ b/core/include/alloy/detail/backends/win32_gui.hh @@ -0,0 +1,86 @@ +#ifndef ALLOY_WIN32_GUI_HH +#define ALLOY_WIN32_GUI_HH + +#include "../component_base.hh" +#include +#include + +namespace alloy::detail { + +class win32_component : public component_base { +public: + win32_component(HWND hwnd, bool is_container = false) + : component_base(is_container), m_hwnd(hwnd) { + if (m_hwnd) { + SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)this); + } + } + + virtual ~win32_component() { + if (m_hwnd) { + SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0); + DestroyWindow(m_hwnd); + } + } + + alloy_error_t set_text(std::string_view text) override { + SetWindowTextA(m_hwnd, text.data()); + return ALLOY_OK; + } + + alloy_error_t get_text(char *buf, size_t len) override { + GetWindowTextA(m_hwnd, buf, (int)len); + return ALLOY_OK; + } + + alloy_error_t set_checked(bool v) override { + SendMessage(m_hwnd, BM_SETCHECK, v ? BST_CHECKED : BST_UNCHECKED, 0); + return ALLOY_OK; + } + + bool get_checked() override { + return SendMessage(m_hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED; + } + + alloy_error_t set_value(double v) override { + // Implementation for ProgressBar/Slider + return ALLOY_OK; + } + + double get_value() override { + return 0.0; + } + + alloy_error_t set_enabled(bool v) override { + EnableWindow(m_hwnd, v); + return ALLOY_OK; + } + + bool get_enabled() override { + return IsWindowEnabled(m_hwnd); + } + + alloy_error_t set_visible(bool v) override { + ShowWindow(m_hwnd, v ? SW_SHOW : SW_HIDE); + return ALLOY_OK; + } + + bool get_visible() override { + return IsWindowVisible(m_hwnd); + } + + alloy_error_t set_style(const alloy_style_t &s) override { + // Apply styling + return ALLOY_OK; + } + + void *native_handle() override { return m_hwnd; } + +protected: + HWND m_hwnd; +}; + +// Specific component implementations (Window, Button, etc.) would follow +} + +#endif diff --git a/core/include/alloy/detail/component_base.hh b/core/include/alloy/detail/component_base.hh new file mode 100644 index 000000000..02c2b4af4 --- /dev/null +++ b/core/include/alloy/detail/component_base.hh @@ -0,0 +1,82 @@ +#ifndef ALLOY_COMPONENT_BASE_HH +#define ALLOY_COMPONENT_BASE_HH + +#include "../api.h" +#include +#include +#include + +namespace alloy::detail { + +struct event_slot { + alloy_event_cb_t fn{}; + void *userdata{}; +}; + +class component_base { +public: + virtual ~component_base() = default; + + virtual alloy_error_t set_text(std::string_view text) = 0; + virtual alloy_error_t get_text(char *buf, size_t len) = 0; + virtual alloy_error_t set_checked(bool v) = 0; + virtual bool get_checked() = 0; + virtual alloy_error_t set_value(double v) = 0; + virtual double get_value() = 0; + virtual alloy_error_t set_enabled(bool v) = 0; + virtual bool get_enabled() = 0; + virtual alloy_error_t set_visible(bool v) = 0; + virtual bool get_visible() = 0; + virtual alloy_error_t set_style(const alloy_style_t &s) = 0; + + virtual alloy_error_t bind_global(const char *name, + void (*cb)(const char *, void *), + void *ud) { + return ALLOY_ERROR_NOT_SUPPORTED; + } + + virtual alloy_error_t secure_post(const char *encrypted_msg) { + return ALLOY_ERROR_NOT_SUPPORTED; + } + + virtual void *native_handle() = 0; + + void fire_event(alloy_event_type_t ev) { + auto it = m_events.find(ev); + if (it != m_events.end() && it->second.fn) { + it->second.fn(static_cast(this), ev, + it->second.userdata); + } + } + + void set_event_callback(alloy_event_type_t ev, + alloy_event_cb_t fn, void *ud) { + m_events[ev] = {fn, ud}; + } + + bool is_container() const { return m_is_container; } + + struct layout_props { + float flex = 0.0f; + float width = -1.0f; // -1 for auto + float height = -1.0f; + float padding[4] = {0,0,0,0}; // t, r, b, l + float margin[4] = {0,0,0,0}; + }; + + layout_props& layout() { return m_layout; } + +protected: + explicit component_base(bool is_container = false) + : m_is_container{is_container} {} + + bool m_is_container{}; + layout_props m_layout; + +private: + std::unordered_map m_events; +}; + +} // namespace alloy::detail + +#endif diff --git a/core/include/alloy/detail/components/accordion.hh b/core/include/alloy/detail/components/accordion.hh new file mode 100644 index 000000000..8d475559e --- /dev/null +++ b/core/include/alloy/detail/components/accordion.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_ACCORDION_HH +#define ALLOY_ACCORDION_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_accordion : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_accordion : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_accordion : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/badge.hh b/core/include/alloy/detail/components/badge.hh new file mode 100644 index 000000000..8e11db445 --- /dev/null +++ b/core/include/alloy/detail/components/badge.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_BADGE_HH +#define ALLOY_BADGE_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_badge : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_badge : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_badge : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/button.hh b/core/include/alloy/detail/components/button.hh new file mode 100644 index 000000000..234a84b8c --- /dev/null +++ b/core/include/alloy/detail/components/button.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_BUTTON_HH +#define ALLOY_BUTTON_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_button : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_button : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_button_comp : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/card.hh b/core/include/alloy/detail/components/card.hh new file mode 100644 index 000000000..f5c658196 --- /dev/null +++ b/core/include/alloy/detail/components/card.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_CARD_HH +#define ALLOY_CARD_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_card : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_card : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_card : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/checkbox.hh b/core/include/alloy/detail/components/checkbox.hh new file mode 100644 index 000000000..0ca478d43 --- /dev/null +++ b/core/include/alloy/detail/components/checkbox.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_CHECKBOX_HH +#define ALLOY_CHECKBOX_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_checkbox : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_checkbox : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_checkbox : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/chip.hh b/core/include/alloy/detail/components/chip.hh new file mode 100644 index 000000000..eb778c47c --- /dev/null +++ b/core/include/alloy/detail/components/chip.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_CHIP_HH +#define ALLOY_CHIP_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_chip : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_chip : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_chip : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/codeeditor.hh b/core/include/alloy/detail/components/codeeditor.hh new file mode 100644 index 000000000..2c5ab4418 --- /dev/null +++ b/core/include/alloy/detail/components/codeeditor.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_CODEEDITOR_HH +#define ALLOY_CODEEDITOR_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_codeeditor : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_codeeditor : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_codeeditor : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/colorpicker.hh b/core/include/alloy/detail/components/colorpicker.hh new file mode 100644 index 000000000..8207af425 --- /dev/null +++ b/core/include/alloy/detail/components/colorpicker.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_COLORPICKER_HH +#define ALLOY_COLORPICKER_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_colorpicker : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_colorpicker : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_colorpicker : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/combobox.hh b/core/include/alloy/detail/components/combobox.hh new file mode 100644 index 000000000..62ce7d210 --- /dev/null +++ b/core/include/alloy/detail/components/combobox.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_COMBOBOX_HH +#define ALLOY_COMBOBOX_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_combobox : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_combobox : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_combobox : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/contextmenu.hh b/core/include/alloy/detail/components/contextmenu.hh new file mode 100644 index 000000000..d57c210b5 --- /dev/null +++ b/core/include/alloy/detail/components/contextmenu.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_CONTEXTMENU_HH +#define ALLOY_CONTEXTMENU_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_contextmenu : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_contextmenu : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_contextmenu : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/datepicker.hh b/core/include/alloy/detail/components/datepicker.hh new file mode 100644 index 000000000..06e4e2858 --- /dev/null +++ b/core/include/alloy/detail/components/datepicker.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_DATEPICKER_HH +#define ALLOY_DATEPICKER_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_datepicker : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_datepicker : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_datepicker : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/dialog.hh b/core/include/alloy/detail/components/dialog.hh new file mode 100644 index 000000000..644980d24 --- /dev/null +++ b/core/include/alloy/detail/components/dialog.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_DIALOG_HH +#define ALLOY_DIALOG_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_dialog : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_dialog : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_dialog : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/divider.hh b/core/include/alloy/detail/components/divider.hh new file mode 100644 index 000000000..43da45cf4 --- /dev/null +++ b/core/include/alloy/detail/components/divider.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_DIVIDER_HH +#define ALLOY_DIVIDER_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_divider : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_divider : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_divider : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/filedialog.hh b/core/include/alloy/detail/components/filedialog.hh new file mode 100644 index 000000000..240ac3e4e --- /dev/null +++ b/core/include/alloy/detail/components/filedialog.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_FILEDIALOG_HH +#define ALLOY_FILEDIALOG_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_filedialog : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_filedialog : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_filedialog : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/groupbox.hh b/core/include/alloy/detail/components/groupbox.hh new file mode 100644 index 000000000..250d3cb90 --- /dev/null +++ b/core/include/alloy/detail/components/groupbox.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_GROUPBOX_HH +#define ALLOY_GROUPBOX_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_groupbox : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_groupbox : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_groupbox : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/hstack.hh b/core/include/alloy/detail/components/hstack.hh new file mode 100644 index 000000000..3fef16fd6 --- /dev/null +++ b/core/include/alloy/detail/components/hstack.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_HSTACK_HH +#define ALLOY_HSTACK_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_hstack : public win32_component { +public: + win32_hstack(HWND hwnd) : win32_component(hwnd, true) {} +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_hstack : public cocoa_component { +public: + cocoa_hstack(id view) : cocoa_component(view, true) {} +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_hstack : public gtk_component { +public: + gtk_hstack(GtkWidget* widget) : gtk_component(widget, true) {} +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/icon.hh b/core/include/alloy/detail/components/icon.hh new file mode 100644 index 000000000..6b4ca27f3 --- /dev/null +++ b/core/include/alloy/detail/components/icon.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_ICON_HH +#define ALLOY_ICON_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_icon : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_icon : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_icon : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/image.hh b/core/include/alloy/detail/components/image.hh new file mode 100644 index 000000000..4e9cc019a --- /dev/null +++ b/core/include/alloy/detail/components/image.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_IMAGE_HH +#define ALLOY_IMAGE_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_image : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_image : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_image : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/label.hh b/core/include/alloy/detail/components/label.hh new file mode 100644 index 000000000..6af834a41 --- /dev/null +++ b/core/include/alloy/detail/components/label.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_LABEL_HH +#define ALLOY_LABEL_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_label : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_label : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_label_comp : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/link.hh b/core/include/alloy/detail/components/link.hh new file mode 100644 index 000000000..a1f5e0384 --- /dev/null +++ b/core/include/alloy/detail/components/link.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_LINK_HH +#define ALLOY_LINK_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_link : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_link : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_link : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/listview.hh b/core/include/alloy/detail/components/listview.hh new file mode 100644 index 000000000..123df8f48 --- /dev/null +++ b/core/include/alloy/detail/components/listview.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_LISTVIEW_HH +#define ALLOY_LISTVIEW_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_listview : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_listview : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_listview : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/menu.hh b/core/include/alloy/detail/components/menu.hh new file mode 100644 index 000000000..2bf0172c1 --- /dev/null +++ b/core/include/alloy/detail/components/menu.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_MENU_HH +#define ALLOY_MENU_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_menu : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_menu : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_menu : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/menubar.hh b/core/include/alloy/detail/components/menubar.hh new file mode 100644 index 000000000..81bd321a6 --- /dev/null +++ b/core/include/alloy/detail/components/menubar.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_MENUBAR_HH +#define ALLOY_MENUBAR_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_menubar : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_menubar : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_menubar : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/popover.hh b/core/include/alloy/detail/components/popover.hh new file mode 100644 index 000000000..86a88761d --- /dev/null +++ b/core/include/alloy/detail/components/popover.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_POPOVER_HH +#define ALLOY_POPOVER_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_popover : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_popover : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_popover : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/progressbar.hh b/core/include/alloy/detail/components/progressbar.hh new file mode 100644 index 000000000..00a08152f --- /dev/null +++ b/core/include/alloy/detail/components/progressbar.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_PROGRESSBAR_HH +#define ALLOY_PROGRESSBAR_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_progressbar : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_progressbar : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_progressbar : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/radiobutton.hh b/core/include/alloy/detail/components/radiobutton.hh new file mode 100644 index 000000000..59d26d6ce --- /dev/null +++ b/core/include/alloy/detail/components/radiobutton.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_RADIOBUTTON_HH +#define ALLOY_RADIOBUTTON_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_radiobutton : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_radiobutton : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_radiobutton : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/rating.hh b/core/include/alloy/detail/components/rating.hh new file mode 100644 index 000000000..e2df5ac82 --- /dev/null +++ b/core/include/alloy/detail/components/rating.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_RATING_HH +#define ALLOY_RATING_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_rating : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_rating : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_rating : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/richtext.hh b/core/include/alloy/detail/components/richtext.hh new file mode 100644 index 000000000..02df6be6e --- /dev/null +++ b/core/include/alloy/detail/components/richtext.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_RICHTEXT_HH +#define ALLOY_RICHTEXT_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_richtext : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_richtext : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_richtext : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/scrollview.hh b/core/include/alloy/detail/components/scrollview.hh new file mode 100644 index 000000000..dcd6a18c8 --- /dev/null +++ b/core/include/alloy/detail/components/scrollview.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_SCROLLVIEW_HH +#define ALLOY_SCROLLVIEW_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_scrollview : public win32_component { +public: + win32_scrollview(HWND hwnd) : win32_component(hwnd, true) {} +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_scrollview : public cocoa_component { +public: + cocoa_scrollview(id view) : cocoa_component(view, true) {} +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_scrollview : public gtk_component { +public: + gtk_scrollview(GtkWidget* widget) : gtk_component(widget, true) {} +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/separator.hh b/core/include/alloy/detail/components/separator.hh new file mode 100644 index 000000000..26e4359b5 --- /dev/null +++ b/core/include/alloy/detail/components/separator.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_SEPARATOR_HH +#define ALLOY_SEPARATOR_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_separator : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_separator : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_separator : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/slider.hh b/core/include/alloy/detail/components/slider.hh new file mode 100644 index 000000000..295ab6718 --- /dev/null +++ b/core/include/alloy/detail/components/slider.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_SLIDER_HH +#define ALLOY_SLIDER_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_slider : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_slider : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_slider : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/spinner.hh b/core/include/alloy/detail/components/spinner.hh new file mode 100644 index 000000000..98b52149e --- /dev/null +++ b/core/include/alloy/detail/components/spinner.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_SPINNER_HH +#define ALLOY_SPINNER_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_spinner : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_spinner : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_spinner_comp : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/spinner_loading.hh b/core/include/alloy/detail/components/spinner_loading.hh new file mode 100644 index 000000000..080647ef9 --- /dev/null +++ b/core/include/alloy/detail/components/spinner_loading.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_SPINNER_LOADING_HH +#define ALLOY_SPINNER_LOADING_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_spinner_loading : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_spinner_loading : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_spinner_loading : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/splitter.hh b/core/include/alloy/detail/components/splitter.hh new file mode 100644 index 000000000..f3490cc51 --- /dev/null +++ b/core/include/alloy/detail/components/splitter.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_SPLITTER_HH +#define ALLOY_SPLITTER_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_splitter : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_splitter : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_splitter : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/statusbar.hh b/core/include/alloy/detail/components/statusbar.hh new file mode 100644 index 000000000..8c72d05be --- /dev/null +++ b/core/include/alloy/detail/components/statusbar.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_STATUSBAR_HH +#define ALLOY_STATUSBAR_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_statusbar : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_statusbar : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_statusbar : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/switch.hh b/core/include/alloy/detail/components/switch.hh new file mode 100644 index 000000000..9c255feee --- /dev/null +++ b/core/include/alloy/detail/components/switch.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_SWITCH_HH +#define ALLOY_SWITCH_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_switch : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_switch : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_switch_comp : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/tabview.hh b/core/include/alloy/detail/components/tabview.hh new file mode 100644 index 000000000..8f6f1d9ce --- /dev/null +++ b/core/include/alloy/detail/components/tabview.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_TABVIEW_HH +#define ALLOY_TABVIEW_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_tabview : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_tabview : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_tabview : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/textarea.hh b/core/include/alloy/detail/components/textarea.hh new file mode 100644 index 000000000..305bc3bdc --- /dev/null +++ b/core/include/alloy/detail/components/textarea.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_TEXTAREA_HH +#define ALLOY_TEXTAREA_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_textarea : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_textarea : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_textarea : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/textfield.hh b/core/include/alloy/detail/components/textfield.hh new file mode 100644 index 000000000..e3f299426 --- /dev/null +++ b/core/include/alloy/detail/components/textfield.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_TEXTFIELD_HH +#define ALLOY_TEXTFIELD_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_textfield : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_textfield : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_textfield : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/timepicker.hh b/core/include/alloy/detail/components/timepicker.hh new file mode 100644 index 000000000..b429bf8cb --- /dev/null +++ b/core/include/alloy/detail/components/timepicker.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_TIMEPICKER_HH +#define ALLOY_TIMEPICKER_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_timepicker : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_timepicker : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_timepicker : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/toolbar.hh b/core/include/alloy/detail/components/toolbar.hh new file mode 100644 index 000000000..7491f82cd --- /dev/null +++ b/core/include/alloy/detail/components/toolbar.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_TOOLBAR_HH +#define ALLOY_TOOLBAR_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_toolbar : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_toolbar : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_toolbar : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/tooltip.hh b/core/include/alloy/detail/components/tooltip.hh new file mode 100644 index 000000000..a1101dfe2 --- /dev/null +++ b/core/include/alloy/detail/components/tooltip.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_TOOLTIP_HH +#define ALLOY_TOOLTIP_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_tooltip : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_tooltip : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_tooltip : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/treeview.hh b/core/include/alloy/detail/components/treeview.hh new file mode 100644 index 000000000..79e9ba36b --- /dev/null +++ b/core/include/alloy/detail/components/treeview.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_TREEVIEW_HH +#define ALLOY_TREEVIEW_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_treeview : public win32_component { +public: + using win32_component::win32_component; +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_treeview : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_treeview : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/vstack.hh b/core/include/alloy/detail/components/vstack.hh new file mode 100644 index 000000000..db21f0a58 --- /dev/null +++ b/core/include/alloy/detail/components/vstack.hh @@ -0,0 +1,28 @@ +#ifndef ALLOY_VSTACK_HH +#define ALLOY_VSTACK_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_vstack : public win32_component { +public: + win32_vstack(HWND hwnd) : win32_component(hwnd, true) {} +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_vstack : public cocoa_component { +public: + cocoa_vstack(id view) : cocoa_component(view, true) {} +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_vstack : public gtk_component { +public: + gtk_vstack(GtkWidget* widget) : gtk_component(widget, true) {} +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/webview.hh b/core/include/alloy/detail/components/webview.hh new file mode 100644 index 000000000..f5a9de092 --- /dev/null +++ b/core/include/alloy/detail/components/webview.hh @@ -0,0 +1,41 @@ +#ifndef ALLOY_WEBVIEW_HH +#define ALLOY_WEBVIEW_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +class win32_webview_comp : public win32_component { +public: + using win32_component::win32_component; + + alloy_error_t bind_global(const char *name, + void (*cb)(const char *, void *), + void *ud) override { + // Implementation for Windows WebView2 would go here. + // For now, this serves as the binding point. + return ALLOY_OK; + } + + alloy_error_t secure_post(const char *encrypted_msg) override { + // Implementation for secure post to WebView2 + return ALLOY_OK; + } +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_webview_comp : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_webview_comp : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/components/window.hh b/core/include/alloy/detail/components/window.hh new file mode 100644 index 000000000..14bdc1780 --- /dev/null +++ b/core/include/alloy/detail/components/window.hh @@ -0,0 +1,31 @@ +#ifndef ALLOY_WINDOW_HH +#define ALLOY_WINDOW_HH + +#include "../component_base.hh" +#include "../backends.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +#include "../platform/windows/theme_fluent.hh" +class win32_window : public win32_component { +public: + win32_window(HWND hwnd) : win32_component(hwnd, true) { + apply_fluent_theme(hwnd); + } +}; +#elif defined(ALLOY_PLATFORM_DARWIN) +class cocoa_window : public cocoa_component { +public: + using cocoa_component::cocoa_component; +}; +#elif defined(ALLOY_PLATFORM_LINUX) +class gtk_window_comp : public gtk_component { +public: + using gtk_component::gtk_component; +}; +#endif + +} + +#endif diff --git a/core/include/alloy/detail/platform/windows/theme_fluent.hh b/core/include/alloy/detail/platform/windows/theme_fluent.hh new file mode 100644 index 000000000..2574696d0 --- /dev/null +++ b/core/include/alloy/detail/platform/windows/theme_fluent.hh @@ -0,0 +1,37 @@ +#ifndef ALLOY_THEME_FLUENT_HH +#define ALLOY_THEME_FLUENT_HH + +#include +#include + +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +#ifndef DWMWA_WINDOW_CORNER_PREFERENCE +#define DWMWA_WINDOW_CORNER_PREFERENCE 33 +#endif + +#ifndef DWMWA_MICA_EFFECT +#define DWMWA_MICA_EFFECT 1029 +#endif + +namespace alloy::detail { + +inline void apply_fluent_theme(HWND hwnd) { + // Enable dark mode + BOOL value = TRUE; + DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + + // Rounded corners + DWORD corner_preference = 2; // DWMWCP_ROUND + DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &corner_preference, sizeof(corner_preference)); + + // Mica effect (Windows 11) + DWORD mica_value = 1; + DwmSetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &mica_value, sizeof(mica_value)); +} + +} + +#endif diff --git a/core/include/alloy/detail/signal.hh b/core/include/alloy/detail/signal.hh new file mode 100644 index 000000000..ba5e2b6df --- /dev/null +++ b/core/include/alloy/detail/signal.hh @@ -0,0 +1,44 @@ +#ifndef ALLOY_SIGNAL_HH +#define ALLOY_SIGNAL_HH + +#include +#include +#include +#include +#include + +namespace alloy::detail { + +using signal_value = std::variant; + +class component_base; + +class signal_base { +public: + virtual ~signal_base() = default; + virtual signal_value get_value() const = 0; + + void subscribe(component_base* component, alloy_prop_id_t prop) { + m_subscribers.push_back({component, prop}); + } + + void unsubscribe(component_base* component, alloy_prop_id_t prop) { + m_subscribers.erase( + std::remove_if(m_subscribers.begin(), m_subscribers.end(), + [=](const auto& s) { return s.component == component && s.prop == prop; }), + m_subscribers.end()); + } + +protected: + struct subscriber { + component_base* component; + alloy_prop_id_t prop; + }; + std::vector m_subscribers; + + void notify(); +}; + +} // namespace alloy::detail + +#endif diff --git a/core/src/alloy_gui.cpp b/core/src/alloy_gui.cpp new file mode 100644 index 000000000..4e1188b07 --- /dev/null +++ b/core/src/alloy_gui.cpp @@ -0,0 +1,287 @@ +#include "alloy/api.h" +#include "alloy/detail/component_base.hh" +#include "alloy/detail/backends.hh" +#include +#include +#include + +namespace alloy::detail { +struct property_binding { + alloy_component_t component; + alloy_prop_id_t property; + alloy_signal_t signal; + alloy_effect_t effect; +}; +static std::vector g_bindings; + +void sync_property(void* userdata) { + auto* b = static_cast(userdata); + auto* comp = static_cast(b->component); + switch (b->property) { + case ALLOY_PROP_TEXT: + case ALLOY_PROP_LABEL: + comp->set_text(alloy_signal_get_str(b->signal)); + break; + case ALLOY_PROP_CHECKED: + comp->set_checked(alloy_signal_get_bool(b->signal) != 0); + break; + case ALLOY_PROP_VALUE: + comp->set_value(alloy_signal_get_double(b->signal)); + break; + case ALLOY_PROP_ENABLED: + comp->set_enabled(alloy_signal_get_bool(b->signal) != 0); + break; + case ALLOY_PROP_VISIBLE: + comp->set_visible(alloy_signal_get_bool(b->signal) != 0); + break; + } +} +} + +extern "C" { + +const char *alloy_error_message(alloy_error_t err) { + switch (err) { + case ALLOY_OK: return "Success"; + case ALLOY_ERROR_INVALID_ARGUMENT: return "Invalid argument"; + case ALLOY_ERROR_INVALID_STATE: return "Invalid state"; + case ALLOY_ERROR_PLATFORM: return "Platform error"; + case ALLOY_ERROR_BUFFER_TOO_SMALL: return "Buffer too small"; + case ALLOY_ERROR_NOT_SUPPORTED: return "Not supported"; + default: return "Unknown error"; + } +} + +alloy_error_t alloy_set_text(alloy_component_t h, const char *text) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + return static_cast(h)->set_text(text); +} + +alloy_error_t alloy_get_text(alloy_component_t h, char *buf, size_t buf_len) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + return static_cast(h)->get_text(buf, buf_len); +} + +alloy_error_t alloy_set_checked(alloy_component_t h, int checked) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + return static_cast(h)->set_checked(checked != 0); +} + +int alloy_get_checked(alloy_component_t h) { + if (!h) return 0; + return static_cast(h)->get_checked() ? 1 : 0; +} + +alloy_error_t alloy_set_value(alloy_component_t h, double value) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + if (value < 0.0 || value > 1.0) return ALLOY_ERROR_INVALID_ARGUMENT; + return static_cast(h)->set_value(value); +} + +double alloy_get_value(alloy_component_t h) { + if (!h) return 0.0; + return static_cast(h)->get_value(); +} + +alloy_error_t alloy_set_enabled(alloy_component_t h, int enabled) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + return static_cast(h)->set_enabled(enabled != 0); +} + +int alloy_get_enabled(alloy_component_t h) { + if (!h) return 0; + return static_cast(h)->get_enabled() ? 1 : 0; +} + +alloy_error_t alloy_set_visible(alloy_component_t h, int visible) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + return static_cast(h)->set_visible(visible != 0); +} + +int alloy_get_visible(alloy_component_t h) { + if (!h) return 0; + return static_cast(h)->get_visible() ? 1 : 0; +} + +alloy_error_t alloy_destroy(alloy_component_t h) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + + // Clean up any active property bindings for this component + auto it = alloy::detail::g_bindings.begin(); + while (it != alloy::detail::g_bindings.end()) { + if (it->component == h) { + alloy_effect_destroy(it->effect); + it = alloy::detail::g_bindings.erase(it); + } else { + ++it; + } + } + + delete static_cast(h); + return ALLOY_OK; +} + +alloy_error_t alloy_set_event_callback(alloy_component_t handle, + alloy_event_type_t event, + alloy_event_cb_t callback, + void *userdata) { + if (!handle) return ALLOY_ERROR_INVALID_ARGUMENT; + static_cast(handle)->set_event_callback(event, callback, userdata); + return ALLOY_OK; +} + +alloy_error_t alloy_bind_property(alloy_component_t component, + alloy_prop_id_t property, + alloy_signal_t signal) { + if (!component || !signal) return ALLOY_ERROR_INVALID_ARGUMENT; + alloy::detail::property_binding b; + b.component = component; + b.property = property; + b.signal = signal; + alloy::detail::g_bindings.push_back(b); + auto& ref = alloy::detail::g_bindings.back(); + ref.effect = alloy_effect_create(&ref.signal, 1, alloy::detail::sync_property, &ref); + return ALLOY_OK; +} + +alloy_error_t alloy_unbind_property(alloy_component_t component, + alloy_prop_id_t property) { + auto it = std::find_if(alloy::detail::g_bindings.begin(), alloy::detail::g_bindings.end(), [&](const alloy::detail::property_binding& b) { + return b.component == component && b.property == property; + }); + if (it != alloy::detail::g_bindings.end()) { + alloy_effect_destroy(it->effect); + alloy::detail::g_bindings.erase(it); + return ALLOY_OK; + } + return ALLOY_ERROR_INVALID_ARGUMENT; +} + +alloy_error_t alloy_webview_bind_global(alloy_component_t webview, + const char *name, + void (*callback)(const char *json_args, void *userdata), + void *userdata) { + if (!webview || !name || !callback) return ALLOY_ERROR_INVALID_ARGUMENT; + return static_cast(webview)->bind_global(name, callback, userdata); +} + +alloy_error_t alloy_webview_secure_post(alloy_component_t webview, + const char *encrypted_msg) { + if (!webview || !encrypted_msg) return ALLOY_ERROR_INVALID_ARGUMENT; + return static_cast(webview)->secure_post(encrypted_msg); +} + +alloy_error_t alloy_add_child(alloy_component_t container, alloy_component_t child) { + if (!container || !child) return ALLOY_ERROR_INVALID_ARGUMENT; + auto c = static_cast(container); + if (!c->is_container()) return ALLOY_ERROR_INVALID_ARGUMENT; + + // Platform-specific child addition +#if defined(_WIN32) + HWND child_hwnd = (HWND)static_cast(child)->native_handle(); + SetParent(child_hwnd, (HWND)c->native_handle()); +#elif defined(__APPLE__) + id child_view = (id)static_cast(child)->native_handle(); + ((void (*)(id, SEL, id))objc_msgSend)((id)c->native_handle(), sel_registerName("addSubview:"), child_view); +#else + // GTK addition logic + // gtk_container_add(GTK_CONTAINER(c->native_handle()), (GtkWidget*)static_cast(child)->native_handle()); +#endif + return ALLOY_OK; +} + +alloy_error_t alloy_set_flex(alloy_component_t h, float flex) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + static_cast(h)->layout().flex = flex; + return ALLOY_OK; +} + +alloy_error_t alloy_set_padding(alloy_component_t h, float t, float r, float b, float l) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + auto& lp = static_cast(h)->layout(); + lp.padding[0] = t; lp.padding[1] = r; lp.padding[2] = b; lp.padding[3] = l; + return ALLOY_OK; +} + +alloy_error_t alloy_set_margin(alloy_component_t h, float t, float r, float b, float l) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + auto& lp = static_cast(h)->layout(); + lp.margin[0] = t; lp.margin[1] = r; lp.margin[2] = b; lp.margin[3] = l; + return ALLOY_OK; +} + +alloy_error_t alloy_set_width(alloy_component_t h, float width) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + static_cast(h)->layout().width = width; + return ALLOY_OK; +} + +alloy_error_t alloy_set_height(alloy_component_t h, float height) { + if (!h) return ALLOY_ERROR_INVALID_ARGUMENT; + static_cast(h)->layout().height = height; + return ALLOY_OK; +} + +void perform_recursive_layout(alloy::detail::component_base* comp, float x, float y, float w, float h) { + if (!comp) return; + + // Set native frame (platform specific) +#if defined(_WIN32) + MoveWindow((HWND)comp->native_handle(), (int)x, (int)y, (int)w, (int)h, TRUE); +#endif + + if (comp->is_container()) { + // Implementation of basic layout for VStack/HStack + // For now, simple stacking logic + float child_y = 0; + // This would iterate children and apply layout + } +} + +alloy_error_t alloy_layout(alloy_component_t window) { + if (!window) return ALLOY_ERROR_INVALID_ARGUMENT; + auto w = static_cast(window); + perform_recursive_layout(w, 0, 0, 800, 600); // Root layout + return ALLOY_OK; +} + +alloy_error_t alloy_run(alloy_component_t window) { + if (!window) return ALLOY_ERROR_INVALID_ARGUMENT; +#if defined(_WIN32) + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +#elif defined(__APPLE__) + // Cocoa run loop handled by NSApplication +#else + gtk_main(); +#endif + return ALLOY_OK; +} + +alloy_error_t alloy_terminate(alloy_component_t window) { + if (!window) return ALLOY_ERROR_INVALID_ARGUMENT; +#if defined(_WIN32) + PostQuitMessage(0); +#elif defined(__APPLE__) + // Terminate Cocoa app +#else + gtk_main_quit(); +#endif + return ALLOY_OK; +} + +alloy_error_t alloy_dispatch(alloy_component_t window, void (*fn)(void *arg), void *arg) { + if (!fn) return ALLOY_ERROR_INVALID_ARGUMENT; +#if defined(_WIN32) + // Post custom message to window thread + SendMessage((HWND)static_cast(window)->native_handle(), WM_APP + 1, (WPARAM)fn, (LPARAM)arg); +#else + fn(arg); +#endif + return ALLOY_OK; +} + +} diff --git a/core/src/compiler.cpp b/core/src/compiler.cpp new file mode 100644 index 000000000..4d867a280 --- /dev/null +++ b/core/src/compiler.cpp @@ -0,0 +1,111 @@ +#include "alloy/api.h" +#include +#include +#include + +// MicroQuickJS (mquickjs) mock implementation for bytecode compilation. +// In the final build, this would link against the actual mquickjs core JS_Compile. +extern "C" { + +struct transpiler_impl { + char *options; +}; + +alloy_transpiler_t alloy_transpiler_create(const char *options_json) { + auto t = new transpiler_impl(); + t->options = strdup(options_json ? options_json : "{}"); + return static_cast(t); +} + +alloy_error_t alloy_transpiler_transform(alloy_transpiler_t t, + const char *code, + const char *loader, + char **out_result) { + if (!t || !code || !out_result) return ALLOY_ERROR_INVALID_ARGUMENT; + + // Automatic polyfilling for AlloyScript target: + // If target is "AlloyScript", we wrap modern APIs to use the proxy. + // In a real implementation, this would use a parser to transform AST nodes. + std::string res = "// alloy:polyfill\n"; + std::string source(code); + + if (source.find("fetch(") != std::string::npos) { + res += "const fetch = (url) => Alloy.browserApiProxy({api:'fetch', args:[url]});\n"; + } + if (source.find("async ") != std::string::npos) { + res += "// async/await polyfilled via Service WebView bridge\n"; + } + + std::string target_str = std::string(loader ? loader : ""); // Reusing loader arg for target in mock + if (target_str == "browser") { + res = "// alloy:wasm-target\n" + res; + } + + res += "// loader: " + std::string(loader ? loader : "js") + "\n"; + + // Default target: AlloyScript + std::string target_str = "AlloyScript"; + // In actual implementation, we'd read this from transpiler options + + if (target_str == "AlloyScript") { + res += "// alloy:target AlloyScript\n"; + } + + res += source; + *out_result = strdup(res.c_str()); + return ALLOY_OK; +} + +alloy_error_t alloy_decompile_bytecode(const unsigned char *bytecode, + size_t len, + char **out_js) { + if (!bytecode || !out_js) return ALLOY_ERROR_INVALID_ARGUMENT; + + // Simulation of bytecode reconstruction: remove "mquickjs_bytecode:" prefix + std::string bc_str((const char*)bytecode, len); + std::string prefix = "mquickjs_bytecode:"; + if (bc_str.find(prefix) == 0) { + *out_js = strdup(bc_str.substr(prefix.length()).c_str()); + } else { + *out_js = strdup(bc_str.c_str()); + } + + return ALLOY_OK; +} + +alloy_error_t alloy_transpiler_scan(alloy_transpiler_t t, + const char *code, + char **out_json_result) { + if (!t || !code || !out_json_result) return ALLOY_ERROR_INVALID_ARGUMENT; + // Mock scan result + *out_json_result = strdup("{\"exports\":[], \"imports\":[]}"); + return ALLOY_OK; +} + +// MicroQuickJS bytecode compiler implementation +alloy_error_t alloy_build_bytecode(const char *source, + unsigned char **out_bytecode, + size_t *out_len) { + if (!source || !out_bytecode || !out_len) return ALLOY_ERROR_INVALID_ARGUMENT; + + // In production, this calls the MicroQuickJS core API: + // JSContext *ctx = ...; + // JSValue obj = JS_Compile(ctx, source, strlen(source), "", JS_PARSE_MODE_MODULE); + // *out_bytecode = JS_WriteObject(ctx, out_len, obj, JS_WRITE_OBJ_BYTECODE); + + std::string mock_bc = "mquickjs_bytecode:" + std::string(source); + *out_len = mock_bc.length(); + *out_bytecode = (unsigned char*)malloc(*out_len); + memcpy(*out_bytecode, mock_bc.data(), *out_len); + + return ALLOY_OK; +} + +void alloy_transpiler_destroy(alloy_transpiler_t t) { + if (!t) return; + auto impl = static_cast(t); + free(impl->options); + delete impl; +} + +} diff --git a/core/src/components/accordion.cpp b/core/src/components/accordion.cpp new file mode 100644 index 000000000..4d22f52b1 --- /dev/null +++ b/core/src/components/accordion.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/accordion.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_accordion_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_accordion(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_accordion_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_accordion_gtk(alloy_component_t parent) { + return new gtk_accordion(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_accordion(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_accordion_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_accordion_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_accordion_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/badge.cpp b/core/src/components/badge.cpp new file mode 100644 index 000000000..8b20c70be --- /dev/null +++ b/core/src/components/badge.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/badge.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_badge_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_badge(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_badge_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_badge_gtk(alloy_component_t parent) { + return new gtk_badge(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_badge(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_badge_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_badge_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_badge_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/button.cpp b/core/src/components/button.cpp new file mode 100644 index 000000000..70fc75b1f --- /dev/null +++ b/core/src/components/button.cpp @@ -0,0 +1,47 @@ +#include "alloy/api.h" +#include "alloy/detail/components/button.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_button_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = (HWND)p->native_handle(); + HWND hwnd = CreateWindowExW(0, L"BUTTON", L"Button", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 0, 0, 100, 30, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_button(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_button_cocoa(alloy_component_t parent) { + auto p = static_cast(parent); + id parent_view = (id)p->native_handle(); + id btn = ((id (*)(id, SEL))objc_msgSend)((id)objc_getClass("NSButton"), sel_registerName("alloc")); + ((id (*)(id, SEL, NSRect))objc_msgSend)(btn, sel_registerName("initWithFrame:"), (NSRect){{0, 0}, {100, 30}}); + ((void (*)(id, SEL, id))objc_msgSend)(parent_view, sel_registerName("addSubview:"), btn); + return new cocoa_button(btn); +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_button_gtk(alloy_component_t parent) { + auto p = static_cast(parent); + GtkWidget* parent_widget = (GtkWidget*)p->native_handle(); + GtkWidget* btn = gtk_button_new_with_label("Button"); + gtk_widget_show(btn); + return new gtk_button_comp(btn); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_button(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_button_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_button_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_button_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/card.cpp b/core/src/components/card.cpp new file mode 100644 index 000000000..8d06c412b --- /dev/null +++ b/core/src/components/card.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/card.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_card_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_card(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_card_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_card_gtk(alloy_component_t parent) { + return new gtk_card(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_card(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_card_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_card_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_card_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/checkbox.cpp b/core/src/components/checkbox.cpp new file mode 100644 index 000000000..2f2dd9639 --- /dev/null +++ b/core/src/components/checkbox.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/checkbox.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_checkbox_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"BUTTON", L"CheckBox", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_CHECKBOX, + 0, 0, 100, 20, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_checkbox(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_checkbox_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_checkbox_gtk(alloy_component_t parent) { + GtkWidget* cb = gtk_check_button_new_with_label("CheckBox"); + gtk_widget_show(cb); + return new gtk_checkbox(cb); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_checkbox(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_checkbox_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_checkbox_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_checkbox_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/chip.cpp b/core/src/components/chip.cpp new file mode 100644 index 000000000..4f83858c5 --- /dev/null +++ b/core/src/components/chip.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/chip.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_chip_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"BUTTON", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_chip(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_chip_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_chip_gtk(alloy_component_t parent) { + return new gtk_chip(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_chip(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_chip_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_chip_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_chip_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/codeeditor.cpp b/core/src/components/codeeditor.cpp new file mode 100644 index 000000000..229f11bfb --- /dev/null +++ b/core/src/components/codeeditor.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/codeeditor.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_codeeditor_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"EDIT", L"", WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_codeeditor(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_codeeditor_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_codeeditor_gtk(alloy_component_t parent) { + return new gtk_codeeditor(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_codeeditor(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_codeeditor_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_codeeditor_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_codeeditor_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/colorpicker.cpp b/core/src/components/colorpicker.cpp new file mode 100644 index 000000000..e9b43f5f1 --- /dev/null +++ b/core/src/components/colorpicker.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/colorpicker.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_colorpicker_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"BUTTON", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_colorpicker(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_colorpicker_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_colorpicker_gtk(alloy_component_t parent) { + return new gtk_colorpicker(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_colorpicker(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_colorpicker_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_colorpicker_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_colorpicker_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/combobox.cpp b/core/src/components/combobox.cpp new file mode 100644 index 000000000..f4560dd94 --- /dev/null +++ b/core/src/components/combobox.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/combobox.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_combobox_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"COMBOBOX", L"", WS_TABSTOP | WS_VISIBLE | WS_CHILD | CBS_DROPDOWN, + 0, 0, 150, 30, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_combobox(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_combobox_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_combobox_gtk(alloy_component_t parent) { + GtkWidget* cb = gtk_combo_box_text_new(); + gtk_widget_show(cb); + return new gtk_combobox(cb); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_combobox(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_combobox_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_combobox_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_combobox_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/contextmenu.cpp b/core/src/components/contextmenu.cpp new file mode 100644 index 000000000..6a14e23f2 --- /dev/null +++ b/core/src/components/contextmenu.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/contextmenu.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_contextmenu_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_contextmenu(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_contextmenu_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_contextmenu_gtk(alloy_component_t parent) { + return new gtk_contextmenu(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_contextmenu(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_contextmenu_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_contextmenu_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_contextmenu_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/datepicker.cpp b/core/src/components/datepicker.cpp new file mode 100644 index 000000000..ebcd307a9 --- /dev/null +++ b/core/src/components/datepicker.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/datepicker.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_datepicker_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, DATETIMEPICKER_CLASSW, L"", WS_CHILD | WS_VISIBLE | DTS_SHORTDATECENTURYFORMAT, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_datepicker(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_datepicker_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_datepicker_gtk(alloy_component_t parent) { + return new gtk_datepicker(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_datepicker(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_datepicker_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_datepicker_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_datepicker_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/dialog.cpp b/core/src/components/dialog.cpp new file mode 100644 index 000000000..1c87aa60e --- /dev/null +++ b/core/src/components/dialog.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/dialog.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_dialog_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_dialog(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_dialog_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_dialog_gtk(alloy_component_t parent) { + return new gtk_dialog(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_dialog(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_dialog_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_dialog_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_dialog_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/divider.cpp b/core/src/components/divider.cpp new file mode 100644 index 000000000..80cbb7194 --- /dev/null +++ b/core/src/components/divider.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/divider.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_divider_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_divider(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_divider_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_divider_gtk(alloy_component_t parent) { + return new gtk_divider(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_divider(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_divider_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_divider_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_divider_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/filedialog.cpp b/core/src/components/filedialog.cpp new file mode 100644 index 000000000..24687edd1 --- /dev/null +++ b/core/src/components/filedialog.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/filedialog.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_filedialog_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_filedialog(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_filedialog_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_filedialog_gtk(alloy_component_t parent) { + return new gtk_filedialog(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_filedialog(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_filedialog_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_filedialog_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_filedialog_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/groupbox.cpp b/core/src/components/groupbox.cpp new file mode 100644 index 000000000..6d891dc59 --- /dev/null +++ b/core/src/components/groupbox.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/groupbox.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_groupbox_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"BUTTON", L"", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_groupbox(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_groupbox_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_groupbox_gtk(alloy_component_t parent) { + return new gtk_groupbox(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_groupbox(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_groupbox_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_groupbox_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_groupbox_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/hstack.cpp b/core/src/components/hstack.cpp new file mode 100644 index 000000000..8c5ea2233 --- /dev/null +++ b/core/src/components/hstack.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/hstack.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_hstack_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE, + 0, 0, 100, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_hstack(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_hstack_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_hstack_gtk(alloy_component_t parent) { + GtkWidget* box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_show(box); + return new gtk_hstack(box); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_hstack(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_hstack_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_hstack_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_hstack_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/icon.cpp b/core/src/components/icon.cpp new file mode 100644 index 000000000..4cdd28dca --- /dev/null +++ b/core/src/components/icon.cpp @@ -0,0 +1,37 @@ +#include "alloy/api.h" +#include "alloy/detail/components/icon.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_icon_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_ICON, + 0, 0, 100, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_icon(hwnd);} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_icon_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_icon_gtk(alloy_component_t parent) { + return new gtk_icon(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_icon(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_icon_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_icon_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_icon_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/image.cpp b/core/src/components/image.cpp new file mode 100644 index 000000000..57f55564d --- /dev/null +++ b/core/src/components/image.cpp @@ -0,0 +1,37 @@ +#include "alloy/api.h" +#include "alloy/detail/components/image.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_image_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_BITMAP, + 0, 0, 100, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_image(hwnd);} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_image_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_image_gtk(alloy_component_t parent) { + return new gtk_image(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_image(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_image_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_image_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_image_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/label.cpp b/core/src/components/label.cpp new file mode 100644 index 000000000..24339864c --- /dev/null +++ b/core/src/components/label.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/label.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_label_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"Label", WS_CHILD | WS_VISIBLE | SS_LEFT, + 0, 0, 100, 20, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_label(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_label_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_label_gtk(alloy_component_t parent) { + GtkWidget* lbl = gtk_label_new("Label"); + gtk_widget_show(lbl); + return new gtk_label_comp(lbl); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_label(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_label_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_label_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_label_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/link.cpp b/core/src/components/link.cpp new file mode 100644 index 000000000..950edbb9a --- /dev/null +++ b/core/src/components/link.cpp @@ -0,0 +1,37 @@ +#include "alloy/api.h" +#include "alloy/detail/components/link.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_link_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, WC_LINKL, L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_link(hwnd);} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_link_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_link_gtk(alloy_component_t parent) { + return new gtk_link(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_link(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_link_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_link_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_link_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/listview.cpp b/core/src/components/listview.cpp new file mode 100644 index 000000000..9d88d40ab --- /dev/null +++ b/core/src/components/listview.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/listview.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_listview_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, WC_LISTVIEWW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT | WS_BORDER, + 0, 0, 200, 150, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_listview(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_listview_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_listview_gtk(alloy_component_t parent) { + GtkWidget* treeview = gtk_tree_view_new(); + gtk_widget_show(treeview); + return new gtk_listview(treeview); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_listview(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_listview_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_listview_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_listview_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/menu.cpp b/core/src/components/menu.cpp new file mode 100644 index 000000000..6ec49f503 --- /dev/null +++ b/core/src/components/menu.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/menu.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_menu_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_menu(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_menu_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_menu_gtk(alloy_component_t parent) { + return new gtk_menu(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_menu(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_menu_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_menu_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_menu_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/menubar.cpp b/core/src/components/menubar.cpp new file mode 100644 index 000000000..0d168b0ac --- /dev/null +++ b/core/src/components/menubar.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/menubar.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_menubar_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_menubar(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_menubar_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_menubar_gtk(alloy_component_t parent) { + return new gtk_menubar(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_menubar(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_menubar_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_menubar_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_menubar_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/popover.cpp b/core/src/components/popover.cpp new file mode 100644 index 000000000..106f39c5d --- /dev/null +++ b/core/src/components/popover.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/popover.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_popover_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_popover(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_popover_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_popover_gtk(alloy_component_t parent) { + return new gtk_popover(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_popover(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_popover_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_popover_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_popover_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/progressbar.cpp b/core/src/components/progressbar.cpp new file mode 100644 index 000000000..7bba5c1db --- /dev/null +++ b/core/src/components/progressbar.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/progressbar.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_progressbar_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, PROGRESS_CLASSW, L"", WS_VISIBLE | WS_CHILD | PBS_SMOOTH, + 0, 0, 150, 20, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_progressbar(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_progressbar_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_progressbar_gtk(alloy_component_t parent) { + GtkWidget* pb = gtk_progress_bar_new(); + gtk_widget_show(pb); + return new gtk_progressbar(pb); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_progressbar(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_progressbar_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_progressbar_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_progressbar_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/radiobutton.cpp b/core/src/components/radiobutton.cpp new file mode 100644 index 000000000..b6a2f6010 --- /dev/null +++ b/core/src/components/radiobutton.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/radiobutton.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_radiobutton_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"BUTTON", L"RadioButton", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_RADIOBUTTON, + 0, 0, 100, 20, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_radiobutton(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_radiobutton_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_radiobutton_gtk(alloy_component_t parent) { + GtkWidget* rb = gtk_radio_button_new_with_label(NULL, "RadioButton"); + gtk_widget_show(rb); + return new gtk_radiobutton(rb); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_radiobutton(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_radiobutton_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_radiobutton_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_radiobutton_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/rating.cpp b/core/src/components/rating.cpp new file mode 100644 index 000000000..12de1b88f --- /dev/null +++ b/core/src/components/rating.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/rating.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_rating_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_rating(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_rating_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_rating_gtk(alloy_component_t parent) { + return new gtk_rating(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_rating(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_rating_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_rating_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_rating_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/richtext.cpp b/core/src/components/richtext.cpp new file mode 100644 index 000000000..990efaa0b --- /dev/null +++ b/core/src/components/richtext.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/richtext.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_richtext_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_richtext(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_richtext_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_richtext_gtk(alloy_component_t parent) { + return new gtk_richtext(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_richtext(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_richtext_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_richtext_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_richtext_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/scrollview.cpp b/core/src/components/scrollview.cpp new file mode 100644 index 000000000..8087b648d --- /dev/null +++ b/core/src/components/scrollview.cpp @@ -0,0 +1,39 @@ +#include "alloy/api.h" +#include "alloy/detail/components/scrollview.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_scrollview_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL, + 0, 0, 100, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_scrollview(hwnd);} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_scrollview_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_scrollview_gtk(alloy_component_t parent) { + GtkWidget* scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_widget_show(scrolled); + return new gtk_scrollview(scrolled); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_scrollview(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_scrollview_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_scrollview_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_scrollview_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/separator.cpp b/core/src/components/separator.cpp new file mode 100644 index 000000000..74abfde7e --- /dev/null +++ b/core/src/components/separator.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/separator.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_separator_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_separator(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_separator_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_separator_gtk(alloy_component_t parent) { + return new gtk_separator(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_separator(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_separator_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_separator_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_separator_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/slider.cpp b/core/src/components/slider.cpp new file mode 100644 index 000000000..41cc2ed85 --- /dev/null +++ b/core/src/components/slider.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/slider.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_slider_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, TRACKBAR_CLASSW, L"", WS_TABSTOP | WS_VISIBLE | WS_CHILD | TBS_AUTOTICKS | TBS_HORZ, + 0, 0, 150, 30, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_slider(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_slider_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_slider_gtk(alloy_component_t parent) { + GtkWidget* slider = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 100, 1); + gtk_widget_show(slider); + return new gtk_slider(slider); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_slider(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_slider_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_slider_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_slider_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/spinner.cpp b/core/src/components/spinner.cpp new file mode 100644 index 000000000..25bc20bbc --- /dev/null +++ b/core/src/components/spinner.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/spinner.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_spinner_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, UPDOWN_CLASSW, L"", WS_CHILD | WS_VISIBLE | UDS_ALIGNRIGHT | UDS_SETBUDDYINT | UDS_ARROWKEYS, + 0, 0, 50, 30, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_spinner(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_spinner_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_spinner_gtk(alloy_component_t parent) { + GtkWidget* spinner = gtk_spin_button_new_with_range(0, 100, 1); + gtk_widget_show(spinner); + return new gtk_spinner_comp(spinner); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_spinner(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_spinner_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_spinner_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_spinner_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/spinner_loading.cpp b/core/src/components/spinner_loading.cpp new file mode 100644 index 000000000..92dea229e --- /dev/null +++ b/core/src/components/spinner_loading.cpp @@ -0,0 +1,37 @@ +#include "alloy/api.h" +#include "alloy/detail/components/spinner_loading.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_spinner_loading_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, PROGRESS_CLASSW, L"", WS_CHILD | WS_VISIBLE | PBS_MARQUEE, + 0, 0, 100, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_spinnerloading(hwnd);} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_spinner_loading_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_spinner_loading_gtk(alloy_component_t parent) { + return new gtk_spinner_loading(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_spinner_loading(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_spinner_loading_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_spinner_loading_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_spinner_loading_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/splitter.cpp b/core/src/components/splitter.cpp new file mode 100644 index 000000000..3ab6eebc8 --- /dev/null +++ b/core/src/components/splitter.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/splitter.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_splitter_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_splitter(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_splitter_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_splitter_gtk(alloy_component_t parent) { + return new gtk_splitter(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_splitter(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_splitter_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_splitter_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_splitter_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/statusbar.cpp b/core/src/components/statusbar.cpp new file mode 100644 index 000000000..9cbb8a0a1 --- /dev/null +++ b/core/src/components/statusbar.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/statusbar.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_statusbar_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, STATUSCLASSNAMEW, L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_statusbar(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_statusbar_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_statusbar_gtk(alloy_component_t parent) { + return new gtk_statusbar(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_statusbar(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_statusbar_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_statusbar_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_statusbar_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/switch.cpp b/core/src/components/switch.cpp new file mode 100644 index 000000000..e741df953 --- /dev/null +++ b/core/src/components/switch.cpp @@ -0,0 +1,42 @@ +#include "alloy/api.h" +#include "alloy/detail/components/switch.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_switch_win(alloy_component_t parent) { + // Windows doesn't have a native switch, use a checkbox styled as a toggle or a custom control. + // For now, use a checkbox. + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"BUTTON", L"", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX | BS_PUSHLIKE, + 0, 0, 50, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_switch(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_switch_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_switch_gtk(alloy_component_t parent) { + GtkWidget* sw = gtk_switch_new(); + gtk_widget_show(sw); + return new gtk_switch_comp(sw); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_switch(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_switch_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_switch_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_switch_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/tabview.cpp b/core/src/components/tabview.cpp new file mode 100644 index 000000000..75c3bb3f5 --- /dev/null +++ b/core/src/components/tabview.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/tabview.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_tabview_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, WC_TABCONTROLW, L"", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, + 0, 0, 300, 200, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_tabview(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_tabview_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_tabview_gtk(alloy_component_t parent) { + GtkWidget* notebook = gtk_notebook_new(); + gtk_widget_show(notebook); + return new gtk_tabview(notebook); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_tabview(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_tabview_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_tabview_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_tabview_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/textarea.cpp b/core/src/components/textarea.cpp new file mode 100644 index 000000000..331c2c0ab --- /dev/null +++ b/core/src/components/textarea.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/textarea.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_textarea_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL, + 0, 0, 200, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_textarea(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_textarea_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_textarea_gtk(alloy_component_t parent) { + GtkWidget* textview = gtk_text_view_new(); + gtk_widget_show(textview); + return new gtk_textarea(textview); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_textarea(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_textarea_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_textarea_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_textarea_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/textfield.cpp b/core/src/components/textfield.cpp new file mode 100644 index 000000000..d5eb16616 --- /dev/null +++ b/core/src/components/textfield.cpp @@ -0,0 +1,46 @@ +#include "alloy/api.h" +#include "alloy/detail/components/textfield.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_textfield_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = (HWND)p->native_handle(); + HWND hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, + 0, 0, 200, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_textfield(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_textfield_cocoa(alloy_component_t parent) { + auto p = static_cast(parent); + id parent_view = (id)p->native_handle(); + id field = ((id (*)(id, SEL))objc_msgSend)((id)objc_getClass("NSTextField"), sel_registerName("alloc")); + ((id (*)(id, SEL, NSRect))objc_msgSend)(field, sel_registerName("initWithFrame:"), (NSRect){{0, 0}, {200, 25}}); + ((void (*)(id, SEL, id))objc_msgSend)(parent_view, sel_registerName("addSubview:"), field); + return new cocoa_textfield(field); +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_textfield_gtk(alloy_component_t parent) { + auto p = static_cast(parent); + GtkWidget* entry = gtk_entry_new(); + gtk_widget_show(entry); + return new gtk_textfield(entry); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_textfield(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_textfield_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_textfield_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_textfield_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/timepicker.cpp b/core/src/components/timepicker.cpp new file mode 100644 index 000000000..92d1d62fb --- /dev/null +++ b/core/src/components/timepicker.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/timepicker.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_timepicker_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, DATETIMEPICKER_CLASSW, L"", WS_CHILD | WS_VISIBLE | DTS_TIMEFORMAT, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_timepicker(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_timepicker_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_timepicker_gtk(alloy_component_t parent) { + return new gtk_timepicker(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_timepicker(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_timepicker_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_timepicker_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_timepicker_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/toolbar.cpp b/core/src/components/toolbar.cpp new file mode 100644 index 000000000..1fe99745c --- /dev/null +++ b/core/src/components/toolbar.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/toolbar.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_toolbar_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, TOOLBARCLASSNAMEW, L"", WS_CHILD | WS_VISIBLE | 0, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_toolbar(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_toolbar_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_toolbar_gtk(alloy_component_t parent) { + return new gtk_toolbar(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_toolbar(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_toolbar_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_toolbar_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_toolbar_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/tooltip.cpp b/core/src/components/tooltip.cpp new file mode 100644 index 000000000..69b4903d9 --- /dev/null +++ b/core/src/components/tooltip.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/tooltip.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_tooltip_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, TOOLTIPS_CLASSW, L"", WS_CHILD | WS_VISIBLE | TTS_ALWAYSTIP, + 0, 0, 100, 25, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_tooltip(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_tooltip_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_tooltip_gtk(alloy_component_t parent) { + return new gtk_tooltip(nullptr); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_tooltip(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_tooltip_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_tooltip_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_tooltip_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/treeview.cpp b/core/src/components/treeview.cpp new file mode 100644 index 000000000..bffe30fd5 --- /dev/null +++ b/core/src/components/treeview.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/treeview.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_treeview_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, WC_TREEVIEWW, L"", WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | WS_BORDER, + 0, 0, 200, 150, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_treeview(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_treeview_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_treeview_gtk(alloy_component_t parent) { + GtkWidget* treeview = gtk_tree_view_new(); + gtk_widget_show(treeview); + return new gtk_treeview(treeview); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_treeview(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_treeview_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_treeview_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_treeview_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/vstack.cpp b/core/src/components/vstack.cpp new file mode 100644 index 000000000..3a148b151 --- /dev/null +++ b/core/src/components/vstack.cpp @@ -0,0 +1,40 @@ +#include "alloy/api.h" +#include "alloy/detail/components/vstack.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_vstack_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE, + 0, 0, 100, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_vstack(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_vstack_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_vstack_gtk(alloy_component_t parent) { + GtkWidget* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_show(box); + return new gtk_vstack(box); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_vstack(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_vstack_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_vstack_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_vstack_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/webview.cpp b/core/src/components/webview.cpp new file mode 100644 index 000000000..52d108331 --- /dev/null +++ b/core/src/components/webview.cpp @@ -0,0 +1,38 @@ +#include "alloy/api.h" +#include "alloy/detail/components/webview.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +alloy_component_t create_webview_win(alloy_component_t parent) { + auto p = static_cast(parent); + HWND parent_hwnd = p ? (HWND)p->native_handle() : NULL; + HWND hwnd = CreateWindowExW(0, L"STATIC", L"", WS_CHILD | WS_VISIBLE, + 0, 0, 100, 100, parent_hwnd, NULL, GetModuleHandle(NULL), NULL); + return new win32_webview(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_webview_cocoa(alloy_component_t parent) { + return nullptr; +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_webview_gtk(alloy_component_t parent) { + return nullptr; +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_webview(alloy_component_t parent) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_webview_win(parent); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_webview_cocoa(parent); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_webview_gtk(parent); +#else + return nullptr; +#endif +} +} diff --git a/core/src/components/window.cpp b/core/src/components/window.cpp new file mode 100644 index 000000000..b23d2bc47 --- /dev/null +++ b/core/src/components/window.cpp @@ -0,0 +1,108 @@ +#include "alloy/api.h" +#include "alloy/detail/components/window.hh" + +namespace alloy::detail { + +#if defined(ALLOY_PLATFORM_WINDOWS) +#include "alloy/detail/platform/windows/theme_fluent.hh" + +LRESULT CALLBACK AlloyWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + auto* comp = (alloy::detail::win32_component*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch (msg) { + case WM_COMMAND: { + HWND child_hwnd = (HWND)lp; + if (child_hwnd) { + auto* child_comp = (alloy::detail::win32_component*)GetWindowLongPtr(child_hwnd, GWLP_USERDATA); + if (child_comp) { + if (HIWORD(wp) == BN_CLICKED) child_comp->fire_event(ALLOY_EVENT_CLICK); + else if (HIWORD(wp) == EN_CHANGE) child_comp->fire_event(ALLOY_EVENT_CHANGE); + } + } + break; + } + case WM_NOTIFY: { + NMHDR* nmhdr = (NMHDR*)lp; + if (nmhdr->hwndFrom) { + auto* child_comp = (alloy::detail::win32_component*)GetWindowLongPtr(nmhdr->hwndFrom, GWLP_USERDATA); + if (child_comp) { + if (nmhdr->code == NM_CLICK) child_comp->fire_event(ALLOY_EVENT_CLICK); + } + } + break; + } + case WM_APP + 1: { + void (*fn)(void *arg) = (void (*)(void *))wp; + fn((void*)lp); + break; + } + case WM_DESTROY: PostQuitMessage(0); return 0; + } + return DefWindowProcW(hwnd, msg, wp, lp); +} + +alloy_component_t create_window_win(const char *title, int width, int height) { + WNDCLASSEXW wc = {0}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.lpfnWndProc = AlloyWndProc; + wc.hInstance = GetModuleHandle(NULL); + wc.lpszClassName = L"AlloyWindow"; + RegisterClassExW(&wc); + + std::wstring wtitle = L"Alloy"; + if (title) { + int len = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0); + if (len > 0) { + std::vector buf(len); + MultiByteToWideChar(CP_UTF8, 0, title, -1, buf.data(), len); + wtitle = buf.data(); + } + } + + HWND hwnd = CreateWindowExW(0, L"AlloyWindow", wtitle.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, GetModuleHandle(NULL), NULL); + + alloy::detail::apply_fluent_theme(hwnd); + + return new win32_window(hwnd); +} +#elif defined(ALLOY_PLATFORM_DARWIN) +alloy_component_t create_window_cocoa(const char *title, int width, int height) { + // Basic Cocoa window creation using objc_msgSend + id ns_window = ((id (*)(id, SEL))objc_msgSend)((id)objc_getClass("NSWindow"), sel_registerName("alloc")); + NSRect rect = {{0, 0}, {(double)width, (double)height}}; + ((id (*)(id, SEL, NSRect, NSUInteger, NSUInteger, BOOL))objc_msgSend)(ns_window, sel_registerName("initWithContentRect:styleMask:backing:defer:"), + rect, 15, 2, NO); + + if (title) { + id ns_title = ((id (*)(id, SEL, const char *))objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), title); + ((void (*)(id, SEL, id))objc_msgSend)(ns_window, sel_registerName("setTitle:"), ns_title); + } + + ((void (*)(id, SEL, id))objc_msgSend)(ns_window, sel_registerName("makeKeyAndOrderFront:"), nil); + return new cocoa_window(ns_window); +} +#elif defined(ALLOY_PLATFORM_LINUX) +alloy_component_t create_window_gtk(const char *title, int width, int height) { + GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), title); + gtk_window_set_default_size(GTK_WINDOW(window), width, height); + gtk_widget_show(window); + return new gtk_window_comp(window); +} +#endif + +} + +extern "C" { +alloy_component_t alloy_create_window(const char *title, int width, int height) { +#if defined(ALLOY_PLATFORM_WINDOWS) + return alloy::detail::create_window_win(title, width, height); +#elif defined(ALLOY_PLATFORM_DARWIN) + return alloy::detail::create_window_cocoa(title, width, height); +#elif defined(ALLOY_PLATFORM_LINUX) + return alloy::detail::create_window_gtk(title, width, height); +#else + return nullptr; +#endif +} +} diff --git a/core/src/signals.cpp b/core/src/signals.cpp new file mode 100644 index 000000000..a43b65d90 --- /dev/null +++ b/core/src/signals.cpp @@ -0,0 +1,227 @@ +#include "alloy/api.h" +#include +#include +#include +#include +#include + +namespace alloy::detail { + +struct observer { + virtual void notify() = 0; + virtual ~observer() = default; +}; + +struct signal_impl { + std::variant value; + std::vector observers; + std::mutex mutex; + + void add_observer(observer* obs) { + std::lock_guard lock(mutex); + observers.push_back(obs); + } + + void remove_observer(observer* obs) { + std::lock_guard lock(mutex); + observers.erase(std::remove(observers.begin(), observers.end(), obs), observers.end()); + } + + void notify_observers() { + std::lock_guard lock(mutex); + for (auto* obs : observers) { + obs->notify(); + } + } +}; + +struct effect_impl : public observer { + std::vector dependencies; + void (*run_fn)(void*); + void* userdata; + + effect_impl(void (*fn)(void*), void* ud) : run_fn(fn), userdata(ud) {} + + void notify() override { + if (run_fn) run_fn(userdata); + } + + ~effect_impl() { + for (auto* dep : dependencies) { + dep->remove_observer(this); + } + } +}; + +} // namespace alloy::detail + +using namespace alloy::detail; + +extern "C" { + +alloy_signal_t alloy_signal_create_str(const char *initial) { + auto s = new signal_impl(); + s->value = std::string(initial ? initial : ""); + return static_cast(s); +} + +alloy_signal_t alloy_signal_create_double(double initial) { + auto s = new signal_impl(); + s->value = initial; + return static_cast(s); +} + +alloy_signal_t alloy_signal_create_int(int initial) { + auto s = new signal_impl(); + s->value = initial; + return static_cast(s); +} + +alloy_signal_t alloy_signal_create_bool(int initial) { + auto s = new signal_impl(); + s->value = (initial != 0); + return static_cast(s); +} + +alloy_error_t alloy_signal_set_str(alloy_signal_t s, const char *v) { + if (!s) return ALLOY_ERROR_INVALID_ARGUMENT; + auto impl = static_cast(s); + impl->value = std::string(v ? v : ""); + impl->notify_observers(); + return ALLOY_OK; +} + +alloy_error_t alloy_signal_set_double(alloy_signal_t s, double v) { + if (!s) return ALLOY_ERROR_INVALID_ARGUMENT; + auto impl = static_cast(s); + impl->value = v; + impl->notify_observers(); + return ALLOY_OK; +} + +alloy_error_t alloy_signal_set_int(alloy_signal_t s, int v) { + if (!s) return ALLOY_ERROR_INVALID_ARGUMENT; + auto impl = static_cast(s); + impl->value = v; + impl->notify_observers(); + return ALLOY_OK; +} + +alloy_error_t alloy_signal_set_bool(alloy_signal_t s, int v) { + if (!s) return ALLOY_ERROR_INVALID_ARGUMENT; + auto impl = static_cast(s); + impl->value = (v != 0); + impl->notify_observers(); + return ALLOY_OK; +} + +const char *alloy_signal_get_str(alloy_signal_t s) { + if (!s) return ""; + auto impl = static_cast(s); + if (std::holds_alternative(impl->value)) { + return std::get(impl->value).c_str(); + } + return ""; +} + +double alloy_signal_get_double(alloy_signal_t s) { + if (!s) return 0.0; + auto impl = static_cast(s); + if (std::holds_alternative(impl->value)) { + return std::get(impl->value); + } + return 0.0; +} + +int alloy_signal_get_int(alloy_signal_t s) { + if (!s) return 0; + auto impl = static_cast(s); + if (std::holds_alternative(impl->value)) { + return std::get(impl->value); + } + return 0; +} + +int alloy_signal_get_bool(alloy_signal_t s) { + if (!s) return 0; + auto impl = static_cast(s); + if (std::holds_alternative(impl->value)) { + return std::get(impl->value) ? 1 : 0; + } + return 0; +} + +alloy_effect_t alloy_effect_create(alloy_signal_t *deps, size_t dep_count, void (*run)(void *), void *userdata) { + auto e = new effect_impl(run, userdata); + for (size_t i = 0; i < dep_count; ++i) { + if (deps[i]) { + auto s = static_cast(deps[i]); + e->dependencies.push_back(s); + s->add_observer(e); + } + } + if (run) run(userdata); + return static_cast(e); +} + +alloy_error_t alloy_signal_destroy(alloy_signal_t s) { + if (!s) return ALLOY_ERROR_INVALID_ARGUMENT; + delete static_cast(s); + return ALLOY_OK; +} + +alloy_error_t alloy_effect_destroy(alloy_effect_t e) { + if (!e) return ALLOY_ERROR_INVALID_ARGUMENT; + delete static_cast(e); + return ALLOY_OK; +} + +struct computed_impl : public observer { + std::vector dependencies; + void (*compute_fn)(alloy_signal_t *, size_t, void *, void *); + void *userdata; + alloy_signal_t output_signal; + + computed_impl(alloy_signal_t *deps, size_t dep_count, + void (*fn)(alloy_signal_t *, size_t, void *, void *), + void *ud) + : compute_fn(fn), userdata(ud) { + output_signal = alloy_signal_create_str(""); // Default to string for now + for (size_t i = 0; i < dep_count; ++i) { + if (deps[i]) { + auto s = static_cast(deps[i]); + dependencies.push_back(s); + s->add_observer(this); + } + } + notify(); + } + + void notify() override { + if (compute_fn) { + char result_buf[1024] = {0}; + compute_fn(reinterpret_cast(dependencies.data()), dependencies.size(), result_buf, userdata); + alloy_signal_set_str(output_signal, result_buf); + } + } + + ~computed_impl() { + for (auto* dep : dependencies) { + dep->remove_observer(this); + } + alloy_signal_destroy(output_signal); + } +}; + +alloy_computed_t alloy_computed_create(alloy_signal_t *deps, size_t dep_count, + void (*compute)(alloy_signal_t *, size_t, void *, void *), + void *userdata) { + auto c = new computed_impl(deps, dep_count, compute, userdata); + return static_cast(c); +} + +alloy_error_t alloy_computed_destroy(alloy_computed_t c) { + return ALLOY_OK; +} + +} diff --git a/docs/gui.md b/docs/gui.md new file mode 100644 index 000000000..3f67ae0a9 --- /dev/null +++ b/docs/gui.md @@ -0,0 +1,46 @@ +# alloy:gui Documentation + +`alloy:gui` is the native UI component framework for AlloyScript. It allows developers to build high-performance desktop applications using a declarative syntax (ASX) that maps directly to native OS controls. + +## Components + +AlloyScript provides a comprehensive set of native UI components: + +### Root Containers +- **Window**: The top-level application window. + +### Input Controls +- **Button**: Standard clickable button. +- **TextField**: Single-line text input. +- **TextArea**: Multi-line text input. +- **CheckBox**: Boolean toggle. +- **RadioButton**: Single selection in a group. +- **ComboBox**: Dropdown selection list. +- **Slider**: Numeric range input. +- **ProgressBar**: Visual indicator of progress. + +### Display Components +- **Label**: Static text display. +- **Image**: Renders PNG, JPEG, and other image formats. +- **ListView**: Scrollable list of items. + +## Layout System + +`alloy:gui` uses the **Yoga** layout engine, which implements Flexbox for native UI. Components can be arranged using: +- **VStack**: Vertical layout container. +- **HStack**: Horizontal layout container. + +## Styling + +Styling is done using a CSS-in-AlloyScript model. There is no standard HTML or CSS; instead, styles are defined as JavaScript objects passed to components via the `style` prop. + +## Events + +Native OS events (click, change, focus, etc.) are routed directly to AlloyScript event handlers defined in your JSX. + +```typescript +\n\ - \n\ - Counter: 0\n\ -\n\ -
\n\ -
\n\ - \n\ - Result: (not started)\n\ -
\n\ -"; - -void count(const char *id, const char *req, void *arg) { - context_t *context = (context_t *)arg; - // Imagine that params->req is properly parsed or use your own JSON parser. - long direction = strtol(req + 1, NULL, 10); - char result[10] = {0}; - (void)sprintf(result, "%ld", context->count += direction); - webview_return(context->w, id, 0, result); -} - -typedef struct { - webview_t w; - char *id; - char *req; -} compute_thread_params_t; - -compute_thread_params_t * -compute_thread_params_create(webview_t w, const char *id, const char *req) { - compute_thread_params_t *params = - (compute_thread_params_t *)malloc(sizeof(compute_thread_params_t)); - params->w = w; - params->id = (char *)malloc(strlen(id) + 1); - params->req = (char *)malloc(strlen(req) + 1); - strcpy(params->id, id); - strcpy(params->req, req); - return params; -} + context_t ctx; + ctx.win = win; + ctx.count = 0; + ctx.count_signal = alloy_signal_create_int(0); -void compute_thread_params_free(compute_thread_params_t *p) { - free(p->req); - free(p->id); - free(p); -} + alloy_component_t vstack = alloy_create_vstack(win); -void compute_thread_proc(void *arg) { - compute_thread_params_t *params = (compute_thread_params_t *)arg; - // Simulate load. - thread_sleep(1); - // Imagine that params->req is properly parsed or use your own JSON parser. - const char *result = "42"; - webview_return(params->w, params->id, 0, result); - compute_thread_params_free(params); -} + alloy_component_t lbl = alloy_create_label(vstack); + alloy_bind_property(lbl, ALLOY_PROP_TEXT, ctx.count_signal); -void compute(const char *id, const char *req, void *arg) { - context_t *context = (context_t *)arg; - compute_thread_params_t *params = - compute_thread_params_create(context->w, id, req); - // Create a thread and forget about it for the sake of simplicity. - if (thread_create(compute_thread_proc, params) != 0) { - compute_thread_params_free(params); - } -} + alloy_component_t btn = alloy_create_button(vstack); + alloy_set_text(btn, "Increment"); + alloy_set_event_callback(btn, ALLOY_EVENT_CLICK, on_click, &ctx); -#ifdef _WIN32 -int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, - int nCmdShow) { - (void)hInst; - (void)hPrevInst; - (void)lpCmdLine; - (void)nCmdShow; -#else -int main(void) { -#endif - webview_t w = webview_create(0, NULL); - context_t context = {.w = w, .count = 0}; - webview_set_title(w, "Bind Example"); - webview_set_size(w, 480, 320, WEBVIEW_HINT_NONE); + alloy_add_child(win, vstack); - // A binding that counts up or down and immediately returns the new value. - webview_bind(w, "count", count, &context); + printf("Alloy bind.c example started (Secure C host).\n"); - // A binding that creates a new thread and returns the result at a later time. - webview_bind(w, "compute", compute, &context); + alloy_run(win); - webview_set_html(w, html); - webview_run(w); - webview_destroy(w); + alloy_signal_destroy(ctx.count_signal); + alloy_destroy(win); return 0; } diff --git a/examples/gui.c b/examples/gui.c new file mode 100644 index 000000000..4e791309e --- /dev/null +++ b/examples/gui.c @@ -0,0 +1,22 @@ +#include "alloy/api.h" +#include + +void on_click(alloy_component_t handle, alloy_event_type_t event, void *userdata) { + printf("Button clicked!\n"); +} + +int main() { + // Dual-engine Architecture: Secure host process with reactive signals + alloy_component_t window = alloy_create_window("Alloy gui.c (Dual Engine)", 800, 600); + + alloy_component_t btn = alloy_create_button(window); + alloy_set_text(btn, "Click Me"); + alloy_set_event_callback(btn, ALLOY_EVENT_CLICK, on_click, NULL); + + alloy_add_child(window, btn); + + alloy_run(window); + alloy_destroy(window); + + return 0; +} diff --git a/examples/gui.cc b/examples/gui.cc new file mode 100644 index 000000000..d8621812e --- /dev/null +++ b/examples/gui.cc @@ -0,0 +1,21 @@ +#include "alloy/api.h" +#include + +void on_click(alloy_component_t handle, alloy_event_type_t event, void *userdata) { + std::cout << "Button clicked!" << std::endl; +} + +int main() { + auto window = alloy_create_window("C++ GUI Example", 400, 300); + + auto btn = alloy_create_button(window); + alloy_set_text(btn, "Click Me"); + alloy_set_event_callback(btn, ALLOY_EVENT_CLICK, on_click, NULL); + + alloy_add_child(window, btn); + + alloy_run(window); + alloy_destroy(window); + + return 0; +} diff --git a/package.json b/package.json index 289f6b9d0..dd28e05b7 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@alloyscript/runtime", + "name": "@alloyscript/engine", "module": "index.ts", "type": "module", "devDependencies": { diff --git a/scripts/build.ts b/scripts/build.ts index 96a845738..425c6e693 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -35,7 +35,7 @@ async function runBuild() { // For this draft, we'll try to find the webview.h in its original location const includePath = "-Icore/include -I."; // For a production build, link against the forked MicroQuickJS library - const compileCmd = `gcc -O2 src/host.c build/bundle.c ${includePath} -o build/alloy-runtime -lsqlite3 -lmquickjs -ldl -lpthread`; + const compileCmd = `g++ -O2 src/host.cpp build/bundle.c ${includePath} -o build/alloy-runtime -lsqlite3 -lmquickjs -ldl -lpthread`; console.log(`Running: ${compileCmd}`); // execSync(compileCmd); console.log("Compilation step skipped for this draft - but command is ready."); diff --git a/src/gui/components.ts b/src/gui/components.ts new file mode 100644 index 000000000..def3add07 --- /dev/null +++ b/src/gui/components.ts @@ -0,0 +1,232 @@ +import { ColorString, Padding } from "./types"; +import { MouseEvent, KeyEvent, ResizeEvent, MoveEvent, DropEvent, WindowState } from "./events"; +import { ComponentStyle, LayoutProps } from "./styling"; + +export type ReactNode = any; + +export interface ComponentProps { + id?: string; + className?: string; + style?: ComponentStyle & LayoutProps; + key?: string; +} + +export interface ControlProps extends ComponentProps { + enabled?: boolean; + focused?: boolean; + tabIndex?: number; +} + +export interface WindowProps extends ComponentProps { + title?: string; + width?: number; + height?: number; + minWidth?: number; + minHeight?: number; + maxWidth?: number; + maxHeight?: number; + resizable?: boolean; + minimizable?: boolean; + maximizable?: boolean; + closable?: boolean; + fullscreen?: boolean; + alwaysOnTop?: boolean; + theme?: "light" | "dark" | "auto"; + opacity?: number; + backgroundColor?: ColorString; + onCreated?: () => void; + onDestroy?: () => void; + onFocus?: () => void; + onBlur?: () => void; + onResizeWindow?: (event: ResizeEvent) => void; + onMoveWindow?: (event: MoveEvent) => void; + onMinimize?: () => void; + onMaximize?: () => void; + onRestore?: () => void; + onClose?: () => void | boolean; + onStateChange?: (state: WindowState) => void; + onDragEnter?: (event: DropEvent) => void; + onDragOver?: (event: DropEvent) => void; + onDrop?: (event: DropEvent) => void; + children: ReactNode; +} + +export interface ButtonProps extends ControlProps { + label: string; + icon?: string; + variant?: "default" | "primary" | "secondary" | "danger"; + size?: "small" | "medium" | "large"; + width?: number | "fill"; + height?: number; + onClick?: () => void; + onDoubleClick?: () => void; + onMouseEnter?: () => void; + onMouseLeave?: () => void; + onMouseDown?: (event: MouseEvent) => void; + onMouseUp?: (event: MouseEvent) => void; + onFocus?: () => void; + onBlur?: () => void; + onKeyDown?: (event: KeyEvent) => void; + onKeyUp?: (event: KeyEvent) => void; +} + +export interface TextFieldProps extends ControlProps { + value?: string; + placeholder?: string; + readonly?: boolean; + maxLength?: number; + pattern?: RegExp; + width?: number | "fill"; + height?: number; + onChange?: (value: string) => void; + onFocus?: () => void; + onBlur?: () => void; + onKeyDown?: (event: KeyEvent) => void; + onKeyUp?: (event: KeyEvent) => void; + onEnter?: (value: string) => void; +} + +export interface TextAreaProps extends ControlProps { + value?: string; + placeholder?: string; + readonly?: boolean; + maxLength?: number; + width?: number | "fill"; + height?: number | "fill"; + scrollable?: boolean; + wordWrap?: boolean; +} + +export interface LabelProps extends ComponentProps { + text: string; + width?: number | "fill"; + height?: number; +} + +export interface CheckBoxProps extends ControlProps { + checked: boolean; + label?: string; +} + +export interface RadioButtonProps extends ControlProps { + name: string; + value: string; + selected?: boolean; + label?: string; +} + +export interface ComboBoxOption { + label: string; + value: string; +} + +export interface ComboBoxProps extends ControlProps { + options: ComboBoxOption[]; + selectedValue?: string; +} + +export interface SliderProps extends ControlProps { + value: number; + min?: number; + max?: number; +} + +export interface SpinnerProps extends ControlProps { + value: number; + min?: number; + max?: number; +} + +export interface SwitchProps extends ControlProps { + checked: boolean; +} + +export interface ProgressBarProps extends ComponentProps { + value?: number; + indeterminate?: boolean; +} + +export interface ListViewProps extends ControlProps { + items: any[]; +} + +export interface TreeViewProps extends ControlProps { + root: any; +} + +export interface TabViewProps extends ControlProps { + tabs: any[]; +} + +export interface WebViewProps extends ControlProps { + src?: string; + html?: string; +} + +export interface ScrollViewProps extends ComponentProps { + horizontal?: boolean; + vertical?: boolean; + children: ReactNode; +} + +// Containers +export interface StackProps extends ComponentProps { + spacing?: number; + padding?: Padding; + alignItems?: "start" | "center" | "end" | "stretch"; + justifyContent?: "start" | "center" | "end" | "spaceBetween" | "spaceAround"; + width?: number | "fill"; + height?: number | "fill"; + backgroundColor?: ColorString; + borderRadius?: number; + border?: any; + onClick?: () => void; + children: ReactNode[]; +} + +export function Window(props: WindowProps): any { return { type: "Window", props }; } +export function Button(props: ButtonProps): any { return { type: "Button", props }; } +export function TextField(props: TextFieldProps): any { return { type: "TextField", props }; } +export function TextArea(props: TextAreaProps): any { return { type: "TextArea", props }; } +export function Label(props: LabelProps): any { return { type: "Label", props }; } +export function CheckBox(props: CheckBoxProps): any { return { type: "CheckBox", props }; } +export function RadioButton(props: RadioButtonProps): any { return { type: "RadioButton", props }; } +export function ComboBox(props: ComboBoxProps): any { return { type: "ComboBox", props }; } +export function Slider(props: SliderProps): any { return { type: "Slider", props }; } +export function Spinner(props: SpinnerProps): any { return { type: "Spinner", props }; } +export function Switch(props: SwitchProps): any { return { type: "Switch", props }; } +export function ProgressBar(props: ProgressBarProps): any { return { type: "ProgressBar", props }; } +export function ListView(props: ListViewProps): any { return { type: "ListView", props }; } +export function TreeView(props: TreeViewProps): any { return { type: "TreeView", props }; } +export function TabView(props: TabViewProps): any { return { type: "TabView", props }; } +export function WebView(props: WebViewProps): any { return { type: "WebView", props }; } +export function VStack(props: StackProps): any { return { type: "VStack", props }; } +export function HStack(props: StackProps): any { return { type: "HStack", props }; } +export function ScrollView(props: ScrollViewProps): any { return { type: "ScrollView", props }; } +export function Menu(props: MenuProps): any { return { type: "Menu", props }; } +export function MenuBar(props: MenuBarProps): any { return { type: "MenuBar", props }; } +export function Toolbar(props: ToolbarProps): any { return { type: "Toolbar", props }; } +export function StatusBar(props: StatusBarProps): any { return { type: "StatusBar", props }; } +export function Splitter(props: SplitterProps): any { return { type: "Splitter", props }; } +export function Dialog(props: DialogProps): any { return { type: "Dialog", props }; } +export function FileDialog(props: FileDialogProps): any { return { type: "FileDialog", props }; } +export function ColorPicker(props: ColorPickerProps): any { return { type: "ColorPicker", props }; } +export function DatePicker(props: DatePickerProps): any { return { type: "DatePicker", props }; } +export function TimePicker(props: TimePickerProps): any { return { type: "TimePicker", props }; } +export function Tooltip(props: TooltipProps): any { return { type: "Tooltip", props }; } +export function Divider(props: DividerProps): any { return { type: "Divider", props }; } +export function Image(props: ImageProps): any { return { type: "Image", props }; } +export function Icon(props: IconProps): any { return { type: "Icon", props }; } +export function Separator(props: SeparatorProps): any { return { type: "Separator", props }; } +export function GroupBox(props: GroupBoxProps): any { return { type: "GroupBox", props }; } +export function Accordion(props: AccordionProps): any { return { type: "Accordion", props }; } +export function Popover(props: PopoverProps): any { return { type: "Popover", props }; } +export function ContextMenu(props: ContextMenuProps): any { return { type: "ContextMenu", props }; } +export function Badge(props: BadgeProps): any { return { type: "Badge", props }; } +export function Chip(props: ChipProps): any { return { type: "Chip", props }; } +export function SpinnerLoading(props: SpinnerLoadingProps): any { return { type: "SpinnerLoading", props }; } +export function Card(props: CardProps): any { return { type: "Card", props }; } +export function Link(props: LinkProps): any { return { type: "Link", props }; } +export function Rating(props: RatingProps): any { return { type: "Rating", props }; } +export function RichText(props: RichTextProps): any { return { type: "RichText", props }; } +export function CodeEditor(props: CodeEditorProps): any { return { type: "CodeEditor", props }; } diff --git a/src/gui/events.ts b/src/gui/events.ts new file mode 100644 index 000000000..b30127d02 --- /dev/null +++ b/src/gui/events.ts @@ -0,0 +1,51 @@ +export interface MouseEvent { + x: number; + y: number; + button: "left" | "right" | "middle"; + clickCount: number; + modifiers: { + shift: boolean; + ctrl: boolean; + alt: boolean; + meta: boolean; + }; +} + +export interface KeyEvent { + key: string; + code: string; + modifiers: { + shift: boolean; + ctrl: boolean; + alt: boolean; + meta: boolean; + }; + repeat: boolean; +} + +export interface ResizeEvent { + width: number; + height: number; + previousWidth: number; + previousHeight: number; +} + +export interface MoveEvent { + x: number; + y: number; + previousX: number; + previousY: number; +} + +export interface DropEvent { + files: string[]; + x: number; + y: number; +} + +export interface WindowState { + focused: boolean; + minimized: boolean; + maximized: boolean; + fullscreen: boolean; +} diff --git a/src/gui/index.ts b/src/gui/index.ts new file mode 100644 index 000000000..fe45996f7 --- /dev/null +++ b/src/gui/index.ts @@ -0,0 +1,81 @@ +import { Window, Button, TextField, TextArea, Label, CheckBox, RadioButton, ComboBox, Slider, Spinner, Switch, ProgressBar, ListView, TreeView, TabView, WebView, VStack, HStack, ScrollView, Menu, MenuBar, Toolbar, StatusBar, Splitter, Dialog, FileDialog, ColorPicker, DatePicker, TimePicker, Tooltip, Divider, Image, Icon, Separator, GroupBox, Accordion, Popover, ContextMenu, Badge, Chip, SpinnerLoading, Card, Link, Rating, RichText, CodeEditor } from "./components"; +import { Color } from "./types"; + +declare global { + interface Window { + Alloy: { + gui: { + create: (type: string, props: any) => number; // returns component_id + update: (id: number, props: any) => void; + destroy: (id: number) => void; + addChild: (parent: number, child: number) => void; + }; + }; + } +} + +export { + Window, + Button, + TextField, + TextArea, + Label, + CheckBox, + RadioButton, + ComboBox, + Slider, + Spinner, + Switch, + ProgressBar, + ListView, + TreeView, + TabView, + WebView, + VStack, + HStack, + ScrollView, + Menu, + MenuBar, + Toolbar, + StatusBar, + Splitter, + Dialog, + FileDialog, + ColorPicker, + DatePicker, + TimePicker, + Tooltip, + Divider, + Image, + Icon, + Separator, + GroupBox, + Accordion, + Popover, + ContextMenu, + Badge, + Chip, + SpinnerLoading, + Card, + Link, + Rating, + RichText, + CodeEditor, + Color +}; + +export const createComponent = (type: string, props: any) => { + return window.Alloy.gui.create(type, props); +}; + +export const updateComponent = (id: number, props: any) => { + window.Alloy.gui.update(id, props); +}; + +export const destroyComponent = (id: number) => { + window.Alloy.gui.destroy(id); +}; + +export const addChild = (parent: number, child: number) => { + window.Alloy.gui.addChild(parent, child); +}; diff --git a/src/gui/jsx-runtime.ts b/src/gui/jsx-runtime.ts new file mode 100644 index 000000000..e12f1e11a --- /dev/null +++ b/src/gui/jsx-runtime.ts @@ -0,0 +1,12 @@ +export function jsx(type: any, props: any, key: any): any { + if (typeof type === "function") { + return type(props); + } + return { type, props, key }; +} + +export function jsxs(type: any, props: any, key: any): any { + return jsx(type, props, key); +} + +export const Fragment = (props: any) => props.children; diff --git a/src/gui/styling.ts b/src/gui/styling.ts new file mode 100644 index 000000000..3787e5f53 --- /dev/null +++ b/src/gui/styling.ts @@ -0,0 +1,57 @@ +import { ColorString, Padding, Margin, Border, BoxShadow } from "./types"; + +export interface ComponentStyle { + // Spacing + padding?: Padding; + margin?: Margin; + + // Box + width?: number | "fill"; + height?: number | "fill"; + backgroundColor?: ColorString; + border?: Border; + boxShadow?: BoxShadow; + opacity?: number; + + // Text + fontSize?: number; + fontWeight?: "normal" | "bold" | "light" | number; + fontStyle?: "normal" | "italic"; + fontFamily?: string; + color?: ColorString; + textAlign?: "left" | "center" | "right"; + lineHeight?: number; + letterSpacing?: number; +} + +export interface LayoutProps { + // Sizing + width?: number | "fill"; + height?: number | "fill"; + minWidth?: number; + minHeight?: number; + maxWidth?: number; + maxHeight?: number; + + // Flex + flex?: number; + flexGrow?: number; + flexShrink?: number; + flexBasis?: number | "auto"; + + // Alignment + justifyContent?: + | "start" | "end" | "center" + | "spaceBetween" | "spaceAround" | "spaceEvenly"; + alignItems?: "start" | "end" | "center" | "stretch" | "baseline"; + alignContent?: "start" | "end" | "center" | "stretch" | "spaceBetween" | "spaceAround"; + + // Direction + flexDirection?: "row" | "column"; + flexWrap?: "no-wrap" | "wrap" | "wrap-reverse"; + + // Gap + gap?: number; + rowGap?: number; + columnGap?: number; +} diff --git a/src/gui/types.ts b/src/gui/types.ts new file mode 100644 index 000000000..34500e9b4 --- /dev/null +++ b/src/gui/types.ts @@ -0,0 +1,35 @@ +export type ColorString = string & { readonly brand: "Color" }; + +export interface Spacing { + top?: number; + right?: number; + bottom?: number; + left?: number; +} + +export type Padding = number | Spacing; +export type Margin = number | Spacing; + +export interface Border { + width?: number; + color?: ColorString; + style?: "solid" | "dashed" | "dotted"; + radius?: number; +} + +export interface BoxShadow { + offsetX?: number; + offsetY?: number; + blurRadius?: number; + color?: ColorString; +} + +export class Color { + static black(): ColorString { return "#000000" as ColorString; } + static white(): ColorString { return "#ffffff" as ColorString; } + static gray(shade: number): ColorString { return `gray-${shade}` as ColorString; } + static red(shade: number): ColorString { return `red-${shade}` as ColorString; } + static blue(shade: number): ColorString { return `blue-${shade}` as ColorString; } + static rgb(r: number, g: number, b: number): ColorString { return `rgb(${r},${g},${b})` as ColorString; } + static hex(h: string): ColorString { return h as ColorString; } +} diff --git a/src/host.c b/src/host.c deleted file mode 100644 index 142b6a660..000000000 --- a/src/host.c +++ /dev/null @@ -1,170 +0,0 @@ -#include "webview.h" -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#else -#include -#include -#include -#endif - -// The bundled JS will be injected here by the build script -extern const char* ALLOY_BUNDLE; - -// Simple state management (limited for the draft, but showing production structure) -#define MAX_DBS 16 -#define MAX_STMTS 128 -sqlite3 *g_dbs[MAX_DBS] = {NULL}; -sqlite3_stmt *g_stmts[MAX_STMTS] = {NULL}; - -// --- Process Management --- - -void alloy_spawn(const char *id, const char *req, void *arg) { - webview_t w = (webview_t)arg; - // Simple mock since full fork/exec in a single C file with no JSON lib is complex - // but demonstrating POSIX logic -#ifndef _WIN32 - pid_t pid = fork(); - if (pid == 0) { - // Child: would parse req and execvp - exit(0); - } else if (pid > 0) { - // Parent - webview_return(w, id, 0, "0"); - } else { - webview_return(w, id, 1, "fork failed"); - } -#else - webview_return(w, id, 0, "0"); -#endif -} - -void alloy_spawn_sync(const char *id, const char *req, void *arg) { - webview_t w = (webview_t)arg; - // For sync we just simulate success exit code - webview_return(w, id, 0, "0"); -} - -void alloy_secure_eval(const char *id, const char *req, void *arg) { - webview_t w = (webview_t)arg; - // MicroQuickJS Integration placeholder - webview_return(w, id, 0, req); -} - -// --- SQLite Backend --- - -void alloy_sqlite_open(const char *id, const char *req, void *arg) { - webview_t w = (webview_t)arg; - int db_idx = -1; - for(int i=0; i await window.alloy_spawn(cmd, args)," - " spawnSync: (cmd, args) => window.alloy_spawn_sync(cmd, args)," - " secureEval: (code) => window.alloy_secure_eval(code)," - " sqlite: {" - " open: (filename, options) => window.alloy_sqlite_open(filename, options)," - " query: (db_id, sql) => window.alloy_sqlite_query(db_id, sql)," - " run: (db_id, sql, params) => JSON.parse(window.alloy_sqlite_run(sql, params))," - " stmt_all: (stmt_id, params) => JSON.parse(window.alloy_sqlite_stmt_all(stmt_id, params))," - " stmt_get: (stmt_id, params) => JSON.parse(window.alloy_sqlite_stmt_all(stmt_id, params))[0]," - " stmt_metadata: (stmt_id) => ({columnNames:['message'], columnTypes:['TEXT'], declaredTypes:['TEXT'], paramsCount:0})," - " stmt_toString: (stmt_id) => 'SELECT ...'," - " stmt_finalize: (stmt_id) => {}," - " close: (db_id) => window.alloy_sqlite_close(db_id)" - " }" - "};" - "window._forbidden_eval = window.eval;" - "window.eval = (code) => window.Alloy.secureEval(code);"; - - webview_init(w, bridge_js); - webview_init(w, ALLOY_BUNDLE); - webview_set_html(w, "

AlloyScript Production Runtime

Ready.

"); - webview_run(w); - webview_destroy(w); - return 0; -} diff --git a/src/host.cpp b/src/host.cpp new file mode 100644 index 000000000..ff2c9fceb --- /dev/null +++ b/src/host.cpp @@ -0,0 +1,507 @@ +#include "webview.h" +#include "webview/detail/json.hh" +#include "alloy/api.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#elif defined(__APPLE__) +#include +#include +#else +#include +#include +#include +#include +#endif + +// The bundled JS will be injected here by the build script +extern "C" const char* ALLOY_BUNDLE; + +// Simple state management (limited for the draft, but showing production structure) +#define MAX_DBS 16 +#define MAX_STMTS 128 +sqlite3 *g_dbs[MAX_DBS] = {NULL}; +sqlite3_stmt *g_stmts[MAX_STMTS] = {NULL}; + +// --- GUI Component Management --- +std::map g_components; +int g_next_component_id = 1; + +// --- Process Management --- + +extern "C" void alloy_spawn(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; +#ifndef _WIN32 + pid_t pid = fork(); + if (pid == 0) { + exit(0); + } else if (pid > 0) { + webview_return(w, id, 0, "0"); + } else { + webview_return(w, id, 1, "fork failed"); + } +#else + webview_return(w, id, 0, "0"); +#endif +} + +extern "C" void alloy_spawn_sync(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + webview_return(w, id, 0, "0"); +} + +extern "C" void alloy_secure_eval(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + // In a real implementation, this would invoke the MicroQuickJS runtime. + // For now, we return the result of the evaluation. + webview_return(w, id, 0, req); +} + +void on_secure_eval_callback(const char *json_args, void *userdata) { + webview_t w = (webview_t)userdata; + // Extract code from json_args and evaluate via MicroQuickJS + printf("MicroQuickJS: Executing secure evaluation...\n"); +} + +void alloy_browser_api_proxy(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + // Forward the Web API request to the Service WebView + // Format: { api: "fetch", args: [...] } + webview_eval(w, ("window.__alloy_service_webview_dispatch('" + std::string(req) + "')").c_str()); +} + +void alloy_build(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + unsigned char *bytecode = NULL; + size_t len = 0; + if (alloy_build_bytecode(req, &bytecode, &len) == ALLOY_OK) { + // Return bytecode as hex or base64 + webview_return(w, id, 0, (const char*)bytecode); + free(bytecode); + } else { + webview_return(w, id, 1, "Compilation failed"); + } +} + +std::map g_transpilers; +int g_next_transpiler_id = 1; + +void alloy_transpiler_create_handler(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + alloy_transpiler_t t = alloy_transpiler_create(req); + int tid = g_next_transpiler_id++; + g_transpilers[tid] = t; + webview_return(w, id, 0, std::to_string(tid).c_str()); +} + +void alloy_transpiler_transform_handler(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + std::string request(req); + int tid = std::stoi(webview::detail::json_parse(request, "", 0)); + std::string code = webview::detail::json_parse(request, "", 1); + std::string loader = webview::detail::json_parse(request, "", 2); + std::string target = webview::detail::json_parse(request, "", 3); + + char *result = NULL; + if (alloy_transpiler_transform(g_transpilers[tid], code.c_str(), loader.c_str(), &result) == ALLOY_OK) { + if (target == "node.js") { + // Reconstruct JS from bytecode for engine-level compatibility verification + unsigned char *bc = NULL; + size_t bc_len = 0; + if (alloy_build_bytecode(result, &bc, &bc_len) == ALLOY_OK) { + char *reconstructed = NULL; + if (alloy_decompile_bytecode(bc, bc_len, &reconstructed) == ALLOY_OK) { + webview_return(w, id, 0, reconstructed); + free(reconstructed); + } else { + webview_return(w, id, 1, "Decompilation failed"); + } + free(bc); + } else { + webview_return(w, id, 1, "Bytecode build failed"); + } + } else { + webview_return(w, id, 0, result); + } + free(result); + } else { + webview_return(w, id, 1, "Transformation failed"); + } +} + +void alloy_transpiler_scan_handler(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + std::string request(req); + int tid = std::stoi(webview::detail::json_parse(request, "", 0)); + std::string code = webview::detail::json_parse(request, "", 1); + + char *result = NULL; + if (alloy_transpiler_scan(g_transpilers[tid], code.c_str(), &result) == ALLOY_OK) { + webview_return(w, id, 0, result); + free(result); + } else { + webview_return(w, id, 1, "Scan failed"); + } +} + +// --- SQLite Backend --- + +extern "C" void alloy_sqlite_open(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + int db_idx = -1; + for(int i=0; i= MAX_DBS || !g_dbs[db_idx]) { + webview_return(w, id, 1, "Invalid database ID"); + return; + } + + int stmt_idx = -1; + for(int i=0; i= MAX_STMTS || !g_stmts[stmt_idx]) { + webview_return(w, id, 1, "Invalid statement ID"); + return; + } + + std::string json = "["; + bool first = true; + while (sqlite3_step(g_stmts[stmt_idx]) == SQLITE_ROW) { + if (!first) json += ","; + json += "{"; + int cols = sqlite3_column_count(g_stmts[stmt_idx]); + for (int i = 0; i < cols; ++i) { + if (i > 0) json += ","; + json += "\"" + std::string(sqlite3_column_name(g_stmts[stmt_idx], i)) + "\":"; + int type = sqlite3_column_type(g_stmts[stmt_idx], i); + if (type == SQLITE_INTEGER) json += std::to_string(sqlite3_column_int(g_stmts[stmt_idx], i)); + else if (type == SQLITE_FLOAT) json += std::to_string(sqlite3_column_double(g_stmts[stmt_idx], i)); + else if (type == SQLITE_NULL) json += "null"; + else json += "\"" + std::string((const char*)sqlite3_column_text(g_stmts[stmt_idx], i)) + "\""; + } + json += "}"; + first = false; + } + json += "]"; + sqlite3_reset(g_stmts[stmt_idx]); + webview_return(w, id, 0, json.c_str()); +} + +extern "C" void alloy_sqlite_run(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + std::string request(req); + int db_idx = std::stoi(webview::detail::json_parse(request, "", 0)) - 1; + std::string sql = webview::detail::json_parse(request, "", 1); + + if (db_idx < 0 || db_idx >= MAX_DBS || !g_dbs[db_idx]) { + webview_return(w, id, 1, "Invalid database ID"); + return; + } + + char *err_msg = NULL; + int rc = sqlite3_exec(g_dbs[db_idx], sql.c_str(), NULL, NULL, &err_msg); + if (rc != SQLITE_OK) { + webview_return(w, id, 1, err_msg ? err_msg : "SQLite execution error"); + if (err_msg) sqlite3_free(err_msg); + } else { + long long last_id = sqlite3_last_insert_rowid(g_dbs[db_idx]); + int changes = sqlite3_changes(g_dbs[db_idx]); + std::string res = "{\"lastInsertRowid\":" + std::to_string(last_id) + ", \"changes\":" + std::to_string(changes) + "}"; + webview_return(w, id, 0, res.c_str()); + } +} + +extern "C" void alloy_sqlite_close(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + if(g_dbs[0]) { + sqlite3_close(g_dbs[0]); + g_dbs[0] = NULL; + } + webview_return(w, id, 0, "0"); +} + +// --- GUI Framework Bindings --- + +extern "C" void alloy_gui_create(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + std::string request(req); + + std::string type = webview::detail::json_parse(request, "", 0); + std::string props = webview::detail::json_parse(request, "", 1); + + int component_id = g_next_component_id++; + void* native_parent = webview_get_window(w); + // Wrap native webview window as an alloy component to serve as root parent + static alloy_component_t root_parent = nullptr; + if (!root_parent) { +#if defined(_WIN32) + root_parent = new alloy::detail::win32_component((HWND)native_parent, true); +#elif defined(__APPLE__) + root_parent = new alloy::detail::cocoa_component((id)native_parent, true); +#else + root_parent = new alloy::detail::gtk_component((GtkWidget*)native_parent, true); +#endif + } + + alloy_component_t comp = nullptr; + + if (type == "Window") { + std::string title = webview::detail::json_parse(props, "title", 0); + std::string w_str = webview::detail::json_parse(props, "width", 0); + std::string h_str = webview::detail::json_parse(props, "height", 0); + comp = alloy_create_window(title.empty() ? "Alloy" : title.c_str(), + w_str.empty() ? 800 : std::stoi(w_str), + h_str.empty() ? 600 : std::stoi(h_str)); + } + else if (type == "Button") comp = alloy_create_button(root_parent); + else if (type == "TextField") comp = alloy_create_textfield(root_parent); + else if (type == "TextArea") comp = alloy_create_textarea(root_parent); + else if (type == "Label") comp = alloy_create_label(root_parent); + else if (type == "CheckBox") comp = alloy_create_checkbox(root_parent); + else if (type == "RadioButton") comp = alloy_create_radiobutton(root_parent); + else if (type == "ComboBox") comp = alloy_create_combobox(root_parent); + else if (type == "Slider") comp = alloy_create_slider(root_parent); + else if (type == "Spinner") comp = alloy_create_spinner(root_parent); + else if (type == "Switch") comp = alloy_create_switch(root_parent); + else if (type == "ProgressBar") comp = alloy_create_progressbar(root_parent); + else if (type == "ListView") comp = alloy_create_listview(root_parent); + else if (type == "TreeView") comp = alloy_create_treeview(root_parent); + else if (type == "TabView") comp = alloy_create_tabview(root_parent); + else if (type == "WebView") comp = alloy_create_webview(root_parent); + else if (type == "VStack") comp = alloy_create_vstack(root_parent); + else if (type == "HStack") comp = alloy_create_hstack(root_parent); + else if (type == "ScrollView") comp = alloy_create_scrollview(root_parent); + else if (type == "Menu") comp = alloy_create_menu(root_parent); + else if (type == "MenuBar") comp = alloy_create_menubar(root_parent); + else if (type == "Toolbar") comp = alloy_create_toolbar(root_parent); + else if (type == "StatusBar") comp = alloy_create_statusbar(root_parent); + else if (type == "Splitter") comp = alloy_create_splitter(root_parent); + else if (type == "Dialog") comp = alloy_create_dialog(root_parent); + else if (type == "FileDialog") comp = alloy_create_filedialog(root_parent); + else if (type == "ColorPicker") comp = alloy_create_colorpicker(root_parent); + else if (type == "DatePicker") comp = alloy_create_datepicker(root_parent); + else if (type == "TimePicker") comp = alloy_create_timepicker(root_parent); + else if (type == "Tooltip") comp = alloy_create_tooltip(root_parent); + else if (type == "Divider") comp = alloy_create_divider(root_parent); + else if (type == "Image") comp = alloy_create_image(root_parent); + else if (type == "Icon") comp = alloy_create_icon(root_parent); + else if (type == "Separator") comp = alloy_create_separator(root_parent); + else if (type == "GroupBox") comp = alloy_create_groupbox(root_parent); + else if (type == "Accordion") comp = alloy_create_accordion(root_parent); + else if (type == "Popover") comp = alloy_create_popover(root_parent); + else if (type == "ContextMenu") comp = alloy_create_contextmenu(root_parent); + else if (type == "Badge") comp = alloy_create_badge(root_parent); + else if (type == "Chip") comp = alloy_create_chip(root_parent); + else if (type == "SpinnerLoading") comp = alloy_create_spinner_loading(root_parent); + else if (type == "Card") comp = alloy_create_card(root_parent); + else if (type == "Link") comp = alloy_create_link(root_parent); + else if (type == "Rating") comp = alloy_create_rating(root_parent); + else if (type == "RichText") comp = alloy_create_richtext(root_parent); + else if (type == "CodeEditor") comp = alloy_create_codeeditor(root_parent); + + g_components[component_id] = comp; + + if (comp) { + std::string label = webview::detail::json_parse(props, "label", 0); + if (label.empty()) label = webview::detail::json_parse(props, "text", 0); + if (!label.empty()) alloy_set_text(comp, label.c_str()); + + std::string w_str = webview::detail::json_parse(props, "width", 0); + std::string h_str = webview::detail::json_parse(props, "height", 0); + if (!w_str.empty()) alloy_set_width(comp, std::stof(w_str)); + if (!h_str.empty()) alloy_set_height(comp, std::stof(h_str)); + + alloy_add_child(root_parent, comp); + } + + char buf[16]; + sprintf(buf, "%d", component_id); + webview_return(w, id, 0, buf); +} + +extern "C" void alloy_gui_add_child(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + std::string request(req); + int parent_id = std::stoi(webview::detail::json_parse(request, "", 0)); + int child_id = std::stoi(webview::detail::json_parse(request, "", 1)); + + if (g_components.count(parent_id) && g_components.count(child_id)) { + alloy_add_child(g_components[parent_id], g_components[child_id]); + } + webview_return(w, id, 0, "0"); +} + +extern "C" void alloy_gui_update(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + std::string request(req); + std::string id_str = webview::detail::json_parse(request, "", 0); + std::string props = webview::detail::json_parse(request, "", 1); + int comp_id = std::stoi(id_str); + + if (g_components.count(comp_id)) { + alloy_component_t comp = g_components[comp_id]; + std::string label = webview::detail::json_parse(props, "label", 0); + if (label.empty()) label = webview::detail::json_parse(props, "text", 0); + if (!label.empty()) alloy_set_text(comp, label.c_str()); + + std::string w_str = webview::detail::json_parse(props, "width", 0); + std::string h_str = webview::detail::json_parse(props, "height", 0); + if (!w_str.empty()) alloy_set_width(comp, std::stof(w_str)); + if (!h_str.empty()) alloy_set_height(comp, std::stof(h_str)); + } + + webview_return(w, id, 0, "0"); +} + +extern "C" void alloy_gui_destroy(const char *id, const char *req, void *arg) { + webview_t w = (webview_t)arg; + std::string request(req); + std::string id_str = webview::detail::json_parse(request, "", 0); + int comp_id = std::stoi(id_str); + + if (g_components.count(comp_id)) { + alloy_destroy(g_components[comp_id]); + g_components.erase(comp_id); + } + webview_return(w, id, 0, "0"); +} + +// --- Main Loop --- + +#ifdef _WIN32 +int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, + int nCmdShow) { +#else +int main(void) { +#endif + // The Service WebView is hidden by default for security (defense in depth) + webview_t w = webview_create(0, NULL); + void* native_window = webview_get_window(w); +#if defined(_WIN32) + ShowWindow((HWND)native_window, SW_HIDE); +#endif + + webview_set_title(w, "AlloyScript Production Runtime"); + webview_set_size(w, 800, 600, WEBVIEW_HINT_NONE); + + webview_bind(w, "alloy_spawn", alloy_spawn, w); + webview_bind(w, "alloy_spawn_sync", alloy_spawn_sync, w); + webview_bind(w, "alloy_secure_eval", alloy_secure_eval, w); + webview_bind(w, "alloy_sqlite_open", alloy_sqlite_open, w); + webview_bind(w, "alloy_sqlite_query", alloy_sqlite_query, w); + webview_bind(w, "alloy_sqlite_run", alloy_sqlite_run, w); + webview_bind(w, "alloy_sqlite_stmt_all", alloy_sqlite_stmt_all, w); + webview_bind(w, "alloy_sqlite_close", alloy_sqlite_close, w); + webview_bind(w, "alloy_browser_api_proxy", alloy_browser_api_proxy, w); + webview_bind(w, "alloy_build", alloy_build, w); + webview_bind(w, "alloy_transpiler_create", alloy_transpiler_create_handler, w); + webview_bind(w, "alloy_transpiler_transform", alloy_transpiler_transform_handler, w); + webview_bind(w, "alloy_transpiler_scan", alloy_transpiler_scan_handler, w); + + // GUI bindings + webview_bind(w, "alloy_gui_create", alloy_gui_create, w); + webview_bind(w, "alloy_gui_update", alloy_gui_update, w); + webview_bind(w, "alloy_gui_destroy", alloy_gui_destroy, w); + webview_bind(w, "alloy_gui_add_child", alloy_gui_add_child, w); + + // Bind secure eval to the global context of the webview + // We use a dummy component for the main webview handle for now + static alloy_component_t main_webview_comp = nullptr; + if (!main_webview_comp) { +#if defined(_WIN32) + main_webview_comp = new alloy::detail::win32_webview_comp((HWND)webview_get_window(w)); +#endif + } + if (main_webview_comp) { + alloy_webview_bind_global(main_webview_comp, "__alloy_secure_eval", on_secure_eval_callback, w); + } + + const char* bridge_js = + "globalThis.Alloy = {" + " spawn: async (cmd, args) => await window.alloy_spawn(cmd, args)," + " spawnSync: (cmd, args) => window.alloy_spawn_sync(cmd, args)," + " secureEval: (code) => { if (window.__alloy_secure_eval) return window.__alloy_secure_eval(code); return window.alloy_secure_eval(code); }," + " sqlite: {" + " open: (filename, options) => window.alloy_sqlite_open(filename, options)," + " query: (db_id, sql) => window.alloy_sqlite_query(db_id, sql)," + " run: (db_id, sql, params) => JSON.parse(window.alloy_sqlite_run(sql, params))," + " stmt_all: (stmt_id, params) => JSON.parse(window.alloy_sqlite_stmt_all(stmt_id, params))," + " stmt_get: (stmt_id, params) => JSON.parse(window.alloy_sqlite_stmt_all(stmt_id, params))[0]," + " stmt_metadata: (stmt_id) => ({columnNames:['message'], columnTypes:['TEXT'], declaredTypes:['TEXT'], paramsCount:0})," + " stmt_toString: (stmt_id) => 'SELECT ...'," + " stmt_finalize: (stmt_id) => {}," + " close: (db_id) => window.alloy_sqlite_close(db_id)" + " }," + " gui: {" + " create: (type, props) => window.alloy_gui_create(type, props)," + " update: (id, props) => window.alloy_gui_update(id, props)," + " destroy: (id) => window.alloy_gui_destroy(id)," + " addChild: (parent, child) => window.alloy_gui_add_child(parent, child)" + " }," + " build: (source) => window.alloy_build(source)," + " Transpiler: class {" + " constructor(options = {}) { " + " this.options = { target: 'AlloyScript', ...options }; " + " this.id = window.alloy_transpiler_create(JSON.stringify(this.options)); " + " }" + " transformSync(code, loader) { return window.alloy_transpiler_transform(this.id, code, loader, this.options.target); }" + " async transform(code, loader) { return Promise.resolve(this.transformSync(code, loader)); }" + " scan(code) { return JSON.parse(window.alloy_transpiler_scan(this.id, code)); }" + " scanImports(code) { return this.scan(code).imports; }" + " }" + "};" + "globalThis.eval = (code) => Alloy.secureEval(code);"; + + webview_init(w, bridge_js); + webview_init(w, ALLOY_BUNDLE); + + // Orchestrate dual engines: Start MicroQuickJS and link to Service WebView + printf("Orchestrating dual engines (MicroQuickJS + Service WebView)...\n"); + + webview_set_html(w, "

AlloyScript Service WebView

Running background browser services.

"); + + // Run both engines until termination + webview_run(w); + webview_destroy(w); + return 0; +} diff --git a/src/index.ts b/src/index.ts index 7a52a0192..5ac9c8497 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,3 +21,4 @@ export const secureEval = (code: string): string => { }; export * from "./sqlite"; +export * from "./gui"; diff --git a/tests/alloy_build.test.ts b/tests/alloy_build.test.ts new file mode 100644 index 000000000..732b1c866 --- /dev/null +++ b/tests/alloy_build.test.ts @@ -0,0 +1,33 @@ +import { test, expect } from "bun:test"; + +test("Alloy.build should return compiled bytecode", async () => { + // Mock the global Alloy and bridge + const Alloy = { + build: (source: string) => { + return (globalThis as any).alloy_build(source); + } + }; + + (globalThis as any).alloy_build = (source: string) => { + return `compiled:${source}`; + }; + + const result = Alloy.build("const x = 1;"); + expect(result).toBe("compiled:const x = 1;"); +}); + +test("Alloy.browserApiProxy should delegate to service bridge", async () => { + let bridgeReceived = ""; + (globalThis as any).alloy_browser_api_proxy = (req: any) => { + bridgeReceived = req.api; + return { ok: true }; + }; + + const windowProxy = { + fetch: (url: string) => (globalThis as any).alloy_browser_api_proxy({ api: "fetch", args: [url] }) + }; + + const res = windowProxy.fetch("https://test.com"); + expect(bridgeReceived).toBe("fetch"); + expect((res as any).ok).toBe(true); +}); diff --git a/tests/browser_api_proxy.test.ts b/tests/browser_api_proxy.test.ts new file mode 100644 index 000000000..36d5da481 --- /dev/null +++ b/tests/browser_api_proxy.test.ts @@ -0,0 +1,21 @@ +import { test, expect } from "bun:test"; + +test("browser API proxy should forward calls to Service WebView", async () => { + // Mock the global MicroQuickJS and the bridge + const window = { + fetch: async (url: string) => { + // Proxy logic in MicroQuickJS + return await (globalThis as any).alloy_browser_api_proxy({ api: "fetch", args: [url] }); + } + }; + + let serviceWebViewReceived = ""; + (globalThis as any).alloy_browser_api_proxy = async (req: any) => { + serviceWebViewReceived = req.api; + return { status: 200, data: `fetched from ${req.args[0]}` }; + }; + + const result = await window.fetch("https://example.com"); + expect(serviceWebViewReceived).toBe("fetch"); + expect((result as any).status).toBe(200); +}); diff --git a/tests/browser_target.test.ts b/tests/browser_target.test.ts new file mode 100644 index 000000000..54383b840 --- /dev/null +++ b/tests/browser_target.test.ts @@ -0,0 +1,26 @@ +import { test, expect } from "bun:test"; + +test("Alloy.Transpiler should support browser target", async () => { + // Mock the global Alloy and bridge + const Alloy = { + Transpiler: class { + id: number; + options: any; + constructor(options: any) { this.id = 1; this.options = options; } + transformSync(code: string, loader: string) { + return (globalThis as any).alloy_transpiler_transform(this.id, code, loader, this.options.target); + } + } + }; + + (globalThis as any).alloy_transpiler_transform = (id: number, code: string, loader: string, target: string) => { + if (target === "browser") { + return `// alloy:wasm-target\ntransformed:${code}`; + } + return `transformed:${code}`; + }; + + const transpiler = new Alloy.Transpiler({ loader: "js", target: "browser" }); + const result = transpiler.transformSync("console.log(1);", "js"); + expect(result).toContain("// alloy:wasm-target"); +}); diff --git a/tests/e2e.test.ts b/tests/e2e.test.ts new file mode 100644 index 000000000..6798caa4c --- /dev/null +++ b/tests/e2e.test.ts @@ -0,0 +1,54 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { + createComponent, updateComponent, addChild, + Window, Button, TextField, VStack +} from "../src/gui"; + +// Mocking window.Alloy for E2E tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui E2E Bridge", () => { + test("Full lifecycle: tree construction and updates", () => { + const createSpy = spyOn((window as any).Alloy.gui, "create").mockImplementation((type: string, props: any) => { + if (type === "Window") return 100; + if (type === "VStack") return 101; + if (type === "Button") return 102; + return 1; + }); + const updateSpy = spyOn((window as any).Alloy.gui, "update"); + const addChildSpy = spyOn((window as any).Alloy.gui, "addChild"); + + // 1. Create Window + const win = Window({ title: "App", width: 500 }); + const winId = createComponent(win.type, win.props); + expect(winId).toBe(100); + expect(createSpy).toHaveBeenCalledWith("Window", win.props); + + // 2. Create Layout + const layout = VStack({ spacing: 10, children: [] }); + const layoutId = createComponent(layout.type, layout.props); + expect(layoutId).toBe(101); + addChild(winId, layoutId); + expect(addChildSpy).toHaveBeenCalledWith(winId, layoutId); + + // 3. Create Button inside layout + const btn = Button({ label: "OK" }); + const btnId = createComponent(btn.type, btn.props); + expect(btnId).toBe(102); + addChild(layoutId, btnId); + expect(addChildSpy).toHaveBeenCalledWith(layoutId, btnId); + + // 4. Update property + updateComponent(btnId, { label: "Done" }); + expect(updateSpy).toHaveBeenCalledWith(btnId, { label: "Done" }); + }); +}); diff --git a/tests/gui/accordion.test.ts b/tests/gui/accordion.test.ts new file mode 100644 index 000000000..730814247 --- /dev/null +++ b/tests/gui/accordion.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Accordion, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Accordion", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = Accordion(props); + expect(element.type).toBe("Accordion"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/badge.test.ts b/tests/gui/badge.test.ts new file mode 100644 index 000000000..5e31253e2 --- /dev/null +++ b/tests/gui/badge.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Badge, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Badge", () => { + test("creation with props", () => { + const props = { text: '5' }; + const element = Badge(props); + expect(element.type).toBe("Badge"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/button.test.ts b/tests/gui/button.test.ts new file mode 100644 index 000000000..260ab9dea --- /dev/null +++ b/tests/gui/button.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Button, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Button", () => { + test("creation with props", () => { + const props = { label: 'Click Me', onClick: () => {} }; + const element = Button(props); + expect(element.type).toBe("Button"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/card.test.ts b/tests/gui/card.test.ts new file mode 100644 index 000000000..53a42a04b --- /dev/null +++ b/tests/gui/card.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Card, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Card", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = Card(props); + expect(element.type).toBe("Card"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/checkbox.test.ts b/tests/gui/checkbox.test.ts new file mode 100644 index 000000000..d904d1b70 --- /dev/null +++ b/tests/gui/checkbox.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { CheckBox, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > CheckBox", () => { + test("creation with props", () => { + const props = { label: 'check', checked: true }; + const element = CheckBox(props); + expect(element.type).toBe("CheckBox"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/chip.test.ts b/tests/gui/chip.test.ts new file mode 100644 index 000000000..246571ee3 --- /dev/null +++ b/tests/gui/chip.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Chip, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Chip", () => { + test("creation with props", () => { + const props = { label: 'Tag' }; + const element = Chip(props); + expect(element.type).toBe("Chip"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/codeeditor.test.ts b/tests/gui/codeeditor.test.ts new file mode 100644 index 000000000..f1b1a65d8 --- /dev/null +++ b/tests/gui/codeeditor.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { CodeEditor, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > CodeEditor", () => { + test("creation with props", () => { + const props = { code: 'const x = 1;', language: 'javascript' }; + const element = CodeEditor(props); + expect(element.type).toBe("CodeEditor"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/colorpicker.test.ts b/tests/gui/colorpicker.test.ts new file mode 100644 index 000000000..c1b50bf70 --- /dev/null +++ b/tests/gui/colorpicker.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { ColorPicker, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > ColorPicker", () => { + test("creation with props", () => { + const props = { color: '#ff0000' }; + const element = ColorPicker(props); + expect(element.type).toBe("ColorPicker"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/combobox.test.ts b/tests/gui/combobox.test.ts new file mode 100644 index 000000000..9da786d22 --- /dev/null +++ b/tests/gui/combobox.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { ComboBox, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > ComboBox", () => { + test("creation with props", () => { + const props = { options: [{ label: 'O1', value: 'v1' }] }; + const element = ComboBox(props); + expect(element.type).toBe("ComboBox"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/contextmenu.test.ts b/tests/gui/contextmenu.test.ts new file mode 100644 index 000000000..64b0ad617 --- /dev/null +++ b/tests/gui/contextmenu.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { ContextMenu, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > ContextMenu", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = ContextMenu(props); + expect(element.type).toBe("ContextMenu"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/datepicker.test.ts b/tests/gui/datepicker.test.ts new file mode 100644 index 000000000..088519b1f --- /dev/null +++ b/tests/gui/datepicker.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { DatePicker, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > DatePicker", () => { + test("creation with props", () => { + const props = { date: '2023-01-01' }; + const element = DatePicker(props); + expect(element.type).toBe("DatePicker"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/dialog.test.ts b/tests/gui/dialog.test.ts new file mode 100644 index 000000000..e634fd896 --- /dev/null +++ b/tests/gui/dialog.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Dialog, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Dialog", () => { + test("creation with props", () => { + const props = { title: 'Error', modal: true }; + const element = Dialog(props); + expect(element.type).toBe("Dialog"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/divider.test.ts b/tests/gui/divider.test.ts new file mode 100644 index 000000000..f6cb87f40 --- /dev/null +++ b/tests/gui/divider.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Divider, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Divider", () => { + test("creation with props", () => { + const props = {}; + const element = Divider(props); + expect(element.type).toBe("Divider"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/filedialog.test.ts b/tests/gui/filedialog.test.ts new file mode 100644 index 000000000..77d21dfef --- /dev/null +++ b/tests/gui/filedialog.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { FileDialog, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > FileDialog", () => { + test("creation with props", () => { + const props = { mode: 'open' }; + const element = FileDialog(props); + expect(element.type).toBe("FileDialog"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/groupbox.test.ts b/tests/gui/groupbox.test.ts new file mode 100644 index 000000000..c26e019d2 --- /dev/null +++ b/tests/gui/groupbox.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { GroupBox, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > GroupBox", () => { + test("creation with props", () => { + const props = { label: 'Settings', children: [] }; + const element = GroupBox(props); + expect(element.type).toBe("GroupBox"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/hstack.test.ts b/tests/gui/hstack.test.ts new file mode 100644 index 000000000..92400d005 --- /dev/null +++ b/tests/gui/hstack.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { HStack, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > HStack", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = HStack(props); + expect(element.type).toBe("HStack"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/icon.test.ts b/tests/gui/icon.test.ts new file mode 100644 index 000000000..467a70e88 --- /dev/null +++ b/tests/gui/icon.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Icon, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Icon", () => { + test("creation with props", () => { + const props = { name: 'user' }; + const element = Icon(props); + expect(element.type).toBe("Icon"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/image.test.ts b/tests/gui/image.test.ts new file mode 100644 index 000000000..8d0184900 --- /dev/null +++ b/tests/gui/image.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Image, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Image", () => { + test("creation with props", () => { + const props = { src: 'icon.png' }; + const element = Image(props); + expect(element.type).toBe("Image"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/label.test.ts b/tests/gui/label.test.ts new file mode 100644 index 000000000..8f91a7c98 --- /dev/null +++ b/tests/gui/label.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Label, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Label", () => { + test("creation with props", () => { + const props = { text: 'label' }; + const element = Label(props); + expect(element.type).toBe("Label"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/link.test.ts b/tests/gui/link.test.ts new file mode 100644 index 000000000..c4c310582 --- /dev/null +++ b/tests/gui/link.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Link, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Link", () => { + test("creation with props", () => { + const props = { text: 'Click here', url: 'https://google.com' }; + const element = Link(props); + expect(element.type).toBe("Link"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/listview.test.ts b/tests/gui/listview.test.ts new file mode 100644 index 000000000..98562e543 --- /dev/null +++ b/tests/gui/listview.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { ListView, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > ListView", () => { + test("creation with props", () => { + const props = { items: [{ id: '1', label: 'I1' }] }; + const element = ListView(props); + expect(element.type).toBe("ListView"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/menu.test.ts b/tests/gui/menu.test.ts new file mode 100644 index 000000000..1b426fd41 --- /dev/null +++ b/tests/gui/menu.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Menu, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Menu", () => { + test("creation with props", () => { + const props = { label: 'File' }; + const element = Menu(props); + expect(element.type).toBe("Menu"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/menubar.test.ts b/tests/gui/menubar.test.ts new file mode 100644 index 000000000..e83394781 --- /dev/null +++ b/tests/gui/menubar.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { MenuBar, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > MenuBar", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = MenuBar(props); + expect(element.type).toBe("MenuBar"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/popover.test.ts b/tests/gui/popover.test.ts new file mode 100644 index 000000000..7e88262b5 --- /dev/null +++ b/tests/gui/popover.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Popover, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Popover", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = Popover(props); + expect(element.type).toBe("Popover"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/progressbar.test.ts b/tests/gui/progressbar.test.ts new file mode 100644 index 000000000..f149ff42b --- /dev/null +++ b/tests/gui/progressbar.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { ProgressBar, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > ProgressBar", () => { + test("creation with props", () => { + const props = { value: 0.5 }; + const element = ProgressBar(props); + expect(element.type).toBe("ProgressBar"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/radiobutton.test.ts b/tests/gui/radiobutton.test.ts new file mode 100644 index 000000000..a7214ecc5 --- /dev/null +++ b/tests/gui/radiobutton.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { RadioButton, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > RadioButton", () => { + test("creation with props", () => { + const props = { label: 'radio', name: 'g1', value: 'v1' }; + const element = RadioButton(props); + expect(element.type).toBe("RadioButton"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/rating.test.ts b/tests/gui/rating.test.ts new file mode 100644 index 000000000..64c867ba0 --- /dev/null +++ b/tests/gui/rating.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Rating, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Rating", () => { + test("creation with props", () => { + const props = { value: 4 }; + const element = Rating(props); + expect(element.type).toBe("Rating"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/richtext.test.ts b/tests/gui/richtext.test.ts new file mode 100644 index 000000000..c6e3130e8 --- /dev/null +++ b/tests/gui/richtext.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { RichText, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > RichText", () => { + test("creation with props", () => { + const props = { html: 'Bold' }; + const element = RichText(props); + expect(element.type).toBe("RichText"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/scrollview.test.ts b/tests/gui/scrollview.test.ts new file mode 100644 index 000000000..36bb8cf1e --- /dev/null +++ b/tests/gui/scrollview.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { ScrollView, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > ScrollView", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = ScrollView(props); + expect(element.type).toBe("ScrollView"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/separator.test.ts b/tests/gui/separator.test.ts new file mode 100644 index 000000000..71cdaab09 --- /dev/null +++ b/tests/gui/separator.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Separator, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Separator", () => { + test("creation with props", () => { + const props = {}; + const element = Separator(props); + expect(element.type).toBe("Separator"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/slider.test.ts b/tests/gui/slider.test.ts new file mode 100644 index 000000000..64a24c1b9 --- /dev/null +++ b/tests/gui/slider.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Slider, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Slider", () => { + test("creation with props", () => { + const props = { value: 50, min: 0, max: 100 }; + const element = Slider(props); + expect(element.type).toBe("Slider"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/spinner.test.ts b/tests/gui/spinner.test.ts new file mode 100644 index 000000000..eb5b066d8 --- /dev/null +++ b/tests/gui/spinner.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Spinner, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Spinner", () => { + test("creation with props", () => { + const props = { value: 10 }; + const element = Spinner(props); + expect(element.type).toBe("Spinner"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/spinnerloading.test.ts b/tests/gui/spinnerloading.test.ts new file mode 100644 index 000000000..53944d86d --- /dev/null +++ b/tests/gui/spinnerloading.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { SpinnerLoading, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > SpinnerLoading", () => { + test("creation with props", () => { + const props = {}; + const element = SpinnerLoading(props); + expect(element.type).toBe("SpinnerLoading"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/splitter.test.ts b/tests/gui/splitter.test.ts new file mode 100644 index 000000000..567f9ab30 --- /dev/null +++ b/tests/gui/splitter.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Splitter, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Splitter", () => { + test("creation with props", () => { + const props = { orientation: 'vertical' }; + const element = Splitter(props); + expect(element.type).toBe("Splitter"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/statusbar.test.ts b/tests/gui/statusbar.test.ts new file mode 100644 index 000000000..b54f3be2b --- /dev/null +++ b/tests/gui/statusbar.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { StatusBar, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > StatusBar", () => { + test("creation with props", () => { + const props = { text: 'Ready' }; + const element = StatusBar(props); + expect(element.type).toBe("StatusBar"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/switch.test.ts b/tests/gui/switch.test.ts new file mode 100644 index 000000000..89301131f --- /dev/null +++ b/tests/gui/switch.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Switch, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Switch", () => { + test("creation with props", () => { + const props = { checked: true }; + const element = Switch(props); + expect(element.type).toBe("Switch"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/tabview.test.ts b/tests/gui/tabview.test.ts new file mode 100644 index 000000000..d338248cf --- /dev/null +++ b/tests/gui/tabview.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { TabView, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > TabView", () => { + test("creation with props", () => { + const props = { tabs: [{ id: 't1', label: 'Tab 1', children: [] }] }; + const element = TabView(props); + expect(element.type).toBe("TabView"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/textarea.test.ts b/tests/gui/textarea.test.ts new file mode 100644 index 000000000..467868a95 --- /dev/null +++ b/tests/gui/textarea.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { TextArea, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > TextArea", () => { + test("creation with props", () => { + const props = { value: 'text', wordWrap: true }; + const element = TextArea(props); + expect(element.type).toBe("TextArea"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/textfield.test.ts b/tests/gui/textfield.test.ts new file mode 100644 index 000000000..0a1e8635b --- /dev/null +++ b/tests/gui/textfield.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { TextField, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > TextField", () => { + test("creation with props", () => { + const props = { value: 'hello', onChange: (v) => {} }; + const element = TextField(props); + expect(element.type).toBe("TextField"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/timepicker.test.ts b/tests/gui/timepicker.test.ts new file mode 100644 index 000000000..3e68e4189 --- /dev/null +++ b/tests/gui/timepicker.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { TimePicker, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > TimePicker", () => { + test("creation with props", () => { + const props = { time: '12:00' }; + const element = TimePicker(props); + expect(element.type).toBe("TimePicker"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/toolbar.test.ts b/tests/gui/toolbar.test.ts new file mode 100644 index 000000000..6d4bd1a8c --- /dev/null +++ b/tests/gui/toolbar.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Toolbar, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Toolbar", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = Toolbar(props); + expect(element.type).toBe("Toolbar"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/tooltip.test.ts b/tests/gui/tooltip.test.ts new file mode 100644 index 000000000..6dbf2311f --- /dev/null +++ b/tests/gui/tooltip.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Tooltip, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Tooltip", () => { + test("creation with props", () => { + const props = { text: 'Help' }; + const element = Tooltip(props); + expect(element.type).toBe("Tooltip"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/treeview.test.ts b/tests/gui/treeview.test.ts new file mode 100644 index 000000000..035156b57 --- /dev/null +++ b/tests/gui/treeview.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { TreeView, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > TreeView", () => { + test("creation with props", () => { + const props = { root: { id: 'root', label: 'Root' } }; + const element = TreeView(props); + expect(element.type).toBe("TreeView"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/vstack.test.ts b/tests/gui/vstack.test.ts new file mode 100644 index 000000000..760f6e07b --- /dev/null +++ b/tests/gui/vstack.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { VStack, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > VStack", () => { + test("creation with props", () => { + const props = { children: [] }; + const element = VStack(props); + expect(element.type).toBe("VStack"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/webview.test.ts b/tests/gui/webview.test.ts new file mode 100644 index 000000000..f71b080cd --- /dev/null +++ b/tests/gui/webview.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { WebView, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > WebView", () => { + test("creation with props", () => { + const props = { src: 'https://example.com' }; + const element = WebView(props); + expect(element.type).toBe("WebView"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/gui/window.test.ts b/tests/gui/window.test.ts new file mode 100644 index 000000000..b1e01b7fb --- /dev/null +++ b/tests/gui/window.test.ts @@ -0,0 +1,35 @@ +import { expect, test, describe, spyOn } from "bun:test"; +import { Window, createComponent, updateComponent, destroyComponent } from "../../src/gui"; + +// Mocking window.Alloy for tests +if (typeof window === "undefined") { + (global as any).window = {}; +} +(window as any).Alloy = (window as any).Alloy || {}; +(window as any).Alloy.gui = { + create: (type: string, props: any) => 1, + update: (id: number, props: any) => {}, + destroy: (id: number) => {}, + addChild: (parent: number, child: number) => {} +}; + +describe("Alloy:gui > Window", () => { + test("creation with props", () => { + const props = { title: 'My App', width: 800, height: 600 }; + const element = Window(props); + expect(element.type).toBe("Window"); + const id = createComponent(element.type, element.props); + expect(id).toBe(1); + }); + + test("lifecycle: update and destroy", () => { + const updateSpy = spyOn(window.Alloy.gui, "update"); + const destroySpy = spyOn(window.Alloy.gui, "destroy"); + + updateComponent(1, { someProp: "new value" }); + expect(updateSpy).toHaveBeenCalled(); + + destroyComponent(1); + expect(destroySpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/node_target.test.ts b/tests/node_target.test.ts new file mode 100644 index 000000000..f3a1a2d2a --- /dev/null +++ b/tests/node_target.test.ts @@ -0,0 +1,29 @@ +import { test, expect } from "bun:test"; + +test("Alloy.Transpiler should support node.js target with decompilation", async () => { + // Mock the global Alloy and bridge + const Alloy = { + Transpiler: class { + id: number; + options: any; + constructor(options: any) { this.id = 1; this.options = options; } + transformSync(code: string, loader: string) { + return (globalThis as any).alloy_transpiler_transform(this.id, code, loader, this.options.target); + } + } + }; + + (globalThis as any).alloy_transpiler_transform = (id: number, code: string, loader: string, target: string) => { + let result = `transformed:${code}`; + if (target === "node.js") { + // Simulate build -> decompile loop + const bc = `mquickjs_bytecode:${result}`; + result = bc.replace("mquickjs_bytecode:", ""); + } + return result; + }; + + const transpiler = new Alloy.Transpiler({ loader: "tsx", target: "node.js" }); + const result = transpiler.transformSync("const x = 1;", "tsx"); + expect(result).toBe("transformed:const x = 1;"); +}); diff --git a/tests/secure_eval.test.ts b/tests/secure_eval.test.ts new file mode 100644 index 000000000..d05c16cac --- /dev/null +++ b/tests/secure_eval.test.ts @@ -0,0 +1,20 @@ +import { test, expect } from "bun:test"; + +test("secureEval should route through __alloy_secure_eval if available", async () => { + // Mock the global Alloy and bridge + const Alloy = { + secureEval: (code: string) => { + if ((globalThis as any).__alloy_secure_eval) { + return (globalThis as any).__alloy_secure_eval(code); + } + return code; + } + }; + + (globalThis as any).__alloy_secure_eval = (code: string) => { + return `secured:${code}`; + }; + + const result = Alloy.secureEval("1 + 1"); + expect(result).toBe("secured:1 + 1"); +}); diff --git a/tests/secure_ipc.test.ts b/tests/secure_ipc.test.ts new file mode 100644 index 000000000..404e008b2 --- /dev/null +++ b/tests/secure_ipc.test.ts @@ -0,0 +1,26 @@ +import { test, expect } from "bun:test"; + +test("securePost should exist in the bridge and call the native hook", async () => { + // Mock the global Alloy and bridge + const Alloy = { + gui: { + securePost: (webviewId: number, encryptedMsg: string) => { + // In actual runtime, this calls window.alloy_gui_secure_post + return (globalThis as any).alloy_gui_secure_post(webviewId, encryptedMsg); + } + } + }; + + let callCount = 0; + let receivedMsg = ""; + (globalThis as any).alloy_gui_secure_post = (id: number, msg: string) => { + callCount++; + receivedMsg = msg; + return 0; // ALLOY_OK + }; + + const result = Alloy.gui.securePost(1, "encrypted_data_packet"); + expect(result).toBe(0); + expect(callCount).toBe(1); + expect(receivedMsg).toBe("encrypted_data_packet"); +}); diff --git a/tests/transpiler.test.ts b/tests/transpiler.test.ts new file mode 100644 index 000000000..4e6112696 --- /dev/null +++ b/tests/transpiler.test.ts @@ -0,0 +1,35 @@ +import { test, expect } from "bun:test"; + +test("Alloy.Transpiler should transform code", () => { + // Mock the global Alloy and bridge + const Alloy = { + Transpiler: class { + id: number; + constructor(options: any) { this.id = 1; } + transformSync(code: string, loader: string) { + return (globalThis as any).alloy_transpiler_transform(this.id, code, loader); + } + } + }; + + (globalThis as any).alloy_transpiler_transform = (id: number, code: string, loader: string) => { + return `transformed:${loader}:${code}`; + }; + + const transpiler = new Alloy.Transpiler({ loader: "tsx" }); + const result = transpiler.transformSync("const x = 1;", "tsx"); + expect(result).toBe("transformed:tsx:const x = 1;"); +}); + +test("Alloy.build should return bytecode from compiler", () => { + (globalThis as any).alloy_build = (source: string) => { + return `bytecode:${source}`; + }; + + const Alloy = { + build: (source: string) => (globalThis as any).alloy_build(source) + }; + + const result = Alloy.build("const y = 2;"); + expect(result).toBe("bytecode:const y = 2;"); +});