work in progress

This commit is contained in:
2026-01-16 12:43:06 +01:00
parent 77a9579025
commit c8ee7defe8
236 changed files with 11405 additions and 28 deletions

104
designer/CMakeLists.txt Normal file
View File

@@ -0,0 +1,104 @@
cmake_minimum_required(VERSION 3.22.1)
project(mosis-designer)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find Lua before RmlUi so it can be used
find_package(Lua REQUIRED)
# Find other dependencies via vcpkg
find_package(glfw3 CONFIG REQUIRED)
find_package(freetype CONFIG REQUIRED)
find_package(PNG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
# Fetch RmlUi
include(FetchContent)
FetchContent_Declare(
rmlui
GIT_REPOSITORY https://github.com/mikke89/RmlUi.git
GIT_TAG 6.0
)
# Enable RmlUi Lua bindings before fetching
set(RMLUI_LUA_BINDINGS ON CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(RMLUI_SAMPLES OFF CACHE BOOL "" FORCE)
set(RMLUI_TESTS OFF CACHE BOOL "" FORCE)
set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE)
FetchContent_MakeAvailable(rmlui)
# Get the RmlUi source directory for backend sources
FetchContent_GetProperties(rmlui)
set(RMLUI_SOURCE_DIR ${rmlui_SOURCE_DIR})
# Shared kernel library sources (platform-agnostic code)
set(KERNEL_SOURCES
../src/main/kernel/src/platform.cpp
../src/main/kernel/src/file_interface.cpp
)
# Desktop platform sources
set(DESIGNER_SOURCES
src/main.cpp
src/desktop_platform.cpp
src/hot_reload.cpp
src/data_models.cpp
src/kernel_impl.cpp
src/testing/action_recorder.cpp
src/testing/action_player.cpp
src/testing/ui_inspector.cpp
src/testing/visual_capture.cpp
# RmlUi backend sources
${RMLUI_SOURCE_DIR}/Backends/RmlUi_Backend_GLFW_GL3.cpp
${RMLUI_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp
${RMLUI_SOURCE_DIR}/Backends/RmlUi_Renderer_GL3.cpp
)
# Designer executable
add_executable(mosis-designer
${KERNEL_SOURCES}
${DESIGNER_SOURCES}
)
target_include_directories(mosis-designer PRIVATE
src
../src/main/kernel/include
${RMLUI_SOURCE_DIR}
${RMLUI_SOURCE_DIR}/Backends
${LUA_INCLUDE_DIR}
)
target_link_libraries(mosis-designer PRIVATE
glfw
freetype
PNG::PNG
RmlUi::RmlUi
RmlUi::Lua
nlohmann_json::nlohmann_json
)
target_compile_definitions(mosis-designer PRIVATE
MOSIS_PLATFORM_DESKTOP
RMLUI_STATIC_LIB
)
# Platform-specific libraries
if(WIN32)
target_link_libraries(mosis-designer PRIVATE opengl32)
elseif(APPLE)
find_library(OPENGL_LIBRARY OpenGL)
target_link_libraries(mosis-designer PRIVATE ${OPENGL_LIBRARY})
else()
find_package(OpenGL REQUIRED)
target_link_libraries(mosis-designer PRIVATE OpenGL::GL)
endif()
# Copy assets for development
add_custom_command(TARGET mosis-designer POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/../src/main/assets
$<TARGET_FILE_DIR:mosis-designer>/assets
)

View File

@@ -0,0 +1,137 @@
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
#ifndef __khrplatform_h_
#define __khrplatform_h_
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
#if defined(KHRONOS_STATIC)
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(KHRONOS_STATIC)
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
#if defined(KHRONOS_SUPPORT_INT64)
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__cplusplus)
#include <cstdint>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#else
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
#ifdef KHRONOS_SUPPORT_FLOAT
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
typedef khronos_uint64_t khronos_usize_t;
typedef khronos_int64_t khronos_ssize_t;
#endif /* __khrplatform_h_ */

View File

@@ -0,0 +1,623 @@
/*
* GLAD OpenGL 3.3 Core Profile Loader
* Generated for Mosis Designer
*/
#ifndef GLAD_GL_H_
#define GLAD_GL_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <KHR/khrplatform.h>
#define GLAD_VERSION_MAJOR 2
#define GLAD_VERSION_MINOR 0
#ifndef GLAD_API_CALL
#if defined(_WIN32) || defined(__CYGWIN__)
# define GLAD_API_CALL __declspec(dllexport)
#else
# define GLAD_API_CALL __attribute__((visibility("default")))
#endif
#endif
#define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor)
#define GLAD_VERSION GLAD_MAKE_VERSION(GLAD_VERSION_MAJOR, GLAD_VERSION_MINOR)
typedef void (*GLADapiproc)(void);
typedef GLADapiproc (*GLADloadfunc)(const char *name);
GLAD_API_CALL int gladLoadGL(GLADloadfunc load);
GLAD_API_CALL int gladLoadGLUserPtr(GLADloadfunc load, void *userptr);
/* GL Types */
typedef void GLvoid;
typedef unsigned int GLenum;
typedef float GLfloat;
typedef int GLint;
typedef int GLsizei;
typedef unsigned int GLbitfield;
typedef double GLdouble;
typedef unsigned int GLuint;
typedef unsigned char GLboolean;
typedef khronos_uint8_t GLubyte;
typedef khronos_int8_t GLbyte;
typedef khronos_int16_t GLshort;
typedef khronos_uint16_t GLushort;
typedef khronos_float_t GLclampf;
typedef double GLclampd;
typedef khronos_ssize_t GLsizeiptr;
typedef khronos_intptr_t GLintptr;
typedef char GLchar;
typedef khronos_int64_t GLint64;
typedef khronos_uint64_t GLuint64;
#ifndef __gl_glcorearb_h_
/* GL Constants */
#define GL_FALSE 0
#define GL_TRUE 1
#define GL_NONE 0
#define GL_ZERO 0
#define GL_ONE 1
/* Primitives */
#define GL_POINTS 0x0000
#define GL_LINES 0x0001
#define GL_LINE_LOOP 0x0002
#define GL_LINE_STRIP 0x0003
#define GL_TRIANGLES 0x0004
#define GL_TRIANGLE_STRIP 0x0005
#define GL_TRIANGLE_FAN 0x0006
/* Depth */
#define GL_DEPTH_BUFFER_BIT 0x00000100
#define GL_STENCIL_BUFFER_BIT 0x00000400
#define GL_COLOR_BUFFER_BIT 0x00004000
#define GL_DEPTH_TEST 0x0B71
#define GL_DEPTH_FUNC 0x0B74
#define GL_DEPTH_WRITEMASK 0x0B72
/* Stencil */
#define GL_STENCIL_TEST 0x0B90
#define GL_STENCIL_FUNC 0x0B92
#define GL_STENCIL_VALUE_MASK 0x0B93
#define GL_STENCIL_REF 0x0B97
#define GL_STENCIL_FAIL 0x0B94
#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95
#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96
#define GL_KEEP 0x1E00
#define GL_REPLACE 0x1E01
#define GL_INCR 0x1E02
#define GL_DECR 0x1E03
#define GL_INVERT 0x150A
#define GL_INCR_WRAP 0x8507
#define GL_DECR_WRAP 0x8508
/* Blending */
#define GL_BLEND 0x0BE2
#define GL_SRC_COLOR 0x0300
#define GL_ONE_MINUS_SRC_COLOR 0x0301
#define GL_SRC_ALPHA 0x0302
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
#define GL_DST_ALPHA 0x0304
#define GL_ONE_MINUS_DST_ALPHA 0x0305
#define GL_DST_COLOR 0x0306
#define GL_ONE_MINUS_DST_COLOR 0x0307
#define GL_SRC_ALPHA_SATURATE 0x0308
#define GL_CONSTANT_COLOR 0x8001
#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002
#define GL_CONSTANT_ALPHA 0x8003
#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
#define GL_FUNC_ADD 0x8006
#define GL_MIN 0x8007
#define GL_MAX 0x8008
#define GL_FUNC_SUBTRACT 0x800A
#define GL_FUNC_REVERSE_SUBTRACT 0x800B
/* Culling */
#define GL_CULL_FACE 0x0B44
#define GL_FRONT 0x0404
#define GL_BACK 0x0405
#define GL_FRONT_AND_BACK 0x0408
#define GL_CW 0x0900
#define GL_CCW 0x0901
/* Comparison */
#define GL_NEVER 0x0200
#define GL_LESS 0x0201
#define GL_EQUAL 0x0202
#define GL_LEQUAL 0x0203
#define GL_GREATER 0x0204
#define GL_NOTEQUAL 0x0205
#define GL_GEQUAL 0x0206
#define GL_ALWAYS 0x0207
/* Errors */
#define GL_NO_ERROR 0
#define GL_INVALID_ENUM 0x0500
#define GL_INVALID_VALUE 0x0501
#define GL_INVALID_OPERATION 0x0502
#define GL_OUT_OF_MEMORY 0x0505
#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506
/* Data types */
#define GL_BYTE 0x1400
#define GL_UNSIGNED_BYTE 0x1401
#define GL_SHORT 0x1402
#define GL_UNSIGNED_SHORT 0x1403
#define GL_INT 0x1404
#define GL_UNSIGNED_INT 0x1405
#define GL_FLOAT 0x1406
#define GL_DOUBLE 0x140A
#define GL_HALF_FLOAT 0x140B
/* Gets */
#define GL_VIEWPORT 0x0BA2
#define GL_SCISSOR_BOX 0x0C10
#define GL_SCISSOR_TEST 0x0C11
#define GL_MAX_TEXTURE_SIZE 0x0D33
#define GL_MAX_VIEWPORT_DIMS 0x0D3A
#define GL_PACK_ALIGNMENT 0x0D05
#define GL_UNPACK_ALIGNMENT 0x0CF5
/* Textures */
#define GL_TEXTURE_1D 0x0DE0
#define GL_TEXTURE_2D 0x0DE1
#define GL_TEXTURE_3D 0x806F
#define GL_TEXTURE_CUBE_MAP 0x8513
#define GL_TEXTURE_2D_ARRAY 0x8C1A
#define GL_TEXTURE_2D_MULTISAMPLE 0x9100
#define GL_TEXTURE_BINDING_2D 0x8069
#define GL_TEXTURE_WIDTH 0x1000
#define GL_TEXTURE_HEIGHT 0x1001
#define GL_TEXTURE_INTERNAL_FORMAT 0x1003
#define GL_TEXTURE_MAG_FILTER 0x2800
#define GL_TEXTURE_MIN_FILTER 0x2801
#define GL_TEXTURE_WRAP_S 0x2802
#define GL_TEXTURE_WRAP_T 0x2803
#define GL_TEXTURE_WRAP_R 0x8072
#define GL_TEXTURE0 0x84C0
#define GL_TEXTURE1 0x84C1
#define GL_TEXTURE2 0x84C2
#define GL_TEXTURE3 0x84C3
#define GL_TEXTURE4 0x84C4
#define GL_TEXTURE5 0x84C5
#define GL_TEXTURE6 0x84C6
#define GL_TEXTURE7 0x84C7
#define GL_TEXTURE8 0x84C8
#define GL_TEXTURE9 0x84C9
#define GL_TEXTURE10 0x84CA
#define GL_ACTIVE_TEXTURE 0x84E0
#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
#define GL_NEAREST 0x2600
#define GL_LINEAR 0x2601
#define GL_NEAREST_MIPMAP_NEAREST 0x2700
#define GL_LINEAR_MIPMAP_NEAREST 0x2701
#define GL_NEAREST_MIPMAP_LINEAR 0x2702
#define GL_LINEAR_MIPMAP_LINEAR 0x2703
#define GL_REPEAT 0x2901
#define GL_CLAMP_TO_EDGE 0x812F
#define GL_CLAMP_TO_BORDER 0x812D
#define GL_MIRRORED_REPEAT 0x8370
/* Pixel formats */
#define GL_RED 0x1903
#define GL_GREEN 0x1904
#define GL_BLUE 0x1905
#define GL_ALPHA 0x1906
#define GL_RGB 0x1907
#define GL_RGBA 0x1908
#define GL_LUMINANCE 0x1909
#define GL_LUMINANCE_ALPHA 0x190A
#define GL_BGR 0x80E0
#define GL_BGRA 0x80E1
#define GL_R8 0x8229
#define GL_RG8 0x822B
#define GL_RGB8 0x8051
#define GL_RGBA8 0x8058
#define GL_SRGB8 0x8C41
#define GL_SRGB8_ALPHA8 0x8C43
#define GL_DEPTH_COMPONENT 0x1902
#define GL_DEPTH_COMPONENT16 0x81A5
#define GL_DEPTH_COMPONENT24 0x81A6
#define GL_DEPTH_COMPONENT32 0x81A7
#define GL_DEPTH_COMPONENT32F 0x8CAC
#define GL_DEPTH24_STENCIL8 0x88F0
#define GL_DEPTH32F_STENCIL8 0x8CAD
#define GL_DEPTH_STENCIL 0x84F9
/* Shaders */
#define GL_VERTEX_SHADER 0x8B31
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_GEOMETRY_SHADER 0x8DD9
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
#define GL_INFO_LOG_LENGTH 0x8B84
#define GL_CURRENT_PROGRAM 0x8B8D
#define GL_SHADER_TYPE 0x8B4F
#define GL_DELETE_STATUS 0x8B80
#define GL_ATTACHED_SHADERS 0x8B85
#define GL_ACTIVE_UNIFORMS 0x8B86
#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87
#define GL_ACTIVE_ATTRIBUTES 0x8B89
#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A
/* Buffers */
#define GL_ARRAY_BUFFER 0x8892
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_UNIFORM_BUFFER 0x8A11
#define GL_STREAM_DRAW 0x88E0
#define GL_STREAM_READ 0x88E1
#define GL_STREAM_COPY 0x88E2
#define GL_STATIC_DRAW 0x88E4
#define GL_STATIC_READ 0x88E5
#define GL_STATIC_COPY 0x88E6
#define GL_DYNAMIC_DRAW 0x88E8
#define GL_DYNAMIC_READ 0x88E9
#define GL_DYNAMIC_COPY 0x88EA
#define GL_BUFFER_SIZE 0x8764
#define GL_BUFFER_USAGE 0x8765
#define GL_BUFFER_MAPPED 0x88BC
#define GL_BUFFER_MAP_POINTER 0x88BD
/* VAOs */
#define GL_VERTEX_ARRAY_BINDING 0x85B5
/* Framebuffers */
#define GL_FRAMEBUFFER 0x8D40
#define GL_READ_FRAMEBUFFER 0x8CA8
#define GL_DRAW_FRAMEBUFFER 0x8CA9
#define GL_RENDERBUFFER 0x8D41
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_COLOR_ATTACHMENT1 0x8CE1
#define GL_COLOR_ATTACHMENT2 0x8CE2
#define GL_COLOR_ATTACHMENT3 0x8CE3
#define GL_DEPTH_ATTACHMENT 0x8D00
#define GL_STENCIL_ATTACHMENT 0x8D20
#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB
#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC
#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF
#define GL_MAX_RENDERBUFFER_SIZE 0x84E8
#define GL_MAX_SAMPLES 0x8D57
/* Multisample */
#define GL_MULTISAMPLE 0x809D
#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E
#define GL_SAMPLE_ALPHA_TO_ONE 0x809F
#define GL_SAMPLE_COVERAGE 0x80A0
/* Misc */
#define GL_VENDOR 0x1F00
#define GL_RENDERER 0x1F01
#define GL_VERSION 0x1F02
#define GL_EXTENSIONS 0x1F03
#define GL_SHADING_LANGUAGE_VERSION 0x8B8C
#define GL_DITHER 0x0BD0
#define GL_LINE_SMOOTH 0x0B20
#define GL_POLYGON_SMOOTH 0x0B41
#define GL_POLYGON_OFFSET_FILL 0x8037
#define GL_PROGRAM_POINT_SIZE 0x8642
#endif /* __gl_glcorearb_h_ */
/* Version flags */
#define GLAD_GL 1
#define GLAD_GL_VERSION_1_0 1
#define GLAD_GL_VERSION_1_1 1
#define GLAD_GL_VERSION_1_2 1
#define GLAD_GL_VERSION_1_3 1
#define GLAD_GL_VERSION_1_4 1
#define GLAD_GL_VERSION_1_5 1
#define GLAD_GL_VERSION_2_0 1
#define GLAD_GL_VERSION_2_1 1
#define GLAD_GL_VERSION_3_0 1
#define GLAD_GL_VERSION_3_1 1
#define GLAD_GL_VERSION_3_2 1
#define GLAD_GL_VERSION_3_3 1
/* Function pointer types */
typedef void (KHRONOS_APIENTRY *PFNGLACTIVETEXTUREPROC)(GLenum texture);
typedef void (KHRONOS_APIENTRY *PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader);
typedef void (KHRONOS_APIENTRY *PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer);
typedef void (KHRONOS_APIENTRY *PFNGLBINDBUFFERBASEPROC)(GLenum target, GLuint index, GLuint buffer);
typedef void (KHRONOS_APIENTRY *PFNGLBINDFRAMEBUFFERPROC)(GLenum target, GLuint framebuffer);
typedef void (KHRONOS_APIENTRY *PFNGLBINDRENDERBUFFERPROC)(GLenum target, GLuint renderbuffer);
typedef void (KHRONOS_APIENTRY *PFNGLBINDTEXTUREPROC)(GLenum target, GLuint texture);
typedef void (KHRONOS_APIENTRY *PFNGLBINDVERTEXARRAYPROC)(GLuint array);
typedef void (KHRONOS_APIENTRY *PFNGLBLENDCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
typedef void (KHRONOS_APIENTRY *PFNGLBLENDEQUATIONPROC)(GLenum mode);
typedef void (KHRONOS_APIENTRY *PFNGLBLENDEQUATIONSEPARATEPROC)(GLenum modeRGB, GLenum modeAlpha);
typedef void (KHRONOS_APIENTRY *PFNGLBLENDFUNCPROC)(GLenum sfactor, GLenum dfactor);
typedef void (KHRONOS_APIENTRY *PFNGLBLENDFUNCSEPARATEPROC)(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
typedef void (KHRONOS_APIENTRY *PFNGLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
typedef void (KHRONOS_APIENTRY *PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
typedef void (KHRONOS_APIENTRY *PFNGLBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
typedef GLenum (KHRONOS_APIENTRY *PFNGLCHECKFRAMEBUFFERSTATUSPROC)(GLenum target);
typedef void (KHRONOS_APIENTRY *PFNGLCLEARPROC)(GLbitfield mask);
typedef void (KHRONOS_APIENTRY *PFNGLCLEARCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
typedef void (KHRONOS_APIENTRY *PFNGLCLEARDEPTHPROC)(GLdouble depth);
typedef void (KHRONOS_APIENTRY *PFNGLCLEARSTENCILPROC)(GLint s);
typedef void (KHRONOS_APIENTRY *PFNGLCOLORMASKPROC)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
typedef void (KHRONOS_APIENTRY *PFNGLCOMPILESHADERPROC)(GLuint shader);
typedef GLuint (KHRONOS_APIENTRY *PFNGLCREATEPROGRAMPROC)(void);
typedef GLuint (KHRONOS_APIENTRY *PFNGLCREATESHADERPROC)(GLenum type);
typedef void (KHRONOS_APIENTRY *PFNGLCULLFACEPROC)(GLenum mode);
typedef void (KHRONOS_APIENTRY *PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint *buffers);
typedef void (KHRONOS_APIENTRY *PFNGLDELETEFRAMEBUFFERSPROC)(GLsizei n, const GLuint *framebuffers);
typedef void (KHRONOS_APIENTRY *PFNGLDELETEPROGRAMPROC)(GLuint program);
typedef void (KHRONOS_APIENTRY *PFNGLDELETERENDERBUFFERSPROC)(GLsizei n, const GLuint *renderbuffers);
typedef void (KHRONOS_APIENTRY *PFNGLDELETESHADERPROC)(GLuint shader);
typedef void (KHRONOS_APIENTRY *PFNGLDELETETEXTURESPROC)(GLsizei n, const GLuint *textures);
typedef void (KHRONOS_APIENTRY *PFNGLDELETEVERTEXARRAYSPROC)(GLsizei n, const GLuint *arrays);
typedef void (KHRONOS_APIENTRY *PFNGLDEPTHFUNCPROC)(GLenum func);
typedef void (KHRONOS_APIENTRY *PFNGLDEPTHMASKPROC)(GLboolean flag);
typedef void (KHRONOS_APIENTRY *PFNGLDISABLEPROC)(GLenum cap);
typedef void (KHRONOS_APIENTRY *PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index);
typedef void (KHRONOS_APIENTRY *PFNGLDRAWARRAYSPROC)(GLenum mode, GLint first, GLsizei count);
typedef void (KHRONOS_APIENTRY *PFNGLDRAWBUFFERSPROC)(GLsizei n, const GLenum *bufs);
typedef void (KHRONOS_APIENTRY *PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, GLenum type, const void *indices);
typedef void (KHRONOS_APIENTRY *PFNGLENABLEPROC)(GLenum cap);
typedef void (KHRONOS_APIENTRY *PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index);
typedef void (KHRONOS_APIENTRY *PFNGLFINISHPROC)(void);
typedef void (KHRONOS_APIENTRY *PFNGLFLUSHPROC)(void);
typedef void (KHRONOS_APIENTRY *PFNGLFRAMEBUFFERRENDERBUFFERPROC)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
typedef void (KHRONOS_APIENTRY *PFNGLFRAMEBUFFERTEXTURE2DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
typedef void (KHRONOS_APIENTRY *PFNGLFRONTFACEPROC)(GLenum mode);
typedef void (KHRONOS_APIENTRY *PFNGLGENBUFFERSPROC)(GLsizei n, GLuint *buffers);
typedef void (KHRONOS_APIENTRY *PFNGLGENFRAMEBUFFERSPROC)(GLsizei n, GLuint *framebuffers);
typedef void (KHRONOS_APIENTRY *PFNGLGENRENDERBUFFERSPROC)(GLsizei n, GLuint *renderbuffers);
typedef void (KHRONOS_APIENTRY *PFNGLGENTEXTURESPROC)(GLsizei n, GLuint *textures);
typedef void (KHRONOS_APIENTRY *PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint *arrays);
typedef void (KHRONOS_APIENTRY *PFNGLGENERATEMIPMAPPROC)(GLenum target);
typedef GLint (KHRONOS_APIENTRY *PFNGLGETATTRIBLOCATIONPROC)(GLuint program, const GLchar *name);
typedef GLenum (KHRONOS_APIENTRY *PFNGLGETERRORPROC)(void);
typedef void (KHRONOS_APIENTRY *PFNGLGETINTEGERVPROC)(GLenum pname, GLint *data);
typedef void (KHRONOS_APIENTRY *PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef void (KHRONOS_APIENTRY *PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint *params);
typedef void (KHRONOS_APIENTRY *PFNGLGETSHADERINFOLOGPROC)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef void (KHRONOS_APIENTRY *PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint *params);
typedef const GLubyte * (KHRONOS_APIENTRY *PFNGLGETSTRINGPROC)(GLenum name);
typedef const GLubyte * (KHRONOS_APIENTRY *PFNGLGETSTRINGIPROC)(GLenum name, GLuint index);
typedef GLint (KHRONOS_APIENTRY *PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar *name);
typedef GLboolean (KHRONOS_APIENTRY *PFNGLISENABLEDPROC)(GLenum cap);
typedef void (KHRONOS_APIENTRY *PFNGLLINKPROGRAMPROC)(GLuint program);
typedef void * (KHRONOS_APIENTRY *PFNGLMAPBUFFERPROC)(GLenum target, GLenum access);
typedef void (KHRONOS_APIENTRY *PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param);
typedef void (KHRONOS_APIENTRY *PFNGLPOLYGONMODEPROC)(GLenum face, GLenum mode);
typedef void (KHRONOS_APIENTRY *PFNGLREADPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels);
typedef void (KHRONOS_APIENTRY *PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (KHRONOS_APIENTRY *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (KHRONOS_APIENTRY *PFNGLSCISSORPROC)(GLint x, GLint y, GLsizei width, GLsizei height);
typedef void (KHRONOS_APIENTRY *PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length);
typedef void (KHRONOS_APIENTRY *PFNGLSTENCILFUNCPROC)(GLenum func, GLint ref, GLuint mask);
typedef void (KHRONOS_APIENTRY *PFNGLSTENCILFUNCSEPARATEPROC)(GLenum face, GLenum func, GLint ref, GLuint mask);
typedef void (KHRONOS_APIENTRY *PFNGLSTENCILMASKPROC)(GLuint mask);
typedef void (KHRONOS_APIENTRY *PFNGLSTENCILMASKSEPARATEPROC)(GLenum face, GLuint mask);
typedef void (KHRONOS_APIENTRY *PFNGLSTENCILOPPROC)(GLenum fail, GLenum zfail, GLenum zpass);
typedef void (KHRONOS_APIENTRY *PFNGLSTENCILOPSEPARATEPROC)(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass);
typedef void (KHRONOS_APIENTRY *PFNGLTEXIMAGE2DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels);
typedef void (KHRONOS_APIENTRY *PFNGLTEXPARAMETERFPROC)(GLenum target, GLenum pname, GLfloat param);
typedef void (KHRONOS_APIENTRY *PFNGLTEXPARAMETERIPROC)(GLenum target, GLenum pname, GLint param);
typedef void (KHRONOS_APIENTRY *PFNGLTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM1FPROC)(GLint location, GLfloat v0);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM1FVPROC)(GLint location, GLsizei count, const GLfloat *value);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM1IPROC)(GLint location, GLint v0);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM2FPROC)(GLint location, GLfloat v0, GLfloat v1);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM2FVPROC)(GLint location, GLsizei count, const GLfloat *value);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM3FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM4FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM4FVPROC)(GLint location, GLsizei count, const GLfloat *value);
typedef void (KHRONOS_APIENTRY *PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
typedef GLboolean (KHRONOS_APIENTRY *PFNGLUNMAPBUFFERPROC)(GLenum target);
typedef void (KHRONOS_APIENTRY *PFNGLUSEPROGRAMPROC)(GLuint program);
typedef void (KHRONOS_APIENTRY *PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
typedef void (KHRONOS_APIENTRY *PFNGLVIEWPORTPROC)(GLint x, GLint y, GLsizei width, GLsizei height);
/* Global function pointers */
GLAD_API_CALL PFNGLACTIVETEXTUREPROC glad_glActiveTexture;
#define glActiveTexture glad_glActiveTexture
GLAD_API_CALL PFNGLATTACHSHADERPROC glad_glAttachShader;
#define glAttachShader glad_glAttachShader
GLAD_API_CALL PFNGLBINDBUFFERPROC glad_glBindBuffer;
#define glBindBuffer glad_glBindBuffer
GLAD_API_CALL PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase;
#define glBindBufferBase glad_glBindBufferBase
GLAD_API_CALL PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer;
#define glBindFramebuffer glad_glBindFramebuffer
GLAD_API_CALL PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer;
#define glBindRenderbuffer glad_glBindRenderbuffer
GLAD_API_CALL PFNGLBINDTEXTUREPROC glad_glBindTexture;
#define glBindTexture glad_glBindTexture
GLAD_API_CALL PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray;
#define glBindVertexArray glad_glBindVertexArray
GLAD_API_CALL PFNGLBLENDCOLORPROC glad_glBlendColor;
#define glBlendColor glad_glBlendColor
GLAD_API_CALL PFNGLBLENDEQUATIONPROC glad_glBlendEquation;
#define glBlendEquation glad_glBlendEquation
GLAD_API_CALL PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate;
#define glBlendEquationSeparate glad_glBlendEquationSeparate
GLAD_API_CALL PFNGLBLENDFUNCPROC glad_glBlendFunc;
#define glBlendFunc glad_glBlendFunc
GLAD_API_CALL PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate;
#define glBlendFuncSeparate glad_glBlendFuncSeparate
GLAD_API_CALL PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer;
#define glBlitFramebuffer glad_glBlitFramebuffer
GLAD_API_CALL PFNGLBUFFERDATAPROC glad_glBufferData;
#define glBufferData glad_glBufferData
GLAD_API_CALL PFNGLBUFFERSUBDATAPROC glad_glBufferSubData;
#define glBufferSubData glad_glBufferSubData
GLAD_API_CALL PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus;
#define glCheckFramebufferStatus glad_glCheckFramebufferStatus
GLAD_API_CALL PFNGLCLEARPROC glad_glClear;
#define glClear glad_glClear
GLAD_API_CALL PFNGLCLEARCOLORPROC glad_glClearColor;
#define glClearColor glad_glClearColor
GLAD_API_CALL PFNGLCLEARDEPTHPROC glad_glClearDepth;
#define glClearDepth glad_glClearDepth
GLAD_API_CALL PFNGLCLEARSTENCILPROC glad_glClearStencil;
#define glClearStencil glad_glClearStencil
GLAD_API_CALL PFNGLCOLORMASKPROC glad_glColorMask;
#define glColorMask glad_glColorMask
GLAD_API_CALL PFNGLCOMPILESHADERPROC glad_glCompileShader;
#define glCompileShader glad_glCompileShader
GLAD_API_CALL PFNGLCREATEPROGRAMPROC glad_glCreateProgram;
#define glCreateProgram glad_glCreateProgram
GLAD_API_CALL PFNGLCREATESHADERPROC glad_glCreateShader;
#define glCreateShader glad_glCreateShader
GLAD_API_CALL PFNGLCULLFACEPROC glad_glCullFace;
#define glCullFace glad_glCullFace
GLAD_API_CALL PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers;
#define glDeleteBuffers glad_glDeleteBuffers
GLAD_API_CALL PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers;
#define glDeleteFramebuffers glad_glDeleteFramebuffers
GLAD_API_CALL PFNGLDELETEPROGRAMPROC glad_glDeleteProgram;
#define glDeleteProgram glad_glDeleteProgram
GLAD_API_CALL PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers;
#define glDeleteRenderbuffers glad_glDeleteRenderbuffers
GLAD_API_CALL PFNGLDELETESHADERPROC glad_glDeleteShader;
#define glDeleteShader glad_glDeleteShader
GLAD_API_CALL PFNGLDELETETEXTURESPROC glad_glDeleteTextures;
#define glDeleteTextures glad_glDeleteTextures
GLAD_API_CALL PFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays;
#define glDeleteVertexArrays glad_glDeleteVertexArrays
GLAD_API_CALL PFNGLDEPTHFUNCPROC glad_glDepthFunc;
#define glDepthFunc glad_glDepthFunc
GLAD_API_CALL PFNGLDEPTHMASKPROC glad_glDepthMask;
#define glDepthMask glad_glDepthMask
GLAD_API_CALL PFNGLDISABLEPROC glad_glDisable;
#define glDisable glad_glDisable
GLAD_API_CALL PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray;
#define glDisableVertexAttribArray glad_glDisableVertexAttribArray
GLAD_API_CALL PFNGLDRAWARRAYSPROC glad_glDrawArrays;
#define glDrawArrays glad_glDrawArrays
GLAD_API_CALL PFNGLDRAWBUFFERSPROC glad_glDrawBuffers;
#define glDrawBuffers glad_glDrawBuffers
GLAD_API_CALL PFNGLDRAWELEMENTSPROC glad_glDrawElements;
#define glDrawElements glad_glDrawElements
GLAD_API_CALL PFNGLENABLEPROC glad_glEnable;
#define glEnable glad_glEnable
GLAD_API_CALL PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray;
#define glEnableVertexAttribArray glad_glEnableVertexAttribArray
GLAD_API_CALL PFNGLFINISHPROC glad_glFinish;
#define glFinish glad_glFinish
GLAD_API_CALL PFNGLFLUSHPROC glad_glFlush;
#define glFlush glad_glFlush
GLAD_API_CALL PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer;
#define glFramebufferRenderbuffer glad_glFramebufferRenderbuffer
GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D;
#define glFramebufferTexture2D glad_glFramebufferTexture2D
GLAD_API_CALL PFNGLFRONTFACEPROC glad_glFrontFace;
#define glFrontFace glad_glFrontFace
GLAD_API_CALL PFNGLGENBUFFERSPROC glad_glGenBuffers;
#define glGenBuffers glad_glGenBuffers
GLAD_API_CALL PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers;
#define glGenFramebuffers glad_glGenFramebuffers
GLAD_API_CALL PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers;
#define glGenRenderbuffers glad_glGenRenderbuffers
GLAD_API_CALL PFNGLGENTEXTURESPROC glad_glGenTextures;
#define glGenTextures glad_glGenTextures
GLAD_API_CALL PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays;
#define glGenVertexArrays glad_glGenVertexArrays
GLAD_API_CALL PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap;
#define glGenerateMipmap glad_glGenerateMipmap
GLAD_API_CALL PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation;
#define glGetAttribLocation glad_glGetAttribLocation
GLAD_API_CALL PFNGLGETERRORPROC glad_glGetError;
#define glGetError glad_glGetError
GLAD_API_CALL PFNGLGETINTEGERVPROC glad_glGetIntegerv;
#define glGetIntegerv glad_glGetIntegerv
GLAD_API_CALL PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog;
#define glGetProgramInfoLog glad_glGetProgramInfoLog
GLAD_API_CALL PFNGLGETPROGRAMIVPROC glad_glGetProgramiv;
#define glGetProgramiv glad_glGetProgramiv
GLAD_API_CALL PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog;
#define glGetShaderInfoLog glad_glGetShaderInfoLog
GLAD_API_CALL PFNGLGETSHADERIVPROC glad_glGetShaderiv;
#define glGetShaderiv glad_glGetShaderiv
GLAD_API_CALL PFNGLGETSTRINGPROC glad_glGetString;
#define glGetString glad_glGetString
GLAD_API_CALL PFNGLGETSTRINGIPROC glad_glGetStringi;
#define glGetStringi glad_glGetStringi
GLAD_API_CALL PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation;
#define glGetUniformLocation glad_glGetUniformLocation
GLAD_API_CALL PFNGLISENABLEDPROC glad_glIsEnabled;
#define glIsEnabled glad_glIsEnabled
GLAD_API_CALL PFNGLLINKPROGRAMPROC glad_glLinkProgram;
#define glLinkProgram glad_glLinkProgram
GLAD_API_CALL PFNGLMAPBUFFERPROC glad_glMapBuffer;
#define glMapBuffer glad_glMapBuffer
GLAD_API_CALL PFNGLPIXELSTOREIPROC glad_glPixelStorei;
#define glPixelStorei glad_glPixelStorei
GLAD_API_CALL PFNGLPOLYGONMODEPROC glad_glPolygonMode;
#define glPolygonMode glad_glPolygonMode
GLAD_API_CALL PFNGLREADPIXELSPROC glad_glReadPixels;
#define glReadPixels glad_glReadPixels
GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage;
#define glRenderbufferStorage glad_glRenderbufferStorage
GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample;
#define glRenderbufferStorageMultisample glad_glRenderbufferStorageMultisample
GLAD_API_CALL PFNGLSCISSORPROC glad_glScissor;
#define glScissor glad_glScissor
GLAD_API_CALL PFNGLSHADERSOURCEPROC glad_glShaderSource;
#define glShaderSource glad_glShaderSource
GLAD_API_CALL PFNGLSTENCILFUNCPROC glad_glStencilFunc;
#define glStencilFunc glad_glStencilFunc
GLAD_API_CALL PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate;
#define glStencilFuncSeparate glad_glStencilFuncSeparate
GLAD_API_CALL PFNGLSTENCILMASKPROC glad_glStencilMask;
#define glStencilMask glad_glStencilMask
GLAD_API_CALL PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate;
#define glStencilMaskSeparate glad_glStencilMaskSeparate
GLAD_API_CALL PFNGLSTENCILOPPROC glad_glStencilOp;
#define glStencilOp glad_glStencilOp
GLAD_API_CALL PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate;
#define glStencilOpSeparate glad_glStencilOpSeparate
GLAD_API_CALL PFNGLTEXIMAGE2DPROC glad_glTexImage2D;
#define glTexImage2D glad_glTexImage2D
GLAD_API_CALL PFNGLTEXPARAMETERFPROC glad_glTexParameterf;
#define glTexParameterf glad_glTexParameterf
GLAD_API_CALL PFNGLTEXPARAMETERIPROC glad_glTexParameteri;
#define glTexParameteri glad_glTexParameteri
GLAD_API_CALL PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D;
#define glTexSubImage2D glad_glTexSubImage2D
GLAD_API_CALL PFNGLUNIFORM1FPROC glad_glUniform1f;
#define glUniform1f glad_glUniform1f
GLAD_API_CALL PFNGLUNIFORM1FVPROC glad_glUniform1fv;
#define glUniform1fv glad_glUniform1fv
GLAD_API_CALL PFNGLUNIFORM1IPROC glad_glUniform1i;
#define glUniform1i glad_glUniform1i
GLAD_API_CALL PFNGLUNIFORM2FPROC glad_glUniform2f;
#define glUniform2f glad_glUniform2f
GLAD_API_CALL PFNGLUNIFORM2FVPROC glad_glUniform2fv;
#define glUniform2fv glad_glUniform2fv
GLAD_API_CALL PFNGLUNIFORM3FPROC glad_glUniform3f;
#define glUniform3f glad_glUniform3f
GLAD_API_CALL PFNGLUNIFORM4FPROC glad_glUniform4f;
#define glUniform4f glad_glUniform4f
GLAD_API_CALL PFNGLUNIFORM4FVPROC glad_glUniform4fv;
#define glUniform4fv glad_glUniform4fv
GLAD_API_CALL PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv;
#define glUniformMatrix4fv glad_glUniformMatrix4fv
GLAD_API_CALL PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer;
#define glUnmapBuffer glad_glUnmapBuffer
GLAD_API_CALL PFNGLUSEPROGRAMPROC glad_glUseProgram;
#define glUseProgram glad_glUseProgram
GLAD_API_CALL PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer;
#define glVertexAttribPointer glad_glVertexAttribPointer
GLAD_API_CALL PFNGLVIEWPORTPROC glad_glViewport;
#define glViewport glad_glViewport
#ifdef __cplusplus
}
#endif
#endif /* GLAD_GL_H_ */

322
designer/glad/src/gl.c Normal file
View File

@@ -0,0 +1,322 @@
/*
* GLAD OpenGL 3.3 Core Profile Loader Implementation
*/
#include <glad/gl.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
static HMODULE libGL;
static int open_gl(void) {
libGL = LoadLibraryW(L"opengl32.dll");
return libGL != NULL;
}
static void close_gl(void) {
if (libGL != NULL) {
FreeLibrary(libGL);
libGL = NULL;
}
}
static GLADapiproc get_proc(const char *name) {
GLADapiproc result = NULL;
/* Try wglGetProcAddress first for extension functions */
typedef GLADapiproc (KHRONOS_APIENTRY *PFNWGLGETPROCADDRESSPROC)(const char*);
static PFNWGLGETPROCADDRESSPROC wglGetProcAddress_ptr = NULL;
if (wglGetProcAddress_ptr == NULL && libGL != NULL) {
wglGetProcAddress_ptr = (PFNWGLGETPROCADDRESSPROC)GetProcAddress(libGL, "wglGetProcAddress");
}
if (wglGetProcAddress_ptr != NULL) {
result = wglGetProcAddress_ptr(name);
}
/* Fall back to GetProcAddress for core functions */
if (result == NULL && libGL != NULL) {
result = (GLADapiproc)GetProcAddress(libGL, name);
}
return result;
}
#elif defined(__APPLE__)
#include <dlfcn.h>
static void* libGL;
static int open_gl(void) {
libGL = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_LOCAL);
return libGL != NULL;
}
static void close_gl(void) {
if (libGL != NULL) {
dlclose(libGL);
libGL = NULL;
}
}
static GLADapiproc get_proc(const char *name) {
if (libGL == NULL) return NULL;
return (GLADapiproc)dlsym(libGL, name);
}
#else /* Linux/Unix */
#include <dlfcn.h>
static void* libGL;
static int open_gl(void) {
libGL = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL);
if (libGL == NULL) {
libGL = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL);
}
return libGL != NULL;
}
static void close_gl(void) {
if (libGL != NULL) {
dlclose(libGL);
libGL = NULL;
}
}
static GLADapiproc get_proc(const char *name) {
if (libGL == NULL) return NULL;
return (GLADapiproc)dlsym(libGL, name);
}
#endif
/* Function pointers */
PFNGLACTIVETEXTUREPROC glad_glActiveTexture = NULL;
PFNGLATTACHSHADERPROC glad_glAttachShader = NULL;
PFNGLBINDBUFFERPROC glad_glBindBuffer = NULL;
PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase = NULL;
PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer = NULL;
PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer = NULL;
PFNGLBINDTEXTUREPROC glad_glBindTexture = NULL;
PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray = NULL;
PFNGLBLENDCOLORPROC glad_glBlendColor = NULL;
PFNGLBLENDEQUATIONPROC glad_glBlendEquation = NULL;
PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate = NULL;
PFNGLBLENDFUNCPROC glad_glBlendFunc = NULL;
PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL;
PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL;
PFNGLBUFFERDATAPROC glad_glBufferData = NULL;
PFNGLBUFFERSUBDATAPROC glad_glBufferSubData = NULL;
PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus = NULL;
PFNGLCLEARPROC glad_glClear = NULL;
PFNGLCLEARCOLORPROC glad_glClearColor = NULL;
PFNGLCLEARDEPTHPROC glad_glClearDepth = NULL;
PFNGLCLEARSTENCILPROC glad_glClearStencil = NULL;
PFNGLCOLORMASKPROC glad_glColorMask = NULL;
PFNGLCOMPILESHADERPROC glad_glCompileShader = NULL;
PFNGLCREATEPROGRAMPROC glad_glCreateProgram = NULL;
PFNGLCREATESHADERPROC glad_glCreateShader = NULL;
PFNGLCULLFACEPROC glad_glCullFace = NULL;
PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers = NULL;
PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers = NULL;
PFNGLDELETEPROGRAMPROC glad_glDeleteProgram = NULL;
PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers = NULL;
PFNGLDELETESHADERPROC glad_glDeleteShader = NULL;
PFNGLDELETETEXTURESPROC glad_glDeleteTextures = NULL;
PFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays = NULL;
PFNGLDEPTHFUNCPROC glad_glDepthFunc = NULL;
PFNGLDEPTHMASKPROC glad_glDepthMask = NULL;
PFNGLDISABLEPROC glad_glDisable = NULL;
PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray = NULL;
PFNGLDRAWARRAYSPROC glad_glDrawArrays = NULL;
PFNGLDRAWBUFFERSPROC glad_glDrawBuffers = NULL;
PFNGLDRAWELEMENTSPROC glad_glDrawElements = NULL;
PFNGLENABLEPROC glad_glEnable = NULL;
PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray = NULL;
PFNGLFINISHPROC glad_glFinish = NULL;
PFNGLFLUSHPROC glad_glFlush = NULL;
PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer = NULL;
PFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D = NULL;
PFNGLFRONTFACEPROC glad_glFrontFace = NULL;
PFNGLGENBUFFERSPROC glad_glGenBuffers = NULL;
PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers = NULL;
PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers = NULL;
PFNGLGENTEXTURESPROC glad_glGenTextures = NULL;
PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays = NULL;
PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap = NULL;
PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation = NULL;
PFNGLGETERRORPROC glad_glGetError = NULL;
PFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL;
PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog = NULL;
PFNGLGETPROGRAMIVPROC glad_glGetProgramiv = NULL;
PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog = NULL;
PFNGLGETSHADERIVPROC glad_glGetShaderiv = NULL;
PFNGLGETSTRINGPROC glad_glGetString = NULL;
PFNGLGETSTRINGIPROC glad_glGetStringi = NULL;
PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation = NULL;
PFNGLISENABLEDPROC glad_glIsEnabled = NULL;
PFNGLLINKPROGRAMPROC glad_glLinkProgram = NULL;
PFNGLMAPBUFFERPROC glad_glMapBuffer = NULL;
PFNGLPIXELSTOREIPROC glad_glPixelStorei = NULL;
PFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL;
PFNGLREADPIXELSPROC glad_glReadPixels = NULL;
PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL;
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL;
PFNGLSCISSORPROC glad_glScissor = NULL;
PFNGLSHADERSOURCEPROC glad_glShaderSource = NULL;
PFNGLSTENCILFUNCPROC glad_glStencilFunc = NULL;
PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate = NULL;
PFNGLSTENCILMASKPROC glad_glStencilMask = NULL;
PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate = NULL;
PFNGLSTENCILOPPROC glad_glStencilOp = NULL;
PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate = NULL;
PFNGLTEXIMAGE2DPROC glad_glTexImage2D = NULL;
PFNGLTEXPARAMETERFPROC glad_glTexParameterf = NULL;
PFNGLTEXPARAMETERIPROC glad_glTexParameteri = NULL;
PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D = NULL;
PFNGLUNIFORM1FPROC glad_glUniform1f = NULL;
PFNGLUNIFORM1FVPROC glad_glUniform1fv = NULL;
PFNGLUNIFORM1IPROC glad_glUniform1i = NULL;
PFNGLUNIFORM2FPROC glad_glUniform2f = NULL;
PFNGLUNIFORM2FVPROC glad_glUniform2fv = NULL;
PFNGLUNIFORM3FPROC glad_glUniform3f = NULL;
PFNGLUNIFORM4FPROC glad_glUniform4f = NULL;
PFNGLUNIFORM4FVPROC glad_glUniform4fv = NULL;
PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv = NULL;
PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer = NULL;
PFNGLUSEPROGRAMPROC glad_glUseProgram = NULL;
PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer = NULL;
PFNGLVIEWPORTPROC glad_glViewport = NULL;
static void load_GL_VERSION_1_0(GLADloadfunc load) {
glad_glClear = (PFNGLCLEARPROC)load("glClear");
glad_glClearColor = (PFNGLCLEARCOLORPROC)load("glClearColor");
glad_glClearDepth = (PFNGLCLEARDEPTHPROC)load("glClearDepth");
glad_glClearStencil = (PFNGLCLEARSTENCILPROC)load("glClearStencil");
glad_glColorMask = (PFNGLCOLORMASKPROC)load("glColorMask");
glad_glCullFace = (PFNGLCULLFACEPROC)load("glCullFace");
glad_glDepthFunc = (PFNGLDEPTHFUNCPROC)load("glDepthFunc");
glad_glDepthMask = (PFNGLDEPTHMASKPROC)load("glDepthMask");
glad_glDisable = (PFNGLDISABLEPROC)load("glDisable");
glad_glDrawArrays = (PFNGLDRAWARRAYSPROC)load("glDrawArrays");
glad_glDrawElements = (PFNGLDRAWELEMENTSPROC)load("glDrawElements");
glad_glEnable = (PFNGLENABLEPROC)load("glEnable");
glad_glFinish = (PFNGLFINISHPROC)load("glFinish");
glad_glFlush = (PFNGLFLUSHPROC)load("glFlush");
glad_glFrontFace = (PFNGLFRONTFACEPROC)load("glFrontFace");
glad_glGetError = (PFNGLGETERRORPROC)load("glGetError");
glad_glGetIntegerv = (PFNGLGETINTEGERVPROC)load("glGetIntegerv");
glad_glGetString = (PFNGLGETSTRINGPROC)load("glGetString");
glad_glIsEnabled = (PFNGLISENABLEDPROC)load("glIsEnabled");
glad_glPixelStorei = (PFNGLPIXELSTOREIPROC)load("glPixelStorei");
glad_glPolygonMode = (PFNGLPOLYGONMODEPROC)load("glPolygonMode");
glad_glReadPixels = (PFNGLREADPIXELSPROC)load("glReadPixels");
glad_glScissor = (PFNGLSCISSORPROC)load("glScissor");
glad_glStencilFunc = (PFNGLSTENCILFUNCPROC)load("glStencilFunc");
glad_glStencilMask = (PFNGLSTENCILMASKPROC)load("glStencilMask");
glad_glStencilOp = (PFNGLSTENCILOPPROC)load("glStencilOp");
glad_glTexImage2D = (PFNGLTEXIMAGE2DPROC)load("glTexImage2D");
glad_glTexParameterf = (PFNGLTEXPARAMETERFPROC)load("glTexParameterf");
glad_glTexParameteri = (PFNGLTEXPARAMETERIPROC)load("glTexParameteri");
glad_glViewport = (PFNGLVIEWPORTPROC)load("glViewport");
}
static void load_GL_VERSION_1_1(GLADloadfunc load) {
glad_glBindTexture = (PFNGLBINDTEXTUREPROC)load("glBindTexture");
glad_glDeleteTextures = (PFNGLDELETETEXTURESPROC)load("glDeleteTextures");
glad_glGenTextures = (PFNGLGENTEXTURESPROC)load("glGenTextures");
glad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC)load("glTexSubImage2D");
}
static void load_GL_VERSION_1_3(GLADloadfunc load) {
glad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load("glActiveTexture");
}
static void load_GL_VERSION_1_4(GLADloadfunc load) {
glad_glBlendColor = (PFNGLBLENDCOLORPROC)load("glBlendColor");
glad_glBlendEquation = (PFNGLBLENDEQUATIONPROC)load("glBlendEquation");
glad_glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)load("glBlendFuncSeparate");
}
static void load_GL_VERSION_1_5(GLADloadfunc load) {
glad_glBindBuffer = (PFNGLBINDBUFFERPROC)load("glBindBuffer");
glad_glBufferData = (PFNGLBUFFERDATAPROC)load("glBufferData");
glad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC)load("glBufferSubData");
glad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)load("glDeleteBuffers");
glad_glGenBuffers = (PFNGLGENBUFFERSPROC)load("glGenBuffers");
glad_glMapBuffer = (PFNGLMAPBUFFERPROC)load("glMapBuffer");
glad_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)load("glUnmapBuffer");
}
static void load_GL_VERSION_2_0(GLADloadfunc load) {
glad_glAttachShader = (PFNGLATTACHSHADERPROC)load("glAttachShader");
glad_glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)load("glBlendEquationSeparate");
glad_glBlendFunc = (PFNGLBLENDFUNCPROC)load("glBlendFunc");
glad_glCompileShader = (PFNGLCOMPILESHADERPROC)load("glCompileShader");
glad_glCreateProgram = (PFNGLCREATEPROGRAMPROC)load("glCreateProgram");
glad_glCreateShader = (PFNGLCREATESHADERPROC)load("glCreateShader");
glad_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)load("glDeleteProgram");
glad_glDeleteShader = (PFNGLDELETESHADERPROC)load("glDeleteShader");
glad_glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)load("glDisableVertexAttribArray");
glad_glDrawBuffers = (PFNGLDRAWBUFFERSPROC)load("glDrawBuffers");
glad_glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)load("glEnableVertexAttribArray");
glad_glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)load("glGetAttribLocation");
glad_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)load("glGetProgramInfoLog");
glad_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)load("glGetProgramiv");
glad_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)load("glGetShaderInfoLog");
glad_glGetShaderiv = (PFNGLGETSHADERIVPROC)load("glGetShaderiv");
glad_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)load("glGetUniformLocation");
glad_glLinkProgram = (PFNGLLINKPROGRAMPROC)load("glLinkProgram");
glad_glShaderSource = (PFNGLSHADERSOURCEPROC)load("glShaderSource");
glad_glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC)load("glStencilFuncSeparate");
glad_glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC)load("glStencilMaskSeparate");
glad_glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)load("glStencilOpSeparate");
glad_glUniform1f = (PFNGLUNIFORM1FPROC)load("glUniform1f");
glad_glUniform1fv = (PFNGLUNIFORM1FVPROC)load("glUniform1fv");
glad_glUniform1i = (PFNGLUNIFORM1IPROC)load("glUniform1i");
glad_glUniform2f = (PFNGLUNIFORM2FPROC)load("glUniform2f");
glad_glUniform2fv = (PFNGLUNIFORM2FVPROC)load("glUniform2fv");
glad_glUniform3f = (PFNGLUNIFORM3FPROC)load("glUniform3f");
glad_glUniform4f = (PFNGLUNIFORM4FPROC)load("glUniform4f");
glad_glUniform4fv = (PFNGLUNIFORM4FVPROC)load("glUniform4fv");
glad_glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)load("glUniformMatrix4fv");
glad_glUseProgram = (PFNGLUSEPROGRAMPROC)load("glUseProgram");
glad_glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)load("glVertexAttribPointer");
}
static void load_GL_VERSION_3_0(GLADloadfunc load) {
glad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load("glBindBufferBase");
glad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)load("glBindFramebuffer");
glad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)load("glBindRenderbuffer");
glad_glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)load("glBindVertexArray");
glad_glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)load("glBlitFramebuffer");
glad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)load("glCheckFramebufferStatus");
glad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)load("glDeleteFramebuffers");
glad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)load("glDeleteRenderbuffers");
glad_glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)load("glDeleteVertexArrays");
glad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)load("glFramebufferRenderbuffer");
glad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)load("glFramebufferTexture2D");
glad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)load("glGenFramebuffers");
glad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)load("glGenRenderbuffers");
glad_glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)load("glGenVertexArrays");
glad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)load("glGenerateMipmap");
glad_glGetStringi = (PFNGLGETSTRINGIPROC)load("glGetStringi");
glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)load("glRenderbufferStorage");
glad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)load("glRenderbufferStorageMultisample");
}
int gladLoadGL(GLADloadfunc load) {
load_GL_VERSION_1_0(load);
load_GL_VERSION_1_1(load);
load_GL_VERSION_1_3(load);
load_GL_VERSION_1_4(load);
load_GL_VERSION_1_5(load);
load_GL_VERSION_2_0(load);
load_GL_VERSION_3_0(load);
return GLAD_MAKE_VERSION(3, 3);
}
int gladLoadGLUserPtr(GLADloadfunc load, void *userptr) {
(void)userptr;
return gladLoadGL(load);
}

