work in progress
This commit is contained in:
104
designer/CMakeLists.txt
Normal file
104
designer/CMakeLists.txt
Normal 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
|
||||
)
|
||||
137
designer/glad/include/KHR/khrplatform.h
Normal file
137
designer/glad/include/KHR/khrplatform.h
Normal 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_ */
|
||||
623
designer/glad/include/glad/gl.h
Normal file
623
designer/glad/include/glad/gl.h
Normal 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
322
designer/glad/src/gl.c
Normal 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);
|
||||
}
|
||||
276
designer/src/data_models.cpp
Normal file
276
designer/src/data_models.cpp
Normal 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;
|
||||
}
|
||||
99
designer/src/data_models.h
Normal file
99
designer/src/data_models.h
Normal 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);
|
||||
89
designer/src/desktop_platform.cpp
Normal file
89
designer/src/desktop_platform.cpp
Normal 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
|
||||
50
designer/src/desktop_platform.h
Normal file
50
designer/src/desktop_platform.h
Normal 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
|
||||
93
designer/src/hot_reload.cpp
Normal file
93
designer/src/hot_reload.cpp
Normal 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
44
designer/src/hot_reload.h
Normal 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
|
||||
330
designer/src/kernel_impl.cpp
Normal file
330
designer/src/kernel_impl.cpp
Normal 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
|
||||
57
designer/src/kernel_impl.h
Normal file
57
designer/src/kernel_impl.h
Normal 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
22
designer/src/log.h
Normal 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
379
designer/src/main.cpp
Normal 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);
|
||||
}
|
||||
131
designer/src/testing/action_player.cpp
Normal file
131
designer/src/testing/action_player.cpp
Normal 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
|
||||
60
designer/src/testing/action_player.h
Normal file
60
designer/src/testing/action_player.h
Normal 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
|
||||
191
designer/src/testing/action_recorder.cpp
Normal file
191
designer/src/testing/action_recorder.cpp
Normal 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
|
||||
69
designer/src/testing/action_recorder.h
Normal file
69
designer/src/testing/action_recorder.h
Normal 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
|
||||
183
designer/src/testing/ui_inspector.cpp
Normal file
183
designer/src/testing/ui_inspector.cpp
Normal 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
|
||||
53
designer/src/testing/ui_inspector.h
Normal file
53
designer/src/testing/ui_inspector.h
Normal 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
|
||||
244
designer/src/testing/visual_capture.cpp
Normal file
244
designer/src/testing/visual_capture.cpp
Normal 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
|
||||
51
designer/src/testing/visual_capture.h
Normal file
51
designer/src/testing/visual_capture.h
Normal 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
144
designer/test/README.md
Normal 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\)
|
||||
102
designer/test/click_test.ahk
Normal file
102
designer/test/click_test.ahk
Normal 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
45
designer/test/config.ahk
Normal 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
124
designer/test/diagnose.ahk
Normal 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()
|
||||
149
designer/test/full_click_test.ahk
Normal file
149
designer/test/full_click_test.ahk
Normal 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
298
designer/test/lib/utils.ahk
Normal 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
|
||||
}
|
||||
}
|
||||
65
designer/test/run_tests.ahk
Normal file
65
designer/test/run_tests.ahk
Normal 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()
|
||||
21
designer/test/simple_test.ahk
Normal file
21
designer/test/simple_test.ahk
Normal 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()
|
||||
110
designer/test/test_interactive.ahk
Normal file
110
designer/test/test_interactive.ahk
Normal 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
|
||||
77
designer/test/test_manual_clicks.ahk
Normal file
77
designer/test/test_manual_clicks.ahk
Normal 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()
|
||||
194
designer/test/test_navigation.ahk
Normal file
194
designer/test/test_navigation.ahk
Normal 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()
|
||||
124
designer/test/visual_click_test.ahk
Normal file
124
designer/test/visual_click_test.ahk
Normal 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
12
designer/vcpkg.json
Normal 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"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user