View File

@@ -0,0 +1,276 @@
// Data models implementation
#include "data_models.h"
#include <iostream>
// Global data instances
SettingsData g_settings;
PhoneData g_phone;
BrowserData g_browser;
std::vector<Conversation> g_conversations;
std::vector<Contact> g_contacts;
int g_selected_conversation = -1;
int g_selected_contact = -1;
// Data model handles
Rml::DataModelHandle g_settings_model;
Rml::DataModelHandle g_phone_model;
Rml::DataModelHandle g_messages_model;
Rml::DataModelHandle g_contacts_model;
Rml::DataModelHandle g_browser_model;
void initializeSampleData()
{
// Initialize contacts
g_contacts = {
{1, "Alice Johnson", "+1 (555) 123-4567", "alice@example.com", "#E91E63", "A"},
{2, "Andrew Smith", "+1 (555) 234-5678", "andrew@example.com", "#9C27B0", "A"},
{3, "Bob Williams", "+1 (555) 345-6789", "bob@example.com", "#2196F3", "B"},
{4, "Brian Davis", "+1 (555) 456-7890", "brian@example.com", "#00BCD4", "B"},
{5, "Carol Martinez", "+1 (555) 567-8901", "carol@example.com", "#4CAF50", "C"},
{6, "David Lee", "+1 (555) 678-9012", "david@example.com", "#FF9800", "D"},
{7, "John Wilson", "+1 (555) 789-0123", "john@example.com", "#F44336", "J"},
{8, "Mom", "+1 (555) 890-1234", "mom@example.com", "#673AB7", "M"},
{9, "Mike Brown", "+1 (555) 901-2345", "mike@example.com", "#3F51B5", "M"},
{10, "Sarah Taylor", "+1 (555) 012-3456", "sarah@example.com", "#009688", "S"}
};
// Initialize conversations
g_conversations = {
{1, "John Wilson", "#4CAF50", "Hey, are you coming to the party tonight?", "2:30 PM", 2, {
{"them", "Hey!", "2:25 PM"},
{"them", "What are you up to?", "2:26 PM"},
{"me", "Not much, just working", "2:27 PM"},
{"them", "Cool! There's a party at Mike's tonight", "2:28 PM"},
{"them", "Hey, are you coming to the party tonight?", "2:30 PM"}
}},
{2, "Mom", "#673AB7", "Don't forget to call your grandmother!", "1:15 PM", 0, {
{"them", "Hi sweetie!", "1:00 PM"},
{"me", "Hi Mom!", "1:05 PM"},
{"them", "How are you doing?", "1:10 PM"},
{"me", "I'm good, how are you?", "1:12 PM"},
{"them", "Don't forget to call your grandmother!", "1:15 PM"}
}},
{3, "Alice Johnson", "#E91E63", "Thanks for the help with the project!", "Yesterday", 0, {
{"me", "Here's the file you needed", "Yesterday"},
{"them", "Thanks for the help with the project!", "Yesterday"}
}},
{4, "Bob Williams", "#2196F3", "Did you see the game last night?", "Yesterday", 0, {
{"them", "Did you see the game last night?", "Yesterday"}
}},
{5, "Work Group", "#FF9800", "Sarah: Meeting moved to 3pm", "Mon", 0, {
{"Sarah", "Meeting moved to 3pm", "Mon"}
}},
{6, "Sarah Taylor", "#009688", "See you at the coffee shop!", "Sun", 0, {
{"them", "See you at the coffee shop!", "Sun"}
}},
{7, "David Lee", "#F44336", "Great talking to you!", "Sat", 0, {
{"them", "Great talking to you!", "Sat"}
}}
};
// Initialize browser tabs
g_browser.tabs = {
{"example.com", "Example Domain", false},
{"rmlui.github.io", "RmlUi Documentation", false},
{"github.com", "GitHub", false}
};
std::cout << "Sample data initialized: " << g_contacts.size() << " contacts, "
<< g_conversations.size() << " conversations, "
<< g_browser.tabs.size() << " browser tabs" << std::endl;
}
void setupDataModels(Rml::Context* context)
{
// Messages data model
if (auto constructor = context->CreateDataModel("messages"))
{
if (auto msg_handle = constructor.RegisterStruct<Message>())
{
msg_handle.RegisterMember("from", &Message::from);
msg_handle.RegisterMember("text", &Message::text);
msg_handle.RegisterMember("time", &Message::time);
}
if (auto conv_handle = constructor.RegisterStruct<Conversation>())
{
conv_handle.RegisterMember("id", &Conversation::id);
conv_handle.RegisterMember("name", &Conversation::name);
conv_handle.RegisterMember("color", &Conversation::color);
conv_handle.RegisterMember("last_message", &Conversation::last_message);
conv_handle.RegisterMember("time", &Conversation::time);
conv_handle.RegisterMember("unread", &Conversation::unread);
conv_handle.RegisterMember("messages", &Conversation::messages);
}
constructor.RegisterArray<std::vector<Message>>();
constructor.RegisterArray<std::vector<Conversation>>();
constructor.Bind("conversations", &g_conversations);
constructor.Bind("selected_conversation", &g_selected_conversation);
constructor.BindEventCallback("select_conversation",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
if (!args.empty()) {
g_selected_conversation = args[0].Get<int>();
std::cout << "Selected conversation: " << g_selected_conversation << std::endl;
handle.DirtyVariable("selected_conversation");
}
});
g_messages_model = constructor.GetModelHandle();
std::cout << "Messages data model created" << std::endl;
}
// Contacts data model
if (auto constructor = context->CreateDataModel("contacts"))
{
if (auto contact_handle = constructor.RegisterStruct<Contact>())
{
contact_handle.RegisterMember("id", &Contact::id);
contact_handle.RegisterMember("name", &Contact::name);
contact_handle.RegisterMember("phone", &Contact::phone);
contact_handle.RegisterMember("email", &Contact::email);
contact_handle.RegisterMember("color", &Contact::color);
contact_handle.RegisterMember("initial", &Contact::initial);
}
constructor.RegisterArray<std::vector<Contact>>();
constructor.Bind("contacts", &g_contacts);
constructor.Bind("selected_contact", &g_selected_contact);
constructor.BindEventCallback("select_contact",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
if (!args.empty()) {
g_selected_contact = args[0].Get<int>();
std::cout << "Selected contact: " << g_selected_contact << std::endl;
handle.DirtyVariable("selected_contact");
}
});
g_contacts_model = constructor.GetModelHandle();
std::cout << "Contacts data model created" << std::endl;
}
// Settings data model
if (auto constructor = context->CreateDataModel("settings"))
{
constructor.Bind("wifi", &g_settings.wifi);
constructor.Bind("bluetooth", &g_settings.bluetooth);
constructor.Bind("airplane_mode", &g_settings.airplane_mode);
constructor.Bind("location", &g_settings.location);
constructor.Bind("dark_mode", &g_settings.dark_mode);
constructor.Bind("notifications", &g_settings.notifications);
constructor.Bind("do_not_disturb", &g_settings.do_not_disturb);
constructor.Bind("user_name", &g_settings.user_name);
constructor.Bind("user_email", &g_settings.user_email);
constructor.Bind("wifi_network", &g_settings.wifi_network);
constructor.Bind("battery_percent", &g_settings.battery_percent);
constructor.Bind("battery_remaining", &g_settings.battery_remaining);
constructor.Bind("storage_used", &g_settings.storage_used);
g_settings_model = constructor.GetModelHandle();
std::cout << "Settings data model created" << std::endl;
}
// Phone data model
if (auto constructor = context->CreateDataModel("phone"))
{
constructor.Bind("dial_number", &g_phone.dial_number);
constructor.Bind("is_calling", &g_phone.is_calling);
constructor.Bind("call_contact", &g_phone.call_contact);
constructor.Bind("call_duration", &g_phone.call_duration);
constructor.BindEventCallback("dial_press",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
if (!args.empty()) {
g_phone.dial_number += args[0].Get<Rml::String>();
std::cout << "Dial: " << g_phone.dial_number << std::endl;
handle.DirtyVariable("dial_number");
}
});
constructor.BindEventCallback("dial_backspace",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
if (!g_phone.dial_number.empty()) {
g_phone.dial_number.pop_back();
handle.DirtyVariable("dial_number");
}
});
constructor.BindEventCallback("dial_clear",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
g_phone.dial_number.clear();
handle.DirtyVariable("dial_number");
});
constructor.BindEventCallback("make_call",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
if (!g_phone.dial_number.empty()) {
g_phone.is_calling = true;
g_phone.call_contact = g_phone.dial_number;
std::cout << "Calling: " << g_phone.call_contact << std::endl;
handle.DirtyVariable("is_calling");
handle.DirtyVariable("call_contact");
}
});
constructor.BindEventCallback("end_call",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
g_phone.is_calling = false;
g_phone.call_contact = "";
g_phone.call_duration = "00:00";
handle.DirtyVariable("is_calling");
handle.DirtyVariable("call_contact");
handle.DirtyVariable("call_duration");
});
g_phone_model = constructor.GetModelHandle();
std::cout << "Phone data model created" << std::endl;
}
// Browser data model
if (auto constructor = context->CreateDataModel("browser"))
{
if (auto tab_handle = constructor.RegisterStruct<BrowserTab>())
{
tab_handle.RegisterMember("url", &BrowserTab::url);
tab_handle.RegisterMember("title", &BrowserTab::title);
tab_handle.RegisterMember("is_loading", &BrowserTab::is_loading);
}
constructor.RegisterArray<std::vector<BrowserTab>>();
constructor.Bind("current_url", &g_browser.current_url);
constructor.Bind("page_title", &g_browser.page_title);
constructor.Bind("page_content", &g_browser.page_content);
constructor.Bind("tabs", &g_browser.tabs);
constructor.Bind("current_tab", &g_browser.current_tab);
constructor.Bind("can_go_back", &g_browser.can_go_back);
constructor.Bind("can_go_forward", &g_browser.can_go_forward);
constructor.BindEventCallback("navigate",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
if (!args.empty()) {
g_browser.current_url = args[0].Get<Rml::String>();
g_browser.page_title = "Loading...";
g_browser.can_go_back = true;
std::cout << "Navigating to: " << g_browser.current_url << std::endl;
handle.DirtyVariable("current_url");
handle.DirtyVariable("page_title");
handle.DirtyVariable("can_go_back");
}
});
constructor.BindEventCallback("refresh",
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
std::cout << "Refreshing page" << std::endl;
});
g_browser_model = constructor.GetModelHandle();
std::cout << "Browser data model created" << std::endl;
}
std::cout << "All data models initialized" << std::endl;
}

View File

@@ -0,0 +1,99 @@
// Data models for Mosis phone UI
#pragma once
#include <RmlUi/Core.h>
#include <string>
#include <vector>
// Settings data model
struct SettingsData {
bool wifi = true;
bool bluetooth = false;
bool airplane_mode = false;
bool location = true;
bool dark_mode = true;
bool notifications = true;
bool do_not_disturb = false;
// User profile
Rml::String user_name = "User Name";
Rml::String user_email = "user@example.com";
// Device info
Rml::String wifi_network = "Home_Network";
int battery_percent = 85;
Rml::String battery_remaining = "About 12h remaining";
Rml::String storage_used = "45.2 GB used of 128 GB";
};
// Phone state data model
struct PhoneData {
Rml::String dial_number = "";
bool is_calling = false;
Rml::String call_contact = "";
Rml::String call_duration = "00:00";
};
// Messages data model
struct Message {
Rml::String from;
Rml::String text;
Rml::String time;
};
struct Conversation {
int id;
Rml::String name;
Rml::String color;
Rml::String last_message;
Rml::String time;
int unread;
std::vector<Message> messages;
};
// Contact data model
struct Contact {
int id;
Rml::String name;
Rml::String phone;
Rml::String email;
Rml::String color;
Rml::String initial;
};
// Browser data model
struct BrowserTab {
Rml::String url;
Rml::String title;
bool is_loading;
};
struct BrowserData {
Rml::String current_url = "example.com";
Rml::String page_title = "Example Domain";
Rml::String page_content = "This domain is for use in illustrative examples.";
std::vector<BrowserTab> tabs;
int current_tab = 0;
bool can_go_back = false;
bool can_go_forward = false;
};
// Global data instances
extern SettingsData g_settings;
extern PhoneData g_phone;
extern BrowserData g_browser;
extern std::vector<Conversation> g_conversations;
extern std::vector<Contact> g_contacts;
extern int g_selected_conversation;
extern int g_selected_contact;
// Data model handles
extern Rml::DataModelHandle g_settings_model;
extern Rml::DataModelHandle g_phone_model;
extern Rml::DataModelHandle g_messages_model;
extern Rml::DataModelHandle g_contacts_model;
extern Rml::DataModelHandle g_browser_model;
// Functions
void initializeSampleData();
void setupDataModels(Rml::Context* context);

View File

@@ -0,0 +1,89 @@
// Desktop platform implementation (simplified - uses RmlUi backend for graphics)
#include "desktop_platform.h"
#include <iostream>
#include <chrono>
// Note: Graphics context and rendering is handled by RmlUi's backend.
// This platform implementation provides additional utilities and state management.
namespace mosis::desktop {
DesktopPlatform::DesktopPlatform()
: m_file_interface(std::make_unique<DesktopFileInterface>())
{
auto now = std::chrono::steady_clock::now();
m_start_time = std::chrono::duration<double>(now.time_since_epoch()).count();
}
DesktopPlatform::~DesktopPlatform() = default;
bool DesktopPlatform::Initialize(uint32_t width, uint32_t height, const char* title) {
m_width = width;
m_height = height;
// Graphics initialization is done by RmlUi Backend
return true;
}
void DesktopPlatform::Shutdown() {
// Graphics shutdown is done by RmlUi Backend
}
std::unique_ptr<IGraphicsContext> DesktopPlatform::CreateGraphicsContext() {
// Graphics context is managed by RmlUi Backend
return nullptr;
}
std::unique_ptr<IRenderTarget> DesktopPlatform::CreateRenderTarget(uint32_t width, uint32_t height) {
// Render targets are managed by RmlUi Backend
return nullptr;
}
IFileInterface& DesktopPlatform::GetFileInterface() {
return *m_file_interface;
}
void DesktopPlatform::Log(const std::string& message) {
std::cout << "[INFO] " << message << std::endl;
}
void DesktopPlatform::LogError(const std::string& message) {
std::cerr << "[ERROR] " << message << std::endl;
}
bool DesktopPlatform::PollEvents() {
// Events are handled by RmlUi Backend
return true;
}
void DesktopPlatform::SwapBuffers() {
// Swap is handled by RmlUi Backend
}
bool DesktopPlatform::ShouldClose() const {
return false; // Determined by RmlUi Backend
}
void DesktopPlatform::SetResolution(uint32_t width, uint32_t height) {
m_width = width;
m_height = height;
}
float DesktopPlatform::GetDpiScale() const {
return 1.0f;
}
double DesktopPlatform::GetElapsedTime() const {
auto now = std::chrono::steady_clock::now();
double current = std::chrono::duration<double>(now.time_since_epoch()).count();
return current - m_start_time;
}
bool DesktopPlatform::IsMouseButtonDown() const {
return false; // Input is handled through RmlUi
}
void DesktopPlatform::GetMousePosition(double& x, double& y) const {
x = y = 0; // Input is handled through RmlUi
}
} // namespace mosis::desktop

View File

@@ -0,0 +1,50 @@
// Desktop platform implementation using GLFW + OpenGL 3.3
// Note: Graphics context and rendering is handled by RmlUi's backend.
// This platform implementation provides file interface and utilities.
#pragma once
#include "platform.h"
#include "file_interface.h"
#include <memory>
namespace mosis::desktop {
class DesktopPlatform : public IPlatform {
uint32_t m_width = 540;
uint32_t m_height = 960;
std::unique_ptr<DesktopFileInterface> m_file_interface;
double m_start_time = 0.0;
public:
DesktopPlatform();
~DesktopPlatform() override;
// Initialize the platform
bool Initialize(uint32_t width, uint32_t height, const char* title);
void Shutdown();
// IPlatform implementation
std::unique_ptr<IGraphicsContext> CreateGraphicsContext() override;
std::unique_ptr<IRenderTarget> CreateRenderTarget(uint32_t width, uint32_t height) override;
IFileInterface& GetFileInterface() override;
void Log(const std::string& message) override;
void LogError(const std::string& message) override;
bool PollEvents() override;
void SwapBuffers() override;
bool ShouldClose() const override;
uint32_t GetWidth() const override { return m_width; }
uint32_t GetHeight() const override { return m_height; }
void SetResolution(uint32_t width, uint32_t height) override;
float GetDpiScale() const override;
double GetElapsedTime() const override;
// Input state (delegated to RmlUi backend)
bool IsMouseButtonDown() const;
void GetMousePosition(double& x, double& y) const;
};
} // namespace mosis::desktop

View File

@@ -0,0 +1,93 @@
// Hot-reload file watcher implementation
#include "hot_reload.h"
#include <iostream>
#ifdef _WIN32
#include <Windows.h>
#endif
namespace mosis::desktop {
HotReload::HotReload(const std::filesystem::path& watch_path, ReloadCallback callback)
: m_watch_path(watch_path)
, m_callback(std::move(callback))
, m_last_change(std::chrono::steady_clock::now())
{}
HotReload::~HotReload() {
Stop();
}
void HotReload::Start() {
if (m_running) return;
#ifdef _WIN32
m_notification_handle = FindFirstChangeNotificationW(
m_watch_path.c_str(),
TRUE, // Watch subtree
FILE_NOTIFY_CHANGE_LAST_WRITE
);
if (m_notification_handle == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to create file change notification for: "
<< m_watch_path << std::endl;
return;
}
#endif
m_running = true;
m_watch_thread = std::thread(&HotReload::WatchThread, this);
}
void HotReload::Stop() {
m_running = false;
if (m_watch_thread.joinable()) {
m_watch_thread.join();
}
#ifdef _WIN32
if (m_notification_handle && m_notification_handle != INVALID_HANDLE_VALUE) {
FindCloseChangeNotification(m_notification_handle);
m_notification_handle = nullptr;
}
#endif
}
bool HotReload::CheckForChanges() {
if (m_change_detected) {
auto now = std::chrono::steady_clock::now();
if (now - m_last_change >= m_debounce_delay) {
m_change_detected = false;
if (m_callback) {
m_callback();
}
return true;
}
}
return false;
}
void HotReload::WatchThread() {
while (m_running) {
#ifdef _WIN32
DWORD result = WaitForSingleObject(m_notification_handle, 100); // 100ms timeout
if (result == WAIT_OBJECT_0) {
m_last_change = std::chrono::steady_clock::now();
m_change_detected = true;
// Reset the notification
if (!FindNextChangeNotification(m_notification_handle)) {
std::cerr << "FindNextChangeNotification failed" << std::endl;
break;
}
}
#else
// TODO: Linux inotify / macOS FSEvents implementation
// For now, just sleep to avoid busy loop
std::this_thread::sleep_for(std::chrono::milliseconds(100));
#endif
}
}
} // namespace mosis::desktop

44
designer/src/hot_reload.h Normal file
View File

@@ -0,0 +1,44 @@
// Hot-reload file watcher for development
#pragma once
#include <filesystem>
#include <functional>
#include <thread>
#include <atomic>
#include <chrono>
namespace mosis::desktop {
class HotReload {
public:
using ReloadCallback = std::function<void()>;
HotReload(const std::filesystem::path& watch_path, ReloadCallback callback);
~HotReload();
void Start();
void Stop();
bool IsRunning() const { return m_running; }
// Check for changes (call from main thread if not using threaded mode)
bool CheckForChanges();
private:
void WatchThread();
std::filesystem::path m_watch_path;
ReloadCallback m_callback;
std::thread m_watch_thread;
std::atomic<bool> m_running{false};
std::atomic<bool> m_change_detected{false};
#ifdef _WIN32
void* m_notification_handle = nullptr; // HANDLE
#endif
// Debounce settings
std::chrono::milliseconds m_debounce_delay{100};
std::chrono::steady_clock::time_point m_last_change;
};
} // namespace mosis::desktop

View File

@@ -0,0 +1,330 @@
// Desktop kernel implementation
#include "kernel_impl.h"
#include "platform.h"
#include "file_interface.h"
#include "log.h"
#include <RmlUi/Core.h>
#include <RmlUi/Lua.h>
#include <RmlUi/Lua/Interpreter.h>
#include <mutex>
#include <vector>
#include <functional>
#include <atomic>
#include <iostream>
#include <chrono>
#include <algorithm>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
namespace mosis {
// Global kernel instance for Lua access
static DesktopKernel* g_kernel_instance = nullptr;
// Implementation class
class DesktopKernel::Impl {
public:
KernelConfig config;
Rml::Context* context = nullptr;
Rml::ElementDocument* document = nullptr;
std::vector<std::shared_ptr<IServiceListener>> listeners;
std::vector<std::function<void()>> tasks;
std::mutex mutex;
std::atomic<bool> running{false};
std::atomic<bool> reload_requested{false};
std::string current_document_path;
// Status bar time
std::string status_time;
Rml::DataModelHandle status_time_handle;
explicit Impl(const KernelConfig& cfg) : config(cfg) {}
};
DesktopKernel::DesktopKernel(const KernelConfig& config)
: m_impl(std::make_unique<Impl>(config))
{
g_kernel_instance = this;
}
DesktopKernel::~DesktopKernel() {
Stop();
g_kernel_instance = nullptr;
}
void DesktopKernel::AddListener(std::shared_ptr<IServiceListener> listener) {
std::lock_guard lock(m_impl->mutex);
m_impl->listeners.push_back(listener);
if (m_impl->running) {
listener->OnServiceInitialized(true);
}
}
void DesktopKernel::RemoveListener(std::shared_ptr<IServiceListener> listener) {
std::lock_guard lock(m_impl->mutex);
m_impl->listeners.erase(
std::remove(m_impl->listeners.begin(), m_impl->listeners.end(), listener),
m_impl->listeners.end()
);
}
void DesktopKernel::OnTouchDown(float x, float y) {
std::lock_guard lock(m_impl->mutex);
auto px = static_cast<int>(x * m_impl->config.width);
auto py = static_cast<int>(y * m_impl->config.height);
m_impl->tasks.emplace_back([this, px, py]() {
if (m_impl->context) {
m_impl->context->ProcessMouseMove(px, py, 0);
m_impl->context->ProcessMouseButtonDown(0, 0);
}
});
}
void DesktopKernel::OnTouchMove(float x, float y) {
std::lock_guard lock(m_impl->mutex);
auto px = static_cast<int>(x * m_impl->config.width);
auto py = static_cast<int>(y * m_impl->config.height);
m_impl->tasks.emplace_back([this, px, py]() {
if (m_impl->context) {
m_impl->context->ProcessMouseMove(px, py, 0);
}
});
}
void DesktopKernel::OnTouchUp(float x, float y) {
std::lock_guard lock(m_impl->mutex);
auto px = static_cast<int>(x * m_impl->config.width);
auto py = static_cast<int>(y * m_impl->config.height);
m_impl->tasks.emplace_back([this, px, py]() {
if (m_impl->context) {
m_impl->context->ProcessMouseMove(px, py, 0);
m_impl->context->ProcessMouseButtonUp(0, 0);
}
});
}
void DesktopKernel::OnBackButton() {
std::lock_guard lock(m_impl->mutex);
m_impl->tasks.emplace_back([this]() {
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
lua_getglobal(L, "goBack");
if (lua_isfunction(L, -1)) {
lua_pcall(L, 0, 0, 0);
} else {
lua_pop(L, 1);
}
});
}
void DesktopKernel::OnHomeButton() {
std::lock_guard lock(m_impl->mutex);
m_impl->tasks.emplace_back([this]() {
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
lua_getglobal(L, "goHome");
if (lua_isfunction(L, -1)) {
lua_pcall(L, 0, 0, 0);
} else {
lua_pop(L, 1);
}
});
}
void DesktopKernel::OnRecentsButton() {
std::cout << "Recents button pressed (not implemented)" << std::endl;
}
void DesktopKernel::RequestReload() {
m_impl->reload_requested = true;
}
void DesktopKernel::LoadDocument(const std::string& path) {
std::lock_guard lock(m_impl->mutex);
m_impl->tasks.emplace_back([this, path]() {
auto& file_interface = GetPlatform().GetFileInterface();
std::string full_path = file_interface.ResolvePath(path);
if (m_impl->document) {
m_impl->context->UnloadDocument(m_impl->document);
m_impl->document = nullptr;
}
m_impl->document = m_impl->context->LoadDocument(full_path);
if (m_impl->document) {
m_impl->document->Show();
m_impl->current_document_path = full_path;
std::cout << "Loaded document: " << path << std::endl;
} else {
std::cerr << "Failed to load document: " << path << std::endl;
}
});
}
void DesktopKernel::Start() {
if (m_impl->running) return;
m_impl->running = true;
std::lock_guard lock(m_impl->mutex);
for (auto& listener : m_impl->listeners) {
listener->OnServiceInitialized(true);
}
}
void DesktopKernel::Stop() {
m_impl->running = false;
}
bool DesktopKernel::IsRunning() const {
return m_impl->running;
}
void DesktopKernel::Update() {
if (!m_impl->running || !m_impl->context) return;
// Process pending tasks
{
std::lock_guard lock(m_impl->mutex);
for (const auto& task : m_impl->tasks) {
task();
}
m_impl->tasks.clear();
}
// Handle reload request
if (m_impl->reload_requested) {
m_impl->reload_requested = false;
if (!m_impl->current_document_path.empty()) {
std::cout << "Reloading document..." << std::endl;
if (m_impl->document) {
m_impl->context->UnloadDocument(m_impl->document);
m_impl->document = nullptr;
}
m_impl->context->Update();
m_impl->document = m_impl->context->LoadDocument(m_impl->current_document_path);
if (m_impl->document) {
m_impl->document->ReloadStyleSheet();
m_impl->document->Show();
std::cout << "Document reloaded" << std::endl;
}
}
}
// Update status bar time
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::tm tm{};
#ifdef _WIN32
localtime_s(&tm, &time_t);
#else
localtime_r(&time_t, &tm);
#endif
char buffer[16];
std::strftime(buffer, sizeof(buffer), "%H:%M", &tm);
m_impl->status_time = buffer;
if (m_impl->status_time_handle) {
m_impl->status_time_handle.DirtyVariable("time");
}
m_impl->context->Update();
}
void DesktopKernel::Render() {
if (!m_impl->running || !m_impl->context) return;
m_impl->context->Render();
std::lock_guard lock(m_impl->mutex);
for (auto& listener : m_impl->listeners) {
listener->OnFrameAvailable();
}
}
void DesktopKernel::SetContext(Rml::Context* context) {
m_impl->context = context;
// Setup status data model
if (m_impl->context) {
if (auto constructor = m_impl->context->CreateDataModel("status-data")) {
constructor.Bind("time", &m_impl->status_time);
m_impl->status_time_handle = constructor.GetModelHandle();
}
}
}
Rml::Context* DesktopKernel::GetContext() const {
return m_impl->context;
}
void DesktopKernel::SetDocument(Rml::ElementDocument* doc) {
m_impl->document = doc;
}
Rml::ElementDocument* DesktopKernel::GetDocument() const {
return m_impl->document;
}
void DesktopKernel::SetCurrentDocumentPath(const std::string& path) {
m_impl->current_document_path = path;
}
int DesktopKernel::LuaLoadScreen(lua_State* L) {
if (!g_kernel_instance) {
lua_pushboolean(L, false);
return 1;
}
const char* path = luaL_checkstring(L, 1);
Log(std::string("Loading screen: ") + path);
auto& file_interface = GetPlatform().GetFileInterface();
std::string full_path = file_interface.ResolvePath(path);
if (!file_interface.FileExists(full_path)) {
Log(std::string("Screen not found: ") + full_path);
lua_pushboolean(L, false);
return 1;
}
auto* impl = g_kernel_instance->m_impl.get();
// Unload current document
if (impl->document) {
impl->context->UnloadDocument(impl->document);
impl->document = nullptr;
}
// Load new document
impl->document = impl->context->LoadDocument(full_path);
if (impl->document) {
impl->document->Show();
impl->current_document_path = full_path;
Log(std::string("Loaded screen: ") + path);
lua_pushboolean(L, true);
} else {
Log(std::string("Failed to load screen: ") + path);
lua_pushboolean(L, false);
}
return 1;
}
void DesktopKernel::RegisterLuaFunctions() {
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
lua_pushcfunction(L, LuaLoadScreen);
lua_setglobal(L, "loadScreen");
std::cout << "Registered Lua loadScreen function" << std::endl;
}
// Factory function
std::unique_ptr<IKernel> CreateKernel(const KernelConfig& config) {
return std::make_unique<DesktopKernel>(config);
}
} // namespace mosis

View File

@@ -0,0 +1,57 @@
// Desktop kernel header
#pragma once
#include "service_interface.h"
#include <RmlUi/Core/Types.h>
#include <memory>
#include <string>
// Forward declarations
namespace Rml {
class Context;
class ElementDocument;
}
struct lua_State;
namespace mosis {
// Desktop kernel implementation
class DesktopKernel : public IKernel {
public:
explicit DesktopKernel(const KernelConfig& config);
~DesktopKernel() override;
// IKernel implementation
void AddListener(std::shared_ptr<IServiceListener> listener) override;
void RemoveListener(std::shared_ptr<IServiceListener> listener) override;
void OnTouchDown(float x, float y) override;
void OnTouchMove(float x, float y) override;
void OnTouchUp(float x, float y) override;
void OnBackButton() override;
void OnHomeButton() override;
void OnRecentsButton() override;
void RequestReload() override;
void LoadDocument(const std::string& path) override;
void Start() override;
void Stop() override;
bool IsRunning() const override;
void Update() override;
void Render() override;
// Desktop-specific methods
void SetContext(Rml::Context* context);
Rml::Context* GetContext() const;
void SetDocument(Rml::ElementDocument* doc);
Rml::ElementDocument* GetDocument() const;
void SetCurrentDocumentPath(const std::string& path);
// Static Lua function registration
static int LuaLoadScreen(lua_State* L);
static void RegisterLuaFunctions();
private:
class Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace mosis

22
designer/src/log.h Normal file
View File

@@ -0,0 +1,22 @@
// Logging utility for Mosis Designer
#pragma once
#include <string>
#include <fstream>
#include <iostream>
namespace mosis {
// Global log file - defined in main.cpp
extern std::ofstream* g_log_file_ptr;
// Log function that writes to both stdout and file
inline void Log(const std::string& message) {
std::cout << message << std::endl;
if (g_log_file_ptr && g_log_file_ptr->is_open()) {
*g_log_file_ptr << message << std::endl;
g_log_file_ptr->flush();
}
}
} // namespace mosis

379
designer/src/main.cpp Normal file
View File

@@ -0,0 +1,379 @@
// Mosis Designer - Desktop designer and testing tool for Mosis virtual phone UI
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <thread>
#include <chrono>
#include <RmlUi/Core.h>
#include <RmlUi/Debugger.h>
#include <RmlUi/Lua.h>
#include <RmlUi/Lua/Interpreter.h>
#include <RmlUi_Backend.h>
#include "platform.h"
#include "file_interface.h"
#include "service_interface.h"
#include "kernel_impl.h"
#include "data_models.h"
#include "hot_reload.h"
#include "desktop_platform.h"
#include "testing/ui_inspector.h"
// Command-line options
struct Options {
std::string document_path;
uint32_t width = 540;
uint32_t height = 960;
bool dump_mode = false;
bool debug_mode = false;
std::string output_dir = "dump";
std::string log_file; // If set, write logs to this file
std::string hierarchy_file; // If set, dump UI hierarchy to this file each frame
};
// Global log file stream
static std::ofstream g_log_file;
// Pointer for shared logging (used by kernel_impl.cpp via log.h)
namespace mosis {
std::ofstream* g_log_file_ptr = nullptr;
}
// Log function that writes to both stdout and file
void LogMessage(const std::string& message) {
std::cout << message << std::endl;
if (g_log_file.is_open()) {
g_log_file << message << std::endl;
g_log_file.flush();
}
}
// Forward declarations
void PrintUsage(const char* program);
Options ParseOptions(int argc, const char* argv[]);
void LoadFonts(const std::filesystem::path& fonts_path);
std::filesystem::path FindAssetsPath(const std::filesystem::path& start_path);
// Custom system interface with logging
class DesignerSystemInterface : public Rml::SystemInterface {
Rml::SystemInterface* m_backend_interface;
public:
explicit DesignerSystemInterface(Rml::SystemInterface* backend)
: m_backend_interface(backend) {}
double GetElapsedTime() override {
return m_backend_interface ? m_backend_interface->GetElapsedTime() : 0.0;
}
bool LogMessage(Rml::Log::Type type, const Rml::String& message) override {
const char* type_str = "INFO";
switch (type) {
case Rml::Log::LT_ERROR: type_str = "ERROR"; break;
case Rml::Log::LT_WARNING: type_str = "WARNING"; break;
case Rml::Log::LT_INFO: type_str = "INFO"; break;
case Rml::Log::LT_DEBUG: type_str = "DEBUG"; break;
default: break;
}
std::cout << "[RmlUi " << type_str << "] " << message << std::endl;
return true;
}
};
// Global state
static DesignerSystemInterface* g_system_interface = nullptr;
static mosis::DesktopFileInterface* g_file_interface = nullptr;
static mosis::IKernel* g_kernel = nullptr;
static std::filesystem::path g_assets_path;
static mosis::testing::UIInspector g_ui_inspector;
int main(int argc, const char* argv[])
{
// Parse command-line options first
Options opts = ParseOptions(argc, argv);
// Open log file if specified
if (!opts.log_file.empty()) {
g_log_file.open(opts.log_file, std::ios::out | std::ios::trunc);
if (!g_log_file.is_open()) {
std::cerr << "Warning: Could not open log file: " << opts.log_file << std::endl;
} else {
mosis::g_log_file_ptr = &g_log_file;
}
}
LogMessage("Mosis Designer v0.1.0");
if (opts.document_path.empty()) {
PrintUsage(argv[0]);
return EXIT_FAILURE;
}
std::filesystem::path document_file = opts.document_path;
if (!std::filesystem::exists(document_file)) {
std::cerr << "File not found: " << opts.document_path << std::endl;
return EXIT_FAILURE;
}
document_file = std::filesystem::absolute(document_file);
// Initialize the RmlUi backend (GLFW + OpenGL)
if (!Backend::Initialize("Mosis Designer", opts.width, opts.height, true)) {
std::cerr << "Failed to initialize backend" << std::endl;
return EXIT_FAILURE;
}
// Find assets path
g_assets_path = FindAssetsPath(document_file.parent_path());
LogMessage("Assets path: " + g_assets_path.generic_string());
// Setup custom interfaces
g_system_interface = new DesignerSystemInterface(Backend::GetSystemInterface());
g_file_interface = new mosis::DesktopFileInterface(g_assets_path.string());
// Set platform for kernel
auto platform = std::make_unique<mosis::desktop::DesktopPlatform>();
platform->GetFileInterface().SetAssetsPath(g_assets_path.string());
mosis::SetPlatform(std::move(platform));
Rml::SetSystemInterface(g_system_interface);
Rml::SetFileInterface(g_file_interface);
Rml::SetRenderInterface(Backend::GetRenderInterface());
// Initialize RmlUi
Rml::Initialise();
Rml::Lua::Initialise();
LogMessage("RmlUi and Lua initialized");
// Create context
Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(opts.width, opts.height));
if (!context) {
std::cerr << "Failed to create RmlUi context" << std::endl;
Rml::Shutdown();
Backend::Shutdown();
return EXIT_FAILURE;
}
// Enable debugger in debug mode
if (opts.debug_mode) {
Rml::Debugger::Initialise(context);
Rml::Debugger::SetVisible(true);
}
// Load fonts
std::filesystem::path fonts_path = g_assets_path / "fonts";
LoadFonts(fonts_path);
// Initialize sample data and data models
initializeSampleData();
setupDataModels(context);
// Create and configure kernel
mosis::KernelConfig kernel_config;
kernel_config.width = opts.width;
kernel_config.height = opts.height;
kernel_config.initial_document = document_file.string();
kernel_config.threaded = false;
auto kernel = mosis::CreateKernel(kernel_config);
g_kernel = kernel.get();
// Set context and register Lua functions
auto* desktop_kernel = dynamic_cast<mosis::DesktopKernel*>(g_kernel);
if (desktop_kernel) {
desktop_kernel->SetContext(context);
mosis::DesktopKernel::RegisterLuaFunctions();
}
// Load the initial document
std::string document_path_str = document_file.generic_string();
LogMessage("Loading document: " + document_path_str);
Rml::ElementDocument* document = context->LoadDocument(document_path_str);
if (document) {
document->Show();
if (desktop_kernel) {
desktop_kernel->SetDocument(document);
desktop_kernel->SetCurrentDocumentPath(document_path_str);
}
LogMessage("Document loaded successfully");
} else {
LogMessage("ERROR: Failed to load document!");
}
// Start kernel
kernel->Start();
// Setup hot-reload
std::unique_ptr<mosis::desktop::HotReload> hot_reload;
if (!opts.dump_mode) {
hot_reload = std::make_unique<mosis::desktop::HotReload>(
g_assets_path,
[&kernel]() {
std::cout << "File change detected, requesting reload..." << std::endl;
kernel->RequestReload();
}
);
hot_reload->Start();
std::cout << "Hot-reload enabled for: " << g_assets_path.generic_string() << std::endl;
}
// Main loop
bool running = true;
while (running) {
// Process hot-reload
if (hot_reload) {
hot_reload->CheckForChanges();
}
// Process events and update
running = Backend::ProcessEvents(context);
// Update kernel (processes tasks, updates time, etc.)
kernel->Update();
// Render
Backend::BeginFrame();
kernel->Render();
Backend::PresentFrame();
// Dump hierarchy if enabled (quiet mode to avoid spamming console)
if (!opts.hierarchy_file.empty() && desktop_kernel) {
Rml::ElementDocument* doc = desktop_kernel->GetDocument();
if (doc) {
g_ui_inspector.SaveToFile(doc, opts.hierarchy_file, true);
}
}
}
// Cleanup
kernel->Stop();
kernel.reset();
g_kernel = nullptr;
if (hot_reload) {
hot_reload->Stop();
}
if (opts.debug_mode) {
Rml::Debugger::Shutdown();
}
delete g_system_interface;
delete g_file_interface;
Rml::Shutdown();
Backend::Shutdown();
std::cout << "Mosis Designer shutdown complete" << std::endl;
return EXIT_SUCCESS;
}
void PrintUsage(const char* program)
{
std::cout << "Usage: " << program << " <document.rml> [options]" << std::endl;
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " --resolution WxH Set phone resolution (default: 540x960)" << std::endl;
std::cout << " --dump Dump mode: screenshot + hierarchy, then exit" << std::endl;
std::cout << " --debug Enable RmlUi debugger" << std::endl;
std::cout << " --output DIR Output directory for dump mode (default: dump)" << std::endl;
std::cout << " --log FILE Write log output to file (for automated testing)" << std::endl;
std::cout << " --hierarchy FILE Continuously dump UI hierarchy to JSON file" << std::endl;
std::cout << std::endl;
std::cout << "Examples:" << std::endl;
std::cout << " " << program << " assets/apps/home/home.rml" << std::endl;
std::cout << " " << program << " assets/apps/home/home.rml --resolution 720x1280" << std::endl;
std::cout << " " << program << " assets/apps/home/home.rml --dump" << std::endl;
}
Options ParseOptions(int argc, const char* argv[])
{
Options opts;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--resolution" && i + 1 < argc) {
std::string res = argv[++i];
size_t x_pos = res.find('x');
if (x_pos != std::string::npos) {
opts.width = std::stoi(res.substr(0, x_pos));
opts.height = std::stoi(res.substr(x_pos + 1));
}
} else if (arg == "--dump") {
opts.dump_mode = true;
} else if (arg == "--debug") {
opts.debug_mode = true;
} else if (arg == "--output" && i + 1 < argc) {
opts.output_dir = argv[++i];
} else if (arg == "--log" && i + 1 < argc) {
opts.log_file = argv[++i];
} else if (arg == "--hierarchy" && i + 1 < argc) {
opts.hierarchy_file = argv[++i];
} else if (arg[0] != '-') {
opts.document_path = arg;
}
}
return opts;
}
void LoadFonts(const std::filesystem::path& fonts_path)
{
if (!std::filesystem::exists(fonts_path)) {
std::cerr << "Fonts directory not found: " << fonts_path << std::endl;
return;
}
int count = 0;
for (const auto& entry : std::filesystem::directory_iterator(fonts_path)) {
if (entry.path().extension() == ".ttf") {
std::string font_path = entry.path().generic_string();
if (Rml::LoadFontFace(font_path)) {
std::cout << "Loaded font: " << entry.path().filename() << std::endl;
++count;
}
}
}
// Also try Roboto subdirectory
std::filesystem::path roboto_path = fonts_path / "Roboto";
if (std::filesystem::exists(roboto_path)) {
for (const auto& entry : std::filesystem::recursive_directory_iterator(roboto_path)) {
if (entry.path().extension() == ".ttf") {
std::string font_path = entry.path().generic_string();
if (Rml::LoadFontFace(font_path)) {
std::cout << "Loaded font: " << entry.path().filename() << std::endl;
++count;
}
}
}
}
std::cout << "Loaded " << count << " fonts" << std::endl;
}
std::filesystem::path FindAssetsPath(const std::filesystem::path& start_path)
{
std::filesystem::path current = std::filesystem::absolute(start_path);
// Walk up the directory tree looking for a fonts/ subdirectory
while (!current.empty() && current.has_parent_path()) {
std::filesystem::path fonts_path = current / "fonts";
if (std::filesystem::exists(fonts_path) && std::filesystem::is_directory(fonts_path)) {
// Check if it contains TTF files
for (const auto& entry : std::filesystem::directory_iterator(fonts_path)) {
if (entry.path().extension() == ".ttf") {
return current;
}
}
}
current = current.parent_path();
}
// Fall back to start path
return std::filesystem::absolute(start_path);
}

View File

@@ -0,0 +1,131 @@
// Action player implementation
#include "action_player.h"
#include "service_interface.h"
#include <iostream>
#include <thread>
namespace mosis::testing {
void ActionPlayer::LoadActions(const std::vector<Action>& actions) {
m_actions = actions;
Reset();
}
void ActionPlayer::LoadFromFile(const std::string& path) {
ActionRecorder recorder;
recorder.LoadFromFile(path);
m_actions = recorder.GetActions();
Reset();
}
void ActionPlayer::Play() {
if (m_actions.empty()) {
std::cout << "No actions to play" << std::endl;
return;
}
m_playing = true;
std::cout << "Playback started" << std::endl;
}
void ActionPlayer::Pause() {
m_playing = false;
std::cout << "Playback paused at action " << m_current_index << std::endl;
}
void ActionPlayer::Stop() {
m_playing = false;
Reset();
std::cout << "Playback stopped" << std::endl;
}
void ActionPlayer::Reset() {
m_current_index = 0;
m_elapsed_time_ms = 0;
}
void ActionPlayer::StepForward() {
if (m_current_index < m_actions.size()) {
ExecuteAction(m_actions[m_current_index]);
++m_current_index;
}
}
void ActionPlayer::Update(double delta_time_ms) {
if (!m_playing || m_current_index >= m_actions.size()) {
return;
}
m_elapsed_time_ms += delta_time_ms;
// Execute all actions whose timestamp has passed
while (m_current_index < m_actions.size()) {
const auto& action = m_actions[m_current_index];
if (action.timestamp_ms <= m_elapsed_time_ms) {
ExecuteAction(action);
++m_current_index;
} else {
break;
}
}
// Check if finished
if (m_current_index >= m_actions.size()) {
m_playing = false;
std::cout << "Playback finished" << std::endl;
}
}
void ActionPlayer::ExecuteAction(const Action& action) {
if (!m_kernel) {
std::cerr << "No kernel set for action player" << std::endl;
return;
}
switch (action.type) {
case ActionType::Tap:
std::cout << "Execute tap at (" << action.x << ", " << action.y << ")" << std::endl;
m_kernel->OnTouchDown(static_cast<float>(action.x), static_cast<float>(action.y));
m_kernel->OnTouchUp(static_cast<float>(action.x), static_cast<float>(action.y));
break;
case ActionType::Swipe:
std::cout << "Execute swipe from (" << action.x1 << ", " << action.y1
<< ") to (" << action.x2 << ", " << action.y2 << ")" << std::endl;
// Simplified swipe - just start and end
m_kernel->OnTouchDown(static_cast<float>(action.x1), static_cast<float>(action.y1));
m_kernel->OnTouchMove(static_cast<float>(action.x2), static_cast<float>(action.y2));
m_kernel->OnTouchUp(static_cast<float>(action.x2), static_cast<float>(action.y2));
break;
case ActionType::LongPress:
std::cout << "Execute long press at (" << action.x << ", " << action.y
<< ") for " << action.duration_ms << "ms" << std::endl;
m_kernel->OnTouchDown(static_cast<float>(action.x), static_cast<float>(action.y));
// In a real implementation, we'd hold for duration
m_kernel->OnTouchUp(static_cast<float>(action.x), static_cast<float>(action.y));
break;
case ActionType::Button:
std::cout << "Execute button: " << action.button << std::endl;
if (action.button == "back") {
m_kernel->OnBackButton();
} else if (action.button == "home") {
m_kernel->OnHomeButton();
} else if (action.button == "recents") {
m_kernel->OnRecentsButton();
}
break;
case ActionType::Wait:
std::cout << "Wait " << action.duration_ms << "ms" << std::endl;
// Wait is handled by timestamp comparison
break;
}
// Call callback if set
if (m_action_callback) {
m_action_callback(action, m_current_index);
}
}
} // namespace mosis::testing

View File

@@ -0,0 +1,60 @@
// Action player for replaying recorded UI interactions
#pragma once
#include "action_recorder.h"
#include <functional>
namespace mosis {
class IKernel;
}
namespace mosis::testing {
// Callback for when an action is executed
using ActionCallback = std::function<void(const Action&, size_t index)>;
// Plays back recorded actions
class ActionPlayer {
public:
ActionPlayer() = default;
// Set the kernel for executing actions
void SetKernel(IKernel* kernel) { m_kernel = kernel; }
// Load actions to play
void LoadActions(const std::vector<Action>& actions);
void LoadFromFile(const std::string& path);
// Playback control
void Play();
void Pause();
void Stop();
void Reset();
// Step through one action at a time
void StepForward();
// Update (call each frame)
void Update(double delta_time_ms);
// State
bool IsPlaying() const { return m_playing; }
bool IsFinished() const { return m_current_index >= m_actions.size(); }
size_t GetCurrentIndex() const { return m_current_index; }
size_t GetActionCount() const { return m_actions.size(); }
// Callbacks
void SetActionCallback(ActionCallback callback) { m_action_callback = callback; }
private:
void ExecuteAction(const Action& action);
IKernel* m_kernel = nullptr;
std::vector<Action> m_actions;
size_t m_current_index = 0;
double m_elapsed_time_ms = 0;
bool m_playing = false;
ActionCallback m_action_callback;
};
} // namespace mosis::testing

View File

@@ -0,0 +1,191 @@
// Action recorder implementation
#include "action_recorder.h"
#include <fstream>
#include <iostream>
namespace mosis::testing {
nlohmann::json Action::ToJson() const {
nlohmann::json j;
switch (type) {
case ActionType::Tap:
j["type"] = "tap";
j["x"] = x;
j["y"] = y;
break;
case ActionType::Swipe:
j["type"] = "swipe";
j["x1"] = x1;
j["y1"] = y1;
j["x2"] = x2;
j["y2"] = y2;
j["duration"] = duration_ms;
break;
case ActionType::LongPress:
j["type"] = "long_press";
j["x"] = x;
j["y"] = y;
j["duration"] = duration_ms;
break;
case ActionType::Button:
j["type"] = "button";
j["button"] = button;
break;
case ActionType::Wait:
j["type"] = "wait";
j["duration"] = duration_ms;
break;
}
j["timestamp"] = timestamp_ms;
return j;
}
Action Action::FromJson(const nlohmann::json& j) {
Action action;
action.timestamp_ms = j.value("timestamp", 0);
std::string type_str = j.value("type", "");
if (type_str == "tap") {
action.type = ActionType::Tap;
action.x = j.value("x", 0.0);
action.y = j.value("y", 0.0);
} else if (type_str == "swipe") {
action.type = ActionType::Swipe;
action.x1 = j.value("x1", 0.0);
action.y1 = j.value("y1", 0.0);
action.x2 = j.value("x2", 0.0);
action.y2 = j.value("y2", 0.0);
action.duration_ms = j.value("duration", 0);
} else if (type_str == "long_press") {
action.type = ActionType::LongPress;
action.x = j.value("x", 0.0);
action.y = j.value("y", 0.0);
action.duration_ms = j.value("duration", 0);
} else if (type_str == "button") {
action.type = ActionType::Button;
action.button = j.value("button", "");
} else if (type_str == "wait") {
action.type = ActionType::Wait;
action.duration_ms = j.value("duration", 0);
}
return action;
}
void ActionRecorder::StartRecording() {
m_recording = true;
m_start_time = std::chrono::steady_clock::now();
m_actions.clear();
std::cout << "Action recording started" << std::endl;
}
void ActionRecorder::StopRecording() {
m_recording = false;
std::cout << "Action recording stopped. Recorded " << m_actions.size() << " actions" << std::endl;
}
void ActionRecorder::RecordTap(double x, double y) {
if (!m_recording) return;
Action action;
action.type = ActionType::Tap;
action.x = x;
action.y = y;
action.timestamp_ms = GetTimestamp();
m_actions.push_back(action);
}
void ActionRecorder::RecordSwipe(double x1, double y1, double x2, double y2, int duration_ms) {
if (!m_recording) return;
Action action;
action.type = ActionType::Swipe;
action.x1 = x1;
action.y1 = y1;
action.x2 = x2;
action.y2 = y2;
action.duration_ms = duration_ms;
action.timestamp_ms = GetTimestamp();
m_actions.push_back(action);
}
void ActionRecorder::RecordLongPress(double x, double y, int duration_ms) {
if (!m_recording) return;
Action action;
action.type = ActionType::LongPress;
action.x = x;
action.y = y;
action.duration_ms = duration_ms;
action.timestamp_ms = GetTimestamp();
m_actions.push_back(action);
}
void ActionRecorder::RecordButton(const std::string& button) {
if (!m_recording) return;
Action action;
action.type = ActionType::Button;
action.button = button;
action.timestamp_ms = GetTimestamp();
m_actions.push_back(action);
}
void ActionRecorder::RecordWait(int duration_ms) {
if (!m_recording) return;
Action action;
action.type = ActionType::Wait;
action.duration_ms = duration_ms;
action.timestamp_ms = GetTimestamp();
m_actions.push_back(action);
}
void ActionRecorder::SaveToFile(const std::string& path) const {
nlohmann::json j;
j["actions"] = nlohmann::json::array();
for (const auto& action : m_actions) {
j["actions"].push_back(action.ToJson());
}
std::ofstream file(path);
if (file) {
file << j.dump(2);
std::cout << "Saved " << m_actions.size() << " actions to " << path << std::endl;
} else {
std::cerr << "Failed to save actions to " << path << std::endl;
}
}
void ActionRecorder::LoadFromFile(const std::string& path) {
std::ifstream file(path);
if (!file) {
std::cerr << "Failed to load actions from " << path << std::endl;
return;
}
nlohmann::json j;
file >> j;
m_actions.clear();
for (const auto& action_json : j["actions"]) {
m_actions.push_back(Action::FromJson(action_json));
}
std::cout << "Loaded " << m_actions.size() << " actions from " << path << std::endl;
}
void ActionRecorder::Clear() {
m_actions.clear();
}
int64_t ActionRecorder::GetTimestamp() const {
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_start_time);
return duration.count();
}
} // namespace mosis::testing

View File

@@ -0,0 +1,69 @@
// Action recorder for UI testing automation
#pragma once
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include <chrono>
namespace mosis::testing {
// Action types for recording user interactions
enum class ActionType {
Tap,
Swipe,
LongPress,
Button,
Wait
};
// Represents a single recorded action
struct Action {
ActionType type;
double x = 0, y = 0; // For tap, long_press
double x1 = 0, y1 = 0; // For swipe start
double x2 = 0, y2 = 0; // For swipe end
int duration_ms = 0; // For swipe, long_press, wait
std::string button; // For button actions (back, home, recents)
int64_t timestamp_ms = 0; // Time offset from recording start
nlohmann::json ToJson() const;
static Action FromJson(const nlohmann::json& j);
};
// Records user interactions into a sequence of actions
class ActionRecorder {
public:
ActionRecorder() = default;
// Start/stop recording
void StartRecording();
void StopRecording();
bool IsRecording() const { return m_recording; }
// Record individual actions
void RecordTap(double x, double y);
void RecordSwipe(double x1, double y1, double x2, double y2, int duration_ms);
void RecordLongPress(double x, double y, int duration_ms);
void RecordButton(const std::string& button);
void RecordWait(int duration_ms);
// Get recorded actions
const std::vector<Action>& GetActions() const { return m_actions; }
// Save/load to JSON
void SaveToFile(const std::string& path) const;
void LoadFromFile(const std::string& path);
// Clear recorded actions
void Clear();
private:
int64_t GetTimestamp() const;
bool m_recording = false;
std::vector<Action> m_actions;
std::chrono::steady_clock::time_point m_start_time;
};
} // namespace mosis::testing

View File

@@ -0,0 +1,183 @@
// UI Inspector implementation
#include "ui_inspector.h"
#include <RmlUi/Core.h>
#include <fstream>
#include <iostream>
#include <filesystem>
namespace mosis::testing {
nlohmann::json UIInspector::DumpDocument(Rml::ElementDocument* document) const {
nlohmann::json j;
if (!document) {
return j;
}
// Add metadata
j["timestamp"] = std::time(nullptr);
j["screen"] = document->GetSourceURL();
j["resolution"] = {
{"width", document->GetContext()->GetDimensions().x},
{"height", document->GetContext()->GetDimensions().y}
};
// Dump element tree
j["elements"] = DumpElement(document);
return j;
}
nlohmann::json UIInspector::DumpElement(Rml::Element* element) const {
nlohmann::json j;
if (!element) {
return j;
}
j["tag"] = element->GetTagName();
j["id"] = element->GetId();
// Get classes
nlohmann::json classes = nlohmann::json::array();
Rml::String class_attr = element->GetAttribute<Rml::String>("class", "");
if (!class_attr.empty()) {
// Split by space
size_t start = 0;
size_t end;
while ((end = class_attr.find(' ', start)) != std::string::npos) {
if (end > start) {
classes.push_back(class_attr.substr(start, end - start));
}
start = end + 1;
}
if (start < class_attr.size()) {
classes.push_back(class_attr.substr(start));
}
}
j["classes"] = classes;
// Get bounds
auto bounds = GetBounds(element);
j["bounds"] = bounds.ToJson();
// Visibility
j["visible"] = IsVisible(element);
// Children
nlohmann::json children = nlohmann::json::array();
for (int i = 0; i < element->GetNumChildren(); ++i) {
Rml::Element* child = element->GetChild(i);
if (child && child->GetTagName() != "#text") {
children.push_back(DumpElement(child));
}
}
// Text content (only for leaf elements without children to avoid huge JSON)
if (children.empty()) {
std::string text = GetText(element);
// Only store short text to avoid huge JSON and escaping issues
if (!text.empty() && text.length() < 200) {
j["text"] = text;
} else {
j["text"] = nlohmann::json(); // null
}
} else {
j["text"] = nlohmann::json(); // null for non-leaf elements
j["children"] = children;
}
return j;
}
Rml::Element* UIInspector::FindById(Rml::ElementDocument* document, const std::string& id) const {
if (!document) return nullptr;
return document->GetElementById(id);
}
std::vector<Rml::Element*> UIInspector::FindByClass(Rml::ElementDocument* document, const std::string& class_name) const {
std::vector<Rml::Element*> results;
if (!document) return results;
Rml::ElementList elements;
document->GetElementsByClassName(elements, class_name);
for (auto* element : elements) {
results.push_back(element);
}
return results;
}
bool UIInspector::IsVisible(Rml::Element* element) const {
if (!element) return false;
// Check if element has zero size
auto box = element->GetBox();
if (box.GetSize().x <= 0 || box.GetSize().y <= 0) {
return false;
}
// Check display property
auto display = element->GetProperty<Rml::Style::Display>("display");
if (display == Rml::Style::Display::None) {
return false;
}
// Check visibility property
auto visibility = element->GetProperty<Rml::Style::Visibility>("visibility");
if (visibility == Rml::Style::Visibility::Hidden) {
return false;
}
return true;
}
ElementBounds UIInspector::GetBounds(Rml::Element* element) const {
ElementBounds bounds = {0, 0, 0, 0};
if (!element) return bounds;
auto abs_offset = element->GetAbsoluteOffset(Rml::BoxArea::Border);
auto box = element->GetBox();
bounds.x = abs_offset.x;
bounds.y = abs_offset.y;
bounds.width = box.GetSize(Rml::BoxArea::Border).x;
bounds.height = box.GetSize(Rml::BoxArea::Border).y;
return bounds;
}
std::string UIInspector::GetText(Rml::Element* element) const {
if (!element) return "";
return element->GetInnerRML();
}
void UIInspector::SaveToFile(Rml::ElementDocument* document, const std::string& path, bool quiet) const {
nlohmann::json j = DumpDocument(document);
// Write to temp file first, then rename for atomic update
std::string tempPath = path + ".tmp";
std::ofstream file(tempPath);
if (file) {
file << j.dump(2);
file.close(); // Ensure file is closed and flushed
// Atomic rename (on Windows, need to remove destination first)
std::error_code ec;
std::filesystem::remove(path, ec);
std::filesystem::rename(tempPath, path, ec);
if (ec && !quiet) {
std::cerr << "Failed to rename temp file: " << ec.message() << std::endl;
} else if (!quiet) {
std::cout << "Saved UI hierarchy to " << path << std::endl;
}
} else {
std::cerr << "Failed to save UI hierarchy to " << path << std::endl;
}
}
} // namespace mosis::testing

View File

@@ -0,0 +1,53 @@
// UI Inspector for dumping element hierarchy
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace Rml {
class Element;
class ElementDocument;
}
namespace mosis::testing {
// Element bounds
struct ElementBounds {
float x, y, width, height;
nlohmann::json ToJson() const {
return {{"x", x}, {"y", y}, {"width", width}, {"height", height}};
}
};
// Inspects and dumps UI element hierarchy
class UIInspector {
public:
UIInspector() = default;
// Dump the element tree of a document to JSON
nlohmann::json DumpDocument(Rml::ElementDocument* document) const;
// Dump a single element and its children
nlohmann::json DumpElement(Rml::Element* element) const;
// Find element by ID
Rml::Element* FindById(Rml::ElementDocument* document, const std::string& id) const;
// Find elements by class
std::vector<Rml::Element*> FindByClass(Rml::ElementDocument* document, const std::string& class_name) const;
// Check if element is visible
bool IsVisible(Rml::Element* element) const;
// Get element bounds (in screen coordinates)
ElementBounds GetBounds(Rml::Element* element) const;
// Get element text content
std::string GetText(Rml::Element* element) const;
// Save hierarchy to file (quiet=true suppresses log message)
void SaveToFile(Rml::ElementDocument* document, const std::string& path, bool quiet = false) const;
};
} // namespace mosis::testing

View File

@@ -0,0 +1,244 @@
// Visual capture implementation
#include "visual_capture.h"
#include <png.h>
#include <fstream>
#include <iostream>
#include <cmath>
#include <algorithm>
// OpenGL header (from RmlUi backend)
#include <RmlUi_Include_GL3.h>
namespace mosis::testing {
ImageData VisualCapture::CaptureFramebuffer(uint32_t width, uint32_t height) const {
ImageData image;
image.width = width;
image.height = height;
image.pixels.resize(width * height * 4);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, image.pixels.data());
// Flip vertically (OpenGL origin is bottom-left)
FlipVertically(image);
return image;
}
bool VisualCapture::SavePNG(const ImageData& image, const std::string& path) const {
if (!image.IsValid()) {
std::cerr << "Invalid image data" << std::endl;
return false;
}
FILE* fp = fopen(path.c_str(), "wb");
if (!fp) {
std::cerr << "Failed to open file for writing: " << path << std::endl;
return false;
}
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png) {
fclose(fp);
return false;
}
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_write_struct(&png, nullptr);
fclose(fp);
return false;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
fclose(fp);
return false;
}
png_init_io(png, fp);
png_set_IHDR(png, info, image.width, image.height, 8,
PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png, info);
// Write rows
std::vector<png_bytep> rows(image.height);
for (uint32_t y = 0; y < image.height; ++y) {
rows[y] = const_cast<png_bytep>(image.pixels.data() + y * image.width * 4);
}
png_write_image(png, rows.data());
png_write_end(png, nullptr);
png_destroy_write_struct(&png, &info);
fclose(fp);
std::cout << "Saved screenshot to " << path << std::endl;
return true;
}
ImageData VisualCapture::LoadPNG(const std::string& path) const {
ImageData image;
FILE* fp = fopen(path.c_str(), "rb");
if (!fp) {
std::cerr << "Failed to open file for reading: " << path << std::endl;
return image;
}
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png) {
fclose(fp);
return image;
}
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_read_struct(&png, nullptr, nullptr);
fclose(fp);
return image;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, &info, nullptr);
fclose(fp);
return image;
}
png_init_io(png, fp);
png_read_info(png, info);
image.width = png_get_image_width(png, info);
image.height = png_get_image_height(png, info);
png_byte color_type = png_get_color_type(png, info);
png_byte bit_depth = png_get_bit_depth(png, info);
// Convert to RGBA
if (bit_depth == 16) {
png_set_strip_16(png);
}
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png);
}
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
png_set_expand_gray_1_2_4_to_8(png);
}
if (png_get_valid(png, info, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(png);
}
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
}
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(png);
}
png_read_update_info(png, info);
// Read rows
image.pixels.resize(image.width * image.height * 4);
std::vector<png_bytep> rows(image.height);
for (uint32_t y = 0; y < image.height; ++y) {
rows[y] = image.pixels.data() + y * image.width * 4;
}
png_read_image(png, rows.data());
png_destroy_read_struct(&png, &info, nullptr);
fclose(fp);
return image;
}
CompareResult VisualCapture::Compare(const ImageData& actual, const ImageData& expected, double threshold) const {
CompareResult result;
if (!actual.IsValid() || !expected.IsValid()) {
return result;
}
if (actual.width != expected.width || actual.height != expected.height) {
std::cerr << "Image dimensions don't match" << std::endl;
return result;
}
uint32_t total_pixels = actual.width * actual.height;
uint32_t diff_pixels = 0;
for (uint32_t i = 0; i < actual.pixels.size(); i += 4) {
int dr = std::abs(static_cast<int>(actual.pixels[i]) - static_cast<int>(expected.pixels[i]));
int dg = std::abs(static_cast<int>(actual.pixels[i+1]) - static_cast<int>(expected.pixels[i+1]));
int db = std::abs(static_cast<int>(actual.pixels[i+2]) - static_cast<int>(expected.pixels[i+2]));
int da = std::abs(static_cast<int>(actual.pixels[i+3]) - static_cast<int>(expected.pixels[i+3]));
// If any channel differs significantly, count as different
if (dr > 2 || dg > 2 || db > 2 || da > 2) {
++diff_pixels;
}
}
result.diff_pixels = diff_pixels;
result.diff_percent = static_cast<double>(diff_pixels) / total_pixels;
result.match = result.diff_percent <= threshold;
if (!result.match) {
result.diff_image = GenerateDiff(actual, expected);
}
return result;
}
ImageData VisualCapture::GenerateDiff(const ImageData& actual, const ImageData& expected) const {
ImageData diff;
if (!actual.IsValid() || !expected.IsValid()) {
return diff;
}
diff.width = actual.width;
diff.height = actual.height;
diff.pixels.resize(actual.pixels.size());
for (uint32_t i = 0; i < actual.pixels.size(); i += 4) {
int dr = std::abs(static_cast<int>(actual.pixels[i]) - static_cast<int>(expected.pixels[i]));
int dg = std::abs(static_cast<int>(actual.pixels[i+1]) - static_cast<int>(expected.pixels[i+1]));
int db = std::abs(static_cast<int>(actual.pixels[i+2]) - static_cast<int>(expected.pixels[i+2]));
if (dr > 2 || dg > 2 || db > 2) {
// Highlight difference in red
diff.pixels[i] = 255;
diff.pixels[i+1] = 0;
diff.pixels[i+2] = 0;
diff.pixels[i+3] = 255;
} else {
// Dim the matching pixels
diff.pixels[i] = actual.pixels[i] / 3;
diff.pixels[i+1] = actual.pixels[i+1] / 3;
diff.pixels[i+2] = actual.pixels[i+2] / 3;
diff.pixels[i+3] = 255;
}
}
return diff;
}
void VisualCapture::FlipVertically(ImageData& image) const {
if (!image.IsValid()) return;
size_t row_size = image.width * 4;
std::vector<uint8_t> temp_row(row_size);
for (uint32_t y = 0; y < image.height / 2; ++y) {
uint8_t* top = image.pixels.data() + y * row_size;
uint8_t* bottom = image.pixels.data() + (image.height - 1 - y) * row_size;
memcpy(temp_row.data(), top, row_size);
memcpy(top, bottom, row_size);
memcpy(bottom, temp_row.data(), row_size);
}
}
} // namespace mosis::testing

View File

@@ -0,0 +1,51 @@
// Visual capture for screenshots and image comparison
#pragma once
#include <string>
#include <vector>
#include <cstdint>
namespace mosis::testing {
// PNG image data
struct ImageData {
uint32_t width = 0;
uint32_t height = 0;
std::vector<uint8_t> pixels; // RGBA format
bool IsValid() const { return width > 0 && height > 0 && !pixels.empty(); }
};
// Result of image comparison
struct CompareResult {
bool match = false;
double diff_percent = 0.0;
uint32_t diff_pixels = 0;
ImageData diff_image;
};
// Captures and compares screenshots
class VisualCapture {
public:
VisualCapture() = default;
// Capture current framebuffer to image
ImageData CaptureFramebuffer(uint32_t width, uint32_t height) const;
// Save image to PNG file
bool SavePNG(const ImageData& image, const std::string& path) const;
// Load PNG file to image
ImageData LoadPNG(const std::string& path) const;
// Compare two images
CompareResult Compare(const ImageData& actual, const ImageData& expected, double threshold = 0.01) const;
// Generate diff image (highlights differences)
ImageData GenerateDiff(const ImageData& actual, const ImageData& expected) const;
// Utility: flip image vertically (for OpenGL coordinate conversion)
void FlipVertically(ImageData& image) const;
};
} // namespace mosis::testing

144
designer/test/README.md Normal file
View File

@@ -0,0 +1,144 @@
# Mosis Designer UI Testing with AutoHotkey
## Overview
This folder contains AutoHotkey v2 scripts for automated UI testing of the Mosis Designer.
## Testing Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Test Runner (run_tests.ahk) │
│ - Launches designer process │
│ - Captures stdout for verification │
│ - Runs test scripts sequentially │
│ - Generates test report │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Test Scripts (test_*.ahk) │
│ - test_navigation.ahk: Click apps, verify screen change │
│ - test_back_button.ahk: Navigate and go back │
│ - test_home_button.ahk: Navigate deep, press home │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Utilities (lib/utils.ahk) │
│ - FindDesignerWindow(): Get window handle │
│ - ClickPhone(x, y): Click at phone coordinates │
│ - WaitForScreen(name): Wait for navigation log │
│ - CaptureScreenshot(path): Save window image │
└─────────────────────────────────────────────────────────────┘
```
## Window Identification
- **Window Title**: "Mosis Designer"
- **Window Class**: GLFW window class (varies by version)
- **Size**: 540x960 client area (phone resolution)
## Coordinate System
The phone UI uses a 540x960 coordinate system. AHK needs to:
1. Find the designer window
2. Get the client area position
3. Convert phone coordinates to screen coordinates
```
Phone Coordinates (0,0 to 540,960)
┌──────────────────┐
│ Window Title │ ← Window decorations
├──────────────────┤
│ │
│ Phone UI │ ← Client area (540x960)
│ (0,0) │
│ ┌──────┐ │
│ │ App │ │ ← Click target
│ │ Icon │ │
│ └──────┘ │
│ │
│ (540,960)
└──────────────────┘
```
## App Icon Positions (Home Screen)
Based on the home.rml layout with 4 columns:
| Row | App | Approx X | Approx Y |
|-----|-----------|----------|----------|
| 1 | Phone | 67 | 100 |
| 1 | Messages | 202 | 100 |
| 1 | Contacts | 337 | 100 |
| 1 | Browser | 472 | 100 |
| 2 | Gallery | 67 | 200 |
| 2 | Camera | 202 | 200 |
| 2 | Settings | 337 | 200 |
| 2 | Music | 472 | 200 |
Dock items at bottom (~900 Y):
- Phone: 67
- Messages: 202
- Contacts: 337
- Browser: 472
## Verification Methods
### 1. Console Output Parsing
The designer outputs navigation events to stdout:
```
navigateTo called with: dialer
Loaded screen: apps/dialer/dialer.rml
Navigated to: dialer (history depth: 1)
goBack called (history depth: 1)
Back to: home
```
We can redirect stdout to a file and parse it for verification.
### 2. Screenshot Comparison
Take screenshots at key points and compare with baselines.
### 3. Window Title Changes (future)
Could modify designer to include current screen in title.
## Test Execution Flow
```
1. Start designer with stdout redirected to log file
2. Wait for window to appear
3. Wait for "Document loaded successfully" in log
4. Execute test actions (clicks, waits)
5. Parse log for expected navigation events
6. Report pass/fail
7. Close designer
```
## Files
- `lib/utils.ahk` - Shared utility functions
- `run_tests.ahk` - Main test runner
- `test_navigation.ahk` - Navigation test suite
- `test_back_button.ahk` - Back button test suite
- `config.ahk` - Test configuration (paths, timeouts)
## Usage
```powershell
# Run all tests
& "C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe" run_tests.ahk
# Run specific test
& "C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe" test_navigation.ahk
```
## Requirements
- AutoHotkey v2 (installed at C:\Program Files\AutoHotkey\v2)
- Mosis Designer built (designer\build\Debug\mosis-designer.exe)
- Test assets in place (src\main\assets\)

View File

@@ -0,0 +1,102 @@
; Click test for Mosis Designer
#Requires AutoHotkey v2.0
; Configuration
WINDOW_TITLE := "Mosis Designer"
PHONE_WIDTH := 540
PHONE_HEIGHT := 960
; Output file
outFile := A_ScriptDir . "\click_test_result.txt"
if (FileExist(outFile))
FileDelete(outFile)
Log(msg) {
global outFile
FileAppend(msg . "`n", outFile, "UTF-8")
}
Log("=== Click Test ===")
Log("Time: " . FormatTime(, "yyyy-MM-dd HH:mm:ss"))
; Find window
hwnd := WinExist(WINDOW_TITLE)
if (!hwnd) {
Log("ERROR: Window not found")
ExitApp(1)
}
Log("Window found: " . hwnd)
; Get window info
WinGetPos(&winX, &winY, &winW, &winH, hwnd)
Log("Window position: X=" . winX . " Y=" . winY)
Log("Window size: W=" . winW . " H=" . winH)
; Calculate DPI scale factor
; Expected phone size is 540x960
; Window has some decorations, estimate ~16 pixels width, ~40 height for title+borders
expectedW := 540 + 16
expectedH := 960 + 40
scaleX := winW / expectedW
scaleY := winH / expectedH
Log("Estimated scale X: " . scaleX . " Y: " . scaleY)
; Use average scale (should be similar for both)
scale := (scaleX + scaleY) / 2
Log("Using scale factor: " . scale)
; Calculate client area offset (estimate title bar and borders scaled)
titleBarHeight := Round(31 * scale)
borderWidth := Round(8 * scale)
Log("Estimated title bar: " . titleBarHeight . "px, border: " . borderWidth . "px")
; Client area start
clientX := winX + borderWidth
clientY := winY + titleBarHeight
Log("Client area start: X=" . clientX . " Y=" . clientY)
; Function to convert phone coords to screen coords
PhoneToScreen(phoneX, phoneY) {
global clientX, clientY, scale
return {
x: Round(clientX + phoneX * scale),
y: Round(clientY + phoneY * scale)
}
}
; Activate window
WinActivate(hwnd)
Sleep(200)
Log("")
Log("=== Performing Click Test ===")
; Click on Phone app icon (first app in grid)
; Position from home.rml layout: approximately (67, 120) in phone coords
phonePos := PhoneToScreen(67, 120)
Log("Phone app: phone(67,120) -> screen(" . phonePos.x . "," . phonePos.y . ")")
; Perform click
Click(phonePos.x, phonePos.y)
Log("Click sent!")
Sleep(1000)
Log("Test complete - check if screen changed to Dialer")
; Also try clicking messages in dock for comparison
Log("")
Log("=== Second Click Test (Messages Dock) ===")
; Dock is at bottom, approximately y=920 in phone coords
; Messages is second item, approximately x=202
msgPos := PhoneToScreen(202, 920)
Log("Messages dock: phone(202,920) -> screen(" . msgPos.x . "," . msgPos.y . ")")
Click(msgPos.x, msgPos.y)
Log("Click sent!")
Sleep(500)
Log("")
Log("=== Test Complete ===")
ExitApp(0)

45
designer/test/config.ahk Normal file
View File

@@ -0,0 +1,45 @@
; Mosis Designer Test Configuration
; AutoHotkey v2
; Paths
global DESIGNER_EXE := A_ScriptDir . "\..\build\Debug\mosis-designer.exe"
global ASSETS_PATH := A_ScriptDir . "\..\..\src\main\assets"
global HOME_RML := ASSETS_PATH . "\apps\home\home.rml"
global LOG_FILE := A_ScriptDir . "\test_output.log"
global SCREENSHOT_DIR := A_ScriptDir . "\screenshots"
; Window settings
global WINDOW_TITLE := "Mosis Designer"
global PHONE_WIDTH := 540
global PHONE_HEIGHT := 960
; Timeouts (milliseconds)
global STARTUP_TIMEOUT := 10000 ; Wait for window to appear
global NAVIGATION_TIMEOUT := 2000 ; Wait for navigation to complete
global CLICK_DELAY := 100 ; Delay after click
; App icon positions on home screen (approximate center of each icon)
; Grid is 4 columns, icons are ~135px apart horizontally
; First row starts around Y=100
global APP_POSITIONS := Map(
"phone", {x: 67, y: 120},
"messages", {x: 202, y: 120},
"contacts", {x: 337, y: 120},
"browser", {x: 472, y: 120},
"gallery", {x: 67, y: 220},
"camera", {x: 202, y: 220},
"settings", {x: 337, y: 220},
"music", {x: 472, y: 220},
"calendar", {x: 67, y: 320},
"clock", {x: 202, y: 320},
"notes", {x: 337, y: 320},
"maps", {x: 472, y: 320}
)
; Dock positions (bottom of screen)
global DOCK_POSITIONS := Map(
"phone", {x: 67, y: 920},
"messages", {x: 202, y: 920},
"contacts", {x: 337, y: 920},
"browser", {x: 472, y: 920}
)

124
designer/test/diagnose.ahk Normal file
View File

@@ -0,0 +1,124 @@
; Mosis Designer Diagnostic Script
; AutoHotkey v2
; Outputs window information without user interaction
#Requires AutoHotkey v2.0
; Simple config (inline to avoid include issues)
WINDOW_TITLE := "Mosis Designer"
PHONE_WIDTH := 540
PHONE_HEIGHT := 960
; Output file
outputFile := A_ScriptDir . "\diagnose_output.txt"
; Clear previous output
if (FileExist(outputFile))
FileDelete(outputFile)
; Helper to write output
WriteOutput(msg) {
FileAppend(msg . "`n", outputFile)
}
WriteOutput("=== Mosis Designer Diagnostic ===")
WriteOutput("Time: " . A_Now)
WriteOutput("")
; Find window
hwnd := WinExist(WINDOW_TITLE)
if (!hwnd) {
WriteOutput("ERROR: Window '" . WINDOW_TITLE . "' not found")
WriteOutput("")
WriteOutput("Looking for any GLFW windows...")
; Try to find any GLFW window
windows := WinGetList()
for wnd in windows {
title := WinGetTitle(wnd)
class := WinGetClass(wnd)
if (InStr(class, "GLFW") || InStr(title, "Mosis") || InStr(title, "Designer")) {
WriteOutput("Found candidate: " . wnd . " - Title: " . title . " - Class: " . class)
}
}
ExitApp()
}
WriteOutput("Window found!")
WriteOutput("HWND: " . hwnd)
; Get window info
title := WinGetTitle(hwnd)
class := WinGetClass(hwnd)
WinGetPos(&winX, &winY, &winW, &winH, hwnd)
WriteOutput("Title: " . title)
WriteOutput("Class: " . class)
WriteOutput("Position: X=" . winX . " Y=" . winY)
WriteOutput("Size: W=" . winW . " H=" . winH)
; Estimate client area
; For GLFW windows, borders are typically small
; Let's try different estimates
WriteOutput("")
WriteOutput("=== Client Area Estimates ===")
; Method 1: Standard Windows decorations
borderW := 8
titleH := 31
clientX1 := winX + borderW
clientY1 := winY + titleH
WriteOutput("Estimate 1 (standard border): X=" . clientX1 . " Y=" . clientY1)
; Method 2: Minimal border (GLFW often uses this)
borderW2 := 1
titleH2 := 1
clientX2 := winX + borderW2
clientY2 := winY + titleH2
WriteOutput("Estimate 2 (minimal border): X=" . clientX2 . " Y=" . clientY2)
; Method 3: No border (borderless window)
WriteOutput("Estimate 3 (no border): X=" . winX . " Y=" . winY)
; Calculate expected phone area size vs actual window
WriteOutput("")
WriteOutput("=== Size Analysis ===")
WriteOutput("Expected phone size: " . PHONE_WIDTH . "x" . PHONE_HEIGHT)
WriteOutput("Actual window size: " . winW . "x" . winH)
expectedW := PHONE_WIDTH + (2 * 8) ; borders
expectedH := PHONE_HEIGHT + 31 + 8 ; title + border
WriteOutput("Expected window size (with decorations): " . expectedW . "x" . expectedH)
; Check if window is maximized or has different size
if (winW > PHONE_WIDTH + 50 || winH > PHONE_HEIGHT + 100) {
WriteOutput("WARNING: Window seems larger than expected - may be scaled or maximized")
}
; Try a click test (won't do anything without activation, but log coordinates)
WriteOutput("")
WriteOutput("=== Click Coordinate Test ===")
; Phone center
phoneX := 270
phoneY := 480
; Convert to screen (using estimate 1)
screenX := clientX1 + phoneX
screenY := clientY1 + phoneY
WriteOutput("Phone center (270, 480) -> Screen (" . screenX . ", " . screenY . ")")
; Phone app icon position
phoneAppX := 67
phoneAppY := 120
screenAppX := clientX1 + phoneAppX
screenAppY := clientY1 + phoneAppY
WriteOutput("Phone app icon (67, 120) -> Screen (" . screenAppX . ", " . screenAppY . ")")
WriteOutput("")
WriteOutput("=== Diagnostic Complete ===")
WriteOutput("Output written to: " . outputFile)
ExitApp()

View File

@@ -0,0 +1,149 @@
; Full click test with output verification
#Requires AutoHotkey v2.0
; Configuration
DESIGNER_EXE := A_ScriptDir . "\..\build\Debug\mosis-designer.exe"
HOME_RML := A_ScriptDir . "\..\..\src\main\assets\apps\home\home.rml"
WINDOW_TITLE := "Mosis Designer"
PHONE_WIDTH := 540
PHONE_HEIGHT := 960
; Output files
logFile := A_ScriptDir . "\designer_output.log"
resultFile := A_ScriptDir . "\full_test_result.txt"
if (FileExist(logFile))
FileDelete(logFile)
if (FileExist(resultFile))
FileDelete(resultFile)
Log(msg) {
global resultFile
FileAppend(msg . "`n", resultFile, "UTF-8")
}
Log("=== Full Click Test with Output Capture ===")
Log("Time: " . FormatTime(, "yyyy-MM-dd HH:mm:ss"))
Log("")
; Start designer with output redirection
Log("Starting designer...")
cmd := '"' . DESIGNER_EXE . '" "' . HOME_RML . '" > "' . logFile . '" 2>&1'
Log("Command: " . cmd)
; Run via cmd to handle redirection
Run(A_ComSpec . " /c " . cmd, A_ScriptDir, , &pid)
Log("Started with PID: " . pid)
; Wait for window
Log("Waiting for window...")
startTime := A_TickCount
hwnd := 0
while (A_TickCount - startTime < 15000) {
hwnd := WinExist(WINDOW_TITLE)
if (hwnd)
break
Sleep(200)
}
if (!hwnd) {
Log("ERROR: Window did not appear within 15 seconds")
ExitApp(1)
}
Log("Window appeared: " . hwnd)
; Wait for document to load
Sleep(2000)
; Get window dimensions and calculate scale
WinGetPos(&winX, &winY, &winW, &winH, hwnd)
Log("Window: X=" . winX . " Y=" . winY . " W=" . winW . " H=" . winH)
; Calculate scale
expectedW := 540 + 16
expectedH := 960 + 40
scale := (winW / expectedW + winH / expectedH) / 2
Log("DPI Scale: " . scale)
; Calculate client offset
titleBar := Round(31 * scale)
border := Round(8 * scale)
clientX := winX + border
clientY := winY + titleBar
Log("Client: X=" . clientX . " Y=" . clientY)
; Activate window
WinActivate(hwnd)
Sleep(300)
; Function to click at phone coordinates
ClickAt(phoneX, phoneY, desc) {
global clientX, clientY, scale, hwnd
screenX := Round(clientX + phoneX * scale)
screenY := Round(clientY + phoneY * scale)
Log("Click " . desc . ": phone(" . phoneX . "," . phoneY . ") -> screen(" . screenX . "," . screenY . ")")
; Activate and click
WinActivate(hwnd)
Sleep(50)
Click(screenX, screenY)
Sleep(500)
}
Log("")
Log("=== Test 1: Click Phone App ===")
ClickAt(67, 120, "Phone app")
Sleep(1500)
; Check log for navigation
Log("Checking log for navigation...")
if (FileExist(logFile)) {
content := FileRead(logFile)
if (InStr(content, "Navigated to: dialer") || InStr(content, "Loaded screen: apps/dialer")) {
Log("SUCCESS: Navigation to dialer detected!")
} else if (InStr(content, "navigateTo called")) {
Log("PARTIAL: navigateTo was called but navigation may have failed")
} else {
Log("FAIL: No navigation detected")
}
} else {
Log("WARNING: Log file not found")
}
Log("")
Log("=== Test 2: Click Messages Dock ===")
ClickAt(202, 920, "Messages dock")
Sleep(1500)
if (FileExist(logFile)) {
content := FileRead(logFile)
if (InStr(content, "messages")) {
Log("SUCCESS: Messages-related activity detected")
} else {
Log("FAIL: No messages navigation detected")
}
}
Log("")
Log("=== Cleaning Up ===")
; Close designer
WinClose(hwnd)
Sleep(500)
; Dump the full log
Log("")
Log("=== Designer Log Contents ===")
if (FileExist(logFile)) {
content := FileRead(logFile)
; Only show last part of log (navigation related)
lines := StrSplit(content, "`n")
Log("(Last 30 lines)")
startIdx := lines.Length > 30 ? lines.Length - 30 : 1
loop lines.Length - startIdx + 1 {
Log(lines[startIdx + A_Index - 1])
}
}
Log("")
Log("=== Test Complete ===")
ExitApp(0)

298
designer/test/lib/utils.ahk Normal file
View File

@@ -0,0 +1,298 @@
; Mosis Designer Test Utilities
; AutoHotkey v2
#Include "..\config.ahk"
; Find the designer window and return its handle
FindDesignerWindow() {
return WinExist(WINDOW_TITLE)
}
; Wait for the designer window to appear
; Returns window handle or 0 if timeout
WaitForDesignerWindow(timeout := 0) {
if (timeout = 0)
timeout := STARTUP_TIMEOUT
startTime := A_TickCount
while (A_TickCount - startTime < timeout) {
hwnd := FindDesignerWindow()
if (hwnd) {
; Give window time to fully initialize
Sleep(500)
return hwnd
}
Sleep(100)
}
return 0
}
; Get the client area position of the designer window
; Returns {x, y, w, h} or 0 if window not found
GetClientArea(hwnd := 0) {
if (hwnd = 0)
hwnd := FindDesignerWindow()
if (!hwnd)
return 0
; Get window position
WinGetPos(&winX, &winY, &winW, &winH, hwnd)
; Get client area - for GLFW windows, typically minimal border
; Client area starts after title bar
; We'll estimate based on typical Windows decorations
; Title bar is usually ~30-40 pixels, borders ~8 pixels each side
; For a more accurate approach, we could use DllCall to GetClientRect
; But for GLFW windows, the client area often matches the requested size
; Calculate client area assuming standard decorations
borderWidth := 8
titleHeight := 31
return {
x: winX + borderWidth,
y: winY + titleHeight,
w: PHONE_WIDTH,
h: PHONE_HEIGHT
}
}
; Convert phone coordinates to screen coordinates
PhoneToScreen(phoneX, phoneY, hwnd := 0) {
client := GetClientArea(hwnd)
if (!client)
return 0
return {
x: client.x + phoneX,
y: client.y + phoneY
}
}
; Click at phone coordinates
ClickPhone(phoneX, phoneY, hwnd := 0) {
if (hwnd = 0)
hwnd := FindDesignerWindow()
if (!hwnd) {
LogMessage("ERROR: Designer window not found")
return false
}
; Activate window first
WinActivate(hwnd)
Sleep(50)
screen := PhoneToScreen(phoneX, phoneY, hwnd)
if (!screen) {
LogMessage("ERROR: Could not convert coordinates")
return false
}
; Move and click
Click(screen.x, screen.y)
Sleep(CLICK_DELAY)
LogMessage("Clicked at phone(" . phoneX . "," . phoneY . ") -> screen(" . screen.x . "," . screen.y . ")")
return true
}
; Click on a named app icon
ClickApp(appName, hwnd := 0) {
if (!APP_POSITIONS.Has(appName)) {
LogMessage("ERROR: Unknown app: " . appName)
return false
}
pos := APP_POSITIONS[appName]
LogMessage("Clicking app: " . appName)
return ClickPhone(pos.x, pos.y, hwnd)
}
; Click on a dock item
ClickDock(appName, hwnd := 0) {
if (!DOCK_POSITIONS.Has(appName)) {
LogMessage("ERROR: Unknown dock item: " . appName)
return false
}
pos := DOCK_POSITIONS[appName]
LogMessage("Clicking dock: " . appName)
return ClickPhone(pos.x, pos.y, hwnd)
}
; Send keyboard input to the designer
SendToDesigner(keys, hwnd := 0) {
if (hwnd = 0)
hwnd := FindDesignerWindow()
if (!hwnd)
return false
WinActivate(hwnd)
Sleep(50)
Send(keys)
return true
}
; Read the log file and check for a pattern
; Returns the matching line or empty string
CheckLogFor(pattern, logFile := 0) {
if (logFile = 0)
logFile := LOG_FILE
if (!FileExist(logFile))
return ""
content := FileRead(logFile)
lines := StrSplit(content, "`n")
; Search from end (most recent)
loop lines.Length {
idx := lines.Length - A_Index + 1
line := lines[idx]
if (InStr(line, pattern))
return line
}
return ""
}
; Wait for a specific pattern to appear in the log
WaitForLog(pattern, timeout := 0, logFile := 0) {
if (timeout = 0)
timeout := NAVIGATION_TIMEOUT
if (logFile = 0)
logFile := LOG_FILE
startTime := A_TickCount
while (A_TickCount - startTime < timeout) {
result := CheckLogFor(pattern, logFile)
if (result)
return result
Sleep(100)
}
return ""
}
; Wait for navigation to a specific screen
WaitForNavigation(screenName, timeout := 0) {
pattern := "Navigated to: " . screenName
return WaitForLog(pattern, timeout)
}
; Wait for back navigation
WaitForBack(timeout := 0) {
return WaitForLog("Back to:", timeout)
}
; Take a screenshot of the designer window
CaptureScreenshot(filename, hwnd := 0) {
if (hwnd = 0)
hwnd := FindDesignerWindow()
if (!hwnd)
return false
; Ensure screenshot directory exists
if (!DirExist(SCREENSHOT_DIR))
DirCreate(SCREENSHOT_DIR)
fullPath := SCREENSHOT_DIR . "\" . filename
; Use built-in screenshot capability
; Note: This captures the entire window including decorations
try {
; Activate and bring to front
WinActivate(hwnd)
Sleep(100)
; Get window position
WinGetPos(&x, &y, &w, &h, hwnd)
; Use GDI+ or Windows API for screenshot
; For simplicity, we'll use the Snipping approach
; In production, you'd use a proper GDI+ screenshot
LogMessage("Screenshot requested: " . fullPath . " (not implemented)")
return false
} catch as e {
LogMessage("Screenshot error: " . e.Message)
return false
}
}
; Log a message with timestamp
LogMessage(msg) {
timestamp := FormatTime(, "yyyy-MM-dd HH:mm:ss")
line := "[" . timestamp . "] " . msg . "`n"
; Write to console
OutputDebug(line)
; Also append to a test log file
testLog := A_ScriptDir . "\test_run.log"
FileAppend(line, testLog)
}
; Clear test logs
ClearLogs() {
testLog := A_ScriptDir . "\test_run.log"
if (FileExist(testLog))
FileDelete(testLog)
if (FileExist(LOG_FILE))
FileDelete(LOG_FILE)
}
; Close the designer window
CloseDesigner(hwnd := 0) {
if (hwnd = 0)
hwnd := FindDesignerWindow()
if (hwnd) {
WinClose(hwnd)
Sleep(500)
}
}
; Kill any running designer processes
KillDesigner() {
try {
Run('taskkill /F /IM mosis-designer.exe', , "Hide")
}
Sleep(500)
}
; Start the designer process with log redirection
; Returns the process ID or 0 on failure
StartDesigner(rmlFile := 0) {
if (rmlFile = 0)
rmlFile := HOME_RML
; Build command with output redirection
cmd := '"' . DESIGNER_EXE . '" "' . rmlFile . '"'
LogMessage("Starting designer: " . cmd)
; Start process and capture output
; We'll redirect to a log file
fullCmd := A_ComSpec . ' /c "' . cmd . '" > "' . LOG_FILE . '" 2>&1'
Run(fullCmd, A_ScriptDir, "Hide", &pid)
if (pid) {
LogMessage("Designer started with PID: " . pid)
return pid
}
LogMessage("ERROR: Failed to start designer")
return 0
}
; Assert helper - logs pass/fail and returns result
Assert(condition, testName) {
if (condition) {
LogMessage("PASS: " . testName)
return true
} else {
LogMessage("FAIL: " . testName)
return false
}
}

View File

@@ -0,0 +1,65 @@
; Mosis Designer Test Runner
; AutoHotkey v2
; Runs all test scripts and generates a report
#Requires AutoHotkey v2.0
#Include "lib\utils.ahk"
; Test scripts to run
global testScripts := [
"test_navigation.ahk"
]
; Run all tests
RunAllTests() {
LogMessage("========================================")
LogMessage("MOSIS DESIGNER TEST SUITE")
LogMessage("========================================")
totalPassed := 0
totalFailed := 0
; Clean up
KillDesigner()
ClearLogs()
for script in testScripts {
LogMessage("")
LogMessage("Running: " . script)
LogMessage("----------------------------------------")
scriptPath := A_ScriptDir . "\" . script
if (!FileExist(scriptPath)) {
LogMessage("ERROR: Script not found: " . scriptPath)
totalFailed++
continue
}
; Run the test script
try {
ahkPath := "C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe"
RunWait('"' . ahkPath . '" "' . scriptPath . '"', A_ScriptDir)
} catch as e {
LogMessage("ERROR running script: " . e.Message)
totalFailed++
}
; Brief pause between tests
Sleep(1000)
}
; Final cleanup
KillDesigner()
; Generate report
LogMessage("")
LogMessage("========================================")
LogMessage("TEST SUITE COMPLETE")
LogMessage("Check test_run.log for detailed results")
LogMessage("========================================")
}
; Main
RunAllTests()
ExitApp()

View File

@@ -0,0 +1,21 @@
; Simple window finder test
#Requires AutoHotkey v2.0
; Write to stdout (will show in console)
OutputDebug("Starting simple test`n")
; Try to find the window
hwnd := WinExist("Mosis Designer")
if (hwnd) {
WinGetPos(&x, &y, &w, &h, hwnd)
msg := "Found! X:" . x . " Y:" . y . " W:" . w . " H:" . h
} else {
msg := "Window not found"
}
; Write result to a file
outFile := A_ScriptDir . "\simple_result.txt"
FileAppend(msg . "`n", outFile, "UTF-8")
ExitApp()

View File

@@ -0,0 +1,110 @@
; Mosis Designer Interactive Test Helper
; AutoHotkey v2
; Launches designer and shows coordinates as you click
#Requires AutoHotkey v2.0
#Include "lib\utils.ahk"
; Global state
global isRunning := true
global hwnd := 0
; Create a simple GUI to show status
CreateStatusGui() {
global statusGui, statusText, coordText
statusGui := Gui("+AlwaysOnTop", "Mosis Test Helper")
statusGui.SetFont("s10")
statusGui.Add("Text", "w300", "Designer Status:")
statusText := statusGui.Add("Text", "w300 h20", "Not running")
statusGui.Add("Text", "w300", "Mouse Position (Phone Coords):")
coordText := statusGui.Add("Text", "w300 h20", "N/A")
statusGui.Add("Text", "w300", "")
statusGui.Add("Button", "w100", "Refresh").OnEvent("Click", RefreshStatus)
statusGui.Add("Button", "x+10 w100", "Click Test").OnEvent("Click", DoClickTest)
statusGui.Add("Button", "x+10 w80", "Exit").OnEvent("Click", (*) => ExitApp())
statusGui.OnEvent("Close", (*) => ExitApp())
statusGui.Show("x10 y10")
}
; Refresh status display
RefreshStatus(*) {
global hwnd, statusText
hwnd := FindDesignerWindow()
if (hwnd) {
statusText.Value := "Running (hwnd: " . hwnd . ")"
} else {
statusText.Value := "Not running"
}
}
; Perform a test click at center of phone
DoClickTest(*) {
global hwnd
hwnd := FindDesignerWindow()
if (!hwnd) {
MsgBox("Designer window not found!")
return
}
; Click center of phone
ClickPhone(270, 480, hwnd)
MsgBox("Clicked at phone center (270, 480)")
}
; Update coordinate display based on mouse position
UpdateCoordinates() {
global hwnd, coordText
if (!hwnd) {
hwnd := FindDesignerWindow()
if (!hwnd) {
coordText.Value := "Window not found"
return
}
}
; Get mouse position
CoordMode("Mouse", "Screen")
MouseGetPos(&mouseX, &mouseY)
; Get client area
client := GetClientArea(hwnd)
if (!client) {
coordText.Value := "Could not get client area"
return
}
; Calculate phone coordinates
phoneX := mouseX - client.x
phoneY := mouseY - client.y
; Check if within phone bounds
if (phoneX >= 0 && phoneX < PHONE_WIDTH && phoneY >= 0 && phoneY < PHONE_HEIGHT) {
coordText.Value := "X: " . phoneX . " Y: " . phoneY
} else {
coordText.Value := "Outside phone area"
}
}
; Timer to update coordinates
SetTimer(UpdateCoordinates, 100)
; Main
LogMessage("Starting interactive test helper")
; Start designer if not running
if (!FindDesignerWindow()) {
LogMessage("Starting designer...")
StartDesigner()
Sleep(3000)
}
CreateStatusGui()
RefreshStatus()
; Keep running
return

View File

@@ -0,0 +1,77 @@
; Mosis Designer Manual Click Test
; AutoHotkey v2
; Tests clicking on specific app icons with an already-running designer
#Requires AutoHotkey v2.0
#Include "lib\utils.ahk"
; Wait for designer to be running
LogMessage("Looking for designer window...")
LogMessage("Please start the designer manually if not running:")
LogMessage(" cd designer && build\\Debug\\mosis-designer.exe ..\\src\\main\\assets\\apps\\home\\home.rml")
hwnd := WaitForDesignerWindow(30000) ; 30 second timeout
if (!hwnd) {
MsgBox("Designer window not found after 30 seconds. Exiting.")
ExitApp()
}
LogMessage("Found designer window: " . hwnd)
; Get and display client area info
client := GetClientArea(hwnd)
if (client) {
LogMessage("Client area: x=" . client.x . " y=" . client.y . " w=" . client.w . " h=" . client.h)
}
; Interactive test loop
MsgBox("Designer found! Click OK to start click tests.`n`nWe will click on the Phone app icon.")
; Test 1: Click Phone app
LogMessage("Test 1: Clicking Phone app at (67, 120)")
ClickPhone(67, 120, hwnd)
Sleep(500)
result := MsgBox("Did the screen change to the Dialer?", "Verify", "YesNo")
if (result = "Yes") {
LogMessage("Test 1 PASSED: Phone app click worked")
} else {
LogMessage("Test 1 FAILED: Phone app click did not work")
}
; Give user time to see result
Sleep(1000)
; Test 2: Click Messages dock
MsgBox("Now testing Messages dock item. Click OK to continue.")
LogMessage("Test 2: Clicking Messages dock at (202, 920)")
ClickPhone(202, 920, hwnd)
Sleep(500)
result := MsgBox("Did the screen change to Messages?", "Verify", "YesNo")
if (result = "Yes") {
LogMessage("Test 2 PASSED: Messages dock click worked")
} else {
LogMessage("Test 2 FAILED: Messages dock click did not work")
}
Sleep(1000)
; Test 3: Different coordinates for Messages (in case dock is higher)
MsgBox("Testing alternative dock position. Click OK to continue.")
LogMessage("Test 3: Clicking Messages at alternative position (202, 880)")
ClickPhone(202, 880, hwnd)
Sleep(500)
result := MsgBox("Did anything happen?", "Verify", "YesNo")
if (result = "Yes") {
LogMessage("Test 3: Alternative position worked better")
} else {
LogMessage("Test 3: Alternative position also failed")
}
; Summary
MsgBox("Tests complete! Check test_run.log for results.`n`nPath: " . A_ScriptDir . "\test_run.log")
ExitApp()

View File

@@ -0,0 +1,194 @@
; Mosis Designer Navigation Test
; AutoHotkey v2
; Tests: Click on app icons and verify navigation occurs
#Requires AutoHotkey v2.0
#Include "lib\utils.ahk"
; Test configuration
testsPassed := 0
testsFailed := 0
; Main test function
RunNavigationTests() {
global testsPassed, testsFailed
LogMessage("========================================")
LogMessage("Starting Navigation Tests")
LogMessage("========================================")
; Clean up any previous runs
KillDesigner()
ClearLogs()
; Start the designer
pid := StartDesigner()
if (!pid) {
LogMessage("FATAL: Could not start designer")
return false
}
; Wait for window to appear
LogMessage("Waiting for designer window...")
hwnd := WaitForDesignerWindow()
if (!hwnd) {
LogMessage("FATAL: Designer window did not appear")
KillDesigner()
return false
}
LogMessage("Designer window found: " . hwnd)
; Wait for document to load
Sleep(2000) ; Give time for initial document load
; Check log for successful load
if (!WaitForLog("Document loaded successfully", 5000)) {
LogMessage("WARNING: Did not detect document load confirmation")
}
; Run individual tests
TestClickDialer(hwnd)
TestClickMessages(hwnd)
TestClickContacts(hwnd)
TestClickSettings(hwnd)
; Clean up
LogMessage("Closing designer...")
CloseDesigner(hwnd)
Sleep(500)
; Report results
LogMessage("========================================")
LogMessage("Navigation Tests Complete")
LogMessage("Passed: " . testsPassed)
LogMessage("Failed: " . testsFailed)
LogMessage("========================================")
return (testsFailed = 0)
}
; Test: Click on Phone/Dialer app
TestClickDialer(hwnd) {
global testsPassed, testsFailed
LogMessage("--- Test: Click Dialer ---")
; First go home to ensure clean state
; (In future, could send Home key)
Sleep(500)
; Click the phone app
if (!ClickApp("phone", hwnd)) {
testsFailed++
return
}
Sleep(1000)
; Check log for navigation
; Note: The log might say "dialer" because that's the screen name
result := CheckLogFor("Navigated to: dialer")
if (result) {
LogMessage("Navigation confirmed: " . result)
testsPassed++
} else {
; Also check if screen was loaded
result := CheckLogFor("Loaded screen: apps/dialer")
if (result) {
LogMessage("Screen load confirmed: " . result)
testsPassed++
} else {
LogMessage("Navigation not detected in log")
testsFailed++
}
}
}
; Test: Click on Messages app
TestClickMessages(hwnd) {
global testsPassed, testsFailed
LogMessage("--- Test: Click Messages ---")
; We're likely on dialer now, need to go back first
; For now, just click messages dock item
Sleep(500)
if (!ClickDock("messages", hwnd)) {
testsFailed++
return
}
Sleep(1000)
result := CheckLogFor("Loaded screen: apps/messages")
if (result) {
LogMessage("Screen load confirmed: " . result)
testsPassed++
} else {
LogMessage("Navigation not detected in log")
testsFailed++
}
}
; Test: Click on Contacts app
TestClickContacts(hwnd) {
global testsPassed, testsFailed
LogMessage("--- Test: Click Contacts ---")
Sleep(500)
if (!ClickDock("contacts", hwnd)) {
testsFailed++
return
}
Sleep(1000)
result := CheckLogFor("Loaded screen: apps/contacts")
if (result) {
LogMessage("Screen load confirmed: " . result)
testsPassed++
} else {
LogMessage("Navigation not detected in log")
testsFailed++
}
}
; Test: Click on Settings app
TestClickSettings(hwnd) {
global testsPassed, testsFailed
LogMessage("--- Test: Click Settings ---")
Sleep(500)
; Settings is on the main grid, not dock
; But we're on contacts now, so we need to navigate
; For this test, we'll just verify the click mechanics work
if (!ClickApp("settings", hwnd)) {
; This will likely fail since we're not on home screen
; But let's see what happens
LogMessage("Click attempted (may fail if not on home)")
testsFailed++
return
}
Sleep(1000)
result := CheckLogFor("Loaded screen: apps/settings")
if (result) {
LogMessage("Screen load confirmed: " . result)
testsPassed++
} else {
LogMessage("Navigation not detected (expected if not on home screen)")
testsFailed++
}
}
; Run the tests
RunNavigationTests()
; Keep script alive briefly for logging
Sleep(1000)
ExitApp()

View File

@@ -0,0 +1,124 @@
; Visual click test - verifies clicks by window interaction
#Requires AutoHotkey v2.0
; Configuration
DESIGNER_EXE := A_ScriptDir . "\..\build\Debug\mosis-designer.exe"
HOME_RML := A_ScriptDir . "\..\..\src\main\assets\apps\home\home.rml"
WINDOW_TITLE := "Mosis Designer"
resultFile := A_ScriptDir . "\visual_test_result.txt"
if (FileExist(resultFile))
FileDelete(resultFile)
Log(msg) {
global resultFile
FileAppend(msg . "`n", resultFile, "UTF-8")
}
Log("=== Visual Click Test ===")
Log("Time: " . FormatTime(, "yyyy-MM-dd HH:mm:ss"))
; Check if designer is running, if not start it
hwnd := WinExist(WINDOW_TITLE)
if (!hwnd) {
Log("Starting designer...")
Run(DESIGNER_EXE . ' "' . HOME_RML . '"', A_ScriptDir)
; Wait for window
startTime := A_TickCount
while (A_TickCount - startTime < 15000) {
hwnd := WinExist(WINDOW_TITLE)
if (hwnd)
break
Sleep(200)
}
if (!hwnd) {
Log("ERROR: Window did not appear")
ExitApp(1)
}
Log("Designer started")
Sleep(3000) ; Wait for full initialization
} else {
Log("Using existing designer window")
}
Log("Window HWND: " . hwnd)
; Get window info
WinGetPos(&winX, &winY, &winW, &winH, hwnd)
Log("Window: " . winW . "x" . winH . " at (" . winX . "," . winY . ")")
; Calculate scale and client area
scale := (winW / 556 + winH / 1000) / 2
titleBar := Round(31 * scale)
border := Round(8 * scale)
clientX := winX + border
clientY := winY + titleBar
Log("Scale: " . scale . ", Client offset: (" . border . "," . titleBar . ")")
; Activate window
WinActivate(hwnd)
WinWaitActive(hwnd, , 3)
Sleep(500)
Log("")
Log("=== Click Sequence ===")
; Function to click
DoClick(phoneX, phoneY, name) {
global clientX, clientY, scale, hwnd
screenX := Round(clientX + phoneX * scale)
screenY := Round(clientY + phoneY * scale)
Log("Clicking " . name . " at (" . phoneX . "," . phoneY . ") -> screen(" . screenX . "," . screenY . ")")
; Make sure window is active
WinActivate(hwnd)
Sleep(100)
; Move mouse first, then click
MouseMove(screenX, screenY)
Sleep(50)
Click()
Sleep(100)
Log("Click sent")
}
; Test sequence:
; 1. Click on Phone app (should navigate to dialer)
Log("")
Log("Step 1: Click Phone app icon")
DoClick(67, 120, "Phone")
Sleep(2000)
; 2. Click somewhere to go back (simulate back button or click home dock)
Log("")
Log("Step 2: Wait and observe...")
Sleep(1000)
; 3. Click on Messages dock
Log("")
Log("Step 3: Click Messages dock")
DoClick(202, 920, "Messages Dock")
Sleep(2000)
; 4. Click on Contacts dock
Log("")
Log("Step 4: Click Contacts dock")
DoClick(337, 920, "Contacts Dock")
Sleep(2000)
Log("")
Log("=== Test Sequence Complete ===")
Log("Please verify visually that navigation occurred:")
Log("- After Phone click: Should show Dialer screen")
Log("- After Messages click: Should show Messages screen")
Log("- After Contacts click: Should show Contacts screen")
Log("")
Log("Leaving designer running for manual inspection.")
Log("Close it manually when done.")
ExitApp(0)

12
designer/vcpkg.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "mosis-designer",
"version-string": "0.1.0",
"description": "Desktop designer and testing tool for Mosis virtual smartphone UI",
"dependencies": [
"glfw3",
"freetype",
"lua",
"libpng",
"nlohmann-json"
]
}